feat: pizdec
This commit is contained in:
2
.gitignore
vendored
Normal file → Executable file
2
.gitignore
vendored
Normal file → Executable file
@@ -1,2 +1,4 @@
|
||||
/.DS_Store
|
||||
/build
|
||||
.idea
|
||||
bin
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
A0EDDFD9DCFA42B28E98A596AFD8237AB506F0D6CE2E10D64BEA80C21CB3949A
|
||||
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/.gitignore
generated
vendored
Normal file → Executable file
0
.idea/compiler.xml
generated
Normal file → Executable file
0
.idea/compiler.xml
generated
Normal file → Executable file
0
.idea/easycode.ignore
generated
Normal file → Executable file
0
.idea/easycode.ignore
generated
Normal file → Executable file
0
.idea/gradle.xml
generated
Normal file → Executable file
0
.idea/gradle.xml
generated
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/misc.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
0
.idea/modules.xml
generated
Normal file → Executable file
1
.idea/modules/AdminMode.main.iml
generated
Normal file → Executable file
1
.idea/modules/AdminMode.main.iml
generated
Normal file → Executable file
@@ -4,7 +4,6 @@
|
||||
<facet type="minecraft" name="Minecraft">
|
||||
<configuration>
|
||||
<autoDetectTypes>
|
||||
<platformType>PAPER</platformType>
|
||||
<platformType>ADVENTURE</platformType>
|
||||
</autoDetectTypes>
|
||||
<projectReimportVersion>1</projectReimportVersion>
|
||||
|
||||
6
.idea/vcs.xml
generated
Executable file
6
.idea/vcs.xml
generated
Executable 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
234
LUCKPERMS_SETUP.md
Normal 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).
|
||||
149
MODERATION_PLUGIN_CONTRACT.md
Normal file
149
MODERATION_PLUGIN_CONTRACT.md
Normal 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
68
README.md
Normal 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
|
||||
|
||||
## Лицензия
|
||||
|
||||
Эксклюзивно для сервера Шлакоблок
|
||||
54
bin/main/config.yml
Normal file → Executable file
54
bin/main/config.yml
Normal file → Executable file
@@ -13,15 +13,23 @@ admin_mode:
|
||||
# Группы которые выдаются при активации режима
|
||||
# Можно указать несколько групп через запятую
|
||||
groups:
|
||||
- "moder_adm"
|
||||
- "soft_moder_adm"
|
||||
profile_groups:
|
||||
soft_moder:
|
||||
- "soft_moder_adm"
|
||||
hard_moder:
|
||||
- "hard_moder_adm"
|
||||
|
||||
# Удалять ли группы при деактивации (если false - группы останутся)
|
||||
remove_on_disable: true
|
||||
|
||||
dialogs:
|
||||
enabled: true
|
||||
|
||||
# Настройки игрового режима
|
||||
gamemode:
|
||||
# Принудительно ставить спектатор (если false - креатив)
|
||||
force_spectator: true
|
||||
# Принудительно ставить спектатор (если false - survival)
|
||||
force_spectator: false
|
||||
|
||||
# Мобы игнорируют игрока в админ моде
|
||||
mobs_ignore_player: true
|
||||
@@ -41,14 +49,15 @@ admin_mode:
|
||||
# Разрешенные режимы игры для переключения в админ моде
|
||||
# Если список пустой - можно переключаться на любые режимы
|
||||
# Доступные: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
|
||||
# ВАЖНО: CREATIVE убран - модеры не должны иметь доступ к креативу!
|
||||
allowed_gamemodes:
|
||||
- "CREATIVE"
|
||||
- "SURVIVAL"
|
||||
- "SPECTATOR"
|
||||
|
||||
# Ограничения в режиме администратора
|
||||
restrictions:
|
||||
# Запретить выбрасывание предметов
|
||||
prevent_item_drop: true
|
||||
prevent_item_drop: false
|
||||
|
||||
# Запретить поднятие предметов
|
||||
prevent_item_pickup: false
|
||||
@@ -57,7 +66,7 @@ admin_mode:
|
||||
prevent_container_transfer: true
|
||||
|
||||
# Запретить использование предметов (кроме открытия контейнеров)
|
||||
prevent_item_use: true
|
||||
prevent_item_use: false
|
||||
|
||||
# Разрешить управление своим инвентарем
|
||||
allow_own_inventory: true
|
||||
@@ -89,13 +98,9 @@ admin_mode:
|
||||
# Если список пустой - можно ставить любые блоки
|
||||
# Если список заполнен - можно ставить только эти блоки
|
||||
allowed_place_blocks:
|
||||
- "BARRIER"
|
||||
- "LIGHT"
|
||||
- "STRUCTURE_VOID"
|
||||
- "STRUCTURE_BLOCK"
|
||||
|
||||
# Выдавать пресет блоков при входе в админ мод
|
||||
give_preset_blocks: true
|
||||
give_preset_blocks: false
|
||||
|
||||
# Пресет блоков (формат: МАТЕРИАЛ:КОЛИЧЕСТВО)
|
||||
preset_blocks:
|
||||
@@ -117,3 +122,30 @@ admin_mode:
|
||||
item_use_denied: "✖ В режиме администратора нельзя использовать предметы!"
|
||||
block_place_denied: "✖ Вы можете ставить только разрешенные блоки!"
|
||||
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
17
bin/main/plugin.yml
Normal file → Executable file
@@ -1,21 +1,34 @@
|
||||
name: AdminMode
|
||||
version: '1.1.0'
|
||||
version: '1.21.11'
|
||||
main: org.blz.adminmode.AdminMode
|
||||
api-version: '1.21'
|
||||
author: BelzeBool, Kilka_v_HJIebe
|
||||
description: Плагин режима администратора с сохранением состояния игрока
|
||||
softdepend: [LuckPerms]
|
||||
depend: [LuckPerms]
|
||||
softdepend: [CoreProtect]
|
||||
|
||||
commands:
|
||||
adminmode:
|
||||
description: Режим администратора с сохранением состояния
|
||||
usage: /<command>
|
||||
aliases: [adm, am]
|
||||
admfix:
|
||||
description: Отключить все способности у игрока (полет, бессмертие)
|
||||
usage: /<command> <игрок>
|
||||
|
||||
permissions:
|
||||
adminmode.use:
|
||||
description: Разрешает использовать режим администратора
|
||||
default: op
|
||||
adminmode.profile.soft:
|
||||
description: Разрешает использовать профиль soft_moder
|
||||
default: op
|
||||
adminmode.profile.hard:
|
||||
description: Разрешает использовать профиль hard_moder
|
||||
default: op
|
||||
adminmode.reload:
|
||||
description: Разрешает перезагружать конфигурацию AdminMode
|
||||
default: op
|
||||
adminmode.admin:
|
||||
description: Разрешает использовать сильные действия и админ команды (disable, admfix)
|
||||
default: op
|
||||
|
||||
13
build.gradle
Normal file → Executable file
13
build.gradle
Normal file → Executable file
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = 'org.blz'
|
||||
version = '1.21.11'
|
||||
version = '1.3+26.1.2'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -14,23 +14,24 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT'
|
||||
compileOnly 'net.luckperms:api:5.4'
|
||||
compileOnly 'com.google.code.gson:gson:2.10.1'
|
||||
compileOnly 'io.papermc.paper:paper-api:26.1.2.build.74-stable'
|
||||
compileOnly 'net.luckperms:api:5.5'
|
||||
compileOnly 'com.google.code.gson:gson:2.13.2'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs.addAll(['-Xlint:deprecation', '-Xlint:unchecked'])
|
||||
}
|
||||
|
||||
processResources {
|
||||
def props = [version: version]
|
||||
inputs.properties props
|
||||
filteringCharset 'UTF-8'
|
||||
filteringCharset = 'UTF-8'
|
||||
filesMatching('plugin.yml') {
|
||||
expand props
|
||||
}
|
||||
|
||||
0
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file → Executable file
0
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file → Executable file
0
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file → Executable file
0
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file → Executable file
0
gradlew.bat
vendored
Normal file → Executable file
0
gradlew.bat
vendored
Normal file → Executable file
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/main/.DS_Store
vendored
Normal file
BIN
src/main/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/main/java/.DS_Store
vendored
Normal file
BIN
src/main/java/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/main/java/org/.DS_Store
vendored
Normal file
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
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
34
src/main/java/org/blz/adminmode/AdminMode.java
Normal file → Executable file
@@ -3,6 +3,8 @@ package org.blz.adminmode;
|
||||
import org.blz.adminmode.commands.AdminModeCommand;
|
||||
import org.blz.adminmode.config.ConfigManager;
|
||||
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.LuckPermsIntegration;
|
||||
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.PlayerDeathListener;
|
||||
import org.blz.adminmode.listeners.PlayerSessionListener;
|
||||
import org.blz.adminmode.moderation.ModeratorAccessManager;
|
||||
import org.blz.adminmode.moderation.ModeManager;
|
||||
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.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@@ -22,6 +28,9 @@ public class AdminMode extends JavaPlugin {
|
||||
private ModeManager modeManager;
|
||||
private AdminModeCommand adminModeCommand;
|
||||
private AdminDialogService dialogService;
|
||||
private org.blz.adminmode.managers.BelzeBoolManager belzeBoolManager;
|
||||
private ModeratorAccessManager moderatorAccessManager;
|
||||
private PlayerFreezeManager freezeManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -30,15 +39,24 @@ public class AdminMode extends JavaPlugin {
|
||||
|
||||
configManager = new ConfigManager(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);
|
||||
CoreProtectIntegration coreProtectIntegration = new CoreProtectIntegration(this, configManager);
|
||||
ModeSessionLogger sessionLogger = new ModeSessionLogger(this, configManager);
|
||||
modeManager = new ModeManager(this, configManager, luckPermsIntegration, sessionStorage, sessionLogger);
|
||||
adminModeCommand = new AdminModeCommand(configManager, modeManager, coreProtectIntegration);
|
||||
dialogService = new AdminDialogService(this, configManager, adminModeCommand);
|
||||
modeManager = new ModeManager(this, configManager, luckPermsIntegration, sessionStorage, sessionLogger, preferencesStore);
|
||||
adminModeCommand = new AdminModeCommand(configManager, modeManager, coreProtectIntegration, moderatorAccessManager);
|
||||
DialogPreferenceStore dialogPreferenceStore = new DialogPreferenceStore(this);
|
||||
MenuEmojiPreferenceStore emojiPreferenceStore = new MenuEmojiPreferenceStore(this);
|
||||
dialogService = new AdminDialogService(this, configManager, adminModeCommand, dialogPreferenceStore, emojiPreferenceStore, preferencesStore, warningStore);
|
||||
adminModeCommand.setDialogService(dialogService);
|
||||
adminModeCommand.setDialogPreferenceStore(dialogPreferenceStore);
|
||||
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),
|
||||
this);
|
||||
@@ -47,10 +65,13 @@ public class AdminMode extends JavaPlugin {
|
||||
this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerSessionListener(adminModeCommand, 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()) {
|
||||
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();
|
||||
|
||||
// Register Abilities
|
||||
new org.blz.adminmode.managers.BelzeBoolManager(this);
|
||||
belzeBoolManager = new org.blz.adminmode.managers.BelzeBoolManager(this);
|
||||
adminModeCommand.setBelzeBoolManager(belzeBoolManager);
|
||||
|
||||
getLogger().info("AdminMode успешно включен!");
|
||||
}
|
||||
|
||||
41
src/main/java/org/blz/adminmode/commands/AdminFixCommand.java
Normal file → Executable file
41
src/main/java/org/blz/adminmode/commands/AdminFixCommand.java
Normal file → Executable file
@@ -1,13 +1,19 @@
|
||||
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.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
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;
|
||||
|
||||
@@ -18,26 +24,45 @@ public class AdminFixCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("adminmode.admin")) {
|
||||
sender.sendMessage(ChatColor.RED + "У вас нет прав для использования этой команды!");
|
||||
sender.sendMessage(Component.text("У вас нет прав для использования этой команды!", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length < 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Использование: /admfix <игрок>");
|
||||
sender.sendMessage(Component.text("Использование: /admfix <игрок>", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
Player target = Bukkit.getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Игрок не найден!");
|
||||
sender.sendMessage(Component.text("Игрок не найден!", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
|
||||
adminModeCommand.forceDisableAbilities(target);
|
||||
if (!adminModeCommand.forceDisableAbilities(target)) {
|
||||
sender.sendMessage(Component.text("У игрока нет активных состояний AdminMode для сброса.", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + "✓ Все способности отключены для игрока " + target.getName());
|
||||
target.sendMessage(ChatColor.YELLOW + "⚠ Ваши способности были сброшены администратором");
|
||||
sender.sendMessage(Component.text("✓ Все способности отключены для игрока " + target.getName(), NamedTextColor.GREEN));
|
||||
target.sendMessage(Component.text("⚠ Ваши способности были сброшены администратором", NamedTextColor.YELLOW));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
501
src/main/java/org/blz/adminmode/commands/AdminModeCommand.java
Normal file → Executable file
501
src/main/java/org/blz/adminmode/commands/AdminModeCommand.java
Normal file → Executable file
@@ -5,38 +5,64 @@ import java.util.UUID;
|
||||
|
||||
import org.blz.adminmode.config.ConfigManager;
|
||||
import org.blz.adminmode.dialog.AdminDialogService;
|
||||
import org.blz.adminmode.dialog.DialogPreferenceStore;
|
||||
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.ModeratorProfile;
|
||||
import org.blz.adminmode.moderation.PlayerFreezeManager;
|
||||
import org.blz.adminmode.moderation.PlayerListAction;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
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 ModeManager modeManager;
|
||||
private final CoreProtectIntegration coreProtectIntegration;
|
||||
private final ModeratorAccessManager moderatorAccessManager;
|
||||
private BelzeBoolManager belzeBoolManager;
|
||||
private PlayerFreezeManager freezeManager;
|
||||
private AdminDialogService dialogService;
|
||||
private DialogPreferenceStore dialogPreferenceStore;
|
||||
|
||||
public AdminModeCommand(
|
||||
ConfigManager configManager,
|
||||
ModeManager modeManager,
|
||||
CoreProtectIntegration coreProtectIntegration) {
|
||||
CoreProtectIntegration coreProtectIntegration,
|
||||
ModeratorAccessManager moderatorAccessManager) {
|
||||
this.configManager = configManager;
|
||||
this.modeManager = modeManager;
|
||||
this.coreProtectIntegration = coreProtectIntegration;
|
||||
this.moderatorAccessManager = moderatorAccessManager;
|
||||
}
|
||||
|
||||
public void setDialogService(AdminDialogService 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
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
|
||||
@@ -54,16 +80,29 @@ public class AdminModeCommand implements CommandExecutor {
|
||||
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(configManager.getPrefixedMessage("§cИспользование: /adminmode disable <игрок>"));
|
||||
return true;
|
||||
}
|
||||
|
||||
forceDisableTarget(sender, args[1]);
|
||||
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)) {
|
||||
sender.sendMessage("Эта команда доступна только игрокам!");
|
||||
return true;
|
||||
@@ -75,59 +114,49 @@ public class AdminModeCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("dialog")) {
|
||||
if (!configManager.isDialogsEnabled()) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!player.hasPermission("adminmode.use")) {
|
||||
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("on")) {
|
||||
return handleEnableMode(player);
|
||||
}
|
||||
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("off")) {
|
||||
return handleDisableMode(player);
|
||||
}
|
||||
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("toggle")) {
|
||||
return handleHardToggle(player);
|
||||
}
|
||||
|
||||
if (!isInAdminMode(player.getUniqueId())) {
|
||||
return handleEnableMode(player);
|
||||
}
|
||||
|
||||
if (!openRelevantDialog(player)) {
|
||||
player.sendMessage(configManager.getPrefixedMessage("§eDialog-панель сейчас недоступна."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 0 && (args[0].equalsIgnoreCase("enable") || args[0].equalsIgnoreCase("profile"))) {
|
||||
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()));
|
||||
return true;
|
||||
}
|
||||
|
||||
openRelevantDialog(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean activateProfile(Player player, ModeratorProfile profile) {
|
||||
if (profile == null) {
|
||||
return openRelevantDialog(player);
|
||||
}
|
||||
|
||||
if (!profile.isAvailableFor(player)) {
|
||||
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modeManager.hasSession(player.getUniqueId())) {
|
||||
ModeratorProfile activeProfile = getActiveProfile(player.getUniqueId());
|
||||
if (activeProfile == profile) {
|
||||
return openRelevantDialog(player);
|
||||
}
|
||||
|
||||
player.sendMessage(configManager.getPrefixedMessage(
|
||||
"§eУ вас уже активен профиль §f" + activeProfile.getDisplayName() + "§e. Сначала выйдите из режима."));
|
||||
return false;
|
||||
"§aВы в режиме администратора. Используйте §f/adminmode dialog §aдля включения GUI-панели."));
|
||||
}
|
||||
|
||||
return modeManager.enterMode(player, profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean exitMode(Player player) {
|
||||
@@ -138,12 +167,8 @@ public class AdminModeCommand implements CommandExecutor {
|
||||
return modeManager.recoverSession(player);
|
||||
}
|
||||
|
||||
public List<ModeratorProfile> getAvailableProfiles(Player player) {
|
||||
return modeManager.getAvailableProfiles(player);
|
||||
}
|
||||
|
||||
public ModeratorProfile getActiveProfile(UUID playerId) {
|
||||
return modeManager.getProfile(playerId);
|
||||
public boolean resumeMode(Player player) {
|
||||
return modeManager.resumeMode(player);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!modeManager.hasSession(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modeManager.hasSession(player.getUniqueId())) return;
|
||||
modeManager.applyAdminSettings(player, flySpeed, walkSpeed, godMode);
|
||||
player.sendMessage(configManager.getPrefixedMessage("§aНастройки режима обновлены."));
|
||||
}
|
||||
|
||||
public boolean performPlayerListAction(Player moderator, PlayerListAction action, String targetName) {
|
||||
Player target = validateActiveTarget(moderator, targetName);
|
||||
if (target == null) {
|
||||
public boolean toggleVanish(Player player) {
|
||||
return modeManager.toggleVanish(player);
|
||||
}
|
||||
|
||||
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 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) {
|
||||
case SPECTATE -> modeManager.spectatePlayer(moderator, target);
|
||||
case TELEPORT -> modeManager.teleportToPlayer(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) {
|
||||
if (!validateHardActionPlayer(moderator)) {
|
||||
if (!modeManager.hasSession(moderator.getUniqueId())) {
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§cСначала войдите в режим модерации."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Player target = resolveOnlinePlayer(targetName, moderator);
|
||||
return target != null && modeManager.teleportPlayerHere(moderator, target);
|
||||
}
|
||||
@@ -195,76 +237,241 @@ public class AdminModeCommand implements CommandExecutor {
|
||||
sender.sendMessage(configManager.getPrefixedMessage(configManager.getMsgNoPermission()));
|
||||
return false;
|
||||
}
|
||||
|
||||
Player target = resolveOnlinePlayer(targetName, sender);
|
||||
if (target == null) {
|
||||
if (target == null) return false;
|
||||
if (!forceDisableAbilities(target)) {
|
||||
sender.sendMessage(configManager.getPrefixedMessage("§eУ игрока нет активных состояний AdminMode для сброса."));
|
||||
return false;
|
||||
}
|
||||
|
||||
forceDisableAbilities(target);
|
||||
sender.sendMessage(configManager.getPrefixedMessage("§aРежим и способности игрока §f" + target.getName() + "§a сброшены."));
|
||||
target.sendMessage(configManager.getPrefixedMessage("§eВаш режим модерации был принудительно отключен."));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean executeCPLookup(Player player, String targetPlayer, int radius, String time) {
|
||||
if (!validateHardActionPlayer(player)) {
|
||||
return false;
|
||||
}
|
||||
if (!modeManager.hasSession(player.getUniqueId())) return false;
|
||||
return coreProtectIntegration.lookup(player, targetPlayer, radius, time);
|
||||
}
|
||||
|
||||
public boolean executeCPRollback(Player player, int radius, String time, String targetPlayer) {
|
||||
if (!validateHardActionPlayer(player)) {
|
||||
return false;
|
||||
public boolean executeCPLookup(Player player, String targetPlayer, String action, String block, String exclude,
|
||||
String world, int radius, String time) {
|
||||
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) {
|
||||
if (!validateHardActionPlayer(player)) {
|
||||
return false;
|
||||
}
|
||||
if (!modeManager.hasSession(player.getUniqueId())) return false;
|
||||
return coreProtectIntegration.toggleInspector(player);
|
||||
}
|
||||
|
||||
public void forceDisableAbilities(Player player) {
|
||||
public boolean forceDisableAbilities(Player player) {
|
||||
boolean changed = false;
|
||||
|
||||
if (modeManager.hasSession(player.getUniqueId())) {
|
||||
modeManager.exitMode(player);
|
||||
return;
|
||||
changed = modeManager.exitMode(player);
|
||||
}
|
||||
|
||||
player.setInvulnerable(false);
|
||||
player.setFlying(false);
|
||||
player.setAllowFlight(false);
|
||||
player.setFlySpeed(0.1f);
|
||||
player.setWalkSpeed(0.2f);
|
||||
if (belzeBoolManager != null) {
|
||||
changed = belzeBoolManager.forceDisable(player) || changed;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean canUseHardActions(CommandSender sender) {
|
||||
return modeManager.canUseHardActions(sender);
|
||||
}
|
||||
|
||||
private boolean openRelevantDialog(Player player) {
|
||||
if (dialogService == null || !configManager.isDialogsEnabled()) {
|
||||
public boolean canManageModeratorAccess(Player actor, Player target) {
|
||||
return actor != null
|
||||
&& target != null
|
||||
&& actor.hasPermission("adminmode.chief")
|
||||
&& !actor.getUniqueId().equals(target.getUniqueId())
|
||||
&& target.hasPermission("adminmode.use");
|
||||
}
|
||||
|
||||
public boolean isModeratorRestricted(UUID playerId) {
|
||||
return moderatorAccessManager.isRestricted(playerId);
|
||||
}
|
||||
|
||||
public boolean restrictModerator(Player actor, OfflinePlayer target, String durationInput, String reason) {
|
||||
if (actor == null || target == null || !actor.hasPermission("adminmode.chief")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modeManager.hasSession(player.getUniqueId())) {
|
||||
return dialogService.openCurrentPanel(player);
|
||||
return applyRestriction(actor, target, durationInput, reason);
|
||||
}
|
||||
|
||||
return dialogService.openModeSelectDialog(player);
|
||||
}
|
||||
|
||||
private boolean validateHardActionPlayer(Player moderator) {
|
||||
if (!canUseHardActions(moderator)) {
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§cДля этого действия нужен активный профиль Hard Moder."));
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!modeManager.hasSession(moderator.getUniqueId())) {
|
||||
@@ -279,17 +486,83 @@ public class AdminModeCommand implements CommandExecutor {
|
||||
sender.sendMessage(configManager.getPrefixedMessage("§cУкажите ник игрока."));
|
||||
return null;
|
||||
}
|
||||
|
||||
Player target = Bukkit.getPlayerExact(targetName.trim());
|
||||
if (target == null) {
|
||||
target = Bukkit.getPlayer(targetName.trim());
|
||||
}
|
||||
|
||||
if (target == null) target = Bukkit.getPlayer(targetName.trim());
|
||||
if (target == null) {
|
||||
sender.sendMessage(configManager.getPrefixedMessage("§cИгрок не найден!"));
|
||||
return null;
|
||||
}
|
||||
|
||||
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
436
src/main/java/org/blz/adminmode/config/ConfigManager.java
Normal file → Executable file
@@ -1,11 +1,10 @@
|
||||
package org.blz.adminmode.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashSet;
|
||||
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.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@@ -17,20 +16,28 @@ public class ConfigManager {
|
||||
|
||||
private boolean enabled;
|
||||
private boolean useLuckPerms;
|
||||
private Map<ModeratorProfile, List<String>> luckPermsGroups;
|
||||
private List<String> adminLuckPermsGroups;
|
||||
private boolean removeGroupsOnDisable;
|
||||
private boolean dialogsEnabled;
|
||||
private String baseGroup;
|
||||
private String softGroup;
|
||||
private String hardGroup;
|
||||
private Material softPanelMaterial;
|
||||
private Material hardPanelMaterial;
|
||||
private String adminGroup;
|
||||
private Material panelMaterial;
|
||||
private Material cpInspectorMaterial;
|
||||
private boolean coreProtectEnabled;
|
||||
private int coreProtectDefaultRadius;
|
||||
private String coreProtectDefaultTime;
|
||||
private boolean sessionLoggingEnabled;
|
||||
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 preventItemPickup;
|
||||
@@ -46,6 +53,7 @@ public class ConfigManager {
|
||||
private boolean mobsIgnorePlayer;
|
||||
private boolean allowFlightInSurvival;
|
||||
private boolean godMode;
|
||||
private boolean chiefCreativeMode;
|
||||
private float customFlySpeed;
|
||||
private float customWalkSpeed;
|
||||
private List<String> allowedGameModes;
|
||||
@@ -63,8 +71,7 @@ public class ConfigManager {
|
||||
private String msgBlockPlaceDenied;
|
||||
private String msgReloaded;
|
||||
private String msgPrefix;
|
||||
private String msgEnterSoft;
|
||||
private String msgEnterHard;
|
||||
private String msgEnter;
|
||||
private String msgExitMode;
|
||||
private String msgCoreProtectUnavailable;
|
||||
private String msgSessionRecovered;
|
||||
@@ -79,17 +86,20 @@ public class ConfigManager {
|
||||
config = plugin.getConfig();
|
||||
|
||||
enabled = config.getBoolean("admin_mode.enabled", true);
|
||||
|
||||
useLuckPerms = config.getBoolean("admin_mode.luckperms.enabled", true);
|
||||
removeGroupsOnDisable = config.getBoolean("admin_mode.luckperms.remove_on_disable", true);
|
||||
dialogsEnabled = config.getBoolean("admin_mode.dialogs.enabled", true);
|
||||
|
||||
baseGroup = config.getString("groups.base", "moder");
|
||||
softGroup = config.getString("groups.soft", "soft_moder_adm");
|
||||
hardGroup = config.getString("groups.hard", "hard_moder_adm");
|
||||
adminGroup = config.getString("groups.admin", "admin_moder");
|
||||
|
||||
softPanelMaterial = readMaterial("items.soft_panel_material", Material.COMPASS);
|
||||
hardPanelMaterial = readMaterial("items.hard_panel_material", Material.NETHER_STAR);
|
||||
List<String> groups = config.getStringList("admin_mode.luckperms.groups");
|
||||
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);
|
||||
|
||||
coreProtectEnabled = config.getBoolean("coreprotect.enabled", true);
|
||||
@@ -98,42 +108,38 @@ public class ConfigManager {
|
||||
|
||||
sessionLoggingEnabled = config.getBoolean("logging.enabled", true);
|
||||
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");
|
||||
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);
|
||||
preventItemDrop = config.getBoolean("admin_mode.restrictions.prevent_item_drop", false);
|
||||
preventItemPickup = config.getBoolean("admin_mode.restrictions.prevent_item_pickup", false);
|
||||
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);
|
||||
allowedBlocks = config.getStringList("admin_mode.restrictions.allowed_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");
|
||||
|
||||
forceSpectator = config.getBoolean("admin_mode.gamemode.force_spectator", false);
|
||||
mobsIgnorePlayer = config.getBoolean("admin_mode.gamemode.mobs_ignore_player", true);
|
||||
allowFlightInSurvival = config.getBoolean("admin_mode.gamemode.allow_flight_in_survival", 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);
|
||||
customWalkSpeed = (float) config.getDouble("admin_mode.gamemode.custom_walk_speed", 0.2);
|
||||
allowedGameModes = config.getStringList("admin_mode.gamemode.allowed_gamemodes");
|
||||
@@ -143,8 +149,7 @@ public class ConfigManager {
|
||||
msgPermissionsGranted = config.getString("admin_mode.messages.permissions_granted",
|
||||
"⚠ Вам выданы временные права администратора!");
|
||||
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",
|
||||
"✓ Права администратора отозваны!");
|
||||
msgNoPermission = config.getString("admin_mode.messages.no_permission",
|
||||
@@ -160,241 +165,158 @@ public class ConfigManager {
|
||||
msgReloaded = config.getString("admin_mode.messages.reloaded", "✓ Конфигурация AdminMode перезагружена!");
|
||||
|
||||
msgPrefix = config.getString("messages.prefix", "§8[§bAdminMode§8] ");
|
||||
msgEnterSoft = config.getString("messages.enter_soft", "§aВошёл в режим §bSOFT MODER");
|
||||
msgEnterHard = config.getString("messages.enter_hard", "§aВошёл в режим §cHARD MODER");
|
||||
msgEnter = config.getString("messages.enter", "§aВошёл в режим §cADMIN MODE");
|
||||
msgExitMode = config.getString("messages.exit", "§7Режим деактивирован. Состояние восстановлено.");
|
||||
msgCoreProtectUnavailable = config.getString("messages.cp_unavailable",
|
||||
"§cCoreProtect не найден. Hard режим работает без CP.");
|
||||
"§cCoreProtect не найден или отключён.");
|
||||
msgSessionRecovered = config.getString("messages.session_recovered",
|
||||
"§eВаша активная сессия модерации была восстановлена и корректно завершена.");
|
||||
|
||||
validatePresetBlocks();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
// --- Getters ---
|
||||
|
||||
public boolean isUseLuckPerms() {
|
||||
return useLuckPerms;
|
||||
}
|
||||
|
||||
public List<String> getLuckPermsGroups() {
|
||||
return getLuckPermsGroups(ModeratorProfile.SOFT_MODER);
|
||||
}
|
||||
|
||||
public List<String> getLuckPermsGroups(ModeratorProfile profile) {
|
||||
return new ArrayList<>(luckPermsGroups.getOrDefault(profile, List.of()));
|
||||
}
|
||||
|
||||
public boolean isRemoveGroupsOnDisable() {
|
||||
return removeGroupsOnDisable;
|
||||
}
|
||||
|
||||
public boolean isDialogsEnabled() {
|
||||
return dialogsEnabled;
|
||||
}
|
||||
|
||||
public String getBaseGroup() {
|
||||
return baseGroup;
|
||||
}
|
||||
|
||||
public String getSoftGroup() {
|
||||
return softGroup;
|
||||
}
|
||||
|
||||
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 isEnabled() { return enabled; }
|
||||
public boolean isUseLuckPerms() { return useLuckPerms; }
|
||||
public List<String> getLuckPermsGroups() { return new ArrayList<>(adminLuckPermsGroups); }
|
||||
public boolean isRemoveGroupsOnDisable() { return removeGroupsOnDisable; }
|
||||
public boolean isDialogsEnabled() { return dialogsEnabled; }
|
||||
public String getBaseGroup() { return baseGroup; }
|
||||
public String getAdminGroup() { return adminGroup; }
|
||||
public Material getPanelMaterial() { return panelMaterial; }
|
||||
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 isModerationActionsAsConsole() { return moderationActionsAsConsole; }
|
||||
public String getWarnCommandTemplate() { return warnCommandTemplate; }
|
||||
public String getMuteCommandTemplate() { return muteCommandTemplate; }
|
||||
public String getUnmuteCommandTemplate() { return unmuteCommandTemplate; }
|
||||
public String getBanCommandTemplate() { return banCommandTemplate; }
|
||||
public String getKickCommandTemplate() { return kickCommandTemplate; }
|
||||
public List<String> getPunishmentDurationOptions() { return new ArrayList<>(punishmentDurationOptions); }
|
||||
public String getDefaultWarnDuration() { return defaultWarnDuration; }
|
||||
public String getDefaultMuteDuration() { return defaultMuteDuration; }
|
||||
public String getDefaultBanDuration() { return defaultBanDuration; }
|
||||
public boolean isPunishmentDurationAllowed(String value) {
|
||||
String normalized = PunishmentDuration.normalizeToken(value);
|
||||
return !normalized.isEmpty() && punishmentDurationOptions.contains(normalized);
|
||||
}
|
||||
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) {
|
||||
if (message == null || message.isEmpty()) {
|
||||
return msgPrefix;
|
||||
}
|
||||
if (message == null || message.isEmpty()) return msgPrefix;
|
||||
return msgPrefix + message;
|
||||
}
|
||||
|
||||
private Material readMaterial(String path, Material fallback) {
|
||||
String value = config.getString(path);
|
||||
if (value == null || value.isBlank()) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (value == null || value.isBlank()) return fallback;
|
||||
try {
|
||||
return Material.valueOf(value.trim().toUpperCase());
|
||||
} catch (IllegalArgumentException exception) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().warning("Неизвестный material в конфиге " + path + ": " + value);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
86
src/main/java/org/blz/adminmode/data/AdminModeDataManager.java
Normal file → Executable file
86
src/main/java/org/blz/adminmode/data/AdminModeDataManager.java
Normal file → Executable file
@@ -1,29 +1,23 @@
|
||||
package org.blz.adminmode.data;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.blz.adminmode.utils.ItemStackCodec;
|
||||
import org.blz.adminmode.utils.PotionEffectCodec;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
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.GsonBuilder;
|
||||
@@ -187,16 +181,7 @@ public class AdminModeDataManager {
|
||||
|
||||
private String serializeItemArray(ItemStack[] items) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeInt(items.length);
|
||||
|
||||
for (ItemStack item : items) {
|
||||
dataOutput.writeObject(item);
|
||||
}
|
||||
|
||||
dataOutput.close();
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
return ItemStackCodec.serializeItems(items);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
@@ -205,17 +190,7 @@ public class AdminModeDataManager {
|
||||
|
||||
private ItemStack[] deserializeItemArray(String data) {
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().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();
|
||||
}
|
||||
|
||||
dataInput.close();
|
||||
return items;
|
||||
return ItemStackCodec.deserializeItems(data);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new ItemStack[0];
|
||||
@@ -224,11 +199,7 @@ public class AdminModeDataManager {
|
||||
|
||||
private String serializeItem(ItemStack item) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeObject(item);
|
||||
dataOutput.close();
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
return ItemStackCodec.serializeItem(item);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
@@ -238,56 +209,23 @@ public class AdminModeDataManager {
|
||||
private ItemStack deserializeItem(String data) {
|
||||
try {
|
||||
if (data.isEmpty()) {
|
||||
return new ItemStack(Material.AIR);
|
||||
return ItemStack.empty();
|
||||
}
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
ItemStack item = (ItemStack) dataInput.readObject();
|
||||
dataInput.close();
|
||||
return item;
|
||||
return ItemStackCodec.deserializeItem(data);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new ItemStack(Material.AIR);
|
||||
return ItemStack.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private String serializePotionEffects(Collection<PotionEffect> effects) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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();
|
||||
return PotionEffectCodec.serialize(effects);
|
||||
}
|
||||
|
||||
private Collection<PotionEffect> deserializePotionEffects(String data) {
|
||||
Collection<PotionEffect> effects = new ArrayList<>();
|
||||
if (data.isEmpty()) return effects;
|
||||
|
||||
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;
|
||||
return PotionEffectCodec.deserialize(data,
|
||||
encodedEffect -> plugin.getLogger().warning(
|
||||
"Ошибка десериализации эффекта: " + encodedEffect));
|
||||
}
|
||||
|
||||
public static class PlayerStateData {
|
||||
|
||||
1473
src/main/java/org/blz/adminmode/dialog/AdminDialogService.java
Normal file → Executable file
1473
src/main/java/org/blz/adminmode/dialog/AdminDialogService.java
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
63
src/main/java/org/blz/adminmode/dialog/DialogPreferenceStore.java
Executable file
63
src/main/java/org/blz/adminmode/dialog/DialogPreferenceStore.java
Executable 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) {}
|
||||
}
|
||||
}
|
||||
72
src/main/java/org/blz/adminmode/dialog/MenuEmojiPreferenceStore.java
Executable file
72
src/main/java/org/blz/adminmode/dialog/MenuEmojiPreferenceStore.java
Executable 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/main/java/org/blz/adminmode/integrations/CoreProtectIntegration.java
Normal file → Executable file
113
src/main/java/org/blz/adminmode/integrations/CoreProtectIntegration.java
Normal file → Executable file
@@ -29,31 +29,67 @@ public class CoreProtectIntegration {
|
||||
}
|
||||
|
||||
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()) {
|
||||
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder command = new StringBuilder("co lookup");
|
||||
appendPlayerFilter(command, targetPlayer);
|
||||
command.append(" r:").append(normalizeRadius(radius));
|
||||
command.append(" t:").append(normalizeTime(time));
|
||||
appendCommonFilters(command, targetPlayer, action, block, exclude, world, radius, time);
|
||||
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()) {
|
||||
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgCoreProtectUnavailable()));
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder command = new StringBuilder("co rollback");
|
||||
appendPlayerFilter(command, targetPlayer);
|
||||
command.append(" r:").append(normalizeRadius(radius));
|
||||
command.append(" t:").append(normalizeTime(time));
|
||||
appendCommonFilters(command, targetPlayer, action, block, exclude, world, radius, time);
|
||||
appendExtraFilters(command, extraFilters);
|
||||
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) {
|
||||
String sanitized = sanitizePlayerName(targetPlayer);
|
||||
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) {
|
||||
return Math.max(1, Math.min(100, radius));
|
||||
}
|
||||
@@ -89,4 +177,15 @@ public class CoreProtectIntegration {
|
||||
}
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
65
src/main/java/org/blz/adminmode/integrations/LuckPermsIntegration.java
Normal file → Executable file
65
src/main/java/org/blz/adminmode/integrations/LuckPermsIntegration.java
Normal file → Executable file
@@ -6,7 +6,6 @@ import net.luckperms.api.model.user.User;
|
||||
import net.luckperms.api.node.Node;
|
||||
import org.blz.adminmode.config.ConfigManager;
|
||||
import org.blz.adminmode.moderation.ModeSession;
|
||||
import org.blz.adminmode.moderation.ModeratorProfile;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
@@ -21,64 +20,54 @@ public class LuckPermsIntegration {
|
||||
}
|
||||
|
||||
public String resolveCurrentGroup(Player player) {
|
||||
if (!configManager.isUseLuckPerms()) {
|
||||
return configManager.getBaseGroup();
|
||||
}
|
||||
|
||||
if (!configManager.isUseLuckPerms()) return configManager.getBaseGroup();
|
||||
try {
|
||||
User user = resolveUser(player);
|
||||
if (user == null) {
|
||||
return configManager.getBaseGroup();
|
||||
}
|
||||
return user.getPrimaryGroup();
|
||||
} catch (IllegalStateException exception) {
|
||||
return user != null ? user.getPrimaryGroup() : configManager.getBaseGroup();
|
||||
} catch (RuntimeException e) {
|
||||
plugin.getLogger().warning("Не удалось определить текущую группу LuckPerms для " + player.getName() + ": " + e.getMessage());
|
||||
return configManager.getBaseGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public void enterMode(Player player, ModeratorProfile profile) {
|
||||
public void enterMode(Player player) {
|
||||
if (!configManager.isUseLuckPerms()) {
|
||||
player.setOp(true);
|
||||
plugin.getLogger().warning("LuckPerms-интеграция отключена в конфиге. Временные группы для " + player.getName() + " не применены.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
User user = resolveUser(player);
|
||||
if (user == null) {
|
||||
return;
|
||||
if (user == null) return;
|
||||
for (String group : configManager.getLuckPermsGroups()) {
|
||||
user.data().add(groupNode(group));
|
||||
}
|
||||
|
||||
removeKnownGroups(user);
|
||||
user.data().add(groupNode(configManager.getActiveGroup(profile)));
|
||||
saveUser(user);
|
||||
} catch (IllegalStateException exception) {
|
||||
player.setOp(true);
|
||||
plugin.getLogger().warning("LuckPerms не найден, используется OP для " + player.getName());
|
||||
} catch (RuntimeException e) {
|
||||
plugin.getLogger().warning("Не удалось выдать временные группы LuckPerms для " + player.getName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void exitMode(Player player, ModeSession session) {
|
||||
if (!configManager.isUseLuckPerms()) {
|
||||
player.setOp(false);
|
||||
plugin.getLogger().warning("LuckPerms-интеграция отключена в конфиге. Группы для " + player.getName() + " не менялись.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
User user = resolveUser(player);
|
||||
if (user == null) {
|
||||
return;
|
||||
if (user == null) return;
|
||||
if (configManager.isRemoveGroupsOnDisable()) {
|
||||
removeTemporaryGroups(user);
|
||||
}
|
||||
saveUser(user);
|
||||
} catch (RuntimeException e) {
|
||||
plugin.getLogger().warning("Не удалось восстановить группы LuckPerms для " + player.getName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
removeKnownGroups(user);
|
||||
String restoreGroup = session.getPreviousGroup();
|
||||
if (restoreGroup == null || restoreGroup.isBlank() || "op".equalsIgnoreCase(restoreGroup)) {
|
||||
restoreGroup = configManager.getBaseGroup();
|
||||
}
|
||||
user.data().add(groupNode(restoreGroup));
|
||||
saveUser(user);
|
||||
} catch (IllegalStateException exception) {
|
||||
player.setOp(false);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return Node.builder("group." + group).build();
|
||||
}
|
||||
|
||||
13
src/main/java/org/blz/adminmode/listeners/AdminModeListener.java
Normal file → Executable file
13
src/main/java/org/blz/adminmode/listeners/AdminModeListener.java
Normal file → Executable file
@@ -97,20 +97,13 @@ public class AdminModeListener implements Listener {
|
||||
|
||||
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
|
||||
|
||||
if ((e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK)
|
||||
&& e.getItem() != null
|
||||
&& modeManager.isPanelItem(e.getItem())) {
|
||||
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getClickedBlock() != null
|
||||
&& e.getClickedBlock().getType() == Material.ENDER_CHEST) {
|
||||
e.setCancelled(true);
|
||||
|
||||
String panelType = modeManager.getPanelType(e.getItem());
|
||||
if (ModeManager.PANEL_CP_INSPECTOR.equals(panelType)) {
|
||||
adminModeCommand.toggleInspector(player);
|
||||
player.sendMessage(configManager.getPrefixedMessage("§cВ admin mode нельзя взаимодействовать с эндер-сундуком."));
|
||||
return;
|
||||
}
|
||||
|
||||
dialogService.openCurrentPanel(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configManager.isPreventItemUse()) return;
|
||||
|
||||
|
||||
49
src/main/java/org/blz/adminmode/listeners/GameModeChangeListener.java
Normal file → Executable file
49
src/main/java/org/blz/adminmode/listeners/GameModeChangeListener.java
Normal file → Executable file
@@ -4,8 +4,6 @@ import java.util.List;
|
||||
|
||||
import org.blz.adminmode.commands.AdminModeCommand;
|
||||
import org.blz.adminmode.config.ConfigManager;
|
||||
import org.blz.adminmode.moderation.ModeratorProfile;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@@ -26,29 +24,11 @@ public class GameModeChangeListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onGameModeChange(PlayerGameModeChangeEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
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;
|
||||
}
|
||||
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
|
||||
|
||||
List<String> allowedModes = configManager.getAllowedGameModes();
|
||||
|
||||
if (!allowedModes.isEmpty()) {
|
||||
String newMode = event.getNewGameMode().name();
|
||||
|
||||
if (!allowedModes.contains(newMode)) {
|
||||
event.setCancelled(true);
|
||||
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()) {
|
||||
player.getServer().getScheduler().runTaskLater(
|
||||
player.getServer().getPluginManager().getPlugin("AdminMode"),
|
||||
() -> {
|
||||
player.setAllowFlight(true);
|
||||
if (!player.isFlying()) {
|
||||
player.setFlying(true);
|
||||
}
|
||||
if (!player.isFlying()) player.setFlying(true);
|
||||
},
|
||||
1L);
|
||||
}
|
||||
@@ -74,18 +57,8 @@ public class GameModeChangeListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onToggleFlight(PlayerToggleFlightEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configManager.isAllowFlightInSurvival()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Всегда разрешаем полет в админ моде
|
||||
if (!player.getAllowFlight()) {
|
||||
player.setAllowFlight(true);
|
||||
}
|
||||
if (!adminModeCommand.isInAdminMode(player.getUniqueId())) return;
|
||||
if (!configManager.isAllowFlightInSurvival()) return;
|
||||
if (!player.getAllowFlight()) player.setAllowFlight(true);
|
||||
}
|
||||
}
|
||||
|
||||
0
src/main/java/org/blz/adminmode/listeners/MobTargetListener.java
Normal file → Executable file
0
src/main/java/org/blz/adminmode/listeners/MobTargetListener.java
Normal file → Executable file
2
src/main/java/org/blz/adminmode/listeners/PlayerDeathListener.java
Normal file → Executable file
2
src/main/java/org/blz/adminmode/listeners/PlayerDeathListener.java
Normal file → Executable file
@@ -15,6 +15,6 @@ public class PlayerDeathListener implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
modeManager.handleHardModeDeath(event);
|
||||
modeManager.handleAdminDeath(event);
|
||||
}
|
||||
}
|
||||
|
||||
3
src/main/java/org/blz/adminmode/listeners/PlayerSessionListener.java
Normal file → Executable file
3
src/main/java/org/blz/adminmode/listeners/PlayerSessionListener.java
Normal file → Executable file
@@ -26,13 +26,14 @@ public class PlayerSessionListener implements Listener {
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
modeManager.refreshVanishFor(player);
|
||||
if (!modeManager.hasSession(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.getServer().getScheduler().runTaskLater(
|
||||
org.blz.adminmode.AdminMode.getInstance(),
|
||||
() -> adminModeCommand.recoverSession(player),
|
||||
() -> adminModeCommand.resumeMode(player),
|
||||
1L);
|
||||
}
|
||||
}
|
||||
|
||||
60
src/main/java/org/blz/adminmode/managers/BelzeBoolManager.java
Normal file → Executable file
60
src/main/java/org/blz/adminmode/managers/BelzeBoolManager.java
Normal file → Executable file
@@ -10,6 +10,7 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
@@ -23,6 +24,7 @@ import java.util.*;
|
||||
|
||||
public class BelzeBoolManager implements Listener {
|
||||
|
||||
private static final String SPECIAL_PERMISSION = "adminmode.special.belzebool";
|
||||
private final Plugin plugin;
|
||||
private final String BELZEBOOL_NAME = "BelzeBool";
|
||||
private final String POCKET_DIMENSION_WORLD = "pocket_dimension";
|
||||
@@ -39,6 +41,9 @@ public class BelzeBoolManager implements Listener {
|
||||
// Vanish State: UUID -> isVanished
|
||||
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
|
||||
private final Map<UUID, List<Long>> rightClickTimestamps = new HashMap<>();
|
||||
|
||||
@@ -49,7 +54,7 @@ public class BelzeBoolManager implements Listener {
|
||||
}
|
||||
|
||||
public boolean isBelzeBool(Player player) {
|
||||
return player.getName().equalsIgnoreCase(BELZEBOOL_NAME);
|
||||
return player.hasPermission(SPECIAL_PERMISSION) && player.getName().equalsIgnoreCase(BELZEBOOL_NAME);
|
||||
}
|
||||
|
||||
// --- 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);
|
||||
|
||||
Location originalLoc = target.getLocation();
|
||||
if (target instanceof Player targetPlayer) {
|
||||
pocketReturnLocations.put(targetPlayer.getUniqueId(), originalLoc.clone());
|
||||
}
|
||||
|
||||
// 2. Teleport after 5 seconds
|
||||
new BukkitRunnable() {
|
||||
@@ -320,6 +328,8 @@ public class BelzeBoolManager implements Listener {
|
||||
if (target instanceof Player targetP) {
|
||||
targetP.removePotionEffect(PotionEffectType.LEVITATION);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
325
src/main/java/org/blz/adminmode/moderation/ModeManager.java
Normal file → Executable file
325
src/main/java/org/blz/adminmode/moderation/ModeManager.java
Normal file → Executable file
@@ -8,7 +8,6 @@ import org.blz.adminmode.session.SessionStorage;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -16,46 +15,44 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
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.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
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 ConfigManager configManager;
|
||||
private final LuckPermsIntegration luckPermsIntegration;
|
||||
private final SessionStorage sessionStorage;
|
||||
private final ModeSessionLogger sessionLogger;
|
||||
private final NamespacedKey panelTypeKey;
|
||||
private final ModeratorPreferencesStore preferencesStore;
|
||||
private final Map<UUID, ModeSession> activeSessions = new HashMap<>();
|
||||
private final Set<UUID> vanishedModerators = new HashSet<>();
|
||||
|
||||
public ModeManager(
|
||||
Plugin plugin,
|
||||
ConfigManager configManager,
|
||||
LuckPermsIntegration luckPermsIntegration,
|
||||
SessionStorage sessionStorage,
|
||||
ModeSessionLogger sessionLogger) {
|
||||
ModeSessionLogger sessionLogger,
|
||||
ModeratorPreferencesStore preferencesStore) {
|
||||
this.plugin = plugin;
|
||||
this.configManager = configManager;
|
||||
this.luckPermsIntegration = luckPermsIntegration;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.sessionLogger = sessionLogger;
|
||||
this.panelTypeKey = new NamespacedKey(plugin, "panel_type");
|
||||
this.preferencesStore = preferencesStore;
|
||||
this.activeSessions.putAll(sessionStorage.loadSessions());
|
||||
}
|
||||
|
||||
@@ -71,37 +68,32 @@ public class ModeManager {
|
||||
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() {
|
||||
return new ArrayList<>(activeSessions.values());
|
||||
}
|
||||
|
||||
public boolean enterMode(Player player, ModeratorProfile profile) {
|
||||
if (player == null || profile == null || hasSession(player.getUniqueId())) {
|
||||
return false;
|
||||
}
|
||||
public boolean enterMode(Player player) {
|
||||
if (player == null || hasSession(player.getUniqueId())) return false;
|
||||
if (!player.hasPermission(ModeratorProfile.ADMIN.getPermission())) return false;
|
||||
|
||||
String previousGroup = luckPermsIntegration.resolveCurrentGroup(player);
|
||||
String activeGroup = configManager.getActiveGroup(profile);
|
||||
ModeSession session = ModeSession.capture(player, profile, previousGroup, activeGroup);
|
||||
ModeSession session = ModeSession.capture(player, ModeratorProfile.ADMIN, previousGroup, configManager.getAdminGroup());
|
||||
try {
|
||||
activeSessions.put(player.getUniqueId(), session);
|
||||
persistSessions();
|
||||
|
||||
preparePlayerForModeration(player, profile);
|
||||
luckPermsIntegration.enterMode(player, profile);
|
||||
preparePlayerForModeration(player);
|
||||
luckPermsIntegration.enterMode(player);
|
||||
sessionLogger.logEnter(session);
|
||||
|
||||
String enterMessage = profile.isHardProfile() ? configManager.getMsgEnterHard() : configManager.getMsgEnterSoft();
|
||||
player.sendMessage(configManager.getPrefixedMessage(enterMessage));
|
||||
player.sendMessage(configManager.getPrefixedMessage(configManager.getMsgEnter()));
|
||||
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) {
|
||||
@@ -112,6 +104,19 @@ public class ModeManager {
|
||||
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) {
|
||||
if (player != null && hasSession(player.getUniqueId())) {
|
||||
persistSessions();
|
||||
@@ -119,33 +124,48 @@ public class ModeManager {
|
||||
}
|
||||
|
||||
public boolean canUseHardActions(CommandSender sender) {
|
||||
if (sender.hasPermission("adminmode.admin")) {
|
||||
return sender.hasPermission("adminmode.admin");
|
||||
}
|
||||
|
||||
public boolean isVanished(UUID playerId) {
|
||||
return vanishedModerators.contains(playerId);
|
||||
}
|
||||
|
||||
public boolean toggleVanish(Player player) {
|
||||
if (player == null || !hasSession(player.getUniqueId())) {
|
||||
return false;
|
||||
}
|
||||
if (isVanished(player.getUniqueId())) {
|
||||
disableVanish(player);
|
||||
player.sendMessage(configManager.getPrefixedMessage("§7Vanish отключён."));
|
||||
return false;
|
||||
}
|
||||
enableVanish(player);
|
||||
player.sendMessage(configManager.getPrefixedMessage("§aVanish включён."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(sender instanceof Player player)) {
|
||||
return false;
|
||||
public void refreshVanishFor(Player viewer) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ModeSession session = getSession(player);
|
||||
return session != null && session.getProfile().isHardProfile();
|
||||
}
|
||||
|
||||
public boolean teleportToPlayer(Player moderator, Player target) {
|
||||
if (moderator == null || target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moderator == null || target == null) return false;
|
||||
moderator.teleport(target.getLocation());
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§aТелепортация к §f" + target.getName() + "§a выполнена."));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean teleportPlayerHere(Player moderator, Player target) {
|
||||
if (moderator == null || target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moderator == null || target == null) return false;
|
||||
target.teleport(moderator.getLocation());
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§aИгрок §f" + target.getName() + "§a телепортирован к вам."));
|
||||
target.sendMessage(configManager.getPrefixedMessage("§eВас телепортировал модератор §f" + moderator.getName()));
|
||||
@@ -153,15 +173,11 @@ public class ModeManager {
|
||||
}
|
||||
|
||||
public boolean spectatePlayer(Player moderator, Player target) {
|
||||
if (moderator == null || target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moderator == null || target == null) return false;
|
||||
if (moderator.equals(target)) {
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§cНельзя спектейтить самого себя."));
|
||||
return false;
|
||||
}
|
||||
|
||||
moderator.setGameMode(GameMode.SPECTATOR);
|
||||
moderator.setSpectatorTarget((Entity) target);
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§bВы наблюдаете за §f" + target.getName()));
|
||||
@@ -169,20 +185,14 @@ public class ModeManager {
|
||||
}
|
||||
|
||||
public boolean openInvsee(Player moderator, Player target) {
|
||||
if (moderator == null || target == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
moderator.openInventory(target.getInventory());
|
||||
if (moderator == null || target == null) return false;
|
||||
moderator.getServer().dispatchCommand(moderator, "invsee " + target.getName());
|
||||
moderator.sendMessage(configManager.getPrefixedMessage("§7Открыт инвентарь игрока §f" + target.getName()));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applyAdminSettings(Player player, float flySpeed, float walkSpeed, boolean invulnerable) {
|
||||
if (!hasSession(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasSession(player.getUniqueId())) return;
|
||||
player.setAllowFlight(true);
|
||||
player.setFlying(true);
|
||||
player.setFlySpeed(clampSpeed(flySpeed, 0.1f));
|
||||
@@ -192,7 +202,7 @@ public class ModeManager {
|
||||
|
||||
public List<Player> listOtherOnlinePlayers(Player viewer) {
|
||||
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));
|
||||
return players;
|
||||
}
|
||||
@@ -200,10 +210,10 @@ public class ModeManager {
|
||||
public List<Component> buildOnlineSummary(Player viewer) {
|
||||
List<Component> lines = new ArrayList<>();
|
||||
for (Player target : listOtherOnlinePlayers(viewer)) {
|
||||
Location location = target.getLocation();
|
||||
Location loc = target.getLocation();
|
||||
lines.add(Component.text(
|
||||
target.getName() + " | " + target.getWorld().getName() + " | " +
|
||||
location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ(),
|
||||
loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ(),
|
||||
NamedTextColor.GRAY));
|
||||
}
|
||||
if (lines.isEmpty()) {
|
||||
@@ -212,12 +222,11 @@ public class ModeManager {
|
||||
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();
|
||||
ModeSession session = getSession(player);
|
||||
if (session == null || !session.getProfile().isHardProfile()) {
|
||||
return;
|
||||
}
|
||||
if (session == null) return;
|
||||
|
||||
event.setKeepInventory(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) {
|
||||
if (viewer == null || inventory == null || inventory.getHolder() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(inventory.getHolder() instanceof Player target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (viewer == null || inventory == null || inventory.getHolder() == null) return false;
|
||||
if (!(inventory.getHolder() instanceof Player target)) return false;
|
||||
return hasSession(viewer.getUniqueId()) && !viewer.getUniqueId().equals(target.getUniqueId());
|
||||
}
|
||||
|
||||
private boolean finishSession(Player player, boolean recovered) {
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player == null) return false;
|
||||
ModeSession session = activeSessions.remove(player.getUniqueId());
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
if (session == null) return false;
|
||||
|
||||
disableVanish(player);
|
||||
restorePlayerState(player, session);
|
||||
luckPermsIntegration.exitMode(player, session);
|
||||
persistSessions();
|
||||
@@ -281,14 +263,14 @@ public class ModeManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void preparePlayerForModeration(Player player, ModeratorProfile profile) {
|
||||
private void preparePlayerForModeration(Player player) {
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
inventory.clear();
|
||||
inventory.setArmorContents(new ItemStack[4]);
|
||||
inventory.setItemInOffHand(new ItemStack(Material.AIR));
|
||||
inventory.setItemInOffHand(ItemStack.empty());
|
||||
|
||||
player.closeInventory();
|
||||
player.setSpectatorTarget(null);
|
||||
clearSpectatorTargetIfNeeded(player);
|
||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
@@ -305,27 +287,35 @@ public class ModeManager {
|
||||
player.setFlying(true);
|
||||
player.setFlySpeed(clampSpeed(configManager.getCustomFlySpeed(), 0.1f));
|
||||
player.setWalkSpeed(clampSpeed(configManager.getCustomWalkSpeed(), 0.1f));
|
||||
player.setInvulnerable(true);
|
||||
player.setGameMode(profile.isHardProfile() ? GameMode.CREATIVE : GameMode.SPECTATOR);
|
||||
|
||||
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));
|
||||
}
|
||||
player.setInvulnerable(configManager.isGodMode());
|
||||
player.setGameMode(resolveAdminGameMode(player));
|
||||
|
||||
if (configManager.isGivePresetBlocks()) {
|
||||
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) {
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
inventory.clear();
|
||||
@@ -333,7 +323,7 @@ public class ModeManager {
|
||||
inventory.setArmorContents(session.getArmorContents());
|
||||
inventory.setItemInOffHand(session.getOffHandItem());
|
||||
|
||||
player.setSpectatorTarget(null);
|
||||
clearSpectatorTargetIfNeeded(player);
|
||||
player.setInvulnerable(false);
|
||||
player.setAllowFlight(false);
|
||||
player.setFlying(false);
|
||||
@@ -346,7 +336,11 @@ public class ModeManager {
|
||||
|
||||
player.setGameMode(session.getGameMode());
|
||||
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()));
|
||||
@@ -377,26 +371,96 @@ public class ModeManager {
|
||||
String[] parts = blockString.split(":");
|
||||
Material material = Material.valueOf(parts[0].trim().toUpperCase());
|
||||
int amount = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 64;
|
||||
player.getInventory().addItem(new ItemStack(material, Math.max(1, amount)));
|
||||
} catch (Exception exception) {
|
||||
player.getInventory().addItem(ItemStack.of(material, Math.max(1, amount)));
|
||||
} catch (Exception e) {
|
||||
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() {
|
||||
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) {
|
||||
if (player.getAttribute(Attribute.MAX_HEALTH) != null) {
|
||||
return player.getAttribute(Attribute.MAX_HEALTH).getValue();
|
||||
@@ -404,7 +468,8 @@ public class ModeManager {
|
||||
return 20.0D;
|
||||
}
|
||||
|
||||
private float clampSpeed(float value, float min) {
|
||||
return Math.max(min, Math.min(1.0f, value));
|
||||
private float clampSpeed(float value, float fallback) {
|
||||
float normalized = value <= 0.0f ? fallback : value;
|
||||
return Math.max(0.1f, Math.min(1.0f, normalized));
|
||||
}
|
||||
}
|
||||
|
||||
2
src/main/java/org/blz/adminmode/moderation/ModeSession.java
Normal file → Executable file
2
src/main/java/org/blz/adminmode/moderation/ModeSession.java
Normal file → Executable file
@@ -246,7 +246,7 @@ public final class ModeSession {
|
||||
|
||||
private static ItemStack cloneItem(ItemStack item) {
|
||||
if (item == null || item.getType() == Material.AIR) {
|
||||
return new ItemStack(Material.AIR);
|
||||
return ItemStack.empty();
|
||||
}
|
||||
return item.clone();
|
||||
}
|
||||
|
||||
2
src/main/java/org/blz/adminmode/moderation/ModeSessionLogger.java
Normal file → Executable file
2
src/main/java/org/blz/adminmode/moderation/ModeSessionLogger.java
Normal file → Executable file
@@ -60,7 +60,7 @@ public class ModeSessionLogger {
|
||||
}
|
||||
|
||||
private String modeSuffix(ModeSession session) {
|
||||
return session.getProfile().isHardProfile() ? "HARD" : "SOFT";
|
||||
return "ADMIN";
|
||||
}
|
||||
|
||||
private String formatDuration(long millis) {
|
||||
|
||||
280
src/main/java/org/blz/adminmode/moderation/ModeratorAccessManager.java
Executable file
280
src/main/java/org/blz/adminmode/moderation/ModeratorAccessManager.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
230
src/main/java/org/blz/adminmode/moderation/ModeratorPreferencesStore.java
Executable file
230
src/main/java/org/blz/adminmode/moderation/ModeratorPreferencesStore.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/main/java/org/blz/adminmode/moderation/ModeratorProfile.java
Normal file → Executable file
54
src/main/java/org/blz/adminmode/moderation/ModeratorProfile.java
Normal file → Executable file
@@ -2,14 +2,10 @@ package org.blz.adminmode.moderation;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum ModeratorProfile {
|
||||
SOFT_MODER("soft_moder", "Soft Moder", "adminmode.profile.soft"),
|
||||
HARD_MODER("hard_moder", "Hard Moder", "adminmode.profile.hard");
|
||||
ADMIN("admin", "Admin Mode", "adminmode.use");
|
||||
|
||||
private final String configKey;
|
||||
private final String displayName;
|
||||
@@ -33,54 +29,12 @@ public enum ModeratorProfile {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public boolean isHardProfile() {
|
||||
return this == HARD_MODER;
|
||||
}
|
||||
|
||||
public boolean isAvailableFor(Player player) {
|
||||
return switch (this) {
|
||||
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;
|
||||
return player.hasPermission(permission);
|
||||
}
|
||||
|
||||
/** Accepts any old key (soft_moder, hard_moder, admin) and returns ADMIN. */
|
||||
public static Optional<ModeratorProfile> fromConfigKey(String key) {
|
||||
if (key == null || key.isBlank()) {
|
||||
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();
|
||||
};
|
||||
return Optional.of(ADMIN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/main/java/org/blz/adminmode/moderation/PlayerFreezeManager.java
Executable file
127
src/main/java/org/blz/adminmode/moderation/PlayerFreezeManager.java
Executable 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());
|
||||
}
|
||||
}
|
||||
3
src/main/java/org/blz/adminmode/moderation/PlayerListAction.java
Normal file → Executable file
3
src/main/java/org/blz/adminmode/moderation/PlayerListAction.java
Normal file → Executable file
@@ -3,5 +3,6 @@ package org.blz.adminmode.moderation;
|
||||
public enum PlayerListAction {
|
||||
SPECTATE,
|
||||
TELEPORT,
|
||||
INVSEE
|
||||
INVSEE,
|
||||
MANAGE
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
90
src/main/java/org/blz/adminmode/session/SessionStorage.java
Normal file → Executable file
90
src/main/java/org/blz/adminmode/session/SessionStorage.java
Normal file → Executable file
@@ -8,29 +8,22 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.blz.adminmode.moderation.ModeSession;
|
||||
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.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
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.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -128,7 +121,7 @@ public class SessionStorage {
|
||||
UUID playerId = UUID.fromString(json.get("playerId").getAsString());
|
||||
String playerName = json.get("playerName").getAsString();
|
||||
ModeratorProfile profile = ModeratorProfile.fromConfigKey(json.get("profile").getAsString())
|
||||
.orElse(ModeratorProfile.SOFT_MODER);
|
||||
.orElse(ModeratorProfile.ADMIN);
|
||||
long startTimestamp = json.get("startTimestamp").getAsLong();
|
||||
String previousGroup = getString(json, "previousGroup");
|
||||
String activeGroup = getString(json, "activeGroup");
|
||||
@@ -224,14 +217,7 @@ public class SessionStorage {
|
||||
|
||||
private String serializeItemArray(ItemStack[] items) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
|
||||
dataOutput.writeInt(items.length);
|
||||
for (ItemStack item : items) {
|
||||
dataOutput.writeObject(item);
|
||||
}
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
return ItemStackCodec.serializeItems(items);
|
||||
} catch (Exception exception) {
|
||||
plugin.getLogger().warning("Не удалось сериализовать item array: " + exception.getMessage());
|
||||
return "";
|
||||
@@ -244,15 +230,7 @@ public class SessionStorage {
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(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;
|
||||
}
|
||||
return ItemStackCodec.deserializeItems(data);
|
||||
} catch (Exception exception) {
|
||||
plugin.getLogger().warning("Не удалось десериализовать item array: " + exception.getMessage());
|
||||
return new ItemStack[0];
|
||||
@@ -261,11 +239,7 @@ public class SessionStorage {
|
||||
|
||||
private String serializeItem(ItemStack item) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
|
||||
dataOutput.writeObject(item);
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
return ItemStackCodec.serializeItem(item);
|
||||
} catch (Exception exception) {
|
||||
plugin.getLogger().warning("Не удалось сериализовать item: " + exception.getMessage());
|
||||
return "";
|
||||
@@ -274,62 +248,24 @@ public class SessionStorage {
|
||||
|
||||
private ItemStack deserializeItem(String data) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return new ItemStack(Material.AIR);
|
||||
return ItemStack.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
|
||||
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
|
||||
return (ItemStack) dataInput.readObject();
|
||||
}
|
||||
return ItemStackCodec.deserializeItem(data);
|
||||
} catch (Exception exception) {
|
||||
plugin.getLogger().warning("Не удалось десериализовать item: " + exception.getMessage());
|
||||
return new ItemStack(Material.AIR);
|
||||
return ItemStack.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private String serializePotionEffects(Collection<PotionEffect> effects) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
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();
|
||||
return PotionEffectCodec.serialize(effects);
|
||||
}
|
||||
|
||||
private Collection<PotionEffect> deserializePotionEffects(String data) {
|
||||
List<PotionEffect> effects = new ArrayList<>();
|
||||
if (data == null || data.isEmpty()) {
|
||||
return effects;
|
||||
}
|
||||
|
||||
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;
|
||||
return PotionEffectCodec.deserialize(data,
|
||||
encodedEffect -> plugin.getLogger().warning(
|
||||
"Не удалось десериализовать potion effect: " + encodedEffect));
|
||||
}
|
||||
}
|
||||
|
||||
85
src/main/java/org/blz/adminmode/utils/ItemStackCodec.java
Normal file
85
src/main/java/org/blz/adminmode/utils/ItemStackCodec.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/main/java/org/blz/adminmode/utils/PotionEffectCodec.java
Normal file
86
src/main/java/org/blz/adminmode/utils/PotionEffectCodec.java
Normal 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);
|
||||
}
|
||||
}
|
||||
0
src/main/java/org/blz/adminmode/utils/ScreenEffects.java
Normal file → Executable file
0
src/main/java/org/blz/adminmode/utils/ScreenEffects.java
Normal file → Executable file
59
src/main/java/org/blz/adminmode/utils/WorldLoader.java
Normal file → Executable file
59
src/main/java/org/blz/adminmode/utils/WorldLoader.java
Normal file → Executable file
@@ -38,6 +38,7 @@ public class WorldLoader {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cleanupSkippedFiles(worldFolder);
|
||||
|
||||
plugin.getLogger().info("Загрузка мира " + worldName + "...");
|
||||
WorldCreator creator = new WorldCreator(worldName);
|
||||
@@ -64,8 +65,18 @@ public class WorldLoader {
|
||||
|
||||
try (ZipInputStream zipIn = new ZipInputStream(in)) {
|
||||
ZipEntry entry = zipIn.getNextEntry();
|
||||
String destinationPath = destination.getCanonicalPath() + File.separator;
|
||||
while (entry != null) {
|
||||
if (shouldSkipEntry(entry.getName())) {
|
||||
zipIn.closeEntry();
|
||||
entry = zipIn.getNextEntry();
|
||||
continue;
|
||||
}
|
||||
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()) {
|
||||
file.mkdirs();
|
||||
} else {
|
||||
@@ -91,4 +102,52 @@ public class WorldLoader {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
src/main/resources/config.yml
Normal file → Executable file
99
src/main/resources/config.yml
Normal file → Executable file
@@ -1,77 +1,43 @@
|
||||
# AdminMode Configuration
|
||||
|
||||
#РЕЖИМ АДМИНИСТРАТОРА
|
||||
admin_mode:
|
||||
# Включен ли режим администратора
|
||||
enabled: true
|
||||
|
||||
# LuckPerms настройки
|
||||
luckperms:
|
||||
# Использовать ли LuckPerms (если false - используется стандартный OP)
|
||||
enabled: true
|
||||
|
||||
# Группы которые выдаются при активации режима
|
||||
# Можно указать несколько групп через запятую
|
||||
# LuckPerms-группы, которые выдаются при входе в режим
|
||||
groups:
|
||||
- "soft_moder_adm"
|
||||
profile_groups:
|
||||
soft_moder:
|
||||
- "soft_moder_adm"
|
||||
hard_moder:
|
||||
- "hard_moder_adm"
|
||||
|
||||
# Удалять ли группы при деактивации (если false - группы останутся)
|
||||
- "admin_moder"
|
||||
remove_on_disable: true
|
||||
|
||||
dialogs:
|
||||
# Глобальный мастер-переключатель Dialog API (false = отключить для всего сервера).
|
||||
# Каждый модератор лично включает/выключает своё GUI командой /adminmode dialog
|
||||
enabled: true
|
||||
|
||||
# Настройки игрового режима
|
||||
gamemode:
|
||||
# Принудительно ставить спектатор (если false - survival)
|
||||
# false = chiefs can use CREATIVE, regular moderators use SPECTATOR
|
||||
# true = everyone enters in SPECTATOR
|
||||
force_spectator: false
|
||||
|
||||
# Мобы игнорируют игрока в админ моде
|
||||
mobs_ignore_player: true
|
||||
|
||||
# Разрешить полет в режиме выживания (в админ моде)
|
||||
allow_flight_in_survival: true
|
||||
|
||||
# Режим бога (неуязвимость)
|
||||
god_mode: true
|
||||
|
||||
# Кастомная скорость полета (0.1 = медленно, 0.2 = нормально, 0.5 = быстро, 1.0 = очень быстро)
|
||||
chief_creative: true
|
||||
# Скорость полёта: 0.1 (медленно) ... 1.0 (максимум)
|
||||
custom_fly_speed: 0.2
|
||||
|
||||
# Кастомная скорость ходьбы (0.1 = медленно, 0.2 = нормально, 0.5 = быстро)
|
||||
custom_walk_speed: 0.2
|
||||
|
||||
# Разрешенные режимы игры для переключения в админ моде
|
||||
# Если список пустой - можно переключаться на любые режимы
|
||||
# Доступные: SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
|
||||
# ВАЖНО: CREATIVE убран - модеры не должны иметь доступ к креативу!
|
||||
# Разрешённые режимы для переключения внутри админ-мода (пусто = любые)
|
||||
allowed_gamemodes:
|
||||
- "CREATIVE"
|
||||
- "SURVIVAL"
|
||||
- "SPECTATOR"
|
||||
|
||||
# Ограничения в режиме администратора
|
||||
restrictions:
|
||||
# Запретить выбрасывание предметов
|
||||
prevent_item_drop: false
|
||||
|
||||
# Запретить поднятие предметов
|
||||
prevent_item_pickup: false
|
||||
|
||||
# Запретить перемещение предметов в контейнеры (сундуки, бочки и т.д.)
|
||||
prevent_container_transfer: true
|
||||
|
||||
# Запретить использование предметов (кроме открытия контейнеров)
|
||||
prevent_item_use: false
|
||||
|
||||
# Разрешить управление своим инвентарем
|
||||
allow_own_inventory: true
|
||||
|
||||
# Разрешенные блоки для взаимодействия (открытие, использование)
|
||||
allowed_blocks:
|
||||
- "CHEST"
|
||||
- "TRAPPED_CHEST"
|
||||
@@ -88,27 +54,18 @@ admin_mode:
|
||||
- "FENCE_GATE"
|
||||
- "BUTTON"
|
||||
- "LEVER"
|
||||
- "ENDER_CHEST"
|
||||
- "CRAFTING_TABLE"
|
||||
- "ENCHANTING_TABLE"
|
||||
- "ANVIL"
|
||||
- "BREWING_STAND"
|
||||
|
||||
# Разрешенные блоки для постановки (whitelist)
|
||||
# Если список пустой - можно ставить любые блоки
|
||||
# Если список заполнен - можно ставить только эти блоки
|
||||
# Whitelist блоков для постановки (пусто = любые)
|
||||
allowed_place_blocks:
|
||||
|
||||
# Выдавать пресет блоков при входе в админ мод
|
||||
give_preset_blocks: false
|
||||
|
||||
# Пресет блоков (формат: МАТЕРИАЛ:КОЛИЧЕСТВО)
|
||||
preset_blocks:
|
||||
- "BARRIER:64"
|
||||
- "LIGHT:64"
|
||||
- "STRUCTURE_VOID:64"
|
||||
|
||||
# Сообщения
|
||||
messages:
|
||||
enabled: "✓ Режим администратора активирован!"
|
||||
enabled_subtitle: "Ваше состояние сохранено."
|
||||
@@ -125,12 +82,11 @@ admin_mode:
|
||||
|
||||
groups:
|
||||
base: "moder"
|
||||
soft: "soft_moder_adm"
|
||||
hard: "hard_moder_adm"
|
||||
admin: "admin_moder"
|
||||
chief: "chief_moder"
|
||||
|
||||
items:
|
||||
soft_panel_material: COMPASS
|
||||
hard_panel_material: NETHER_STAR
|
||||
panel_material: NETHER_STAR
|
||||
cp_inspector_material: STICK
|
||||
|
||||
coreprotect:
|
||||
@@ -140,12 +96,33 @@ coreprotect:
|
||||
|
||||
messages:
|
||||
prefix: "§8[§bAdminMode§8] "
|
||||
enter_soft: "§aВошёл в режим §bSOFT MODER"
|
||||
enter_hard: "§aВошёл в режим §cHARD MODER"
|
||||
enter: "§aВошёл в §cADMIN MODE"
|
||||
exit: "§7Режим деактивирован. Состояние восстановлено."
|
||||
cp_unavailable: "§cCoreProtect не найден. Hard режим работает без CP."
|
||||
cp_unavailable: "§cCoreProtect не найден или отключён."
|
||||
session_recovered: "§eВаша активная сессия модерации была восстановлена и корректно завершена."
|
||||
|
||||
logging:
|
||||
enabled: true
|
||||
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
41
src/main/resources/plugin.yml
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
name: AdminMode
|
||||
version: '1.21.11'
|
||||
version: '${version}'
|
||||
main: org.blz.adminmode.AdminMode
|
||||
api-version: '1.21'
|
||||
api-version: '26.1.2'
|
||||
author: BelzeBool, Kilka_v_HJIebe
|
||||
description: Плагин режима администратора с сохранением состояния игрока
|
||||
depend: [LuckPerms]
|
||||
@@ -10,7 +10,7 @@ softdepend: [CoreProtect]
|
||||
commands:
|
||||
adminmode:
|
||||
description: Режим администратора с сохранением состояния
|
||||
usage: /<command>
|
||||
usage: /<command> [on|off|toggle|dialog|disable|restrict|unrestrict|status|reload]
|
||||
aliases: [adm, am]
|
||||
admfix:
|
||||
description: Отключить все способности у игрока (полет, бессмертие)
|
||||
@@ -20,15 +20,36 @@ permissions:
|
||||
adminmode.use:
|
||||
description: Разрешает использовать режим администратора
|
||||
default: op
|
||||
adminmode.profile.soft:
|
||||
description: Разрешает использовать профиль soft_moder
|
||||
default: op
|
||||
adminmode.profile.hard:
|
||||
description: Разрешает использовать профиль hard_moder
|
||||
default: op
|
||||
adminmode.reload:
|
||||
description: Разрешает перезагружать конфигурацию AdminMode
|
||||
default: op
|
||||
adminmode.admin:
|
||||
description: Разрешает использовать сильные действия и админ команды (disable, admfix)
|
||||
description: Разрешает использовать сильные действия и команды (disable, admfix)
|
||||
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
0
src/main/resources/pocket_dimension.zip
Normal file → Executable file
Reference in New Issue
Block a user