From 6a4c8a788630499583d0ad2df86375ca2e8fa782 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 23 Feb 2026 17:27:34 +0100 Subject: [PATCH] feat: LoHub Action system + Join system implementation --- .gitea/workflows/build.yml | 27 -- .gitea/workflows/release.yml | 49 -- README.md | 432 +++++++----------- REFACTORING_PLAN.md | 365 --------------- TODO.md | 227 +++++---- build.gradle | 55 --- build.gradle.kts | 54 +++ lohub/build.gradle.kts | 28 ++ lohub/src/main/java/com/loki/lohub/LoHub.java | 78 ++++ .../java/com/loki/lohub/actions/Action.java | 10 + .../com/loki/lohub/actions/ActionType.java | 23 + .../lohub/actions/impl/ActionbarAction.java | 28 ++ .../lohub/actions/impl/BroadcastAction.java | 28 ++ .../loki/lohub/actions/impl/CloseAction.java | 18 + .../lohub/actions/impl/CommandAction.java | 26 ++ .../lohub/actions/impl/ConsoleAction.java | 27 ++ .../loki/lohub/actions/impl/EffectAction.java | 47 ++ .../lohub/actions/impl/GamemodeAction.java | 37 ++ .../lohub/actions/impl/MessageAction.java | 27 ++ .../loki/lohub/actions/impl/SoundAction.java | 46 ++ .../loki/lohub/actions/impl/TitleAction.java | 61 +++ .../com/loki/lohub/commands/HubCommand.java | 35 ++ .../com/loki/lohub/commands/LoHubCommand.java | 38 ++ .../loki/lohub/commands/SetHubCommand.java | 42 ++ .../com/loki/lohub/config/ConfigManager.java | 56 +++ .../listeners/HubProtectionListener.java | 104 +++++ .../lohub/listeners/PlayerJoinListener.java | 208 +++++++++ .../loki/lohub/managers/ActionManager.java | 98 ++++ .../loki/lohub/managers/CooldownManager.java | 60 +++ .../loki/lohub/managers/RegionManager.java | 130 ++++++ .../com/loki/lohub/managers/SpawnManager.java | 86 ++++ .../com/loki/lohub/utils/ItemBuilder.java | 156 +++++++ .../com/loki/lohub/utils/PlaceholderUtil.java | 55 +++ .../java/com/loki/lohub/utils/TextUtil.java | 72 +++ lohub/src/main/resources/config.yml | 343 ++++++++++++++ lohub/src/main/resources/plugin.yml | 40 ++ lovelocity/build.gradle.kts | 25 + .../java/com/loki/lovelocity/LoVelocity.java | 65 +++ .../loki/lovelocity/commands/HubCommand.java | 79 ++++ settings.gradle | 1 - settings.gradle.kts | 4 + .../loki/lochat/commands/CustomCommand.java | 4 + .../com/loki/lochat/commands/NickCommand.java | 30 +- .../core/filter/AdvancedMessageFilter.java | 164 +++++++ .../lochat/core/registry/ServiceRegistry.java | 16 +- .../lochat/listener/ChatEventListener.java | 122 ++--- .../lochat/managers/CustomCommandManager.java | 4 + .../loki/lochat/managers/EmojiManager.java | 97 ++++ .../com/loki/lochat/utils/MentionHandler.java | 106 +++++ .../com/loki/lochat/utils/TextFormatter.java | 95 ++++ src/main/resources/automessages.yml | 2 +- src/main/resources/config.yml | 89 ++-- src/main/resources/gradient-messages.yml | 2 +- src/main/resources/messages.yml | 66 +-- src/main/resources/plugin.yml | 15 + 55 files changed, 3180 insertions(+), 1022 deletions(-) delete mode 100644 .gitea/workflows/build.yml delete mode 100644 .gitea/workflows/release.yml delete mode 100644 REFACTORING_PLAN.md delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 lohub/build.gradle.kts create mode 100644 lohub/src/main/java/com/loki/lohub/LoHub.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/Action.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/ActionType.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/ActionbarAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/BroadcastAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/CloseAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/CommandAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/ConsoleAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/EffectAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/GamemodeAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/MessageAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/SoundAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/actions/impl/TitleAction.java create mode 100644 lohub/src/main/java/com/loki/lohub/commands/HubCommand.java create mode 100644 lohub/src/main/java/com/loki/lohub/commands/LoHubCommand.java create mode 100644 lohub/src/main/java/com/loki/lohub/commands/SetHubCommand.java create mode 100644 lohub/src/main/java/com/loki/lohub/config/ConfigManager.java create mode 100644 lohub/src/main/java/com/loki/lohub/listeners/HubProtectionListener.java create mode 100644 lohub/src/main/java/com/loki/lohub/listeners/PlayerJoinListener.java create mode 100644 lohub/src/main/java/com/loki/lohub/managers/ActionManager.java create mode 100644 lohub/src/main/java/com/loki/lohub/managers/CooldownManager.java create mode 100644 lohub/src/main/java/com/loki/lohub/managers/RegionManager.java create mode 100644 lohub/src/main/java/com/loki/lohub/managers/SpawnManager.java create mode 100644 lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java create mode 100644 lohub/src/main/java/com/loki/lohub/utils/PlaceholderUtil.java create mode 100644 lohub/src/main/java/com/loki/lohub/utils/TextUtil.java create mode 100644 lohub/src/main/resources/config.yml create mode 100644 lohub/src/main/resources/plugin.yml create mode 100644 lovelocity/build.gradle.kts create mode 100644 lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java create mode 100644 lovelocity/src/main/java/com/loki/lovelocity/commands/HubCommand.java delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts create mode 100644 src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java create mode 100644 src/main/java/com/loki/lochat/managers/EmojiManager.java create mode 100644 src/main/java/com/loki/lochat/utils/MentionHandler.java create mode 100644 src/main/java/com/loki/lochat/utils/TextFormatter.java diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml deleted file mode 100644 index 66b9c82..0000000 --- a/.gitea/workflows/build.yml +++ /dev/null @@ -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 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml deleted file mode 100644 index c1c074b..0000000 --- a/.gitea/workflows/release.yml +++ /dev/null @@ -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" diff --git a/README.md b/README.md index 5437fd7..ec5a746 100644 --- a/README.md +++ b/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. Поместите файл в папку `plugins/` вашего сервера -3. Перезапустите сервер -4. Настройте конфигурацию в файлах `config.yml` и других +### 2. LoHub - Hub плагин (Paper/Folia) +Плагин для лобби сервера с защитой мира и интерактивными элементами. ---- +**Основные функции:** +- Spawn система с телепортом +- WorldGuard регионы (авто-создание) +- Защита мира (блоки, PvP, мобы) +- Launchpad и Double Jump +- Player Hider +- Custom Join Items +- Action система для гибкой настройки -## ✨ Основной функционал +### 3. LoVelocity - Velocity плагин +Простой плагин для прокси с командой /hub. -### 💬 Система чата -- **Глобальный чат** - `!сообщение` или `/g <сообщение>` -- **Локальный чат** - радиус 100 блоков (настраивается) -- **Личные сообщения** - `/msg`, `/reply` с поддержкой игнорирования -- **Автосообщения** - многострочные сообщения с настраиваемым порядком -- **Смайлики** - 200+ эмодзи для украшения сообщений +**Основные функции:** +- Команда /hub для телепорта на lobby +- Кастомные сообщения +- PlaceholderAPI поддержка -### 🛡️ Модерация -- **Система мутов** - `/lmute`, `/lunmute` с поддержкой времени и причин -- **Фильтр мата** - умная система с обходом символов (censor/block/warn) -- **Анти-спам** - защита от CAPS, повторов и похожих сообщений -- **Кулдауны** - настраиваемые задержки между сообщениями -- **Шпион режим** - `/chatspy` для мониторинга всех сообщений -- **Очистка чата** - `/clearchat` с настраиваемым сообщением +## 🏗️ Структура проекта -### 🌈 Градиентные ники (встроенный LoPreff) -- **Кастомные градиенты** - `/color [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 [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_mute_time% — оставшееся время мута -%lochat_mute_reason% — причина мута -%lochat_ignored_count% — количество игнорируемых -%lochat_global_enabled% — глобальный чат включен -%lochat_spy_enabled% — шпион режим включен +LoChat/ # Root проекта +│ +├── src/main/ # LoChat (основной модуль) +│ ├── java/com/loki/lochat/ +│ │ ├── api/ # API интерфейсы +│ │ │ ├── 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% — полное имя (префикс + ник) -%lochat_gradient_name% — только градиентный ник -%lochat_gradient_prefix% — только префикс -%lochat_full% — алиас для gradient_full -%lochat_name% — алиас для gradient_name -%lochat_prefix% — алиас для gradient_prefix +## 🚀 Сборка + +```bash +# Собрать все модули +./gradlew build + +# Собрать только LoChat +./gradlew :build + +# Собрать только LoHub +./gradlew :lohub:build + +# Собрать только LoVelocity +./gradlew :lovelocity:build + +# Без тестов +./gradlew build -x test ``` ---- +## 📦 Артефакты -## 📁 Конфигурационные файлы - -| Файл | Описание | -|------|----------| -| `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 (рекомендуется) -``` -Красный текст -Градиент от красного к синему -Радужный текст -Жирный -Курсив -``` - -### HEX цвета -``` -<#FF0000>Красный через HEX -HEX градиент -``` - -### Legacy коды (поддерживаются) -``` -&cКрасный -&aЗеленый -&lЖирный -&nПодчеркнутый -``` - ---- +После сборки 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` ## 🔧 Требования -### Обязательные -- **Paper 1.20.1** или новее -- **Java 21** +- **Java 21+** +- **Paper 1.20.1+** (для LoChat и LoHub) +- **Velocity 3.3.0+** (для LoVelocity) +- **Gradle 9.1+** -### Опциональные -- **PlaceholderAPI** — для плейсхолдеров -- **PlayerPoints** — для покупки градиентов за поинты -- **LuckPerms** — для интеграции с префиксами -- **SkinsRestorer** — для поддержки кастомных скинов -- **LibertyBans** — для интеграции с банами +## 📝 Зависимости ---- +### LoChat +- PlaceholderAPI (опционально) +- LuckPerms (опционально) +- PlayerPoints (опционально) -## 🏗️ Сборка +### LoHub +- WorldGuard 7.0.9+ (опционально) +- PlaceholderAPI (опционально) -```bash -./gradlew clean build -``` +### LoVelocity +- Нет зависимостей -Готовый 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 - Все права защищены -## 🤝 Поддержка +## 👥 Авторы -Если у вас возникли проблемы или вопросы: -1. Проверьте конфигурационные файлы -2. Убедитесь в совместимости версий -3. Проверьте права доступа игроков -4. Создайте issue в репозитории - ---- - -**© 2024 loki. Все права защищены.** \ No newline at end of file +Loki Development Team diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md deleted file mode 100644 index 0bcab1b..0000000 --- a/REFACTORING_PLAN.md +++ /dev/null @@ -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 getLastConversation(UUID player); - void setLastConversation(UUID player, UUID target); -} - -// core/service/PMServiceImpl.java -public class PMServiceImpl implements PMService { - private final Map 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> 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 spies = ConcurrentHashMap.newKeySet(); - // ... -} -``` - -### 2.4 MentionService (упоминания) -```java -// api/service/MentionService.java -public interface MentionService { - String processMentions(String message, Set mentioned); - void notifyMentioned(Set 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 diff --git a/TODO.md b/TODO.md index 3159c81..9846635 100644 --- a/TODO.md +++ b/TODO.md @@ -1,102 +1,167 @@ -# LoChat - Статус проекта +# 🚀 Loki Plugins - TODO -## ✅ Готово +## 📦 Проекты -### Чат -- [x] Глобальный чат (`!сообщение`, `/g`) -- [x] Локальный чат (100 блоков) -- [x] PM система (`/msg`, `/reply`) -- [x] Игнор (`/ignore`, `/unignore`) — сохраняется в файл -- [x] Упоминания @ник со звуком +### 1. LoChat - Чат плагин (Paper/Folia) +**Статус:** ✅ Готов к использованию -### Модерация -- [x] Мут система (`/mute`, `/unmute`) — сохраняется в файл -- [x] Фильтр мата (censor/block/warn) -- [x] Анти-спам (CAPS, повторы, похожие) -- [x] Кулдауны на сообщения -- [x] Чат-шпион (`/chatspy`) -- [x] Очистка чата (`/clearchat`) - -### Градиентные ники (LoPreff интеграция) -- [x] Команды `/color`, `/prefix` -- [x] Градиенты на ники и префиксы -- [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] Горячая перезагрузка +#### Функции +- ✅ Глобальный/локальный чат +- ✅ PM система с игнором +- ✅ Мут система с историей +- ✅ Фильтры (мат, спам, CAPS) +- ✅ Градиентные ники (LuckPerms интеграция) +- ✅ Смайлики (300+ эмодзи) +- ✅ PlaceholderAPI +- ✅ Markdown форматирование (**bold**, *italic*, ~~strike~~, `code`) +- ✅ Упоминания (@player, @everyone, @here, @role:permission) +- ✅ Фильтры URL/IP/повторов --- -## 📊 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_mute_time% — время мута -%lochat_mute_reason% — причина мута -%lochat_ignored_count% — кол-во игнорируемых -%lochat_global_enabled% — глобальный чат вкл -%lochat_last_pm% — последний собеседник -%lochat_spy_enabled% — шпион режим -%lochat_full% — префикс + градиентный ник (для TAB) -%lochat_name% — только градиентный ник -%lochat_prefix% — только префикс с градиентом -%lochat_lp_prefix% — LuckPerms префикс +LoChat/ # Root проекта +├── src/main/ # LoChat (основной модуль) +│ ├── java/com/loki/lochat/ +│ │ ├── api/ # API интерфейсы +│ │ ├── commands/ # Команды +│ │ ├── config/ # Конфигурация +│ │ ├── core/ # Реализации +│ │ ├── gradient/ # Градиентные ники +│ │ ├── listener/ # Слушатели событий +│ │ ├── utils/ # Утилиты +│ │ └── 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` — список игнорируемых игроков -- `mutes.yml` — активные муты -- `chatcolors.yml` — цвета чата игроков -- `gradient-data.yml` — данные градиентных ников +**LoHub - ЭТАП 2: Action система** + +Создание гибкой системы действий для настройки поведения через конфиг: + +```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" +``` --- -## 🔑 Права +## 📝 Правила разработки -| Право | Описание | -|-------|----------| -| chat.global.use | Глобальный чат | -| chat.global.color | Цвета в глобальном чате | -| chat.local.use | Локальный чат | -| chat.local.color | Цвета в локальном чате | -| 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 | Админ команды плагина | +1. **Никакого русского текста в Java файлах** - только в конфигах +2. **Цвета только в формате `&#RRGGBB`** - унифицированный стиль +3. **SOLID принципы** - чистая архитектура +4. **Классы до 100-150 строк** - читаемость кода +5. **Gradle Kotlin DSL** - современный подход +6. **Без лишних MD файлов** - только TODO.md и docs/ --- -## 🎯 Возможные улучшения +## 🔧 Технологии -- [ ] 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** diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 7fbaf9c..0000000 --- a/build.gradle +++ /dev/null @@ -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 -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..611b25a --- /dev/null +++ b/build.gradle.kts @@ -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 { + 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 +} diff --git a/lohub/build.gradle.kts b/lohub/build.gradle.kts new file mode 100644 index 0000000..fc2cfe5 --- /dev/null +++ b/lohub/build.gradle.kts @@ -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 +} diff --git a/lohub/src/main/java/com/loki/lohub/LoHub.java b/lohub/src/main/java/com/loki/lohub/LoHub.java new file mode 100644 index 0000000..58bdacd --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/LoHub.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/Action.java b/lohub/src/main/java/com/loki/lohub/actions/Action.java new file mode 100644 index 0000000..4fb6bfb --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/Action.java @@ -0,0 +1,10 @@ +package com.loki.lohub.actions; + +import org.bukkit.entity.Player; + +public interface Action { + + void execute(Player player); + + ActionType getType(); +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/ActionType.java b/lohub/src/main/java/com/loki/lohub/actions/ActionType.java new file mode 100644 index 0000000..dadd717 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/ActionType.java @@ -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; + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/ActionbarAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/ActionbarAction.java new file mode 100644 index 0000000..33419d5 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/ActionbarAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/BroadcastAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/BroadcastAction.java new file mode 100644 index 0000000..8fe63bd --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/BroadcastAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/CloseAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/CloseAction.java new file mode 100644 index 0000000..b47c55b --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/CloseAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/CommandAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/CommandAction.java new file mode 100644 index 0000000..cfb9f0d --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/CommandAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/ConsoleAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/ConsoleAction.java new file mode 100644 index 0000000..0049a37 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/ConsoleAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/EffectAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/EffectAction.java new file mode 100644 index 0000000..f4337aa --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/EffectAction.java @@ -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; + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/GamemodeAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/GamemodeAction.java new file mode 100644 index 0000000..16586ba --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/GamemodeAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/MessageAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/MessageAction.java new file mode 100644 index 0000000..15c3dd5 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/MessageAction.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/SoundAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/SoundAction.java new file mode 100644 index 0000000..655c3f3 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/SoundAction.java @@ -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; + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/actions/impl/TitleAction.java b/lohub/src/main/java/com/loki/lohub/actions/impl/TitleAction.java new file mode 100644 index 0000000..bcd51e5 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/actions/impl/TitleAction.java @@ -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; + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/commands/HubCommand.java b/lohub/src/main/java/com/loki/lohub/commands/HubCommand.java new file mode 100644 index 0000000..705b2c6 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/commands/HubCommand.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/commands/LoHubCommand.java b/lohub/src/main/java/com/loki/lohub/commands/LoHubCommand.java new file mode 100644 index 0000000..67b2046 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/commands/LoHubCommand.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/commands/SetHubCommand.java b/lohub/src/main/java/com/loki/lohub/commands/SetHubCommand.java new file mode 100644 index 0000000..ebb6f04 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/commands/SetHubCommand.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/config/ConfigManager.java b/lohub/src/main/java/com/loki/lohub/config/ConfigManager.java new file mode 100644 index 0000000..0e5e231 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/config/ConfigManager.java @@ -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); + } +} diff --git a/lohub/src/main/java/com/loki/lohub/listeners/HubProtectionListener.java b/lohub/src/main/java/com/loki/lohub/listeners/HubProtectionListener.java new file mode 100644 index 0000000..89cfc51 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/listeners/HubProtectionListener.java @@ -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); + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/listeners/PlayerJoinListener.java b/lohub/src/main/java/com/loki/lohub/listeners/PlayerJoinListener.java new file mode 100644 index 0000000..748c452 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/listeners/PlayerJoinListener.java @@ -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 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 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 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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/managers/ActionManager.java b/lohub/src/main/java/com/loki/lohub/managers/ActionManager.java new file mode 100644 index 0000000..dd44e17 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/managers/ActionManager.java @@ -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 parseActions(List actionStrings) { + List 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 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 actionStrings, boolean parse) { + if (parse) { + List 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()); + } + } + } + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/managers/CooldownManager.java b/lohub/src/main/java/com/loki/lohub/managers/CooldownManager.java new file mode 100644 index 0000000..c71ab53 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/managers/CooldownManager.java @@ -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> 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 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 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 keyCooldowns = cooldowns.get(key); + if (keyCooldowns != null) { + keyCooldowns.remove(playerId); + } + } + + public void clearAll() { + cooldowns.clear(); + } +} diff --git a/lohub/src/main/java/com/loki/lohub/managers/RegionManager.java b/lohub/src/main/java/com/loki/lohub/managers/RegionManager.java new file mode 100644 index 0000000..51b465b --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/managers/RegionManager.java @@ -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; + } + } +} diff --git a/lohub/src/main/java/com/loki/lohub/managers/SpawnManager.java b/lohub/src/main/java/com/loki/lohub/managers/SpawnManager.java new file mode 100644 index 0000000..bea7974 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/managers/SpawnManager.java @@ -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); + } +} diff --git a/lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java b/lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java new file mode 100644 index 0000000..5972bb1 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/utils/ItemBuilder.java @@ -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 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 lore) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta != null) { + List 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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/utils/PlaceholderUtil.java b/lohub/src/main/java/com/loki/lohub/utils/PlaceholderUtil.java new file mode 100644 index 0000000..2e3f9f1 --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/utils/PlaceholderUtil.java @@ -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; + } +} diff --git a/lohub/src/main/java/com/loki/lohub/utils/TextUtil.java b/lohub/src/main/java/com/loki/lohub/utils/TextUtil.java new file mode 100644 index 0000000..0ab841f --- /dev/null +++ b/lohub/src/main/java/com/loki/lohub/utils/TextUtil.java @@ -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 colorize(List 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 list) { + if (list == null || list.isEmpty()) { + return ""; + } + + return String.join("\n", list); + } +} diff --git a/lohub/src/main/resources/config.yml b/lohub/src/main/resources/config.yml new file mode 100644 index 0000000..7620319 --- /dev/null +++ b/lohub/src/main/resources/config.yml @@ -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" diff --git a/lohub/src/main/resources/plugin.yml b/lohub/src/main/resources/plugin.yml new file mode 100644 index 0000000..4628887 --- /dev/null +++ b/lohub/src/main/resources/plugin.yml @@ -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 diff --git a/lovelocity/build.gradle.kts b/lovelocity/build.gradle.kts new file mode 100644 index 0000000..5640933 --- /dev/null +++ b/lovelocity/build.gradle.kts @@ -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 +} diff --git a/lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java b/lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java new file mode 100644 index 0000000..8cd814b --- /dev/null +++ b/lovelocity/src/main/java/com/loki/lovelocity/LoVelocity.java @@ -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; + } +} diff --git a/lovelocity/src/main/java/com/loki/lovelocity/commands/HubCommand.java b/lovelocity/src/main/java/com/loki/lovelocity/commands/HubCommand.java new file mode 100644 index 0000000..6233c7f --- /dev/null +++ b/lovelocity/src/main/java/com/loki/lovelocity/commands/HubCommand.java @@ -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 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 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 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 findHubServer() { + return plugin.getServer().getAllServers().stream() + .filter(server -> isHubServer(server.getServerInfo().getName().toLowerCase())) + .findFirst(); + } + + @Override + public CompletableFuture> suggestAsync(Invocation invocation) { + return CompletableFuture.completedFuture(List.of()); + } + + @Override + public boolean hasPermission(Invocation invocation) { + return invocation.source().hasPermission("lovelocity.hub"); + } +} diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 9ba20f7..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'LoChat' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e9ef55e --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "LoNetwork" + +include("lohub") +include("lovelocity") diff --git a/src/main/java/com/loki/lochat/commands/CustomCommand.java b/src/main/java/com/loki/lochat/commands/CustomCommand.java index 6570d75..cb8ebfe 100644 --- a/src/main/java/com/loki/lochat/commands/CustomCommand.java +++ b/src/main/java/com/loki/lochat/commands/CustomCommand.java @@ -16,6 +16,10 @@ public class CustomCommand extends Command { public CustomCommand(String name, CustomCommandManager manager) { super(name); this.manager = manager; + init(name); + } + + private void init(String name) { this.setDescription("Кастомная команда LoChat"); this.setUsage("/" + name); } diff --git a/src/main/java/com/loki/lochat/commands/NickCommand.java b/src/main/java/com/loki/lochat/commands/NickCommand.java index d8cf00a..020e68f 100644 --- a/src/main/java/com/loki/lochat/commands/NickCommand.java +++ b/src/main/java/com/loki/lochat/commands/NickCommand.java @@ -14,9 +14,6 @@ import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; -/** - * Команда /nick для установки кастомного ника - */ public class NickCommand implements CommandExecutor, TabCompleter { private final LoChat plugin; private final NickService nickService; @@ -38,17 +35,14 @@ public class NickCommand implements CommandExecutor, TabCompleter { return true; } - // /nick - показать текущий ник if (args.length == 0) { return showCurrentNick(player); } - // /nick reset - сбросить ник if (args[0].equalsIgnoreCase("reset")) { return resetNick(player); } - // /nick <ник> - установить ник String nickname = String.join(" ", args); return setNick(player, nickname); } @@ -87,27 +81,23 @@ public class NickCommand implements CommandExecutor, TabCompleter { } private boolean setNick(Player player, String nickname) { - // Валидация if (!nickService.isValidNickname(nickname)) { player.sendMessage(Component.text("Невалидный ник!", NamedTextColor.RED)); player.sendMessage(Component.text("Требования:", 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("• Поддержка цветов: &#FF0000текст, текст", NamedTextColor.GRAY)); return true; } - // Проверка уникальности if (nickService.isNicknameTaken(nickname)) { var currentNick = nickService.getNickname(player.getUniqueId()); - // Если это не свой текущий ник if (currentNick.isEmpty() || !currentNick.get().equals(nickname)) { player.sendMessage(Component.text("Этот ник уже занят!", NamedTextColor.RED)); return true; } } - // Устанавливаем ник boolean success = nickService.setNickname(player.getUniqueId(), nickname); if (success) { @@ -126,8 +116,22 @@ public class NickCommand implements CommandExecutor, TabCompleter { List completions = new ArrayList<>(); if (args.length == 1) { - completions.add("reset"); - completions.add("<ник>"); + String input = args[0].toLowerCase(); + + if ("reset".startsWith(input)) { + completions.add("reset"); + } + + 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; diff --git a/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java b/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java new file mode 100644 index 0000000..5f45aae --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java @@ -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 whitelistedDomains; + private final List 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; + } + } +} diff --git a/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java b/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java index 67f02e5..5df639a 100644 --- a/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java +++ b/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java @@ -9,25 +9,29 @@ import org.bukkit.plugin.java.JavaPlugin; import java.util.HashMap; import java.util.Map; -/** - * Реестр сервисов - Dependency Injection контейнер (SOLID DIP) - */ public class ServiceRegistry { private final Map, Object> services = new HashMap<>(); private final JavaPlugin plugin; public ServiceRegistry(JavaPlugin plugin) { this.plugin = plugin; - registerServices(); + init(); } public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) { this.plugin = plugin; + initWithDeps(configManager, messageConfig); + } + + private void init() { + registerServices(); + } + + private void initWithDeps(ConfigManager configManager, MessageConfig messageConfig) { registerServicesWithDeps(configManager, messageConfig); } private void registerServices() { - // Регистрируем все сервисы (базовая версия) register(ChatService.class, new ChatServiceImpl(plugin, this)); register(MuteService.class, new MuteServiceImpl(plugin)); register(CooldownService.class, new CooldownServiceImpl()); @@ -36,14 +40,12 @@ public class ServiceRegistry { } private void registerServicesWithDeps(ConfigManager configManager, MessageConfig messageConfig) { - // Регистрируем все сервисы с зависимостями register(ChatService.class, new ChatServiceImpl(plugin, this)); register(MuteService.class, new MuteServiceImpl(plugin)); register(CooldownService.class, new CooldownServiceImpl()); register(MessageService.class, new MessageServiceImpl(plugin, this)); register(PlayerDataService.class, new PlayerDataServiceImpl(plugin)); - // Новые сервисы register(PMService.class, new PMServiceImpl()); register(IgnoreService.class, new IgnoreServiceImpl(plugin)); register(SpyService.class, new SpyServiceImpl(plugin, messageConfig)); diff --git a/src/main/java/com/loki/lochat/listener/ChatEventListener.java b/src/main/java/com/loki/lochat/listener/ChatEventListener.java index 70a3f68..1404db1 100644 --- a/src/main/java/com/loki/lochat/listener/ChatEventListener.java +++ b/src/main/java/com/loki/lochat/listener/ChatEventListener.java @@ -1,8 +1,10 @@ package com.loki.lochat.listener; 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.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.chat.ChatRenderer; 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.TextDecoration; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; -/** - * Слушатель событий чата с Paper Chat API - * Поддерживает hover tooltips, click actions, mentions - */ public class ChatEventListener implements Listener { private final JavaPlugin plugin; private final MessageService messageService; + private final AdvancedMessageFilter advancedFilter; + private final MentionHandler mentionHandler; public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) { this.plugin = plugin; this.messageService = registry.get(MessageService.class); + this.advancedFilter = new AdvancedMessageFilter(plugin.getConfig()); + this.mentionHandler = new MentionHandler(plugin); } @EventHandler(priority = EventPriority.HIGHEST) @@ -37,7 +38,6 @@ public class ChatEventListener implements Listener { Player sender = event.getPlayer(); String message = PlainTextComponentSerializer.plainText().serialize(event.message()); - // Проверяем фильтры (мут, кулдаун) boolean allowed = messageService.processMessage(sender, message); if (!allowed) { @@ -45,13 +45,20 @@ public class ChatEventListener implements Listener { 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())); } - /** - * Продвинутый рендерер чата с hover/click/mentions - */ private class EnhancedChatRenderer implements ChatRenderer { private final Player sender; private final Component originalMessage; @@ -63,47 +70,31 @@ public class ChatEventListener implements Listener { @Override 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]"); - - // Создаём компонент с hover и click Component playerComponent = createPlayerComponent(source, sourceDisplayName); - - // Обрабатываем сообщение Component processedMessage = message; - // 1. Обрабатываем URL (делаем кликабельными) + processedMessage = TextFormatter.formatMarkdown(processedMessage); processedMessage = processUrls(processedMessage); - - // 2. Обрабатываем эмодзи шорткоды (:smile: -> 😊) processedMessage = processEmojis(processedMessage); - // 3. Обрабатываем упоминания если viewer это Player if (viewer instanceof Player viewerPlayer) { - processedMessage = processMentions(processedMessage, viewerPlayer); + processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer); } - // Собираем финальное сообщение return Component.text(prefix + " ") .append(playerComponent) .append(Component.text(": ")) .append(processedMessage); } - /** - * Создаёт компонент имени игрока с hover и click - */ private Component createPlayerComponent(Player player, Component displayName) { - // Проверяем включен ли hover if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) { return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " ")); } - // Получаем формат из конфига java.util.List hoverLines = plugin.getConfig().getStringList("chat.hover.format"); if (hoverLines.isEmpty()) { - // Дефолтный формат если конфиг пустой hoverLines = java.util.Arrays.asList( "Игрок: {player}", "Мир: {world}", @@ -112,12 +103,10 @@ public class ChatEventListener implements Listener { ); } - // Строим hover text net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text(); for (int i = 0; i < hoverLines.size(); i++) { String line = hoverLines.get(i); - // Заменяем плейсхолдеры line = line.replace("{player}", player.getName()) .replace("{world}", player.getWorld().getName()) .replace("{ping}", String.valueOf(player.getPing())) @@ -125,62 +114,28 @@ public class ChatEventListener implements Listener { .replace("{health}", String.valueOf(Math.round(player.getHealth()))) .replace("{food}", String.valueOf(player.getFoodLevel())); - // Парсим MiniMessage Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line); hoverBuilder.append(lineComponent); - // Добавляем перенос строки если не последняя if (i < hoverLines.size() - 1) { hoverBuilder.append(Component.newline()); } } - // Click: открываем /msg return displayName .hoverEvent(HoverEvent.showText(hoverBuilder.build())) .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) { String plainText = PlainTextComponentSerializer.plainText().serialize(message); - - // Простой regex для URL - String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)"; + String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!35538117-c5a8-4bfe-b9f2-b0dcf9374222'()*+,;=%]+)"; java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern); java.util.regex.Matcher matcher = pattern.matcher(plainText); Component result = message; while (matcher.find()) { String url = matcher.group(1); - // Делаем URL кликабельным с hover Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED) .hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY))) .clickEvent(ClickEvent.openUrl(url)); @@ -193,35 +148,28 @@ public class ChatEventListener implements Listener { return result; } - /** - * Обрабатывает эмодзи шорткоды (:smile: -> ☺) - * Использует только MC-совместимые Unicode символы - */ private Component processEmojis(Component message) { Component result = message; - - // 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(":heart:").replacement("❤")); result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★")); - result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✓")); - result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✗")); - result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠")); + result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✔")); + result = result.replaceText(builder -> builder.matchLiteral(":cross:").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(":sun:").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(":moon:").replacement("☽")); result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄")); - result = result.replaceText(builder -> builder.matchLiteral(":lightning:").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(":fire:").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; } } diff --git a/src/main/java/com/loki/lochat/managers/CustomCommandManager.java b/src/main/java/com/loki/lochat/managers/CustomCommandManager.java index 3c580d7..4a3b48d 100644 --- a/src/main/java/com/loki/lochat/managers/CustomCommandManager.java +++ b/src/main/java/com/loki/lochat/managers/CustomCommandManager.java @@ -27,6 +27,10 @@ public class CustomCommandManager { public CustomCommandManager(LoChat plugin) { this.plugin = plugin; this.commands = new HashMap<>(); + init(); + } + + private void init() { loadCommands(); } diff --git a/src/main/java/com/loki/lochat/managers/EmojiManager.java b/src/main/java/com/loki/lochat/managers/EmojiManager.java new file mode 100644 index 0000000..f38e49e --- /dev/null +++ b/src/main/java/com/loki/lochat/managers/EmojiManager.java @@ -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 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 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 entry : emojis.entrySet()) { + result = result.replace(entry.getKey(), entry.getValue()); + } + + return result; + } + + public Map getEmojis() { + return emojis; + } + + public boolean isEnabled() { + return enabled; + } +} diff --git a/src/main/java/com/loki/lochat/utils/MentionHandler.java b/src/main/java/com/loki/lochat/utils/MentionHandler.java new file mode 100644 index 0000000..64caad3 --- /dev/null +++ b/src/main/java/com/loki/lochat/utils/MentionHandler.java @@ -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 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); + }); + } +} diff --git a/src/main/java/com/loki/lochat/utils/TextFormatter.java b/src/main/java/com/loki/lochat/utils/TextFormatter.java new file mode 100644 index 0000000..1d44c0b --- /dev/null +++ b/src/main/java/com/loki/lochat/utils/TextFormatter.java @@ -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, "" + content + ""); + } + 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, "" + content + ""); + } + } + 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, "" + content + ""); + } + 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, "" + content + ""); + } + 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(); + } +} diff --git a/src/main/resources/automessages.yml b/src/main/resources/automessages.yml index 89bbac0..d696774 100644 --- a/src/main/resources/automessages.yml +++ b/src/main/resources/automessages.yml @@ -10,7 +10,7 @@ settings: # Префикс автосообщений - prefix: "[INFO]" + prefix: "�D4FF[�D4FFI�CAF7N�C0EEF�B7E6OcCC]" # Режим отправки: sequential (по порядку), random (случайно), custom (свой порядок) mode: "sequential" diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f9e0598..1abd5ba 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -12,7 +12,7 @@ config-version: 3 # └─────────────────────────────────────────────────────────────────────────────┘ # Префикс плагина в сообщениях -prefix: "[LoChat]" +prefix: "�D4FF[�D4FFL�CAF7o�C0EEC�B7E6h�ADDDa�A3D5tcCC]" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ НАСТРОЙКИ ЧАТА │ @@ -30,9 +30,8 @@ chat: # Кулдаун между сообщениями (секунды) cooldown: 3 # Префикс глобального чата - prefix: "[G]" - # Формат сообщения: {prefix} {player} {message} - format: "{prefix} {player}: {message}" + prefix: "&#FFD700[G]" + format: "{prefix} �FF88{player}򇨣: {message}" # ═══════════════════════════════════════════════════════════════════════════ # ЛОКАЛЬНЫЙ ЧАТ @@ -45,9 +44,8 @@ chat: # Кулдаун между сообщениями (секунды) cooldown: 1 # Префикс локального чата - prefix: "[L]" - # Формат сообщения - format: "{prefix} {player}: {message}" + prefix: "WCEEB[L]" + format: "{prefix} �FF88{player}򇨣: {message}" # ═══════════════════════════════════════════════════════════════════════════ # ЛИЧНЫЕ СООБЩЕНИЯ @@ -111,9 +109,8 @@ mentions: # Тип звука sound-type: "BLOCK_NOTE_BLOCK_PLING" # Подсветка упоминания в чате - highlight: "@{player}" - # Подсветка собственного ника - self-highlight: "{player}" + highlight: "&#FFFF00@{player}" + self-highlight: "&#FFD700{player}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ АВТОСООБЩЕНИЯ │ # └─────────────────────────────────────────────────────────────────────────────┘ @@ -134,7 +131,7 @@ announcements: # Показывать title при объявлении show-title: true # Заголовок title - title-header: "ОБЪЯВЛЕНИЕ" + title-header: "&#FF6B6BОБЪЯВЛЕНИЕ" # Показывать actionbar при объявлении show-actionbar: true # Время показа title (в секундах) @@ -142,7 +139,7 @@ announcements: # Формат объявления formats: - announcement: "[ОБЪЯВЛЕНИЕ] " + announcement: "&#FF6B6B[ОБЪЯВЛЕНИЕ] " # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ОЧИСТКА ЧАТА │ @@ -153,7 +150,7 @@ clearchat: # Показывать сообщение после очистки чата enabled: true # Текст сообщения ({player} - имя администратора) - text: "Чат очищен администратором {player}" + text: "&#FF6B6BЧат очищен администратором {player}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ГОЛОВЫ ИГРОКОВ │ @@ -179,20 +176,14 @@ mute: # %operator% - модератор messages: - # Сообщение при муте (broadcast) - muted: "%player% был замучен на %duration% модератором %operator%. Причина: %reason%" - # Сообщение при размуте (broadcast) - unmuted: "%player% был размучен модератором %operator%" - # Сообщение замученному игроку - you-muted: "Вы замучены на %duration%! Причина: %reason%" - # Сообщение при перманентном муте - you-muted-permanent: "Вы замучены навсегда! Причина: %reason%" - # Сообщение при попытке написать в чат - cannot-chat: "Вы замучены! Осталось: %duration%" - cannot-chat-permanent: "Вы замучены навсегда!" - # Тихий мут (только для тех кто видит) - silent-muted: "[Тихо] %player% замучен на %duration% (%operator%). Причина: %reason%" - silent-unmuted: "[Тихо] %player% размучен (%operator%)" + muted: "&#FF6B6B%player% 󙀸был замучен на &#FF6B6B%duration% 󙀸модератором &#FF6B6B%operator%󙀸. Причина: &#FF6B6B%reason%" + unmuted: "BCB77%player% 󙀸был размучен модератором BCB77%operator%" + you-muted: "&#FF6B6BВы замучены на %duration%! Причина: %reason%" + you-muted-permanent: "&#FF6B6BВы замучены навсегда! Причина: %reason%" + cannot-chat: "&#FF6B6BВы замучены! Осталось: %duration%" + cannot-chat-permanent: "&#FF6B6BВы замучены навсегда!" + silent-muted: "󙀸[Тихо] &#FF6B6B%player% 󙀸замучен на &#FF6B6B%duration% 󙀸(%operator%). Причина: %reason%" + silent-unmuted: "󙀸[Тихо] BCB77%player% 󙀸размучен (%operator%)" # Причина по умолчанию если не указана default-reason: "Нарушение правил" @@ -212,4 +203,46 @@ mute: - "7d" - "14d" - "30d" - - "perm" \ No newline at end of file + - "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" \ No newline at end of file diff --git a/src/main/resources/gradient-messages.yml b/src/main/resources/gradient-messages.yml index 664b10a..9d23baa 100644 --- a/src/main/resources/gradient-messages.yml +++ b/src/main/resources/gradient-messages.yml @@ -10,7 +10,7 @@ # └─────────────────────────────────────────────────────────────────────────────┘ # Префикс градиентного модуля -prefix: "[Gradient]" +prefix: "⒚DB[⒚DBGE6CD9r΀AD7aℓD5dF65D3iA62D1eᶈCFnˁDCDtB5BCB]" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ КОМАНДА /color │ diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 3d29841..0624c38 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -9,23 +9,23 @@ # └─────────────────────────────────────────────────────────────────────────────┘ # Префикс плагина -prefix: "[LoChat]" +prefix: "�D4FF[�D4FFL�CAF7o�C0EEC�B7E6h�ADDDa�A3D5tcCC]" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ГЛОБАЛЬНЫЙ ЧАТ │ # └─────────────────────────────────────────────────────────────────────────────┘ global: - disabled: "Глобальный чат отключен" - toggled-on: "Глобальный чат включен" - toggled-off: "Глобальный чат отключен" + disabled: "&#FF6B6BГ&#FF5F5Fл&#FF5353о&#FF4747б&#FF3B3Bа&#FF2F2Fл&#FF2323ь&#FF1717н&#FF0B0Bы&#FF0000й &#FF0000ч&#FF0000а&#FF0000т &#FF0000о&#FF0000т&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н" + toggled-on: "BCB77Г>C973лAC76Fо3C56Bб1C367а(C163л&BF5FьFBD5BнBB57ыEB953й B74F ч�DB54BаB347т B347вB347кB347лB347юB347чB347еB347н" + toggled-off: "&#FF6B6BГ&#FF5F5Fл&#FF5353о&#FF4747б&#FF3B3Bа&#FF2F2Fл&#FF2323ь&#FF1717н&#FF0B0Bы&#FF0000й &#FF0000ч&#FF0000а&#FF0000т &#FF0000о&#FF0000т&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ЛОКАЛЬНЫЙ ЧАТ │ # └─────────────────────────────────────────────────────────────────────────────┘ local: - no-players: "Рядом нет игроков" + no-players: "&#FFA726Р&#FFA322я&#FF9F1Eд&#FF9B1Aо&#FF9716м &#FF9312н&#FF8F0Eе&#FF8B0Aт &#FF8706и&#FF8302г&#FF8000р&#FF8000о&#FF8000к&#FF8000о&#FF8000в" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ЛИЧНЫЕ СООБЩЕНИЯ │ @@ -47,36 +47,36 @@ pm: received: "[PM] {player} → Ты: {message}" # Ошибки PM - no-reply: "Некому ответить" - ignored: "Этот игрок вас игнорирует" - player-offline: "Игрок не в сети" - self: "Нельзя писать самому себе" + no-reply: "&#FF6B6BН&#FF5F5Fе&#FF5353к&#FF4747о&#FF3B3Bм&#FF2F2Fу &#FF2323о&#FF1717т&#FF0B0Bв&#FF0000е&#FF0000т&#FF0000и&#FF0000т&#FF0000ь" + 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: "&#FF6B6BИ&#FF5F5Fг&#FF5353р&#FF4747о&#FF3B3Bк &#FF2F2Fн&#FF2323е &#FF1717в &#FF0B0Bс&#FF0000е&#FF0000т&#FF0000и" + self: "&#FF6B6BН&#FF5F5Fе&#FF5353л&#FF4747ь&#FF3B3Bз&#FF2F2Fя &#FF2323п&#FF1717и&#FF0B0Bс&#FF0000а&#FF0000т&#FF0000ь &#FF0000с&#FF0000а&#FF0000м&#FF0000о&#FF0000м&#FF0000у &#FF0000с&#FF0000е&#FF0000б&#FF0000е" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ИГНОРИРОВАНИЕ │ # └─────────────────────────────────────────────────────────────────────────────┘ ignore: - added: "Игрок {player} добавлен в игнор" - removed: "Игрок {player} убран из игнора" - already-ignored: "Игрок уже в игноре" - not-ignored: "Игрок не в игноре" - self: "Нельзя игнорировать себя" + added: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347дB347оB347бB347аB347вB347лB347еB347н B347в B347иB347гB347нB347оB347р" + removed: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347уB347бB347рB347аB347н B347иB347з B347иB347гB347нB347оB347рB347а" + already-ignored: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312у&#FF8F0Eж&#FF8B0Aе &#FF8706в &#FF8302и&#FF8000г&#FF8000н&#FF8000о&#FF8000р&#FF8000е" + not-ignored: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312н&#FF8F0Eе &#FF8B0Aв &#FF8706и&#FF8302г&#FF8000н&#FF8000о&#FF8000р&#FF8000е" + self: "&#FF6B6BН&#FF5F5Fе&#FF5353л&#FF4747ь&#FF3B3Bз&#FF2F2Fя &#FF2323и&#FF1717г&#FF0B0Bн&#FF0000о&#FF0000р&#FF0000и&#FF0000р&#FF0000о&#FF0000в&#FF0000а&#FF0000т&#FF0000ь &#FF0000с&#FF0000е&#FF0000б&#FF0000я" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ОБЪЯВЛЕНИЯ │ # └─────────────────────────────────────────────────────────────────────────────┘ announce: - reloaded: "Конфигурация перезагружена" + 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: - enabled: "Шпион режим включен" - disabled: "Шпион режим выключен" + enabled: "BCB77Ш>C973пAC76Fи3C56Bо1C367н (C163р&BF5FеFBD5BжBB57иEB953м B74Fв�DB54BкB347лB347юB347чB347еB347н" + disabled: "&#FF6B6BШ&#FF5F5Fп&#FF5353и&#FF4747о&#FF3B3Bн &#FF2F2Fр&#FF2323е&#FF1717ж&#FF0B0Bи&#FF0000м &#FF0000в&#FF0000ы&#FF0000к&#FF0000л&#FF0000ю&#FF0000ч&#FF0000е&#FF0000н" format: "[SPY] {sender} → {receiver}: {message}" chat-format: "[SPY] {type} {sender}: {message}" @@ -85,49 +85,49 @@ spy: # └─────────────────────────────────────────────────────────────────────────────┘ filter: - blocked: "Сообщение заблокировано (недопустимые слова)" - warning: "Предупреждение: не используйте недопустимые слова!" + blocked: "&#FF6B6BС&#FF5F5Fо&#FF5353о&#FF4747б&#FF3B3Bщ&#FF2F2Fе&#FF2323н&#FF1717и&#FF0B0Bе &#FF0000з&#FF0000а&#FF0000б&#FF0000л&#FF0000о&#FF0000к&#FF0000и&#FF0000р&#FF0000о&#FF0000в&#FF0000а&#FF0000н&#FF0000о" + warning: "&#FFA726П&#FFA322р&#FF9F1Eе&#FF9B1Aд&#FF9716у&#FF9312п&#FF8F0Eр&#FF8B0Aе&#FF8706ж&#FF8302д&#FF8000е&#FF8000н&#FF8000и&#FF8000е" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ КУЛДАУНЫ │ # └─────────────────────────────────────────────────────────────────────────────┘ cooldown: - wait: "Подождите {time} сек перед следующим сообщением" + wait: "&#FFA726П&#FFA322о&#FF9F1Eд&#FF9B1Aо&#FF9716ж&#FF9312д&#FF8F0Eи&#FF8B0Aт&#FF8706е &#FF8302{&#FF8000t&#FF8000i&#FF8000m&#FF8000e&#FF8000}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ АНТИ-СПАМ │ # └─────────────────────────────────────────────────────────────────────────────┘ antispam: - caps: "Слишком много заглавных букв!" - repeat: "Не повторяйте символы!" - similar: "Не отправляйте одинаковые сообщения!" + caps: "&#FF6B6BС&#FF5F5Fл&#FF5353и&#FF4747ш&#FF3B3Bк&#FF2F2Fо&#FF2323м &#FF1717м&#FF0B0Bн&#FF0000о&#FF0000г&#FF0000о &#FF0000з&#FF0000а&#FF0000г&#FF0000л&#FF0000а&#FF0000в&#FF0000н&#FF0000ы&#FF0000х" + repeat: "&#FF6B6BН&#FF5F5Fе &#FF5353п&#FF4747о&#FF3B3Bв&#FF2F2Fт&#FF2323о&#FF1717р&#FF0B0Bя&#FF0000й&#FF0000т&#FF0000е &#FF0000с&#FF0000и&#FF0000м&#FF0000в&#FF0000о&#FF0000л&#FF0000ы" + 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: - cleared: "Чат очищен администратором {player}" + cleared: "BCB77Ч>C973аAC76Fт 3C56Bо1C367ч(C163и&BF5FщFBD5BеBB57н EB953{B74Fp�DB54BlB347aB347yB347eB347rB347}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ОШИБКИ │ # └─────────────────────────────────────────────────────────────────────────────┘ errors: - no-permission: "Нет прав!" - player-not-found: "Игрок не найден" - invalid-usage: "Использование: {usage}" + no-permission: "&#FF6B6BН&#FF5F5Fе&#FF5353т &#FF4747п&#FF3B3Bр&#FF2F2Fа&#FF2323в&#FF1717!" + player-not-found: "&#FF6B6BИ&#FF5F5Fг&#FF5353р&#FF4747о&#FF3B3Bк &#FF2F2Fн&#FF2323е &#FF1717н&#FF0B0Bа&#FF0000й&#FF0000д&#FF0000е&#FF0000н" + invalid-usage: "&#FFA726И&#FFA322с&#FF9F1Eп&#FF9B1Aо&#FF9716л&#FF9312ь&#FF8F0Eз&#FF8B0Aо&#FF8706в&#FF8302а&#FF8000н&#FF8000и&#FF8000е&#FF8000: &#FF8000{&#FF8000u&#FF8000s&#FF8000a&#FF8000g&#FF8000e&#FF8000}" # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ СИСТЕМА МУТОВ │ # └─────────────────────────────────────────────────────────────────────────────┘ mute: - muted: "Игрок {player} замучен на {time}" - unmuted: "Игрок {player} размучен" - already-muted: "Игрок уже замучен" - not-muted: "Игрок не замучен" - you-muted: "Вы замучены! Осталось: {time}" - permanent: "Вы замучены навсегда!" \ No newline at end of file + muted: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347зB347аB347мB347уB347чB347еB347н B347нB347а B347{B347tB347iB347mB347eB347}" + unmuted: "BCB77И>C973гAC76Fр3C56Bо1C367к (C163{&BF5FpFBD5BlBB57aEB953yB74Fe�DB54BrB347} B347рB347аB347зB347мB347уB347чB347еB347н" + already-muted: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312у&#FF8F0Eж&#FF8B0Aе &#FF8706з&#FF8302а&#FF8000м&#FF8000у&#FF8000ч&#FF8000е&#FF8000н" + not-muted: "&#FFA726И&#FFA322г&#FF9F1Eр&#FF9B1Aо&#FF9716к &#FF9312н&#FF8F0Eе &#FF8B0Aз&#FF8706а&#FF8302м&#FF8000у&#FF8000ч&#FF8000е&#FF8000н" + 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: "&#FF6B6BВ&#FF5F5Fы &#FF5353з&#FF4747а&#FF3B3Bм&#FF2F2Fу&#FF2323ч&#FF1717е&#FF0B0Bн&#FF0000ы &#FF0000н&#FF0000а&#FF0000в&#FF0000с&#FF0000е&#FF0000г&#FF0000д&#FF0000а&#FF0000!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e9b388c..584ae9c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -241,3 +241,18 @@ permissions: lochat.nick: description: Can use custom nicknames 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: + default: op + lochat.bypass.urlfilter: + description: Bypass URL filter + default: op + lochat.bypass.ipfilter: + description: Bypass IP filter + default: op