feat: LoHub Action system + Join system implementation
This commit is contained in:
parent
69cae99476
commit
6a4c8a7886
55 changed files with 3180 additions and 1022 deletions
|
|
@ -1,27 +0,0 @@
|
||||||
name: Build & Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, master, develop]
|
|
||||||
pull_request:
|
|
||||||
branches: [main, master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
git clone --depth 1 https://oauth2:${{ secrets.RELEASE_TOKEN }}@git.lokili.xyz/${{ gitea.repository }}.git .
|
|
||||||
|
|
||||||
- name: Setup Java 21
|
|
||||||
run: |
|
|
||||||
curl -sL https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz | tar xz
|
|
||||||
|
|
||||||
- name: Build & Test
|
|
||||||
run: |
|
|
||||||
export JAVA_HOME=$PWD/jdk-21.0.2
|
|
||||||
export PATH=$JAVA_HOME/bin:$PATH
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew build test --no-daemon
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
git clone --depth 1 --branch ${{ gitea.ref_name }} https://oauth2:${{ secrets.RELEASE_TOKEN }}@git.lokili.xyz/${{ gitea.repository }}.git .
|
|
||||||
|
|
||||||
- name: Setup Java 21
|
|
||||||
run: |
|
|
||||||
curl -sL https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz | tar xz
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
export JAVA_HOME=$PWD/jdk-21.0.2
|
|
||||||
export PATH=$JAVA_HOME/bin:$PATH
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew build --no-daemon
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
run: |
|
|
||||||
VERSION=${{ gitea.ref_name }}
|
|
||||||
VERSION=${VERSION#v}
|
|
||||||
JAR_FILE=$(ls build/libs/LoChat-*.jar | head -1)
|
|
||||||
|
|
||||||
curl -X POST \
|
|
||||||
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"https://git.lokili.xyz/api/v1/repos/${{ gitea.repository }}/releases" \
|
|
||||||
-d "{\"tag_name\": \"${{ gitea.ref_name }}\", \"name\": \"LoChat $VERSION\", \"body\": \"Release $VERSION\"}"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
|
|
||||||
"https://git.lokili.xyz/api/v1/repos/${{ gitea.repository }}/releases/tags/${{ gitea.ref_name }}" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
|
|
||||||
|
|
||||||
curl -X POST \
|
|
||||||
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
"https://git.lokili.xyz/api/v1/repos/${{ gitea.repository }}/releases/$RELEASE_ID/assets?name=LoChat-$VERSION.jar" \
|
|
||||||
--data-binary @"$JAR_FILE"
|
|
||||||
432
README.md
432
README.md
|
|
@ -1,304 +1,188 @@
|
||||||
# LoChat
|
# 🎮 Loki Network Plugins
|
||||||
|
|
||||||
**Продвинутый чат-плагин для Minecraft Paper 1.20.1 с интегрированным модулем градиентных ников**
|
Набор плагинов для Minecraft серверов: чат, хаб и прокси.
|
||||||
|
|
||||||
**Автор:** loki
|
## 📦 Модули
|
||||||
**Версия:** 1.5.5-1.20.1
|
|
||||||
|
|
||||||
---
|
### 1. LoChat - Чат плагин (Paper/Folia)
|
||||||
|
Продвинутый чат с градиентными никами, модерацией и фильтрами.
|
||||||
|
|
||||||
## 🚀 Установка
|
**Основные функции:**
|
||||||
|
- Глобальный/локальный чат
|
||||||
|
- PM система с игнором
|
||||||
|
- Мут система с историей
|
||||||
|
- Фильтры (мат, спам, CAPS, URL, IP)
|
||||||
|
- Градиентные ники (LuckPerms)
|
||||||
|
- Markdown форматирование
|
||||||
|
- Упоминания (@player, @everyone, @here, @role)
|
||||||
|
- 300+ эмодзи
|
||||||
|
|
||||||
1. Скачайте `LoChat-1.5.5-1.20.1.jar` из папки `build/libs/`
|
### 2. LoHub - Hub плагин (Paper/Folia)
|
||||||
2. Поместите файл в папку `plugins/` вашего сервера
|
Плагин для лобби сервера с защитой мира и интерактивными элементами.
|
||||||
3. Перезапустите сервер
|
|
||||||
4. Настройте конфигурацию в файлах `config.yml` и других
|
|
||||||
|
|
||||||
---
|
**Основные функции:**
|
||||||
|
- Spawn система с телепортом
|
||||||
|
- WorldGuard регионы (авто-создание)
|
||||||
|
- Защита мира (блоки, PvP, мобы)
|
||||||
|
- Launchpad и Double Jump
|
||||||
|
- Player Hider
|
||||||
|
- Custom Join Items
|
||||||
|
- Action система для гибкой настройки
|
||||||
|
|
||||||
## ✨ Основной функционал
|
### 3. LoVelocity - Velocity плагин
|
||||||
|
Простой плагин для прокси с командой /hub.
|
||||||
|
|
||||||
### 💬 Система чата
|
**Основные функции:**
|
||||||
- **Глобальный чат** - `!сообщение` или `/g <сообщение>`
|
- Команда /hub для телепорта на lobby
|
||||||
- **Локальный чат** - радиус 100 блоков (настраивается)
|
- Кастомные сообщения
|
||||||
- **Личные сообщения** - `/msg`, `/reply` с поддержкой игнорирования
|
- PlaceholderAPI поддержка
|
||||||
- **Автосообщения** - многострочные сообщения с настраиваемым порядком
|
|
||||||
- **Смайлики** - 200+ эмодзи для украшения сообщений
|
|
||||||
|
|
||||||
### 🛡️ Модерация
|
## 🏗️ Структура проекта
|
||||||
- **Система мутов** - `/lmute`, `/lunmute` с поддержкой времени и причин
|
|
||||||
- **Фильтр мата** - умная система с обходом символов (censor/block/warn)
|
|
||||||
- **Анти-спам** - защита от CAPS, повторов и похожих сообщений
|
|
||||||
- **Кулдауны** - настраиваемые задержки между сообщениями
|
|
||||||
- **Шпион режим** - `/chatspy` для мониторинга всех сообщений
|
|
||||||
- **Очистка чата** - `/clearchat` с настраиваемым сообщением
|
|
||||||
|
|
||||||
### 🌈 Градиентные ники (встроенный LoPreff)
|
|
||||||
- **Кастомные градиенты** - `/color <hex1> [hex2] ...` до 7 цветов
|
|
||||||
- **Кастомные префиксы** - `/prefix <текст>` с поддержкой цветов
|
|
||||||
- **Предустановки** - fire, ocean, forest, sunset, rainbow и другие
|
|
||||||
- **Интеграция с LuckPerms** - автоматическое обновление префиксов
|
|
||||||
- **Покупка за PlayerPoints** - опциональная система оплаты
|
|
||||||
- **GUI подтверждения** - красивый интерфейс для покупок
|
|
||||||
|
|
||||||
### 🎭 Дополнительные возможности
|
|
||||||
- **Головы игроков** - `:head_nickname:` и `@head nickname` в чате
|
|
||||||
- **Кастомные команды** - создание собственных команд через конфиг
|
|
||||||
- **Упоминания** - @nickname с звуковыми уведомлениями
|
|
||||||
- **Интеграция с SkinsRestorer** - поддержка кастомных скинов
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Команды
|
|
||||||
|
|
||||||
### Чат
|
|
||||||
| Команда | Описание | Право |
|
|
||||||
|---------|----------|-------|
|
|
||||||
| `!<сообщение>` | Глобальный чат | `chat.global.use` |
|
|
||||||
| `/g <сообщение>` | Глобальный чат | `chat.global.use` |
|
|
||||||
| `/globalchat toggle` | Переключить глобальный чат | `chat.global.toggle` |
|
|
||||||
| `/l <сообщение>` | Локальный чат | `chat.local.use` |
|
|
||||||
| `/msg <ник> <сообщение>` | Личное сообщение | `chat.pm.use` |
|
|
||||||
| `/reply <сообщение>` | Ответить на последнее PM | `chat.pm.use` |
|
|
||||||
| `/ignore <ник>` | Добавить в игнор | `chat.pm.ignore` |
|
|
||||||
| `/unignore <ник>` | Убрать из игнора | `chat.pm.ignore` |
|
|
||||||
| `/announce <сообщение>` | Объявление | `chat.announce.use` |
|
|
||||||
|
|
||||||
### Модерация
|
|
||||||
| Команда | Описание | Право |
|
|
||||||
|---------|----------|-------|
|
|
||||||
| `/chatspy` | Включить/выключить шпион режим | `chat.spy` |
|
|
||||||
| `/clearchat` | Очистить чат | `chat.clear` |
|
|
||||||
| `/clearchatconfig <действие>` | Настроить сообщение очистки | `lochat.clearchat.config` |
|
|
||||||
| `/lmute <ник> [время] [-s] [причина]` | Замутить игрока | `lochat.mute` |
|
|
||||||
| `/lunmute <ник> [-s]` | Размутить игрока | `lochat.unmute` |
|
|
||||||
| `/lmutelist` | Список замученных | `lochat.mutelist` |
|
|
||||||
| `/lmutehistory <ник>` | История мутов игрока | `lochat.mutehistory` |
|
|
||||||
| `/lmuteblame <модератор>` | Муты выданные модератором | `lochat.muteblame` |
|
|
||||||
|
|
||||||
### Градиентные ники
|
|
||||||
| Команда | Описание | Право |
|
|
||||||
|---------|----------|-------|
|
|
||||||
| `/color <hex1> [hex2] ...` | Установить градиент | `gradient.color` |
|
|
||||||
| `/color on/off/reset` | Управление градиентом | `gradient.color` |
|
|
||||||
| `/color copy <ник>` | Скопировать градиент | `gradient.color` |
|
|
||||||
| `/color preset <название>` | Использовать предустановку | `gradient.color` |
|
|
||||||
| `/prefix <текст>` | Установить префикс | `gradient.prefix` |
|
|
||||||
| `/prefix on/off/reset` | Управление префиксом | `gradient.prefix` |
|
|
||||||
|
|
||||||
### Админские команды
|
|
||||||
| Команда | Описание | Право |
|
|
||||||
|---------|----------|-------|
|
|
||||||
| `/lochat reload` | Перезагрузить конфиг | `lochat.admin` |
|
|
||||||
| `/aprefix <подкоманда>` | Админские команды градиентов | `gradient.admin` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 Права доступа
|
|
||||||
|
|
||||||
### Чат
|
|
||||||
| Право | По умолчанию | Описание |
|
|
||||||
|-------|--------------|----------|
|
|
||||||
| `chat.global.use` | `true` | Использование глобального чата |
|
|
||||||
| `chat.global.toggle` | `true` | Переключение глобального чата |
|
|
||||||
| `chat.local.use` | `true` | Использование локального чата |
|
|
||||||
| `chat.pm.use` | `true` | Личные сообщения |
|
|
||||||
| `chat.pm.ignore` | `true` | Игнорирование игроков |
|
|
||||||
| `chat.mention.use` | `true` | Упоминания @nickname |
|
|
||||||
|
|
||||||
### Модерация
|
|
||||||
| Право | По умолчанию | Описание |
|
|
||||||
|-------|--------------|----------|
|
|
||||||
| `chat.announce.use` | `op` | Объявления |
|
|
||||||
| `chat.spy` | `op` | Шпион режим |
|
|
||||||
| `chat.clear` | `op` | Очистка чата |
|
|
||||||
| `chat.bypass.cooldown` | `op` | Обход кулдаунов |
|
|
||||||
| `chat.bypass.filter` | `op` | Обход фильтра мата |
|
|
||||||
| `chat.bypass.antispam` | `op` | Обход анти-спама |
|
|
||||||
| `lochat.mute` | `op` | Выдача мутов |
|
|
||||||
| `lochat.unmute` | `op` | Снятие мутов |
|
|
||||||
| `lochat.clearchat.config` | `op` | Настройка очистки чата |
|
|
||||||
|
|
||||||
### Градиенты
|
|
||||||
| Право | По умолчанию | Описание |
|
|
||||||
|-------|--------------|----------|
|
|
||||||
| `gradient.color` | `true` | Градиентные ники |
|
|
||||||
| `gradient.prefix` | `true` | Кастомные префиксы |
|
|
||||||
| `gradient.admin` | `op` | Админские команды |
|
|
||||||
| `gradient.bypass.cooldown` | `op` | Обход кулдаунов |
|
|
||||||
| `gradient.bypass.cost` | `op` | Бесплатные градиенты |
|
|
||||||
|
|
||||||
### Дополнительные
|
|
||||||
| Право | По умолчанию | Описание |
|
|
||||||
|-------|--------------|----------|
|
|
||||||
| `lochat.heademoji.use` | `true` | Головы игроков в чате |
|
|
||||||
| `lochat.admin` | `op` | Админские команды |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 PlaceholderAPI
|
|
||||||
|
|
||||||
### Основные плейсхолдеры
|
|
||||||
```
|
```
|
||||||
%lochat_muted% — замучен (true/false)
|
LoChat/ # Root проекта
|
||||||
%lochat_mute_time% — оставшееся время мута
|
│
|
||||||
%lochat_mute_reason% — причина мута
|
├── src/main/ # LoChat (основной модуль)
|
||||||
%lochat_ignored_count% — количество игнорируемых
|
│ ├── java/com/loki/lochat/
|
||||||
%lochat_global_enabled% — глобальный чат включен
|
│ │ ├── api/ # API интерфейсы
|
||||||
%lochat_spy_enabled% — шпион режим включен
|
│ │ │ ├── filter/ # Фильтры сообщений
|
||||||
|
│ │ │ └── service/ # Сервисы (Chat, Mute, PM и т.д.)
|
||||||
|
│ │ ├── commands/ # Команды плагина
|
||||||
|
│ │ ├── config/ # Конфигурация
|
||||||
|
│ │ ├── core/ # Реализации
|
||||||
|
│ │ │ ├── filter/ # Реализации фильтров
|
||||||
|
│ │ │ ├── registry/ # ServiceRegistry (DI)
|
||||||
|
│ │ │ └── service/ # Реализации сервисов
|
||||||
|
│ │ ├── gradient/ # Градиентные ники
|
||||||
|
│ │ │ ├── commands/ # Команды градиентов
|
||||||
|
│ │ │ ├── config/ # Конфиг градиентов
|
||||||
|
│ │ │ ├── hook/ # LuckPerms интеграция
|
||||||
|
│ │ │ └── util/ # Утилиты градиентов
|
||||||
|
│ │ ├── listener/ # Слушатели событий
|
||||||
|
│ │ ├── util/ # Утилиты (Folia)
|
||||||
|
│ │ ├── utils/ # Утилиты (Formatter, Mention)
|
||||||
|
│ │ └── LoChat.java # Главный класс
|
||||||
|
│ └── resources/ # Конфиги
|
||||||
|
│ ├── config.yml # Основной конфиг
|
||||||
|
│ ├── messages.yml # Сообщения
|
||||||
|
│ ├── chat.yml # Настройки чата
|
||||||
|
│ ├── gradient-config.yml # Конфиг градиентов
|
||||||
|
│ ├── gradient-messages.yml
|
||||||
|
│ ├── custom-commands.yml
|
||||||
|
│ ├── automessages.yml
|
||||||
|
│ └── plugin.yml
|
||||||
|
│
|
||||||
|
├── lohub/ # LoHub модуль
|
||||||
|
│ ├── src/main/
|
||||||
|
│ │ ├── java/com/loki/lohub/
|
||||||
|
│ │ │ ├── actions/ # Action система
|
||||||
|
│ │ │ │ ├── impl/ # Реализации действий
|
||||||
|
│ │ │ │ ├── Action.java
|
||||||
|
│ │ │ │ └── ActionType.java
|
||||||
|
│ │ │ ├── commands/ # Команды
|
||||||
|
│ │ │ │ ├── HubCommand.java
|
||||||
|
│ │ │ │ ├── SetHubCommand.java
|
||||||
|
│ │ │ │ └── LoHubCommand.java
|
||||||
|
│ │ │ ├── config/ # Конфигурация
|
||||||
|
│ │ │ │ └── ConfigManager.java
|
||||||
|
│ │ │ ├── listeners/ # Слушатели
|
||||||
|
│ │ │ │ ├── HubProtectionListener.java
|
||||||
|
│ │ │ │ ├── PlayerJoinListener.java
|
||||||
|
│ │ │ │ ├── LaunchpadListener.java
|
||||||
|
│ │ │ │ └── DoubleJumpListener.java
|
||||||
|
│ │ │ ├── managers/ # Менеджеры
|
||||||
|
│ │ │ │ ├── SpawnManager.java
|
||||||
|
│ │ │ │ ├── RegionManager.java
|
||||||
|
│ │ │ │ ├── ActionManager.java
|
||||||
|
│ │ │ │ ├── CooldownManager.java
|
||||||
|
│ │ │ │ ├── HotbarManager.java
|
||||||
|
│ │ │ │ └── PlayerHiderManager.java
|
||||||
|
│ │ │ ├── utils/ # Утилиты
|
||||||
|
│ │ │ │ ├── ItemBuilder.java
|
||||||
|
│ │ │ │ ├── TextUtil.java
|
||||||
|
│ │ │ │ └── PlaceholderUtil.java
|
||||||
|
│ │ │ └── LoHub.java # Главный класс
|
||||||
|
│ │ └── resources/ # Конфиги
|
||||||
|
│ │ ├── config.yml
|
||||||
|
│ │ └── plugin.yml
|
||||||
|
│ └── build.gradle.kts # Gradle конфиг
|
||||||
|
│
|
||||||
|
├── lovelocity/ # LoVelocity модуль
|
||||||
|
│ ├── src/main/
|
||||||
|
│ │ ├── java/com/loki/lovelocity/
|
||||||
|
│ │ │ ├── commands/
|
||||||
|
│ │ │ │ └── HubCommand.java
|
||||||
|
│ │ │ └── LoVelocity.java
|
||||||
|
│ │ └── resources/
|
||||||
|
│ │ └── velocity-plugin.json
|
||||||
|
│ └── build.gradle.kts
|
||||||
|
│
|
||||||
|
├── build.gradle.kts # Root Gradle (Kotlin DSL)
|
||||||
|
├── settings.gradle.kts # Настройки проектов
|
||||||
|
├── gradle.properties # Gradle свойства
|
||||||
|
├── TODO.md # Список задач
|
||||||
|
└── README.md # Этот файл
|
||||||
```
|
```
|
||||||
|
|
||||||
### Градиентные плейсхолдеры
|
## 🚀 Сборка
|
||||||
```
|
|
||||||
%lochat_gradient_full% — полное имя (префикс + ник)
|
```bash
|
||||||
%lochat_gradient_name% — только градиентный ник
|
# Собрать все модули
|
||||||
%lochat_gradient_prefix% — только префикс
|
./gradlew build
|
||||||
%lochat_full% — алиас для gradient_full
|
|
||||||
%lochat_name% — алиас для gradient_name
|
# Собрать только LoChat
|
||||||
%lochat_prefix% — алиас для gradient_prefix
|
./gradlew :build
|
||||||
|
|
||||||
|
# Собрать только LoHub
|
||||||
|
./gradlew :lohub:build
|
||||||
|
|
||||||
|
# Собрать только LoVelocity
|
||||||
|
./gradlew :lovelocity:build
|
||||||
|
|
||||||
|
# Без тестов
|
||||||
|
./gradlew build -x test
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 📦 Артефакты
|
||||||
|
|
||||||
## 📁 Конфигурационные файлы
|
После сборки jar файлы находятся в:
|
||||||
|
- `build/libs/LoChat-1.5.5-1.20.1.jar`
|
||||||
| Файл | Описание |
|
- `lohub/build/libs/LoHub-1.5.5-1.20.1.jar`
|
||||||
|------|----------|
|
- `lovelocity/build/libs/LoVelocity-1.5.5.jar`
|
||||||
| `config.yml` | Основные настройки плагина |
|
|
||||||
| `messages.yml` | Сообщения плагина |
|
|
||||||
| `automessages.yml` | Настройки автосообщений |
|
|
||||||
| `emojis.yml` | Список смайликов |
|
|
||||||
| `custom-commands.yml` | Кастомные команды |
|
|
||||||
| `gradient-config.yml` | Настройки градиентов |
|
|
||||||
| `gradient-messages.yml` | Сообщения градиентного модуля |
|
|
||||||
| `ignores.yml` | Сохранённые игноры (создается автоматически) |
|
|
||||||
| `mutes.yml` | Активные муты (создается автоматически) |
|
|
||||||
| `gradient-data.yml` | Данные градиентов (создается автоматически) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Кастомные команды
|
|
||||||
|
|
||||||
LoChat позволяет создавать собственные команды через `custom-commands.yml`. После изменения файла выполните `/lochat reload`.
|
|
||||||
|
|
||||||
### Структура команды
|
|
||||||
```yaml
|
|
||||||
command-name:
|
|
||||||
permission: "право.для.использования" # Опционально
|
|
||||||
message: "Текст сообщения" # Обязательно
|
|
||||||
aliases: ["alias1", "alias2"] # Опционально
|
|
||||||
type: "chat" # chat, broadcast, title, actionbar
|
|
||||||
target: "sender" # sender, all, permission:право
|
|
||||||
```
|
|
||||||
|
|
||||||
### Типы команд
|
|
||||||
- **chat** — отправляет сообщение в чат
|
|
||||||
- **broadcast** — отправляет сообщение всем игрокам
|
|
||||||
- **title** — показывает title (разделение строк через `\n`)
|
|
||||||
- **actionbar** — показывает сообщение в actionbar
|
|
||||||
|
|
||||||
### Целевая аудитория
|
|
||||||
- **sender** — только отправителю команды
|
|
||||||
- **all** — всем игрокам на сервере
|
|
||||||
- **permission:право** — только игрокам с указанным правом
|
|
||||||
|
|
||||||
### Плейсхолдеры в командах
|
|
||||||
- `{player}` — имя игрока
|
|
||||||
- `{x}`, `{y}`, `{z}` — координаты игрока
|
|
||||||
- `{world}` — название мира
|
|
||||||
- `{arg0}`, `{arg1}`, `{arg2}` — аргументы команды
|
|
||||||
- `{args}` — все аргументы через пробел
|
|
||||||
- Все плейсхолдеры PlaceholderAPI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎭 Головы игроков
|
|
||||||
|
|
||||||
### Использование
|
|
||||||
- `:head_nickname:` — показать ASCII голову игрока
|
|
||||||
- `@head nickname` — показать интерактивную голову с информацией
|
|
||||||
|
|
||||||
### Требования
|
|
||||||
- Право `lochat.heademoji.use`
|
|
||||||
- Включенная настройка `head-emoji.enabled: true`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Форматирование цветов
|
|
||||||
|
|
||||||
### MiniMessage (рекомендуется)
|
|
||||||
```
|
|
||||||
<red>Красный текст</red>
|
|
||||||
<gradient:red:blue>Градиент от красного к синему</gradient>
|
|
||||||
<rainbow>Радужный текст</rainbow>
|
|
||||||
<bold>Жирный</bold>
|
|
||||||
<italic>Курсив</italic>
|
|
||||||
```
|
|
||||||
|
|
||||||
### HEX цвета
|
|
||||||
```
|
|
||||||
<#FF0000>Красный через HEX</color>
|
|
||||||
<gradient:#FF0000:#0000FF>HEX градиент</gradient>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Legacy коды (поддерживаются)
|
|
||||||
```
|
|
||||||
&cКрасный
|
|
||||||
&aЗеленый
|
|
||||||
&lЖирный
|
|
||||||
&nПодчеркнутый
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Требования
|
## 🔧 Требования
|
||||||
|
|
||||||
### Обязательные
|
- **Java 21+**
|
||||||
- **Paper 1.20.1** или новее
|
- **Paper 1.20.1+** (для LoChat и LoHub)
|
||||||
- **Java 21**
|
- **Velocity 3.3.0+** (для LoVelocity)
|
||||||
|
- **Gradle 9.1+**
|
||||||
|
|
||||||
### Опциональные
|
## 📝 Зависимости
|
||||||
- **PlaceholderAPI** — для плейсхолдеров
|
|
||||||
- **PlayerPoints** — для покупки градиентов за поинты
|
|
||||||
- **LuckPerms** — для интеграции с префиксами
|
|
||||||
- **SkinsRestorer** — для поддержки кастомных скинов
|
|
||||||
- **LibertyBans** — для интеграции с банами
|
|
||||||
|
|
||||||
---
|
### LoChat
|
||||||
|
- PlaceholderAPI (опционально)
|
||||||
|
- LuckPerms (опционально)
|
||||||
|
- PlayerPoints (опционально)
|
||||||
|
|
||||||
## 🏗️ Сборка
|
### LoHub
|
||||||
|
- WorldGuard 7.0.9+ (опционально)
|
||||||
|
- PlaceholderAPI (опционально)
|
||||||
|
|
||||||
```bash
|
### LoVelocity
|
||||||
./gradlew clean build
|
- Нет зависимостей
|
||||||
```
|
|
||||||
|
|
||||||
Готовый jar файл: `build/libs/LoChat-1.5.5-1.20.1.jar`
|
## 🎯 Статус разработки
|
||||||
|
|
||||||
---
|
- ✅ **LoChat** - Готов к использованию
|
||||||
|
- 🔄 **LoHub** - В разработке (21% готово)
|
||||||
|
- ✅ **LoVelocity** - Готов к использованию
|
||||||
|
|
||||||
## 📝 Changelog
|
Подробности в [TODO.md](TODO.md)
|
||||||
|
|
||||||
### v1.5.5-1.20.1
|
## 📄 Лицензия
|
||||||
- ✅ Адаптация для Minecraft 1.20.1 Paper
|
|
||||||
- ✅ Исправлен фильтр мата с поддержкой обходов
|
|
||||||
- ✅ Исправлена команда spy для мониторинга всех сообщений
|
|
||||||
- ✅ Добавлена поддержка голов игроков в чате
|
|
||||||
- ✅ Добавлена команда настройки сообщения очистки чата
|
|
||||||
- ✅ Интеграция с SkinsRestorer
|
|
||||||
- ✅ Улучшено качество кода (SOLID, DRY, KISS)
|
|
||||||
- ✅ Обновлены все конфиги в едином стиле
|
|
||||||
- ✅ Исправлены все предупреждения компилятора
|
|
||||||
|
|
||||||
---
|
Proprietary - Все права защищены
|
||||||
|
|
||||||
## 🤝 Поддержка
|
## 👥 Авторы
|
||||||
|
|
||||||
Если у вас возникли проблемы или вопросы:
|
Loki Development Team
|
||||||
1. Проверьте конфигурационные файлы
|
|
||||||
2. Убедитесь в совместимости версий
|
|
||||||
3. Проверьте права доступа игроков
|
|
||||||
4. Создайте issue в репозитории
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**© 2024 loki. Все права защищены.**
|
|
||||||
|
|
|
||||||
|
|
@ -1,365 +0,0 @@
|
||||||
# План рефакторинга LoChat
|
|
||||||
|
|
||||||
## Цель
|
|
||||||
Убрать legacy код, перевести всё на новую архитектуру (SOLID + DI через ServiceRegistry)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Текущая структура
|
|
||||||
|
|
||||||
### ✅ Новая архитектура (уже готово)
|
|
||||||
```
|
|
||||||
src/main/java/com/loki/lochat/
|
|
||||||
├── api/ # Интерфейсы сервисов
|
|
||||||
│ ├── service/
|
|
||||||
│ │ ├── ChatService.java
|
|
||||||
│ │ ├── MuteService.java
|
|
||||||
│ │ ├── AntiSpamService.java
|
|
||||||
│ │ ├── CooldownService.java
|
|
||||||
│ │ ├── MessageService.java
|
|
||||||
│ │ └── PlayerDataService.java
|
|
||||||
│ └── filter/
|
|
||||||
│ └── MessageFilter.java
|
|
||||||
├── core/ # Реализации
|
|
||||||
│ ├── service/
|
|
||||||
│ │ ├── ChatServiceImpl.java
|
|
||||||
│ │ ├── MuteServiceImpl.java
|
|
||||||
│ │ ├── AntiSpamServiceImpl.java
|
|
||||||
│ │ ├── CooldownServiceImpl.java
|
|
||||||
│ │ ├── MessageServiceImpl.java
|
|
||||||
│ │ └── PlayerDataServiceImpl.java
|
|
||||||
│ ├── filter/
|
|
||||||
│ │ ├── MuteFilter.java
|
|
||||||
│ │ ├── CooldownFilter.java
|
|
||||||
│ │ ├── SpamFilter.java
|
|
||||||
│ │ └── WordFilter.java
|
|
||||||
│ └── registry/
|
|
||||||
│ └── ServiceRegistry.java
|
|
||||||
└── listener/ # Новые слушатели
|
|
||||||
├── ChatEventListener.java (35 строк)
|
|
||||||
└── PlayerEventListener.java (23 строки)
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Legacy код (нужно удалить/переписать)
|
|
||||||
```
|
|
||||||
src/main/java/com/loki/lochat/
|
|
||||||
├── managers/ # Старые менеджеры
|
|
||||||
│ ├── MuteManager.java (409 строк) ❌
|
|
||||||
│ ├── ChatManager.java (131 строк) ❌
|
|
||||||
│ ├── AntiSpamManager.java ❌
|
|
||||||
│ ├── CooldownManager.java ❌
|
|
||||||
│ ├── PMManager.java ❌
|
|
||||||
│ ├── IgnoreManager.java ❌
|
|
||||||
│ ├── SpyManager.java ❌
|
|
||||||
│ ├── MentionManager.java (123 строки) ❌
|
|
||||||
│ ├── AutoMessageManager.java (159 строк) ⚠️
|
|
||||||
│ └── CustomCommandManager.java (269 строк) ⚠️
|
|
||||||
└── listeners/
|
|
||||||
└── ChatListener.java (185 строк) ❌
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 1: Анализ зависимостей
|
|
||||||
|
|
||||||
### 1.1 Проверить что использует legacy менеджеры
|
|
||||||
```bash
|
|
||||||
# Найти все вызовы getMuteManager(), getChatManager() и т.д.
|
|
||||||
grep -r "getMuteManager\|getChatManager\|getAntiSpamManager" src/main/java/com/loki/lochat/commands/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Составить список команд для миграции
|
|
||||||
- [x] ChatEventListener - уже использует ServiceRegistry
|
|
||||||
- [ ] MuteCommand - использует MuteManager
|
|
||||||
- [ ] UnmuteCommand - использует MuteManager
|
|
||||||
- [ ] MuteListCommand - использует MuteManager
|
|
||||||
- [ ] MuteHistoryCommand - использует MuteManager
|
|
||||||
- [ ] MuteBlameCommand - использует MuteManager
|
|
||||||
- [ ] GlobalChatCommand - использует ChatManager
|
|
||||||
- [ ] LocalChatCommand - использует ChatManager
|
|
||||||
- [ ] MsgCommand - использует PMManager, IgnoreManager
|
|
||||||
- [ ] ReplyCommand - использует PMManager, IgnoreManager
|
|
||||||
- [ ] IgnoreCommand - использует IgnoreManager
|
|
||||||
- [ ] UnignoreCommand - использует IgnoreManager
|
|
||||||
- [ ] ChatSpyCommand - использует SpyManager
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 2: Создание новых сервисов
|
|
||||||
|
|
||||||
### 2.1 PMService (личные сообщения)
|
|
||||||
```java
|
|
||||||
// api/service/PMService.java
|
|
||||||
public interface PMService {
|
|
||||||
void sendPrivateMessage(Player sender, Player receiver, String message);
|
|
||||||
Optional<UUID> getLastConversation(UUID player);
|
|
||||||
void setLastConversation(UUID player, UUID target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// core/service/PMServiceImpl.java
|
|
||||||
public class PMServiceImpl implements PMService {
|
|
||||||
private final Map<UUID, UUID> conversations = new ConcurrentHashMap<>();
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 IgnoreService (игнорирование)
|
|
||||||
```java
|
|
||||||
// api/service/IgnoreService.java
|
|
||||||
public interface IgnoreService {
|
|
||||||
boolean isIgnoring(UUID player, UUID target);
|
|
||||||
void addIgnore(UUID player, UUID target);
|
|
||||||
void removeIgnore(UUID player, UUID target);
|
|
||||||
int getIgnoredCount(UUID player);
|
|
||||||
}
|
|
||||||
|
|
||||||
// core/service/IgnoreServiceImpl.java
|
|
||||||
public class IgnoreServiceImpl implements IgnoreService {
|
|
||||||
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 SpyService (шпионаж за чатом)
|
|
||||||
```java
|
|
||||||
// api/service/SpyService.java
|
|
||||||
public interface SpyService {
|
|
||||||
boolean toggleSpy(UUID player);
|
|
||||||
boolean isSpying(UUID player);
|
|
||||||
void sendToSpies(Player sender, Object message, boolean isGlobal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// core/service/SpyServiceImpl.java
|
|
||||||
public class SpyServiceImpl implements SpyService {
|
|
||||||
private final Set<UUID> spies = ConcurrentHashMap.newKeySet();
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 MentionService (упоминания)
|
|
||||||
```java
|
|
||||||
// api/service/MentionService.java
|
|
||||||
public interface MentionService {
|
|
||||||
String processMentions(String message, Set<Player> mentioned);
|
|
||||||
void notifyMentioned(Set<Player> players);
|
|
||||||
}
|
|
||||||
|
|
||||||
// core/service/MentionServiceImpl.java
|
|
||||||
public class MentionServiceImpl implements MentionService {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 3: Миграция команд
|
|
||||||
|
|
||||||
### 3.1 Mute команды
|
|
||||||
**Приоритет: ВЫСОКИЙ** (используют MuteManager 409 строк)
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Было:
|
|
||||||
public class MuteCommand {
|
|
||||||
private final LoChat plugin;
|
|
||||||
// plugin.getMuteManager().mute(...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Станет:
|
|
||||||
public class MuteCommand {
|
|
||||||
private final MuteService muteService;
|
|
||||||
|
|
||||||
public MuteCommand(LoChat plugin) {
|
|
||||||
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Файлы для миграции:**
|
|
||||||
- [ ] MuteCommand.java
|
|
||||||
- [ ] UnmuteCommand.java
|
|
||||||
- [ ] MuteListCommand.java
|
|
||||||
- [ ] MuteHistoryCommand.java
|
|
||||||
- [ ] MuteBlameCommand.java
|
|
||||||
|
|
||||||
### 3.2 Chat команды
|
|
||||||
**Приоритет: ВЫСОКИЙ**
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Было:
|
|
||||||
plugin.getChatManager().sendGlobalMessage(...)
|
|
||||||
|
|
||||||
// Станет:
|
|
||||||
ChatService chatService = plugin.getServiceRegistry().get(ChatService.class);
|
|
||||||
chatService.sendGlobalMessage(...)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Файлы для миграции:**
|
|
||||||
- [ ] GlobalChatCommand.java
|
|
||||||
- [ ] LocalChatCommand.java
|
|
||||||
|
|
||||||
### 3.3 PM команды
|
|
||||||
**Приоритет: СРЕДНИЙ**
|
|
||||||
|
|
||||||
**Файлы для миграции:**
|
|
||||||
- [ ] MsgCommand.java
|
|
||||||
- [ ] ReplyCommand.java
|
|
||||||
|
|
||||||
### 3.4 Ignore команды
|
|
||||||
**Приоритет: СРЕДНИЙ**
|
|
||||||
|
|
||||||
**Файлы для миграции:**
|
|
||||||
- [ ] IgnoreCommand.java
|
|
||||||
- [ ] UnignoreCommand.java
|
|
||||||
|
|
||||||
### 3.5 Spy команды
|
|
||||||
**Приоритет: НИЗКИЙ**
|
|
||||||
|
|
||||||
**Файлы для миграции:**
|
|
||||||
- [ ] ChatSpyCommand.java
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 4: Удаление legacy кода
|
|
||||||
|
|
||||||
### 4.1 Удалить старые менеджеры
|
|
||||||
После миграции всех команд:
|
|
||||||
```bash
|
|
||||||
rm src/main/java/com/loki/lochat/managers/MuteManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/ChatManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/AntiSpamManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/CooldownManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/PMManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/IgnoreManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/SpyManager.java
|
|
||||||
rm src/main/java/com/loki/lochat/managers/MentionManager.java
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Удалить старый ChatListener
|
|
||||||
```bash
|
|
||||||
rm src/main/java/com/loki/lochat/listeners/ChatListener.java
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Очистить LoChat.java
|
|
||||||
Удалить все legacy геттеры:
|
|
||||||
```java
|
|
||||||
// Удалить:
|
|
||||||
public PMManager getPmManager() { return pmManager; }
|
|
||||||
public IgnoreManager getIgnoreManager() { return ignoreManager; }
|
|
||||||
public SpyManager getSpyManager() { return spyManager; }
|
|
||||||
public MuteManager getMuteManager() { return muteManager; }
|
|
||||||
public ChatManager getChatManager() { return chatManager; }
|
|
||||||
public AntiSpamManager getAntiSpamManager() { return antiSpamManager; }
|
|
||||||
public CooldownManager getCooldownManager() { return cooldownManager; }
|
|
||||||
public MentionManager getMentionManager() { return mentionManager; }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 5: Оптимизация больших классов
|
|
||||||
|
|
||||||
### 5.1 ConfigManager (270 строк)
|
|
||||||
**Проблема:** Слишком много методов
|
|
||||||
|
|
||||||
**Решение:** Разбить на категории
|
|
||||||
```java
|
|
||||||
// config/ChatConfig.java - настройки чата
|
|
||||||
// config/MuteConfig.java - настройки мутов
|
|
||||||
// config/SpamConfig.java - настройки антиспама
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 CustomCommandManager (269 строк)
|
|
||||||
**Проблема:** Много логики в одном классе
|
|
||||||
|
|
||||||
**Решение:** Вынести парсинг команд
|
|
||||||
```java
|
|
||||||
// commands/custom/CustomCommandParser.java
|
|
||||||
// commands/custom/CustomCommandExecutor.java
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 GradientModule (249 строк)
|
|
||||||
**Статус:** Оставить как есть (это отдельный модуль)
|
|
||||||
|
|
||||||
### 5.4 AutoMessageManager (159 строк)
|
|
||||||
**Статус:** Оставить как есть (нормальный размер)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 6: Тестирование
|
|
||||||
|
|
||||||
### 6.1 Написать тесты для новых сервисов
|
|
||||||
```java
|
|
||||||
// test/java/com/loki/lochat/core/service/
|
|
||||||
├── PMServiceImplTest.java
|
|
||||||
├── IgnoreServiceImplTest.java
|
|
||||||
├── SpyServiceImplTest.java
|
|
||||||
└── MentionServiceImplTest.java
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Интеграционные тесты
|
|
||||||
```java
|
|
||||||
// test/java/com/loki/lochat/integration/
|
|
||||||
└── ChatFlowTest.java - тест полного флоу отправки сообщения
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Этап 7: Финальная проверка
|
|
||||||
|
|
||||||
### 7.1 Проверить размеры классов
|
|
||||||
```bash
|
|
||||||
# Все классы должны быть < 150 строк (кроме модулей)
|
|
||||||
find src/main/java -name "*.java" -exec wc -l {} \; | sort -rn | head -20
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 Проверить архитектуру
|
|
||||||
- [ ] Все сервисы через интерфейсы
|
|
||||||
- [ ] Все зависимости через ServiceRegistry
|
|
||||||
- [ ] Нет прямых вызовов new Service()
|
|
||||||
- [ ] Все команды используют DI
|
|
||||||
|
|
||||||
### 7.3 Проверить производительность
|
|
||||||
- [ ] Нет утечек памяти
|
|
||||||
- [ ] Нет блокирующих операций в главном потоке
|
|
||||||
- [ ] Folia совместимость
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Метрики успеха
|
|
||||||
|
|
||||||
### До рефакторинга
|
|
||||||
- Главный класс: 166 строк
|
|
||||||
- Legacy менеджеры: 8 классов, ~1500 строк
|
|
||||||
- Старый ChatListener: 185 строк
|
|
||||||
- Дублирование кода: высокое
|
|
||||||
- Тестовое покрытие: 52 теста
|
|
||||||
|
|
||||||
### После рефакторинга (цель)
|
|
||||||
- Главный класс: <100 строк
|
|
||||||
- Новые сервисы: 8 интерфейсов + 8 реализаций, ~800 строк
|
|
||||||
- Новые слушатели: 2 класса, ~60 строк
|
|
||||||
- Дублирование кода: минимальное
|
|
||||||
- Тестовое покрытие: >80 тестов
|
|
||||||
- Все классы: <150 строк
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Порядок выполнения
|
|
||||||
|
|
||||||
1. ✅ Создать новую архитектуру (api + core + registry)
|
|
||||||
2. ✅ Мигрировать ChatEventListener
|
|
||||||
3. ✅ Удалить эмодзи
|
|
||||||
4. **→ Создать PMService, IgnoreService, SpyService, MentionService**
|
|
||||||
5. **→ Мигрировать все команды на новые сервисы**
|
|
||||||
6. **→ Удалить legacy менеджеры**
|
|
||||||
7. **→ Оптимизировать большие классы**
|
|
||||||
8. **→ Написать тесты**
|
|
||||||
9. **→ Финальная проверка**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Примечания
|
|
||||||
|
|
||||||
- Миграция должна быть постепенной (по одной команде)
|
|
||||||
- После каждого шага - компиляция и тесты
|
|
||||||
- Сохранять обратную совместимость до полной миграции
|
|
||||||
- Документировать изменения в CHANGELOG.md
|
|
||||||
227
TODO.md
227
TODO.md
|
|
@ -1,102 +1,167 @@
|
||||||
# LoChat - Статус проекта
|
# 🚀 Loki Plugins - TODO
|
||||||
|
|
||||||
## ✅ Готово
|
## 📦 Проекты
|
||||||
|
|
||||||
### Чат
|
### 1. LoChat - Чат плагин (Paper/Folia)
|
||||||
- [x] Глобальный чат (`!сообщение`, `/g`)
|
**Статус:** ✅ Готов к использованию
|
||||||
- [x] Локальный чат (100 блоков)
|
|
||||||
- [x] PM система (`/msg`, `/reply`)
|
|
||||||
- [x] Игнор (`/ignore`, `/unignore`) — сохраняется в файл
|
|
||||||
- [x] Упоминания @ник со звуком
|
|
||||||
|
|
||||||
### Модерация
|
#### Функции
|
||||||
- [x] Мут система (`/mute`, `/unmute`) — сохраняется в файл
|
- ✅ Глобальный/локальный чат
|
||||||
- [x] Фильтр мата (censor/block/warn)
|
- ✅ PM система с игнором
|
||||||
- [x] Анти-спам (CAPS, повторы, похожие)
|
- ✅ Мут система с историей
|
||||||
- [x] Кулдауны на сообщения
|
- ✅ Фильтры (мат, спам, CAPS)
|
||||||
- [x] Чат-шпион (`/chatspy`)
|
- ✅ Градиентные ники (LuckPerms интеграция)
|
||||||
- [x] Очистка чата (`/clearchat`)
|
- ✅ Смайлики (300+ эмодзи)
|
||||||
|
- ✅ PlaceholderAPI
|
||||||
### Градиентные ники (LoPreff интеграция)
|
- ✅ Markdown форматирование (**bold**, *italic*, ~~strike~~, `code`)
|
||||||
- [x] Команды `/color`, `/prefix`
|
- ✅ Упоминания (@player, @everyone, @here, @role:permission)
|
||||||
- [x] Градиенты на ники и префиксы
|
- ✅ Фильтры URL/IP/повторов
|
||||||
- [x] Интеграция с LuckPerms
|
|
||||||
- [x] PlayerPoints поддержка
|
|
||||||
- [x] TAB плагин совместимость (`%lochat_full%`)
|
|
||||||
- [x] Админ команды `/aprefix`
|
|
||||||
|
|
||||||
### Дополнительно
|
|
||||||
- [x] Цвет чата (`/chatcolor`)
|
|
||||||
- [x] Смайлики (300+ эмодзи)
|
|
||||||
- [x] Кастомные команды
|
|
||||||
- [x] Автосообщения
|
|
||||||
|
|
||||||
### Система
|
|
||||||
- [x] Объявления (Title + ActionBar)
|
|
||||||
- [x] PlaceholderAPI интеграция
|
|
||||||
- [x] HEX цвета `&#RRGGBB` и `§x§...`
|
|
||||||
- [x] MiniMessage форматирование
|
|
||||||
- [x] Folia совместимость
|
|
||||||
- [x] Горячая перезагрузка
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 PlaceholderAPI
|
### 2. LoHub - Hub плагин (Paper/Folia)
|
||||||
|
**Статус:** 🔄 В разработке (21% готово)
|
||||||
|
|
||||||
|
#### ✅ Готово
|
||||||
|
- ✅ Spawn система с телепортом
|
||||||
|
- ✅ WorldGuard регионы (авто-создание)
|
||||||
|
- ✅ Защита мира (блоки, PvP, мобы)
|
||||||
|
- ✅ Команды /hub, /sethub, /lohub
|
||||||
|
- ✅ Базовые утилиты (ItemBuilder, TextUtil, PlaceholderUtil)
|
||||||
|
|
||||||
|
#### 🔄 В работе - ЭТАП 2: Action система
|
||||||
|
- [ ] Action интерфейс и ActionType enum
|
||||||
|
- [ ] MessageAction - отправка сообщений
|
||||||
|
- [ ] SoundAction - звуки
|
||||||
|
- [ ] TitleAction - title/subtitle
|
||||||
|
- [ ] CommandAction - команды от игрока
|
||||||
|
- [ ] ConsoleAction - команды от консоли
|
||||||
|
- [ ] GamemodeAction - смена режима
|
||||||
|
- [ ] EffectAction - эффекты
|
||||||
|
- [ ] ActionManager - парсинг и выполнение
|
||||||
|
|
||||||
|
#### 📋 Запланировано
|
||||||
|
**ЭТАП 3: Join система**
|
||||||
|
- [ ] PlayerJoinListener
|
||||||
|
- [ ] Телепорт на спавн при входе
|
||||||
|
- [ ] Хил и очистка эффектов
|
||||||
|
- [ ] Кастомные сообщения входа/выхода
|
||||||
|
- [ ] Интеграция с Action системой
|
||||||
|
|
||||||
|
**ЭТАП 4: Custom Join Items**
|
||||||
|
- [ ] HotbarManager
|
||||||
|
- [ ] Выдача предметов при входе
|
||||||
|
- [ ] Обработка кликов (actions)
|
||||||
|
- [ ] Защита от выбрасывания
|
||||||
|
|
||||||
|
**ЭТАП 5: Launchpad**
|
||||||
|
- [ ] LaunchpadListener
|
||||||
|
- [ ] Прыжковые площадки (плита + редстоун)
|
||||||
|
|
||||||
|
**ЭТАП 6: Double Jump**
|
||||||
|
- [ ] DoubleJumpListener
|
||||||
|
- [ ] CooldownManager
|
||||||
|
- [ ] Двойной прыжок в воздухе
|
||||||
|
|
||||||
|
**ЭТАП 7: Player Hider**
|
||||||
|
- [ ] PlayerHiderManager
|
||||||
|
- [ ] Скрытие/показ игроков
|
||||||
|
- [ ] Предмет для переключения
|
||||||
|
|
||||||
|
**ЭТАП 8-10: Опционально**
|
||||||
|
- [ ] Scoreboard модуль
|
||||||
|
- [ ] Tablist модуль
|
||||||
|
- [ ] Announcements модуль
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. LoVelocity - Velocity плагин
|
||||||
|
**Статус:** ✅ Готов
|
||||||
|
|
||||||
|
#### Функции
|
||||||
|
- ✅ Команда /hub для телепорта на lobby/hub сервер
|
||||||
|
- ✅ Кастомные сообщения
|
||||||
|
- ✅ Поддержка PlaceholderAPI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Структура проекта
|
||||||
|
|
||||||
```
|
```
|
||||||
%lochat_muted% — замучен (true/false)
|
LoChat/ # Root проекта
|
||||||
%lochat_mute_time% — время мута
|
├── src/main/ # LoChat (основной модуль)
|
||||||
%lochat_mute_reason% — причина мута
|
│ ├── java/com/loki/lochat/
|
||||||
%lochat_ignored_count% — кол-во игнорируемых
|
│ │ ├── api/ # API интерфейсы
|
||||||
%lochat_global_enabled% — глобальный чат вкл
|
│ │ ├── commands/ # Команды
|
||||||
%lochat_last_pm% — последний собеседник
|
│ │ ├── config/ # Конфигурация
|
||||||
%lochat_spy_enabled% — шпион режим
|
│ │ ├── core/ # Реализации
|
||||||
%lochat_full% — префикс + градиентный ник (для TAB)
|
│ │ ├── gradient/ # Градиентные ники
|
||||||
%lochat_name% — только градиентный ник
|
│ │ ├── listener/ # Слушатели событий
|
||||||
%lochat_prefix% — только префикс с градиентом
|
│ │ ├── utils/ # Утилиты
|
||||||
%lochat_lp_prefix% — LuckPerms префикс
|
│ │ └── LoChat.java # Главный класс
|
||||||
|
│ └── resources/ # Конфиги
|
||||||
|
│
|
||||||
|
├── lohub/ # LoHub модуль
|
||||||
|
│ ├── src/main/
|
||||||
|
│ │ ├── java/com/loki/lohub/
|
||||||
|
│ │ │ ├── actions/ # Action система
|
||||||
|
│ │ │ ├── commands/ # Команды
|
||||||
|
│ │ │ ├── config/ # Конфигурация
|
||||||
|
│ │ │ ├── listeners/ # Слушатели
|
||||||
|
│ │ │ ├── managers/ # Менеджеры
|
||||||
|
│ │ │ ├── utils/ # Утилиты
|
||||||
|
│ │ │ └── LoHub.java # Главный класс
|
||||||
|
│ │ └── resources/ # Конфиги
|
||||||
|
│ └── build.gradle.kts # Gradle конфиг
|
||||||
|
│
|
||||||
|
├── lovelocity/ # LoVelocity модуль
|
||||||
|
│ ├── src/main/
|
||||||
|
│ │ └── java/com/loki/lovelocity/
|
||||||
|
│ └── build.gradle.kts # Gradle конфиг
|
||||||
|
│
|
||||||
|
├── build.gradle.kts # Root Gradle (Kotlin DSL)
|
||||||
|
├── settings.gradle.kts # Настройки проектов
|
||||||
|
└── TODO.md # Этот файл
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Файлы данных
|
## 🎯 Текущий фокус
|
||||||
|
|
||||||
- `ignores.yml` — список игнорируемых игроков
|
**LoHub - ЭТАП 2: Action система**
|
||||||
- `mutes.yml` — активные муты
|
|
||||||
- `chatcolors.yml` — цвета чата игроков
|
Создание гибкой системы действий для настройки поведения через конфиг:
|
||||||
- `gradient-data.yml` — данные градиентных ников
|
|
||||||
|
```yaml
|
||||||
|
actions:
|
||||||
|
- "[MESSAGE] &aПривет, %player%!"
|
||||||
|
- "[SOUND] ENTITY_PLAYER_LEVELUP"
|
||||||
|
- "[TITLE] &b&lДобро пожаловать;&fНа сервер;1;3;1"
|
||||||
|
- "[COMMAND] spawn"
|
||||||
|
- "[CONSOLE] give %player% diamond 1"
|
||||||
|
- "[GAMEMODE] survival"
|
||||||
|
- "[EFFECT] SPEED;1"
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔑 Права
|
## 📝 Правила разработки
|
||||||
|
|
||||||
| Право | Описание |
|
1. **Никакого русского текста в Java файлах** - только в конфигах
|
||||||
|-------|----------|
|
2. **Цвета только в формате `&#RRGGBB`** - унифицированный стиль
|
||||||
| chat.global.use | Глобальный чат |
|
3. **SOLID принципы** - чистая архитектура
|
||||||
| chat.global.color | Цвета в глобальном чате |
|
4. **Классы до 100-150 строк** - читаемость кода
|
||||||
| chat.local.use | Локальный чат |
|
5. **Gradle Kotlin DSL** - современный подход
|
||||||
| chat.local.color | Цвета в локальном чате |
|
6. **Без лишних MD файлов** - только TODO.md и docs/
|
||||||
| chat.pm.use | Личные сообщения |
|
|
||||||
| chat.pm.ignore | Игнор игроков |
|
|
||||||
| chat.announce.use | Объявления |
|
|
||||||
| chat.spy | Шпион режим |
|
|
||||||
| chat.clear | Очистка чата |
|
|
||||||
| chat.mute | Мут игроков |
|
|
||||||
| chat.bypass.cooldown | Обход кулдауна |
|
|
||||||
| chat.bypass.filter | Обход фильтра |
|
|
||||||
| chat.bypass.antispam | Обход анти-спама |
|
|
||||||
| lochat.chatcolor | Цвет чата |
|
|
||||||
| gradient.color | Градиентные цвета |
|
|
||||||
| gradient.prefix | Кастомные префиксы |
|
|
||||||
| gradient.admin | Админ команды градиентов |
|
|
||||||
| lochat.admin | Админ команды плагина |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Возможные улучшения
|
## 🔧 Технологии
|
||||||
|
|
||||||
- [ ] GUI для настройки градиентов
|
- **Java 17+**
|
||||||
- [ ] Анимированные градиенты
|
- **Paper API 1.20.1**
|
||||||
- [ ] Больше форматов времени в муте
|
- **Velocity API 3.3.0**
|
||||||
- [ ] Статистика сообщений
|
- **Gradle 9.1+ (Kotlin DSL)**
|
||||||
- [ ] Бэкап системы данных
|
- **WorldGuard 7.0.9**
|
||||||
|
- **PlaceholderAPI 2.11.6**
|
||||||
|
- **LuckPerms API**
|
||||||
|
|
|
||||||
55
build.gradle
55
build.gradle
|
|
@ -1,55 +0,0 @@
|
||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
}
|
|
||||||
|
|
||||||
group = 'com.loki'
|
|
||||||
version = '1.5.5-1.20.1'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
|
|
||||||
maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' }
|
|
||||||
maven { url = 'https://repo.rosewooddev.io/repository/public/' }
|
|
||||||
maven { url = 'https://jitpack.io' }
|
|
||||||
maven { url = 'https://nexus.skinsrestorer.net/repository/maven-public/' }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT'
|
|
||||||
compileOnly 'me.clip:placeholderapi:2.11.6'
|
|
||||||
compileOnly 'org.black_ixx:playerpoints:3.2.7'
|
|
||||||
compileOnly 'net.luckperms:api:5.4'
|
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
|
|
||||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
|
||||||
testImplementation 'io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT'
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
|
||||||
options.encoding = 'UTF-8'
|
|
||||||
options.compilerArgs += ['-Xlint:all', '-Xlint:-processing']
|
|
||||||
options.fork = true
|
|
||||||
options.forkOptions.jvmArgs += ['-XX:+UseParallelGC']
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
// Параллельное выполнение тестов
|
|
||||||
maxParallelForks = Runtime.runtime.availableProcessors()
|
|
||||||
forkEvery = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
archiveFileName = 'LoChat-1.5.5-1.20.1.jar'
|
|
||||||
from {
|
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
|
||||||
}
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
|
||||||
}
|
|
||||||
54
build.gradle.kts
Normal file
54
build.gradle.kts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "com.loki"
|
||||||
|
version = "1.5.5-1.20.1"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
|
||||||
|
maven("https://repo.rosewooddev.io/repository/public/")
|
||||||
|
maven("https://jitpack.io")
|
||||||
|
maven("https://nexus.skinsrestorer.net/repository/maven-public/")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
|
||||||
|
compileOnly("me.clip:placeholderapi:2.11.6")
|
||||||
|
compileOnly("org.black_ixx:playerpoints:3.2.7")
|
||||||
|
compileOnly("net.luckperms:api:5.4")
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||||
|
testImplementation("org.mockito:mockito-core:5.11.0")
|
||||||
|
testImplementation("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
options.compilerArgs.addAll(listOf("-Xlint:all", "-Xlint:-processing"))
|
||||||
|
options.isFork = true
|
||||||
|
options.forkOptions.jvmArgs = listOf("-XX:+UseParallelGC")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
maxParallelForks = Runtime.getRuntime().availableProcessors()
|
||||||
|
setForkEvery(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
archiveFileName.set("LoChat-1.5.5-1.20.1.jar")
|
||||||
|
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
28
lohub/build.gradle.kts
Normal file
28
lohub/build.gradle.kts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
|
||||||
|
maven("https://maven.enginehub.org/repo/")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
|
||||||
|
compileOnly("me.clip:placeholderapi:2.11.6")
|
||||||
|
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||||
|
testImplementation("org.mockito:mockito-core:5.11.0")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
archiveFileName.set("LoHub-1.5.5-1.20.1.jar")
|
||||||
|
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
78
lohub/src/main/java/com/loki/lohub/LoHub.java
Normal file
78
lohub/src/main/java/com/loki/lohub/LoHub.java
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.loki.lohub;
|
||||||
|
|
||||||
|
import com.loki.lohub.config.ConfigManager;
|
||||||
|
import com.loki.lohub.listeners.HubProtectionListener;
|
||||||
|
import com.loki.lohub.listeners.PlayerJoinListener;
|
||||||
|
import com.loki.lohub.managers.ActionManager;
|
||||||
|
import com.loki.lohub.managers.CooldownManager;
|
||||||
|
import com.loki.lohub.managers.RegionManager;
|
||||||
|
import com.loki.lohub.managers.SpawnManager;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class LoHub extends JavaPlugin {
|
||||||
|
|
||||||
|
private ConfigManager configManager;
|
||||||
|
private SpawnManager spawnManager;
|
||||||
|
private RegionManager regionManager;
|
||||||
|
private ActionManager actionManager;
|
||||||
|
private CooldownManager cooldownManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
getLogger().info("Loading LoHub v" + getDescription().getVersion());
|
||||||
|
|
||||||
|
saveDefaultConfig();
|
||||||
|
|
||||||
|
PlaceholderUtil.init();
|
||||||
|
|
||||||
|
this.configManager = new ConfigManager(this);
|
||||||
|
this.spawnManager = new SpawnManager(this);
|
||||||
|
this.regionManager = new RegionManager(this);
|
||||||
|
this.actionManager = new ActionManager(this);
|
||||||
|
this.cooldownManager = new CooldownManager();
|
||||||
|
|
||||||
|
registerListeners();
|
||||||
|
registerCommands();
|
||||||
|
|
||||||
|
regionManager.createHubRegion();
|
||||||
|
|
||||||
|
getLogger().info("LoHub successfully loaded!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
getLogger().info("LoHub disabled!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerListeners() {
|
||||||
|
getServer().getPluginManager().registerEvents(new HubProtectionListener(this), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
getCommand("hub").setExecutor(new com.loki.lohub.commands.HubCommand(this));
|
||||||
|
getCommand("sethub").setExecutor(new com.loki.lohub.commands.SetHubCommand(this));
|
||||||
|
getCommand("lohub").setExecutor(new com.loki.lohub.commands.LoHubCommand(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigManager getConfigManager() {
|
||||||
|
return configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpawnManager getSpawnManager() {
|
||||||
|
return spawnManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionManager getRegionManager() {
|
||||||
|
return regionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionManager getActionManager() {
|
||||||
|
return actionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CooldownManager getCooldownManager() {
|
||||||
|
return cooldownManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lohub/src/main/java/com/loki/lohub/actions/Action.java
Normal file
10
lohub/src/main/java/com/loki/lohub/actions/Action.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.loki.lohub.actions;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public interface Action {
|
||||||
|
|
||||||
|
void execute(Player player);
|
||||||
|
|
||||||
|
ActionType getType();
|
||||||
|
}
|
||||||
23
lohub/src/main/java/com/loki/lohub/actions/ActionType.java
Normal file
23
lohub/src/main/java/com/loki/lohub/actions/ActionType.java
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.loki.lohub.actions;
|
||||||
|
|
||||||
|
public enum ActionType {
|
||||||
|
MESSAGE,
|
||||||
|
BROADCAST,
|
||||||
|
TITLE,
|
||||||
|
ACTIONBAR,
|
||||||
|
SOUND,
|
||||||
|
COMMAND,
|
||||||
|
CONSOLE,
|
||||||
|
GAMEMODE,
|
||||||
|
EFFECT,
|
||||||
|
CLOSE,
|
||||||
|
UNKNOWN;
|
||||||
|
|
||||||
|
public static ActionType fromString(String type) {
|
||||||
|
try {
|
||||||
|
return valueOf(type.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class ActionbarAction implements Action {
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public ActionbarAction(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsed = PlaceholderUtil.parse(message, player);
|
||||||
|
player.sendActionBar(Component.text(TextUtil.colorize(parsed)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.ACTIONBAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class BroadcastAction implements Action {
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public BroadcastAction(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsed = PlaceholderUtil.parse(message, player);
|
||||||
|
Bukkit.broadcastMessage(TextUtil.colorize(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.BROADCAST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class CloseAction implements Action {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
player.closeInventory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.CLOSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class CommandAction implements Action {
|
||||||
|
|
||||||
|
private final String command;
|
||||||
|
|
||||||
|
public CommandAction(String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsed = PlaceholderUtil.parse(command, player);
|
||||||
|
player.performCommand(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.COMMAND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class ConsoleAction implements Action {
|
||||||
|
|
||||||
|
private final String command;
|
||||||
|
|
||||||
|
public ConsoleAction(String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsed = PlaceholderUtil.parse(command, player);
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.CONSOLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
import org.bukkit.potion.PotionEffectType;
|
||||||
|
|
||||||
|
public class EffectAction implements Action {
|
||||||
|
|
||||||
|
private final PotionEffectType effectType;
|
||||||
|
private final int amplifier;
|
||||||
|
private final int duration;
|
||||||
|
|
||||||
|
public EffectAction(String effectData) {
|
||||||
|
String[] parts = effectData.split(";");
|
||||||
|
|
||||||
|
PotionEffectType parsed;
|
||||||
|
try {
|
||||||
|
parsed = PotionEffectType.getByName(parts[0].toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
parsed = PotionEffectType.SPEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.effectType = parsed != null ? parsed : PotionEffectType.SPEED;
|
||||||
|
this.amplifier = parts.length > 1 ? parseInt(parts[1], 0) : 0;
|
||||||
|
this.duration = parts.length > 2 ? parseInt(parts[2], 999999) : 999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
player.addPotionEffect(new PotionEffect(effectType, duration * 20, amplifier, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.EFFECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseInt(String value, int defaultValue) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class GamemodeAction implements Action {
|
||||||
|
|
||||||
|
private final GameMode gameMode;
|
||||||
|
|
||||||
|
public GamemodeAction(String mode) {
|
||||||
|
GameMode parsed;
|
||||||
|
try {
|
||||||
|
parsed = GameMode.valueOf(mode.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
parsed = switch (mode.toLowerCase()) {
|
||||||
|
case "0", "s" -> GameMode.SURVIVAL;
|
||||||
|
case "1", "c" -> GameMode.CREATIVE;
|
||||||
|
case "2", "a" -> GameMode.ADVENTURE;
|
||||||
|
case "3", "sp" -> GameMode.SPECTATOR;
|
||||||
|
default -> GameMode.SURVIVAL;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.gameMode = parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
player.setGameMode(gameMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.GAMEMODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class MessageAction implements Action {
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public MessageAction(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsed = PlaceholderUtil.parse(message, player);
|
||||||
|
player.sendMessage(TextUtil.colorize(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.MESSAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class SoundAction implements Action {
|
||||||
|
|
||||||
|
private final Sound sound;
|
||||||
|
private final float volume;
|
||||||
|
private final float pitch;
|
||||||
|
|
||||||
|
public SoundAction(String soundData) {
|
||||||
|
String[] parts = soundData.split(";");
|
||||||
|
|
||||||
|
Sound parsedSound;
|
||||||
|
try {
|
||||||
|
parsedSound = Sound.valueOf(parts[0].toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
parsedSound = Sound.ENTITY_EXPERIENCE_ORB_PICKUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sound = parsedSound;
|
||||||
|
this.volume = parts.length > 1 ? parseFloat(parts[1], 1.0f) : 1.0f;
|
||||||
|
this.pitch = parts.length > 2 ? parseFloat(parts[2], 1.0f) : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
player.playSound(player.getLocation(), sound, volume, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.SOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float parseFloat(String value, float defaultValue) {
|
||||||
|
try {
|
||||||
|
return Float.parseFloat(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.loki.lohub.actions.impl;
|
||||||
|
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.utils.PlaceholderUtil;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class TitleAction implements Action {
|
||||||
|
|
||||||
|
private final String title;
|
||||||
|
private final String subtitle;
|
||||||
|
private final int fadeIn;
|
||||||
|
private final int stay;
|
||||||
|
private final int fadeOut;
|
||||||
|
|
||||||
|
public TitleAction(String titleData) {
|
||||||
|
String[] parts = titleData.split(";");
|
||||||
|
|
||||||
|
this.title = parts.length > 0 ? parts[0] : "";
|
||||||
|
this.subtitle = parts.length > 1 ? parts[1] : "";
|
||||||
|
this.fadeIn = parts.length > 2 ? parseInt(parts[2], 10) : 10;
|
||||||
|
this.stay = parts.length > 3 ? parseInt(parts[3], 40) : 40;
|
||||||
|
this.fadeOut = parts.length > 4 ? parseInt(parts[4], 10) : 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Player player) {
|
||||||
|
String parsedTitle = PlaceholderUtil.parse(title, player);
|
||||||
|
String parsedSubtitle = PlaceholderUtil.parse(subtitle, player);
|
||||||
|
|
||||||
|
Component titleComponent = Component.text(TextUtil.colorize(parsedTitle));
|
||||||
|
Component subtitleComponent = Component.text(TextUtil.colorize(parsedSubtitle));
|
||||||
|
|
||||||
|
Title.Times times = Title.Times.times(
|
||||||
|
Duration.ofMillis(fadeIn * 50L),
|
||||||
|
Duration.ofMillis(stay * 50L),
|
||||||
|
Duration.ofMillis(fadeOut * 50L)
|
||||||
|
);
|
||||||
|
|
||||||
|
Title title = Title.title(titleComponent, subtitleComponent, times);
|
||||||
|
player.showTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionType getType() {
|
||||||
|
return ActionType.TITLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseInt(String value, int defaultValue) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
lohub/src/main/java/com/loki/lohub/commands/HubCommand.java
Normal file
35
lohub/src/main/java/com/loki/lohub/commands/HubCommand.java
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.loki.lohub.commands;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class HubCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
|
||||||
|
public HubCommand(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(TextUtil.colorize("&cThis command is only for players!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getSpawnManager().getSpawn() == null) {
|
||||||
|
player.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.no-spawn")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getSpawnManager().teleportToSpawn(player);
|
||||||
|
player.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.spawn-teleport")));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.loki.lohub.commands;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
public class LoHubCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
|
||||||
|
public LoHubCommand(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!sender.hasPermission("lohub.admin")) {
|
||||||
|
sender.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.no-permission")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage(TextUtil.colorize("&7LoHub v" + plugin.getDescription().getVersion()));
|
||||||
|
sender.sendMessage(TextUtil.colorize("&7Use: &e/lohub reload"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[0].equalsIgnoreCase("reload")) {
|
||||||
|
plugin.getConfigManager().reload();
|
||||||
|
sender.sendMessage(TextUtil.colorize("&aConfiguration reloaded!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.loki.lohub.commands;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class SetHubCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
|
||||||
|
public SetHubCommand(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(TextUtil.colorize("&cThis command is only for players!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("lohub.admin")) {
|
||||||
|
player.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.no-permission")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getSpawnManager().setSpawn(player.getLocation());
|
||||||
|
player.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.spawn-set")));
|
||||||
|
|
||||||
|
plugin.getRegionManager().deleteHubRegion();
|
||||||
|
plugin.getRegionManager().createHubRegion();
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("worldguard.auto-create-region")) {
|
||||||
|
player.sendMessage(TextUtil.colorize(plugin.getConfig().getString("messages.region-created")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lohub/src/main/java/com/loki/lohub/config/ConfigManager.java
Normal file
56
lohub/src/main/java/com/loki/lohub/config/ConfigManager.java
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.loki.lohub.config;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
private FileConfiguration config;
|
||||||
|
|
||||||
|
public ConfigManager(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileConfiguration getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProtectionEnabled() {
|
||||||
|
return config.getBoolean("protection.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockBreakProtected() {
|
||||||
|
return config.getBoolean("protection.block-break", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlockPlaceProtected() {
|
||||||
|
return config.getBoolean("protection.block-place", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPvPProtected() {
|
||||||
|
return config.getBoolean("protection.pvp", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isItemDropProtected() {
|
||||||
|
return config.getBoolean("protection.item-drop", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isItemPickupProtected() {
|
||||||
|
return config.getBoolean("protection.item-pickup", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFoodLevelProtected() {
|
||||||
|
return config.getBoolean("protection.food-level", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMobSpawningProtected() {
|
||||||
|
return config.getBoolean("protection.mob-spawning", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.loki.lohub.listeners;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent;
|
||||||
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||||
|
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||||
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerPickupItemEvent;
|
||||||
|
|
||||||
|
public class HubProtectionListener implements Listener {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
|
||||||
|
public HubProtectionListener(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
if (plugin.getConfig().getBoolean("spawn.teleport-on-join", true)) {
|
||||||
|
plugin.getSpawnManager().teleportToSpawn(event.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isBlockBreakProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockPlace(BlockPlaceEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isBlockPlaceProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPvP(EntityDamageByEntityEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
if (!(event.getDamager() instanceof Player)) return;
|
||||||
|
if (!(event.getEntity() instanceof Player)) return;
|
||||||
|
|
||||||
|
Player damager = (Player) event.getDamager();
|
||||||
|
if (damager.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isPvPProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onItemDrop(PlayerDropItemEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isItemDropProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onItemPickup(PlayerPickupItemEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (player.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isItemPickupProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onFoodLevelChange(FoodLevelChangeEvent event) {
|
||||||
|
if (!plugin.getConfigManager().isProtectionEnabled()) return;
|
||||||
|
if (!(event.getEntity() instanceof Player)) return;
|
||||||
|
|
||||||
|
Player player = (Player) event.getEntity();
|
||||||
|
if (player.hasPermission("lohub.bypass.protection")) return;
|
||||||
|
|
||||||
|
if (plugin.getConfigManager().isFoodLevelProtected()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package com.loki.lohub.listeners;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.loki.lohub.utils.TextUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.Color;
|
||||||
|
import org.bukkit.FireworkEffect;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Firework;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||||
|
import org.bukkit.inventory.meta.FireworkMeta;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PlayerJoinListener implements Listener {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
|
||||||
|
public PlayerJoinListener(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (isWorldDisabled(player.getWorld().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleJoinMessage(event, player);
|
||||||
|
handleJoinSettings(player);
|
||||||
|
handleJoinEvents(player);
|
||||||
|
handleFirework(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (isWorldDisabled(player.getWorld().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("join_leave_messages.enabled", true)) {
|
||||||
|
String quitMessage = plugin.getConfig().getString("join_leave_messages.quit_message", "");
|
||||||
|
if (quitMessage.isEmpty()) {
|
||||||
|
event.quitMessage(null);
|
||||||
|
} else {
|
||||||
|
event.quitMessage(Component.text(TextUtil.colorize(quitMessage.replace("%player%", player.getName()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
if (isWorldDisabled(player.getWorld().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("spawn.teleport-on-respawn", true)) {
|
||||||
|
Location spawn = plugin.getSpawnManager().getSpawn();
|
||||||
|
if (spawn != null) {
|
||||||
|
event.setRespawnLocation(spawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
|
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||||
|
if (isWorldDisabled(event.getPlayer().getWorld().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("world_settings.disable_death_message", true)) {
|
||||||
|
event.deathMessage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleJoinMessage(PlayerJoinEvent event, Player player) {
|
||||||
|
if (plugin.getConfig().getBoolean("join_leave_messages.enabled", true)) {
|
||||||
|
String joinMessage = plugin.getConfig().getString("join_leave_messages.join_message", "");
|
||||||
|
if (joinMessage.isEmpty()) {
|
||||||
|
event.joinMessage(null);
|
||||||
|
} else {
|
||||||
|
event.joinMessage(Component.text(TextUtil.colorize(joinMessage.replace("%player%", player.getName()))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleJoinSettings(Player player) {
|
||||||
|
if (plugin.getConfig().getBoolean("join_settings.spawn_join", true)) {
|
||||||
|
Location spawn = plugin.getSpawnManager().getSpawn();
|
||||||
|
if (spawn != null) {
|
||||||
|
player.teleport(spawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("join_settings.heal", true)) {
|
||||||
|
player.setHealth(player.getMaxHealth());
|
||||||
|
player.setFoodLevel(20);
|
||||||
|
player.setSaturation(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("join_settings.extinguish", true)) {
|
||||||
|
player.setFireTicks(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("join_settings.clear_inventory", false)) {
|
||||||
|
player.getInventory().clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleJoinEvents(Player player) {
|
||||||
|
List<String> actions = plugin.getConfig().getStringList("join_events");
|
||||||
|
if (!actions.isEmpty()) {
|
||||||
|
plugin.getActionManager().executeActions(player, actions, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFirework(Player player) {
|
||||||
|
if (!plugin.getConfig().getBoolean("join_settings.firework.enabled", false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstJoinOnly = plugin.getConfig().getBoolean("join_settings.firework.first_join_only", true);
|
||||||
|
if (firstJoinOnly && player.hasPlayedBefore()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
Firework firework = (Firework) loc.getWorld().spawnEntity(loc, EntityType.FIREWORK);
|
||||||
|
FireworkMeta meta = firework.getFireworkMeta();
|
||||||
|
|
||||||
|
String typeStr = plugin.getConfig().getString("join_settings.firework.type", "BALL_LARGE");
|
||||||
|
FireworkEffect.Type type;
|
||||||
|
try {
|
||||||
|
type = FireworkEffect.Type.valueOf(typeStr);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
type = FireworkEffect.Type.BALL_LARGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Color> colors = new ArrayList<>();
|
||||||
|
for (String colorStr : plugin.getConfig().getStringList("join_settings.firework.colors")) {
|
||||||
|
Color color = parseColor(colorStr);
|
||||||
|
if (color != null) {
|
||||||
|
colors.add(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colors.isEmpty()) {
|
||||||
|
colors.add(Color.AQUA);
|
||||||
|
}
|
||||||
|
|
||||||
|
FireworkEffect effect = FireworkEffect.builder()
|
||||||
|
.with(type)
|
||||||
|
.withColor(colors)
|
||||||
|
.flicker(plugin.getConfig().getBoolean("join_settings.firework.flicker", true))
|
||||||
|
.trail(plugin.getConfig().getBoolean("join_settings.firework.trail", true))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
meta.addEffect(effect);
|
||||||
|
meta.setPower(plugin.getConfig().getInt("join_settings.firework.power", 1));
|
||||||
|
firework.setFireworkMeta(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color parseColor(String colorStr) {
|
||||||
|
return switch (colorStr.toUpperCase()) {
|
||||||
|
case "AQUA" -> Color.AQUA;
|
||||||
|
case "BLACK" -> Color.BLACK;
|
||||||
|
case "BLUE" -> Color.BLUE;
|
||||||
|
case "FUCHSIA" -> Color.FUCHSIA;
|
||||||
|
case "GRAY" -> Color.GRAY;
|
||||||
|
case "GREEN" -> Color.GREEN;
|
||||||
|
case "LIME" -> Color.LIME;
|
||||||
|
case "MAROON" -> Color.MAROON;
|
||||||
|
case "NAVY" -> Color.NAVY;
|
||||||
|
case "OLIVE" -> Color.OLIVE;
|
||||||
|
case "ORANGE" -> Color.ORANGE;
|
||||||
|
case "PURPLE" -> Color.PURPLE;
|
||||||
|
case "RED" -> Color.RED;
|
||||||
|
case "SILVER" -> Color.SILVER;
|
||||||
|
case "TEAL" -> Color.TEAL;
|
||||||
|
case "WHITE" -> Color.WHITE;
|
||||||
|
case "YELLOW" -> Color.YELLOW;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWorldDisabled(String worldName) {
|
||||||
|
List<String> disabledWorlds = plugin.getConfig().getStringList("disabled-worlds.worlds");
|
||||||
|
boolean invert = plugin.getConfig().getBoolean("disabled-worlds.invert", false);
|
||||||
|
boolean isInList = disabledWorlds.contains(worldName);
|
||||||
|
|
||||||
|
return invert ? !isInList : isInList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.loki.lohub.managers;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.loki.lohub.actions.Action;
|
||||||
|
import com.loki.lohub.actions.ActionType;
|
||||||
|
import com.loki.lohub.actions.impl.*;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class ActionManager {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
private static final Pattern ACTION_PATTERN = Pattern.compile("\\[([A-Z]+)]\\s*(.*)");
|
||||||
|
|
||||||
|
public ActionManager(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Action> parseActions(List<String> actionStrings) {
|
||||||
|
List<Action> actions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String actionString : actionStrings) {
|
||||||
|
Action action = parseAction(actionString);
|
||||||
|
if (action != null) {
|
||||||
|
actions.add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action parseAction(String actionString) {
|
||||||
|
if (actionString == null || actionString.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = ACTION_PATTERN.matcher(actionString);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
plugin.getLogger().warning("Invalid action format: " + actionString);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String typeStr = matcher.group(1);
|
||||||
|
String data = matcher.group(2);
|
||||||
|
|
||||||
|
ActionType type = ActionType.fromString(typeStr);
|
||||||
|
|
||||||
|
return switch (type) {
|
||||||
|
case MESSAGE -> new MessageAction(data);
|
||||||
|
case BROADCAST -> new BroadcastAction(data);
|
||||||
|
case TITLE -> new TitleAction(data);
|
||||||
|
case ACTIONBAR -> new ActionbarAction(data);
|
||||||
|
case SOUND -> new SoundAction(data);
|
||||||
|
case COMMAND -> new CommandAction(data);
|
||||||
|
case CONSOLE -> new ConsoleAction(data);
|
||||||
|
case GAMEMODE -> new GamemodeAction(data);
|
||||||
|
case EFFECT -> new EffectAction(data);
|
||||||
|
case CLOSE -> new CloseAction();
|
||||||
|
default -> {
|
||||||
|
plugin.getLogger().warning("Unknown action type: " + typeStr);
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeActions(Player player, List<Action> actions) {
|
||||||
|
for (Action action : actions) {
|
||||||
|
try {
|
||||||
|
action.execute(player);
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Error executing action " + action.getType() + ": " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeActions(Player player, List<String> actionStrings, boolean parse) {
|
||||||
|
if (parse) {
|
||||||
|
List<Action> actions = parseActions(actionStrings);
|
||||||
|
executeActions(player, actions);
|
||||||
|
} else {
|
||||||
|
for (String actionString : actionStrings) {
|
||||||
|
Action action = parseAction(actionString);
|
||||||
|
if (action != null) {
|
||||||
|
try {
|
||||||
|
action.execute(player);
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Error executing action: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.loki.lohub.managers;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class CooldownManager {
|
||||||
|
|
||||||
|
private final Map<String, Map<UUID, Long>> cooldowns = new HashMap<>();
|
||||||
|
|
||||||
|
public void setCooldown(String key, UUID playerId, int seconds) {
|
||||||
|
cooldowns.computeIfAbsent(key, k -> new HashMap<>())
|
||||||
|
.put(playerId, System.currentTimeMillis() + (seconds * 1000L));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCooldown(String key, UUID playerId) {
|
||||||
|
Map<UUID, Long> keyCooldowns = cooldowns.get(key);
|
||||||
|
if (keyCooldowns == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long expireTime = keyCooldowns.get(playerId);
|
||||||
|
if (expireTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() >= expireTime) {
|
||||||
|
keyCooldowns.remove(playerId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemainingSeconds(String key, UUID playerId) {
|
||||||
|
Map<UUID, Long> keyCooldowns = cooldowns.get(key);
|
||||||
|
if (keyCooldowns == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long expireTime = keyCooldowns.get(playerId);
|
||||||
|
if (expireTime == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long remaining = expireTime - System.currentTimeMillis();
|
||||||
|
return remaining > 0 ? (int) (remaining / 1000) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCooldown(String key, UUID playerId) {
|
||||||
|
Map<UUID, Long> keyCooldowns = cooldowns.get(key);
|
||||||
|
if (keyCooldowns != null) {
|
||||||
|
keyCooldowns.remove(playerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAll() {
|
||||||
|
cooldowns.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
130
lohub/src/main/java/com/loki/lohub/managers/RegionManager.java
Normal file
130
lohub/src/main/java/com/loki/lohub/managers/RegionManager.java
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
package com.loki.lohub.managers;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldguard.WorldGuard;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||||
|
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||||
|
import com.sk89q.worldguard.protection.regions.RegionContainer;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
public class RegionManager {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
private static final String HUB_REGION_NAME = "lohub_spawn";
|
||||||
|
|
||||||
|
public RegionManager(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createHubRegion() {
|
||||||
|
if (!plugin.getConfig().getBoolean("worldguard.auto-create-region", true)) {
|
||||||
|
plugin.getLogger().info("WorldGuard region auto-creation is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWorldGuardAvailable()) {
|
||||||
|
plugin.getLogger().warning("WorldGuard not found! Region will not be created.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location spawn = plugin.getSpawnManager().getSpawn();
|
||||||
|
if (spawn == null || spawn.getWorld() == null) {
|
||||||
|
plugin.getLogger().warning("Spawn not set! Use /sethub");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World world = spawn.getWorld();
|
||||||
|
int radius = plugin.getConfig().getInt("worldguard.region-radius", 100);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
|
||||||
|
com.sk89q.worldguard.protection.managers.RegionManager wgRegionManager = container.get(BukkitAdapter.adapt(world));
|
||||||
|
|
||||||
|
if (wgRegionManager == null) {
|
||||||
|
plugin.getLogger().warning("Failed to get RegionManager for world " + world.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProtectedRegion existingRegion = wgRegionManager.getRegion(HUB_REGION_NAME);
|
||||||
|
if (existingRegion != null) {
|
||||||
|
plugin.getLogger().info("Region '" + HUB_REGION_NAME + "' already exists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockVector3 min = BlockVector3.at(
|
||||||
|
spawn.getBlockX() - radius,
|
||||||
|
world.getMinHeight(),
|
||||||
|
spawn.getBlockZ() - radius
|
||||||
|
);
|
||||||
|
|
||||||
|
BlockVector3 max = BlockVector3.at(
|
||||||
|
spawn.getBlockX() + radius,
|
||||||
|
world.getMaxHeight(),
|
||||||
|
spawn.getBlockZ() + radius
|
||||||
|
);
|
||||||
|
|
||||||
|
ProtectedCuboidRegion region = new ProtectedCuboidRegion(HUB_REGION_NAME, min, max);
|
||||||
|
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.PVP,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.BLOCK_BREAK,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.BLOCK_PLACE,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.DAMAGE_ANIMALS,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.MOB_SPAWNING,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.LEAF_DECAY,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.ITEM_DROP,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.ITEM_PICKUP,
|
||||||
|
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
|
||||||
|
|
||||||
|
wgRegionManager.addRegion(region);
|
||||||
|
|
||||||
|
plugin.getLogger().info("Region '" + HUB_REGION_NAME + "' created around spawn (radius: " + radius + " blocks)");
|
||||||
|
plugin.getLogger().info("Coordinates: " + min + " -> " + max);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Error creating WorldGuard region: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteHubRegion() {
|
||||||
|
if (!isWorldGuardAvailable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location spawn = plugin.getSpawnManager().getSpawn();
|
||||||
|
if (spawn == null || spawn.getWorld() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
|
||||||
|
com.sk89q.worldguard.protection.managers.RegionManager wgRegionManager = container.get(BukkitAdapter.adapt(spawn.getWorld()));
|
||||||
|
|
||||||
|
if (wgRegionManager != null) {
|
||||||
|
wgRegionManager.removeRegion(HUB_REGION_NAME);
|
||||||
|
plugin.getLogger().info("Region '" + HUB_REGION_NAME + "' deleted");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().severe("Error deleting region: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWorldGuardAvailable() {
|
||||||
|
try {
|
||||||
|
Class.forName("com.sk89q.worldguard.WorldGuard");
|
||||||
|
return plugin.getServer().getPluginManager().isPluginEnabled("WorldGuard");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.loki.lohub.managers;
|
||||||
|
|
||||||
|
import com.loki.lohub.LoHub;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SpawnManager {
|
||||||
|
|
||||||
|
private final LoHub plugin;
|
||||||
|
private final File spawnFile;
|
||||||
|
private FileConfiguration spawnConfig;
|
||||||
|
private Location spawnLocation;
|
||||||
|
|
||||||
|
public SpawnManager(LoHub plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.spawnFile = new File(plugin.getDataFolder(), "spawn.yml");
|
||||||
|
loadSpawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSpawn() {
|
||||||
|
if (!spawnFile.exists()) {
|
||||||
|
try {
|
||||||
|
spawnFile.createNewFile();
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Failed to create spawn.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnConfig = YamlConfiguration.loadConfiguration(spawnFile);
|
||||||
|
|
||||||
|
if (spawnConfig.contains("spawn")) {
|
||||||
|
String worldName = spawnConfig.getString("spawn.world");
|
||||||
|
double x = spawnConfig.getDouble("spawn.x");
|
||||||
|
double y = spawnConfig.getDouble("spawn.y");
|
||||||
|
double z = spawnConfig.getDouble("spawn.z");
|
||||||
|
float yaw = (float) spawnConfig.getDouble("spawn.yaw");
|
||||||
|
float pitch = (float) spawnConfig.getDouble("spawn.pitch");
|
||||||
|
|
||||||
|
World world = Bukkit.getWorld(worldName);
|
||||||
|
if (world != null) {
|
||||||
|
spawnLocation = new Location(world, x, y, z, yaw, pitch);
|
||||||
|
plugin.getLogger().info("Spawn loaded: " + worldName + " (" + x + ", " + y + ", " + z + ")");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("World '" + worldName + "' not found!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpawn(Location location) {
|
||||||
|
this.spawnLocation = location;
|
||||||
|
|
||||||
|
spawnConfig.set("spawn.world", location.getWorld().getName());
|
||||||
|
spawnConfig.set("spawn.x", location.getX());
|
||||||
|
spawnConfig.set("spawn.y", location.getY());
|
||||||
|
spawnConfig.set("spawn.z", location.getZ());
|
||||||
|
spawnConfig.set("spawn.yaw", location.getYaw());
|
||||||
|
spawnConfig.set("spawn.pitch", location.getPitch());
|
||||||
|
|
||||||
|
try {
|
||||||
|
spawnConfig.save(spawnFile);
|
||||||
|
plugin.getLogger().info("Spawn set: " + location.getWorld().getName() +
|
||||||
|
" (" + location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ() + ")");
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Failed to save spawn.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Location getSpawn() {
|
||||||
|
return spawnLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void teleportToSpawn(Player player) {
|
||||||
|
if (spawnLocation == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.teleport(spawnLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
156
lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java
Normal file
156
lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
package com.loki.lohub.utils;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemFlag;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class ItemBuilder {
|
||||||
|
|
||||||
|
private final ItemStack itemStack;
|
||||||
|
|
||||||
|
public ItemBuilder(Material material) {
|
||||||
|
this.itemStack = new ItemStack(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder(ItemStack itemStack) {
|
||||||
|
this.itemStack = itemStack.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemBuilder fromConfig(ConfigurationSection section) {
|
||||||
|
return fromConfig(section, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemBuilder fromConfig(ConfigurationSection section, Player player) {
|
||||||
|
String materialName = section.getString("material", "STONE").toUpperCase();
|
||||||
|
Material material;
|
||||||
|
|
||||||
|
try {
|
||||||
|
material = Material.valueOf(materialName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
material = Material.STONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemBuilder builder = new ItemBuilder(material);
|
||||||
|
|
||||||
|
if (section.contains("amount")) {
|
||||||
|
builder.amount(section.getInt("amount", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.contains("display_name")) {
|
||||||
|
String name = section.getString("display_name");
|
||||||
|
builder.name(player != null ? PlaceholderUtil.parse(name, player) : name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.contains("lore")) {
|
||||||
|
List<String> lore = section.getStringList("lore");
|
||||||
|
builder.lore(player != null ?
|
||||||
|
lore.stream().map(line -> PlaceholderUtil.parse(line, player)).collect(Collectors.toList()) :
|
||||||
|
lore);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.contains("glow") && section.getBoolean("glow")) {
|
||||||
|
builder.glow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.contains("custom_model_data")) {
|
||||||
|
builder.customModelData(section.getInt("custom_model_data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.contains("item_flags")) {
|
||||||
|
section.getStringList("item_flags").forEach(flagName -> {
|
||||||
|
try {
|
||||||
|
builder.flag(ItemFlag.valueOf(flagName.toUpperCase()));
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (material == Material.PLAYER_HEAD && section.contains("skull_owner")) {
|
||||||
|
String owner = section.getString("skull_owner");
|
||||||
|
builder.skullOwner(player != null ? owner.replace("%player%", player.getName()) : owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder amount(int amount) {
|
||||||
|
itemStack.setAmount(Math.max(1, amount));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder name(String name) {
|
||||||
|
ItemMeta meta = itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(TextUtil.colorize(name));
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder lore(List<String> lore) {
|
||||||
|
ItemMeta meta = itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
List<String> coloredLore = lore.stream()
|
||||||
|
.map(TextUtil::colorize)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
meta.setLore(coloredLore);
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder lore(String... lines) {
|
||||||
|
return lore(List.of(lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder flag(ItemFlag... flags) {
|
||||||
|
ItemMeta meta = itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.addItemFlags(flags);
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder glow() {
|
||||||
|
ItemMeta meta = itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.addEnchant(Enchantment.LUCK, 1, true);
|
||||||
|
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder customModelData(int data) {
|
||||||
|
ItemMeta meta = itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setCustomModelData(data);
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemBuilder skullOwner(String owner) {
|
||||||
|
if (itemStack.getType() == Material.PLAYER_HEAD) {
|
||||||
|
SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setOwner(owner);
|
||||||
|
itemStack.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack build() {
|
||||||
|
return itemStack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.loki.lohub.utils;
|
||||||
|
|
||||||
|
import me.clip.placeholderapi.PlaceholderAPI;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class PlaceholderUtil {
|
||||||
|
|
||||||
|
private static boolean placeholderAPIEnabled = false;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
placeholderAPIEnabled = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parse(String text, Player player) {
|
||||||
|
if (text == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
text = parseInternalPlaceholders(text, player);
|
||||||
|
|
||||||
|
if (placeholderAPIEnabled && player != null) {
|
||||||
|
text = PlaceholderAPI.setPlaceholders(player, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parseInternalPlaceholders(String text, Player player) {
|
||||||
|
if (player != null) {
|
||||||
|
text = text.replace("%player%", player.getName());
|
||||||
|
text = text.replace("%player_displayname%", player.getDisplayName());
|
||||||
|
|
||||||
|
if (text.contains("%location%")) {
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
text = text.replace("%location%",
|
||||||
|
loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.contains("%world%")) {
|
||||||
|
text = text.replace("%world%", player.getWorld().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
|
||||||
|
text = text.replace("%online_max%", String.valueOf(Bukkit.getMaxPlayers()));
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlaceholderAPIEnabled() {
|
||||||
|
return placeholderAPIEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
72
lohub/src/main/java/com/loki/lohub/utils/TextUtil.java
Normal file
72
lohub/src/main/java/com/loki/lohub/utils/TextUtil.java
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.loki.lohub.utils;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class TextUtil {
|
||||||
|
|
||||||
|
private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})");
|
||||||
|
|
||||||
|
public static String colorize(String text) {
|
||||||
|
if (text == null || text.isEmpty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
text = translateHexCodes(text);
|
||||||
|
text = ChatColor.translateAlternateColorCodes('&', text);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> colorize(List<String> lines) {
|
||||||
|
return lines.stream()
|
||||||
|
.map(TextUtil::colorize)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String translateHexCodes(String text) {
|
||||||
|
Matcher matcher = HEX_PATTERN.matcher(text);
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String hexCode = matcher.group(1);
|
||||||
|
String replacement = ChatColor.of("#" + hexCode).toString();
|
||||||
|
matcher.appendReplacement(buffer, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String strip(String text) {
|
||||||
|
return ChatColor.stripColor(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String joinFrom(int startIndex, String[] args) {
|
||||||
|
if (args == null || startIndex >= args.length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = startIndex; i < args.length; i++) {
|
||||||
|
builder.append(args[i]);
|
||||||
|
if (i < args.length - 1) {
|
||||||
|
builder.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String listToString(List<String> list) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.join("\n", list);
|
||||||
|
}
|
||||||
|
}
|
||||||
343
lohub/src/main/resources/config.yml
Normal file
343
lohub/src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
# ╔══════════════════════════════════════════════════════════════════════════════╗
|
||||||
|
# ║ LoHub Configuration ║
|
||||||
|
# ║ Hub plugin for Paper/Folia 1.20.1+ ║
|
||||||
|
# ║ Version: 1.5.5-1.20.1 ║
|
||||||
|
# ║ Compatible with DeluxeHub config format ║
|
||||||
|
# ╚══════════════════════════════════════════════════════════════════════════════╝
|
||||||
|
#
|
||||||
|
# BUILT-IN PLACEHOLDERS:
|
||||||
|
# %player% - Player name
|
||||||
|
# %player_displayname% - Player display name
|
||||||
|
# %location% - Player location (x, y, z)
|
||||||
|
# %world% - World name
|
||||||
|
# %online% - Online players count
|
||||||
|
# %online_max% - Max players
|
||||||
|
#
|
||||||
|
# ACTIONS (for join_events, custom_join_items, etc):
|
||||||
|
# [MESSAGE] text - Send message to player
|
||||||
|
# [BROADCAST] text - Broadcast to all players
|
||||||
|
# [TITLE] title;subtitle;fadeIn;stay;fadeOut - Send title
|
||||||
|
# [ACTIONBAR] text - Send actionbar message
|
||||||
|
# [SOUND] sound - Play sound
|
||||||
|
# [COMMAND] command - Execute as player
|
||||||
|
# [CONSOLE] command - Execute as console
|
||||||
|
# [GAMEMODE] mode - Change gamemode (survival/creative/adventure/spectator)
|
||||||
|
# [EFFECT] effect;level - Give potion effect
|
||||||
|
# [CLOSE] - Close inventory
|
||||||
|
#
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | GENERAL SETTINGS |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
# Check for updates (recommended)
|
||||||
|
check-updates: false
|
||||||
|
|
||||||
|
# Disable plugin in specific worlds
|
||||||
|
disabled-worlds:
|
||||||
|
invert: false
|
||||||
|
worlds:
|
||||||
|
- world_nether
|
||||||
|
- world_the_end
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | SPAWN SETTINGS |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
spawn:
|
||||||
|
# Teleport players to spawn on join
|
||||||
|
teleport-on-join: true
|
||||||
|
|
||||||
|
# Teleport players to spawn on respawn
|
||||||
|
teleport-on-respawn: true
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | WORLDGUARD INTEGRATION |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
worldguard:
|
||||||
|
# Auto-create WorldGuard region around spawn
|
||||||
|
auto-create-region: true
|
||||||
|
|
||||||
|
# Region radius in blocks
|
||||||
|
region-radius: 100
|
||||||
|
|
||||||
|
# Region name (don't change unless you know what you're doing)
|
||||||
|
region-name: "lohub_spawn"
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | WORLD PROTECTION |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
world_settings:
|
||||||
|
# Player
|
||||||
|
disable_hunger_loss: true
|
||||||
|
disable_fall_damage: true
|
||||||
|
disable_player_pvp: true
|
||||||
|
disable_void_death: true
|
||||||
|
disable_fire_damage: true
|
||||||
|
disable_drowning: true
|
||||||
|
disable_off_hand_swap: true
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
disable_weather_change: true
|
||||||
|
disable_death_message: true
|
||||||
|
disable_mob_spawning: true
|
||||||
|
|
||||||
|
# Item entities
|
||||||
|
disable_item_drop: true
|
||||||
|
disable_item_pickup: true
|
||||||
|
|
||||||
|
# Blocks
|
||||||
|
disable_block_break: true
|
||||||
|
disable_block_place: true
|
||||||
|
disable_block_interact: true
|
||||||
|
disable_block_burn: true
|
||||||
|
disable_block_fire_spread: true
|
||||||
|
disable_block_leaf_decay: true
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | LAUNCHPAD |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
launchpad:
|
||||||
|
enabled: true
|
||||||
|
launch_power: 3.0
|
||||||
|
launch_power_y: 1.0
|
||||||
|
|
||||||
|
# Blocks required: pressure plate on top of redstone block
|
||||||
|
top_block: STONE_PRESSURE_PLATE
|
||||||
|
bottom_block: REDSTONE_BLOCK
|
||||||
|
|
||||||
|
actions:
|
||||||
|
- '[SOUND] ENTITY_BAT_TAKEOFF'
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | DOUBLE JUMP |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
double_jump:
|
||||||
|
enabled: true
|
||||||
|
launch_power: 1.0
|
||||||
|
launch_power_y: 0.9
|
||||||
|
cooldown: 3
|
||||||
|
|
||||||
|
actions:
|
||||||
|
- '[SOUND] ENTITY_BAT_TAKEOFF'
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | COMMAND BLOCKER |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
command_block:
|
||||||
|
enabled: true
|
||||||
|
blocked_commands:
|
||||||
|
- /pl
|
||||||
|
- /plugins
|
||||||
|
- /?
|
||||||
|
- /bukkit:?
|
||||||
|
- /bukkit:plugins
|
||||||
|
- /bukkit:pl
|
||||||
|
- /bukkit:help
|
||||||
|
- /ver
|
||||||
|
- /version
|
||||||
|
- /bukkit:version
|
||||||
|
- /bukkit:ver
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | ANTI-SWEAR |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
anti_swear:
|
||||||
|
enabled: false
|
||||||
|
blocked_words:
|
||||||
|
- fuck
|
||||||
|
- shit
|
||||||
|
- bitch
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | PLAYER JOIN |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
join_leave_messages:
|
||||||
|
enabled: true
|
||||||
|
join_message: "BCB77+ &#FFD700%player% &#AAAAAAconnected"
|
||||||
|
quit_message: "&#FF6B6B- &#FFD700%player% &#AAAAAAdisconnected"
|
||||||
|
|
||||||
|
join_events:
|
||||||
|
- "[MESSAGE] &#FFD700╔═══════════════════════════════╗"
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] &r BCB77Welcome, &#FFD700%player%BCB77!"
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] &r �D4FFWebsite: &#FFFFFFwww.example.net"
|
||||||
|
- "[MESSAGE] &r &#FF6B6BStore: &#FFFFFFstore.example.net"
|
||||||
|
- "[MESSAGE] &r 	B59B6Discord: &#FFFFFFdiscord.example.net"
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] &#FFD700╚═══════════════════════════════╝"
|
||||||
|
- "[TITLE] BCB77W>C973EAC76FL3C56BC1C367O(C163M&BF5FE;&#FFD700%player%;10;40;10"
|
||||||
|
- "[SOUND] ENTITY_PLAYER_LEVELUP"
|
||||||
|
- "[GAMEMODE] survival"
|
||||||
|
|
||||||
|
join_settings:
|
||||||
|
spawn_join: true
|
||||||
|
heal: true
|
||||||
|
extinguish: true
|
||||||
|
clear_inventory: false
|
||||||
|
|
||||||
|
firework:
|
||||||
|
enabled: true
|
||||||
|
first_join_only: true
|
||||||
|
type: BALL_LARGE
|
||||||
|
power: 1
|
||||||
|
flicker: true
|
||||||
|
trail: true
|
||||||
|
colors:
|
||||||
|
- AQUA
|
||||||
|
- RED
|
||||||
|
- TEAL
|
||||||
|
- WHITE
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | CUSTOM JOIN ITEMS |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
custom_join_items:
|
||||||
|
enabled: true
|
||||||
|
disable_inventory_movement: true
|
||||||
|
|
||||||
|
items:
|
||||||
|
info:
|
||||||
|
material: BOOK
|
||||||
|
amount: 1
|
||||||
|
slot: 0
|
||||||
|
display_name: "&#FFD700I&#FFC700n&#FFB700f&#FFA700o&#FF9700r&#FF8700m&#FF7700a&#FF6700t&#FF5700i&#FF4700o&#FF3700n"
|
||||||
|
lore:
|
||||||
|
- "&#AAAAAARight-click for server info!"
|
||||||
|
actions:
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] &#FFD700╔═══ Server Information ═══╗"
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] �D4FFWebsite: &#FFFFFFwww.example.net"
|
||||||
|
- "[MESSAGE] &#FF6B6BStore: &#FFFFFFstore.example.net"
|
||||||
|
- "[MESSAGE] 	B59B6Discord: &#FFFFFFdiscord.example.net"
|
||||||
|
- "[MESSAGE] &r"
|
||||||
|
- "[MESSAGE] &#FFD700╚═══════════════════════════╝"
|
||||||
|
- "[SOUND] BLOCK_NOTE_BLOCK_PLING"
|
||||||
|
|
||||||
|
compass:
|
||||||
|
material: COMPASS
|
||||||
|
amount: 1
|
||||||
|
slot: 4
|
||||||
|
display_name: "BCB77S>C973eAC76Fr3C56Bv1C367e(C163r &BF5FSFBD5BeBB57lEB953eB74Fc�DB54BtB347oB347r"
|
||||||
|
lore:
|
||||||
|
- "&#AAAAAARight-click to choose server!"
|
||||||
|
actions:
|
||||||
|
- "[MESSAGE] &#FF6B6BServer selector not implemented yet!"
|
||||||
|
- "[SOUND] ENTITY_VILLAGER_NO"
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | PLAYER HIDER |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
player_hider:
|
||||||
|
enabled: true
|
||||||
|
slot: 8
|
||||||
|
disable_inventory_movement: true
|
||||||
|
cooldown: 3
|
||||||
|
|
||||||
|
not_hidden:
|
||||||
|
material: LIME_DYE
|
||||||
|
amount: 1
|
||||||
|
display_name: 'BCB77Players &#AAAAAA> BCB77Shown 󙀸(Right-Click)'
|
||||||
|
lore:
|
||||||
|
- '&#AAAAAAClick to hide all players!'
|
||||||
|
|
||||||
|
hidden:
|
||||||
|
material: GRAY_DYE
|
||||||
|
amount: 1
|
||||||
|
display_name: '&#FF6B6BPlayers &#AAAAAA> &#FF6B6BHidden 󙀸(Right-Click)'
|
||||||
|
lore:
|
||||||
|
- '&#AAAAAAClick to show all players!'
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | SCOREBOARD |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
scoreboard:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
display_delay:
|
||||||
|
server_enter: 60
|
||||||
|
world_change: 40
|
||||||
|
|
||||||
|
refresh:
|
||||||
|
enabled: true
|
||||||
|
rate: 200
|
||||||
|
|
||||||
|
title: "&#FFD700L&#FFC700o&#FFB700H&#FFA700u&#FF9700b 󙀸(%online%/%online_max%)"
|
||||||
|
lines:
|
||||||
|
- "&#AAAAAA━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
- "BCB77Player:"
|
||||||
|
- " &#FFFFFF%player%"
|
||||||
|
- ""
|
||||||
|
- "BCB77Rank:"
|
||||||
|
- " &#FFFFFF%vault_rank%"
|
||||||
|
- "&#AAAAAA━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | TABLIST |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
tablist:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
refresh:
|
||||||
|
enabled: true
|
||||||
|
rate: 400
|
||||||
|
|
||||||
|
header:
|
||||||
|
- ""
|
||||||
|
- "&#FFD700L&#FFC700o&#FFB700H&#FFA700u&#FF9700b 󙀸(%online%/%online_max%)"
|
||||||
|
- ""
|
||||||
|
|
||||||
|
footer:
|
||||||
|
- ""
|
||||||
|
- "�D4FFWebsite &#FFFFFFwww.example.net"
|
||||||
|
- "&#FF6B6BStore &#FFFFFFstore.example.net"
|
||||||
|
- ""
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | ANNOUNCEMENTS |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
announcements:
|
||||||
|
enabled: false
|
||||||
|
delay: 60
|
||||||
|
required_players: 1
|
||||||
|
|
||||||
|
sound:
|
||||||
|
enabled: true
|
||||||
|
value: BLOCK_NOTE_BLOCK_PLING
|
||||||
|
volume: 1.0
|
||||||
|
pitch: 1.0
|
||||||
|
|
||||||
|
announcements:
|
||||||
|
welcome:
|
||||||
|
- "&r"
|
||||||
|
- "&#FFD700╔═══ Announcement ═══╗"
|
||||||
|
- "&#AAAAAAWelcome to our server!"
|
||||||
|
- "&#FFD700╚═══════════════════════╝"
|
||||||
|
- "&r"
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
# | MESSAGES |
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
messages:
|
||||||
|
prefix: "�D4FF[�D4FFL�CAF7o�C0EEH�B7E6u�ADDDbcCC]"
|
||||||
|
spawn-set: "BCB77Spawn set successfully!"
|
||||||
|
spawn-teleport: "BCB77Teleporting to spawn..."
|
||||||
|
no-spawn: "&#FF6B6BSpawn not set! Use /sethub"
|
||||||
|
no-permission: "&#FF6B6BYou don't have permission!"
|
||||||
|
players-only: "&#FF6B6BThis command is only for players!"
|
||||||
|
config-reloaded: "BCB77Configuration reloaded!"
|
||||||
|
usage: "&#FFD700Usage: /lohub reload"
|
||||||
40
lohub/src/main/resources/plugin.yml
Normal file
40
lohub/src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: LoHub
|
||||||
|
version: '1.5.5-1.20.1'
|
||||||
|
main: com.loki.lohub.LoHub
|
||||||
|
api-version: '1.20'
|
||||||
|
folia-supported: true
|
||||||
|
author: loki
|
||||||
|
description: Hub plugin for Paper/Folia with WorldGuard integration
|
||||||
|
|
||||||
|
softdepend:
|
||||||
|
- WorldGuard
|
||||||
|
- WorldEdit
|
||||||
|
|
||||||
|
commands:
|
||||||
|
hub:
|
||||||
|
description: Teleport to spawn
|
||||||
|
usage: /hub
|
||||||
|
permission: lohub.hub
|
||||||
|
aliases: [spawn, хаб, спавн]
|
||||||
|
|
||||||
|
sethub:
|
||||||
|
description: Set hub spawn location
|
||||||
|
usage: /sethub
|
||||||
|
permission: lohub.admin
|
||||||
|
aliases: [setspawn, установитьспавн]
|
||||||
|
|
||||||
|
lohub:
|
||||||
|
description: LoHub admin command
|
||||||
|
usage: /lohub [reload]
|
||||||
|
permission: lohub.admin
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
lohub.hub:
|
||||||
|
description: Can use /hub command
|
||||||
|
default: true
|
||||||
|
lohub.admin:
|
||||||
|
description: Admin permissions
|
||||||
|
default: op
|
||||||
|
lohub.bypass.protection:
|
||||||
|
description: Bypass world protection
|
||||||
|
default: op
|
||||||
25
lovelocity/build.gradle.kts
Normal file
25
lovelocity/build.gradle.kts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://repo.velocitypowered.com/snapshots/")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
|
||||||
|
annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
archiveFileName.set("LoVelocity-1.5.5.jar")
|
||||||
|
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
65
lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java
Normal file
65
lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.loki.lovelocity;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.loki.lovelocity.commands.HubCommand;
|
||||||
|
import com.velocitypowered.api.command.CommandMeta;
|
||||||
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
|
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||||
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
|
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||||
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Plugin(
|
||||||
|
id = "lovelocity",
|
||||||
|
name = "LoVelocity",
|
||||||
|
version = "1.5.5",
|
||||||
|
description = "Velocity integration for LoNetwork",
|
||||||
|
authors = {"loki"}
|
||||||
|
)
|
||||||
|
public class LoVelocity {
|
||||||
|
|
||||||
|
private final ProxyServer server;
|
||||||
|
private final Logger logger;
|
||||||
|
private final Path dataDirectory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LoVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
|
||||||
|
this.server = server;
|
||||||
|
this.logger = logger;
|
||||||
|
this.dataDirectory = dataDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||||
|
logger.info("LoVelocity loaded!");
|
||||||
|
logger.info("Version: 1.5.5");
|
||||||
|
|
||||||
|
registerCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
CommandMeta hubMeta = server.getCommandManager().metaBuilder("hub")
|
||||||
|
.aliases("lobby")
|
||||||
|
.plugin(this)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.getCommandManager().register(hubMeta, new HubCommand(this));
|
||||||
|
|
||||||
|
logger.info("Commands registered: /hub, /lobby");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyServer getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger getLogger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getDataDirectory() {
|
||||||
|
return dataDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package com.loki.lovelocity.commands;
|
||||||
|
|
||||||
|
import com.loki.lovelocity.LoVelocity;
|
||||||
|
import com.velocitypowered.api.command.SimpleCommand;
|
||||||
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class HubCommand implements SimpleCommand {
|
||||||
|
|
||||||
|
private final LoVelocity plugin;
|
||||||
|
private final List<String> hubServers = List.of("hub", "lobby");
|
||||||
|
|
||||||
|
public HubCommand(LoVelocity plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Invocation invocation) {
|
||||||
|
if (!(invocation.source() instanceof Player player)) {
|
||||||
|
invocation.source().sendMessage(Component.text("This command is only for players!", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<RegisteredServer> currentServer = player.getCurrentServer().map(s -> s.getServer());
|
||||||
|
|
||||||
|
if (currentServer.isEmpty()) {
|
||||||
|
player.sendMessage(Component.text("Failed to determine current server!", NamedTextColor.RED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentServerName = currentServer.get().getServerInfo().getName().toLowerCase();
|
||||||
|
|
||||||
|
if (isHubServer(currentServerName)) {
|
||||||
|
player.sendMessage(Component.text("You are already on the hub!", NamedTextColor.YELLOW));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<RegisteredServer> hubServer = findHubServer();
|
||||||
|
|
||||||
|
if (hubServer.isEmpty()) {
|
||||||
|
player.sendMessage(Component.text("Hub server not found!", NamedTextColor.RED));
|
||||||
|
plugin.getLogger().warn("Hub server not found! Available servers: " +
|
||||||
|
plugin.getServer().getAllServers().stream()
|
||||||
|
.map(s -> s.getServerInfo().getName())
|
||||||
|
.toList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage(Component.text("Teleporting to hub...", NamedTextColor.GREEN));
|
||||||
|
|
||||||
|
player.createConnectionRequest(hubServer.get()).fireAndForget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHubServer(String serverName) {
|
||||||
|
return hubServers.stream().anyMatch(hub -> serverName.contains(hub));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<RegisteredServer> findHubServer() {
|
||||||
|
return plugin.getServer().getAllServers().stream()
|
||||||
|
.filter(server -> isHubServer(server.getServerInfo().getName().toLowerCase()))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<List<String>> suggestAsync(Invocation invocation) {
|
||||||
|
return CompletableFuture.completedFuture(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Invocation invocation) {
|
||||||
|
return invocation.source().hasPermission("lovelocity.hub");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rootProject.name = 'LoChat'
|
|
||||||
4
settings.gradle.kts
Normal file
4
settings.gradle.kts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
rootProject.name = "LoNetwork"
|
||||||
|
|
||||||
|
include("lohub")
|
||||||
|
include("lovelocity")
|
||||||
|
|
@ -16,6 +16,10 @@ public class CustomCommand extends Command {
|
||||||
public CustomCommand(String name, CustomCommandManager manager) {
|
public CustomCommand(String name, CustomCommandManager manager) {
|
||||||
super(name);
|
super(name);
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
init(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(String name) {
|
||||||
this.setDescription("Кастомная команда LoChat");
|
this.setDescription("Кастомная команда LoChat");
|
||||||
this.setUsage("/" + name);
|
this.setUsage("/" + name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ import org.bukkit.entity.Player;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* Команда /nick для установки кастомного ника
|
|
||||||
*/
|
|
||||||
public class NickCommand implements CommandExecutor, TabCompleter {
|
public class NickCommand implements CommandExecutor, TabCompleter {
|
||||||
private final LoChat plugin;
|
private final LoChat plugin;
|
||||||
private final NickService nickService;
|
private final NickService nickService;
|
||||||
|
|
@ -38,17 +35,14 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /nick - показать текущий ник
|
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
return showCurrentNick(player);
|
return showCurrentNick(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /nick reset - сбросить ник
|
|
||||||
if (args[0].equalsIgnoreCase("reset")) {
|
if (args[0].equalsIgnoreCase("reset")) {
|
||||||
return resetNick(player);
|
return resetNick(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /nick <ник> - установить ник
|
|
||||||
String nickname = String.join(" ", args);
|
String nickname = String.join(" ", args);
|
||||||
return setNick(player, nickname);
|
return setNick(player, nickname);
|
||||||
}
|
}
|
||||||
|
|
@ -87,27 +81,23 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setNick(Player player, String nickname) {
|
private boolean setNick(Player player, String nickname) {
|
||||||
// Валидация
|
|
||||||
if (!nickService.isValidNickname(nickname)) {
|
if (!nickService.isValidNickname(nickname)) {
|
||||||
player.sendMessage(Component.text("Невалидный ник!", NamedTextColor.RED));
|
player.sendMessage(Component.text("Невалидный ник!", NamedTextColor.RED));
|
||||||
player.sendMessage(Component.text("Требования:", NamedTextColor.GRAY));
|
player.sendMessage(Component.text("Требования:", NamedTextColor.GRAY));
|
||||||
player.sendMessage(Component.text("• Длина: 3-16 символов", NamedTextColor.GRAY));
|
player.sendMessage(Component.text("• Длина: 3-16 символов", NamedTextColor.GRAY));
|
||||||
player.sendMessage(Component.text("• Разрешены: буквы, цифры, _, русские буквы", NamedTextColor.GRAY));
|
player.sendMessage(Component.text("• Разрешены: буквы, цифры, _, русские буквы", NamedTextColor.GRAY));
|
||||||
player.sendMessage(Component.text("• Поддержка цветов: <red>текст</red>, <gradient:#FF0000:#00FF00>текст</gradient>", NamedTextColor.GRAY));
|
player.sendMessage(Component.text("• Поддержка цветов: &#FF0000текст, <gradient:&#FF0000:�FF00>текст</gradient>", NamedTextColor.GRAY));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка уникальности
|
|
||||||
if (nickService.isNicknameTaken(nickname)) {
|
if (nickService.isNicknameTaken(nickname)) {
|
||||||
var currentNick = nickService.getNickname(player.getUniqueId());
|
var currentNick = nickService.getNickname(player.getUniqueId());
|
||||||
// Если это не свой текущий ник
|
|
||||||
if (currentNick.isEmpty() || !currentNick.get().equals(nickname)) {
|
if (currentNick.isEmpty() || !currentNick.get().equals(nickname)) {
|
||||||
player.sendMessage(Component.text("Этот ник уже занят!", NamedTextColor.RED));
|
player.sendMessage(Component.text("Этот ник уже занят!", NamedTextColor.RED));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Устанавливаем ник
|
|
||||||
boolean success = nickService.setNickname(player.getUniqueId(), nickname);
|
boolean success = nickService.setNickname(player.getUniqueId(), nickname);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
@ -126,8 +116,22 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
|
String input = args[0].toLowerCase();
|
||||||
|
|
||||||
|
if ("reset".startsWith(input)) {
|
||||||
completions.add("reset");
|
completions.add("reset");
|
||||||
completions.add("<ник>");
|
}
|
||||||
|
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
var currentNick = nickService.getNickname(player.getUniqueId());
|
||||||
|
if (currentNick.isPresent()) {
|
||||||
|
completions.add(currentNick.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completions.add("&#FF0000Красный");
|
||||||
|
completions.add("�FF00Зелёный");
|
||||||
|
completions.add("Русский_Ник");
|
||||||
}
|
}
|
||||||
|
|
||||||
return completions;
|
return completions;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
package com.loki.lochat.core.filter;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class AdvancedMessageFilter {
|
||||||
|
|
||||||
|
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||||
|
"(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?",
|
||||||
|
Pattern.CASE_INSENSITIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Pattern IP_PATTERN = Pattern.compile(
|
||||||
|
"\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Pattern REPEAT_PATTERN = Pattern.compile(
|
||||||
|
"(.)\\1{3,}"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final FileConfiguration config;
|
||||||
|
private final List<String> whitelistedDomains;
|
||||||
|
private final List<String> blacklistedDomains;
|
||||||
|
|
||||||
|
public AdvancedMessageFilter(FileConfiguration config) {
|
||||||
|
this.config = config;
|
||||||
|
this.whitelistedDomains = config.getStringList("filters.url.whitelist");
|
||||||
|
this.blacklistedDomains = config.getStringList("filters.url.blacklist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterResult filterMessage(Player player, String message) {
|
||||||
|
if (config.getBoolean("filters.url.enabled", true)) {
|
||||||
|
FilterResult urlResult = filterUrls(player, message);
|
||||||
|
if (!urlResult.isAllowed()) {
|
||||||
|
return urlResult;
|
||||||
|
}
|
||||||
|
message = urlResult.getFilteredMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.getBoolean("filters.ip.enabled", true)) {
|
||||||
|
FilterResult ipResult = filterIPs(player, message);
|
||||||
|
if (!ipResult.isAllowed()) {
|
||||||
|
return ipResult;
|
||||||
|
}
|
||||||
|
message = ipResult.getFilteredMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.getBoolean("filters.repeat.enabled", true)) {
|
||||||
|
message = filterRepeatingChars(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FilterResult(true, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterResult filterUrls(Player player, String message) {
|
||||||
|
if (player.hasPermission("lochat.bypass.urlfilter")) {
|
||||||
|
return new FilterResult(true, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = URL_PATTERN.matcher(message);
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String url = matcher.group();
|
||||||
|
String domain = extractDomain(url);
|
||||||
|
|
||||||
|
if (!whitelistedDomains.isEmpty()) {
|
||||||
|
boolean isWhitelisted = whitelistedDomains.stream()
|
||||||
|
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
|
||||||
|
|
||||||
|
if (!isWhitelisted) {
|
||||||
|
return new FilterResult(false, message,
|
||||||
|
config.getString("filters.url.blocked-message", "&#FF6B6BСсылки запрещены!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blacklistedDomains.isEmpty()) {
|
||||||
|
boolean isBlacklisted = blacklistedDomains.stream()
|
||||||
|
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
|
||||||
|
|
||||||
|
if (isBlacklisted) {
|
||||||
|
return new FilterResult(false, message,
|
||||||
|
config.getString("filters.url.blocked-message", "&#FF6B6BЭта ссылка запрещена!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FilterResult(true, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterResult filterIPs(Player player, String message) {
|
||||||
|
if (player.hasPermission("lochat.bypass.ipfilter")) {
|
||||||
|
return new FilterResult(true, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = IP_PATTERN.matcher(message);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
String replacement = config.getString("filters.ip.replacement", "[IP скрыт]");
|
||||||
|
String filtered = matcher.replaceAll(replacement);
|
||||||
|
|
||||||
|
if (config.getBoolean("filters.ip.block", false)) {
|
||||||
|
return new FilterResult(false, message,
|
||||||
|
config.getString("filters.ip.blocked-message", "&#FF6B6BIP адреса запрещены!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FilterResult(true, filtered, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FilterResult(true, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String filterRepeatingChars(String message) {
|
||||||
|
int maxRepeats = config.getInt("filters.repeat.max-repeats", 3);
|
||||||
|
|
||||||
|
Matcher matcher = REPEAT_PATTERN.matcher(message);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String repeated = matcher.group(1);
|
||||||
|
String replacement = repeated.repeat(Math.min(maxRepeats, matcher.group().length()));
|
||||||
|
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractDomain(String url) {
|
||||||
|
url = url.replaceFirst("^https?://", "");
|
||||||
|
int slashIndex = url.indexOf('/');
|
||||||
|
if (slashIndex != -1) {
|
||||||
|
url = url.substring(0, slashIndex);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FilterResult {
|
||||||
|
private final boolean allowed;
|
||||||
|
private final String filteredMessage;
|
||||||
|
private final String blockReason;
|
||||||
|
|
||||||
|
public FilterResult(boolean allowed, String filteredMessage, String blockReason) {
|
||||||
|
this.allowed = allowed;
|
||||||
|
this.filteredMessage = filteredMessage;
|
||||||
|
this.blockReason = blockReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowed() {
|
||||||
|
return allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilteredMessage() {
|
||||||
|
return filteredMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBlockReason() {
|
||||||
|
return blockReason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,25 +9,29 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
|
||||||
* Реестр сервисов - Dependency Injection контейнер (SOLID DIP)
|
|
||||||
*/
|
|
||||||
public class ServiceRegistry {
|
public class ServiceRegistry {
|
||||||
private final Map<Class<?>, Object> services = new HashMap<>();
|
private final Map<Class<?>, Object> services = new HashMap<>();
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
|
|
||||||
public ServiceRegistry(JavaPlugin plugin) {
|
public ServiceRegistry(JavaPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
registerServices();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
|
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
initWithDeps(configManager, messageConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
registerServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
|
||||||
registerServicesWithDeps(configManager, messageConfig);
|
registerServicesWithDeps(configManager, messageConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerServices() {
|
private void registerServices() {
|
||||||
// Регистрируем все сервисы (базовая версия)
|
|
||||||
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
||||||
register(MuteService.class, new MuteServiceImpl(plugin));
|
register(MuteService.class, new MuteServiceImpl(plugin));
|
||||||
register(CooldownService.class, new CooldownServiceImpl());
|
register(CooldownService.class, new CooldownServiceImpl());
|
||||||
|
|
@ -36,14 +40,12 @@ public class ServiceRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerServicesWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
|
private void registerServicesWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
|
||||||
// Регистрируем все сервисы с зависимостями
|
|
||||||
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
||||||
register(MuteService.class, new MuteServiceImpl(plugin));
|
register(MuteService.class, new MuteServiceImpl(plugin));
|
||||||
register(CooldownService.class, new CooldownServiceImpl());
|
register(CooldownService.class, new CooldownServiceImpl());
|
||||||
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
||||||
register(PlayerDataService.class, new PlayerDataServiceImpl(plugin));
|
register(PlayerDataService.class, new PlayerDataServiceImpl(plugin));
|
||||||
|
|
||||||
// Новые сервисы
|
|
||||||
register(PMService.class, new PMServiceImpl());
|
register(PMService.class, new PMServiceImpl());
|
||||||
register(IgnoreService.class, new IgnoreServiceImpl(plugin));
|
register(IgnoreService.class, new IgnoreServiceImpl(plugin));
|
||||||
register(SpyService.class, new SpyServiceImpl(plugin, messageConfig));
|
register(SpyService.class, new SpyServiceImpl(plugin, messageConfig));
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.loki.lochat.listener;
|
package com.loki.lochat.listener;
|
||||||
|
|
||||||
import com.loki.lochat.api.service.MessageService;
|
import com.loki.lochat.api.service.MessageService;
|
||||||
|
import com.loki.lochat.core.filter.AdvancedMessageFilter;
|
||||||
import com.loki.lochat.core.registry.ServiceRegistry;
|
import com.loki.lochat.core.registry.ServiceRegistry;
|
||||||
import com.loki.lochat.util.FoliaUtil;
|
import com.loki.lochat.utils.MentionHandler;
|
||||||
|
import com.loki.lochat.utils.TextFormatter;
|
||||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||||
import io.papermc.paper.chat.ChatRenderer;
|
import io.papermc.paper.chat.ChatRenderer;
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
|
@ -12,24 +14,23 @@ import net.kyori.adventure.text.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
/**
|
|
||||||
* Слушатель событий чата с Paper Chat API
|
|
||||||
* Поддерживает hover tooltips, click actions, mentions
|
|
||||||
*/
|
|
||||||
public class ChatEventListener implements Listener {
|
public class ChatEventListener implements Listener {
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final MessageService messageService;
|
private final MessageService messageService;
|
||||||
|
private final AdvancedMessageFilter advancedFilter;
|
||||||
|
private final MentionHandler mentionHandler;
|
||||||
|
|
||||||
public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) {
|
public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.messageService = registry.get(MessageService.class);
|
this.messageService = registry.get(MessageService.class);
|
||||||
|
this.advancedFilter = new AdvancedMessageFilter(plugin.getConfig());
|
||||||
|
this.mentionHandler = new MentionHandler(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
|
@ -37,7 +38,6 @@ public class ChatEventListener implements Listener {
|
||||||
Player sender = event.getPlayer();
|
Player sender = event.getPlayer();
|
||||||
String message = PlainTextComponentSerializer.plainText().serialize(event.message());
|
String message = PlainTextComponentSerializer.plainText().serialize(event.message());
|
||||||
|
|
||||||
// Проверяем фильтры (мут, кулдаун)
|
|
||||||
boolean allowed = messageService.processMessage(sender, message);
|
boolean allowed = messageService.processMessage(sender, message);
|
||||||
|
|
||||||
if (!allowed) {
|
if (!allowed) {
|
||||||
|
|
@ -45,13 +45,20 @@ public class ChatEventListener implements Listener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Устанавливаем кастомный рендерер
|
AdvancedMessageFilter.FilterResult filterResult = advancedFilter.filterMessage(sender, message);
|
||||||
|
|
||||||
|
if (!filterResult.isAllowed()) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.getBlockReason()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String filteredMessage = filterResult.getFilteredMessage();
|
||||||
|
event.message(Component.text(filteredMessage));
|
||||||
|
|
||||||
event.renderer(new EnhancedChatRenderer(sender, event.message()));
|
event.renderer(new EnhancedChatRenderer(sender, event.message()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Продвинутый рендерер чата с hover/click/mentions
|
|
||||||
*/
|
|
||||||
private class EnhancedChatRenderer implements ChatRenderer {
|
private class EnhancedChatRenderer implements ChatRenderer {
|
||||||
private final Player sender;
|
private final Player sender;
|
||||||
private final Component originalMessage;
|
private final Component originalMessage;
|
||||||
|
|
@ -63,47 +70,31 @@ public class ChatEventListener implements Listener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
||||||
// Получаем конфиг формата
|
|
||||||
String format = plugin.getConfig().getString("chat.global.format", "{prefix} {player}: {message}");
|
|
||||||
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
|
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
|
||||||
|
|
||||||
// Создаём компонент с hover и click
|
|
||||||
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
|
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
|
||||||
|
|
||||||
// Обрабатываем сообщение
|
|
||||||
Component processedMessage = message;
|
Component processedMessage = message;
|
||||||
|
|
||||||
// 1. Обрабатываем URL (делаем кликабельными)
|
processedMessage = TextFormatter.formatMarkdown(processedMessage);
|
||||||
processedMessage = processUrls(processedMessage);
|
processedMessage = processUrls(processedMessage);
|
||||||
|
|
||||||
// 2. Обрабатываем эмодзи шорткоды (:smile: -> 😊)
|
|
||||||
processedMessage = processEmojis(processedMessage);
|
processedMessage = processEmojis(processedMessage);
|
||||||
|
|
||||||
// 3. Обрабатываем упоминания если viewer это Player
|
|
||||||
if (viewer instanceof Player viewerPlayer) {
|
if (viewer instanceof Player viewerPlayer) {
|
||||||
processedMessage = processMentions(processedMessage, viewerPlayer);
|
processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Собираем финальное сообщение
|
|
||||||
return Component.text(prefix + " ")
|
return Component.text(prefix + " ")
|
||||||
.append(playerComponent)
|
.append(playerComponent)
|
||||||
.append(Component.text(": "))
|
.append(Component.text(": "))
|
||||||
.append(processedMessage);
|
.append(processedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Создаёт компонент имени игрока с hover и click
|
|
||||||
*/
|
|
||||||
private Component createPlayerComponent(Player player, Component displayName) {
|
private Component createPlayerComponent(Player player, Component displayName) {
|
||||||
// Проверяем включен ли hover
|
|
||||||
if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) {
|
if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) {
|
||||||
return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем формат из конфига
|
|
||||||
java.util.List<String> hoverLines = plugin.getConfig().getStringList("chat.hover.format");
|
java.util.List<String> hoverLines = plugin.getConfig().getStringList("chat.hover.format");
|
||||||
if (hoverLines.isEmpty()) {
|
if (hoverLines.isEmpty()) {
|
||||||
// Дефолтный формат если конфиг пустой
|
|
||||||
hoverLines = java.util.Arrays.asList(
|
hoverLines = java.util.Arrays.asList(
|
||||||
"<gray>Игрок: <white>{player}",
|
"<gray>Игрок: <white>{player}",
|
||||||
"<gray>Мир: <green>{world}",
|
"<gray>Мир: <green>{world}",
|
||||||
|
|
@ -112,12 +103,10 @@ public class ChatEventListener implements Listener {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Строим hover text
|
|
||||||
net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text();
|
net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text();
|
||||||
for (int i = 0; i < hoverLines.size(); i++) {
|
for (int i = 0; i < hoverLines.size(); i++) {
|
||||||
String line = hoverLines.get(i);
|
String line = hoverLines.get(i);
|
||||||
|
|
||||||
// Заменяем плейсхолдеры
|
|
||||||
line = line.replace("{player}", player.getName())
|
line = line.replace("{player}", player.getName())
|
||||||
.replace("{world}", player.getWorld().getName())
|
.replace("{world}", player.getWorld().getName())
|
||||||
.replace("{ping}", String.valueOf(player.getPing()))
|
.replace("{ping}", String.valueOf(player.getPing()))
|
||||||
|
|
@ -125,62 +114,28 @@ public class ChatEventListener implements Listener {
|
||||||
.replace("{health}", String.valueOf(Math.round(player.getHealth())))
|
.replace("{health}", String.valueOf(Math.round(player.getHealth())))
|
||||||
.replace("{food}", String.valueOf(player.getFoodLevel()));
|
.replace("{food}", String.valueOf(player.getFoodLevel()));
|
||||||
|
|
||||||
// Парсим MiniMessage
|
|
||||||
Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line);
|
Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line);
|
||||||
hoverBuilder.append(lineComponent);
|
hoverBuilder.append(lineComponent);
|
||||||
|
|
||||||
// Добавляем перенос строки если не последняя
|
|
||||||
if (i < hoverLines.size() - 1) {
|
if (i < hoverLines.size() - 1) {
|
||||||
hoverBuilder.append(Component.newline());
|
hoverBuilder.append(Component.newline());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click: открываем /msg
|
|
||||||
return displayName
|
return displayName
|
||||||
.hoverEvent(HoverEvent.showText(hoverBuilder.build()))
|
.hoverEvent(HoverEvent.showText(hoverBuilder.build()))
|
||||||
.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обрабатывает упоминания (@player) в сообщении
|
|
||||||
*/
|
|
||||||
private Component processMentions(Component message, Player viewer) {
|
|
||||||
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
|
||||||
|
|
||||||
// Проверяем упоминание текущего игрока
|
|
||||||
if (plainText.toLowerCase().contains("@" + viewer.getName().toLowerCase())) {
|
|
||||||
// Подсвечиваем упоминание
|
|
||||||
Component highlighted = message.replaceText(builder ->
|
|
||||||
builder.matchLiteral("@" + viewer.getName())
|
|
||||||
.replacement(Component.text("@" + viewer.getName(), NamedTextColor.YELLOW, TextDecoration.BOLD))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Воспроизводим звук уведомления
|
|
||||||
FoliaUtil.runEntityTask(plugin, viewer, () -> {
|
|
||||||
viewer.playSound(viewer.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.5f);
|
|
||||||
});
|
|
||||||
|
|
||||||
return highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Обрабатывает URL в сообщении (делает кликабельными)
|
|
||||||
*/
|
|
||||||
private Component processUrls(Component message) {
|
private Component processUrls(Component message) {
|
||||||
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
||||||
|
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!35538117-c5a8-4bfe-b9f2-b0dcf9374222'()*+,;=%]+)";
|
||||||
// Простой regex для URL
|
|
||||||
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)";
|
|
||||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
|
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
|
||||||
java.util.regex.Matcher matcher = pattern.matcher(plainText);
|
java.util.regex.Matcher matcher = pattern.matcher(plainText);
|
||||||
|
|
||||||
Component result = message;
|
Component result = message;
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String url = matcher.group(1);
|
String url = matcher.group(1);
|
||||||
// Делаем URL кликабельным с hover
|
|
||||||
Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
|
Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
|
||||||
.hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY)))
|
.hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY)))
|
||||||
.clickEvent(ClickEvent.openUrl(url));
|
.clickEvent(ClickEvent.openUrl(url));
|
||||||
|
|
@ -193,35 +148,28 @@ public class ChatEventListener implements Listener {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Обрабатывает эмодзи шорткоды (:smile: -> ☺)
|
|
||||||
* Использует только MC-совместимые Unicode символы
|
|
||||||
*/
|
|
||||||
private Component processEmojis(Component message) {
|
private Component processEmojis(Component message) {
|
||||||
Component result = message;
|
Component result = message;
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("❤"));
|
||||||
// MC-совместимые Unicode эмодзи
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":smile:").replacement("☺"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("♥"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("※"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★"));
|
result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✓"));
|
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✔"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✗"));
|
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✖"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":arrow:").replacement("→"));
|
result = result.replaceText(builder -> builder.matchLiteral(":arrow:").replacement("→"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":note:").replacement("♪"));
|
result = result.replaceText(builder -> builder.matchLiteral(":note:").replacement("♪"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":sun:").replacement("☀"));
|
result = result.replaceText(builder -> builder.matchLiteral(":sun:").replacement("☀"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☾"));
|
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☽"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":peace:").replacement("☮"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":yin:").replacement("☯"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":flower:").replacement("✿"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄"));
|
result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":lightning:").replacement("⚡"));
|
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("🔥"));
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("♔"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("⛨"));
|
|
||||||
result = result.replaceText(builder -> builder.matchLiteral(":diamond:").replacement("◆"));
|
result = result.replaceText(builder -> builder.matchLiteral(":diamond:").replacement("◆"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":pickaxe:").replacement("⛏"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":bow:").replacement("🏹"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("🛡"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":potion:").replacement("🧪"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":book:").replacement("📖"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("👑"));
|
||||||
|
result = result.replaceText(builder -> builder.matchLiteral(":gem:").replacement("💎"));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ public class CustomCommandManager {
|
||||||
public CustomCommandManager(LoChat plugin) {
|
public CustomCommandManager(LoChat plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.commands = new HashMap<>();
|
this.commands = new HashMap<>();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
loadCommands();
|
loadCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
97
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
97
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EmojiManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Map<String, String> emojis = new HashMap<>();
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean requirePermission;
|
||||||
|
|
||||||
|
public EmojiManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
File file = new File(plugin.getDataFolder(), "emojis.yml");
|
||||||
|
if (!file.exists()) {
|
||||||
|
plugin.saveResource("emojis.yml", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||||
|
emojis.clear();
|
||||||
|
|
||||||
|
// Настройки
|
||||||
|
enabled = config.getBoolean("settings.enabled", true);
|
||||||
|
requirePermission = config.getBoolean("settings.require-permission", false);
|
||||||
|
|
||||||
|
// Загружаем все категории
|
||||||
|
for (String category : config.getKeys(false)) {
|
||||||
|
if (category.equals("settings")) continue;
|
||||||
|
|
||||||
|
ConfigurationSection section = config.getConfigurationSection(category);
|
||||||
|
if (section != null) {
|
||||||
|
for (String key : section.getKeys(false)) {
|
||||||
|
String value = section.getString(key);
|
||||||
|
if (value != null) {
|
||||||
|
emojis.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("Загружено " + emojis.size() + " смайликов");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет смайлики в сообщении
|
||||||
|
*/
|
||||||
|
public String process(String message, Player player) {
|
||||||
|
if (!enabled) return message;
|
||||||
|
|
||||||
|
// Проверка права
|
||||||
|
if (requirePermission && player != null && !player.hasPermission("chat.emoji.use")) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = message;
|
||||||
|
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||||
|
result = result.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String process(String message) {
|
||||||
|
if (!enabled) return message;
|
||||||
|
|
||||||
|
String result = message;
|
||||||
|
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||||
|
result = result.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEmojis() {
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/main/java/com/loki/lochat/utils/MentionHandler.java
Normal file
106
src/main/java/com/loki/lochat/utils/MentionHandler.java
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.loki.lochat.utils;
|
||||||
|
|
||||||
|
import com.loki.lochat.util.FoliaUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MentionHandler {
|
||||||
|
|
||||||
|
private static final Pattern EVERYONE_PATTERN = Pattern.compile("@everyone\\b");
|
||||||
|
private static final Pattern HERE_PATTERN = Pattern.compile("@here\\b");
|
||||||
|
private static final Pattern ROLE_PATTERN = Pattern.compile("@role:([\\w.]+)");
|
||||||
|
private static final Pattern PLAYER_PATTERN = Pattern.compile("@(\\w+)");
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
|
||||||
|
public MentionHandler(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component processMentions(Component message, Player sender, Player viewer) {
|
||||||
|
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
||||||
|
List<Player> mentionedPlayers = new ArrayList<>();
|
||||||
|
|
||||||
|
if (sender.hasPermission("lochat.mention.everyone")) {
|
||||||
|
Matcher everyoneMatcher = EVERYONE_PATTERN.matcher(plainText);
|
||||||
|
if (everyoneMatcher.find()) {
|
||||||
|
mentionedPlayers.addAll(Bukkit.getOnlinePlayers());
|
||||||
|
message = highlightMention(message, "@everyone");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender.hasPermission("lochat.mention.here")) {
|
||||||
|
Matcher hereMatcher = HERE_PATTERN.matcher(plainText);
|
||||||
|
if (hereMatcher.find()) {
|
||||||
|
mentionedPlayers.addAll(Bukkit.getOnlinePlayers());
|
||||||
|
message = highlightMention(message, "@here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender.hasPermission("lochat.mention.role")) {
|
||||||
|
Matcher roleMatcher = ROLE_PATTERN.matcher(plainText);
|
||||||
|
while (roleMatcher.find()) {
|
||||||
|
String permission = roleMatcher.group(1);
|
||||||
|
String fullMention = "@role:" + permission;
|
||||||
|
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (player.hasPermission(permission)) {
|
||||||
|
mentionedPlayers.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message = highlightMention(message, fullMention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher playerMatcher = PLAYER_PATTERN.matcher(plainText);
|
||||||
|
while (playerMatcher.find()) {
|
||||||
|
String playerName = playerMatcher.group(1);
|
||||||
|
Player mentioned = Bukkit.getPlayerExact(playerName);
|
||||||
|
|
||||||
|
if (mentioned != null && mentioned.isOnline()) {
|
||||||
|
mentionedPlayers.add(mentioned);
|
||||||
|
message = highlightMention(message, "@" + playerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mentionedPlayers.contains(viewer)) {
|
||||||
|
playMentionSound(viewer);
|
||||||
|
message = highlightForViewer(message, viewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component highlightMention(Component message, String mention) {
|
||||||
|
return message.replaceText(builder ->
|
||||||
|
builder.matchLiteral(mention)
|
||||||
|
.replacement(Component.text(mention, NamedTextColor.YELLOW, TextDecoration.BOLD))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component highlightForViewer(Component message, Player viewer) {
|
||||||
|
String viewerMention = "@" + viewer.getName();
|
||||||
|
return message.replaceText(builder ->
|
||||||
|
builder.matchLiteral(viewerMention)
|
||||||
|
.replacement(Component.text(viewerMention, NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playMentionSound(Player player) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, () -> {
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.5f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/main/java/com/loki/lochat/utils/TextFormatter.java
Normal file
95
src/main/java/com/loki/lochat/utils/TextFormatter.java
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.loki.lochat.utils;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class TextFormatter {
|
||||||
|
|
||||||
|
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*");
|
||||||
|
private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*(.+?)\\*");
|
||||||
|
private static final Pattern UNDERLINE_PATTERN = Pattern.compile("__(.+?)__");
|
||||||
|
private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~(.+?)~~");
|
||||||
|
private static final Pattern CODE_PATTERN = Pattern.compile("`(.+?)`");
|
||||||
|
|
||||||
|
public static Component formatMarkdown(Component message) {
|
||||||
|
String text = PlainTextComponentSerializer.plainText().serialize(message);
|
||||||
|
|
||||||
|
text = formatBold(text);
|
||||||
|
text = formatItalic(text);
|
||||||
|
text = formatUnderline(text);
|
||||||
|
text = formatStrikethrough(text);
|
||||||
|
text = formatCode(text);
|
||||||
|
|
||||||
|
return ChatFormatter.parse(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatBold(String text) {
|
||||||
|
Matcher matcher = BOLD_PATTERN.matcher(text);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String content = matcher.group(1);
|
||||||
|
matcher.appendReplacement(result, "<bold>" + content + "</bold>");
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatItalic(String text) {
|
||||||
|
Matcher matcher = ITALIC_PATTERN.matcher(text);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String content = matcher.group(1);
|
||||||
|
if (!content.startsWith("*") && !content.endsWith("*")) {
|
||||||
|
matcher.appendReplacement(result, "<italic>" + content + "</italic>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatUnderline(String text) {
|
||||||
|
Matcher matcher = UNDERLINE_PATTERN.matcher(text);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String content = matcher.group(1);
|
||||||
|
matcher.appendReplacement(result, "<underlined>" + content + "</underlined>");
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatStrikethrough(String text) {
|
||||||
|
Matcher matcher = STRIKETHROUGH_PATTERN.matcher(text);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String content = matcher.group(1);
|
||||||
|
matcher.appendReplacement(result, "<strikethrough>" + content + "</strikethrough>");
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatCode(String text) {
|
||||||
|
Matcher matcher = CODE_PATTERN.matcher(text);
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String content = matcher.group(1);
|
||||||
|
matcher.appendReplacement(result, "󙀸`" + content + "`");
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
# Префикс автосообщений
|
# Префикс автосообщений
|
||||||
prefix: "<gradient:#00D4FF:#0099CC>[INFO]</gradient>"
|
prefix: "�D4FF[�D4FFI�CAF7N�C0EEF�B7E6OcCC]"
|
||||||
|
|
||||||
# Режим отправки: sequential (по порядку), random (случайно), custom (свой порядок)
|
# Режим отправки: sequential (по порядку), random (случайно), custom (свой порядок)
|
||||||
mode: "sequential"
|
mode: "sequential"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ config-version: 3
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
# Префикс плагина в сообщениях
|
# Префикс плагина в сообщениях
|
||||||
prefix: "<gradient:#00D4FF:#0099CC>[LoChat]</gradient>"
|
prefix: "�D4FF[�D4FFL�CAF7o�C0EEC�B7E6h�ADDDa�A3D5tcCC]"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ НАСТРОЙКИ ЧАТА │
|
# │ НАСТРОЙКИ ЧАТА │
|
||||||
|
|
@ -30,9 +30,8 @@ chat:
|
||||||
# Кулдаун между сообщениями (секунды)
|
# Кулдаун между сообщениями (секунды)
|
||||||
cooldown: 3
|
cooldown: 3
|
||||||
# Префикс глобального чата
|
# Префикс глобального чата
|
||||||
prefix: "<gradient:#FFD700:#FFA500>[G]</gradient>"
|
prefix: "&#FFD700[G]"
|
||||||
# Формат сообщения: {prefix} {player} {message}
|
format: "{prefix} �FF88{player}򇨣: {message}"
|
||||||
format: "{prefix} <gradient:#00FF88:#00CC66>{player}</gradient><dark_gray>:</dark_gray> {message}"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# ЛОКАЛЬНЫЙ ЧАТ
|
# ЛОКАЛЬНЫЙ ЧАТ
|
||||||
|
|
@ -45,9 +44,8 @@ chat:
|
||||||
# Кулдаун между сообщениями (секунды)
|
# Кулдаун между сообщениями (секунды)
|
||||||
cooldown: 1
|
cooldown: 1
|
||||||
# Префикс локального чата
|
# Префикс локального чата
|
||||||
prefix: "<gradient:#87CEEB:#4682B4>[L]</gradient>"
|
prefix: "WCEEB[L]"
|
||||||
# Формат сообщения
|
format: "{prefix} �FF88{player}򇨣: {message}"
|
||||||
format: "{prefix} <gradient:#00FF88:#00CC66>{player}</gradient><dark_gray>:</dark_gray> {message}"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# ЛИЧНЫЕ СООБЩЕНИЯ
|
# ЛИЧНЫЕ СООБЩЕНИЯ
|
||||||
|
|
@ -111,9 +109,8 @@ mentions:
|
||||||
# Тип звука
|
# Тип звука
|
||||||
sound-type: "BLOCK_NOTE_BLOCK_PLING"
|
sound-type: "BLOCK_NOTE_BLOCK_PLING"
|
||||||
# Подсветка упоминания в чате
|
# Подсветка упоминания в чате
|
||||||
highlight: "<gradient:#FFFF00:#FFD700>@{player}</gradient>"
|
highlight: "&#FFFF00@{player}"
|
||||||
# Подсветка собственного ника
|
self-highlight: "&#FFD700{player}"
|
||||||
self-highlight: "<gradient:#FFD700:#FFA500>{player}</gradient>"
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ АВТОСООБЩЕНИЯ │
|
# │ АВТОСООБЩЕНИЯ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
@ -134,7 +131,7 @@ announcements:
|
||||||
# Показывать title при объявлении
|
# Показывать title при объявлении
|
||||||
show-title: true
|
show-title: true
|
||||||
# Заголовок title
|
# Заголовок title
|
||||||
title-header: "<gradient:#FF6B6B:#FF4757>ОБЪЯВЛЕНИЕ</gradient>"
|
title-header: "&#FF6B6BОБЪЯВЛЕНИЕ"
|
||||||
# Показывать actionbar при объявлении
|
# Показывать actionbar при объявлении
|
||||||
show-actionbar: true
|
show-actionbar: true
|
||||||
# Время показа title (в секундах)
|
# Время показа title (в секундах)
|
||||||
|
|
@ -142,7 +139,7 @@ announcements:
|
||||||
|
|
||||||
# Формат объявления
|
# Формат объявления
|
||||||
formats:
|
formats:
|
||||||
announcement: "<gradient:#FF6B6B:#FF4757>[ОБЪЯВЛЕНИЕ]</gradient> <message>"
|
announcement: "&#FF6B6B[ОБЪЯВЛЕНИЕ] <message>"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ОЧИСТКА ЧАТА │
|
# │ ОЧИСТКА ЧАТА │
|
||||||
|
|
@ -153,7 +150,7 @@ clearchat:
|
||||||
# Показывать сообщение после очистки чата
|
# Показывать сообщение после очистки чата
|
||||||
enabled: true
|
enabled: true
|
||||||
# Текст сообщения ({player} - имя администратора)
|
# Текст сообщения ({player} - имя администратора)
|
||||||
text: "<gradient:#FF6B6B:#FF4757>Чат очищен администратором {player}</gradient>"
|
text: "&#FF6B6BЧат очищен администратором {player}"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ГОЛОВЫ ИГРОКОВ │
|
# │ ГОЛОВЫ ИГРОКОВ │
|
||||||
|
|
@ -179,20 +176,14 @@ mute:
|
||||||
# %operator% - модератор
|
# %operator% - модератор
|
||||||
|
|
||||||
messages:
|
messages:
|
||||||
# Сообщение при муте (broadcast)
|
muted: "&#FF6B6B%player% 󙀸был замучен на &#FF6B6B%duration% 󙀸модератором &#FF6B6B%operator%󙀸. Причина: &#FF6B6B%reason%"
|
||||||
muted: "<gradient:#FF6B6B:#FF4757>%player%</gradient> <gray>был замучен на</gray> <gradient:#FF6B6B:#FF4757>%duration%</gradient> <gray>модератором</gray> <gradient:#FF6B6B:#FF4757>%operator%</gradient><gray>. Причина:</gray> <gradient:#FF6B6B:#FF4757>%reason%</gradient>"
|
unmuted: "BCB77%player% 󙀸был размучен модератором BCB77%operator%"
|
||||||
# Сообщение при размуте (broadcast)
|
you-muted: "&#FF6B6BВы замучены на %duration%! Причина: %reason%"
|
||||||
unmuted: "<gradient:#6BCB77:#4ECDC4>%player%</gradient> <gray>был размучен модератором</gray> <gradient:#6BCB77:#4ECDC4>%operator%</gradient>"
|
you-muted-permanent: "&#FF6B6BВы замучены навсегда! Причина: %reason%"
|
||||||
# Сообщение замученному игроку
|
cannot-chat: "&#FF6B6BВы замучены! Осталось: %duration%"
|
||||||
you-muted: "<gradient:#FF6B6B:#FF4757>Вы замучены на %duration%! Причина: %reason%</gradient>"
|
cannot-chat-permanent: "&#FF6B6BВы замучены навсегда!"
|
||||||
# Сообщение при перманентном муте
|
silent-muted: "󙀸[Тихо] &#FF6B6B%player% 󙀸замучен на &#FF6B6B%duration% 󙀸(%operator%). Причина: %reason%"
|
||||||
you-muted-permanent: "<gradient:#FF6B6B:#FF4757>Вы замучены навсегда! Причина: %reason%</gradient>"
|
silent-unmuted: "󙀸[Тихо] BCB77%player% 󙀸размучен (%operator%)"
|
||||||
# Сообщение при попытке написать в чат
|
|
||||||
cannot-chat: "<gradient:#FF6B6B:#FF4757>Вы замучены! Осталось: %duration%</gradient>"
|
|
||||||
cannot-chat-permanent: "<gradient:#FF6B6B:#FF4757>Вы замучены навсегда!</gradient>"
|
|
||||||
# Тихий мут (только для тех кто видит)
|
|
||||||
silent-muted: "<gray>[Тихо]</gray> <gradient:#FF6B6B:#FF4757>%player%</gradient> <gray>замучен на</gray> <gradient:#FF6B6B:#FF4757>%duration%</gradient> <gray>(%operator%). Причина: %reason%</gray>"
|
|
||||||
silent-unmuted: "<gray>[Тихо]</gray> <gradient:#6BCB77:#4ECDC4>%player%</gradient> <gray>размучен (%operator%)</gray>"
|
|
||||||
|
|
||||||
# Причина по умолчанию если не указана
|
# Причина по умолчанию если не указана
|
||||||
default-reason: "Нарушение правил"
|
default-reason: "Нарушение правил"
|
||||||
|
|
@ -213,3 +204,45 @@ mute:
|
||||||
- "14d"
|
- "14d"
|
||||||
- "30d"
|
- "30d"
|
||||||
- "perm"
|
- "perm"
|
||||||
|
|
||||||
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
# │ ФИЛЬТРЫ СООБЩЕНИЙ │
|
||||||
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
filters:
|
||||||
|
url:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- "youtube.com"
|
||||||
|
- "youtu.be"
|
||||||
|
- "discord.gg"
|
||||||
|
- "twitch.tv"
|
||||||
|
blacklist:
|
||||||
|
- "scam-site.com"
|
||||||
|
blocked-message: "&#FF6B6BС&#FF5F5Fс&#FF5353ы&#FF4747л&#FF3B3Bк&#FF2F2Fи &#FF2323з&#FF1717а&#FF0B0Bп&#FF0000р&#FF0000е&#FF0000щ&#FF0000е&#FF0000н&#FF0000ы!"
|
||||||
|
|
||||||
|
ip:
|
||||||
|
enabled: true
|
||||||
|
block: false
|
||||||
|
replacement: "[IP скрыт]"
|
||||||
|
blocked-message: "&#FF6B6BI&#FF5F5FP &#FF5353а&#FF4747д&#FF3B3Bр&#FF2F2Fе&#FF2323с&#FF1717а &#FF0B0Bз&#FF0000а&#FF0000п&#FF0000р&#FF0000е&#FF0000щ&#FF0000е&#FF0000н&#FF0000ы!"
|
||||||
|
|
||||||
|
repeat:
|
||||||
|
enabled: true
|
||||||
|
max-repeats: 3
|
||||||
|
|
||||||
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
# │ УПОМИНАНИЯ │
|
||||||
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
advanced-mentions:
|
||||||
|
enabled: true
|
||||||
|
everyone:
|
||||||
|
enabled: true
|
||||||
|
permission: "lochat.mention.everyone"
|
||||||
|
here:
|
||||||
|
enabled: true
|
||||||
|
permission: "lochat.mention.here"
|
||||||
|
role:
|
||||||
|
enabled: true
|
||||||
|
permission: "lochat.mention.role"
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
# Префикс градиентного модуля
|
# Префикс градиентного модуля
|
||||||
prefix: "<gradient:#9370DB:#8A2BE2>[Gradient]</gradient>"
|
prefix: "⒚DB[⒚DBGE6CD9r΀AD7aℓD5dF65D3iA62D1eᶈCFnˁDCDtB5BCB]"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ КОМАНДА /color │
|
# │ КОМАНДА /color │
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,23 @@
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
# Префикс плагина
|
# Префикс плагина
|
||||||
prefix: "<gradient:#00D4FF:#0099CC>[LoChat]</gradient>"
|
prefix: "�D4FF[�D4FFL�CAF7o�C0EEC�B7E6h�ADDDa�A3D5tcCC]"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ГЛОБАЛЬНЫЙ ЧАТ │
|
# │ ГЛОБАЛЬНЫЙ ЧАТ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
global:
|
global:
|
||||||
disabled: "<gradient:#FF6B6B:#FF4757>Глобальный чат отключен</gradient>"
|
disabled: "&#FF6B6BГ&#FF5F5Fл&#FF5353о&#FF4747б&#FF3B3Bа&#FF2F2Fл&#FF2323ь&#FF1717н&#FF0B0Bы&#FF0000й &#FF0000ч&#FF0000а&#FF0000т &#FF0000о&#FF0000т&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н"
|
||||||
toggled-on: "<gradient:#6BCB77:#4ECDC4>Глобальный чат включен</gradient>"
|
toggled-on: "BCB77Г>C973лAC76Fо3C56Bб1C367а(C163л&BF5FьFBD5BнBB57ыEB953й B74F ч�DB54BаB347т B347вB347кB347лB347юB347чB347еB347н"
|
||||||
toggled-off: "<gradient:#FF6B6B:#FF4757>Глобальный чат отключен</gradient>"
|
toggled-off: "&#FF6B6BГ&#FF5F5Fл&#FF5353о&#FF4747б&#FF3B3Bа&#FF2F2Fл&#FF2323ь&#FF1717н&#FF0B0Bы&#FF0000й &#FF0000ч&#FF0000а&#FF0000т &#FF0000о&#FF0000т&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ЛОКАЛЬНЫЙ ЧАТ │
|
# │ ЛОКАЛЬНЫЙ ЧАТ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
local:
|
local:
|
||||||
no-players: "<gradient:#FFA726:#FF9800>Рядом нет игроков</gradient>"
|
no-players: "&#FFA726Р&#FFA322я&#FF9F1Eд&#FF9B1Aо&#FF9716м &#FF9312н&#FF8F0Eе&#FF8B0Aт &#FF8706и&#FF8302г&#FF8000р&#FF8000о&#FF8000к&#FF8000о&#FF8000в"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ЛИЧНЫЕ СООБЩЕНИЯ │
|
# │ ЛИЧНЫЕ СООБЩЕНИЯ │
|
||||||
|
|
@ -47,36 +47,36 @@ pm:
|
||||||
received: "<gray>[PM]</gray> <white>{player}</white> <gray>→</gray> <gradient:#87CEEB:#4682B4>Ты</gradient><gray>:</gray> {message}"
|
received: "<gray>[PM]</gray> <white>{player}</white> <gray>→</gray> <gradient:#87CEEB:#4682B4>Ты</gradient><gray>:</gray> {message}"
|
||||||
|
|
||||||
# Ошибки PM
|
# Ошибки PM
|
||||||
no-reply: "<gradient:#FF6B6B:#FF4757>Некому ответить</gradient>"
|
no-reply: "&#FF6B6BН&#FF5F5Fе&#FF5353к&#FF4747о&#FF3B3Bм&#FF2F2Fу &#FF2323о&#FF1717т&#FF0B0Bв&#FF0000е&#FF0000т&#FF0000и&#FF0000т&#FF0000ь"
|
||||||
ignored: "<gradient:#FF6B6B:#FF4757>Этот игрок вас игнорирует</gradient>"
|
ignored: "&#FF6B6BЭ&#FF5F5Fт&#FF5353о&#FF4747т &#FF3B3Bи&#FF2F2Fг&#FF2323р&#FF1717о&#FF0B0Bк &#FF0000в&#FF0000а&#FF0000с &#FF0000и&#FF0000г&#FF0000н&#FF0000о&#FF0000р&#FF0000и&#FF0000р&#FF0000у&#FF0000е&#FF0000т"
|
||||||
player-offline: "<gradient:#FF6B6B:#FF4757>Игрок не в сети</gradient>"
|
player-offline: "&#FF6B6BИ&#FF5F5Fг&#FF5353р&#FF4747о&#FF3B3Bк &#FF2F2Fн&#FF2323е &#FF1717в &#FF0B0Bс&#FF0000е&#FF0000т&#FF0000и"
|
||||||
self: "<gradient:#FF6B6B:#FF4757>Нельзя писать самому себе</gradient>"
|
self: "&#FF6B6BН&#FF5F5Fе&#FF5353л&#FF4747ь&#FF3B3Bз&#FF2F2Fя &#FF2323п&#FF1717и&#FF0B0Bс&#FF0000а&#FF0000т&#FF0000ь &#FF0000с&#FF0000а&#FF0000м&#FF0000о&#FF0000м&#FF0000у &#FF0000с&#FF0000е&#FF0000б&#FF0000е"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ИГНОРИРОВАНИЕ │
|
# │ ИГНОРИРОВАНИЕ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
added: "<gradient:#6BCB77:#4ECDC4>Игрок {player} добавлен в игнор</gradient>"
|
added: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347дB347оB347бB347аB347вB347лB347еB347н B347в B347иB347гB347нB347оB347р"
|
||||||
removed: "<gradient:#6BCB77:#4ECDC4>Игрок {player} убран из игнора</gradient>"
|
removed: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347уB347бB347рB347аB347н B347иB347з B347иB347гB347нB347оB347рB347а"
|
||||||
already-ignored: "<gradient:#FFA726:#FF9800>Игрок уже в игноре</gradient>"
|
already-ignored: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312у&#FF8F0Eж&#FF8B0Aе &#FF8706в &#FF8302и&#FF8000г&#FF8000н&#FF8000о&#FF8000р&#FF8000е"
|
||||||
not-ignored: "<gradient:#FFA726:#FF9800>Игрок не в игноре</gradient>"
|
not-ignored: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312н&#FF8F0Eе &#FF8B0Aв &#FF8706и&#FF8302г&#FF8000н&#FF8000о&#FF8000р&#FF8000е"
|
||||||
self: "<gradient:#FF6B6B:#FF4757>Нельзя игнорировать себя</gradient>"
|
self: "&#FF6B6BН&#FF5F5Fе&#FF5353л&#FF4747ь&#FF3B3Bз&#FF2F2Fя &#FF2323и&#FF1717г&#FF0B0Bн&#FF0000о&#FF0000р&#FF0000и&#FF0000р&#FF0000о&#FF0000в&#FF0000а&#FF0000т&#FF0000ь &#FF0000с&#FF0000е&#FF0000б&#FF0000я"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ОБЪЯВЛЕНИЯ │
|
# │ ОБЪЯВЛЕНИЯ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
announce:
|
announce:
|
||||||
reloaded: "<gradient:#6BCB77:#4ECDC4>Конфигурация перезагружена</gradient>"
|
reloaded: "BCB77К>C973оAC76Fн3C56Bф1C367и(C163г&BF5FуFBD5BрBB57аEB953цB74Fи�DB54Bя B347пB347еB347рB347еB347зB347аB347гB347рB347уB347жB347еB347нB347а"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ШПИОН РЕЖИМ │
|
# │ ШПИОН РЕЖИМ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
spy:
|
spy:
|
||||||
enabled: "<gradient:#6BCB77:#4ECDC4>Шпион режим включен</gradient>"
|
enabled: "BCB77Ш>C973пAC76Fи3C56Bо1C367н (C163р&BF5FеFBD5BжBB57иEB953м B74Fв�DB54BкB347лB347юB347чB347еB347н"
|
||||||
disabled: "<gradient:#FF6B6B:#FF4757>Шпион режим выключен</gradient>"
|
disabled: "&#FF6B6BШ&#FF5F5Fп&#FF5353и&#FF4747о&#FF3B3Bн &#FF2F2Fр&#FF2323е&#FF1717ж&#FF0B0Bи&#FF0000м &#FF0000в&#FF0000ы&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н"
|
||||||
format: "<gray>[SPY]</gray> <gradient:#FFD700:#FFA500>{sender}</gradient> <gray>→</gray> <gradient:#87CEEB:#4682B4>{receiver}</gradient><gray>:</gray> <white>{message}</white>"
|
format: "<gray>[SPY]</gray> <gradient:#FFD700:#FFA500>{sender}</gradient> <gray>→</gray> <gradient:#87CEEB:#4682B4>{receiver}</gradient><gray>:</gray> <white>{message}</white>"
|
||||||
chat-format: "<gray>[SPY]</gray> <gradient:#FFA726:#FF9800>{type}</gradient> <gradient:#00FF88:#00CC66>{sender}</gradient><gray>:</gray> <white>{message}</white>"
|
chat-format: "<gray>[SPY]</gray> <gradient:#FFA726:#FF9800>{type}</gradient> <gradient:#00FF88:#00CC66>{sender}</gradient><gray>:</gray> <white>{message}</white>"
|
||||||
|
|
||||||
|
|
@ -85,49 +85,49 @@ spy:
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
filter:
|
filter:
|
||||||
blocked: "<gradient:#FF6B6B:#FF4757>Сообщение заблокировано (недопустимые слова)</gradient>"
|
blocked: "&#FF6B6BС&#FF5F5Fо&#FF5353о&#FF4747б&#FF3B3Bщ&#FF2F2Fе&#FF2323н&#FF1717и&#FF0B0Bе &#FF0000з&#FF0000а&#FF0000б&#FF0000л&#FF0000о&#FF0000к&#FF0000и&#FF0000р&#FF0000о&#FF0000в&#FF0000а&#FF0000н&#FF0000о"
|
||||||
warning: "<gradient:#FFA726:#FF9800>Предупреждение: не используйте недопустимые слова!</gradient>"
|
warning: "&#FFA726П&#FFA322р&#FF9F1Eе&#FF9B1Aд&#FF9716у&#FF9312п&#FF8F0Eр&#FF8B0Aе&#FF8706ж&#FF8302д&#FF8000е&#FF8000н&#FF8000и&#FF8000е"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ КУЛДАУНЫ │
|
# │ КУЛДАУНЫ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
cooldown:
|
cooldown:
|
||||||
wait: "<gradient:#FFA726:#FF9800>Подождите {time} сек перед следующим сообщением</gradient>"
|
wait: "&#FFA726П&#FFA322о&#FF9F1Eд&#FF9B1Aо&#FF9716ж&#FF9312д&#FF8F0Eи&#FF8B0Aт&#FF8706е &#FF8302{&#FF8000t&#FF8000i&#FF8000m&#FF8000e&#FF8000}"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ АНТИ-СПАМ │
|
# │ АНТИ-СПАМ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
antispam:
|
antispam:
|
||||||
caps: "<gradient:#FF6B6B:#FF4757>Слишком много заглавных букв!</gradient>"
|
caps: "&#FF6B6BС&#FF5F5Fл&#FF5353и&#FF4747ш&#FF3B3Bк&#FF2F2Fо&#FF2323м &#FF1717м&#FF0B0Bн&#FF0000о&#FF0000г&#FF0000о &#FF0000з&#FF0000а&#FF0000г&#FF0000л&#FF0000а&#FF0000в&#FF0000н&#FF0000ы&#FF0000х"
|
||||||
repeat: "<gradient:#FF6B6B:#FF4757>Не повторяйте символы!</gradient>"
|
repeat: "&#FF6B6BН&#FF5F5Fе &#FF5353п&#FF4747о&#FF3B3Bв&#FF2F2Fт&#FF2323о&#FF1717р&#FF0B0Bя&#FF0000й&#FF0000т&#FF0000е &#FF0000с&#FF0000и&#FF0000м&#FF0000в&#FF0000о&#FF0000л&#FF0000ы"
|
||||||
similar: "<gradient:#FF6B6B:#FF4757>Не отправляйте одинаковые сообщения!</gradient>"
|
similar: "&#FF6B6BН&#FF5F5Fе &#FF5353о&#FF4747т&#FF3B3Bп&#FF2F2Fр&#FF2323а&#FF1717в&#FF0B0Bл&#FF0000я&#FF0000й&#FF0000т&#FF0000е &#FF0000о&#FF0000д&#FF0000и&#FF0000н&#FF0000а&#FF0000к&#FF0000о&#FF0000в&#FF0000ы&#FF0000е"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ОЧИСТКА ЧАТА │
|
# │ ОЧИСТКА ЧАТА │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
clearchat:
|
clearchat:
|
||||||
cleared: "<gradient:#6BCB77:#4ECDC4>Чат очищен администратором {player}</gradient>"
|
cleared: "BCB77Ч>C973аAC76Fт 3C56Bо1C367ч(C163и&BF5FщFBD5BеBB57н EB953{B74Fp�DB54BlB347aB347yB347eB347rB347}"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ ОШИБКИ │
|
# │ ОШИБКИ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
errors:
|
errors:
|
||||||
no-permission: "<gradient:#FF6B6B:#FF4757>Нет прав!</gradient>"
|
no-permission: "&#FF6B6BН&#FF5F5Fе&#FF5353т &#FF4747п&#FF3B3Bр&#FF2F2Fа&#FF2323в&#FF1717!"
|
||||||
player-not-found: "<gradient:#FF6B6B:#FF4757>Игрок не найден</gradient>"
|
player-not-found: "&#FF6B6BИ&#FF5F5Fг&#FF5353р&#FF4747о&#FF3B3Bк &#FF2F2Fн&#FF2323е &#FF1717н&#FF0B0Bа&#FF0000й&#FF0000д&#FF0000е&#FF0000н"
|
||||||
invalid-usage: "<gradient:#FFA726:#FF9800>Использование: {usage}</gradient>"
|
invalid-usage: "&#FFA726И&#FFA322с&#FF9F1Eп&#FF9B1Aо&#FF9716л&#FF9312ь&#FF8F0Eз&#FF8B0Aо&#FF8706в&#FF8302а&#FF8000н&#FF8000и&#FF8000е&#FF8000: &#FF8000{&#FF8000u&#FF8000s&#FF8000a&#FF8000g&#FF8000e&#FF8000}"
|
||||||
|
|
||||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
# │ СИСТЕМА МУТОВ │
|
# │ СИСТЕМА МУТОВ │
|
||||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
mute:
|
mute:
|
||||||
muted: "<gradient:#6BCB77:#4ECDC4>Игрок {player} замучен на {time}</gradient>"
|
muted: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347зB347аB347мB347уB347чB347еB347н B347нB347а B347{B347tB347iB347mB347eB347}"
|
||||||
unmuted: "<gradient:#6BCB77:#4ECDC4>Игрок {player} размучен</gradient>"
|
unmuted: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347рB347аB347зB347мB347уB347чB347еB347н"
|
||||||
already-muted: "<gradient:#FFA726:#FF9800>Игрок уже замучен</gradient>"
|
already-muted: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312у&#FF8F0Eж&#FF8B0Aе &#FF8706з&#FF8302а&#FF8000м&#FF8000у&#FF8000ч&#FF8000е&#FF8000н"
|
||||||
not-muted: "<gradient:#FFA726:#FF9800>Игрок не замучен</gradient>"
|
not-muted: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312н&#FF8F0Eе &#FF8B0Aз&#FF8706а&#FF8302м&#FF8000у&#FF8000ч&#FF8000е&#FF8000н"
|
||||||
you-muted: "<gradient:#FF6B6B:#FF4757>Вы замучены! Осталось: {time}</gradient>"
|
you-muted: "&#FF6B6BВ&#FF5F5Fы &#FF5353з&#FF4747а&#FF3B3Bм&#FF2F2Fу&#FF2323ч&#FF1717е&#FF0B0Bн&#FF0000ы&#FF0000! &#FF0000О&#FF0000с&#FF0000т&#FF0000а&#FF0000л&#FF0000о&#FF0000с&#FF0000ь&#FF0000: &#FF0000{&#FF0000t&#FF0000i&#FF0000m&#FF0000e&#FF0000}"
|
||||||
permanent: "<gradient:#FF6B6B:#FF4757>Вы замучены навсегда!</gradient>"
|
permanent: "&#FF6B6BВ&#FF5F5Fы &#FF5353з&#FF4747а&#FF3B3Bм&#FF2F2Fу&#FF2323ч&#FF1717е&#FF0B0Bн&#FF0000ы &#FF0000н&#FF0000а&#FF0000в&#FF0000с&#FF0000е&#FF0000г&#FF0000д&#FF0000а&#FF0000!"
|
||||||
|
|
@ -241,3 +241,18 @@ permissions:
|
||||||
lochat.nick:
|
lochat.nick:
|
||||||
description: Can use custom nicknames
|
description: Can use custom nicknames
|
||||||
default: true
|
default: true
|
||||||
|
lochat.mention.everyone:
|
||||||
|
description: Can mention @everyone
|
||||||
|
default: op
|
||||||
|
lochat.mention.here:
|
||||||
|
description: Can mention @here
|
||||||
|
default: op
|
||||||
|
lochat.mention.role:
|
||||||
|
description: Can mention @role:<permission>
|
||||||
|
default: op
|
||||||
|
lochat.bypass.urlfilter:
|
||||||
|
description: Bypass URL filter
|
||||||
|
default: op
|
||||||
|
lochat.bypass.ipfilter:
|
||||||
|
description: Bypass IP filter
|
||||||
|
default: op
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue