Initial commit: LoChat v1.0.0 with Gradient module
This commit is contained in:
commit
252b0a60db
86 changed files with 8922 additions and 0 deletions
40
.gitea/workflows/build.yml
Normal file
40
.gitea/workflows/build.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: Build & Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
|
- name: Grant execute permission
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew build --no-daemon
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ./gradlew test --no-daemon
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: LoChat
|
||||||
|
path: build/libs/LoChat-*.jar
|
||||||
|
retention-days: 30
|
||||||
43
.gitea/workflows/release.yml
Normal file
43
.gitea/workflows/release.yml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
|
- name: Grant execute permission
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew build --no-daemon
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: build/libs/LoChat-*.jar
|
||||||
|
generate_release_notes: true
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local config
|
||||||
|
local.properties
|
||||||
193
README.md
Normal file
193
README.md
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
# LoChat
|
||||||
|
|
||||||
|
Продвинутый чат-плагин для Minecraft Folia 1.21.8 с интегрированным модулем градиентных ников.
|
||||||
|
|
||||||
|
**Автор:** loki
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
1. Скачай `LoChat-1.0.0.jar` из `build/libs/`
|
||||||
|
2. Положи в папку `plugins/` сервера
|
||||||
|
3. Перезапусти сервер
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Функционал
|
||||||
|
|
||||||
|
### Чат
|
||||||
|
- Глобальный чат (`!сообщение` или `/g`)
|
||||||
|
- Локальный чат (радиус 100 блоков)
|
||||||
|
- Личные сообщения (`/msg`, `/reply`)
|
||||||
|
- Система игнорирования
|
||||||
|
- Автосообщения (многострочные, с порядком)
|
||||||
|
- Смайлики (200+ эмодзи)
|
||||||
|
|
||||||
|
### Модерация
|
||||||
|
- Мут система (`/mute`, `/unmute`)
|
||||||
|
- Фильтр мата (censor/block/warn)
|
||||||
|
- Анти-спам (CAPS, повторы, похожие сообщения)
|
||||||
|
- Кулдауны на сообщения
|
||||||
|
- Шпион режим (`/chatspy`)
|
||||||
|
- Очистка чата (`/clearchat`)
|
||||||
|
|
||||||
|
### Градиентные ники (интегрированный LoPreff)
|
||||||
|
- Кастомные градиенты на ник (`/color`)
|
||||||
|
- Кастомные префиксы (`/prefix`)
|
||||||
|
- Интеграция с LuckPerms
|
||||||
|
- Покупка за PlayerPoints (опционально)
|
||||||
|
- GUI подтверждения
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
### Чат
|
||||||
|
| Команда | Описание | Право |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| `!сообщение` | Глобальный чат | chat.global.use |
|
||||||
|
| `/g <msg>` | Глобальный чат | chat.global.use |
|
||||||
|
| `/globalchat toggle` | Вкл/выкл глобальный | chat.global.toggle |
|
||||||
|
| `/l <msg>` | Локальный чат | chat.local.use |
|
||||||
|
| `/msg <ник> <msg>` | Личное сообщение | chat.pm.use |
|
||||||
|
| `/reply <msg>` | Ответить | chat.pm.use |
|
||||||
|
| `/ignore <ник>` | Игнорировать | chat.pm.ignore |
|
||||||
|
| `/unignore <ник>` | Убрать из игнора | chat.pm.ignore |
|
||||||
|
| `/announce <msg>` | Объявление | chat.announce.use |
|
||||||
|
|
||||||
|
### Модерация
|
||||||
|
| Команда | Описание | Право |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| `/chatspy` | Шпион режим | chat.spy |
|
||||||
|
| `/clearchat` | Очистить чат | chat.clear |
|
||||||
|
| `/mute <ник> <время>` | Замутить | chat.mute |
|
||||||
|
| `/unmute <ник>` | Размутить | chat.mute |
|
||||||
|
|
||||||
|
### Градиенты
|
||||||
|
| Команда | Описание | Право |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| `/color <hex1> [hex2]...` | Установить градиент | gradient.color |
|
||||||
|
| `/color on/off/reset` | Управление цветом | gradient.color |
|
||||||
|
| `/color copy <ник>` | Скопировать цвет | gradient.color |
|
||||||
|
| `/prefix <текст>` | Установить префикс | gradient.prefix |
|
||||||
|
| `/prefix on/off/reset` | Управление префиксом | gradient.prefix |
|
||||||
|
| `/aprefix reload` | Перезагрузка | gradient.admin |
|
||||||
|
| `/aprefix info <ник>` | Инфо об игроке | gradient.admin |
|
||||||
|
| `/aprefix setcolor <ник> <цвета>` | Установить цвет | gradient.admin |
|
||||||
|
| `/aprefix setprefix <ник> <текст>` | Установить префикс | gradient.admin |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Права
|
||||||
|
|
||||||
|
### Чат
|
||||||
|
| Право | По умолчанию |
|
||||||
|
|-------|--------------|
|
||||||
|
| chat.global.use | true |
|
||||||
|
| chat.global.toggle | true |
|
||||||
|
| chat.global.color | op |
|
||||||
|
| chat.local.use | true |
|
||||||
|
| chat.local.color | op |
|
||||||
|
| chat.pm.use | true |
|
||||||
|
| chat.pm.ignore | true |
|
||||||
|
| chat.mention.use | true |
|
||||||
|
|
||||||
|
### Модерация
|
||||||
|
| Право | По умолчанию |
|
||||||
|
|-------|--------------|
|
||||||
|
| chat.announce.use | op |
|
||||||
|
| chat.spy | op |
|
||||||
|
| chat.clear | op |
|
||||||
|
| chat.mute | op |
|
||||||
|
| chat.bypass.cooldown | op |
|
||||||
|
| chat.bypass.filter | op |
|
||||||
|
| chat.bypass.antispam | op |
|
||||||
|
|
||||||
|
### Градиенты
|
||||||
|
| Право | По умолчанию |
|
||||||
|
|-------|--------------|
|
||||||
|
| gradient.color | true |
|
||||||
|
| gradient.prefix | true |
|
||||||
|
| gradient.admin | op |
|
||||||
|
| gradient.bypass.cooldown | op |
|
||||||
|
| gradient.bypass.cost | op |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PlaceholderAPI
|
||||||
|
|
||||||
|
```
|
||||||
|
%lochat_muted% — замучен (true/false)
|
||||||
|
%lochat_mute_time% — оставшееся время мута
|
||||||
|
%lochat_mute_reason% — причина мута
|
||||||
|
%lochat_ignored_count% — кол-во игнорируемых
|
||||||
|
%lochat_global_enabled% — глобальный чат включен
|
||||||
|
%lochat_spy_enabled% — шпион режим включен
|
||||||
|
|
||||||
|
# Градиенты
|
||||||
|
%lochat_gradient_full% — полное имя (префикс + ник)
|
||||||
|
%lochat_gradient_name% — только градиентный ник
|
||||||
|
%lochat_gradient_prefix% — только префикс
|
||||||
|
%lochat_full% — алиас для gradient_full
|
||||||
|
%lochat_name% — алиас для gradient_name
|
||||||
|
%lochat_prefix% — алиас для gradient_prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Конфигурационные файлы
|
||||||
|
|
||||||
|
- `config.yml` — основные настройки чата
|
||||||
|
- `messages.yml` — сообщения чата
|
||||||
|
- `automessages.yml` — автосообщения
|
||||||
|
- `emojis.yml` — смайлики
|
||||||
|
- `gradient-config.yml` — настройки градиентов
|
||||||
|
- `gradient-messages.yml` — сообщения градиентов
|
||||||
|
- `ignores.yml` — сохранённые игноры
|
||||||
|
- `mutes.yml` — активные муты
|
||||||
|
- `gradient-data.yml` — данные градиентов (или SQLite)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Форматирование цветов
|
||||||
|
|
||||||
|
### MiniMessage
|
||||||
|
```
|
||||||
|
<red>Красный</red>
|
||||||
|
<gradient:red:blue>Градиент</gradient>
|
||||||
|
```
|
||||||
|
|
||||||
|
### HEX цвета
|
||||||
|
```
|
||||||
|
&#FF0000Красный
|
||||||
|
BA8FFГолубой
|
||||||
|
```
|
||||||
|
|
||||||
|
### Legacy коды
|
||||||
|
```
|
||||||
|
&aЗелёный
|
||||||
|
&lЖирный
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Folia 1.21.8+ / Paper 1.21+
|
||||||
|
- Java 21
|
||||||
|
|
||||||
|
**Опционально:**
|
||||||
|
- PlaceholderAPI — для плейсхолдеров
|
||||||
|
- PlayerPoints — для покупки градиентов
|
||||||
|
- LuckPerms — для префиксов в табе
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
Jar файл: `build/libs/LoChat-1.0.0.jar`
|
||||||
66
TODO.md
Normal file
66
TODO.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# LoChat - Статус проекта
|
||||||
|
|
||||||
|
## ✅ Готово
|
||||||
|
|
||||||
|
### Чат
|
||||||
|
- [x] Глобальный чат (`!сообщение`, `/g`)
|
||||||
|
- [x] Локальный чат (100 блоков)
|
||||||
|
- [x] PM система (`/msg`, `/reply`)
|
||||||
|
- [x] Игнор (`/ignore`, `/unignore`) — сохраняется в файл
|
||||||
|
- [x] Упоминания @ник со звуком
|
||||||
|
|
||||||
|
### Модерация
|
||||||
|
- [x] Мут система (`/mute`, `/unmute`) — сохраняется в файл
|
||||||
|
- [x] Фильтр мата (censor/block/warn)
|
||||||
|
- [x] Анти-спам (CAPS, повторы, похожие)
|
||||||
|
- [x] Кулдауны на сообщения
|
||||||
|
- [x] Чат-шпион (`/chatspy`)
|
||||||
|
- [x] Очистка чата (`/clearchat`)
|
||||||
|
|
||||||
|
### Система
|
||||||
|
- [x] Автосообщения
|
||||||
|
- [x] Объявления (Title + ActionBar)
|
||||||
|
- [x] PlaceholderAPI интеграция
|
||||||
|
- [x] HEX цвета `&#RRGGBB`
|
||||||
|
- [x] MiniMessage форматирование
|
||||||
|
- [x] Folia совместимость
|
||||||
|
- [x] Горячая перезагрузка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 PlaceholderAPI
|
||||||
|
|
||||||
|
```
|
||||||
|
%lochat_muted% — замучен (true/false)
|
||||||
|
%lochat_mute_time% — время мута
|
||||||
|
%lochat_mute_reason% — причина мута
|
||||||
|
%lochat_ignored_count% — кол-во игнорируемых
|
||||||
|
%lochat_global_enabled% — глобальный чат вкл
|
||||||
|
%lochat_last_pm% — последний собеседник
|
||||||
|
%lochat_spy_enabled% — шпион режим
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Файлы данных
|
||||||
|
|
||||||
|
- `ignores.yml` — список игнорируемых игроков
|
||||||
|
- `mutes.yml` — активные муты
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 Права
|
||||||
|
|
||||||
|
| Право | Описание |
|
||||||
|
|-------|----------|
|
||||||
|
| chat.global.use | Глобальный чат |
|
||||||
|
| chat.local.use | Локальный чат |
|
||||||
|
| chat.pm.use | Личные сообщения |
|
||||||
|
| chat.pm.ignore | Игнор игроков |
|
||||||
|
| chat.announce.use | Объявления |
|
||||||
|
| chat.spy | Шпион режим |
|
||||||
|
| chat.clear | Очистка чата |
|
||||||
|
| chat.mute | Мут игроков |
|
||||||
|
| chat.bypass.cooldown | Обход кулдауна |
|
||||||
|
| chat.bypass.filter | Обход фильтра |
|
||||||
|
| chat.bypass.antispam | Обход анти-спама |
|
||||||
44
build.gradle
Normal file
44
build.gradle
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.loki'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
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/' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'dev.folia:folia-api:1.21.4-R0.1-SNAPSHOT'
|
||||||
|
compileOnly 'me.clip:placeholderapi:2.11.6'
|
||||||
|
compileOnly 'org.black_ixx:playerpoints:3.2.7'
|
||||||
|
compileOnly 'net.luckperms:api:5.4'
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
archiveFileName = 'LoChat-1.0.0.jar'
|
||||||
|
from {
|
||||||
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
}
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
248
gradlew
vendored
Normal file
248
gradlew
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
93
gradlew.bat
vendored
Normal file
93
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
rootProject.name = 'LoChat'
|
||||||
179
src/main/java/com/loki/lochat/LoChat.java
Normal file
179
src/main/java/com/loki/lochat/LoChat.java
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
package com.loki.lochat;
|
||||||
|
|
||||||
|
import com.loki.lochat.commands.*;
|
||||||
|
import com.loki.lochat.config.ConfigManager;
|
||||||
|
import com.loki.lochat.config.MessageConfig;
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.integrations.PlaceholderAPIHook;
|
||||||
|
import com.loki.lochat.listeners.ChatListener;
|
||||||
|
import com.loki.lochat.managers.*;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public final class LoChat extends JavaPlugin {
|
||||||
|
|
||||||
|
private static LoChat instance;
|
||||||
|
|
||||||
|
private ConfigManager configManager;
|
||||||
|
private MessageConfig messageConfig;
|
||||||
|
private ChatManager chatManager;
|
||||||
|
private PMManager pmManager;
|
||||||
|
private IgnoreManager ignoreManager;
|
||||||
|
private AutoMessageManager autoMessageManager;
|
||||||
|
private FilterManager filterManager;
|
||||||
|
private AntiSpamManager antiSpamManager;
|
||||||
|
private CooldownManager cooldownManager;
|
||||||
|
private MuteManager muteManager;
|
||||||
|
private MentionManager mentionManager;
|
||||||
|
private SpyManager spyManager;
|
||||||
|
private EmojiManager emojiManager;
|
||||||
|
private GradientModule gradientModule;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
// Инициализация конфигов
|
||||||
|
configManager = new ConfigManager(this);
|
||||||
|
messageConfig = new MessageConfig(this);
|
||||||
|
|
||||||
|
// Инициализация менеджеров
|
||||||
|
emojiManager = new EmojiManager(this);
|
||||||
|
ignoreManager = new IgnoreManager(this);
|
||||||
|
pmManager = new PMManager();
|
||||||
|
filterManager = new FilterManager(this);
|
||||||
|
antiSpamManager = new AntiSpamManager(this);
|
||||||
|
cooldownManager = new CooldownManager();
|
||||||
|
muteManager = new MuteManager(this);
|
||||||
|
mentionManager = new MentionManager(this);
|
||||||
|
spyManager = new SpyManager(this);
|
||||||
|
chatManager = new ChatManager(this);
|
||||||
|
autoMessageManager = new AutoMessageManager(this);
|
||||||
|
|
||||||
|
// Инициализация модуля градиентов (интегрированный LoPreff)
|
||||||
|
gradientModule = new GradientModule(this);
|
||||||
|
if (gradientModule.init()) {
|
||||||
|
getLogger().info("Gradient модуль (LoPreff) загружен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регистрация команд
|
||||||
|
registerCommands();
|
||||||
|
|
||||||
|
// Регистрация листенеров
|
||||||
|
getServer().getPluginManager().registerEvents(new ChatListener(this), this);
|
||||||
|
|
||||||
|
// Запуск автосообщений
|
||||||
|
autoMessageManager.start();
|
||||||
|
|
||||||
|
// PlaceholderAPI
|
||||||
|
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||||
|
new PlaceholderAPIHook(this).register();
|
||||||
|
getLogger().info("PlaceholderAPI подключен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogger().info("LoChat успешно запущен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
if (autoMessageManager != null) {
|
||||||
|
autoMessageManager.stop();
|
||||||
|
}
|
||||||
|
if (ignoreManager != null) {
|
||||||
|
ignoreManager.save();
|
||||||
|
}
|
||||||
|
if (muteManager != null) {
|
||||||
|
muteManager.save();
|
||||||
|
}
|
||||||
|
if (gradientModule != null) {
|
||||||
|
gradientModule.shutdown();
|
||||||
|
}
|
||||||
|
getLogger().info("LoChat отключен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
getCommand("g").setExecutor(new GlobalChatCommand(this));
|
||||||
|
getCommand("globalchat").setExecutor(new GlobalChatCommand(this));
|
||||||
|
getCommand("l").setExecutor(new LocalChatCommand(this));
|
||||||
|
getCommand("msg").setExecutor(new MsgCommand(this));
|
||||||
|
getCommand("reply").setExecutor(new ReplyCommand(this));
|
||||||
|
getCommand("ignore").setExecutor(new IgnoreCommand(this));
|
||||||
|
getCommand("unignore").setExecutor(new UnignoreCommand(this));
|
||||||
|
getCommand("announce").setExecutor(new AnnounceCommand(this));
|
||||||
|
getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
|
||||||
|
getCommand("clearchat").setExecutor(new ClearChatCommand(this));
|
||||||
|
getCommand("mute").setExecutor(new MuteCommand(this));
|
||||||
|
getCommand("unmute").setExecutor(new UnmuteCommand(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
configManager.reload();
|
||||||
|
messageConfig.reload();
|
||||||
|
emojiManager.reload();
|
||||||
|
autoMessageManager.stop();
|
||||||
|
autoMessageManager.start();
|
||||||
|
if (gradientModule != null) {
|
||||||
|
gradientModule.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoChat getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigManager getConfigManager() {
|
||||||
|
return configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageConfig getMessageConfig() {
|
||||||
|
return messageConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatManager getChatManager() {
|
||||||
|
return chatManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PMManager getPmManager() {
|
||||||
|
return pmManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IgnoreManager getIgnoreManager() {
|
||||||
|
return ignoreManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoMessageManager getAutoMessageManager() {
|
||||||
|
return autoMessageManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterManager getFilterManager() {
|
||||||
|
return filterManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AntiSpamManager getAntiSpamManager() {
|
||||||
|
return antiSpamManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CooldownManager getCooldownManager() {
|
||||||
|
return cooldownManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MuteManager getMuteManager() {
|
||||||
|
return muteManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MentionManager getMentionManager() {
|
||||||
|
return mentionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpyManager getSpyManager() {
|
||||||
|
return spyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmojiManager getEmojiManager() {
|
||||||
|
return emojiManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GradientModule getGradientModule() {
|
||||||
|
return gradientModule;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/java/com/loki/lochat/commands/AnnounceCommand.java
Normal file
61
src/main/java/com/loki/lochat/commands/AnnounceCommand.java
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class AnnounceCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public AnnounceCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/announce <сообщение>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /announce reload
|
||||||
|
if (args[0].equalsIgnoreCase("reload") && sender.hasPermission("chat.announce.reload")) {
|
||||||
|
plugin.reload();
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("announce.reloaded")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.join(" ", args);
|
||||||
|
String format = plugin.getConfigManager().getAnnouncementFormat();
|
||||||
|
Component formatted = ChatFormatter.formatAnnouncement(format, message);
|
||||||
|
|
||||||
|
// Отправка в чат
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
player.sendMessage(formatted);
|
||||||
|
|
||||||
|
// ActionBar
|
||||||
|
player.sendActionBar(formatted);
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Title title = Title.title(
|
||||||
|
ChatFormatter.parse("<gold>ОБЪЯВЛЕНИЕ</gold>"),
|
||||||
|
ChatFormatter.parse(message),
|
||||||
|
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))
|
||||||
|
);
|
||||||
|
player.showTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("[ANNOUNCE] " + sender.getName() + ": " + ChatFormatter.stripTags(message));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/java/com/loki/lochat/commands/ChatSpyCommand.java
Normal file
32
src/main/java/com/loki/lochat/commands/ChatSpyCommand.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class ChatSpyCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public ChatSpyCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enabled = plugin.getSpyManager().toggleSpy(player.getUniqueId());
|
||||||
|
String msgKey = enabled ? "spy.enabled" : "spy.disabled";
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/java/com/loki/lochat/commands/ClearChatCommand.java
Normal file
36
src/main/java/com/loki/lochat/commands/ClearChatCommand.java
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class ClearChatCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public ClearChatCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
// Отправляем 100 пустых строк всем игрокам
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
player.sendMessage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомление
|
||||||
|
player.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("clearchat.cleared", "{player}", sender.getName())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class GlobalChatCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public GlobalChatCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /globalchat toggle
|
||||||
|
if (command.getName().equalsIgnoreCase("globalchat")) {
|
||||||
|
if (args.length > 0 && args[0].equalsIgnoreCase("toggle")) {
|
||||||
|
boolean enabled = plugin.getChatManager().toggleGlobalChat(player.getUniqueId());
|
||||||
|
String msgKey = enabled ? "global.toggled-on" : "global.toggled-off";
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/globalchat toggle")));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /g <message>
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/g <сообщение>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.join(" ", args);
|
||||||
|
plugin.getChatManager().sendGlobalMessage(player, message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/java/com/loki/lochat/commands/IgnoreCommand.java
Normal file
55
src/main/java/com/loki/lochat/commands/IgnoreCommand.java
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class IgnoreCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public IgnoreCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/ignore <ник>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.equals(player)) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.self")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.already-ignored")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getIgnoreManager().addIgnore(player.getUniqueId(), target.getUniqueId());
|
||||||
|
player.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("ignore.added", "{player}", target.getName())
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main/java/com/loki/lochat/commands/LocalChatCommand.java
Normal file
35
src/main/java/com/loki/lochat/commands/LocalChatCommand.java
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class LocalChatCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public LocalChatCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/l <сообщение>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.join(" ", args);
|
||||||
|
plugin.getChatManager().sendLocalMessage(player, message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main/java/com/loki/lochat/commands/MsgCommand.java
Normal file
97
src/main/java/com/loki/lochat/commands/MsgCommand.java
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class MsgCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public MsgCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getConfigManager().isPmEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/msg <ник> <сообщение>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.equals(player)) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.self")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка игнора
|
||||||
|
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собираем сообщение
|
||||||
|
StringBuilder messageBuilder = new StringBuilder();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
if (i > 1) messageBuilder.append(" ");
|
||||||
|
messageBuilder.append(args[i]);
|
||||||
|
}
|
||||||
|
String message = messageBuilder.toString();
|
||||||
|
|
||||||
|
// Отправляем сообщения
|
||||||
|
player.sendMessage(ChatFormatter.formatPmSent(
|
||||||
|
plugin.getMessageConfig().get("pm.sent"),
|
||||||
|
target.getName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
|
||||||
|
target.sendMessage(ChatFormatter.formatPmReceived(
|
||||||
|
plugin.getMessageConfig().get("pm.received"),
|
||||||
|
player.getName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
|
||||||
|
// Звук для получателя
|
||||||
|
if (plugin.getConfigManager().isPmSoundEnabled()) {
|
||||||
|
try {
|
||||||
|
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
|
||||||
|
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шпион
|
||||||
|
plugin.getSpyManager().broadcastPM(player, target, message);
|
||||||
|
|
||||||
|
// Сохраняем последнего собеседника
|
||||||
|
plugin.getPmManager().setLastConversation(player.getUniqueId(), target.getUniqueId());
|
||||||
|
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
|
||||||
|
|
||||||
|
// Логирование
|
||||||
|
if (plugin.getConfigManager().isPmLogEnabled()) {
|
||||||
|
plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main/java/com/loki/lochat/commands/MuteCommand.java
Normal file
56
src/main/java/com/loki/lochat/commands/MuteCommand.java
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.managers.MuteManager;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class MuteCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public MuteCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/mute <ник> <время> [причина]")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
if (target == null) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.getMuteManager().isAlreadyMuted(target.getUniqueId())) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.already-muted")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
long duration = MuteManager.parseTime(args[1]);
|
||||||
|
String reason = args.length > 2 ? String.join(" ", java.util.Arrays.copyOfRange(args, 2, args.length)) : null;
|
||||||
|
|
||||||
|
plugin.getMuteManager().mute(target.getUniqueId(), duration, reason);
|
||||||
|
|
||||||
|
String timeStr = duration <= 0 ? "навсегда" : args[1];
|
||||||
|
sender.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("mute.muted", "{player}", target.getName(), "{time}", timeStr)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Уведомляем игрока
|
||||||
|
target.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("mute.you-muted", "{time}", timeStr)
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/main/java/com/loki/lochat/commands/ReplyCommand.java
Normal file
93
src/main/java/com/loki/lochat/commands/ReplyCommand.java
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ReplyCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public ReplyCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getConfigManager().isPmEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/reply <сообщение>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID lastUuid = plugin.getPmManager().getLastConversation(player.getUniqueId());
|
||||||
|
if (lastUuid == null) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.no-reply")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(lastUuid);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка игнора
|
||||||
|
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.join(" ", args);
|
||||||
|
|
||||||
|
// Отправляем сообщения
|
||||||
|
player.sendMessage(ChatFormatter.formatPmSent(
|
||||||
|
plugin.getMessageConfig().get("pm.sent"),
|
||||||
|
target.getName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
|
||||||
|
target.sendMessage(ChatFormatter.formatPmReceived(
|
||||||
|
plugin.getMessageConfig().get("pm.received"),
|
||||||
|
player.getName(),
|
||||||
|
message
|
||||||
|
));
|
||||||
|
|
||||||
|
// Звук для получателя
|
||||||
|
if (plugin.getConfigManager().isPmSoundEnabled()) {
|
||||||
|
try {
|
||||||
|
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
|
||||||
|
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шпион
|
||||||
|
plugin.getSpyManager().broadcastPM(player, target, message);
|
||||||
|
|
||||||
|
// Обновляем последнего собеседника
|
||||||
|
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
|
||||||
|
|
||||||
|
// Логирование
|
||||||
|
if (plugin.getConfigManager().isPmLogEnabled()) {
|
||||||
|
plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main/java/com/loki/lochat/commands/UnignoreCommand.java
Normal file
50
src/main/java/com/loki/lochat/commands/UnignoreCommand.java
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class UnignoreCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public UnignoreCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage("Только для игроков!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/unignore <ник>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.not-ignored")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getIgnoreManager().removeIgnore(player.getUniqueId(), target.getUniqueId());
|
||||||
|
player.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("ignore.removed", "{player}", target.getName())
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/com/loki/lochat/commands/UnmuteCommand.java
Normal file
45
src/main/java/com/loki/lochat/commands/UnmuteCommand.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.loki.lochat.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class UnmuteCommand implements CommandExecutor {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public UnmuteCommand(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getInvalidUsage("/unmute <ник>")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[0]);
|
||||||
|
if (target == null) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getMuteManager().isMuted(target.getUniqueId())) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.not-muted")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getMuteManager().unmute(target.getUniqueId());
|
||||||
|
sender.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("mute.unmuted", "{player}", target.getName())
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
168
src/main/java/com/loki/lochat/config/ConfigManager.java
Normal file
168
src/main/java/com/loki/lochat/config/ConfigManager.java
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
package com.loki.lochat.config;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private FileConfiguration config;
|
||||||
|
|
||||||
|
public ConfigManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
plugin.saveDefaultConfig();
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global chat
|
||||||
|
public boolean isGlobalEnabled() {
|
||||||
|
return config.getBoolean("chat.global.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGlobalPrefix() {
|
||||||
|
return config.getString("chat.global.prefix", "[G]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGlobalFormat() {
|
||||||
|
return config.getString("chat.global.format", "<prefix><player>: <message>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGlobalSymbol() {
|
||||||
|
return config.getString("chat.global.symbol", "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGlobalCooldown() {
|
||||||
|
return config.getInt("chat.global.cooldown", 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local chat
|
||||||
|
public boolean isLocalEnabled() {
|
||||||
|
return config.getBoolean("chat.local.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalRadius() {
|
||||||
|
return config.getInt("chat.local.radius", 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalPrefix() {
|
||||||
|
return config.getString("chat.local.prefix", "[L]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalFormat() {
|
||||||
|
return config.getString("chat.local.format", "<player>: <message>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalCooldown() {
|
||||||
|
return config.getInt("chat.local.cooldown", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PM
|
||||||
|
public boolean isPmEnabled() {
|
||||||
|
return config.getBoolean("chat.pm.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPmLogEnabled() {
|
||||||
|
return config.getBoolean("chat.pm.log", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPmSoundEnabled() {
|
||||||
|
return config.getBoolean("chat.pm.sound", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPmSoundType() {
|
||||||
|
return config.getString("chat.pm.sound-type", "ENTITY_EXPERIENCE_ORB_PICKUP");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions
|
||||||
|
public boolean isMentionsEnabled() {
|
||||||
|
return config.getBoolean("mentions.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMentionSoundEnabled() {
|
||||||
|
return config.getBoolean("mentions.sound", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMentionSoundType() {
|
||||||
|
return config.getString("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMentionHighlight() {
|
||||||
|
return config.getString("mentions.highlight", "<yellow>@{player}</yellow>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelfMentionHighlight() {
|
||||||
|
return config.getString("mentions.self-highlight", "<gold><bold>{player}</bold></gold>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter
|
||||||
|
public boolean isFilterEnabled() {
|
||||||
|
return config.getBoolean("filter.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFilterWords() {
|
||||||
|
return config.getStringList("filter.words");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilterAction() {
|
||||||
|
return config.getString("filter.action", "censor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilterReplacement() {
|
||||||
|
return config.getString("filter.replacement", "***");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anti-spam
|
||||||
|
public boolean isAntiSpamEnabled() {
|
||||||
|
return config.getBoolean("antispam.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxCapsPercent() {
|
||||||
|
return config.getInt("antispam.max-caps-percent", 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxRepeatChars() {
|
||||||
|
return config.getInt("antispam.max-repeat-chars", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSimilarMessageDelay() {
|
||||||
|
return config.getInt("antispam.similar-message-delay", 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto messages
|
||||||
|
public boolean isAutoMessagesEnabled() {
|
||||||
|
return config.getBoolean("automessages.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAutoMessagesInterval() {
|
||||||
|
return config.getInt("automessages.interval", 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoMessagesRandom() {
|
||||||
|
return config.getBoolean("automessages.random", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats
|
||||||
|
public String getAnnouncementFormat() {
|
||||||
|
return config.getString("formats.announcement", "<gold>[ОБЪЯВЛЕНИЕ]</gold> <message>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoPreff integration
|
||||||
|
public boolean isLopreffEnabled() {
|
||||||
|
return config.getBoolean("lopreff.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useGradientName() {
|
||||||
|
return config.getBoolean("lopreff.use-gradient-name", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean useCustomPrefix() {
|
||||||
|
return config.getBoolean("lopreff.use-custom-prefix", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/main/java/com/loki/lochat/config/MessageConfig.java
Normal file
60
src/main/java/com/loki/lochat/config/MessageConfig.java
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.loki.lochat.config;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MessageConfig {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private FileConfiguration messages;
|
||||||
|
private File messagesFile;
|
||||||
|
|
||||||
|
public MessageConfig(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessages() {
|
||||||
|
messagesFile = new File(plugin.getDataFolder(), "messages.yml");
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
plugin.saveResource("messages.yml", false);
|
||||||
|
}
|
||||||
|
messages = YamlConfiguration.loadConfiguration(messagesFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String path) {
|
||||||
|
return messages.getString(path, "<red>Message not found: " + path + "</red>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String path, String... replacements) {
|
||||||
|
String message = get(path);
|
||||||
|
for (int i = 0; i < replacements.length - 1; i += 2) {
|
||||||
|
message = message.replace(replacements[i], replacements[i + 1]);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcuts
|
||||||
|
public String getPrefix() {
|
||||||
|
return get("prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNoPermission() {
|
||||||
|
return get("errors.no-permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlayerNotFound() {
|
||||||
|
return get("errors.player-not-found");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInvalidUsage(String usage) {
|
||||||
|
return get("errors.invalid-usage", "{usage}", usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/main/java/com/loki/lochat/gradient/GradientModule.java
Normal file
165
src/main/java/com/loki/lochat/gradient/GradientModule.java
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
package com.loki.lochat.gradient;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.commands.GradientAdminCommand;
|
||||||
|
import com.loki.lochat.gradient.commands.GradientColorCommand;
|
||||||
|
import com.loki.lochat.gradient.commands.GradientPrefixCommand;
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import com.loki.lochat.gradient.config.GradientMessages;
|
||||||
|
import com.loki.lochat.gradient.data.GradientDataManager;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.hooks.GradientLuckPermsHook;
|
||||||
|
import com.loki.lochat.gradient.listeners.GradientGUIListener;
|
||||||
|
import com.loki.lochat.gradient.listeners.GradientPlayerListener;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.GradientUtil;
|
||||||
|
import org.black_ixx.playerpoints.PlayerPoints;
|
||||||
|
import org.black_ixx.playerpoints.PlayerPointsAPI;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модуль градиентных ников (интегрированный LoPreff)
|
||||||
|
*/
|
||||||
|
public class GradientModule {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private GradientConfig config;
|
||||||
|
private GradientMessages messages;
|
||||||
|
private GradientDataManager dataManager;
|
||||||
|
private GradientLuckPermsHook luckPermsHook;
|
||||||
|
private PlayerPointsAPI playerPointsAPI;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public GradientModule(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация модуля
|
||||||
|
* @return true если модуль успешно инициализирован
|
||||||
|
*/
|
||||||
|
public boolean init() {
|
||||||
|
// Загружаем конфиги
|
||||||
|
config = new GradientConfig(plugin);
|
||||||
|
messages = new GradientMessages(plugin);
|
||||||
|
|
||||||
|
if (!config.isEnabled()) {
|
||||||
|
plugin.getLogger().info("Gradient модуль отключен в конфиге.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем PlayerPoints (опционально теперь)
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("PlayerPoints")) {
|
||||||
|
playerPointsAPI = PlayerPoints.getInstance().getAPI();
|
||||||
|
plugin.getLogger().info("Gradient: PlayerPoints подключен!");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("Gradient: PlayerPoints не найден. Покупки будут бесплатными.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация хранилища
|
||||||
|
dataManager = new GradientDataManager(plugin, config);
|
||||||
|
|
||||||
|
// Подключение LuckPerms
|
||||||
|
luckPermsHook = new GradientLuckPermsHook(plugin);
|
||||||
|
|
||||||
|
// Регистрация команд
|
||||||
|
registerCommands();
|
||||||
|
|
||||||
|
// Регистрация слушателей
|
||||||
|
Bukkit.getPluginManager().registerEvents(new GradientPlayerListener(this), plugin);
|
||||||
|
Bukkit.getPluginManager().registerEvents(new GradientGUIListener(this), plugin);
|
||||||
|
|
||||||
|
enabled = true;
|
||||||
|
plugin.getLogger().info("Gradient модуль успешно загружен!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
GradientColorCommand colorCommand = new GradientColorCommand(this);
|
||||||
|
if (plugin.getCommand("color") != null) {
|
||||||
|
plugin.getCommand("color").setExecutor(colorCommand);
|
||||||
|
plugin.getCommand("color").setTabCompleter(colorCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPrefixCommand prefixCommand = new GradientPrefixCommand(this);
|
||||||
|
if (plugin.getCommand("prefix") != null) {
|
||||||
|
plugin.getCommand("prefix").setExecutor(prefixCommand);
|
||||||
|
plugin.getCommand("prefix").setTabCompleter(prefixCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientAdminCommand adminCommand = new GradientAdminCommand(this);
|
||||||
|
if (plugin.getCommand("aprefix") != null) {
|
||||||
|
plugin.getCommand("aprefix").setExecutor(adminCommand);
|
||||||
|
plugin.getCommand("aprefix").setTabCompleter(adminCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выключение модуля
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
if (dataManager != null) {
|
||||||
|
dataManager.saveAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Перезагрузка модуля
|
||||||
|
*/
|
||||||
|
public void reload() {
|
||||||
|
config.reload();
|
||||||
|
messages.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает отформатированное имя игрока с градиентом
|
||||||
|
*/
|
||||||
|
public String getFormattedName(Player player) {
|
||||||
|
if (!enabled) return player.getName();
|
||||||
|
return DisplayNameUtil.getFullDisplayName(this, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает только градиентный ник (без префикса)
|
||||||
|
*/
|
||||||
|
public String getGradientNick(Player player) {
|
||||||
|
if (!enabled) return player.getName();
|
||||||
|
|
||||||
|
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
|
||||||
|
if (!data.hasColors() || !data.isColorEnabled()) {
|
||||||
|
return player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GradientUtil.applyGradient(player.getName(), data.getColors(), config.isUseLegacyRgbFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает только префикс игрока
|
||||||
|
*/
|
||||||
|
public String getPrefix(Player player) {
|
||||||
|
if (!enabled) return "";
|
||||||
|
|
||||||
|
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
|
||||||
|
if (!data.hasPrefix() || !data.isPrefixEnabled()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String prefix = config.getPrefixFormat().replace("{prefix}", data.getPrefix());
|
||||||
|
if (data.hasColors() && data.isColorEnabled() && config.isGradientOnPrefix()) {
|
||||||
|
return GradientUtil.applyGradient(prefix, data.getColors(), config.isUseLegacyRgbFormat());
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Геттеры
|
||||||
|
public JavaPlugin getPlugin() { return plugin; }
|
||||||
|
public GradientConfig getConfig() { return config; }
|
||||||
|
public GradientMessages getMessages() { return messages; }
|
||||||
|
public GradientDataManager getDataManager() { return dataManager; }
|
||||||
|
public GradientLuckPermsHook getLuckPermsHook() { return luckPermsHook; }
|
||||||
|
public PlayerPointsAPI getPlayerPointsAPI() { return playerPointsAPI; }
|
||||||
|
public boolean hasPlayerPoints() { return playerPointsAPI != null; }
|
||||||
|
public boolean isEnabled() { return enabled; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
package com.loki.lochat.gradient.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import com.loki.lochat.gradient.config.GradientMessages;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
|
import com.loki.lochat.gradient.util.GradientUtil;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Админ команда /aprefix для управления градиентами
|
||||||
|
*/
|
||||||
|
public class GradientAdminCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
private static final List<String> PRESET_COLORS = Arrays.asList(
|
||||||
|
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"
|
||||||
|
);
|
||||||
|
|
||||||
|
public GradientAdminCommand(GradientModule module) {
|
||||||
|
this.module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
|
||||||
|
if (!sender.hasPermission("gradient.admin")) {
|
||||||
|
sender.sendMessage(msg.get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendHelp(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
|
||||||
|
switch (subCommand) {
|
||||||
|
case "reload" -> {
|
||||||
|
module.reload();
|
||||||
|
sender.sendMessage(msg.get("reload-success"));
|
||||||
|
}
|
||||||
|
case "info" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix info <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleInfo(sender, args[1], msg);
|
||||||
|
}
|
||||||
|
case "setcolor" -> {
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleSetColor(sender, args, msg);
|
||||||
|
}
|
||||||
|
case "setprefix" -> {
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleSetPrefix(sender, args, msg);
|
||||||
|
}
|
||||||
|
case "coloron" -> handleColorToggle(sender, args, true, msg);
|
||||||
|
case "coloroff" -> handleColorToggle(sender, args, false, msg);
|
||||||
|
case "prefixon" -> handlePrefixToggle(sender, args, true, msg);
|
||||||
|
case "prefixoff" -> handlePrefixToggle(sender, args, false, msg);
|
||||||
|
case "resetcolor" -> handleResetColor(sender, args, msg);
|
||||||
|
case "resetprefix" -> handleResetPrefix(sender, args, msg);
|
||||||
|
default -> sendHelp(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendHelp(CommandSender sender) {
|
||||||
|
sender.sendMessage("§e=== LoChat Gradient Admin ===");
|
||||||
|
sender.sendMessage("§e/aprefix reload §7— перезагрузить конфиг");
|
||||||
|
sender.sendMessage("§e/aprefix info <игрок> §7— информация об игроке");
|
||||||
|
sender.sendMessage("§e/aprefix setcolor <игрок> <цвета> §7— установить цвет");
|
||||||
|
sender.sendMessage("§e/aprefix setprefix <игрок> <префикс> §7— установить префикс");
|
||||||
|
sender.sendMessage("§e/aprefix coloron/coloroff <игрок> §7— вкл/выкл цвет");
|
||||||
|
sender.sendMessage("§e/aprefix prefixon/prefixoff <игрок> §7— вкл/выкл префикс");
|
||||||
|
sender.sendMessage("§e/aprefix resetcolor <игрок> §7— сбросить цвет");
|
||||||
|
sender.sendMessage("§e/aprefix resetprefix <игрок> §7— сбросить префикс");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
|
||||||
|
sender.sendMessage(msg.get("info-header", "player", playerName));
|
||||||
|
sender.sendMessage(msg.get("info-prefix", "prefix", data.hasPrefix() ? data.getPrefix() : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-prefix-enabled", "enabled", data.isPrefixEnabled() ? "да" : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-colors", "colors", data.hasColors() ? String.join(", ", data.getColors()) : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-color-enabled", "enabled", data.isColorEnabled() ? "да" : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-prefix-purchased", "purchased", data.isPrefixPurchased() ? "да" : "нет"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>();
|
||||||
|
for (int i = 2; i < args.length; i++) {
|
||||||
|
String color = args[i].startsWith("#") ? args[i] : "#" + args[i];
|
||||||
|
if (!GradientUtil.isValidHex(color)) {
|
||||||
|
sender.sendMessage(msg.get("invalid-color", "color", args[i]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colors.add(color.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
if (colors.size() > cfg.getMaxColors()) {
|
||||||
|
sender.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColors(colors);
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder prefixBuilder = new StringBuilder();
|
||||||
|
for (int i = 2; i < args.length; i++) {
|
||||||
|
if (i > 2) prefixBuilder.append(" ");
|
||||||
|
prefixBuilder.append(args[i]);
|
||||||
|
}
|
||||||
|
String prefix = prefixBuilder.toString();
|
||||||
|
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
if (prefix.length() > cfg.getMaxPrefixLength()) {
|
||||||
|
sender.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefix(prefix);
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
data.setPrefixPurchased(true);
|
||||||
|
|
||||||
|
module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data));
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColorEnabled(enable);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefixEnabled(enable);
|
||||||
|
|
||||||
|
if (enable && data.hasPrefix()) {
|
||||||
|
module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data));
|
||||||
|
} else {
|
||||||
|
module.getLuckPermsHook().removePrefix(target.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColors(new ArrayList<>());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aсброшен");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefix(null);
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
|
||||||
|
module.getLuckPermsHook().removePrefix(target.getUniqueId());
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aсброшен");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (!sender.hasPermission("gradient.admin")) return new ArrayList<>();
|
||||||
|
|
||||||
|
if (args.length == 1) {
|
||||||
|
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff",
|
||||||
|
"prefixon", "prefixoff", "resetcolor", "resetprefix").stream()
|
||||||
|
.filter(s -> s.startsWith(args[0].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 2) {
|
||||||
|
String sub = args[0].toLowerCase();
|
||||||
|
if (Arrays.asList("info", "setcolor", "setprefix", "coloron", "coloroff",
|
||||||
|
"prefixon", "prefixoff", "resetcolor", "resetprefix").contains(sub)) {
|
||||||
|
return Bukkit.getOnlinePlayers().stream()
|
||||||
|
.map(Player::getName)
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length >= 3 && args[0].equalsIgnoreCase("setcolor")) {
|
||||||
|
return PRESET_COLORS.stream()
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
package com.loki.lochat.gradient.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import com.loki.lochat.gradient.config.GradientMessages;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
|
import com.loki.lochat.gradient.util.GradientConstants;
|
||||||
|
import com.loki.lochat.gradient.util.GradientUtil;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Команда /color для установки градиента ника
|
||||||
|
*/
|
||||||
|
public class GradientColorCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
|
||||||
|
|
||||||
|
private static final List<String> PRESET_COLORS = List.of(
|
||||||
|
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3",
|
||||||
|
"#FF69B4", "#00FFFF", "#FFD700", "#FFFFFF", "#000000"
|
||||||
|
);
|
||||||
|
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset", "copy");
|
||||||
|
|
||||||
|
public GradientColorCommand(GradientModule module) {
|
||||||
|
this.module = module;
|
||||||
|
registerSubCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerSubCommands() {
|
||||||
|
subCommands.put("on", this::handleOn);
|
||||||
|
subCommands.put("off", this::handleOff);
|
||||||
|
subCommands.put("reset", this::handleReset);
|
||||||
|
subCommands.put("copy", this::handleCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(module.getMessages().get("only-player"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.color")) {
|
||||||
|
player.sendMessage(module.getMessages().get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(module.getMessages().get("color-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
var handler = subCommands.get(subCommand);
|
||||||
|
if (handler != null) {
|
||||||
|
return handler.apply(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleSetColors(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOn(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
|
||||||
|
if (!data.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("color-enabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOff(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(module.getMessages().get("color-disabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleReset(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
|
||||||
|
if (!data.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setColors(new ArrayList<>());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("color-reset"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleCopy(Player player, String[] args) {
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(msg.get("color-copy-usage"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[1]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(msg.get("player-not-found"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData targetData = module.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
if (!targetData.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-copy-no-colors", "player", target.getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") &&
|
||||||
|
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>(targetData.getColors());
|
||||||
|
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
|
||||||
|
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSetColors(Player player, String[] args) {
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
|
||||||
|
if (args.length > cfg.getMaxColors()) {
|
||||||
|
player.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < cfg.getMinColors()) {
|
||||||
|
player.sendMessage(msg.get("not-enough-colors", "min", String.valueOf(cfg.getMinColors())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") &&
|
||||||
|
!checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>();
|
||||||
|
for (String arg : args) {
|
||||||
|
String color = arg.startsWith("#") ? arg : "#" + arg;
|
||||||
|
if (!GradientUtil.isValidHex(color)) {
|
||||||
|
player.sendMessage(msg.get("invalid-color", "color", arg));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
colors.add(color.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
|
||||||
|
GradientConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
|
||||||
|
long cooldownMs = cooldownSec * GradientConstants.MILLIS_PER_SECOND;
|
||||||
|
long timePassed = System.currentTimeMillis() - lastChange;
|
||||||
|
if (timePassed < cooldownMs) {
|
||||||
|
long remaining = (cooldownMs - timePassed) / GradientConstants.MILLIS_PER_SECOND;
|
||||||
|
player.sendMessage(module.getMessages().get("cooldown", "time", String.valueOf(remaining)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBalance(Player player, int price) {
|
||||||
|
if (!module.hasPlayerPoints()) {
|
||||||
|
player.sendMessage("§cPlayerPoints не установлен!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(module.getMessages().get("not-enough-points-color", "price", String.valueOf(price)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndUpdate(Player player, GradientPlayerData data) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, player, data));
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
String input = args[0].toLowerCase();
|
||||||
|
List<String> completions = new ArrayList<>(SUB_COMMANDS);
|
||||||
|
completions.addAll(PRESET_COLORS);
|
||||||
|
return completions.stream().filter(s -> s.toLowerCase().startsWith(input)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String firstArg = args[0].toLowerCase();
|
||||||
|
|
||||||
|
if (args.length == 2 && firstArg.equals("copy")) {
|
||||||
|
return Bukkit.getOnlinePlayers().stream().map(Player::getName)
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase())).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SUB_COMMANDS.contains(firstArg) && args.length <= module.getConfig().getMaxColors()) {
|
||||||
|
return PRESET_COLORS.stream()
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
package com.loki.lochat.gradient.commands;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import com.loki.lochat.gradient.config.GradientMessages;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
|
import com.loki.lochat.gradient.util.GradientConstants;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Команда /prefix для установки кастомного префикса
|
||||||
|
*/
|
||||||
|
public class GradientPrefixCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
|
||||||
|
|
||||||
|
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile(
|
||||||
|
"(?i)(&[0-9a-fk-or]|§[0-9a-fk-or]|#[0-9a-f]{6}|&#[0-9a-f]{6}|<#[0-9a-f]{6}>)");
|
||||||
|
private static final Pattern ALLOWED_PREFIX_PATTERN = Pattern.compile("^[a-zA-Zа-яА-ЯёЁ0-9 _\\-]+$");
|
||||||
|
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset");
|
||||||
|
|
||||||
|
public GradientPrefixCommand(GradientModule module) {
|
||||||
|
this.module = module;
|
||||||
|
registerSubCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerSubCommands() {
|
||||||
|
subCommands.put("on", this::handleOn);
|
||||||
|
subCommands.put("off", this::handleOff);
|
||||||
|
subCommands.put("reset", this::handleReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(module.getMessages().get("only-player"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.prefix")) {
|
||||||
|
player.sendMessage(module.getMessages().get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(module.getMessages().get("prefix-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
var handler = subCommands.get(subCommand);
|
||||||
|
if (handler != null) {
|
||||||
|
return handler.apply(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleSetPrefix(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOn(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
|
||||||
|
if (!data.hasPrefix()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-prefix"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("prefix-enabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOff(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
module.getLuckPermsHook().removePrefix(player);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(module.getMessages().get("prefix-disabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleReset(Player player, String[] args) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
|
||||||
|
if (!data.hasPrefix()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-prefix"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setPrefix(null);
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
module.getLuckPermsHook().removePrefix(player);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("prefix-reset-success"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSetPrefix(Player player, String[] args) {
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
String prefix = String.join(" ", args);
|
||||||
|
|
||||||
|
if (prefix.trim().isEmpty()) {
|
||||||
|
player.sendMessage(msg.get("prefix-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COLOR_CODE_PATTERN.matcher(prefix).find()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ALLOWED_PREFIX_PATTERN.matcher(prefix).matches()) {
|
||||||
|
player.sendMessage(msg.get("prefix-invalid-chars"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.isPrefixBlacklisted(prefix)) {
|
||||||
|
player.sendMessage(msg.get("prefix-blacklisted"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix.length() > cfg.getMaxPrefixLength()) {
|
||||||
|
player.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") &&
|
||||||
|
!checkCooldown(player, cfg.getPrefixCooldown(), data.getLastPrefixChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int price = calculatePrice(player, cfg, data);
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientConfirmGUI gui = new GradientConfirmGUI(module, player,
|
||||||
|
GradientConfirmGUI.ConfirmType.PREFIX, data.getColors(), prefix, price);
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculatePrice(Player player, GradientConfig cfg, GradientPlayerData data) {
|
||||||
|
if (player.hasPermission("gradient.bypass.cost")) return 0;
|
||||||
|
return (cfg.isPrefixOneTimePurchase() && data.isPrefixPurchased()) ? 0 : cfg.getPrefixPrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
|
||||||
|
long cooldownMs = cooldownSec * GradientConstants.MILLIS_PER_SECOND;
|
||||||
|
long timePassed = System.currentTimeMillis() - lastChange;
|
||||||
|
if (timePassed < cooldownMs) {
|
||||||
|
long remaining = (cooldownMs - timePassed) / GradientConstants.MILLIS_PER_SECOND;
|
||||||
|
player.sendMessage(module.getMessages().get("cooldown", "time", String.valueOf(remaining)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBalance(Player player, int price) {
|
||||||
|
if (!module.hasPlayerPoints()) {
|
||||||
|
player.sendMessage("§cPlayerPoints не установлен!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(module.getMessages().get("not-enough-points-prefix", "price", String.valueOf(price)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndUpdate(Player player, GradientPlayerData data) {
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, player, data));
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
return SUB_COMMANDS.stream()
|
||||||
|
.filter(s -> s.startsWith(args[0].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.loki.lochat.gradient.config;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конфигурация градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientConfig {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private FileConfiguration config;
|
||||||
|
private File configFile;
|
||||||
|
|
||||||
|
public GradientConfig(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadConfig() {
|
||||||
|
configFile = new File(plugin.getDataFolder(), "gradient-config.yml");
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
plugin.saveResource("gradient-config.yml", false);
|
||||||
|
}
|
||||||
|
config = YamlConfiguration.loadConfiguration(configFile);
|
||||||
|
|
||||||
|
// Загружаем дефолты
|
||||||
|
InputStream defStream = plugin.getResource("gradient-config.yml");
|
||||||
|
if (defStream != null) {
|
||||||
|
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
|
||||||
|
new InputStreamReader(defStream, StandardCharsets.UTF_8));
|
||||||
|
config.setDefaults(defConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try {
|
||||||
|
config.save(configFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка сохранения gradient-config.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() { return config.getBoolean("enabled", true); }
|
||||||
|
public int getMaxColors() { return config.getInt("max-colors", 7); }
|
||||||
|
public int getMinColors() { return config.getInt("min-colors", 1); }
|
||||||
|
public int getPricePerColor() { return config.getInt("price-per-color", 50); }
|
||||||
|
public int getPrefixPrice() { return config.getInt("prefix-price", 500); }
|
||||||
|
public boolean isPrefixOneTimePurchase() { return config.getBoolean("prefix-one-time-purchase", true); }
|
||||||
|
public int getColorCooldown() { return config.getInt("color-cooldown", 60); }
|
||||||
|
public int getPrefixCooldown() { return config.getInt("prefix-cooldown", 300); }
|
||||||
|
public int getMaxPrefixLength() { return config.getInt("max-prefix-length", 7); }
|
||||||
|
public String getPrefixFormat() { return config.getString("prefix-format", "[{prefix}] "); }
|
||||||
|
public boolean isGradientOnPrefix() { return config.getBoolean("gradient-on-prefix", true); }
|
||||||
|
public boolean isContinuousGradient() { return config.getBoolean("continuous-gradient", true); }
|
||||||
|
public boolean isUseLegacyRgbFormat() { return config.getBoolean("use-legacy-rgb-format", true); }
|
||||||
|
public String getStorageType() { return config.getString("storage-type", "YAML"); }
|
||||||
|
public String getSqliteFile() { return config.getString("sqlite-file", "gradient-data.db"); }
|
||||||
|
|
||||||
|
public List<String> getPrefixBlacklist() {
|
||||||
|
return config.getStringList("prefix-blacklist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrefixBlacklisted(String prefix) {
|
||||||
|
String lowerPrefix = prefix.toLowerCase();
|
||||||
|
return getPrefixBlacklist().stream()
|
||||||
|
.anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.loki.lochat.gradient.config;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сообщения градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientMessages {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private FileConfiguration messages;
|
||||||
|
private File messagesFile;
|
||||||
|
|
||||||
|
public GradientMessages(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessages() {
|
||||||
|
messagesFile = new File(plugin.getDataFolder(), "gradient-messages.yml");
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
plugin.saveResource("gradient-messages.yml", false);
|
||||||
|
}
|
||||||
|
messages = YamlConfiguration.loadConfiguration(messagesFile);
|
||||||
|
|
||||||
|
InputStream defStream = plugin.getResource("gradient-messages.yml");
|
||||||
|
if (defStream != null) {
|
||||||
|
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
|
||||||
|
new InputStreamReader(defStream, StandardCharsets.UTF_8));
|
||||||
|
messages.setDefaults(defConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key) {
|
||||||
|
return messages.getString(key, "§cСообщение не найдено: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key, String... replacements) {
|
||||||
|
String message = get(key);
|
||||||
|
for (int i = 0; i < replacements.length; i += 2) {
|
||||||
|
if (i + 1 < replacements.length) {
|
||||||
|
message = message.replace("{" + replacements[i] + "}", replacements[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
package com.loki.lochat.gradient.data;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Менеджер данных градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientDataManager {
|
||||||
|
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final GradientConfig config;
|
||||||
|
private final Map<UUID, GradientPlayerData> cache = new ConcurrentHashMap<>();
|
||||||
|
private Connection sqliteConnection;
|
||||||
|
private boolean useSqlite;
|
||||||
|
|
||||||
|
public GradientDataManager(JavaPlugin plugin, GradientConfig config) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.config = config;
|
||||||
|
this.useSqlite = config.getStorageType().equalsIgnoreCase("SQLITE");
|
||||||
|
|
||||||
|
if (useSqlite) {
|
||||||
|
initSqlite();
|
||||||
|
} else {
|
||||||
|
loadYamlData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSqlite() {
|
||||||
|
try {
|
||||||
|
File dbFile = new File(plugin.getDataFolder(), config.getSqliteFile());
|
||||||
|
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||||
|
|
||||||
|
try (Statement stmt = sqliteConnection.createStatement()) {
|
||||||
|
stmt.executeUpdate("""
|
||||||
|
CREATE TABLE IF NOT EXISTS gradient_data (
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
prefix TEXT,
|
||||||
|
colors TEXT,
|
||||||
|
color_enabled INTEGER DEFAULT 1,
|
||||||
|
prefix_enabled INTEGER DEFAULT 1,
|
||||||
|
prefix_purchased INTEGER DEFAULT 0,
|
||||||
|
last_color_change INTEGER DEFAULT 0,
|
||||||
|
last_prefix_change INTEGER DEFAULT 0
|
||||||
|
)
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
plugin.getLogger().info("Gradient SQLite база данных инициализирована.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Ошибка инициализации Gradient SQLite: " + e.getMessage());
|
||||||
|
useSqlite = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadYamlData() {
|
||||||
|
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
|
||||||
|
if (!dataFile.exists()) return;
|
||||||
|
|
||||||
|
FileConfiguration data = YamlConfiguration.loadConfiguration(dataFile);
|
||||||
|
for (String uuidStr : data.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(uuidStr);
|
||||||
|
GradientPlayerData playerData = new GradientPlayerData(uuid);
|
||||||
|
playerData.setPrefix(data.getString(uuidStr + ".prefix"));
|
||||||
|
playerData.setColors(data.getStringList(uuidStr + ".colors"));
|
||||||
|
playerData.setColorEnabled(data.getBoolean(uuidStr + ".color-enabled", true));
|
||||||
|
playerData.setPrefixEnabled(data.getBoolean(uuidStr + ".prefix-enabled", true));
|
||||||
|
playerData.setPrefixPurchased(data.getBoolean(uuidStr + ".prefix-purchased", false));
|
||||||
|
playerData.setLastColorChange(data.getLong(uuidStr + ".last-color-change", 0));
|
||||||
|
playerData.setLastPrefixChange(data.getLong(uuidStr + ".last-prefix-change", 0));
|
||||||
|
cache.put(uuid, playerData);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GradientPlayerData getPlayerData(UUID uuid) {
|
||||||
|
return cache.computeIfAbsent(uuid, this::loadPlayerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GradientPlayerData loadPlayerData(UUID uuid) {
|
||||||
|
GradientPlayerData data = new GradientPlayerData(uuid);
|
||||||
|
|
||||||
|
if (useSqlite && sqliteConnection != null) {
|
||||||
|
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
|
||||||
|
"SELECT prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change FROM gradient_data WHERE uuid = ?")) {
|
||||||
|
stmt.setString(1, uuid.toString());
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
if (rs.next()) {
|
||||||
|
data.setPrefix(rs.getString("prefix"));
|
||||||
|
String colorsStr = rs.getString("colors");
|
||||||
|
if (colorsStr != null && !colorsStr.isEmpty()) {
|
||||||
|
data.setColors(List.of(colorsStr.split(",")));
|
||||||
|
}
|
||||||
|
data.setColorEnabled(rs.getBoolean("color_enabled"));
|
||||||
|
data.setPrefixEnabled(rs.getBoolean("prefix_enabled"));
|
||||||
|
data.setPrefixPurchased(rs.getBoolean("prefix_purchased"));
|
||||||
|
data.setLastColorChange(rs.getLong("last_color_change"));
|
||||||
|
data.setLastPrefixChange(rs.getLong("last_prefix_change"));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка загрузки gradient данных: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePlayerData(UUID uuid) {
|
||||||
|
GradientPlayerData data = cache.get(uuid);
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
if (useSqlite && sqliteConnection != null) {
|
||||||
|
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
|
||||||
|
"INSERT OR REPLACE INTO gradient_data (uuid, prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) {
|
||||||
|
stmt.setString(1, uuid.toString());
|
||||||
|
stmt.setString(2, data.getPrefix());
|
||||||
|
stmt.setString(3, data.hasColors() ? String.join(",", data.getColors()) : "");
|
||||||
|
stmt.setBoolean(4, data.isColorEnabled());
|
||||||
|
stmt.setBoolean(5, data.isPrefixEnabled());
|
||||||
|
stmt.setBoolean(6, data.isPrefixPurchased());
|
||||||
|
stmt.setLong(7, data.getLastColorChange());
|
||||||
|
stmt.setLong(8, data.getLastPrefixChange());
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка сохранения gradient данных: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAll() {
|
||||||
|
if (useSqlite) {
|
||||||
|
for (UUID uuid : cache.keySet()) {
|
||||||
|
savePlayerData(uuid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveYamlData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveYamlData() {
|
||||||
|
File dataFile = new File(plugin.getDataFolder(), "gradient-data.yml");
|
||||||
|
FileConfiguration data = new YamlConfiguration();
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, GradientPlayerData> entry : cache.entrySet()) {
|
||||||
|
String path = entry.getKey().toString();
|
||||||
|
GradientPlayerData pd = entry.getValue();
|
||||||
|
data.set(path + ".prefix", pd.getPrefix());
|
||||||
|
data.set(path + ".colors", pd.getColors());
|
||||||
|
data.set(path + ".color-enabled", pd.isColorEnabled());
|
||||||
|
data.set(path + ".prefix-enabled", pd.isPrefixEnabled());
|
||||||
|
data.set(path + ".prefix-purchased", pd.isPrefixPurchased());
|
||||||
|
data.set(path + ".last-color-change", pd.getLastColorChange());
|
||||||
|
data.set(path + ".last-prefix-change", pd.getLastPrefixChange());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data.save(dataFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка сохранения gradient-data.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.loki.lochat.gradient.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Данные игрока для градиентного ника
|
||||||
|
*/
|
||||||
|
public class GradientPlayerData {
|
||||||
|
|
||||||
|
private final UUID uuid;
|
||||||
|
private String prefix;
|
||||||
|
private List<String> colors;
|
||||||
|
private boolean colorEnabled;
|
||||||
|
private boolean prefixEnabled;
|
||||||
|
private boolean prefixPurchased;
|
||||||
|
private long lastColorChange;
|
||||||
|
private long lastPrefixChange;
|
||||||
|
|
||||||
|
public GradientPlayerData(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.prefix = null;
|
||||||
|
this.colors = new ArrayList<>();
|
||||||
|
this.colorEnabled = true;
|
||||||
|
this.prefixEnabled = true;
|
||||||
|
this.prefixPurchased = false;
|
||||||
|
this.lastColorChange = 0;
|
||||||
|
this.lastPrefixChange = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() { return uuid; }
|
||||||
|
public String getPrefix() { return prefix; }
|
||||||
|
public void setPrefix(String prefix) { this.prefix = prefix; }
|
||||||
|
public List<String> getColors() { return colors; }
|
||||||
|
|
||||||
|
public void setColors(List<String> colors) {
|
||||||
|
this.colors = colors != null ? new ArrayList<>(colors) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPrefix() { return prefix != null && !prefix.isEmpty(); }
|
||||||
|
public boolean hasColors() { return colors != null && !colors.isEmpty(); }
|
||||||
|
public boolean isColorEnabled() { return colorEnabled; }
|
||||||
|
public void setColorEnabled(boolean colorEnabled) { this.colorEnabled = colorEnabled; }
|
||||||
|
public boolean isPrefixEnabled() { return prefixEnabled; }
|
||||||
|
public void setPrefixEnabled(boolean prefixEnabled) { this.prefixEnabled = prefixEnabled; }
|
||||||
|
public boolean isPrefixPurchased() { return prefixPurchased; }
|
||||||
|
public void setPrefixPurchased(boolean prefixPurchased) { this.prefixPurchased = prefixPurchased; }
|
||||||
|
public long getLastColorChange() { return lastColorChange; }
|
||||||
|
public void setLastColorChange(long lastColorChange) { this.lastColorChange = lastColorChange; }
|
||||||
|
public long getLastPrefixChange() { return lastPrefixChange; }
|
||||||
|
public void setLastPrefixChange(long lastPrefixChange) { this.lastPrefixChange = lastPrefixChange; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
package com.loki.lochat.gradient.gui;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.util.GradientConstants;
|
||||||
|
import com.loki.lochat.gradient.util.GradientUtil;
|
||||||
|
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.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI подтверждения для градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientConfirmGUI implements InventoryHolder {
|
||||||
|
|
||||||
|
public enum ConfirmType {
|
||||||
|
COLOR, PREFIX
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
|
||||||
|
.character('&')
|
||||||
|
.hexCharacter('#')
|
||||||
|
.hexColors()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
private final Player player;
|
||||||
|
private final Inventory inventory;
|
||||||
|
private final ConfirmType type;
|
||||||
|
private final List<String> colors;
|
||||||
|
private final String prefix;
|
||||||
|
private final int price;
|
||||||
|
|
||||||
|
public GradientConfirmGUI(GradientModule module, Player player, ConfirmType type,
|
||||||
|
List<String> colors, String prefix, int price) {
|
||||||
|
this.module = module;
|
||||||
|
this.player = player;
|
||||||
|
this.type = type;
|
||||||
|
this.colors = colors;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.price = price;
|
||||||
|
this.inventory = Bukkit.createInventory(this, 27,
|
||||||
|
Component.text("Подтверждение", NamedTextColor.DARK_GRAY));
|
||||||
|
setupItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupItems() {
|
||||||
|
ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ");
|
||||||
|
for (int i = 0; i < 27; i++) {
|
||||||
|
inventory.setItem(i, glass);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||||
|
SkullMeta skullMeta = (SkullMeta) head.getItemMeta();
|
||||||
|
skullMeta.setOwningPlayer(player);
|
||||||
|
|
||||||
|
String preview = buildPreview();
|
||||||
|
Component previewComponent = SERIALIZER.deserialize(preview);
|
||||||
|
skullMeta.displayName(previewComponent.decoration(TextDecoration.ITALIC, false));
|
||||||
|
|
||||||
|
List<Component> lore = new ArrayList<>();
|
||||||
|
lore.add(Component.empty());
|
||||||
|
lore.add(Component.text("Так будет выглядеть ваш ник", NamedTextColor.GRAY)
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
if (price > 0) {
|
||||||
|
lore.add(Component.text("Стоимость: ", NamedTextColor.GRAY)
|
||||||
|
.append(Component.text(price + " PP", NamedTextColor.GOLD))
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
}
|
||||||
|
skullMeta.lore(lore);
|
||||||
|
head.setItemMeta(skullMeta);
|
||||||
|
inventory.setItem(GradientConstants.GUI_PREVIEW_SLOT, head);
|
||||||
|
|
||||||
|
ItemStack confirm = createItem(Material.LIME_WOOL, "§a§lПодтвердить");
|
||||||
|
ItemMeta confirmMeta = confirm.getItemMeta();
|
||||||
|
List<Component> confirmLore = new ArrayList<>();
|
||||||
|
confirmLore.add(Component.empty());
|
||||||
|
confirmLore.add(Component.text("Нажмите для подтверждения", NamedTextColor.GREEN)
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
confirmMeta.lore(confirmLore);
|
||||||
|
confirm.setItemMeta(confirmMeta);
|
||||||
|
inventory.setItem(GradientConstants.GUI_CONFIRM_SLOT, confirm);
|
||||||
|
|
||||||
|
ItemStack cancel = createItem(Material.RED_WOOL, "§c§lОтменить");
|
||||||
|
ItemMeta cancelMeta = cancel.getItemMeta();
|
||||||
|
List<Component> cancelLore = new ArrayList<>();
|
||||||
|
cancelLore.add(Component.empty());
|
||||||
|
cancelLore.add(Component.text("Нажмите для отмены", NamedTextColor.RED)
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
cancelMeta.lore(cancelLore);
|
||||||
|
cancel.setItemMeta(cancelMeta);
|
||||||
|
inventory.setItem(GradientConstants.GUI_CANCEL_SLOT, cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPreview() {
|
||||||
|
String nick = player.getName();
|
||||||
|
String prefixToUse = this.prefix;
|
||||||
|
List<String> colorsToUse = this.colors;
|
||||||
|
|
||||||
|
if (type == ConfirmType.PREFIX && (colorsToUse == null || colorsToUse.isEmpty())) {
|
||||||
|
var data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
if (data.hasColors() && data.isColorEnabled()) {
|
||||||
|
colorsToUse = data.getColors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == ConfirmType.COLOR && (prefixToUse == null || prefixToUse.isEmpty())) {
|
||||||
|
var data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
if (data.hasPrefix() && data.isPrefixEnabled()) {
|
||||||
|
prefixToUse = data.getPrefix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GradientUtil.buildDisplayName(
|
||||||
|
prefixToUse,
|
||||||
|
nick,
|
||||||
|
colorsToUse,
|
||||||
|
module.getConfig().isGradientOnPrefix(),
|
||||||
|
module.getConfig().isContinuousGradient(),
|
||||||
|
module.getConfig().getPrefixFormat(),
|
||||||
|
module.getConfig().isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack createItem(Material material, String name) {
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
meta.displayName(LegacyComponentSerializer.legacySection().deserialize(name)
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
player.openInventory(inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Inventory getInventory() { return inventory; }
|
||||||
|
public Player getPlayer() { return player; }
|
||||||
|
public ConfirmType getType() { return type; }
|
||||||
|
public List<String> getColors() { return colors; }
|
||||||
|
public String getPrefix() { return prefix; }
|
||||||
|
public int getPrice() { return price; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.loki.lochat.gradient.hooks;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.util.GradientConstants;
|
||||||
|
import net.luckperms.api.LuckPerms;
|
||||||
|
import net.luckperms.api.LuckPermsProvider;
|
||||||
|
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
|
import net.luckperms.api.node.types.PrefixNode;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Хук для LuckPerms
|
||||||
|
*/
|
||||||
|
public class GradientLuckPermsHook {
|
||||||
|
|
||||||
|
private LuckPerms luckPerms;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public GradientLuckPermsHook(JavaPlugin plugin) {
|
||||||
|
this.enabled = false;
|
||||||
|
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
|
||||||
|
try {
|
||||||
|
this.luckPerms = LuckPermsProvider.get();
|
||||||
|
this.enabled = true;
|
||||||
|
plugin.getLogger().info("Gradient: LuckPerms подключен!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("Gradient: Не удалось подключить LuckPerms: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled && luckPerms != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrefix(Player player, String prefix) {
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
user.data().clear(node ->
|
||||||
|
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build();
|
||||||
|
user.data().add(prefixNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
luckPerms.getUserManager().saveUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrefix(UUID uuid, String prefix) {
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
luckPerms.getUserManager().loadUser(uuid).thenAccept(user -> {
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
user.data().clear(node ->
|
||||||
|
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build();
|
||||||
|
user.data().add(prefixNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
luckPerms.getUserManager().saveUser(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePrefix(Player player) {
|
||||||
|
setPrefix(player, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePrefix(UUID uuid) {
|
||||||
|
setPrefix(uuid, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivePrefix(Player player) {
|
||||||
|
if (!isEnabled()) return null;
|
||||||
|
|
||||||
|
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
CachedMetaData metaData = user.getCachedData().getMetaData();
|
||||||
|
return metaData.getPrefix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.loki.lochat.gradient.listeners;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.config.GradientMessages;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.gui.GradientConfirmGUI;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
|
import com.loki.lochat.gradient.util.GradientConstants;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Слушатель GUI градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientGUIListener implements Listener {
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
|
||||||
|
private static final Map<GradientConfirmGUI.ConfirmType, String> PRICE_MSG_KEYS = Map.of(
|
||||||
|
GradientConfirmGUI.ConfirmType.COLOR, "not-enough-points-color",
|
||||||
|
GradientConfirmGUI.ConfirmType.PREFIX, "not-enough-points-prefix"
|
||||||
|
);
|
||||||
|
|
||||||
|
public GradientGUIListener(GradientModule module) {
|
||||||
|
this.module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!(event.getInventory().getHolder() instanceof GradientConfirmGUI gui)) return;
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
if (!(event.getWhoClicked() instanceof Player player) || !player.equals(gui.getPlayer())) return;
|
||||||
|
|
||||||
|
switch (event.getRawSlot()) {
|
||||||
|
case GradientConstants.GUI_CONFIRM_SLOT -> handleConfirm(player, gui);
|
||||||
|
case GradientConstants.GUI_CANCEL_SLOT -> handleCancel(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConfirm(Player player, GradientConfirmGUI gui) {
|
||||||
|
player.closeInventory();
|
||||||
|
|
||||||
|
GradientMessages msg = module.getMessages();
|
||||||
|
int price = gui.getPrice();
|
||||||
|
|
||||||
|
if (price > 0 && !player.hasPermission("gradient.bypass.cost")) {
|
||||||
|
if (!module.hasPlayerPoints()) {
|
||||||
|
player.sendMessage("§cPlayerPoints не установлен!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int balance = module.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(msg.get(PRICE_MSG_KEYS.get(gui.getType()), "price", String.valueOf(price)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
module.getPlayerPointsAPI().take(player.getUniqueId(), price);
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
switch (gui.getType()) {
|
||||||
|
case COLOR -> {
|
||||||
|
data.setColors(gui.getColors());
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
data.setLastColorChange(System.currentTimeMillis());
|
||||||
|
player.sendMessage(msg.get("color-success", "price", String.valueOf(price)));
|
||||||
|
}
|
||||||
|
case PREFIX -> {
|
||||||
|
data.setPrefix(gui.getPrefix());
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
data.setPrefixPurchased(true);
|
||||||
|
data.setLastPrefixChange(System.currentTimeMillis());
|
||||||
|
module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data));
|
||||||
|
player.sendMessage(msg.get(price > 0 ? "prefix-success" : "prefix-success-free",
|
||||||
|
"price", String.valueOf(price)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player,
|
||||||
|
() -> DisplayNameUtil.updateDisplayName(module, player, data));
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCancel(Player player) {
|
||||||
|
player.closeInventory();
|
||||||
|
player.sendMessage("§7Действие отменено.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.loki.lochat.gradient.listeners;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import com.loki.lochat.gradient.util.DisplayNameUtil;
|
||||||
|
import com.loki.lochat.gradient.util.FoliaUtil;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Слушатель событий игроков для градиентного модуля
|
||||||
|
*/
|
||||||
|
public class GradientPlayerListener implements Listener {
|
||||||
|
|
||||||
|
private final GradientModule module;
|
||||||
|
|
||||||
|
public GradientPlayerListener(GradientModule module) {
|
||||||
|
this.module = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(), () -> {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
FoliaUtil.runEntityTask(module.getPlugin(), player, () -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
|
||||||
|
DisplayNameUtil.updateDisplayName(module, player, data);
|
||||||
|
|
||||||
|
if (data.hasPrefix() && data.isPrefixEnabled()) {
|
||||||
|
module.getLuckPermsHook().setPrefix(player,
|
||||||
|
DisplayNameUtil.buildColoredPrefix(module, data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
FoliaUtil.runAsync(module.getPlugin(),
|
||||||
|
() -> module.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java
Normal file
118
src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import com.loki.lochat.gradient.config.GradientConfig;
|
||||||
|
import com.loki.lochat.gradient.data.GradientPlayerData;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для обновления отображаемого имени игрока
|
||||||
|
*/
|
||||||
|
public final class DisplayNameUtil {
|
||||||
|
|
||||||
|
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
|
||||||
|
.character('&')
|
||||||
|
.hexCharacter('#')
|
||||||
|
.hexColors()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private DisplayNameUtil() {}
|
||||||
|
|
||||||
|
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
|
||||||
|
String prefix = null;
|
||||||
|
String prefixFormat = cfg.getPrefixFormat();
|
||||||
|
|
||||||
|
if (data.isPrefixEnabled() && data.hasPrefix()) {
|
||||||
|
prefix = data.getPrefix();
|
||||||
|
} else {
|
||||||
|
String lpPrefix = module.getLuckPermsHook().getActivePrefix(player);
|
||||||
|
if (lpPrefix != null && !lpPrefix.isEmpty()) {
|
||||||
|
String displayName = buildWithLuckPermsPrefix(
|
||||||
|
lpPrefix,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
cfg.isUseLegacyRgbFormat(),
|
||||||
|
cfg.isContinuousGradient()
|
||||||
|
);
|
||||||
|
var component = SERIALIZER.deserialize(displayName);
|
||||||
|
player.displayName(component);
|
||||||
|
player.playerListName(component);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String displayName = GradientUtil.buildDisplayName(
|
||||||
|
prefix,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
cfg.isGradientOnPrefix(),
|
||||||
|
cfg.isContinuousGradient(),
|
||||||
|
prefixFormat,
|
||||||
|
cfg.isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
var component = SERIALIZER.deserialize(displayName);
|
||||||
|
player.displayName(component);
|
||||||
|
player.playerListName(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildWithLuckPermsPrefix(String lpPrefix, String nick,
|
||||||
|
List<String> colors,
|
||||||
|
boolean useLegacyFormat,
|
||||||
|
boolean continuousGradient) {
|
||||||
|
if (colors == null || colors.isEmpty()) {
|
||||||
|
return lpPrefix + nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continuousGradient) {
|
||||||
|
String cleanPrefix = stripColors(lpPrefix);
|
||||||
|
String fullText = cleanPrefix + nick;
|
||||||
|
return GradientUtil.applyGradient(fullText, colors, useLegacyFormat);
|
||||||
|
} else {
|
||||||
|
return lpPrefix + GradientUtil.applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stripColors(String text) {
|
||||||
|
if (text == null) return null;
|
||||||
|
return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6})", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildColoredPrefix(GradientModule module, GradientPlayerData data) {
|
||||||
|
if (!data.hasPrefix() || !data.isPrefixEnabled()) return null;
|
||||||
|
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
String prefix = cfg.getPrefixFormat().replace("{prefix}", data.getPrefix()).stripTrailing();
|
||||||
|
|
||||||
|
return (data.hasColors() && data.isColorEnabled() && cfg.isGradientOnPrefix())
|
||||||
|
? GradientUtil.applyGradient(prefix, data.getColors(), cfg.isUseLegacyRgbFormat())
|
||||||
|
: prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает полное отображаемое имя игрока с градиентом
|
||||||
|
*/
|
||||||
|
public static String getFullDisplayName(GradientModule module, Player player) {
|
||||||
|
GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
GradientConfig cfg = module.getConfig();
|
||||||
|
|
||||||
|
String prefix = null;
|
||||||
|
if (data.isPrefixEnabled() && data.hasPrefix()) {
|
||||||
|
prefix = data.getPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GradientUtil.buildDisplayName(
|
||||||
|
prefix,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
cfg.isGradientOnPrefix(),
|
||||||
|
cfg.isContinuousGradient(),
|
||||||
|
cfg.getPrefixFormat(),
|
||||||
|
cfg.isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/java/com/loki/lochat/gradient/util/FoliaUtil.java
Normal file
74
src/main/java/com/loki/lochat/gradient/util/FoliaUtil.java
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для работы с Folia
|
||||||
|
*/
|
||||||
|
public final class FoliaUtil {
|
||||||
|
|
||||||
|
private static final boolean IS_FOLIA;
|
||||||
|
|
||||||
|
static {
|
||||||
|
IS_FOLIA = checkFolia();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FoliaUtil() {}
|
||||||
|
|
||||||
|
private static boolean checkFolia() {
|
||||||
|
try {
|
||||||
|
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFolia() {
|
||||||
|
return IS_FOLIA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runEntityTask(Plugin plugin, Entity entity, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runEntityTaskLater(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
entity.getScheduler().runDelayed(plugin, scheduledTask -> task.run(), null, delayTicks);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runAsync(Plugin plugin, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runAsyncLater(Plugin plugin, Runnable task, long delayMs) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayMs, TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delayMs / 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runGlobalTask(Plugin plugin, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> task.run());
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Константы для градиентного модуля
|
||||||
|
*/
|
||||||
|
public final class GradientConstants {
|
||||||
|
|
||||||
|
private GradientConstants() {}
|
||||||
|
|
||||||
|
public static final long MILLIS_PER_SECOND = 1000L;
|
||||||
|
|
||||||
|
// GUI слоты
|
||||||
|
public static final int GUI_CONFIRM_SLOT = 11;
|
||||||
|
public static final int GUI_CANCEL_SLOT = 15;
|
||||||
|
public static final int GUI_PREVIEW_SLOT = 13;
|
||||||
|
|
||||||
|
// LuckPerms
|
||||||
|
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
|
||||||
|
}
|
||||||
123
src/main/java/com/loki/lochat/gradient/util/GradientUtil.java
Normal file
123
src/main/java/com/loki/lochat/gradient/util/GradientUtil.java
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для работы с градиентами
|
||||||
|
*/
|
||||||
|
public final class GradientUtil {
|
||||||
|
|
||||||
|
private static final Pattern HEX_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
|
||||||
|
|
||||||
|
private GradientUtil() {}
|
||||||
|
|
||||||
|
public static boolean isValidHex(String color) {
|
||||||
|
return HEX_PATTERN.matcher(color).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String applyGradient(String text, List<String> hexColors, boolean useLegacyFormat) {
|
||||||
|
if (text == null || text.isEmpty() || hexColors == null || hexColors.isEmpty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] chars = text.toCharArray();
|
||||||
|
int length = chars.length;
|
||||||
|
if (length == 0) return text;
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
if (hexColors.size() == 1) {
|
||||||
|
String colorCode = formatHex(hexColors.get(0), useLegacyFormat);
|
||||||
|
for (char c : chars) {
|
||||||
|
result.append(colorCode).append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color[] colors = hexColors.stream()
|
||||||
|
.map(GradientUtil::parseHex)
|
||||||
|
.toArray(Color[]::new);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Color interpolated = interpolateMultiColor(colors, (double) i / Math.max(1, length - 1));
|
||||||
|
String hex = String.format("#%02x%02x%02x",
|
||||||
|
interpolated.getRed(), interpolated.getGreen(), interpolated.getBlue());
|
||||||
|
result.append(formatHex(hex, useLegacyFormat)).append(chars[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color interpolateMultiColor(Color[] colors, double ratio) {
|
||||||
|
if (colors.length == 1) return colors[0];
|
||||||
|
if (ratio <= 0) return colors[0];
|
||||||
|
if (ratio >= 1) return colors[colors.length - 1];
|
||||||
|
|
||||||
|
double scaledRatio = ratio * (colors.length - 1);
|
||||||
|
int index = (int) scaledRatio;
|
||||||
|
double localRatio = scaledRatio - index;
|
||||||
|
|
||||||
|
if (index >= colors.length - 1) {
|
||||||
|
return colors[colors.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolate(colors[index], colors[index + 1], localRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color interpolate(Color c1, Color c2, double ratio) {
|
||||||
|
int r = (int) (c1.getRed() + (c2.getRed() - c1.getRed()) * ratio);
|
||||||
|
int g = (int) (c1.getGreen() + (c2.getGreen() - c1.getGreen()) * ratio);
|
||||||
|
int b = (int) (c1.getBlue() + (c2.getBlue() - c1.getBlue()) * ratio);
|
||||||
|
return new Color(
|
||||||
|
Math.max(0, Math.min(255, r)),
|
||||||
|
Math.max(0, Math.min(255, g)),
|
||||||
|
Math.max(0, Math.min(255, b))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color parseHex(String hex) {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
return new Color(
|
||||||
|
Integer.parseInt(hex.substring(0, 2), 16),
|
||||||
|
Integer.parseInt(hex.substring(2, 4), 16),
|
||||||
|
Integer.parseInt(hex.substring(4, 6), 16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatHex(String hex, boolean useLegacyFormat) {
|
||||||
|
hex = hex.replace("#", "").toLowerCase();
|
||||||
|
if (useLegacyFormat) {
|
||||||
|
return "&#" + hex;
|
||||||
|
} else {
|
||||||
|
return "<#" + hex + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildDisplayName(String prefix, String nick, List<String> colors,
|
||||||
|
boolean gradientOnPrefix, boolean continuousGradient,
|
||||||
|
String prefixFormat, boolean useLegacyFormat) {
|
||||||
|
if (colors == null || colors.isEmpty()) {
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
return prefixFormat.replace("{prefix}", prefix) + nick;
|
||||||
|
}
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix == null || prefix.isEmpty()) {
|
||||||
|
return applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continuousGradient && gradientOnPrefix) {
|
||||||
|
String fullText = prefixFormat.replace("{prefix}", prefix) + nick;
|
||||||
|
return applyGradient(fullText, colors, useLegacyFormat);
|
||||||
|
} else if (gradientOnPrefix) {
|
||||||
|
String coloredPrefix = applyGradient(prefixFormat.replace("{prefix}", prefix), colors, useLegacyFormat);
|
||||||
|
String coloredNick = applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
return coloredPrefix + coloredNick;
|
||||||
|
} else {
|
||||||
|
String formattedPrefix = prefixFormat.replace("{prefix}", prefix);
|
||||||
|
return formattedPrefix + applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.loki.lochat.integrations;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.gradient.GradientModule;
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlaceholderAPIHook extends PlaceholderExpansion {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public PlaceholderAPIHook(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getIdentifier() {
|
||||||
|
return "lochat";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getAuthor() {
|
||||||
|
return "loki";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getVersion() {
|
||||||
|
return plugin.getDescription().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean persist() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
|
||||||
|
if (player == null) return "";
|
||||||
|
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
GradientModule gradient = plugin.getGradientModule();
|
||||||
|
|
||||||
|
return switch (params.toLowerCase()) {
|
||||||
|
case "muted" -> String.valueOf(plugin.getMuteManager().isMuted(uuid));
|
||||||
|
case "mute_time" -> {
|
||||||
|
String time = plugin.getMuteManager().getRemainingTime(uuid);
|
||||||
|
yield time != null ? time : "0";
|
||||||
|
}
|
||||||
|
case "mute_reason" -> {
|
||||||
|
String reason = plugin.getMuteManager().getReason(uuid);
|
||||||
|
yield reason != null ? reason : "";
|
||||||
|
}
|
||||||
|
case "ignored_count" -> String.valueOf(plugin.getIgnoreManager().getIgnoredCount(uuid));
|
||||||
|
case "global_enabled" -> String.valueOf(!plugin.getChatManager().isGlobalChatDisabled(uuid));
|
||||||
|
case "last_pm" -> {
|
||||||
|
UUID last = plugin.getPmManager().getLastConversation(uuid);
|
||||||
|
if (last == null) yield "";
|
||||||
|
var lastPlayer = Bukkit.getOfflinePlayer(last);
|
||||||
|
yield lastPlayer.getName() != null ? lastPlayer.getName() : "";
|
||||||
|
}
|
||||||
|
case "spy_enabled" -> String.valueOf(plugin.getSpyManager().isSpying(uuid));
|
||||||
|
|
||||||
|
// Gradient placeholders (совместимость с LoPreff)
|
||||||
|
case "gradient_full", "full" -> {
|
||||||
|
if (gradient == null || !gradient.isEnabled()) yield player.getName();
|
||||||
|
Player onlinePlayer = player.getPlayer();
|
||||||
|
if (onlinePlayer == null) yield player.getName();
|
||||||
|
yield gradient.getFormattedName(onlinePlayer);
|
||||||
|
}
|
||||||
|
case "gradient_name", "name" -> {
|
||||||
|
if (gradient == null || !gradient.isEnabled()) yield player.getName();
|
||||||
|
Player onlinePlayer = player.getPlayer();
|
||||||
|
if (onlinePlayer == null) yield player.getName();
|
||||||
|
yield gradient.getGradientNick(onlinePlayer);
|
||||||
|
}
|
||||||
|
case "gradient_prefix", "prefix" -> {
|
||||||
|
if (gradient == null || !gradient.isEnabled()) yield "";
|
||||||
|
Player onlinePlayer = player.getPlayer();
|
||||||
|
if (onlinePlayer == null) yield "";
|
||||||
|
yield gradient.getPrefix(onlinePlayer);
|
||||||
|
}
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/main/java/com/loki/lochat/listeners/ChatListener.java
Normal file
124
src/main/java/com/loki/lochat/listeners/ChatListener.java
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.loki.lochat.listeners;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.managers.AntiSpamManager;
|
||||||
|
import com.loki.lochat.managers.FilterManager;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
public class ChatListener implements Listener {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public ChatListener(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onChat(AsyncChatEvent event) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
String message = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer
|
||||||
|
.plainText().serialize(event.message());
|
||||||
|
|
||||||
|
// Обработка через RegionScheduler для Folia
|
||||||
|
player.getScheduler().run(plugin, task -> {
|
||||||
|
processChat(player, message);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processChat(Player player, String message) {
|
||||||
|
// Проверка мута
|
||||||
|
if (plugin.getMuteManager().isMuted(player.getUniqueId())) {
|
||||||
|
String remaining = plugin.getMuteManager().getRemainingTime(player.getUniqueId());
|
||||||
|
if (remaining.equals("навсегда")) {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("mute.permanent")));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("mute.you-muted", "{time}", remaining)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String globalSymbol = plugin.getConfigManager().getGlobalSymbol();
|
||||||
|
boolean isGlobal = message.startsWith(globalSymbol) && player.hasPermission("chat.global.use");
|
||||||
|
String chatType = isGlobal ? "global" : "local";
|
||||||
|
String processedMessage = isGlobal ? message.substring(globalSymbol.length()).trim() : message;
|
||||||
|
|
||||||
|
if (processedMessage.isEmpty()) return;
|
||||||
|
|
||||||
|
// Проверка кулдауна
|
||||||
|
int cooldown = isGlobal ? plugin.getConfigManager().getGlobalCooldown() : plugin.getConfigManager().getLocalCooldown();
|
||||||
|
if (cooldown > 0 && !player.hasPermission("chat.bypass.cooldown")) {
|
||||||
|
if (plugin.getCooldownManager().isOnCooldown(player.getUniqueId(), chatType, cooldown)) {
|
||||||
|
int remaining = plugin.getCooldownManager().getRemainingCooldown(player.getUniqueId(), chatType, cooldown);
|
||||||
|
player.sendMessage(ChatFormatter.parse(
|
||||||
|
plugin.getMessageConfig().get("cooldown.wait", "{time}", String.valueOf(remaining))
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка анти-спама
|
||||||
|
if (!player.hasPermission("chat.bypass.antispam")) {
|
||||||
|
AntiSpamManager.SpamResult spamResult = plugin.getAntiSpamManager().checkMessage(player.getUniqueId(), processedMessage);
|
||||||
|
switch (spamResult) {
|
||||||
|
case TOO_MANY_CAPS -> {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.caps")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case REPEAT_CHARS -> {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.repeat")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case SIMILAR_MESSAGE -> {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("antispam.similar")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка фильтра
|
||||||
|
if (!player.hasPermission("chat.bypass.filter")) {
|
||||||
|
FilterManager.FilterResult filterResult = plugin.getFilterManager().checkMessage(processedMessage);
|
||||||
|
switch (filterResult) {
|
||||||
|
case BLOCKED -> {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.blocked")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case WARNED -> {
|
||||||
|
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("filter.warned")));
|
||||||
|
// Продолжаем, но цензурим
|
||||||
|
processedMessage = plugin.getFilterManager().censorMessage(processedMessage);
|
||||||
|
}
|
||||||
|
case CENSORED -> processedMessage = plugin.getFilterManager().censorMessage(processedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем кулдаун
|
||||||
|
plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType);
|
||||||
|
|
||||||
|
// Отправка сообщения
|
||||||
|
if (isGlobal) {
|
||||||
|
plugin.getChatManager().sendGlobalMessage(player, processedMessage);
|
||||||
|
} else {
|
||||||
|
plugin.getChatManager().sendLocalMessage(player, processedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
plugin.getPmManager().removeConversation(player.getUniqueId());
|
||||||
|
plugin.getCooldownManager().removeCooldown(player.getUniqueId());
|
||||||
|
plugin.getAntiSpamManager().clearPlayer(player.getUniqueId());
|
||||||
|
plugin.getSpyManager().removeSpy(player.getUniqueId());
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/main/java/com/loki/lochat/managers/AntiSpamManager.java
Normal file
128
src/main/java/com/loki/lochat/managers/AntiSpamManager.java
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class AntiSpamManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Map<UUID, String> lastMessages = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, Long> lastMessageTime = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public AntiSpamManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SpamResult {
|
||||||
|
ALLOWED,
|
||||||
|
TOO_MANY_CAPS,
|
||||||
|
REPEAT_CHARS,
|
||||||
|
SIMILAR_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpamResult checkMessage(UUID player, String message) {
|
||||||
|
if (!plugin.getConfigManager().isAntiSpamEnabled()) {
|
||||||
|
return SpamResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка CAPS
|
||||||
|
if (hasTooManyCaps(message)) {
|
||||||
|
return SpamResult.TOO_MANY_CAPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка повторяющихся символов
|
||||||
|
if (hasRepeatChars(message)) {
|
||||||
|
return SpamResult.REPEAT_CHARS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка похожих сообщений
|
||||||
|
if (isSimilarMessage(player, message)) {
|
||||||
|
return SpamResult.SIMILAR_MESSAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем сообщение
|
||||||
|
lastMessages.put(player, message.toLowerCase());
|
||||||
|
lastMessageTime.put(player, System.currentTimeMillis());
|
||||||
|
|
||||||
|
return SpamResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasTooManyCaps(String message) {
|
||||||
|
if (message.length() < 5) return false;
|
||||||
|
|
||||||
|
int maxPercent = plugin.getConfigManager().getMaxCapsPercent();
|
||||||
|
long upperCount = message.chars().filter(Character::isUpperCase).count();
|
||||||
|
long letterCount = message.chars().filter(Character::isLetter).count();
|
||||||
|
|
||||||
|
if (letterCount == 0) return false;
|
||||||
|
|
||||||
|
int percent = (int) ((upperCount * 100) / letterCount);
|
||||||
|
return percent > maxPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasRepeatChars(String message) {
|
||||||
|
int maxRepeat = plugin.getConfigManager().getMaxRepeatChars();
|
||||||
|
int count = 1;
|
||||||
|
char lastChar = 0;
|
||||||
|
|
||||||
|
for (char c : message.toCharArray()) {
|
||||||
|
if (c == lastChar) {
|
||||||
|
count++;
|
||||||
|
if (count > maxRepeat) return true;
|
||||||
|
} else {
|
||||||
|
count = 1;
|
||||||
|
lastChar = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSimilarMessage(UUID player, String message) {
|
||||||
|
String lastMsg = lastMessages.get(player);
|
||||||
|
Long lastTime = lastMessageTime.get(player);
|
||||||
|
|
||||||
|
if (lastMsg == null || lastTime == null) return false;
|
||||||
|
|
||||||
|
int delay = plugin.getConfigManager().getSimilarMessageDelay();
|
||||||
|
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||||
|
|
||||||
|
if (elapsed > delay) return false;
|
||||||
|
|
||||||
|
// Проверка схожести (простая)
|
||||||
|
return message.toLowerCase().equals(lastMsg) ||
|
||||||
|
similarity(message.toLowerCase(), lastMsg) > 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double similarity(String s1, String s2) {
|
||||||
|
int maxLen = Math.max(s1.length(), s2.length());
|
||||||
|
if (maxLen == 0) return 1.0;
|
||||||
|
|
||||||
|
int distance = levenshteinDistance(s1, s2);
|
||||||
|
return 1.0 - ((double) distance / maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int levenshteinDistance(String s1, String s2) {
|
||||||
|
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
|
||||||
|
|
||||||
|
for (int i = 0; i <= s1.length(); i++) dp[i][0] = i;
|
||||||
|
for (int j = 0; j <= s2.length(); j++) dp[0][j] = j;
|
||||||
|
|
||||||
|
for (int i = 1; i <= s1.length(); i++) {
|
||||||
|
for (int j = 1; j <= s2.length(); j++) {
|
||||||
|
int cost = s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1;
|
||||||
|
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s1.length()][s2.length()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearPlayer(UUID player) {
|
||||||
|
lastMessages.remove(player);
|
||||||
|
lastMessageTime.remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/main/java/com/loki/lochat/managers/AutoMessageManager.java
Normal file
174
src/main/java/com/loki/lochat/managers/AutoMessageManager.java
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class AutoMessageManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Random random = new Random();
|
||||||
|
private final AtomicInteger currentIndex = new AtomicInteger(0);
|
||||||
|
private ScheduledTask task;
|
||||||
|
|
||||||
|
private Map<String, List<String>> messages;
|
||||||
|
private List<String> messageOrder;
|
||||||
|
private String prefix;
|
||||||
|
private String mode;
|
||||||
|
|
||||||
|
public AutoMessageManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessages() {
|
||||||
|
File file = new File(plugin.getDataFolder(), "automessages.yml");
|
||||||
|
if (!file.exists()) {
|
||||||
|
plugin.saveResource("automessages.yml", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||||
|
|
||||||
|
// Настройки
|
||||||
|
prefix = config.getString("settings.prefix", "&6[&eINFO&6]&r");
|
||||||
|
mode = config.getString("settings.mode", "sequential").toLowerCase();
|
||||||
|
|
||||||
|
// Загружаем сообщения
|
||||||
|
messages = new LinkedHashMap<>();
|
||||||
|
ConfigurationSection msgSection = config.getConfigurationSection("messages");
|
||||||
|
|
||||||
|
if (msgSection != null) {
|
||||||
|
for (String key : msgSection.getKeys(false)) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
if (msgSection.isList(key)) {
|
||||||
|
lines.addAll(msgSection.getStringList(key));
|
||||||
|
} else {
|
||||||
|
String single = msgSection.getString(key);
|
||||||
|
if (single != null) {
|
||||||
|
lines.add(single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lines.isEmpty()) {
|
||||||
|
messages.put(key, lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем порядок
|
||||||
|
messageOrder = new ArrayList<>();
|
||||||
|
|
||||||
|
if (mode.equals("custom")) {
|
||||||
|
String orderStr = config.getString("settings.order", "");
|
||||||
|
for (String item : orderStr.split(",")) {
|
||||||
|
String trimmed = item.trim();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
try {
|
||||||
|
int index = Integer.parseInt(trimmed) - 1;
|
||||||
|
List<String> keys = new ArrayList<>(messages.keySet());
|
||||||
|
if (index >= 0 && index < keys.size()) {
|
||||||
|
messageOrder.add(keys.get(index));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (messages.containsKey(trimmed)) {
|
||||||
|
messageOrder.add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageOrder.isEmpty()) {
|
||||||
|
messageOrder.addAll(messages.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("Загружено " + messages.size() + " автосообщений");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (!plugin.getConfigManager().isAutoMessagesEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages == null || messages.isEmpty()) {
|
||||||
|
plugin.getLogger().warning("Автосообщения не настроены!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int intervalSeconds = plugin.getConfigManager().getAutoMessagesInterval();
|
||||||
|
long intervalTicks = intervalSeconds * 20L;
|
||||||
|
|
||||||
|
task = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(
|
||||||
|
plugin,
|
||||||
|
scheduledTask -> broadcastNextMessage(),
|
||||||
|
intervalTicks,
|
||||||
|
intervalTicks
|
||||||
|
);
|
||||||
|
|
||||||
|
plugin.getLogger().info("Автосообщения запущены (интервал: " + intervalSeconds + " сек, режим: " + mode + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (task != null && !task.isCancelled()) {
|
||||||
|
task.cancel();
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastNextMessage() {
|
||||||
|
if (messageOrder.isEmpty() || Bukkit.getOnlinePlayers().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String messageKey;
|
||||||
|
|
||||||
|
if (mode.equals("random")) {
|
||||||
|
messageKey = messageOrder.get(random.nextInt(messageOrder.size()));
|
||||||
|
} else {
|
||||||
|
int index = currentIndex.getAndUpdate(i -> (i + 1) % messageOrder.size());
|
||||||
|
messageKey = messageOrder.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lines = messages.get(messageKey);
|
||||||
|
if (lines == null || lines.isEmpty()) return;
|
||||||
|
|
||||||
|
boolean isFirstLine = true;
|
||||||
|
for (String line : lines) {
|
||||||
|
// Заменяем смайлики через EmojiManager
|
||||||
|
String processed = ChatFormatter.replaceEmojis(line);
|
||||||
|
|
||||||
|
// Заменяем плейсхолдеры
|
||||||
|
processed = processed.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
|
||||||
|
|
||||||
|
// Добавляем префикс к первой непустой строке
|
||||||
|
Component formatted;
|
||||||
|
if (isFirstLine && !line.trim().isEmpty()) {
|
||||||
|
formatted = ChatFormatter.parse(prefix + " " + processed);
|
||||||
|
isFirstLine = false;
|
||||||
|
} else {
|
||||||
|
formatted = ChatFormatter.parse(processed);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var player : Bukkit.getOnlinePlayers()) {
|
||||||
|
player.sendMessage(formatted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
stop();
|
||||||
|
loadMessages();
|
||||||
|
currentIndex.set(0);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/main/java/com/loki/lochat/managers/ChatManager.java
Normal file
115
src/main/java/com/loki/lochat/managers/ChatManager.java
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import com.loki.lochat.utils.DistanceUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class ChatManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
public ChatManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendGlobalMessage(Player sender, String message) {
|
||||||
|
if (!plugin.getConfigManager().isGlobalEnabled()) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("global.disabled")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка упоминаний @ник
|
||||||
|
Set<Player> mentionedPlayers = new HashSet<>();
|
||||||
|
String processedMessage = plugin.getMentionManager().processMentions(message, mentionedPlayers);
|
||||||
|
|
||||||
|
String format = plugin.getConfigManager().getGlobalFormat();
|
||||||
|
String prefix = plugin.getConfigManager().getGlobalPrefix();
|
||||||
|
boolean hasColor = sender.hasPermission("chat.global.color");
|
||||||
|
|
||||||
|
// Отправка каждому игроку персонализированное сообщение
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (isGlobalChatDisabled(player.getUniqueId())) continue;
|
||||||
|
|
||||||
|
// Персонализируем сообщение для этого игрока (выделяем его ник)
|
||||||
|
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
|
||||||
|
|
||||||
|
Component formatted = ChatFormatter.formatGlobalMessage(format, prefix, sender, personalizedMsg, hasColor);
|
||||||
|
player.sendMessage(formatted);
|
||||||
|
|
||||||
|
// Проверяем упоминание ника без @ и уведомляем
|
||||||
|
plugin.getMentionManager().notifyIfMentioned(message, player, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомление упомянутых через @
|
||||||
|
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
|
||||||
|
|
||||||
|
plugin.getLogger().info("[G] " + sender.getName() + ": " + ChatFormatter.stripTags(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendLocalMessage(Player sender, String message) {
|
||||||
|
if (!plugin.getConfigManager().isLocalEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка упоминаний @ник
|
||||||
|
Set<Player> mentionedPlayers = new HashSet<>();
|
||||||
|
String processedMessage = plugin.getMentionManager().processMentions(message, mentionedPlayers);
|
||||||
|
|
||||||
|
int radius = plugin.getConfigManager().getLocalRadius();
|
||||||
|
String format = plugin.getConfigManager().getLocalFormat();
|
||||||
|
String prefix = plugin.getConfigManager().getLocalPrefix();
|
||||||
|
boolean hasColor = sender.hasPermission("chat.local.color");
|
||||||
|
|
||||||
|
boolean foundRecipient = false;
|
||||||
|
Set<Player> recipients = new HashSet<>();
|
||||||
|
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (DistanceUtil.isInRange(sender, player, radius)) {
|
||||||
|
// Персонализируем сообщение для этого игрока
|
||||||
|
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
|
||||||
|
|
||||||
|
Component formatted = ChatFormatter.formatLocalMessage(format, prefix, sender, personalizedMsg, hasColor);
|
||||||
|
player.sendMessage(formatted);
|
||||||
|
recipients.add(player);
|
||||||
|
|
||||||
|
// Проверяем упоминание ника без @
|
||||||
|
plugin.getMentionManager().notifyIfMentioned(message, player, sender);
|
||||||
|
|
||||||
|
if (!player.equals(sender)) {
|
||||||
|
foundRecipient = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомление упомянутых через @ (только тех кто в радиусе)
|
||||||
|
mentionedPlayers.retainAll(recipients);
|
||||||
|
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
|
||||||
|
|
||||||
|
if (!foundRecipient) {
|
||||||
|
sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("local.no-players")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean toggleGlobalChat(UUID player) {
|
||||||
|
if (globalChatDisabled.contains(player)) {
|
||||||
|
globalChatDisabled.remove(player);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
globalChatDisabled.add(player);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGlobalChatDisabled(UUID player) {
|
||||||
|
return globalChatDisabled.contains(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/com/loki/lochat/managers/CooldownManager.java
Normal file
45
src/main/java/com/loki/lochat/managers/CooldownManager.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class CooldownManager {
|
||||||
|
|
||||||
|
private final Map<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public boolean isOnCooldown(UUID player, String chatType, int cooldownSeconds) {
|
||||||
|
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
||||||
|
Long lastTime = cooldowns.get(player);
|
||||||
|
|
||||||
|
if (lastTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||||
|
return elapsed < cooldownSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemainingCooldown(UUID player, String chatType, int cooldownSeconds) {
|
||||||
|
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
||||||
|
Long lastTime = cooldowns.get(player);
|
||||||
|
|
||||||
|
if (lastTime == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||||
|
return Math.max(0, cooldownSeconds - (int) elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCooldown(UUID player, String chatType) {
|
||||||
|
Map<UUID, Long> cooldowns = chatType.equals("global") ? globalCooldowns : localCooldowns;
|
||||||
|
cooldowns.put(player, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCooldown(UUID player) {
|
||||||
|
globalCooldowns.remove(player);
|
||||||
|
localCooldowns.remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
100
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EmojiManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Map<String, String> emojis = new HashMap<>();
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean requirePermission;
|
||||||
|
|
||||||
|
public EmojiManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
File file = new File(plugin.getDataFolder(), "emojis.yml");
|
||||||
|
if (!file.exists()) {
|
||||||
|
plugin.saveResource("emojis.yml", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||||
|
emojis.clear();
|
||||||
|
|
||||||
|
// Настройки
|
||||||
|
enabled = config.getBoolean("settings.enabled", true);
|
||||||
|
requirePermission = config.getBoolean("settings.require-permission", false);
|
||||||
|
|
||||||
|
// Загружаем все категории
|
||||||
|
for (String category : config.getKeys(false)) {
|
||||||
|
if (category.equals("settings")) continue;
|
||||||
|
|
||||||
|
ConfigurationSection section = config.getConfigurationSection(category);
|
||||||
|
if (section != null) {
|
||||||
|
for (String key : section.getKeys(false)) {
|
||||||
|
String value = section.getString(key);
|
||||||
|
if (value != null) {
|
||||||
|
emojis.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("Загружено " + emojis.size() + " смайликов");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет смайлики в сообщении
|
||||||
|
*/
|
||||||
|
public String process(String message, Player player) {
|
||||||
|
if (!enabled) return message;
|
||||||
|
|
||||||
|
// Проверка права
|
||||||
|
if (requirePermission && player != null && !player.hasPermission("chat.emoji.use")) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = message;
|
||||||
|
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||||
|
result = result.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет смайлики без проверки прав (для автосообщений)
|
||||||
|
*/
|
||||||
|
public String process(String message) {
|
||||||
|
if (!enabled) return message;
|
||||||
|
|
||||||
|
String result = message;
|
||||||
|
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||||
|
result = result.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEmojis() {
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/main/java/com/loki/lochat/managers/FilterManager.java
Normal file
61
src/main/java/com/loki/lochat/managers/FilterManager.java
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class FilterManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
|
||||||
|
public FilterManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FilterResult {
|
||||||
|
ALLOWED,
|
||||||
|
CENSORED,
|
||||||
|
BLOCKED,
|
||||||
|
WARNED
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterResult checkMessage(String message) {
|
||||||
|
if (!plugin.getConfigManager().isFilterEnabled()) {
|
||||||
|
return FilterResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||||
|
String lowerMessage = message.toLowerCase();
|
||||||
|
|
||||||
|
for (String word : badWords) {
|
||||||
|
if (lowerMessage.contains(word.toLowerCase())) {
|
||||||
|
String action = plugin.getConfigManager().getFilterAction();
|
||||||
|
return switch (action) {
|
||||||
|
case "block" -> FilterResult.BLOCKED;
|
||||||
|
case "warn" -> FilterResult.WARNED;
|
||||||
|
default -> FilterResult.CENSORED;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilterResult.ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String censorMessage(String message) {
|
||||||
|
if (!plugin.getConfigManager().isFilterEnabled()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> badWords = plugin.getConfigManager().getFilterWords();
|
||||||
|
String replacement = plugin.getConfigManager().getFilterReplacement();
|
||||||
|
String result = message;
|
||||||
|
|
||||||
|
for (String word : badWords) {
|
||||||
|
Pattern pattern = Pattern.compile("(?i)" + Pattern.quote(word));
|
||||||
|
result = pattern.matcher(result).replaceAll(replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main/java/com/loki/lochat/managers/IgnoreManager.java
Normal file
97
src/main/java/com/loki/lochat/managers/IgnoreManager.java
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class IgnoreManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final File ignoreFile;
|
||||||
|
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public IgnoreManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
if (!ignoreFile.exists()) return;
|
||||||
|
|
||||||
|
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
|
||||||
|
for (String key : config.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(key);
|
||||||
|
List<String> ignoredList = config.getStringList(key);
|
||||||
|
Set<UUID> ignored = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
for (String ignoredStr : ignoredList) {
|
||||||
|
try {
|
||||||
|
ignored.add(UUID.fromString(ignoredStr));
|
||||||
|
} catch (Exception ignored2) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignored.isEmpty()) {
|
||||||
|
ignoreMap.put(uuid, ignored);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
|
||||||
|
List<String> ignoredList = new ArrayList<>();
|
||||||
|
for (UUID ignored : entry.getValue()) {
|
||||||
|
ignoredList.add(ignored.toString());
|
||||||
|
}
|
||||||
|
config.set(entry.getKey().toString(), ignoredList);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.save(ignoreFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoring(UUID player, UUID target) {
|
||||||
|
Set<UUID> ignored = ignoreMap.get(player);
|
||||||
|
return ignored != null && ignored.contains(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addIgnore(UUID player, UUID target) {
|
||||||
|
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
||||||
|
boolean added = ignored.add(target);
|
||||||
|
if (added) save();
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeIgnore(UUID player, UUID target) {
|
||||||
|
Set<UUID> ignored = ignoreMap.get(player);
|
||||||
|
if (ignored == null) return false;
|
||||||
|
boolean removed = ignored.remove(target);
|
||||||
|
if (removed) save();
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<UUID> getIgnoredPlayers(UUID player) {
|
||||||
|
return ignoreMap.getOrDefault(player, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIgnoredCount(UUID player) {
|
||||||
|
Set<UUID> ignored = ignoreMap.get(player);
|
||||||
|
return ignored == null ? 0 : ignored.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearIgnores(UUID player) {
|
||||||
|
ignoreMap.remove(player);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/main/java/com/loki/lochat/managers/MentionManager.java
Normal file
141
src/main/java/com/loki/lochat/managers/MentionManager.java
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MentionManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
|
||||||
|
|
||||||
|
// Хранит для какого игрока какие части сообщения выделить
|
||||||
|
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
|
||||||
|
|
||||||
|
public MentionManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обрабатывает упоминания в сообщении
|
||||||
|
* Возвращает сообщение с подсвеченными никами
|
||||||
|
*/
|
||||||
|
public String processMentions(String message, Set<Player> mentionedPlayers) {
|
||||||
|
if (!plugin.getConfigManager().isMentionsEnabled()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightedNicks.clear();
|
||||||
|
String highlight = plugin.getConfigManager().getMentionHighlight();
|
||||||
|
Matcher matcher = MENTION_PATTERN.matcher(message);
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String name = matcher.group(1);
|
||||||
|
Player mentioned = Bukkit.getPlayer(name);
|
||||||
|
|
||||||
|
if (mentioned != null && mentioned.isOnline()) {
|
||||||
|
mentionedPlayers.add(mentioned);
|
||||||
|
String replacement = highlight.replace("{player}", mentioned.getName());
|
||||||
|
matcher.appendReplacement(result, replacement);
|
||||||
|
|
||||||
|
// Запоминаем что этот игрок упомянут
|
||||||
|
highlightedNicks.computeIfAbsent(mentioned, k -> new HashSet<>()).add(mentioned.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создаёт персонализированное сообщение для игрока
|
||||||
|
* Если его ник упомянут — выделяет его особым образом
|
||||||
|
*/
|
||||||
|
public String getPersonalizedMessage(String message, Player viewer) {
|
||||||
|
if (!plugin.getConfigManager().isMentionsEnabled()) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
String viewerName = viewer.getName();
|
||||||
|
String result = message;
|
||||||
|
|
||||||
|
// Проверяем упоминание через @ник
|
||||||
|
String highlight = plugin.getConfigManager().getMentionHighlight();
|
||||||
|
String highlightedName = highlight.replace("{player}", viewerName);
|
||||||
|
|
||||||
|
// Если в сообщении есть выделенный ник этого игрока — добавляем дополнительное выделение
|
||||||
|
if (result.contains(highlightedName)) {
|
||||||
|
// Уже выделено через processMentions, добавляем эффект
|
||||||
|
String extraHighlight = "<bold>" + highlightedName + "</bold>";
|
||||||
|
result = result.replace(highlightedName, extraHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Также проверяем просто ник в сообщении (без @)
|
||||||
|
// Регистронезависимый поиск
|
||||||
|
Pattern nickPattern = Pattern.compile("(?i)\\b" + Pattern.quote(viewerName) + "\\b");
|
||||||
|
Matcher nickMatcher = nickPattern.matcher(result);
|
||||||
|
|
||||||
|
if (nickMatcher.find()) {
|
||||||
|
// Ник найден в сообщении — выделяем для этого игрока
|
||||||
|
String selfHighlight = plugin.getConfigManager().getSelfMentionHighlight();
|
||||||
|
result = nickMatcher.replaceAll(selfHighlight.replace("{player}", viewerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, упомянут ли игрок в сообщении (по нику без @)
|
||||||
|
*/
|
||||||
|
public boolean isPlayerMentioned(String message, Player player) {
|
||||||
|
String name = player.getName();
|
||||||
|
Pattern pattern = Pattern.compile("(?i)\\b" + Pattern.quote(name) + "\\b");
|
||||||
|
return pattern.matcher(message).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Уведомляет упомянутых игроков звуком
|
||||||
|
*/
|
||||||
|
public void notifyMentioned(Set<Player> players) {
|
||||||
|
if (!plugin.getConfigManager().isMentionSoundEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String soundName = plugin.getConfigManager().getMentionSoundType();
|
||||||
|
Sound sound;
|
||||||
|
try {
|
||||||
|
sound = Sound.valueOf(soundName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Player player : players) {
|
||||||
|
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Уведомляет игрока если его ник упомянут (без @)
|
||||||
|
*/
|
||||||
|
public void notifyIfMentioned(String message, Player viewer, Player sender) {
|
||||||
|
if (viewer.equals(sender)) return;
|
||||||
|
if (!plugin.getConfigManager().isMentionSoundEnabled()) return;
|
||||||
|
|
||||||
|
if (isPlayerMentioned(message, viewer)) {
|
||||||
|
String soundName = plugin.getConfigManager().getMentionSoundType();
|
||||||
|
try {
|
||||||
|
Sound sound = Sound.valueOf(soundName);
|
||||||
|
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/main/java/com/loki/lochat/managers/MuteManager.java
Normal file
136
src/main/java/com/loki/lochat/managers/MuteManager.java
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class MuteManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final File muteFile;
|
||||||
|
private final Map<UUID, Long> mutedPlayers = new ConcurrentHashMap<>();
|
||||||
|
private final Map<UUID, String> muteReasons = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public MuteManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.muteFile = new File(plugin.getDataFolder(), "mutes.yml");
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
if (!muteFile.exists()) return;
|
||||||
|
|
||||||
|
YamlConfiguration config = YamlConfiguration.loadConfiguration(muteFile);
|
||||||
|
for (String key : config.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(key);
|
||||||
|
long muteEnd = config.getLong(key + ".end");
|
||||||
|
String reason = config.getString(key + ".reason");
|
||||||
|
|
||||||
|
// Пропускаем истёкшие муты
|
||||||
|
if (muteEnd != 0 && System.currentTimeMillis() > muteEnd) continue;
|
||||||
|
|
||||||
|
mutedPlayers.put(uuid, muteEnd);
|
||||||
|
if (reason != null) muteReasons.put(uuid, reason);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
YamlConfiguration config = new YamlConfiguration();
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, Long> entry : mutedPlayers.entrySet()) {
|
||||||
|
String key = entry.getKey().toString();
|
||||||
|
config.set(key + ".end", entry.getValue());
|
||||||
|
String reason = muteReasons.get(entry.getKey());
|
||||||
|
if (reason != null) config.set(key + ".reason", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.save(muteFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Не удалось сохранить муты: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMuted(UUID player) {
|
||||||
|
Long muteEnd = mutedPlayers.get(player);
|
||||||
|
if (muteEnd == null) return false;
|
||||||
|
|
||||||
|
if (muteEnd == 0) return true;
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > muteEnd) {
|
||||||
|
mutedPlayers.remove(player);
|
||||||
|
muteReasons.remove(player);
|
||||||
|
save();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mute(UUID player, long durationSeconds, String reason) {
|
||||||
|
long muteEnd = durationSeconds <= 0 ? 0 : System.currentTimeMillis() + (durationSeconds * 1000);
|
||||||
|
mutedPlayers.put(player, muteEnd);
|
||||||
|
if (reason != null && !reason.isEmpty()) {
|
||||||
|
muteReasons.put(player, reason);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unmute(UUID player) {
|
||||||
|
mutedPlayers.remove(player);
|
||||||
|
muteReasons.remove(player);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemainingTime(UUID player) {
|
||||||
|
Long muteEnd = mutedPlayers.get(player);
|
||||||
|
if (muteEnd == null) return null;
|
||||||
|
if (muteEnd == 0) return "навсегда";
|
||||||
|
|
||||||
|
long remaining = (muteEnd - System.currentTimeMillis()) / 1000;
|
||||||
|
if (remaining <= 0) return null;
|
||||||
|
|
||||||
|
return formatTime(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason(UUID player) {
|
||||||
|
return muteReasons.get(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlreadyMuted(UUID player) {
|
||||||
|
return mutedPlayers.containsKey(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatTime(long seconds) {
|
||||||
|
if (seconds < 60) return seconds + " сек";
|
||||||
|
if (seconds < 3600) return (seconds / 60) + " мин";
|
||||||
|
if (seconds < 86400) return (seconds / 3600) + " ч";
|
||||||
|
return (seconds / 86400) + " д";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long parseTime(String time) {
|
||||||
|
if (time == null || time.isEmpty()) return -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
char unit = time.charAt(time.length() - 1);
|
||||||
|
long value = Long.parseLong(time.substring(0, time.length() - 1));
|
||||||
|
|
||||||
|
return switch (Character.toLowerCase(unit)) {
|
||||||
|
case 's' -> value;
|
||||||
|
case 'm' -> value * 60;
|
||||||
|
case 'h' -> value * 3600;
|
||||||
|
case 'd' -> value * 86400;
|
||||||
|
default -> Long.parseLong(time);
|
||||||
|
};
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/com/loki/lochat/managers/PMManager.java
Normal file
27
src/main/java/com/loki/lochat/managers/PMManager.java
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class PMManager {
|
||||||
|
|
||||||
|
// UUID игрока -> UUID последнего собеседника
|
||||||
|
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void setLastConversation(UUID player, UUID target) {
|
||||||
|
lastConversation.put(player, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getLastConversation(UUID player) {
|
||||||
|
return lastConversation.get(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeConversation(UUID player) {
|
||||||
|
lastConversation.remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasConversation(UUID player) {
|
||||||
|
return lastConversation.containsKey(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/com/loki/lochat/managers/SpyManager.java
Normal file
52
src/main/java/com/loki/lochat/managers/SpyManager.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import com.loki.lochat.utils.ChatFormatter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class SpyManager {
|
||||||
|
|
||||||
|
private final LoChat plugin;
|
||||||
|
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
public SpyManager(LoChat plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean toggleSpy(UUID player) {
|
||||||
|
if (spyEnabled.contains(player)) {
|
||||||
|
spyEnabled.remove(player);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
spyEnabled.add(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSpying(UUID player) {
|
||||||
|
return spyEnabled.contains(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastPM(Player sender, Player receiver, String message) {
|
||||||
|
String format = plugin.getMessageConfig().get("spy.format");
|
||||||
|
|
||||||
|
for (UUID spyUuid : spyEnabled) {
|
||||||
|
Player spy = Bukkit.getPlayer(spyUuid);
|
||||||
|
if (spy != null && spy.isOnline() && !spy.equals(sender) && !spy.equals(receiver)) {
|
||||||
|
spy.sendMessage(ChatFormatter.parse(format
|
||||||
|
.replace("{sender}", sender.getName())
|
||||||
|
.replace("{receiver}", receiver.getName())
|
||||||
|
.replace("{message}", message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSpy(UUID player) {
|
||||||
|
spyEnabled.remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
228
src/main/java/com/loki/lochat/utils/ChatFormatter.java
Normal file
228
src/main/java/com/loki/lochat/utils/ChatFormatter.java
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
package com.loki.lochat.utils;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class ChatFormatter {
|
||||||
|
|
||||||
|
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
|
||||||
|
|
||||||
|
// Паттерны для цветов
|
||||||
|
private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})");
|
||||||
|
private static final Pattern LEGACY_COLOR_PATTERN = Pattern.compile("&([0-9a-fk-orA-FK-OR])");
|
||||||
|
|
||||||
|
private ChatFormatter() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конвертирует все форматы цветов в MiniMessage
|
||||||
|
* Поддерживает: &коды, &#HEX, MiniMessage теги
|
||||||
|
*/
|
||||||
|
public static String convertAllColors(String message) {
|
||||||
|
String result = message;
|
||||||
|
|
||||||
|
// 1. Конвертируем &#RRGGBB -> <#RRGGBB>
|
||||||
|
result = convertHexColors(result);
|
||||||
|
|
||||||
|
// 2. Конвертируем &коды -> MiniMessage теги
|
||||||
|
result = convertLegacyColors(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конвертирует &#RRGGBB в MiniMessage формат <#RRGGBB>
|
||||||
|
*/
|
||||||
|
public static String convertHexColors(String message) {
|
||||||
|
Matcher matcher = HEX_PATTERN.matcher(message);
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
while (matcher.find()) {
|
||||||
|
matcher.appendReplacement(result, "<#" + matcher.group(1) + ">");
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конвертирует &коды в MiniMessage теги
|
||||||
|
* &a -> <green>, &b -> <aqua>, &l -> <bold>, etc.
|
||||||
|
*/
|
||||||
|
public static String convertLegacyColors(String message) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
Matcher matcher = LEGACY_COLOR_PATTERN.matcher(message);
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String code = matcher.group(1).toLowerCase();
|
||||||
|
String replacement = switch (code) {
|
||||||
|
case "0" -> "<black>";
|
||||||
|
case "1" -> "<dark_blue>";
|
||||||
|
case "2" -> "<dark_green>";
|
||||||
|
case "3" -> "<dark_aqua>";
|
||||||
|
case "4" -> "<dark_red>";
|
||||||
|
case "5" -> "<dark_purple>";
|
||||||
|
case "6" -> "<gold>";
|
||||||
|
case "7" -> "<gray>";
|
||||||
|
case "8" -> "<dark_gray>";
|
||||||
|
case "9" -> "<blue>";
|
||||||
|
case "a" -> "<green>";
|
||||||
|
case "b" -> "<aqua>";
|
||||||
|
case "c" -> "<red>";
|
||||||
|
case "d" -> "<light_purple>";
|
||||||
|
case "e" -> "<yellow>";
|
||||||
|
case "f" -> "<white>";
|
||||||
|
case "k" -> "<obfuscated>";
|
||||||
|
case "l" -> "<bold>";
|
||||||
|
case "m" -> "<strikethrough>";
|
||||||
|
case "n" -> "<underlined>";
|
||||||
|
case "o" -> "<italic>";
|
||||||
|
case "r" -> "<reset>";
|
||||||
|
default -> "&" + code;
|
||||||
|
};
|
||||||
|
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||||
|
}
|
||||||
|
matcher.appendTail(result);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет смайлики в сообщении
|
||||||
|
*/
|
||||||
|
public static String replaceEmojis(String message, Player player) {
|
||||||
|
LoChat plugin = LoChat.getInstance();
|
||||||
|
if (plugin != null && plugin.getEmojiManager() != null) {
|
||||||
|
return plugin.getEmojiManager().process(message, player);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет смайлики без проверки прав
|
||||||
|
*/
|
||||||
|
public static String replaceEmojis(String message) {
|
||||||
|
LoChat plugin = LoChat.getInstance();
|
||||||
|
if (plugin != null && plugin.getEmojiManager() != null) {
|
||||||
|
return plugin.getEmojiManager().process(message);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use replaceEmojis(String) instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static String replaceEmojis(String message, Map<String, String> emojis) {
|
||||||
|
return replaceEmojis(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Полная обработка сообщения игрока
|
||||||
|
* Цвета + смайлики
|
||||||
|
*/
|
||||||
|
public static String processPlayerMessage(String message, Player player, boolean hasColorPermission) {
|
||||||
|
String result = message;
|
||||||
|
|
||||||
|
// Заменяем смайлики
|
||||||
|
result = replaceEmojis(result, player);
|
||||||
|
|
||||||
|
// Если есть право на цвета - конвертируем
|
||||||
|
if (hasColorPermission) {
|
||||||
|
result = convertAllColors(result);
|
||||||
|
} else {
|
||||||
|
// Убираем цветовые коды
|
||||||
|
result = stripLegacyColors(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Убирает &коды из сообщения
|
||||||
|
*/
|
||||||
|
public static String stripLegacyColors(String message) {
|
||||||
|
return LEGACY_COLOR_PATTERN.matcher(message).replaceAll("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component parse(String message) {
|
||||||
|
return MINI_MESSAGE.deserialize(convertAllColors(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component parse(String message, String... replacements) {
|
||||||
|
String result = message;
|
||||||
|
for (int i = 0; i < replacements.length - 1; i += 2) {
|
||||||
|
result = result.replace(replacements[i], replacements[i + 1]);
|
||||||
|
}
|
||||||
|
return MINI_MESSAGE.deserialize(convertAllColors(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toPlain(Component component) {
|
||||||
|
return PlainTextComponentSerializer.plainText().serialize(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stripTags(String message) {
|
||||||
|
// Убираем MiniMessage теги и legacy коды
|
||||||
|
String result = MINI_MESSAGE.stripTags(message);
|
||||||
|
result = stripLegacyColors(result);
|
||||||
|
result = HEX_PATTERN.matcher(result).replaceAll("");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматирует глобальное сообщение с поддержкой LoPreff
|
||||||
|
*/
|
||||||
|
public static Component formatGlobalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
|
||||||
|
String playerDisplay = getPlayerDisplay(player);
|
||||||
|
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
|
||||||
|
|
||||||
|
String formatted = format
|
||||||
|
.replace("<prefix>", prefix)
|
||||||
|
.replace("<player>", playerDisplay)
|
||||||
|
.replace("<message>", finalMessage);
|
||||||
|
|
||||||
|
return parse(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматирует локальное сообщение с поддержкой LoPreff
|
||||||
|
*/
|
||||||
|
public static Component formatLocalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) {
|
||||||
|
String playerDisplay = getPlayerDisplay(player);
|
||||||
|
String finalMessage = processPlayerMessage(message, player, hasColorPermission);
|
||||||
|
|
||||||
|
String formatted = prefix + " " + format
|
||||||
|
.replace("<player>", playerDisplay)
|
||||||
|
.replace("<message>", finalMessage);
|
||||||
|
|
||||||
|
return parse(formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getPlayerDisplay(Player player) {
|
||||||
|
LoChat plugin = LoChat.getInstance();
|
||||||
|
if (plugin != null && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) {
|
||||||
|
return plugin.getGradientModule().getFormattedName(player);
|
||||||
|
}
|
||||||
|
return player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component formatPmSent(String format, String targetName, String message) {
|
||||||
|
return parse(format
|
||||||
|
.replace("{player}", targetName)
|
||||||
|
.replace("{message}", message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component formatPmReceived(String format, String senderName, String message) {
|
||||||
|
return parse(format
|
||||||
|
.replace("{player}", senderName)
|
||||||
|
.replace("{message}", message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component formatAnnouncement(String format, String message) {
|
||||||
|
return parse(format.replace("<message>", message));
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/main/java/com/loki/lochat/utils/DistanceUtil.java
Normal file
33
src/main/java/com/loki/lochat/utils/DistanceUtil.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.loki.lochat.utils;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class DistanceUtil {
|
||||||
|
|
||||||
|
private DistanceUtil() {}
|
||||||
|
|
||||||
|
public static boolean isInRange(Player player1, Player player2, int radius) {
|
||||||
|
// Проверка на один мир
|
||||||
|
if (!player1.getWorld().equals(player2.getWorld())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location loc1 = player1.getLocation();
|
||||||
|
Location loc2 = player2.getLocation();
|
||||||
|
|
||||||
|
// Быстрая проверка по квадрату (без sqrt)
|
||||||
|
double dx = loc1.getX() - loc2.getX();
|
||||||
|
double dz = loc1.getZ() - loc2.getZ();
|
||||||
|
double radiusSquared = (double) radius * radius;
|
||||||
|
|
||||||
|
return (dx * dx + dz * dz) <= radiusSquared;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getDistance(Player player1, Player player2) {
|
||||||
|
if (!player1.getWorld().equals(player2.getWorld())) {
|
||||||
|
return Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return player1.getLocation().distance(player2.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/resources/automessages.yml
Normal file
74
src/main/resources/automessages.yml
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# LoChat - Автосообщения
|
||||||
|
# Автор: loki
|
||||||
|
#
|
||||||
|
# Смайлики настраиваются в emojis.yml
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# НАСТРОЙКИ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
settings:
|
||||||
|
enabled: true
|
||||||
|
interval: 300 # Интервал между сообщениями (секунды)
|
||||||
|
prefix: "&6[&eINFO&6]&r"
|
||||||
|
|
||||||
|
# Режим отправки:
|
||||||
|
# - "sequential" — по порядку (1, 2, 3...)
|
||||||
|
# - "random" — случайный порядок
|
||||||
|
# - "custom" — свой порядок (указать в order)
|
||||||
|
mode: "sequential"
|
||||||
|
|
||||||
|
# Свой порядок (для mode: custom)
|
||||||
|
# Можно указывать названия или номера
|
||||||
|
# Пример: "welcome, rules, 3, donate, tips"
|
||||||
|
order: "welcome, motd, rules, donate, tips, discord"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# СООБЩЕНИЯ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
messages:
|
||||||
|
# Приветствие (однострочное)
|
||||||
|
welcome: "&aДобро пожаловать на сервер! &e:star:"
|
||||||
|
|
||||||
|
# MOTD (многострочное)
|
||||||
|
motd:
|
||||||
|
- "&6╔══════════════════════════════════╗"
|
||||||
|
- "&6║ &e:star: &fДобро пожаловать! &e:star: &6║"
|
||||||
|
- "&6║ &7Онлайн: &a%online% &7игроков &6║"
|
||||||
|
- "&6║ &7Сайт: &bexample.com &6║"
|
||||||
|
- "&6╚══════════════════════════════════╝"
|
||||||
|
|
||||||
|
# Правила
|
||||||
|
rules:
|
||||||
|
- "&c&l:warning: ПРАВИЛА СЕРВЕРА :warning:"
|
||||||
|
- "&71. &fНе гриферить"
|
||||||
|
- "&72. &fНе читерить"
|
||||||
|
- "&73. &fУважать игроков"
|
||||||
|
- "&aПолные правила: &e/rules"
|
||||||
|
|
||||||
|
# Донат
|
||||||
|
donate:
|
||||||
|
- "&#FFD700&l:gem: ДОНАТ :gem:"
|
||||||
|
- "&7Поддержи сервер и получи"
|
||||||
|
- "&7крутые привилегии!"
|
||||||
|
- "&eПодробнее: &a/donate"
|
||||||
|
|
||||||
|
# Советы
|
||||||
|
tips:
|
||||||
|
- "&b:zap: &fСовет дня:"
|
||||||
|
- "&7Используй &e/sethome &7чтобы"
|
||||||
|
- "&7сохранить свою локацию!"
|
||||||
|
|
||||||
|
# Discord
|
||||||
|
discord: "&9:game: &fНаш Discord: &bdiscord.gg/example"
|
||||||
|
|
||||||
|
# Голосование
|
||||||
|
vote: "&a:thumbsup: &fГолосуй за сервер: &e/vote &7и получи награды!"
|
||||||
|
|
||||||
|
# Градиентное сообщение
|
||||||
|
gradient:
|
||||||
|
- "<gradient:#FF6B6B:#4ECDC4>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</gradient>"
|
||||||
|
- ""
|
||||||
|
- " &f:fire: &eСпециальное событие!"
|
||||||
|
- " &7Двойной опыт до конца недели!"
|
||||||
|
- ""
|
||||||
|
- "<gradient:#4ECDC4:#FF6B6B>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</gradient>"
|
||||||
69
src/main/resources/config.yml
Normal file
69
src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# LoChat Configuration
|
||||||
|
# Author: loki
|
||||||
|
|
||||||
|
chat:
|
||||||
|
global:
|
||||||
|
enabled: true
|
||||||
|
prefix: "[G]"
|
||||||
|
format: "<prefix><player>: <message>"
|
||||||
|
symbol: "!"
|
||||||
|
cooldown: 3
|
||||||
|
|
||||||
|
local:
|
||||||
|
enabled: true
|
||||||
|
radius: 100
|
||||||
|
prefix: "[L]"
|
||||||
|
format: "<player>: <message>"
|
||||||
|
cooldown: 1
|
||||||
|
|
||||||
|
pm:
|
||||||
|
enabled: true
|
||||||
|
log: false
|
||||||
|
sound: true
|
||||||
|
sound-type: "ENTITY_EXPERIENCE_ORB_PICKUP"
|
||||||
|
|
||||||
|
# Интеграция с GradientNick (LoPreff)
|
||||||
|
# Использует PlaceholderAPI плейсхолдеры
|
||||||
|
lopreff:
|
||||||
|
enabled: true
|
||||||
|
# Использовать градиентный ник в чате
|
||||||
|
use-gradient-name: true
|
||||||
|
# Использовать кастомный префикс
|
||||||
|
use-custom-prefix: true
|
||||||
|
|
||||||
|
# Упоминания @ник
|
||||||
|
mentions:
|
||||||
|
enabled: true
|
||||||
|
sound: true
|
||||||
|
sound-type: "BLOCK_NOTE_BLOCK_PLING"
|
||||||
|
# Выделение при @ник
|
||||||
|
highlight: "<yellow>@{player}</yellow>"
|
||||||
|
# Выделение когда твой ник написали (без @)
|
||||||
|
self-highlight: "<gold><bold>{player}</bold></gold>"
|
||||||
|
|
||||||
|
# Фильтр мата
|
||||||
|
filter:
|
||||||
|
enabled: true
|
||||||
|
words:
|
||||||
|
- "хуй"
|
||||||
|
- "пизд"
|
||||||
|
- "блять"
|
||||||
|
- "ебать"
|
||||||
|
- "сука"
|
||||||
|
action: "censor" # censor/block/warn
|
||||||
|
replacement: "***"
|
||||||
|
|
||||||
|
# Анти-спам
|
||||||
|
antispam:
|
||||||
|
enabled: true
|
||||||
|
max-caps-percent: 70
|
||||||
|
max-repeat-chars: 5
|
||||||
|
similar-message-delay: 30
|
||||||
|
|
||||||
|
automessages:
|
||||||
|
enabled: true
|
||||||
|
interval: 300
|
||||||
|
random: false
|
||||||
|
|
||||||
|
formats:
|
||||||
|
announcement: "<gold>[ОБЪЯВЛЕНИЕ]</gold> <message>"
|
||||||
385
src/main/resources/emojis.yml
Normal file
385
src/main/resources/emojis.yml
Normal file
|
|
@ -0,0 +1,385 @@
|
||||||
|
# LoChat - Смайлики и стикеры
|
||||||
|
# Автор: loki
|
||||||
|
#
|
||||||
|
# Формат: "триггер": "замена"
|
||||||
|
# Игроки пишут триггер, он заменяется на символ
|
||||||
|
#
|
||||||
|
# Права:
|
||||||
|
# - chat.emoji.use — использование смайликов
|
||||||
|
# - chat.emoji.* — все смайлики
|
||||||
|
# - chat.emoji.hearts — только категория hearts
|
||||||
|
# - chat.emoji.custom — кастомные смайлики
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# НАСТРОЙКИ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
settings:
|
||||||
|
enabled: true
|
||||||
|
# Требовать право chat.emoji.use
|
||||||
|
require-permission: false
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ЛИЦА И ЭМОЦИИ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
faces:
|
||||||
|
":)": "☺"
|
||||||
|
":D": "😃"
|
||||||
|
":(": "☹"
|
||||||
|
";)": "😉"
|
||||||
|
":P": "😛"
|
||||||
|
":O": "😮"
|
||||||
|
"XD": "😆"
|
||||||
|
":/": "😕"
|
||||||
|
":*": "😘"
|
||||||
|
"^_^": "😊"
|
||||||
|
"-_-": "😑"
|
||||||
|
">:(": "😠"
|
||||||
|
":3": "😺"
|
||||||
|
":'(": "😢"
|
||||||
|
":')": "😂"
|
||||||
|
"o_o": "😳"
|
||||||
|
"O_O": "😱"
|
||||||
|
":thinking:": "🤔"
|
||||||
|
":cool:": "😎"
|
||||||
|
":angry:": "😡"
|
||||||
|
":sad:": "😢"
|
||||||
|
":happy:": "😊"
|
||||||
|
":laugh:": "😂"
|
||||||
|
":cry:": "😭"
|
||||||
|
":sleep:": "😴"
|
||||||
|
":sick:": "🤢"
|
||||||
|
":dead:": "💀"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# СЕРДЦА И ЛЮБОВЬ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
hearts:
|
||||||
|
"<3": "❤"
|
||||||
|
":heart:": "❤"
|
||||||
|
":red_heart:": "❤"
|
||||||
|
":blue_heart:": "💙"
|
||||||
|
":green_heart:": "💚"
|
||||||
|
":yellow_heart:": "💛"
|
||||||
|
":purple_heart:": "💜"
|
||||||
|
":black_heart:": "🖤"
|
||||||
|
":white_heart:": "🤍"
|
||||||
|
":orange_heart:": "🧡"
|
||||||
|
":broken_heart:": "💔"
|
||||||
|
":two_hearts:": "💕"
|
||||||
|
":sparkling_heart:": "💖"
|
||||||
|
":heartbeat:": "💓"
|
||||||
|
":heartpulse:": "💗"
|
||||||
|
":love:": "💕"
|
||||||
|
":kiss:": "💋"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ЖЕСТЫ И РУКИ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
gestures:
|
||||||
|
":thumbsup:": "👍"
|
||||||
|
":+1:": "👍"
|
||||||
|
":thumbsdown:": "👎"
|
||||||
|
":-1:": "👎"
|
||||||
|
":clap:": "👏"
|
||||||
|
":wave:": "👋"
|
||||||
|
":ok:": "👌"
|
||||||
|
":ok_hand:": "👌"
|
||||||
|
":fist:": "✊"
|
||||||
|
":punch:": "👊"
|
||||||
|
":peace:": "✌"
|
||||||
|
":v:": "✌"
|
||||||
|
":muscle:": "💪"
|
||||||
|
":strong:": "💪"
|
||||||
|
":pray:": "🙏"
|
||||||
|
":hands:": "🙌"
|
||||||
|
":point_up:": "☝"
|
||||||
|
":point_down:": "👇"
|
||||||
|
":point_left:": "👈"
|
||||||
|
":point_right:": "👉"
|
||||||
|
":middle_finger:": "🖕"
|
||||||
|
":call:": "🤙"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ПРИРОДА И ПОГОДА
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
nature:
|
||||||
|
":fire:": "🔥"
|
||||||
|
":flame:": "🔥"
|
||||||
|
":zap:": "⚡"
|
||||||
|
":lightning:": "⚡"
|
||||||
|
":boom:": "💥"
|
||||||
|
":explosion:": "💥"
|
||||||
|
":sparkles:": "✨"
|
||||||
|
":stars:": "✨"
|
||||||
|
":snow:": "❄"
|
||||||
|
":snowflake:": "❄"
|
||||||
|
":cloud:": "☁"
|
||||||
|
":sun:": "☀"
|
||||||
|
":sunny:": "☀"
|
||||||
|
":moon:": "🌙"
|
||||||
|
":crescent_moon:": "🌙"
|
||||||
|
":full_moon:": "🌕"
|
||||||
|
":rainbow:": "🌈"
|
||||||
|
":water:": "💧"
|
||||||
|
":drop:": "💧"
|
||||||
|
":ocean:": "🌊"
|
||||||
|
":wind:": "💨"
|
||||||
|
":tornado:": "🌪"
|
||||||
|
":leaf:": "🍃"
|
||||||
|
":tree:": "🌳"
|
||||||
|
":flower:": "🌸"
|
||||||
|
":rose:": "🌹"
|
||||||
|
":cactus:": "🌵"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ЗВЁЗДЫ И СИМВОЛЫ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
symbols:
|
||||||
|
":star:": "⭐"
|
||||||
|
":star2:": "🌟"
|
||||||
|
":glowing_star:": "🌟"
|
||||||
|
":dizzy:": "💫"
|
||||||
|
":sparkle:": "❇"
|
||||||
|
":skull:": "💀"
|
||||||
|
":ghost:": "👻"
|
||||||
|
":alien:": "👽"
|
||||||
|
":robot:": "🤖"
|
||||||
|
":poop:": "💩"
|
||||||
|
":100:": "💯"
|
||||||
|
":gem:": "💎"
|
||||||
|
":diamond:": "💎"
|
||||||
|
":money:": "💰"
|
||||||
|
":dollar:": "💵"
|
||||||
|
":coin:": "🪙"
|
||||||
|
":gift:": "🎁"
|
||||||
|
":balloon:": "🎈"
|
||||||
|
":party:": "🎉"
|
||||||
|
":tada:": "🎉"
|
||||||
|
":confetti:": "🎊"
|
||||||
|
":trophy:": "🏆"
|
||||||
|
":medal:": "🏅"
|
||||||
|
":crown:": "👑"
|
||||||
|
":king:": "👑"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# МАЙНКРАФТ ТЕМАТИКА
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
minecraft:
|
||||||
|
":sword:": "⚔"
|
||||||
|
":crossed_swords:": "⚔"
|
||||||
|
":shield:": "🛡"
|
||||||
|
":axe:": "🪓"
|
||||||
|
":pickaxe:": "⛏"
|
||||||
|
":bow:": "🏹"
|
||||||
|
":arrow:": "➤"
|
||||||
|
":bomb:": "💣"
|
||||||
|
":tnt:": "🧨"
|
||||||
|
":potion:": "🧪"
|
||||||
|
":ender:": "🟣"
|
||||||
|
":nether:": "🔴"
|
||||||
|
":emerald:": "💚"
|
||||||
|
":gold:": "🟡"
|
||||||
|
":iron:": "⬜"
|
||||||
|
":coal:": "⬛"
|
||||||
|
":redstone:": "🔴"
|
||||||
|
":lapis:": "🔵"
|
||||||
|
":exp:": "🟢"
|
||||||
|
":xp:": "🟢"
|
||||||
|
":heart_mc:": "❤"
|
||||||
|
":hunger:": "🍖"
|
||||||
|
":armor:": "🛡"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ЖИВОТНЫЕ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
animals:
|
||||||
|
":cat:": "🐱"
|
||||||
|
":dog:": "🐶"
|
||||||
|
":wolf:": "🐺"
|
||||||
|
":fox:": "🦊"
|
||||||
|
":pig:": "🐷"
|
||||||
|
":cow:": "🐮"
|
||||||
|
":sheep:": "🐑"
|
||||||
|
":chicken:": "🐔"
|
||||||
|
":dragon:": "🐉"
|
||||||
|
":bee:": "🐝"
|
||||||
|
":fish:": "🐟"
|
||||||
|
":spider:": "🕷"
|
||||||
|
":bat:": "🦇"
|
||||||
|
":rabbit:": "🐰"
|
||||||
|
":bear:": "🐻"
|
||||||
|
":panda:": "🐼"
|
||||||
|
":monkey:": "🐵"
|
||||||
|
":horse:": "🐴"
|
||||||
|
":unicorn:": "🦄"
|
||||||
|
":snake:": "🐍"
|
||||||
|
":turtle:": "🐢"
|
||||||
|
":frog:": "🐸"
|
||||||
|
":bird:": "🐦"
|
||||||
|
":penguin:": "🐧"
|
||||||
|
":owl:": "🦉"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ЕДА И НАПИТКИ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
food:
|
||||||
|
":apple:": "🍎"
|
||||||
|
":green_apple:": "🍏"
|
||||||
|
":bread:": "🍞"
|
||||||
|
":meat:": "🍖"
|
||||||
|
":steak:": "🥩"
|
||||||
|
":cake:": "🎂"
|
||||||
|
":cookie:": "🍪"
|
||||||
|
":pizza:": "🍕"
|
||||||
|
":hamburger:": "🍔"
|
||||||
|
":fries:": "🍟"
|
||||||
|
":hotdog:": "🌭"
|
||||||
|
":taco:": "🌮"
|
||||||
|
":sushi:": "🍣"
|
||||||
|
":ramen:": "🍜"
|
||||||
|
":egg:": "🥚"
|
||||||
|
":cheese:": "🧀"
|
||||||
|
":milk:": "🥛"
|
||||||
|
":coffee:": "☕"
|
||||||
|
":tea:": "🍵"
|
||||||
|
":beer:": "🍺"
|
||||||
|
":wine:": "🍷"
|
||||||
|
":cocktail:": "🍹"
|
||||||
|
":ice_cream:": "🍦"
|
||||||
|
":donut:": "🍩"
|
||||||
|
":candy:": "🍬"
|
||||||
|
":chocolate:": "🍫"
|
||||||
|
":watermelon:": "🍉"
|
||||||
|
":banana:": "🍌"
|
||||||
|
":carrot:": "🥕"
|
||||||
|
":corn:": "🌽"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# ИГРОВЫЕ И ИНТЕРФЕЙС
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
gaming:
|
||||||
|
":game:": "🎮"
|
||||||
|
":controller:": "🎮"
|
||||||
|
":joystick:": "🕹"
|
||||||
|
":dice:": "🎲"
|
||||||
|
":chess:": "♟"
|
||||||
|
":target:": "🎯"
|
||||||
|
":flag:": "🚩"
|
||||||
|
":checkered_flag:": "🏁"
|
||||||
|
":warning:": "⚠"
|
||||||
|
":no_entry:": "⛔"
|
||||||
|
":stop:": "🛑"
|
||||||
|
":check:": "✓"
|
||||||
|
":checkmark:": "✔"
|
||||||
|
":x:": "✗"
|
||||||
|
":cross:": "✖"
|
||||||
|
":question:": "❓"
|
||||||
|
":exclamation:": "❗"
|
||||||
|
":zzz:": "💤"
|
||||||
|
":bulb:": "💡"
|
||||||
|
":idea:": "💡"
|
||||||
|
":bell:": "🔔"
|
||||||
|
":mute:": "🔇"
|
||||||
|
":speaker:": "🔊"
|
||||||
|
":mega:": "📢"
|
||||||
|
":mic:": "🎤"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# СТРЕЛКИ И НАПРАВЛЕНИЯ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
arrows:
|
||||||
|
":arrow_up:": "↑"
|
||||||
|
":arrow_down:": "↓"
|
||||||
|
":arrow_left:": "←"
|
||||||
|
":arrow_right:": "→"
|
||||||
|
":arrow_up_right:": "↗"
|
||||||
|
":arrow_up_left:": "↖"
|
||||||
|
":arrow_down_right:": "↘"
|
||||||
|
":arrow_down_left:": "↙"
|
||||||
|
":arrows_lr:": "↔"
|
||||||
|
":arrows_ud:": "↕"
|
||||||
|
":return:": "↩"
|
||||||
|
":redo:": "↪"
|
||||||
|
":refresh:": "🔄"
|
||||||
|
":infinity:": "∞"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# МУЗЫКА И ИСКУССТВО
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
music:
|
||||||
|
":note:": "♪"
|
||||||
|
":notes:": "♫"
|
||||||
|
":music:": "🎵"
|
||||||
|
":musical_note:": "🎶"
|
||||||
|
":headphones:": "🎧"
|
||||||
|
":guitar:": "🎸"
|
||||||
|
":piano:": "🎹"
|
||||||
|
":drum:": "🥁"
|
||||||
|
":violin:": "🎻"
|
||||||
|
":art:": "🎨"
|
||||||
|
":palette:": "🎨"
|
||||||
|
":camera:": "📷"
|
||||||
|
":movie:": "🎬"
|
||||||
|
":tv:": "📺"
|
||||||
|
":radio:": "📻"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# РАЗНОЕ
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
misc:
|
||||||
|
":lock:": "🔒"
|
||||||
|
":unlock:": "🔓"
|
||||||
|
":key:": "🔑"
|
||||||
|
":hourglass:": "⏳"
|
||||||
|
":timer:": "⏱"
|
||||||
|
":clock:": "🕐"
|
||||||
|
":calendar:": "📅"
|
||||||
|
":pin:": "📌"
|
||||||
|
":paperclip:": "📎"
|
||||||
|
":scissors:": "✂"
|
||||||
|
":pencil:": "✏"
|
||||||
|
":pen:": "🖊"
|
||||||
|
":book:": "📖"
|
||||||
|
":scroll:": "📜"
|
||||||
|
":mail:": "📧"
|
||||||
|
":envelope:": "✉"
|
||||||
|
":phone:": "📱"
|
||||||
|
":computer:": "💻"
|
||||||
|
":keyboard:": "⌨"
|
||||||
|
":mouse:": "🖱"
|
||||||
|
":trash:": "🗑"
|
||||||
|
":tools:": "🛠"
|
||||||
|
":wrench:": "🔧"
|
||||||
|
":hammer:": "🔨"
|
||||||
|
":gear:": "⚙"
|
||||||
|
":link:": "🔗"
|
||||||
|
":magnet:": "🧲"
|
||||||
|
":battery:": "🔋"
|
||||||
|
":plug:": "🔌"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
# КАСТОМНЫЕ (добавляй свои)
|
||||||
|
# ═══════════════════════════════════════════
|
||||||
|
custom:
|
||||||
|
# Примеры кастомных смайликов
|
||||||
|
":server:": "🖥"
|
||||||
|
":admin:": "👨💻"
|
||||||
|
":mod:": "🛡"
|
||||||
|
":vip:": "⭐"
|
||||||
|
":donate:": "💎"
|
||||||
|
":online:": "🟢"
|
||||||
|
":offline:": "🔴"
|
||||||
|
":afk:": "💤"
|
||||||
|
":gg:": "🏆"
|
||||||
|
":ez:": "😎"
|
||||||
|
":rip:": "⚰"
|
||||||
|
":f:": "🇫"
|
||||||
|
":pog:": "😲"
|
||||||
|
":kek:": "😂"
|
||||||
|
":sus:": "🤨"
|
||||||
|
":bruh:": "😐"
|
||||||
|
":based:": "💪"
|
||||||
|
":cringe:": "😬"
|
||||||
|
":ratio:": "📉"
|
||||||
|
":w:": "🏆"
|
||||||
|
":l:": "📉"
|
||||||
58
src/main/resources/gradient-config.yml
Normal file
58
src/main/resources/gradient-config.yml
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# LoChat Gradient Module Configuration
|
||||||
|
# Модуль градиентных ников (интегрированный LoPreff)
|
||||||
|
|
||||||
|
# Включить/выключить модуль
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Максимальное количество цветов в градиенте
|
||||||
|
max-colors: 7
|
||||||
|
|
||||||
|
# Минимальное количество цветов
|
||||||
|
min-colors: 1
|
||||||
|
|
||||||
|
# Цена за каждый цвет (PlayerPoints)
|
||||||
|
price-per-color: 50
|
||||||
|
|
||||||
|
# Цена за префикс
|
||||||
|
prefix-price: 500
|
||||||
|
|
||||||
|
# Покупка префикса один раз (потом бесплатно)
|
||||||
|
prefix-one-time-purchase: true
|
||||||
|
|
||||||
|
# Кулдаун на смену цвета (секунды)
|
||||||
|
color-cooldown: 60
|
||||||
|
|
||||||
|
# Кулдаун на смену префикса (секунды)
|
||||||
|
prefix-cooldown: 300
|
||||||
|
|
||||||
|
# Максимальная длина префикса
|
||||||
|
max-prefix-length: 7
|
||||||
|
|
||||||
|
# Формат префикса ({prefix} - сам префикс)
|
||||||
|
prefix-format: "[{prefix}] "
|
||||||
|
|
||||||
|
# Применять градиент на префикс
|
||||||
|
gradient-on-prefix: true
|
||||||
|
|
||||||
|
# Единый градиент на префикс + ник
|
||||||
|
continuous-gradient: true
|
||||||
|
|
||||||
|
# Использовать &#RRGGBB формат (true) или <#RRGGBB> (false)
|
||||||
|
use-legacy-rgb-format: true
|
||||||
|
|
||||||
|
# Тип хранилища: YAML или SQLITE
|
||||||
|
storage-type: YAML
|
||||||
|
|
||||||
|
# Файл SQLite базы данных
|
||||||
|
sqlite-file: gradient-data.db
|
||||||
|
|
||||||
|
# Чёрный список префиксов (нельзя использовать)
|
||||||
|
prefix-blacklist:
|
||||||
|
- admin
|
||||||
|
- owner
|
||||||
|
- moder
|
||||||
|
- helper
|
||||||
|
- staff
|
||||||
|
- модер
|
||||||
|
- админ
|
||||||
|
- хелпер
|
||||||
57
src/main/resources/gradient-messages.yml
Normal file
57
src/main/resources/gradient-messages.yml
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# LoChat Gradient Module Messages
|
||||||
|
# Сообщения модуля градиентных ников
|
||||||
|
|
||||||
|
# Общие
|
||||||
|
only-player: "§cЭта команда только для игроков!"
|
||||||
|
no-permission: "§cУ вас нет прав на это действие!"
|
||||||
|
player-not-found: "§cИгрок не найден!"
|
||||||
|
reload-success: "§aКонфигурация градиентов перезагружена!"
|
||||||
|
cooldown: "§cПодождите ещё {time} секунд!"
|
||||||
|
|
||||||
|
# Цвета
|
||||||
|
color-help: |
|
||||||
|
§e=== Команда /color ===
|
||||||
|
§e/color <цвет1> [цвет2] ... §7— установить градиент
|
||||||
|
§e/color on §7— включить цвет
|
||||||
|
§e/color off §7— выключить цвет
|
||||||
|
§e/color reset §7— сбросить цвет
|
||||||
|
§e/color copy <игрок> §7— скопировать цвет
|
||||||
|
§7Пример: /color #FF0000 #00FF00 #0000FF
|
||||||
|
color-enabled: "§aЦвет ника включён!"
|
||||||
|
color-disabled: "§cЦвет ника выключен!"
|
||||||
|
color-reset: "§aЦвет ника сброшен!"
|
||||||
|
color-no-colors: "§cУ вас нет установленных цветов!"
|
||||||
|
color-success: "§aЦвет успешно установлен! Списано: {price} PP"
|
||||||
|
color-copy-usage: "§cИспользование: /color copy <игрок>"
|
||||||
|
color-copy-no-colors: "§cУ игрока {player} нет цветов!"
|
||||||
|
invalid-color: "§cНеверный формат цвета: {color}. Используйте #RRGGBB"
|
||||||
|
too-many-colors: "§cМаксимум {max} цветов!"
|
||||||
|
not-enough-colors: "§cМинимум {min} цветов!"
|
||||||
|
not-enough-points-color: "§cНедостаточно поинтов! Нужно: {price} PP"
|
||||||
|
|
||||||
|
# Префикс
|
||||||
|
prefix-help: |
|
||||||
|
§e=== Команда /prefix ===
|
||||||
|
§e/prefix <текст> §7— установить префикс
|
||||||
|
§e/prefix on §7— включить префикс
|
||||||
|
§e/prefix off §7— выключить префикс
|
||||||
|
§e/prefix reset §7— сбросить префикс
|
||||||
|
prefix-enabled: "§aПрефикс включён!"
|
||||||
|
prefix-disabled: "§cПрефикс выключен!"
|
||||||
|
prefix-reset-success: "§aПрефикс сброшен!"
|
||||||
|
prefix-no-prefix: "§cУ вас нет установленного префикса!"
|
||||||
|
prefix-success: "§aПрефикс успешно установлен! Списано: {price} PP"
|
||||||
|
prefix-success-free: "§aПрефикс успешно изменён!"
|
||||||
|
prefix-no-colors: "§cПрефикс не может содержать цветовые коды!"
|
||||||
|
prefix-invalid-chars: "§cПрефикс содержит недопустимые символы!"
|
||||||
|
prefix-blacklisted: "§cЭтот префикс запрещён!"
|
||||||
|
prefix-too-long: "§cПрефикс слишком длинный! Максимум: {max} символов"
|
||||||
|
not-enough-points-prefix: "§cНедостаточно поинтов! Нужно: {price} PP"
|
||||||
|
|
||||||
|
# Админ информация
|
||||||
|
info-header: "§e=== Информация об игроке {player} ==="
|
||||||
|
info-prefix: "§7Префикс: §f{prefix}"
|
||||||
|
info-prefix-enabled: "§7Префикс включён: §f{enabled}"
|
||||||
|
info-colors: "§7Цвета: §f{colors}"
|
||||||
|
info-color-enabled: "§7Цвет включён: §f{enabled}"
|
||||||
|
info-prefix-purchased: "§7Префикс куплен: §f{purchased}"
|
||||||
60
src/main/resources/messages.yml
Normal file
60
src/main/resources/messages.yml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
prefix: "<gold>[LoChat]</gold>"
|
||||||
|
|
||||||
|
global:
|
||||||
|
disabled: "<red>Глобальный чат отключен</red>"
|
||||||
|
toggled-on: "<green>Глобальный чат включен</green>"
|
||||||
|
toggled-off: "<red>Глобальный чат отключен</red>"
|
||||||
|
|
||||||
|
local:
|
||||||
|
no-players: "<red>Рядом нет игроков</red>"
|
||||||
|
|
||||||
|
pm:
|
||||||
|
sent: "<gray>[PM] Ты → {player}: {message}</gray>"
|
||||||
|
received: "<gray>[PM] {player} → Ты: {message}</gray>"
|
||||||
|
no-reply: "<red>Некому ответить</red>"
|
||||||
|
ignored: "<red>Этот игрок вас игнорирует</red>"
|
||||||
|
player-offline: "<red>Игрок не в сети</red>"
|
||||||
|
self: "<red>Нельзя писать самому себе</red>"
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
added: "<green>Игрок {player} добавлен в игнор</green>"
|
||||||
|
removed: "<green>Игрок {player} убран из игнора</green>"
|
||||||
|
already-ignored: "<red>Игрок уже в игноре</red>"
|
||||||
|
not-ignored: "<red>Игрок не в игноре</red>"
|
||||||
|
self: "<red>Нельзя игнорировать себя</red>"
|
||||||
|
|
||||||
|
announce:
|
||||||
|
reloaded: "<green>Конфигурация перезагружена</green>"
|
||||||
|
|
||||||
|
mute:
|
||||||
|
muted: "<green>Игрок {player} замучен на {time}</green>"
|
||||||
|
unmuted: "<green>Игрок {player} размучен</green>"
|
||||||
|
already-muted: "<red>Игрок уже замучен</red>"
|
||||||
|
not-muted: "<red>Игрок не замучен</red>"
|
||||||
|
you-muted: "<red>Вы замучены! Осталось: {time}</red>"
|
||||||
|
permanent: "<red>Вы замучены навсегда!</red>"
|
||||||
|
|
||||||
|
spy:
|
||||||
|
enabled: "<green>Шпион режим включен</green>"
|
||||||
|
disabled: "<red>Шпион режим выключен</red>"
|
||||||
|
format: "<gray>[SPY] {sender} → {receiver}: {message}</gray>"
|
||||||
|
|
||||||
|
filter:
|
||||||
|
blocked: "<red>Сообщение заблокировано (мат)</red>"
|
||||||
|
warned: "<yellow>Предупреждение: не используйте мат!</yellow>"
|
||||||
|
|
||||||
|
cooldown:
|
||||||
|
wait: "<red>Подождите {time} сек перед следующим сообщением</red>"
|
||||||
|
|
||||||
|
antispam:
|
||||||
|
caps: "<red>Слишком много заглавных букв!</red>"
|
||||||
|
repeat: "<red>Не повторяйте символы!</red>"
|
||||||
|
similar: "<red>Не отправляйте одинаковые сообщения!</red>"
|
||||||
|
|
||||||
|
clearchat:
|
||||||
|
cleared: "<green>Чат очищен администратором {player}</green>"
|
||||||
|
|
||||||
|
errors:
|
||||||
|
no-permission: "<red>Нет прав!</red>"
|
||||||
|
player-not-found: "<red>Игрок не найден</red>"
|
||||||
|
invalid-usage: "<red>Использование: {usage}</red>"
|
||||||
138
src/main/resources/plugin.yml
Normal file
138
src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
name: LoChat
|
||||||
|
version: '1.0.0'
|
||||||
|
main: com.loki.lochat.LoChat
|
||||||
|
api-version: '1.21'
|
||||||
|
folia-supported: true
|
||||||
|
author: loki
|
||||||
|
description: Advanced chat plugin for Folia with gradient nicks
|
||||||
|
|
||||||
|
softdepend:
|
||||||
|
- PlaceholderAPI
|
||||||
|
- PlayerPoints
|
||||||
|
- LuckPerms
|
||||||
|
|
||||||
|
commands:
|
||||||
|
g:
|
||||||
|
description: Send global message
|
||||||
|
usage: /g <message>
|
||||||
|
permission: chat.global.use
|
||||||
|
|
||||||
|
globalchat:
|
||||||
|
description: Toggle global chat
|
||||||
|
usage: /globalchat toggle
|
||||||
|
permission: chat.global.toggle
|
||||||
|
|
||||||
|
l:
|
||||||
|
description: Send local message
|
||||||
|
usage: /l <message>
|
||||||
|
permission: chat.local.use
|
||||||
|
|
||||||
|
msg:
|
||||||
|
description: Send private message
|
||||||
|
usage: /msg <player> <message>
|
||||||
|
permission: chat.pm.use
|
||||||
|
aliases: [tell, w, pm]
|
||||||
|
|
||||||
|
reply:
|
||||||
|
description: Reply to last message
|
||||||
|
usage: /reply <message>
|
||||||
|
permission: chat.pm.use
|
||||||
|
aliases: [r]
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
description: Ignore player
|
||||||
|
usage: /ignore <player>
|
||||||
|
permission: chat.pm.ignore
|
||||||
|
|
||||||
|
unignore:
|
||||||
|
description: Unignore player
|
||||||
|
usage: /unignore <player>
|
||||||
|
permission: chat.pm.ignore
|
||||||
|
|
||||||
|
announce:
|
||||||
|
description: Send announcement
|
||||||
|
usage: /announce <message>
|
||||||
|
permission: chat.announce.use
|
||||||
|
|
||||||
|
chatspy:
|
||||||
|
description: Toggle chat spy mode
|
||||||
|
usage: /chatspy
|
||||||
|
permission: chat.spy
|
||||||
|
aliases: [spy]
|
||||||
|
|
||||||
|
clearchat:
|
||||||
|
description: Clear chat for all players
|
||||||
|
usage: /clearchat
|
||||||
|
permission: chat.clear
|
||||||
|
aliases: [cc]
|
||||||
|
|
||||||
|
mute:
|
||||||
|
description: Mute a player
|
||||||
|
usage: /mute <player> <time> [reason]
|
||||||
|
permission: chat.mute
|
||||||
|
|
||||||
|
unmute:
|
||||||
|
description: Unmute a player
|
||||||
|
usage: /unmute <player>
|
||||||
|
permission: chat.mute
|
||||||
|
|
||||||
|
color:
|
||||||
|
description: Set gradient nick color
|
||||||
|
usage: /color <hex1> [hex2] ...
|
||||||
|
permission: gradient.color
|
||||||
|
aliases: [gradient]
|
||||||
|
|
||||||
|
prefix:
|
||||||
|
description: Set custom prefix
|
||||||
|
usage: /prefix <text>
|
||||||
|
permission: gradient.prefix
|
||||||
|
|
||||||
|
aprefix:
|
||||||
|
description: Admin gradient commands
|
||||||
|
usage: /aprefix <subcommand>
|
||||||
|
permission: gradient.admin
|
||||||
|
aliases: [gradientadmin]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
chat.global.use:
|
||||||
|
default: true
|
||||||
|
chat.global.toggle:
|
||||||
|
default: true
|
||||||
|
chat.global.color:
|
||||||
|
default: op
|
||||||
|
chat.local.use:
|
||||||
|
default: true
|
||||||
|
chat.local.color:
|
||||||
|
default: op
|
||||||
|
chat.pm.use:
|
||||||
|
default: true
|
||||||
|
chat.pm.ignore:
|
||||||
|
default: true
|
||||||
|
chat.announce.use:
|
||||||
|
default: op
|
||||||
|
chat.announce.reload:
|
||||||
|
default: op
|
||||||
|
chat.spy:
|
||||||
|
default: op
|
||||||
|
chat.clear:
|
||||||
|
default: op
|
||||||
|
chat.mute:
|
||||||
|
default: op
|
||||||
|
chat.bypass.cooldown:
|
||||||
|
default: op
|
||||||
|
chat.bypass.filter:
|
||||||
|
default: op
|
||||||
|
chat.bypass.antispam:
|
||||||
|
default: op
|
||||||
|
chat.mention.use:
|
||||||
|
default: true
|
||||||
|
gradient.color:
|
||||||
|
default: true
|
||||||
|
gradient.prefix:
|
||||||
|
default: true
|
||||||
|
gradient.admin:
|
||||||
|
default: op
|
||||||
|
gradient.bypass.cooldown:
|
||||||
|
default: op
|
||||||
|
gradient.bypass.cost:
|
||||||
|
default: op
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.loki.lochat.gradient.data;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class GradientPlayerDataTest {
|
||||||
|
|
||||||
|
private GradientPlayerData data;
|
||||||
|
private UUID testUuid;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
testUuid = UUID.randomUUID();
|
||||||
|
data = new GradientPlayerData(testUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_defaultValues() {
|
||||||
|
assertEquals(testUuid, data.getUuid());
|
||||||
|
assertNull(data.getPrefix());
|
||||||
|
assertNotNull(data.getColors());
|
||||||
|
assertTrue(data.getColors().isEmpty());
|
||||||
|
assertTrue(data.isColorEnabled());
|
||||||
|
assertTrue(data.isPrefixEnabled());
|
||||||
|
assertFalse(data.isPrefixPurchased());
|
||||||
|
assertEquals(0, data.getLastColorChange());
|
||||||
|
assertEquals(0, data.getLastPrefixChange());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasPrefix_false() {
|
||||||
|
assertFalse(data.hasPrefix());
|
||||||
|
data.setPrefix("");
|
||||||
|
assertFalse(data.hasPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasPrefix_true() {
|
||||||
|
data.setPrefix("VIP");
|
||||||
|
assertTrue(data.hasPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasColors_false() {
|
||||||
|
assertFalse(data.hasColors());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasColors_true() {
|
||||||
|
data.setColors(List.of("#FF0000", "#00FF00"));
|
||||||
|
assertTrue(data.hasColors());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setColors_null() {
|
||||||
|
data.setColors(null);
|
||||||
|
assertNotNull(data.getColors());
|
||||||
|
assertTrue(data.getColors().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setColors_copiesList() {
|
||||||
|
List<String> original = List.of("#FF0000");
|
||||||
|
data.setColors(original);
|
||||||
|
assertEquals(1, data.getColors().size());
|
||||||
|
assertEquals("#FF0000", data.getColors().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void colorEnabled_toggle() {
|
||||||
|
assertTrue(data.isColorEnabled());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
assertFalse(data.isColorEnabled());
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
assertTrue(data.isColorEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void prefixEnabled_toggle() {
|
||||||
|
assertTrue(data.isPrefixEnabled());
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
assertFalse(data.isPrefixEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void prefixPurchased() {
|
||||||
|
assertFalse(data.isPrefixPurchased());
|
||||||
|
data.setPrefixPurchased(true);
|
||||||
|
assertTrue(data.isPrefixPurchased());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lastColorChange() {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
data.setLastColorChange(time);
|
||||||
|
assertEquals(time, data.getLastColorChange());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lastPrefixChange() {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
data.setLastPrefixChange(time);
|
||||||
|
assertEquals(time, data.getLastPrefixChange());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class GradientConstantsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void millisPerSecond() {
|
||||||
|
assertEquals(1000L, GradientConstants.MILLIS_PER_SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void guiSlots() {
|
||||||
|
assertEquals(11, GradientConstants.GUI_CONFIRM_SLOT);
|
||||||
|
assertEquals(15, GradientConstants.GUI_CANCEL_SLOT);
|
||||||
|
assertEquals(13, GradientConstants.GUI_PREVIEW_SLOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void luckPermsPriority() {
|
||||||
|
assertEquals(100, GradientConstants.LUCKPERMS_PREFIX_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.loki.lochat.gradient.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class GradientUtilTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isValidHex_valid() {
|
||||||
|
assertTrue(GradientUtil.isValidHex("#FF0000"));
|
||||||
|
assertTrue(GradientUtil.isValidHex("#00ff00"));
|
||||||
|
assertTrue(GradientUtil.isValidHex("#3BA8FF"));
|
||||||
|
assertTrue(GradientUtil.isValidHex("#000000"));
|
||||||
|
assertTrue(GradientUtil.isValidHex("#FFFFFF"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isValidHex_invalid() {
|
||||||
|
assertFalse(GradientUtil.isValidHex("FF0000")); // без #
|
||||||
|
assertFalse(GradientUtil.isValidHex("#FF00")); // короткий
|
||||||
|
assertFalse(GradientUtil.isValidHex("#FF00000")); // длинный
|
||||||
|
assertFalse(GradientUtil.isValidHex("#GGGGGG")); // невалидные символы
|
||||||
|
assertFalse(GradientUtil.isValidHex(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isValidHex_null() {
|
||||||
|
assertThrows(NullPointerException.class, () -> GradientUtil.isValidHex(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_singleColor_legacy() {
|
||||||
|
String result = GradientUtil.applyGradient("Hi", List.of("#FF0000"), true);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("&#ff0000"));
|
||||||
|
assertEquals("&#ff0000H&#ff0000i", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_singleColor_minimessage() {
|
||||||
|
String result = GradientUtil.applyGradient("Hi", List.of("#FF0000"), false);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("<#ff0000>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_twoColors() {
|
||||||
|
String result = GradientUtil.applyGradient("AB", List.of("#FF0000", "#0000FF"), true);
|
||||||
|
assertNotNull(result);
|
||||||
|
// Первый символ должен быть красным, последний синим
|
||||||
|
assertTrue(result.startsWith("&#ff0000A"));
|
||||||
|
assertTrue(result.contains("�ffB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_emptyText() {
|
||||||
|
String result = GradientUtil.applyGradient("", List.of("#FF0000"), true);
|
||||||
|
assertEquals("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_nullText() {
|
||||||
|
String result = GradientUtil.applyGradient(null, List.of("#FF0000"), true);
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_emptyColors() {
|
||||||
|
String result = GradientUtil.applyGradient("Test", List.of(), true);
|
||||||
|
assertEquals("Test", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_nullColors() {
|
||||||
|
String result = GradientUtil.applyGradient("Test", null, true);
|
||||||
|
assertEquals("Test", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyGradient_multipleColors() {
|
||||||
|
String result = GradientUtil.applyGradient("ABC", List.of("#FF0000", "#00FF00", "#0000FF"), true);
|
||||||
|
assertNotNull(result);
|
||||||
|
// Должен содержать цветовые коды для каждого символа
|
||||||
|
assertTrue(result.contains("A"));
|
||||||
|
assertTrue(result.contains("B"));
|
||||||
|
assertTrue(result.contains("C"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_noPrefix_noColors() {
|
||||||
|
String result = GradientUtil.buildDisplayName(null, "Player", null, true, true, "[{prefix}] ", true);
|
||||||
|
assertEquals("Player", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_withPrefix_noColors() {
|
||||||
|
String result = GradientUtil.buildDisplayName("VIP", "Player", null, true, true, "[{prefix}] ", true);
|
||||||
|
assertEquals("[VIP] Player", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_noPrefix_withColors() {
|
||||||
|
String result = GradientUtil.buildDisplayName(null, "AB", List.of("#FF0000", "#0000FF"), true, true, "[{prefix}] ", true);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("&#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_withPrefix_withColors_continuous() {
|
||||||
|
String result = GradientUtil.buildDisplayName("V", "AB", List.of("#FF0000", "#0000FF"), true, true, "[{prefix}] ", true);
|
||||||
|
assertNotNull(result);
|
||||||
|
// Единый градиент на всё
|
||||||
|
assertTrue(result.contains("&#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_withPrefix_withColors_separate() {
|
||||||
|
String result = GradientUtil.buildDisplayName("V", "AB", List.of("#FF0000", "#0000FF"), true, false, "[{prefix}] ", true);
|
||||||
|
assertNotNull(result);
|
||||||
|
// Отдельные градиенты
|
||||||
|
assertTrue(result.contains("&#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildDisplayName_gradientOnlyOnNick() {
|
||||||
|
String result = GradientUtil.buildDisplayName("VIP", "AB", List.of("#FF0000", "#0000FF"), false, false, "[{prefix}] ", true);
|
||||||
|
assertNotNull(result);
|
||||||
|
// Префикс без градиента
|
||||||
|
assertTrue(result.startsWith("[VIP] "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class CooldownManagerTest {
|
||||||
|
|
||||||
|
private CooldownManager cooldownManager;
|
||||||
|
private UUID testPlayer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
cooldownManager = new CooldownManager();
|
||||||
|
testPlayer = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isOnCooldown_noCooldown() {
|
||||||
|
assertFalse(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isOnCooldown_afterSet() {
|
||||||
|
cooldownManager.setCooldown(testPlayer, "global");
|
||||||
|
assertTrue(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getRemainingCooldown_noCooldown() {
|
||||||
|
assertEquals(0, cooldownManager.getRemainingCooldown(testPlayer, "global", 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeCooldown() {
|
||||||
|
cooldownManager.setCooldown(testPlayer, "global");
|
||||||
|
cooldownManager.removeCooldown(testPlayer);
|
||||||
|
assertFalse(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void separateCooldowns() {
|
||||||
|
cooldownManager.setCooldown(testPlayer, "global");
|
||||||
|
assertTrue(cooldownManager.isOnCooldown(testPlayer, "global", 5));
|
||||||
|
assertFalse(cooldownManager.isOnCooldown(testPlayer, "local", 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/test/java/com/loki/lochat/managers/MuteManagerTest.java
Normal file
39
src/test/java/com/loki/lochat/managers/MuteManagerTest.java
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class MuteManagerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_seconds() {
|
||||||
|
assertEquals(30, MuteManager.parseTime("30s"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_minutes() {
|
||||||
|
assertEquals(300, MuteManager.parseTime("5m"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_hours() {
|
||||||
|
assertEquals(3600, MuteManager.parseTime("1h"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_days() {
|
||||||
|
assertEquals(86400, MuteManager.parseTime("1d"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_invalid() {
|
||||||
|
assertEquals(-1, MuteManager.parseTime("abc"));
|
||||||
|
assertEquals(-1, MuteManager.parseTime(""));
|
||||||
|
assertEquals(-1, MuteManager.parseTime(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseTime_numberOnly() {
|
||||||
|
assertEquals(60, MuteManager.parseTime("60"));
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/test/java/com/loki/lochat/managers/PMManagerTest.java
Normal file
47
src/test/java/com/loki/lochat/managers/PMManagerTest.java
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.loki.lochat.managers;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class PMManagerTest {
|
||||||
|
|
||||||
|
private PMManager pmManager;
|
||||||
|
private UUID player1;
|
||||||
|
private UUID player2;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
pmManager = new PMManager();
|
||||||
|
player1 = UUID.randomUUID();
|
||||||
|
player2 = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setAndGetLastConversation() {
|
||||||
|
pmManager.setLastConversation(player1, player2);
|
||||||
|
assertEquals(player2, pmManager.getLastConversation(player1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getLastConversation_noConversation() {
|
||||||
|
assertNull(pmManager.getLastConversation(player1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hasConversation() {
|
||||||
|
assertFalse(pmManager.hasConversation(player1));
|
||||||
|
pmManager.setLastConversation(player1, player2);
|
||||||
|
assertTrue(pmManager.hasConversation(player1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removeConversation() {
|
||||||
|
pmManager.setLastConversation(player1, player2);
|
||||||
|
pmManager.removeConversation(player1);
|
||||||
|
assertFalse(pmManager.hasConversation(player1));
|
||||||
|
}
|
||||||
|
}
|
||||||
113
surce/gradientnick/GradientNick.java
Normal file
113
surce/gradientnick/GradientNick.java
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package ru.lovar.gradientnick;
|
||||||
|
|
||||||
|
import org.black_ixx.playerpoints.PlayerPoints;
|
||||||
|
import org.black_ixx.playerpoints.PlayerPointsAPI;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import ru.lovar.gradientnick.commands.ColorCommand;
|
||||||
|
import ru.lovar.gradientnick.commands.GradientCommand;
|
||||||
|
import ru.lovar.gradientnick.commands.PrefixCommand;
|
||||||
|
import ru.lovar.gradientnick.config.ConfigManager;
|
||||||
|
import ru.lovar.gradientnick.config.MessagesManager;
|
||||||
|
import ru.lovar.gradientnick.data.DataManager;
|
||||||
|
import ru.lovar.gradientnick.hooks.LuckPermsHook;
|
||||||
|
import ru.lovar.gradientnick.hooks.PlaceholderAPIHook;
|
||||||
|
import ru.lovar.gradientnick.listeners.GUIListener;
|
||||||
|
import ru.lovar.gradientnick.listeners.PlayerListener;
|
||||||
|
|
||||||
|
public class GradientNick extends JavaPlugin {
|
||||||
|
|
||||||
|
private static GradientNick instance;
|
||||||
|
private PlayerPointsAPI playerPointsAPI;
|
||||||
|
private ConfigManager configManager;
|
||||||
|
private MessagesManager messagesManager;
|
||||||
|
private DataManager dataManager;
|
||||||
|
private LuckPermsHook luckPermsHook;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
// Инициализация конфигов
|
||||||
|
this.configManager = new ConfigManager(this);
|
||||||
|
this.messagesManager = new MessagesManager(this);
|
||||||
|
|
||||||
|
// Инициализация хранилища данных
|
||||||
|
this.dataManager = new DataManager(this);
|
||||||
|
|
||||||
|
// Подключение PlayerPoints
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("PlayerPoints")) {
|
||||||
|
this.playerPointsAPI = PlayerPoints.getInstance().getAPI();
|
||||||
|
getLogger().info("PlayerPoints подключен!");
|
||||||
|
} else {
|
||||||
|
getLogger().severe("PlayerPoints не найден! Плагин отключается.");
|
||||||
|
Bukkit.getPluginManager().disablePlugin(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подключение LuckPerms
|
||||||
|
this.luckPermsHook = new LuckPermsHook(this);
|
||||||
|
|
||||||
|
// Подключение PlaceholderAPI
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
|
||||||
|
new PlaceholderAPIHook(this).register();
|
||||||
|
getLogger().info("PlaceholderAPI подключен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регистрация команд
|
||||||
|
ColorCommand colorCommand = new ColorCommand(this);
|
||||||
|
getCommand("color").setExecutor(colorCommand);
|
||||||
|
getCommand("color").setTabCompleter(colorCommand);
|
||||||
|
|
||||||
|
PrefixCommand prefixCommand = new PrefixCommand(this);
|
||||||
|
getCommand("prefix").setExecutor(prefixCommand);
|
||||||
|
getCommand("prefix").setTabCompleter(prefixCommand);
|
||||||
|
|
||||||
|
GradientCommand gradientCommand = new GradientCommand(this);
|
||||||
|
getCommand("aprefix").setExecutor(gradientCommand);
|
||||||
|
getCommand("aprefix").setTabCompleter(gradientCommand);
|
||||||
|
|
||||||
|
// Регистрация слушателей
|
||||||
|
Bukkit.getPluginManager().registerEvents(new PlayerListener(this), this);
|
||||||
|
Bukkit.getPluginManager().registerEvents(new GUIListener(this), this);
|
||||||
|
|
||||||
|
getLogger().info("LoPreff успешно запущен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable() {
|
||||||
|
if (dataManager != null) {
|
||||||
|
dataManager.saveAll();
|
||||||
|
}
|
||||||
|
getLogger().info("LoPreff отключен!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GradientNick getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerPointsAPI getPlayerPointsAPI() {
|
||||||
|
return playerPointsAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigManager getConfigManager() {
|
||||||
|
return configManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessagesManager getMessagesManager() {
|
||||||
|
return messagesManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataManager getDataManager() {
|
||||||
|
return dataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuckPermsHook getLuckPermsHook() {
|
||||||
|
return luckPermsHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
configManager.reload();
|
||||||
|
messagesManager.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
244
surce/gradientnick/commands/ColorCommand.java
Normal file
244
surce/gradientnick/commands/ColorCommand.java
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
package ru.lovar.gradientnick.commands;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.config.ConfigManager;
|
||||||
|
import ru.lovar.gradientnick.config.MessagesManager;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.gui.ConfirmGUI;
|
||||||
|
import ru.lovar.gradientnick.util.Constants;
|
||||||
|
import ru.lovar.gradientnick.util.DisplayNameUtil;
|
||||||
|
import ru.lovar.gradientnick.util.FoliaUtil;
|
||||||
|
import ru.lovar.gradientnick.util.GradientUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
public class ColorCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
|
||||||
|
|
||||||
|
private static final List<String> PRESET_COLORS = List.of(
|
||||||
|
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3",
|
||||||
|
"#FF69B4", "#00FFFF", "#FFD700", "#FFFFFF", "#000000"
|
||||||
|
);
|
||||||
|
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset", "copy");
|
||||||
|
|
||||||
|
public ColorCommand(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
registerSubCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerSubCommands() {
|
||||||
|
subCommands.put("on", this::handleOn);
|
||||||
|
subCommands.put("off", this::handleOff);
|
||||||
|
subCommands.put("reset", this::handleReset);
|
||||||
|
subCommands.put("copy", this::handleCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(plugin.getMessagesManager().get("only-player"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.color")) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("color-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
var handler = subCommands.get(subCommand);
|
||||||
|
if (handler != null) {
|
||||||
|
return handler.apply(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleSetColors(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOn(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
|
||||||
|
if (!data.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("color-enabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOff(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("color-disabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleReset(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
|
||||||
|
if (!data.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setColors(new ArrayList<>());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("color-reset"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleCopy(Player player, String[] args) {
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(msg.get("color-copy-usage"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[1]);
|
||||||
|
if (target == null) {
|
||||||
|
player.sendMessage(msg.get("player-not-found"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData targetData = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
if (!targetData.hasColors()) {
|
||||||
|
player.sendMessage(msg.get("color-copy-no-colors", "player", target.getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") && !checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>(targetData.getColors());
|
||||||
|
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmGUI gui = new ConfirmGUI(plugin, player, ConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSetColors(Player player, String[] args) {
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
|
||||||
|
if (args.length > cfg.getMaxColors()) {
|
||||||
|
player.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length < cfg.getMinColors()) {
|
||||||
|
player.sendMessage(msg.get("not-enough-colors", "min", String.valueOf(cfg.getMinColors())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") && !checkCooldown(player, cfg.getColorCooldown(), data.getLastColorChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>();
|
||||||
|
for (String arg : args) {
|
||||||
|
String color = arg.startsWith("#") ? arg : "#" + arg;
|
||||||
|
if (!GradientUtil.isValidHex(color)) {
|
||||||
|
player.sendMessage(msg.get("invalid-color", "color", arg));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
colors.add(color.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
int price = player.hasPermission("gradient.bypass.cost") ? 0 : colors.size() * cfg.getPricePerColor();
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmGUI gui = new ConfirmGUI(plugin, player, ConfirmGUI.ConfirmType.COLOR, colors, data.getPrefix(), price);
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
|
||||||
|
long cooldownMs = cooldownSec * Constants.MILLIS_PER_SECOND;
|
||||||
|
long timePassed = System.currentTimeMillis() - lastChange;
|
||||||
|
if (timePassed < cooldownMs) {
|
||||||
|
long remaining = (cooldownMs - timePassed) / Constants.MILLIS_PER_SECOND;
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("cooldown", "time", String.valueOf(remaining)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBalance(Player player, int price) {
|
||||||
|
int balance = plugin.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("not-enough-points-color", "price", String.valueOf(price)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndUpdate(Player player, PlayerData data) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, () -> DisplayNameUtil.updateDisplayName(plugin, player, data));
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
String input = args[0].toLowerCase();
|
||||||
|
List<String> completions = new ArrayList<>(SUB_COMMANDS);
|
||||||
|
completions.addAll(PRESET_COLORS);
|
||||||
|
return completions.stream().filter(s -> s.toLowerCase().startsWith(input)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String firstArg = args[0].toLowerCase();
|
||||||
|
|
||||||
|
if (args.length == 2 && firstArg.equals("copy")) {
|
||||||
|
return Bukkit.getOnlinePlayers().stream().map(Player::getName)
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase())).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для цветов — показываем пресеты
|
||||||
|
if (!SUB_COMMANDS.contains(firstArg) && args.length <= plugin.getConfigManager().getMaxColors()) {
|
||||||
|
return PRESET_COLORS.stream()
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
347
surce/gradientnick/commands/GradientCommand.java
Normal file
347
surce/gradientnick/commands/GradientCommand.java
Normal file
|
|
@ -0,0 +1,347 @@
|
||||||
|
package ru.lovar.gradientnick.commands;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.config.ConfigManager;
|
||||||
|
import ru.lovar.gradientnick.config.MessagesManager;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.util.DisplayNameUtil;
|
||||||
|
import ru.lovar.gradientnick.util.FoliaUtil;
|
||||||
|
import ru.lovar.gradientnick.util.GradientUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GradientCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private static final List<String> PRESET_COLORS = Arrays.asList(
|
||||||
|
"#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"
|
||||||
|
);
|
||||||
|
|
||||||
|
public GradientCommand(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
|
||||||
|
if (!sender.hasPermission("gradient.admin")) {
|
||||||
|
sender.sendMessage(msg.get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sendHelp(sender);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
|
||||||
|
switch (subCommand) {
|
||||||
|
case "reload" -> {
|
||||||
|
plugin.reload();
|
||||||
|
sender.sendMessage(msg.get("reload-success"));
|
||||||
|
}
|
||||||
|
case "info" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix info <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleInfo(sender, args[1], msg);
|
||||||
|
}
|
||||||
|
case "setcolor" -> {
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleSetColor(sender, args, msg, cfg);
|
||||||
|
}
|
||||||
|
case "setprefix" -> {
|
||||||
|
if (args.length < 3) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleSetPrefix(sender, args, msg, cfg);
|
||||||
|
}
|
||||||
|
case "coloron" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix coloron <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleColorToggle(sender, args[1], true, msg);
|
||||||
|
}
|
||||||
|
case "coloroff" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix coloroff <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleColorToggle(sender, args[1], false, msg);
|
||||||
|
}
|
||||||
|
case "prefixon" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix prefixon <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handlePrefixToggle(sender, args[1], true, msg);
|
||||||
|
}
|
||||||
|
case "prefixoff" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix prefixoff <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handlePrefixToggle(sender, args[1], false, msg);
|
||||||
|
}
|
||||||
|
case "resetcolor" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleResetColor(sender, args[1], msg);
|
||||||
|
}
|
||||||
|
case "resetprefix" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleResetPrefix(sender, args[1], msg);
|
||||||
|
}
|
||||||
|
default -> sendHelp(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendHelp(CommandSender sender) {
|
||||||
|
sender.sendMessage("§e=== LoPreff Admin ===");
|
||||||
|
sender.sendMessage("§e/aprefix reload §7— перезагрузить конфиг");
|
||||||
|
sender.sendMessage("§e/aprefix info <игрок> §7— информация об игроке");
|
||||||
|
sender.sendMessage("§e/aprefix setcolor <игрок> <цвета> §7— установить цвет");
|
||||||
|
sender.sendMessage("§e/aprefix setprefix <игрок> <префикс> §7— установить префикс");
|
||||||
|
sender.sendMessage("§e/aprefix coloron/coloroff <игрок> §7— вкл/выкл цвет");
|
||||||
|
sender.sendMessage("§e/aprefix prefixon/prefixoff <игрок> §7— вкл/выкл префикс");
|
||||||
|
sender.sendMessage("§e/aprefix resetcolor <игрок> §7— сбросить цвет");
|
||||||
|
sender.sendMessage("§e/aprefix resetprefix <игрок> §7— сбросить префикс");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleInfo(CommandSender sender, String playerName, MessagesManager msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
|
||||||
|
sender.sendMessage(msg.get("info-header", "player", playerName));
|
||||||
|
sender.sendMessage(msg.get("info-prefix", "prefix", data.hasPrefix() ? data.getPrefix() : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-prefix-enabled", "enabled", data.isPrefixEnabled() ? "да" : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-colors", "colors", data.hasColors() ? String.join(", ", data.getColors()) : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-color-enabled", "enabled", data.isColorEnabled() ? "да" : "нет"));
|
||||||
|
sender.sendMessage(msg.get("info-prefix-purchased", "purchased", data.isPrefixPurchased() ? "да" : "нет"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetColor(CommandSender sender, String[] args, MessagesManager msg, ConfigManager cfg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> colors = new ArrayList<>();
|
||||||
|
for (int i = 2; i < args.length; i++) {
|
||||||
|
String color = args[i].startsWith("#") ? args[i] : "#" + args[i];
|
||||||
|
if (!GradientUtil.isValidHex(color)) {
|
||||||
|
sender.sendMessage(msg.get("invalid-color", "color", args[i]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
colors.add(color.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colors.size() > cfg.getMaxColors()) {
|
||||||
|
sender.sendMessage(msg.get("too-many-colors", "max", String.valueOf(cfg.getMaxColors())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColors(colors);
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSetPrefix(CommandSender sender, String[] args, MessagesManager msg, ConfigManager cfg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder prefixBuilder = new StringBuilder();
|
||||||
|
for (int i = 2; i < args.length; i++) {
|
||||||
|
if (i > 2) prefixBuilder.append(" ");
|
||||||
|
prefixBuilder.append(args[i]);
|
||||||
|
}
|
||||||
|
String prefix = prefixBuilder.toString();
|
||||||
|
|
||||||
|
if (prefix.length() > cfg.getMaxPrefixLength()) {
|
||||||
|
sender.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefix(prefix);
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
data.setPrefixPurchased(true);
|
||||||
|
|
||||||
|
plugin.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(plugin, data));
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleColorToggle(CommandSender sender, String playerName, boolean enable, MessagesManager msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColorEnabled(enable);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + playerName + (enable ? " §aвключён" : " §cвыключен"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePrefixToggle(CommandSender sender, String playerName, boolean enable, MessagesManager msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefixEnabled(enable);
|
||||||
|
|
||||||
|
if (enable && data.hasPrefix()) {
|
||||||
|
plugin.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(plugin, data));
|
||||||
|
} else {
|
||||||
|
plugin.getLuckPermsHook().removePrefix(target.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + playerName + (enable ? " §aвключён" : " §cвыключен"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResetColor(CommandSender sender, String playerName, MessagesManager msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setColors(new ArrayList<>());
|
||||||
|
data.setColorEnabled(false);
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aЦвет для игрока §f" + playerName + " §aсброшен");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResetPrefix(CommandSender sender, String playerName, MessagesManager msg) {
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
||||||
|
if (!target.hasPlayedBefore() && !target.isOnline()) {
|
||||||
|
sender.sendMessage(msg.get("player-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(target.getUniqueId());
|
||||||
|
data.setPrefix(null);
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
|
||||||
|
plugin.getLuckPermsHook().removePrefix(target.getUniqueId());
|
||||||
|
|
||||||
|
Player onlineTarget = target.getPlayer();
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, onlineTarget, () -> DisplayNameUtil.updateDisplayName(plugin, onlineTarget, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(target.getUniqueId()));
|
||||||
|
|
||||||
|
sender.sendMessage("§aПрефикс для игрока §f" + playerName + " §aсброшен");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (!sender.hasPermission("gradient.admin")) return new ArrayList<>();
|
||||||
|
|
||||||
|
if (args.length == 1) {
|
||||||
|
return Arrays.asList("reload", "info", "setcolor", "setprefix", "coloron", "coloroff", "prefixon", "prefixoff", "resetcolor", "resetprefix").stream()
|
||||||
|
.filter(s -> s.startsWith(args[0].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 2) {
|
||||||
|
String sub = args[0].toLowerCase();
|
||||||
|
if (Arrays.asList("info", "setcolor", "setprefix", "coloron", "coloroff", "prefixon", "prefixoff", "resetcolor", "resetprefix").contains(sub)) {
|
||||||
|
return Bukkit.getOnlinePlayers().stream()
|
||||||
|
.map(Player::getName)
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length >= 3 && args[0].equalsIgnoreCase("setcolor")) {
|
||||||
|
return PRESET_COLORS.stream()
|
||||||
|
.filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
199
surce/gradientnick/commands/PrefixCommand.java
Normal file
199
surce/gradientnick/commands/PrefixCommand.java
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
package ru.lovar.gradientnick.commands;
|
||||||
|
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.TabCompleter;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.config.ConfigManager;
|
||||||
|
import ru.lovar.gradientnick.config.MessagesManager;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.gui.ConfirmGUI;
|
||||||
|
import ru.lovar.gradientnick.util.Constants;
|
||||||
|
import ru.lovar.gradientnick.util.DisplayNameUtil;
|
||||||
|
import ru.lovar.gradientnick.util.FoliaUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class PrefixCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
|
||||||
|
|
||||||
|
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("(?i)(&[0-9a-fk-or]|§[0-9a-fk-or]|#[0-9a-f]{6}|&#[0-9a-f]{6}|<#[0-9a-f]{6}>)");
|
||||||
|
private static final Pattern ALLOWED_PREFIX_PATTERN = Pattern.compile("^[a-zA-Zа-яА-ЯёЁ0-9 _\\-]+$");
|
||||||
|
private static final Set<String> SUB_COMMANDS = Set.of("on", "off", "reset");
|
||||||
|
|
||||||
|
public PrefixCommand(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
registerSubCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerSubCommands() {
|
||||||
|
subCommands.put("on", this::handleOn);
|
||||||
|
subCommands.put("off", this::handleOff);
|
||||||
|
subCommands.put("reset", this::handleReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(plugin.getMessagesManager().get("only-player"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.prefix")) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("no-permission"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("prefix-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subCommand = args[0].toLowerCase();
|
||||||
|
var handler = subCommands.get(subCommand);
|
||||||
|
if (handler != null) {
|
||||||
|
return handler.apply(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleSetPrefix(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOn(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
|
||||||
|
if (!data.hasPrefix()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-prefix"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
plugin.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(plugin, data));
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("prefix-enabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleOff(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
plugin.getLuckPermsHook().removePrefix(player);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("prefix-disabled"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleReset(Player player, String[] args) {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
|
||||||
|
if (!data.hasPrefix()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-prefix"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setPrefix(null);
|
||||||
|
data.setPrefixEnabled(false);
|
||||||
|
plugin.getLuckPermsHook().removePrefix(player);
|
||||||
|
saveAndUpdate(player, data);
|
||||||
|
player.sendMessage(msg.get("prefix-reset-success"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleSetPrefix(Player player, String[] args) {
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
String prefix = String.join(" ", args);
|
||||||
|
|
||||||
|
if (prefix.trim().isEmpty()) {
|
||||||
|
player.sendMessage(msg.get("prefix-help"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COLOR_CODE_PATTERN.matcher(prefix).find()) {
|
||||||
|
player.sendMessage(msg.get("prefix-no-colors"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ALLOWED_PREFIX_PATTERN.matcher(prefix).matches()) {
|
||||||
|
player.sendMessage(msg.get("prefix-invalid-chars"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.isPrefixBlacklisted(prefix)) {
|
||||||
|
player.sendMessage(msg.get("prefix-blacklisted"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix.length() > cfg.getMaxPrefixLength()) {
|
||||||
|
player.sendMessage(msg.get("prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
if (!player.hasPermission("gradient.bypass.cooldown") && !checkCooldown(player, cfg.getPrefixCooldown(), data.getLastPrefixChange())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int price = calculatePrice(player, cfg, data);
|
||||||
|
|
||||||
|
if (price > 0 && !checkBalance(player, price, "not-enough-points-prefix")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmGUI gui = new ConfirmGUI(plugin, player, ConfirmGUI.ConfirmType.PREFIX, data.getColors(), prefix, price);
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, gui::open);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculatePrice(Player player, ConfigManager cfg, PlayerData data) {
|
||||||
|
if (player.hasPermission("gradient.bypass.cost")) return 0;
|
||||||
|
return (cfg.isPrefixOneTimePurchase() && data.isPrefixPurchased()) ? 0 : cfg.getPrefixPrice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCooldown(Player player, int cooldownSec, long lastChange) {
|
||||||
|
long cooldownMs = cooldownSec * Constants.MILLIS_PER_SECOND;
|
||||||
|
long timePassed = System.currentTimeMillis() - lastChange;
|
||||||
|
if (timePassed < cooldownMs) {
|
||||||
|
long remaining = (cooldownMs - timePassed) / Constants.MILLIS_PER_SECOND;
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get("cooldown", "time", String.valueOf(remaining)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBalance(Player player, int price, String msgKey) {
|
||||||
|
int balance = plugin.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(plugin.getMessagesManager().get(msgKey, "price", String.valueOf(price)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAndUpdate(Player player, PlayerData data) {
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, () -> DisplayNameUtil.updateDisplayName(plugin, player, data));
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
|
@NotNull String alias, @NotNull String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
return SUB_COMMANDS.stream()
|
||||||
|
.filter(s -> s.startsWith(args[0].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
129
surce/gradientnick/config/ConfigManager.java
Normal file
129
surce/gradientnick/config/ConfigManager.java
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package ru.lovar.gradientnick.config;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConfigManager {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private FileConfiguration config;
|
||||||
|
|
||||||
|
public ConfigManager(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
plugin.saveDefaultConfig();
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
updateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
this.config = plugin.getConfig();
|
||||||
|
updateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConfig() {
|
||||||
|
boolean needsSave = false;
|
||||||
|
|
||||||
|
// Автодополнение новых параметров
|
||||||
|
if (!config.contains("prefix-blacklist")) {
|
||||||
|
config.set("prefix-blacklist", List.of("admin", "owner", "moder", "helper", "staff"));
|
||||||
|
needsSave = true;
|
||||||
|
}
|
||||||
|
if (!config.contains("history-max-entries")) {
|
||||||
|
config.set("history-max-entries", 10);
|
||||||
|
needsSave = true;
|
||||||
|
}
|
||||||
|
if (!config.contains("history-free-apply")) {
|
||||||
|
config.set("history-free-apply", true);
|
||||||
|
needsSave = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsSave) {
|
||||||
|
plugin.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxColors() {
|
||||||
|
return config.getInt("max-colors", 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinColors() {
|
||||||
|
return config.getInt("min-colors", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPricePerColor() {
|
||||||
|
return config.getInt("price-per-color", 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPrefixPrice() {
|
||||||
|
return config.getInt("prefix-price", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrefixOneTimePurchase() {
|
||||||
|
return config.getBoolean("prefix-one-time-purchase", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAnimatedPrice() {
|
||||||
|
return config.getInt("animated-price", 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAnimatedIntervalTicks() {
|
||||||
|
return config.getInt("animated-interval-ticks", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColorCooldown() {
|
||||||
|
return config.getInt("color-cooldown", 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPrefixCooldown() {
|
||||||
|
return config.getInt("prefix-cooldown", 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxPrefixLength() {
|
||||||
|
return config.getInt("max-prefix-length", 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefixFormat() {
|
||||||
|
return config.getString("prefix-format", "[{prefix}] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGradientOnPrefix() {
|
||||||
|
return config.getBoolean("gradient-on-prefix", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isContinuousGradient() {
|
||||||
|
return config.getBoolean("continuous-gradient", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseLegacyRgbFormat() {
|
||||||
|
return config.getBoolean("use-legacy-rgb-format", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStorageType() {
|
||||||
|
return config.getString("storage-type", "YAML");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSqliteFile() {
|
||||||
|
return config.getString("sqlite-file", "data.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPrefixBlacklist() {
|
||||||
|
return config.getStringList("prefix-blacklist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrefixBlacklisted(String prefix) {
|
||||||
|
String lowerPrefix = prefix.toLowerCase();
|
||||||
|
return getPrefixBlacklist().stream()
|
||||||
|
.anyMatch(blacklisted -> lowerPrefix.contains(blacklisted.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHistoryMaxEntries() {
|
||||||
|
return config.getInt("history-max-entries", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHistoryFreeApply() {
|
||||||
|
return config.getBoolean("history-free-apply", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
surce/gradientnick/config/MessagesManager.java
Normal file
45
surce/gradientnick/config/MessagesManager.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package ru.lovar.gradientnick.config;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MessagesManager {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private FileConfiguration messages;
|
||||||
|
private File messagesFile;
|
||||||
|
|
||||||
|
public MessagesManager(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessages() {
|
||||||
|
messagesFile = new File(plugin.getDataFolder(), "messages.yml");
|
||||||
|
if (!messagesFile.exists()) {
|
||||||
|
plugin.saveResource("messages.yml", false);
|
||||||
|
}
|
||||||
|
messages = YamlConfiguration.loadConfiguration(messagesFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key) {
|
||||||
|
return messages.getString(key, "§cСообщение не найдено: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key, String... replacements) {
|
||||||
|
String message = get(key);
|
||||||
|
for (int i = 0; i < replacements.length; i += 2) {
|
||||||
|
if (i + 1 < replacements.length) {
|
||||||
|
message = message.replace("{" + replacements[i] + "}", replacements[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
166
surce/gradientnick/data/DataManager.java
Normal file
166
surce/gradientnick/data/DataManager.java
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package ru.lovar.gradientnick.data;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class DataManager {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private final Map<UUID, PlayerData> cache = new ConcurrentHashMap<>();
|
||||||
|
private Connection sqliteConnection;
|
||||||
|
private boolean useSqlite;
|
||||||
|
|
||||||
|
public DataManager(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.useSqlite = plugin.getConfigManager().getStorageType().equalsIgnoreCase("SQLITE");
|
||||||
|
|
||||||
|
if (useSqlite) {
|
||||||
|
initSqlite();
|
||||||
|
} else {
|
||||||
|
loadYamlData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSqlite() {
|
||||||
|
try {
|
||||||
|
File dbFile = new File(plugin.getDataFolder(), plugin.getConfigManager().getSqliteFile());
|
||||||
|
sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||||
|
|
||||||
|
try (Statement stmt = sqliteConnection.createStatement()) {
|
||||||
|
stmt.executeUpdate("""
|
||||||
|
CREATE TABLE IF NOT EXISTS player_data (
|
||||||
|
uuid TEXT PRIMARY KEY,
|
||||||
|
prefix TEXT,
|
||||||
|
colors TEXT,
|
||||||
|
color_enabled INTEGER DEFAULT 1,
|
||||||
|
prefix_enabled INTEGER DEFAULT 1,
|
||||||
|
prefix_purchased INTEGER DEFAULT 0,
|
||||||
|
last_color_change INTEGER DEFAULT 0,
|
||||||
|
last_prefix_change INTEGER DEFAULT 0
|
||||||
|
)
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
plugin.getLogger().info("SQLite база данных инициализирована.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().severe("Ошибка инициализации SQLite: " + e.getMessage());
|
||||||
|
useSqlite = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadYamlData() {
|
||||||
|
File dataFile = new File(plugin.getDataFolder(), "data.yml");
|
||||||
|
if (!dataFile.exists()) return;
|
||||||
|
|
||||||
|
FileConfiguration data = YamlConfiguration.loadConfiguration(dataFile);
|
||||||
|
for (String uuidStr : data.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
UUID uuid = UUID.fromString(uuidStr);
|
||||||
|
PlayerData playerData = new PlayerData(uuid);
|
||||||
|
playerData.setPrefix(data.getString(uuidStr + ".prefix"));
|
||||||
|
playerData.setColors(data.getStringList(uuidStr + ".colors"));
|
||||||
|
playerData.setColorEnabled(data.getBoolean(uuidStr + ".color-enabled", true));
|
||||||
|
playerData.setPrefixEnabled(data.getBoolean(uuidStr + ".prefix-enabled", true));
|
||||||
|
playerData.setPrefixPurchased(data.getBoolean(uuidStr + ".prefix-purchased", false));
|
||||||
|
playerData.setLastColorChange(data.getLong(uuidStr + ".last-color-change", 0));
|
||||||
|
playerData.setLastPrefixChange(data.getLong(uuidStr + ".last-prefix-change", 0));
|
||||||
|
cache.put(uuid, playerData);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerData getPlayerData(UUID uuid) {
|
||||||
|
return cache.computeIfAbsent(uuid, this::loadPlayerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerData loadPlayerData(UUID uuid) {
|
||||||
|
PlayerData data = new PlayerData(uuid);
|
||||||
|
|
||||||
|
if (useSqlite && sqliteConnection != null) {
|
||||||
|
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
|
||||||
|
"SELECT prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change FROM player_data WHERE uuid = ?")) {
|
||||||
|
stmt.setString(1, uuid.toString());
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
if (rs.next()) {
|
||||||
|
data.setPrefix(rs.getString("prefix"));
|
||||||
|
String colorsStr = rs.getString("colors");
|
||||||
|
if (colorsStr != null && !colorsStr.isEmpty()) {
|
||||||
|
data.setColors(List.of(colorsStr.split(",")));
|
||||||
|
}
|
||||||
|
data.setColorEnabled(rs.getBoolean("color_enabled"));
|
||||||
|
data.setPrefixEnabled(rs.getBoolean("prefix_enabled"));
|
||||||
|
data.setPrefixPurchased(rs.getBoolean("prefix_purchased"));
|
||||||
|
data.setLastColorChange(rs.getLong("last_color_change"));
|
||||||
|
data.setLastPrefixChange(rs.getLong("last_prefix_change"));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка загрузки данных игрока: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePlayerData(UUID uuid) {
|
||||||
|
PlayerData data = cache.get(uuid);
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
if (useSqlite && sqliteConnection != null) {
|
||||||
|
try (PreparedStatement stmt = sqliteConnection.prepareStatement(
|
||||||
|
"INSERT OR REPLACE INTO player_data (uuid, prefix, colors, color_enabled, prefix_enabled, prefix_purchased, last_color_change, last_prefix_change) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) {
|
||||||
|
stmt.setString(1, uuid.toString());
|
||||||
|
stmt.setString(2, data.getPrefix());
|
||||||
|
stmt.setString(3, data.hasColors() ? String.join(",", data.getColors()) : "");
|
||||||
|
stmt.setBoolean(4, data.isColorEnabled());
|
||||||
|
stmt.setBoolean(5, data.isPrefixEnabled());
|
||||||
|
stmt.setBoolean(6, data.isPrefixPurchased());
|
||||||
|
stmt.setLong(7, data.getLastColorChange());
|
||||||
|
stmt.setLong(8, data.getLastPrefixChange());
|
||||||
|
stmt.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка сохранения данных игрока: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAll() {
|
||||||
|
if (useSqlite) {
|
||||||
|
for (UUID uuid : cache.keySet()) {
|
||||||
|
savePlayerData(uuid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveYamlData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveYamlData() {
|
||||||
|
File dataFile = new File(plugin.getDataFolder(), "data.yml");
|
||||||
|
FileConfiguration data = new YamlConfiguration();
|
||||||
|
|
||||||
|
for (Map.Entry<UUID, PlayerData> entry : cache.entrySet()) {
|
||||||
|
String path = entry.getKey().toString();
|
||||||
|
PlayerData pd = entry.getValue();
|
||||||
|
data.set(path + ".prefix", pd.getPrefix());
|
||||||
|
data.set(path + ".colors", pd.getColors());
|
||||||
|
data.set(path + ".color-enabled", pd.isColorEnabled());
|
||||||
|
data.set(path + ".prefix-enabled", pd.isPrefixEnabled());
|
||||||
|
data.set(path + ".prefix-purchased", pd.isPrefixPurchased());
|
||||||
|
data.set(path + ".last-color-change", pd.getLastColorChange());
|
||||||
|
data.set(path + ".last-prefix-change", pd.getLastPrefixChange());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data.save(dataFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Ошибка сохранения data.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
surce/gradientnick/data/PlayerData.java
Normal file
96
surce/gradientnick/data/PlayerData.java
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package ru.lovar.gradientnick.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PlayerData {
|
||||||
|
|
||||||
|
private final UUID uuid;
|
||||||
|
private String prefix;
|
||||||
|
private List<String> colors;
|
||||||
|
private boolean colorEnabled;
|
||||||
|
private boolean prefixEnabled;
|
||||||
|
private boolean prefixPurchased;
|
||||||
|
private long lastColorChange;
|
||||||
|
private long lastPrefixChange;
|
||||||
|
|
||||||
|
public PlayerData(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.prefix = null;
|
||||||
|
this.colors = new ArrayList<>();
|
||||||
|
this.colorEnabled = true;
|
||||||
|
this.prefixEnabled = true;
|
||||||
|
this.prefixPurchased = false;
|
||||||
|
this.lastColorChange = 0;
|
||||||
|
this.lastPrefixChange = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrefix(String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getColors() {
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColors(List<String> colors) {
|
||||||
|
this.colors = colors != null ? new ArrayList<>(colors) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPrefix() {
|
||||||
|
return prefix != null && !prefix.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasColors() {
|
||||||
|
return colors != null && !colors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isColorEnabled() {
|
||||||
|
return colorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorEnabled(boolean colorEnabled) {
|
||||||
|
this.colorEnabled = colorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrefixEnabled() {
|
||||||
|
return prefixEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrefixEnabled(boolean prefixEnabled) {
|
||||||
|
this.prefixEnabled = prefixEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrefixPurchased() {
|
||||||
|
return prefixPurchased;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrefixPurchased(boolean prefixPurchased) {
|
||||||
|
this.prefixPurchased = prefixPurchased;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastColorChange() {
|
||||||
|
return lastColorChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastColorChange(long lastColorChange) {
|
||||||
|
this.lastColorChange = lastColorChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastPrefixChange() {
|
||||||
|
return lastPrefixChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastPrefixChange(long lastPrefixChange) {
|
||||||
|
this.lastPrefixChange = lastPrefixChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
surce/gradientnick/gui/ConfirmGUI.java
Normal file
165
surce/gradientnick/gui/ConfirmGUI.java
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
package ru.lovar.gradientnick.gui;
|
||||||
|
|
||||||
|
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.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.util.Constants;
|
||||||
|
import ru.lovar.gradientnick.util.GradientUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConfirmGUI implements InventoryHolder {
|
||||||
|
|
||||||
|
public enum ConfirmType {
|
||||||
|
COLOR, PREFIX
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
|
||||||
|
.character('&')
|
||||||
|
.hexCharacter('#')
|
||||||
|
.hexColors()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
private final Player player;
|
||||||
|
private final Inventory inventory;
|
||||||
|
private final ConfirmType type;
|
||||||
|
private final List<String> colors;
|
||||||
|
private final String prefix;
|
||||||
|
private final int price;
|
||||||
|
|
||||||
|
public ConfirmGUI(GradientNick plugin, Player player, ConfirmType type, List<String> colors, String prefix, int price) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.player = player;
|
||||||
|
this.type = type;
|
||||||
|
this.colors = colors;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.price = price;
|
||||||
|
this.inventory = Bukkit.createInventory(this, 27, Component.text("Подтверждение", NamedTextColor.DARK_GRAY));
|
||||||
|
setupItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupItems() {
|
||||||
|
ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ");
|
||||||
|
for (int i = 0; i < 27; i++) {
|
||||||
|
inventory.setItem(i, glass);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack head = new ItemStack(Material.PLAYER_HEAD);
|
||||||
|
SkullMeta skullMeta = (SkullMeta) head.getItemMeta();
|
||||||
|
skullMeta.setOwningPlayer(player);
|
||||||
|
|
||||||
|
String preview = buildPreview();
|
||||||
|
Component previewComponent = SERIALIZER.deserialize(preview);
|
||||||
|
skullMeta.displayName(previewComponent.decoration(TextDecoration.ITALIC, false));
|
||||||
|
|
||||||
|
List<Component> lore = new ArrayList<>();
|
||||||
|
lore.add(Component.empty());
|
||||||
|
lore.add(Component.text("Так будет выглядеть ваш ник", NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, false));
|
||||||
|
if (price > 0) {
|
||||||
|
lore.add(Component.text("Стоимость: ", NamedTextColor.GRAY)
|
||||||
|
.append(Component.text(price + " PP", NamedTextColor.GOLD))
|
||||||
|
.decoration(TextDecoration.ITALIC, false));
|
||||||
|
}
|
||||||
|
skullMeta.lore(lore);
|
||||||
|
head.setItemMeta(skullMeta);
|
||||||
|
inventory.setItem(Constants.GUI_PREVIEW_SLOT, head);
|
||||||
|
|
||||||
|
ItemStack confirm = createItem(Material.LIME_WOOL, "§a§lПодтвердить");
|
||||||
|
ItemMeta confirmMeta = confirm.getItemMeta();
|
||||||
|
List<Component> confirmLore = new ArrayList<>();
|
||||||
|
confirmLore.add(Component.empty());
|
||||||
|
confirmLore.add(Component.text("Нажмите для подтверждения", NamedTextColor.GREEN).decoration(TextDecoration.ITALIC, false));
|
||||||
|
confirmMeta.lore(confirmLore);
|
||||||
|
confirm.setItemMeta(confirmMeta);
|
||||||
|
inventory.setItem(Constants.GUI_CONFIRM_SLOT, confirm);
|
||||||
|
|
||||||
|
ItemStack cancel = createItem(Material.RED_WOOL, "§c§lОтменить");
|
||||||
|
ItemMeta cancelMeta = cancel.getItemMeta();
|
||||||
|
List<Component> cancelLore = new ArrayList<>();
|
||||||
|
cancelLore.add(Component.empty());
|
||||||
|
cancelLore.add(Component.text("Нажмите для отмены", NamedTextColor.RED).decoration(TextDecoration.ITALIC, false));
|
||||||
|
cancelMeta.lore(cancelLore);
|
||||||
|
cancel.setItemMeta(cancelMeta);
|
||||||
|
inventory.setItem(Constants.GUI_CANCEL_SLOT, cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPreview() {
|
||||||
|
String nick = player.getName();
|
||||||
|
String prefixToUse = this.prefix;
|
||||||
|
List<String> colorsToUse = this.colors;
|
||||||
|
|
||||||
|
if (type == ConfirmType.PREFIX && (colorsToUse == null || colorsToUse.isEmpty())) {
|
||||||
|
var data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
if (data.hasColors() && data.isColorEnabled()) {
|
||||||
|
colorsToUse = data.getColors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == ConfirmType.COLOR && (prefixToUse == null || prefixToUse.isEmpty())) {
|
||||||
|
var data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
if (data.hasPrefix() && data.isPrefixEnabled()) {
|
||||||
|
prefixToUse = data.getPrefix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GradientUtil.buildDisplayName(
|
||||||
|
prefixToUse,
|
||||||
|
nick,
|
||||||
|
colorsToUse,
|
||||||
|
plugin.getConfigManager().isGradientOnPrefix(),
|
||||||
|
plugin.getConfigManager().isContinuousGradient(),
|
||||||
|
plugin.getConfigManager().getPrefixFormat(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack createItem(Material material, String name) {
|
||||||
|
ItemStack item = new ItemStack(material);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
meta.displayName(LegacyComponentSerializer.legacySection().deserialize(name).decoration(TextDecoration.ITALIC, false));
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
player.openInventory(inventory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfirmType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getColors() {
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
surce/gradientnick/hooks/LuckPermsHook.java
Normal file
129
surce/gradientnick/hooks/LuckPermsHook.java
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package ru.lovar.gradientnick.hooks;
|
||||||
|
|
||||||
|
import net.luckperms.api.LuckPerms;
|
||||||
|
import net.luckperms.api.LuckPermsProvider;
|
||||||
|
import net.luckperms.api.cacheddata.CachedMetaData;
|
||||||
|
import net.luckperms.api.model.user.User;
|
||||||
|
import net.luckperms.api.node.types.PrefixNode;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.util.Constants;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class LuckPermsHook {
|
||||||
|
|
||||||
|
private LuckPerms luckPerms;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public LuckPermsHook(GradientNick plugin) {
|
||||||
|
this.enabled = false;
|
||||||
|
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
|
||||||
|
try {
|
||||||
|
this.luckPerms = LuckPermsProvider.get();
|
||||||
|
this.enabled = true;
|
||||||
|
plugin.getLogger().info("LuckPerms подключен!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("Не удалось подключить LuckPerms: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled && luckPerms != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuckPerms getApi() {
|
||||||
|
return luckPerms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает кастомный префикс игроку (приоритет 100 — выше групповых)
|
||||||
|
*/
|
||||||
|
public void setPrefix(Player player, String prefix) {
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
// Удаляем старые кастомные префиксы (приоритет 100)
|
||||||
|
user.data().clear(node ->
|
||||||
|
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == Constants.LUCKPERMS_PREFIX_PRIORITY
|
||||||
|
);
|
||||||
|
|
||||||
|
// Добавляем новый префикс если есть
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
PrefixNode prefixNode = PrefixNode.builder(prefix, Constants.LUCKPERMS_PREFIX_PRIORITY).build();
|
||||||
|
user.data().add(prefixNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
luckPerms.getUserManager().saveUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает кастомный префикс по UUID (асинхронно)
|
||||||
|
*/
|
||||||
|
public void setPrefix(UUID uuid, String prefix) {
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
luckPerms.getUserManager().loadUser(uuid).thenAccept(user -> {
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
user.data().clear(node ->
|
||||||
|
node instanceof PrefixNode && ((PrefixNode) node).getPriority() == Constants.LUCKPERMS_PREFIX_PRIORITY
|
||||||
|
);
|
||||||
|
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
PrefixNode prefixNode = PrefixNode.builder(prefix, Constants.LUCKPERMS_PREFIX_PRIORITY).build();
|
||||||
|
user.data().add(prefixNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
luckPerms.getUserManager().saveUser(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаляет кастомный префикс — будет показываться префикс от группы
|
||||||
|
*/
|
||||||
|
public void removePrefix(Player player) {
|
||||||
|
setPrefix(player, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаляет кастомный префикс по UUID
|
||||||
|
*/
|
||||||
|
public void removePrefix(UUID uuid) {
|
||||||
|
setPrefix(uuid, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает текущий активный префикс игрока (включая групповой)
|
||||||
|
*/
|
||||||
|
public String getActivePrefix(Player player) {
|
||||||
|
if (!isEnabled()) return null;
|
||||||
|
|
||||||
|
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
CachedMetaData metaData = user.getCachedData().getMetaData();
|
||||||
|
return metaData.getPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает префикс от группы (без кастомного)
|
||||||
|
*/
|
||||||
|
public String getGroupPrefix(Player player) {
|
||||||
|
if (!isEnabled()) return null;
|
||||||
|
|
||||||
|
User user = luckPerms.getUserManager().getUser(player.getUniqueId());
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
// Временно убираем кастомный префикс чтобы получить групповой
|
||||||
|
// Это сложно сделать правильно, поэтому просто возвращаем активный
|
||||||
|
// Групповой префикс будет показан когда кастомный удалён
|
||||||
|
CachedMetaData metaData = user.getCachedData().getMetaData();
|
||||||
|
return metaData.getPrefix();
|
||||||
|
}
|
||||||
|
}
|
||||||
139
surce/gradientnick/hooks/PlaceholderAPIHook.java
Normal file
139
surce/gradientnick/hooks/PlaceholderAPIHook.java
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package ru.lovar.gradientnick.hooks;
|
||||||
|
|
||||||
|
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.util.GradientUtil;
|
||||||
|
|
||||||
|
public class PlaceholderAPIHook extends PlaceholderExpansion {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
|
||||||
|
public PlaceholderAPIHook(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getIdentifier() {
|
||||||
|
return "lopreff";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getAuthor() {
|
||||||
|
return "loki";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getVersion() {
|
||||||
|
return "1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean persist() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
|
||||||
|
if (player == null) return null;
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
org.bukkit.entity.Player onlinePlayer = player.getPlayer();
|
||||||
|
|
||||||
|
return switch (params.toLowerCase()) {
|
||||||
|
case "name" -> {
|
||||||
|
if (!data.isColorEnabled() || !data.hasColors()) {
|
||||||
|
yield player.getName();
|
||||||
|
}
|
||||||
|
yield GradientUtil.applyGradient(
|
||||||
|
player.getName(),
|
||||||
|
data.getColors(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "prefix" -> {
|
||||||
|
if (!data.isPrefixEnabled() || !data.hasPrefix()) {
|
||||||
|
yield "";
|
||||||
|
}
|
||||||
|
String format = plugin.getConfigManager().getPrefixFormat();
|
||||||
|
String prefix = format.replace("{prefix}", data.getPrefix());
|
||||||
|
if (data.isColorEnabled() && data.hasColors() && plugin.getConfigManager().isGradientOnPrefix()) {
|
||||||
|
yield GradientUtil.applyGradient(prefix, data.getColors(), plugin.getConfigManager().isUseLegacyRgbFormat());
|
||||||
|
}
|
||||||
|
yield prefix;
|
||||||
|
}
|
||||||
|
case "full", "displayname" -> {
|
||||||
|
yield GradientUtil.buildDisplayName(
|
||||||
|
data.isPrefixEnabled() ? data.getPrefix() : null,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
plugin.getConfigManager().isGradientOnPrefix(),
|
||||||
|
plugin.getConfigManager().isContinuousGradient(),
|
||||||
|
plugin.getConfigManager().getPrefixFormat(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "tabname" -> {
|
||||||
|
if (!data.isColorEnabled() || !data.hasColors()) {
|
||||||
|
yield player.getName();
|
||||||
|
}
|
||||||
|
yield GradientUtil.applyGradient(
|
||||||
|
player.getName(),
|
||||||
|
data.getColors(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "tab" -> {
|
||||||
|
String prefix = null;
|
||||||
|
boolean hasCustomPrefix = data.isPrefixEnabled() && data.hasPrefix();
|
||||||
|
|
||||||
|
if (hasCustomPrefix) {
|
||||||
|
prefix = plugin.getConfigManager().getPrefixFormat()
|
||||||
|
.replace("{prefix}", data.getPrefix());
|
||||||
|
} else if (onlinePlayer != null) {
|
||||||
|
String lpPrefix = plugin.getLuckPermsHook().getActivePrefix(onlinePlayer);
|
||||||
|
if (lpPrefix != null && !lpPrefix.isEmpty()) {
|
||||||
|
prefix = stripColors(lpPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String nick = player.getName();
|
||||||
|
|
||||||
|
if (data.isColorEnabled() && data.hasColors()) {
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
String fullText = prefix + nick;
|
||||||
|
yield GradientUtil.applyGradient(fullText, data.getColors(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat());
|
||||||
|
} else {
|
||||||
|
yield GradientUtil.applyGradient(nick, data.getColors(),
|
||||||
|
plugin.getConfigManager().isUseLegacyRgbFormat());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
if (!hasCustomPrefix && onlinePlayer != null) {
|
||||||
|
String lpPrefix = plugin.getLuckPermsHook().getActivePrefix(onlinePlayer);
|
||||||
|
if (lpPrefix != null) {
|
||||||
|
yield lpPrefix + nick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield prefix + nick;
|
||||||
|
}
|
||||||
|
yield nick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "hasprefix" -> (data.isPrefixEnabled() && data.hasPrefix()) ? "true" : "false";
|
||||||
|
case "colors" -> data.hasColors() ? String.join(", ", data.getColors()) : "нет";
|
||||||
|
case "color_enabled" -> data.isColorEnabled() ? "да" : "нет";
|
||||||
|
case "prefix_enabled" -> data.isPrefixEnabled() ? "да" : "нет";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stripColors(String text) {
|
||||||
|
if (text == null) return null;
|
||||||
|
return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6})", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
86
surce/gradientnick/listeners/GUIListener.java
Normal file
86
surce/gradientnick/listeners/GUIListener.java
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
package ru.lovar.gradientnick.listeners;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.config.MessagesManager;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.gui.ConfirmGUI;
|
||||||
|
import ru.lovar.gradientnick.util.Constants;
|
||||||
|
import ru.lovar.gradientnick.util.DisplayNameUtil;
|
||||||
|
import ru.lovar.gradientnick.util.FoliaUtil;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class GUIListener implements Listener {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
|
||||||
|
private static final Map<ConfirmGUI.ConfirmType, String> PRICE_MSG_KEYS = Map.of(
|
||||||
|
ConfirmGUI.ConfirmType.COLOR, "not-enough-points-color",
|
||||||
|
ConfirmGUI.ConfirmType.PREFIX, "not-enough-points-prefix"
|
||||||
|
);
|
||||||
|
|
||||||
|
public GUIListener(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!(event.getInventory().getHolder() instanceof ConfirmGUI gui)) return;
|
||||||
|
|
||||||
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
if (!(event.getWhoClicked() instanceof Player player) || !player.equals(gui.getPlayer())) return;
|
||||||
|
|
||||||
|
switch (event.getRawSlot()) {
|
||||||
|
case Constants.GUI_CONFIRM_SLOT -> handleConfirm(player, gui);
|
||||||
|
case Constants.GUI_CANCEL_SLOT -> handleCancel(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConfirm(Player player, ConfirmGUI gui) {
|
||||||
|
player.closeInventory();
|
||||||
|
|
||||||
|
MessagesManager msg = plugin.getMessagesManager();
|
||||||
|
int price = gui.getPrice();
|
||||||
|
|
||||||
|
if (price > 0 && !player.hasPermission("gradient.bypass.cost")) {
|
||||||
|
int balance = plugin.getPlayerPointsAPI().look(player.getUniqueId());
|
||||||
|
if (balance < price) {
|
||||||
|
player.sendMessage(msg.get(PRICE_MSG_KEYS.get(gui.getType()), "price", String.valueOf(price)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.getPlayerPointsAPI().take(player.getUniqueId(), price);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
switch (gui.getType()) {
|
||||||
|
case COLOR -> {
|
||||||
|
data.setColors(gui.getColors());
|
||||||
|
data.setColorEnabled(true);
|
||||||
|
data.setLastColorChange(System.currentTimeMillis());
|
||||||
|
player.sendMessage(msg.get("color-success", "price", String.valueOf(price)));
|
||||||
|
}
|
||||||
|
case PREFIX -> {
|
||||||
|
data.setPrefix(gui.getPrefix());
|
||||||
|
data.setPrefixEnabled(true);
|
||||||
|
data.setPrefixPurchased(true);
|
||||||
|
data.setLastPrefixChange(System.currentTimeMillis());
|
||||||
|
plugin.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(plugin, data));
|
||||||
|
player.sendMessage(msg.get(price > 0 ? "prefix-success" : "prefix-success-free", "price", String.valueOf(price)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, () -> DisplayNameUtil.updateDisplayName(plugin, player, data));
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCancel(Player player) {
|
||||||
|
player.closeInventory();
|
||||||
|
player.sendMessage("§7Действие отменено.");
|
||||||
|
}
|
||||||
|
}
|
||||||
51
surce/gradientnick/listeners/PlayerListener.java
Normal file
51
surce/gradientnick/listeners/PlayerListener.java
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ru.lovar.gradientnick.listeners;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
import ru.lovar.gradientnick.util.DisplayNameUtil;
|
||||||
|
import ru.lovar.gradientnick.util.FoliaUtil;
|
||||||
|
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
|
private final GradientNick plugin;
|
||||||
|
|
||||||
|
public PlayerListener(GradientNick plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
// Загружаем данные асинхронно, применяем синхронно
|
||||||
|
FoliaUtil.runAsync(plugin, () -> {
|
||||||
|
PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId());
|
||||||
|
|
||||||
|
FoliaUtil.runEntityTask(plugin, player, () -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
|
||||||
|
// Всегда обновляем displayName при заходе
|
||||||
|
DisplayNameUtil.updateDisplayName(plugin, player, data);
|
||||||
|
|
||||||
|
// Устанавливаем кастомный префикс в LuckPerms если включён
|
||||||
|
if (data.hasPrefix() && data.isPrefixEnabled()) {
|
||||||
|
plugin.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(plugin, data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
// Сохраняем данные при выходе
|
||||||
|
FoliaUtil.runAsync(plugin, () -> plugin.getDataManager().savePlayerData(player.getUniqueId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
surce/gradientnick/util/Constants.java
Normal file
20
surce/gradientnick/util/Constants.java
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package ru.lovar.gradientnick.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Константы плагина
|
||||||
|
*/
|
||||||
|
public final class Constants {
|
||||||
|
|
||||||
|
private Constants() {}
|
||||||
|
|
||||||
|
// Время
|
||||||
|
public static final long MILLIS_PER_SECOND = 1000L;
|
||||||
|
|
||||||
|
// GUI слоты
|
||||||
|
public static final int GUI_CONFIRM_SLOT = 11;
|
||||||
|
public static final int GUI_CANCEL_SLOT = 15;
|
||||||
|
public static final int GUI_PREVIEW_SLOT = 13;
|
||||||
|
|
||||||
|
// LuckPerms
|
||||||
|
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
|
||||||
|
}
|
||||||
115
surce/gradientnick/util/DisplayNameUtil.java
Normal file
115
surce/gradientnick/util/DisplayNameUtil.java
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
package ru.lovar.gradientnick.util;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import ru.lovar.gradientnick.GradientNick;
|
||||||
|
import ru.lovar.gradientnick.config.ConfigManager;
|
||||||
|
import ru.lovar.gradientnick.data.PlayerData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для обновления отображаемого имени игрока
|
||||||
|
*/
|
||||||
|
public final class DisplayNameUtil {
|
||||||
|
|
||||||
|
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
|
||||||
|
.character('&')
|
||||||
|
.hexCharacter('#')
|
||||||
|
.hexColors()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private DisplayNameUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет displayName и playerListName игрока
|
||||||
|
*/
|
||||||
|
public static void updateDisplayName(GradientNick plugin, Player player, PlayerData data) {
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
|
||||||
|
// Определяем префикс: кастомный или групповой от LuckPerms
|
||||||
|
String prefix = null;
|
||||||
|
String prefixFormat = cfg.getPrefixFormat();
|
||||||
|
|
||||||
|
if (data.isPrefixEnabled() && data.hasPrefix()) {
|
||||||
|
// Кастомный префикс включён
|
||||||
|
prefix = data.getPrefix();
|
||||||
|
} else {
|
||||||
|
// Кастомный выключен — берём групповой из LuckPerms
|
||||||
|
String lpPrefix = plugin.getLuckPermsHook().getActivePrefix(player);
|
||||||
|
if (lpPrefix != null && !lpPrefix.isEmpty()) {
|
||||||
|
// Групповой префикс — применяем градиент на всё (префикс + ник)
|
||||||
|
String displayName = buildWithLuckPermsPrefix(
|
||||||
|
lpPrefix,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
cfg.isUseLegacyRgbFormat(),
|
||||||
|
cfg.isContinuousGradient()
|
||||||
|
);
|
||||||
|
var component = SERIALIZER.deserialize(displayName);
|
||||||
|
player.displayName(component);
|
||||||
|
player.playerListName(component);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String displayName = GradientUtil.buildDisplayName(
|
||||||
|
prefix,
|
||||||
|
player.getName(),
|
||||||
|
data.isColorEnabled() ? data.getColors() : null,
|
||||||
|
cfg.isGradientOnPrefix(),
|
||||||
|
cfg.isContinuousGradient(),
|
||||||
|
prefixFormat,
|
||||||
|
cfg.isUseLegacyRgbFormat()
|
||||||
|
);
|
||||||
|
var component = SERIALIZER.deserialize(displayName);
|
||||||
|
player.displayName(component);
|
||||||
|
player.playerListName(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Строит displayName с префиксом от LuckPerms
|
||||||
|
* Если есть цвета и continuousGradient — применяем единый градиент на всё
|
||||||
|
*/
|
||||||
|
private static String buildWithLuckPermsPrefix(String lpPrefix, String nick,
|
||||||
|
java.util.List<String> colors,
|
||||||
|
boolean useLegacyFormat,
|
||||||
|
boolean continuousGradient) {
|
||||||
|
if (colors == null || colors.isEmpty()) {
|
||||||
|
// Нет цветов — просто префикс + ник
|
||||||
|
return lpPrefix + nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continuousGradient) {
|
||||||
|
// Единый градиент на префикс + ник
|
||||||
|
// Убираем цветовые коды из LP префикса чтобы применить наш градиент
|
||||||
|
String cleanPrefix = stripColors(lpPrefix);
|
||||||
|
String fullText = cleanPrefix + nick;
|
||||||
|
return GradientUtil.applyGradient(fullText, colors, useLegacyFormat);
|
||||||
|
} else {
|
||||||
|
// Градиент только на ник, префикс от LP оставляем
|
||||||
|
return lpPrefix + GradientUtil.applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Убирает цветовые коды из строки
|
||||||
|
*/
|
||||||
|
private static String stripColors(String text) {
|
||||||
|
if (text == null) return null;
|
||||||
|
// Убираем §x§R§R§G§G§B§B, §c, &c, &#RRGGBB форматы
|
||||||
|
return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6})", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Строит цветной префикс для LuckPerms
|
||||||
|
*/
|
||||||
|
public static String buildColoredPrefix(GradientNick plugin, PlayerData data) {
|
||||||
|
if (!data.hasPrefix() || !data.isPrefixEnabled()) return null;
|
||||||
|
|
||||||
|
ConfigManager cfg = plugin.getConfigManager();
|
||||||
|
String prefix = cfg.getPrefixFormat().replace("{prefix}", data.getPrefix()).stripTrailing();
|
||||||
|
|
||||||
|
return (data.hasColors() && data.isColorEnabled() && cfg.isGradientOnPrefix())
|
||||||
|
? GradientUtil.applyGradient(prefix, data.getColors(), cfg.isUseLegacyRgbFormat())
|
||||||
|
: prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
surce/gradientnick/util/FoliaUtil.java
Normal file
101
surce/gradientnick/util/FoliaUtil.java
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
package ru.lovar.gradientnick.util;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Утилита для работы с Folia и Paper 1.21.8+
|
||||||
|
* Обеспечивает потокобезопасность при работе с игроками
|
||||||
|
*/
|
||||||
|
public final class FoliaUtil {
|
||||||
|
|
||||||
|
private static final boolean IS_FOLIA;
|
||||||
|
|
||||||
|
static {
|
||||||
|
IS_FOLIA = checkFolia();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FoliaUtil() {}
|
||||||
|
|
||||||
|
private static boolean checkFolia() {
|
||||||
|
try {
|
||||||
|
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFolia() {
|
||||||
|
return IS_FOLIA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет задачу в контексте сущности (потокобезопасно для Folia)
|
||||||
|
*/
|
||||||
|
public static void runEntityTask(Plugin plugin, Entity entity, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет задачу в контексте сущности с задержкой
|
||||||
|
*/
|
||||||
|
public static void runEntityTaskLater(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
entity.getScheduler().runDelayed(plugin, scheduledTask -> task.run(), null, delayTicks);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет задачу асинхронно
|
||||||
|
*/
|
||||||
|
public static void runAsync(Plugin plugin, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет задачу асинхронно с задержкой
|
||||||
|
*/
|
||||||
|
public static void runAsyncLater(Plugin plugin, Runnable task, long delayMs) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayMs, TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, task, delayMs / 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет глобальную задачу (для Folia — на глобальном регионе)
|
||||||
|
*/
|
||||||
|
public static void runGlobalTask(Plugin plugin, Runnable task) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> task.run());
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет глобальную задачу с задержкой
|
||||||
|
*/
|
||||||
|
public static void runGlobalTaskLater(Plugin plugin, Runnable task, long delayTicks) {
|
||||||
|
if (IS_FOLIA) {
|
||||||
|
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayTicks);
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
surce/gradientnick/util/GradientUtil.java
Normal file
129
surce/gradientnick/util/GradientUtil.java
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package ru.lovar.gradientnick.util;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class GradientUtil {
|
||||||
|
|
||||||
|
private static final Pattern HEX_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
|
||||||
|
|
||||||
|
public static boolean isValidHex(String color) {
|
||||||
|
return HEX_PATTERN.matcher(color).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String applyGradient(String text, List<String> hexColors, boolean useLegacyFormat) {
|
||||||
|
if (text == null || text.isEmpty() || hexColors == null || hexColors.isEmpty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] chars = text.toCharArray();
|
||||||
|
int length = chars.length;
|
||||||
|
|
||||||
|
if (length == 0) return text;
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
if (hexColors.size() == 1) {
|
||||||
|
// Один цвет — весь текст одним цветом
|
||||||
|
String colorCode = formatHex(hexColors.get(0), useLegacyFormat);
|
||||||
|
for (char c : chars) {
|
||||||
|
result.append(colorCode).append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Градиент из нескольких цветов
|
||||||
|
Color[] colors = hexColors.stream()
|
||||||
|
.map(GradientUtil::parseHex)
|
||||||
|
.toArray(Color[]::new);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Color interpolated = interpolateMultiColor(colors, (double) i / Math.max(1, length - 1));
|
||||||
|
String hex = String.format("#%02x%02x%02x",
|
||||||
|
interpolated.getRed(), interpolated.getGreen(), interpolated.getBlue());
|
||||||
|
result.append(formatHex(hex, useLegacyFormat)).append(chars[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color interpolateMultiColor(Color[] colors, double ratio) {
|
||||||
|
if (colors.length == 1) return colors[0];
|
||||||
|
if (ratio <= 0) return colors[0];
|
||||||
|
if (ratio >= 1) return colors[colors.length - 1];
|
||||||
|
|
||||||
|
// Определяем между какими двумя цветами находимся
|
||||||
|
double scaledRatio = ratio * (colors.length - 1);
|
||||||
|
int index = (int) scaledRatio;
|
||||||
|
double localRatio = scaledRatio - index;
|
||||||
|
|
||||||
|
if (index >= colors.length - 1) {
|
||||||
|
return colors[colors.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolate(colors[index], colors[index + 1], localRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color interpolate(Color c1, Color c2, double ratio) {
|
||||||
|
int r = (int) (c1.getRed() + (c2.getRed() - c1.getRed()) * ratio);
|
||||||
|
int g = (int) (c1.getGreen() + (c2.getGreen() - c1.getGreen()) * ratio);
|
||||||
|
int b = (int) (c1.getBlue() + (c2.getBlue() - c1.getBlue()) * ratio);
|
||||||
|
return new Color(
|
||||||
|
Math.max(0, Math.min(255, r)),
|
||||||
|
Math.max(0, Math.min(255, g)),
|
||||||
|
Math.max(0, Math.min(255, b))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color parseHex(String hex) {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
return new Color(
|
||||||
|
Integer.parseInt(hex.substring(0, 2), 16),
|
||||||
|
Integer.parseInt(hex.substring(2, 4), 16),
|
||||||
|
Integer.parseInt(hex.substring(4, 6), 16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatHex(String hex, boolean useLegacyFormat) {
|
||||||
|
hex = hex.replace("#", "").toLowerCase();
|
||||||
|
if (useLegacyFormat) {
|
||||||
|
// &#RRGGBB формат
|
||||||
|
return "&#" + hex;
|
||||||
|
} else {
|
||||||
|
// <#RRGGBB> формат для MiniMessage
|
||||||
|
return "<#" + hex + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildDisplayName(String prefix, String nick, List<String> colors,
|
||||||
|
boolean gradientOnPrefix, boolean continuousGradient,
|
||||||
|
String prefixFormat, boolean useLegacyFormat) {
|
||||||
|
if (colors == null || colors.isEmpty()) {
|
||||||
|
// Нет цветов — возвращаем как есть
|
||||||
|
if (prefix != null && !prefix.isEmpty()) {
|
||||||
|
return prefixFormat.replace("{prefix}", prefix) + nick;
|
||||||
|
}
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix == null || prefix.isEmpty()) {
|
||||||
|
// Нет префикса — градиент только на ник
|
||||||
|
return applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continuousGradient && gradientOnPrefix) {
|
||||||
|
// Единый градиент на префикс + ник
|
||||||
|
String fullText = prefixFormat.replace("{prefix}", prefix) + nick;
|
||||||
|
return applyGradient(fullText, colors, useLegacyFormat);
|
||||||
|
} else if (gradientOnPrefix) {
|
||||||
|
// Градиент отдельно на префикс и ник
|
||||||
|
String coloredPrefix = applyGradient(prefixFormat.replace("{prefix}", prefix), colors, useLegacyFormat);
|
||||||
|
String coloredNick = applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
return coloredPrefix + coloredNick;
|
||||||
|
} else {
|
||||||
|
// Градиент только на ник
|
||||||
|
String formattedPrefix = prefixFormat.replace("{prefix}", prefix);
|
||||||
|
return formattedPrefix + applyGradient(nick, colors, useLegacyFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue