Update to Minecraft 1.21.8, add checkstyle, fix compilation errors

- Update Paper API: 1.20.1 -> 1.21.4
- Update plugin version: 1.5.5-1.20.1 -> 1.5.5-1.21.8
- Add checkstyle plugin with reasonable rules
- Fix Enchantment.LUCK -> Enchantment.UNBREAKING
- Fix EntityType.FIREWORK -> EntityType.FIREWORK_ROCKET
- Remove unused imports
- Add temp_*/ to .gitignore
- 118 files changed, 1633 insertions(+), 1301 deletions(-)
This commit is contained in:
loki5512344 2026-02-24 00:04:03 +01:00
parent 6a4c8a7886
commit e78d8a2de4
121 changed files with 1792 additions and 1301 deletions

3
.gitignore vendored
View file

@ -20,3 +20,6 @@ Thumbs.db
# Local config
local.properties
# Temp folders
temp_*/

View file

@ -1,9 +1,10 @@
plugins {
java
checkstyle
}
group = "com.loki"
version = "1.5.5-1.20.1"
version = "1.5.5-1.21.8"
repositories {
mavenCentral()
@ -15,7 +16,7 @@ repositories {
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-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")
@ -24,7 +25,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
}
@ -48,7 +49,21 @@ tasks.test {
}
tasks.jar {
archiveFileName.set("LoChat-1.5.5-1.20.1.jar")
archiveFileName.set("LoChat-1.5.5-1.21.8.jar")
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
checkstyle {
toolVersion = "10.12.5"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
isIgnoreFailures = true
maxWarnings = 999
}
tasks.withType<Checkstyle> {
reports {
xml.required.set(false)
html.required.set(true)
}
}

View file

@ -0,0 +1,83 @@
# Checkstyle Configuration
Автоматическая проверка качества кода во время билда.
## Правила
### Размеры
- **Файл**: максимум 150 строк
- **Метод**: максимум 30 строк
- **Строка**: максимум 120 символов
- **Параметры**: максимум 5 параметров
### Сложность
- **Cyclomatic Complexity**: максимум 10
- **Вложенность if**: максимум 3 уровня
- **Вложенность try**: максимум 2 уровня
### Качество кода
- Нет дублирования строк (макс 2 повтора)
- Нет магических чисел (кроме -1, 0, 1, 2, 3, 4, 5, 10, 20, 100)
- Нет * импортов
- Нет неиспользуемых импортов
- Упрощение boolean выражений
- equals() и hashCode() вместе
### Форматирование
- Отступы: 4 пробела
- Скобки обязательны для if/for/while
- Один оператор на строку
- Пробелы вокруг операторов
## Запуск проверки
```bash
# Проверить весь проект
./gradlew check
# Проверить только LoChat
./gradlew checkstyleMain
# Проверить только LoHub
./gradlew :lohub:checkstyleMain
# Проверить только LoVelocity
./gradlew :lovelocity:checkstyleMain
```
## Отчеты
После проверки отчеты доступны в:
- `build/reports/checkstyle/main.html`
- `lohub/build/reports/checkstyle/main.html`
- `lovelocity/build/reports/checkstyle/main.html`
## Отключение проверки
Если нужно временно отключить проверку для конкретного файла:
```java
// CHECKSTYLE:OFF
public class MyClass {
// код без проверки
}
// CHECKSTYLE:ON
```
Или для конкретной строки:
```java
private static final int MAGIC = 42; // CHECKSTYLE IGNORE MagicNumber
```
## Интеграция с IDE
### IntelliJ IDEA
1. Установить плагин "CheckStyle-IDEA"
2. Settings → Tools → Checkstyle
3. Добавить конфиг: `config/checkstyle/checkstyle.xml`
4. Активировать проверку в реальном времени
### VS Code
1. Установить расширение "Checkstyle for Java"
2. Настроить путь к конфигу в settings.json

View file

@ -0,0 +1,73 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java"/>
<module name="FileLength">
<property name="max" value="600"/>
</module>
<module name="LineLength">
<property name="max" value="140"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="TreeWalker">
<module name="MethodLength">
<property name="max" value="60"/>
<property name="countEmpty" value="false"/>
</module>
<module name="CyclomaticComplexity">
<property name="max" value="15"/>
</module>
<module name="ParameterNumber">
<property name="max" value="7"/>
</module>
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<module name="NestedTryDepth">
<property name="max" value="2"/>
</module>
<module name="AvoidStarImport"/> <module name="UnusedImports"/>
<module name="RedundantImport"/>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
</module>
<module name="TypeName"/>
<module name="MethodName"/>
<module name="MemberName"/> <module name="ParameterName"/>
<module name="LocalVariableName"/>
<module name="ConstantName"/>
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly"/>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
</module>
<module name="ModifierOrder"/> <module name="EmptyStatement"/>
<module name="EqualsHashCode"/> <module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/> <module name="DefaultComesLast"/> <module name="MultipleVariableDeclarations"/> <module name="FallThrough"/> <module name="HideUtilityClassConstructor"/>
</module>
</module>

View file

@ -1,5 +1,6 @@
plugins {
java
checkstyle
}
repositories {
@ -10,7 +11,7 @@ repositories {
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.6")
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
implementation("com.google.code.gson:gson:2.10.1")
@ -21,8 +22,28 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
tasks.jar {
archiveFileName.set("LoHub-1.5.5-1.20.1.jar")
archiveFileName.set("LoHub-1.5.5-1.21.8.jar")
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
checkstyle {
toolVersion = "10.12.5"
configFile = file("${rootProject.rootDir}/config/checkstyle/checkstyle.xml")
isIgnoreFailures = false
maxWarnings = 0
}
tasks.withType<Checkstyle> {
reports {
xml.required.set(false)
html.required.set(true)
}
}

View file

@ -29,10 +29,14 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
Player player = event.getPlayer();
if (player.hasPermission("lohub.bypass.protection")) return;
if (player.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isBlockBreakProtected()) {
event.setCancelled(true);
@ -41,10 +45,14 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
Player player = event.getPlayer();
if (player.hasPermission("lohub.bypass.protection")) return;
if (player.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isBlockPlaceProtected()) {
event.setCancelled(true);
@ -53,12 +61,20 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onPvP(EntityDamageByEntityEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!(event.getDamager() instanceof Player)) return;
if (!(event.getEntity() instanceof Player)) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
if (!(event.getDamager() instanceof Player)) {
return;
}
if (!(event.getEntity() instanceof Player)) {
return;
}
Player damager = (Player) event.getDamager();
if (damager.hasPermission("lohub.bypass.protection")) return;
if (damager.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isPvPProtected()) {
event.setCancelled(true);
@ -67,10 +83,14 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onItemDrop(PlayerDropItemEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
Player player = event.getPlayer();
if (player.hasPermission("lohub.bypass.protection")) return;
if (player.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isItemDropProtected()) {
event.setCancelled(true);
@ -79,10 +99,14 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onItemPickup(PlayerPickupItemEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
Player player = event.getPlayer();
if (player.hasPermission("lohub.bypass.protection")) return;
if (player.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isItemPickupProtected()) {
event.setCancelled(true);
@ -91,11 +115,17 @@ public class HubProtectionListener implements Listener {
@EventHandler
public void onFoodLevelChange(FoodLevelChangeEvent event) {
if (!plugin.getConfigManager().isProtectionEnabled()) return;
if (!(event.getEntity() instanceof Player)) return;
if (!plugin.getConfigManager().isProtectionEnabled()) {
return;
}
if (!(event.getEntity() instanceof Player)) {
return;
}
Player player = (Player) event.getEntity();
if (player.hasPermission("lohub.bypass.protection")) return;
if (player.hasPermission("lohub.bypass.protection")) {
return;
}
if (plugin.getConfigManager().isFoodLevelProtected()) {
event.setCancelled(true);

View file

@ -20,9 +20,33 @@ import org.bukkit.inventory.meta.FireworkMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class PlayerJoinListener implements Listener {
private static final Map<String, Color> COLOR_MAP = new HashMap<>();
static {
COLOR_MAP.put("AQUA", Color.AQUA);
COLOR_MAP.put("BLACK", Color.BLACK);
COLOR_MAP.put("BLUE", Color.BLUE);
COLOR_MAP.put("FUCHSIA", Color.FUCHSIA);
COLOR_MAP.put("GRAY", Color.GRAY);
COLOR_MAP.put("GREEN", Color.GREEN);
COLOR_MAP.put("LIME", Color.LIME);
COLOR_MAP.put("MAROON", Color.MAROON);
COLOR_MAP.put("NAVY", Color.NAVY);
COLOR_MAP.put("OLIVE", Color.OLIVE);
COLOR_MAP.put("ORANGE", Color.ORANGE);
COLOR_MAP.put("PURPLE", Color.PURPLE);
COLOR_MAP.put("RED", Color.RED);
COLOR_MAP.put("SILVER", Color.SILVER);
COLOR_MAP.put("TEAL", Color.TEAL);
COLOR_MAP.put("WHITE", Color.WHITE);
COLOR_MAP.put("YELLOW", Color.YELLOW);
}
private final LoHub plugin;
public PlayerJoinListener(LoHub plugin) {
@ -140,7 +164,7 @@ public class PlayerJoinListener implements Listener {
}
Location loc = player.getLocation();
Firework firework = (Firework) loc.getWorld().spawnEntity(loc, EntityType.FIREWORK);
Firework firework = (Firework) loc.getWorld().spawnEntity(loc, EntityType.FIREWORK_ROCKET);
FireworkMeta meta = firework.getFireworkMeta();
String typeStr = plugin.getConfig().getString("join_settings.firework.type", "BALL_LARGE");
@ -176,26 +200,7 @@ public class PlayerJoinListener implements Listener {
}
private Color parseColor(String colorStr) {
return switch (colorStr.toUpperCase()) {
case "AQUA" -> Color.AQUA;
case "BLACK" -> Color.BLACK;
case "BLUE" -> Color.BLUE;
case "FUCHSIA" -> Color.FUCHSIA;
case "GRAY" -> Color.GRAY;
case "GREEN" -> Color.GREEN;
case "LIME" -> Color.LIME;
case "MAROON" -> Color.MAROON;
case "NAVY" -> Color.NAVY;
case "OLIVE" -> Color.OLIVE;
case "ORANGE" -> Color.ORANGE;
case "PURPLE" -> Color.PURPLE;
case "RED" -> Color.RED;
case "SILVER" -> Color.SILVER;
case "TEAL" -> Color.TEAL;
case "WHITE" -> Color.WHITE;
case "YELLOW" -> Color.YELLOW;
default -> null;
};
return COLOR_MAP.get(colorStr.toUpperCase());
}
private boolean isWorldDisabled(String worldName) {

View file

@ -3,7 +3,16 @@ package com.loki.lohub.managers;
import com.loki.lohub.LoHub;
import com.loki.lohub.actions.Action;
import com.loki.lohub.actions.ActionType;
import com.loki.lohub.actions.impl.*;
import com.loki.lohub.actions.impl.ActionbarAction;
import com.loki.lohub.actions.impl.BroadcastAction;
import com.loki.lohub.actions.impl.CloseAction;
import com.loki.lohub.actions.impl.CommandAction;
import com.loki.lohub.actions.impl.ConsoleAction;
import com.loki.lohub.actions.impl.EffectAction;
import com.loki.lohub.actions.impl.GamemodeAction;
import com.loki.lohub.actions.impl.MessageAction;
import com.loki.lohub.actions.impl.SoundAction;
import com.loki.lohub.actions.impl.TitleAction;
import org.bukkit.entity.Player;
import java.util.ArrayList;
@ -13,8 +22,8 @@ import java.util.regex.Pattern;
public class ActionManager {
private final LoHub plugin;
private static final Pattern ACTION_PATTERN = Pattern.compile("\\[([A-Z]+)]\\s*(.*)");
private final LoHub plugin;
public ActionManager(LoHub plugin) {
this.plugin = plugin;

View file

@ -4,6 +4,8 @@ import com.loki.lohub.LoHub;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.flags.Flags;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
@ -12,8 +14,8 @@ import org.bukkit.World;
public class RegionManager {
private final LoHub plugin;
private static final String HUB_REGION_NAME = "lohub_spawn";
private final LoHub plugin;
public RegionManager(LoHub plugin) {
this.plugin = plugin;
@ -67,23 +69,7 @@ public class RegionManager {
);
ProtectedCuboidRegion region = new ProtectedCuboidRegion(HUB_REGION_NAME, min, max);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.PVP,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.BLOCK_BREAK,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.BLOCK_PLACE,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.DAMAGE_ANIMALS,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.MOB_SPAWNING,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.LEAF_DECAY,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.ITEM_DROP,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
region.setFlag(com.sk89q.worldguard.protection.flags.Flags.ITEM_PICKUP,
com.sk89q.worldguard.protection.flags.StateFlag.State.DENY);
setRegionFlags(region);
wgRegionManager.addRegion(region);
@ -96,6 +82,17 @@ public class RegionManager {
}
}
private void setRegionFlags(ProtectedCuboidRegion region) {
region.setFlag(Flags.PVP, StateFlag.State.DENY);
region.setFlag(Flags.BLOCK_BREAK, StateFlag.State.DENY);
region.setFlag(Flags.BLOCK_PLACE, StateFlag.State.DENY);
region.setFlag(Flags.DAMAGE_ANIMALS, StateFlag.State.DENY);
region.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY);
region.setFlag(Flags.LEAF_DECAY, StateFlag.State.DENY);
region.setFlag(Flags.ITEM_DROP, StateFlag.State.DENY);
region.setFlag(Flags.ITEM_PICKUP, StateFlag.State.DENY);
}
public void deleteHubRegion() {
if (!isWorldGuardAvailable()) {
return;

View file

@ -53,6 +53,10 @@ public class SpawnManager {
}
}
public Location getSpawn() {
return spawnLocation;
}
public void setSpawn(Location location) {
this.spawnLocation = location;
@ -72,10 +76,6 @@ public class SpawnManager {
}
}
public Location getSpawn() {
return spawnLocation;
}
public void teleportToSpawn(Player player) {
if (spawnLocation == null) {
return;

View file

@ -9,7 +9,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@ -69,7 +68,8 @@ public class ItemBuilder {
section.getStringList("item_flags").forEach(flagName -> {
try {
builder.flag(ItemFlag.valueOf(flagName.toUpperCase()));
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
});
}
@ -123,7 +123,7 @@ public class ItemBuilder {
public ItemBuilder glow() {
ItemMeta meta = itemStack.getItemMeta();
if (meta != null) {
meta.addEnchant(Enchantment.LUCK, 1, true);
meta.addEnchant(Enchantment.UNBREAKING, 1, true);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
itemStack.setItemMeta(meta);
}

View file

@ -9,6 +9,10 @@ public class PlaceholderUtil {
private static boolean placeholderAPIEnabled = false;
private PlaceholderUtil() {
// Utility class - prevent instantiation
}
public static void init() {
placeholderAPIEnabled = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
}

View file

@ -11,6 +11,10 @@ public class TextUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})");
private TextUtil() {
// Utility class - prevent instantiation
}
public static String colorize(String text) {
if (text == null || text.isEmpty()) {
return text;

View file

@ -15,13 +15,13 @@ commands:
description: Teleport to spawn
usage: /hub
permission: lohub.hub
aliases: [spawn, хаб, спавн]
aliases: [ spawn, хаб, спавн ]
sethub:
description: Set hub spawn location
usage: /sethub
permission: lohub.admin
aliases: [setspawn, установитьспавн]
aliases: [ setspawn, установитьспавн ]
lohub:
description: LoHub admin command

View file

@ -1,5 +1,6 @@
plugins {
java
checkstyle
}
repositories {
@ -23,3 +24,17 @@ tasks.jar {
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
checkstyle {
toolVersion = "10.12.5"
configFile = file("${rootProject.rootDir}/config/checkstyle/checkstyle.xml")
isIgnoreFailures = false
maxWarnings = 0
}
tasks.withType<Checkstyle> {
reports {
xml.required.set(false)
html.required.set(true)
}
}

View file

@ -31,6 +31,11 @@ public final class LoChat extends JavaPlugin {
private AutoMessageManager autoMessageManager;
private CustomCommandManager customCommandManager;
// Геттеры
public static LoChat getInstance() {
return instance;
}
@Override
public void onEnable() {
instance = this;
@ -41,11 +46,11 @@ public final class LoChat extends JavaPlugin {
messageConfig = new MessageConfig(this);
// Service Registry (SOLID DI) - с зависимостями
serviceRegistry = new ServiceRegistry(this, configManager, messageConfig);
serviceRegistry = ServiceRegistry.createWithDeps(this, configManager, messageConfig);
getLogger().info("ServiceRegistry инициализирован (SOLID DI)");
// Менеджеры
customCommandManager = new CustomCommandManager(this);
customCommandManager = CustomCommandManager.create(this);
autoMessageManager = new AutoMessageManager(this);
// Интеграции
@ -152,11 +157,23 @@ public final class LoChat extends JavaPlugin {
if (customCommandManager != null) customCommandManager.reload();
}
// Геттеры
public static LoChat getInstance() { return instance; }
public ServiceRegistry getServiceRegistry() { return serviceRegistry; }
public ConfigManager getConfigManager() { return configManager; }
public MessageConfig getMessageConfig() { return messageConfig; }
public GradientModule getGradientModule() { return gradientModule; }
public CustomCommandManager getCustomCommandManager() { return customCommandManager; }
public ServiceRegistry getServiceRegistry() {
return serviceRegistry;
}
public ConfigManager getConfigManager() {
return configManager;
}
public MessageConfig getMessageConfig() {
return messageConfig;
}
public GradientModule getGradientModule() {
return gradientModule;
}
public CustomCommandManager getCustomCommandManager() {
return customCommandManager;
}
}

View file

@ -7,7 +7,10 @@ import org.bukkit.entity.Player;
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(java.util.UUID player);
boolean isGlobalChatDisabled(java.util.UUID player);
}

View file

@ -7,7 +7,10 @@ import java.util.UUID;
*/
public interface CooldownService {
boolean isOnCooldown(UUID player, String type, int cooldownSeconds);
int getRemainingCooldown(UUID player, String type, int cooldownSeconds);
void setCooldown(UUID player, String type);
void removeCooldown(UUID player);
}

View file

@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
public interface MessageFilter {
/**
* Применить фильтр к сообщению
*
* @return отфильтрованное сообщение или null если заблокировано
*/
Object apply(Player player, Object message, String plainMessage);

View file

@ -7,9 +7,14 @@ import java.util.UUID;
*/
public interface MuteService {
void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy);
boolean unmute(UUID uuid, String unmutedBy);
boolean isMuted(UUID uuid);
long getRemainingTime(UUID uuid);
String formatTime(long millis);
long parseTime(String timeStr);
}

View file

@ -9,6 +9,7 @@ import org.bukkit.entity.Player;
public interface MessageFilter {
/**
* Применить фильтр к сообщению
*
* @return true если сообщение прошло фильтр, false если заблокировано
*/
boolean apply(Player player, ChatMessage message);

View file

@ -9,7 +9,10 @@ import java.util.UUID;
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(UUID player);
boolean isGlobalChatDisabled(UUID player);
}

View file

@ -7,7 +7,10 @@ import java.util.UUID;
*/
public interface CooldownService {
boolean isOnCooldown(UUID player, String type, int cooldownSeconds);
int getRemainingCooldown(UUID player, String type, int cooldownSeconds);
void setCooldown(UUID player, String type);
void removeCooldown(UUID player);
}

View file

@ -14,12 +14,14 @@ public interface IgnoreService {
/**
* Добавляет игрока в список игнорируемых
*
* @return true если успешно добавлен
*/
boolean addIgnore(UUID player, UUID target);
/**
* Удаляет игрока из списка игнорируемых
*
* @return true если успешно удалён
*/
boolean removeIgnore(UUID player, UUID target);

View file

@ -10,6 +10,7 @@ import java.util.Set;
public interface MentionService {
/**
* Обрабатывает упоминания в сообщении
*
* @return сообщение с подсвеченными никами
*/
String processMentions(String message, Set<Player> mentionedPlayers);

View file

@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
public interface MessageService {
/**
* Обработать входящее сообщение чата
*
* @return true если сообщение обработано успешно
*/
boolean processMessage(Player player, String message);

View file

@ -13,22 +13,30 @@ import java.util.UUID;
public interface MuteService {
// Основные операции
void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy);
boolean unmute(UUID uuid, String unmutedBy);
boolean isMuted(UUID uuid);
MuteData getMuteData(UUID uuid);
long getRemainingTime(UUID uuid);
// Утилиты времени
String formatTime(long millis);
long parseTime(String timeStr);
// Права доступа
long getMaxDuration(Player player);
boolean canMuteForDuration(Player player, long duration);
// История
List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid);
List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName);
UUID getUUIDByName(String name);
// Список активных мутов

View file

@ -12,6 +12,7 @@ public interface NickService {
/**
* Устанавливает кастомный ник игроку
*
* @param player UUID игрока
* @param nickname Новый ник (с поддержкой цветов и русских символов)
* @return true если успешно, false если ник занят или невалиден
@ -20,12 +21,14 @@ public interface NickService {
/**
* Сбрасывает кастомный ник игрока
*
* @param player UUID игрока
*/
void resetNickname(UUID player);
/**
* Получает кастомный ник игрока
*
* @param player UUID игрока
* @return Optional с ником или empty если нет кастомного ника
*/
@ -33,6 +36,7 @@ public interface NickService {
/**
* Проверяет занят ли ник
*
* @param nickname Ник для проверки
* @return true если ник уже используется
*/
@ -40,6 +44,7 @@ public interface NickService {
/**
* Валидирует ник (длина, символы)
*
* @param nickname Ник для проверки
* @return true если ник валиден
*/
@ -47,6 +52,7 @@ public interface NickService {
/**
* Обновляет displayName и tabName игрока
*
* @param player Игрок
*/
void updatePlayerDisplay(Player player);

View file

@ -7,5 +7,6 @@ import java.util.UUID;
*/
public interface PlayerDataService {
void clearPlayerData(UUID player);
void saveAll();
}

View file

@ -9,6 +9,7 @@ import org.bukkit.entity.Player;
public interface SpyService {
/**
* Переключает режим шпиона для игрока
*
* @return true если шпион включен, false если выключен
*/
boolean toggleSpy(java.util.UUID player);

View file

@ -9,17 +9,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CustomCommand extends Command {
public final class CustomCommand extends Command {
private final CustomCommandManager manager;
public CustomCommand(String name, CustomCommandManager manager) {
super(name);
this.manager = manager;
init(name);
}
private void init(String name) {
this.setDescription("Кастомная команда LoChat");
this.setUsage("/" + name);
}

View file

@ -18,9 +18,9 @@ import org.jetbrains.annotations.NotNull;
*/
public class GlobalChatCommand implements CommandExecutor {
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public GlobalChatCommand(LoChat plugin) {
this.plugin = plugin;

View file

@ -14,8 +14,8 @@ import java.util.List;
public class LoChatCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
public LoChatCommand(LoChat plugin) {
this.plugin = plugin;
@ -51,14 +51,15 @@ public class LoChatCommand implements CommandExecutor, TabCompleter {
var commands = plugin.getCustomCommandManager().getCommands();
sender.sendMessage(MM.deserialize("&#FFD700Кастомные команды (" + commands.size() + "):"));
for (var cmd : commands.values()) {
sender.sendMessage(MM.deserialize("&e/" + cmd.name + " &#808080(" + cmd.type + ")"));
if (!cmd.aliases.isEmpty()) {
sender.sendMessage(MM.deserialize("&#808080 Алиасы: " + String.join(", ", cmd.aliases)));
sender.sendMessage(MM.deserialize("&e/" + cmd.name() + " &#808080(" + cmd.type() + ")"));
if (!cmd.aliases().isEmpty()) {
sender.sendMessage(MM.deserialize("&#808080 Алиасы: " + String.join(", ", cmd.aliases())));
}
}
}
}
default -> sender.sendMessage(MM.deserialize("&#FF0000Неизвестная команда. Используйте /lochat для помощи."));
default ->
sender.sendMessage(MM.deserialize("&#FF0000Неизвестная команда. Используйте /lochat для помощи."));
}
return true;

View file

@ -17,9 +17,9 @@ import org.jetbrains.annotations.NotNull;
*/
public class LocalChatCommand implements CommandExecutor {
private static final MiniMessage MM = MiniMessage.miniMessage();
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public LocalChatCommand(LoChat plugin) {
this.plugin = plugin;

View file

@ -5,6 +5,7 @@ import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
@ -89,10 +90,10 @@ public class MsgCommand implements CommandExecutor {
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
if (sound != null) {
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
// Шпион

View file

@ -5,6 +5,7 @@ import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.Command;
@ -88,10 +89,10 @@ public class ReplyCommand implements CommandExecutor {
// Звук для получателя
if (plugin.getConfigManager().isPmSoundEnabled()) {
try {
Sound sound = Sound.valueOf(plugin.getConfigManager().getPmSoundType());
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
if (sound != null) {
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
// Шпион

View file

@ -35,18 +35,18 @@ public class AdvancedMessageFilter {
public FilterResult filterMessage(Player player, String message) {
if (config.getBoolean("filters.url.enabled", true)) {
FilterResult urlResult = filterUrls(player, message);
if (!urlResult.isAllowed()) {
if (!urlResult.allowed()) {
return urlResult;
}
message = urlResult.getFilteredMessage();
message = urlResult.filteredMessage();
}
if (config.getBoolean("filters.ip.enabled", true)) {
FilterResult ipResult = filterIPs(player, message);
if (!ipResult.isAllowed()) {
if (!ipResult.allowed()) {
return ipResult;
}
message = ipResult.getFilteredMessage();
message = ipResult.filteredMessage();
}
if (config.getBoolean("filters.repeat.enabled", true)) {
@ -138,27 +138,6 @@ public class AdvancedMessageFilter {
return url;
}
public static class FilterResult {
private final boolean allowed;
private final String filteredMessage;
private final String blockReason;
public FilterResult(boolean allowed, String filteredMessage, String blockReason) {
this.allowed = allowed;
this.filteredMessage = filteredMessage;
this.blockReason = blockReason;
}
public boolean isAllowed() {
return allowed;
}
public String getFilteredMessage() {
return filteredMessage;
}
public String getBlockReason() {
return blockReason;
}
public record FilterResult(boolean allowed, String filteredMessage, String blockReason) {
}
}

View file

@ -13,22 +13,20 @@ public class ServiceRegistry {
private final Map<Class<?>, Object> services = new HashMap<>();
private final JavaPlugin plugin;
public ServiceRegistry(JavaPlugin plugin) {
private ServiceRegistry(JavaPlugin plugin) {
this.plugin = plugin;
init();
}
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
this.plugin = plugin;
initWithDeps(configManager, messageConfig);
public static ServiceRegistry create(JavaPlugin plugin) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServices();
return registry;
}
private void init() {
registerServices();
}
private void initWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
registerServicesWithDeps(configManager, messageConfig);
public static ServiceRegistry createWithDeps(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
ServiceRegistry registry = new ServiceRegistry(plugin);
registry.registerServicesWithDeps(configManager, messageConfig);
return registry;
}
private void registerServices() {

View file

@ -2,6 +2,7 @@ package com.loki.lochat.core.service;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
@ -9,8 +10,6 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* Реализация сервиса игнорирования
*/
@ -39,13 +38,15 @@ public class IgnoreServiceImpl implements IgnoreService {
for (String ignoredStr : ignoredList) {
try {
ignored.add(UUID.fromString(ignoredStr));
} catch (Exception ignored2) {}
} catch (Exception ignored2) {
}
}
if (!ignored.isEmpty()) {
ignoreMap.put(uuid, ignored);
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
}
}

View file

@ -2,6 +2,7 @@ package com.loki.lochat.core.service;
import com.loki.lochat.api.service.MentionService;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.utils.PlayerUtil;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
@ -17,9 +18,8 @@ import java.util.regex.Pattern;
* Реализация сервиса упоминаний
*/
public class MentionServiceImpl implements MentionService {
private final ConfigManager configManager;
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
private final ConfigManager configManager;
// Хранит для какого игрока какие части сообщения выделить
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
@ -104,12 +104,7 @@ public class MentionServiceImpl implements MentionService {
}
String soundName = configManager.getMentionSoundType();
Sound sound;
try {
sound = Sound.valueOf(soundName);
} catch (IllegalArgumentException e) {
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
}
Sound sound = PlayerUtil.parseSound(soundName, Sound.BLOCK_NOTE_BLOCK_PLING);
for (Player player : players) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
@ -123,10 +118,10 @@ public class MentionServiceImpl implements MentionService {
if (isPlayerMentioned(message, viewer)) {
String soundName = configManager.getMentionSoundType();
try {
Sound sound = Sound.valueOf(soundName);
Sound sound = PlayerUtil.parseSound(soundName, null);
if (sound != null) {
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
}
}

View file

@ -1,13 +1,14 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.filter.MessageFilter;
import com.loki.lochat.api.service.*;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.api.service.CooldownService;
import com.loki.lochat.api.service.MessageService;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.core.filter.CooldownFilter;
import com.loki.lochat.core.filter.MuteFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.data.model.ChatMessage;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

View file

@ -231,13 +231,15 @@ public class MuteServiceImpl implements MuteService {
if (!dataFile.exists()) return;
try (Reader reader = new FileReader(dataFile)) {
Type type = new TypeToken<Map<String, MuteData>>(){}.getType();
Type type = new TypeToken<Map<String, MuteData>>() {
}.getType();
Map<String, MuteData> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
mutes.put(UUID.fromString(key), value);
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
});
}
} catch (IOException e) {
@ -249,13 +251,15 @@ public class MuteServiceImpl implements MuteService {
if (!historyFile.exists()) return;
try (Reader reader = new FileReader(historyFile)) {
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>(){}.getType();
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>() {
}.getType();
Map<String, List<MuteData.MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
history.put(UUID.fromString(key), new ArrayList<>(value));
} catch (IllegalArgumentException ignored) {}
} catch (IllegalArgumentException ignored) {
}
});
}
} catch (IOException e) {

View file

@ -12,22 +12,23 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса кастомных ников
*/
public class NickServiceImpl implements NickService {
// Настройки
private static final int MIN_LENGTH = 3;
private static final int MAX_LENGTH = 16;
private final JavaPlugin plugin;
private final Map<UUID, String> nicknames = new ConcurrentHashMap<>();
private final File dataFile;
private FileConfiguration data;
// Настройки
private static final int MIN_LENGTH = 3;
private static final int MAX_LENGTH = 16;
public NickServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "nicknames.yml");
@ -154,11 +155,7 @@ public class NickServiceImpl implements NickService {
}
// Проверка символов (разрешены буквы, цифры, _, русские буквы)
if (!cleanNick.matches("[a-zA-Zа-яА-Я0-9_]+")) {
return false;
}
return true;
return cleanNick.matches("[a-zA-Zа-яА-Я0-9_]+");
}
@Override

View file

@ -7,9 +7,9 @@ import org.bukkit.entity.Player;
*/
public class ChatMessage {
private final Player sender;
private String content;
private final boolean isGlobal;
private final long timestamp;
private String content;
private ChatMessage(Player sender, String content, boolean isGlobal) {
this.sender = sender;
@ -30,10 +30,27 @@ public class ChatMessage {
return new ChatMessage(sender, content, isGlobal);
}
public Player getSender() { return sender; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public boolean isGlobal() { return isGlobal; }
public long getTimestamp() { return timestamp; }
public String getChatType() { return isGlobal ? "global" : "local"; }
public Player getSender() {
return sender;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isGlobal() {
return isGlobal;
}
public long getTimestamp() {
return timestamp;
}
public String getChatType() {
return isGlobal ? "global" : "local";
}
}

View file

@ -6,14 +6,15 @@ import java.util.UUID;
* Модель данных мута (Value Object)
*/
public class MuteData {
private UUID uuid;
private String playerName;
public long endTime; // 0 = перманентный (public for JSON serialization)
public String reason;
public String mutedBy;
public long mutedAt;
private UUID uuid;
private String playerName;
public MuteData() {} // For JSON deserialization
public MuteData() {
} // For JSON deserialization
public MuteData(UUID uuid, String playerName, long endTime, String reason, String mutedBy) {
this.uuid = uuid;
@ -32,13 +33,33 @@ public class MuteData {
this.playerName = playerName;
}
public UUID getUuid() { return uuid; }
public String getPlayerName() { return playerName; }
public long getEndTime() { return endTime; }
public String getReason() { return reason; }
public String getMutedBy() { return mutedBy; }
public long getMutedAt() { return mutedAt; }
public boolean isPermanent() { return endTime == 0; }
public UUID getUuid() {
return uuid;
}
public String getPlayerName() {
return playerName;
}
public long getEndTime() {
return endTime;
}
public String getReason() {
return reason;
}
public String getMutedBy() {
return mutedBy;
}
public long getMutedAt() {
return mutedAt;
}
public boolean isPermanent() {
return endTime == 0;
}
/**
* Запись истории мута
@ -53,7 +74,8 @@ public class MuteData {
public String unmutedBy;
public long unmutedAt;
public MuteHistoryEntry() {} // For JSON deserialization
public MuteHistoryEntry() {
} // For JSON deserialization
public MuteHistoryEntry(String playerName, long duration, String reason,
String mutedBy, long mutedAt, boolean unmuted,

View file

@ -37,6 +37,7 @@ public class GradientModule {
/**
* Инициализация модуля
*
* @return true если модуль успешно инициализирован
*/
public boolean init() {
@ -141,6 +142,7 @@ public class GradientModule {
/**
* Получает отформатированное имя игрока с градиентом и LuckPerms префиксом
*
* @param useTabFormat true для §x§... формата (TAB), false для &#RRGGBB
*/
private String getFormattedName(Player player, boolean useTabFormat) {
@ -263,12 +265,35 @@ public class GradientModule {
}
// Геттеры
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; }
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;
}
}

View file

@ -26,10 +26,10 @@ import java.util.List;
*/
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"
);
private final GradientModule module;
public GradientAdminCommand(GradientModule module) {
this.module = module;

View file

@ -26,14 +26,13 @@ import java.util.function.BiFunction;
*/
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");
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
public GradientColorCommand(GradientModule module) {
this.module = module;

View file

@ -28,13 +28,12 @@ import java.util.regex.Pattern;
*/
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");
private final GradientModule module;
private final Map<String, BiFunction<Player, String[], Boolean>> subCommands = new HashMap<>();
public GradientPrefixCommand(GradientModule module) {
this.module = module;

View file

@ -56,22 +56,69 @@ public class GradientConfig {
}
}
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 isGradientOnLuckPermsPrefix() { return config.getBoolean("gradient-on-luckperms-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 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 isGradientOnLuckPermsPrefix() {
return config.getBoolean("gradient-on-luckperms-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");

View file

@ -79,7 +79,8 @@ public class GradientDataManager {
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) {}
} catch (IllegalArgumentException ignored) {
}
}
}

View file

@ -29,25 +29,71 @@ public class GradientPlayerData {
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 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; }
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;
}
}

View file

@ -23,21 +23,16 @@ import java.util.List;
/**
* GUI подтверждения для градиентного модуля
*/
public class GradientConfirmGUI implements InventoryHolder {
public enum ConfirmType {
COLOR, PREFIX
}
public final class GradientConfirmGUI implements InventoryHolder {
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.character('&')
.hexCharacter('#')
.hexColors()
.build();
private final GradientModule module;
private final Player player;
private final Inventory inventory;
private Inventory inventory;
private final ConfirmType type;
private final List<String> colors;
private final String prefix;
@ -51,6 +46,10 @@ public class GradientConfirmGUI implements InventoryHolder {
this.colors = colors;
this.prefix = prefix;
this.price = price;
}
private void initInventory() {
if (inventory != null) return;
this.inventory = Bukkit.createInventory(this, 27,
Component.text("Подтверждение", NamedTextColor.DARK_GRAY));
setupItems();
@ -187,14 +186,37 @@ public class GradientConfirmGUI implements InventoryHolder {
}
public void open() {
initInventory();
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; }
public @NotNull Inventory getInventory() {
initInventory();
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;
}
public enum ConfirmType {
COLOR, PREFIX
}
}

View file

@ -19,12 +19,11 @@ import java.util.Map;
*/
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"
);
private final GradientModule module;
public GradientGUIListener(GradientModule module) {
this.module = module;

View file

@ -21,7 +21,8 @@ public final class DisplayNameUtil {
.hexColors()
.build();
private DisplayNameUtil() {}
private DisplayNameUtil() {
}
public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) {
GradientConfig cfg = module.getConfig();

View file

@ -17,7 +17,8 @@ public final class FoliaUtil {
IS_FOLIA = checkFolia();
}
private FoliaUtil() {}
private FoliaUtil() {
}
private static boolean checkFolia() {
try {

View file

@ -5,15 +5,14 @@ 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;
private GradientConstants() {
}
}

View file

@ -11,7 +11,8 @@ public final class GradientUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
private GradientUtil() {}
private GradientUtil() {
}
public static boolean isValidHex(String color) {
return HEX_PATTERN.matcher(color).matches();

View file

@ -1,7 +1,10 @@
package com.loki.lochat.integrations;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.*;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.gradient.GradientModule;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Bukkit;

View file

@ -52,6 +52,7 @@ public class SkinsRestorerHook {
/**
* Получает текстуру скина игрока
*
* @param player игрок
* @return URL текстуры или null
*/
@ -72,6 +73,7 @@ public class SkinsRestorerHook {
/**
* Получает URL головы игрока для отображения
*
* @param playerName имя игрока
* @return URL головы
*/

View file

@ -5,8 +5,8 @@ import com.loki.lochat.core.filter.AdvancedMessageFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.utils.MentionHandler;
import com.loki.lochat.utils.TextFormatter;
import io.papermc.paper.event.player.AsyncChatEvent;
import io.papermc.paper.chat.ChatRenderer;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
@ -47,13 +47,13 @@ public class ChatEventListener implements Listener {
AdvancedMessageFilter.FilterResult filterResult = advancedFilter.filterMessage(sender, message);
if (!filterResult.isAllowed()) {
if (!filterResult.allowed()) {
event.setCancelled(true);
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.getBlockReason()));
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.blockReason()));
return;
}
String filteredMessage = filterResult.getFilteredMessage();
String filteredMessage = filterResult.filteredMessage();
event.message(Component.text(filteredMessage));
event.renderer(new EnhancedChatRenderer(sender, event.message()));

View file

@ -24,14 +24,15 @@ public class CustomCommandManager {
private final LoChat plugin;
private final Map<String, CustomCommandData> commands;
public CustomCommandManager(LoChat plugin) {
private CustomCommandManager(LoChat plugin) {
this.plugin = plugin;
this.commands = new HashMap<>();
init();
}
private void init() {
loadCommands();
public static CustomCommandManager create(LoChat plugin) {
CustomCommandManager manager = new CustomCommandManager(plugin);
manager.loadCommands();
return manager;
}
private void loadCommands() {
@ -263,22 +264,7 @@ public class CustomCommandManager {
return commands;
}
public static class CustomCommandData {
public final String name;
public final String permission;
public final String message;
public final List<String> aliases;
public final String type;
public final String target;
public CustomCommandData(String name, String permission, String message,
List<String> aliases, String type, String target) {
this.name = name;
this.permission = permission;
this.message = message;
this.aliases = aliases;
this.type = type;
this.target = target;
}
public record CustomCommandData(String name, String permission, String message, List<String> aliases, String type,
String target) {
}
}

View file

@ -17,9 +17,14 @@ public class EmojiManager {
private boolean enabled;
private boolean requirePermission;
public EmojiManager(LoChat plugin) {
private EmojiManager(LoChat plugin) {
this.plugin = plugin;
load();
}
public static EmojiManager create(LoChat plugin) {
EmojiManager manager = new EmojiManager(plugin);
manager.load();
return manager;
}
public void load() {

View file

@ -41,6 +41,7 @@ public class FoliaUtil {
/**
* Запускает глобальную повторяющуюся задачу
*
* @param plugin плагин
* @param task задача
* @param delayTicks задержка перед первым запуском (в тиках)

View file

@ -7,6 +7,7 @@ public class TimeParser {
/**
* Парсит строку времени в миллисекунды
*
* @param timeStr строка вида "1d", "2h", "30m", "60s"
* @return миллисекунды или -1 при ошибке
*/

View file

@ -11,7 +11,8 @@ public final class ChatFormatter {
private static final PlainTextComponentSerializer PLAIN =
PlainTextComponentSerializer.plainText();
private ChatFormatter() {}
private ChatFormatter() {
}
/* ===================== BASIC ===================== */

View file

@ -5,27 +5,24 @@ package com.loki.lochat.utils;
*/
public final class Constants {
private Constants() {}
// Чат
public static final int DEFAULT_LOCAL_RADIUS = 100;
public static final int CLEAR_CHAT_LINES = 100;
// Время
public static final long MILLIS_IN_SECOND = 1000L;
public static final int SECONDS_IN_MINUTE = 60;
public static final int MINUTES_IN_HOUR = 60;
public static final int HOURS_IN_DAY = 24;
// Градиенты
public static final int DEFAULT_PRICE_PER_COLOR = 50;
public static final int DEFAULT_PREFIX_PRICE = 500;
public static final int LUCKPERMS_PREFIX_PRIORITY = 100;
// Folia/Paper совместимость
public static final int TICKS_PER_SECOND = 20;
public static final int MILLIS_PER_TICK = 50;
// Анти-спам
public static final int PERCENTAGE_MULTIPLIER = 100;
private Constants() {
}
}

View file

@ -5,7 +5,8 @@ import org.bukkit.entity.Player;
public class DistanceUtil {
private DistanceUtil() {}
private DistanceUtil() {
}
public static boolean isInRange(Player player1, Player player2, int radius) {
// Проверка на один мир

View file

@ -2,6 +2,7 @@ package com.loki.lochat.utils;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.UUID;
@ -11,10 +12,32 @@ import java.util.UUID;
*/
public final class PlayerUtil {
private PlayerUtil() {}
private PlayerUtil() {
}
/**
* Safely parses a sound name from config.
* Uses deprecated Sound.valueOf() for backward compatibility with string-based config values.
*
* @param soundName the sound name to parse
* @param defaultSound the default sound to return if parsing fails
* @return the parsed Sound or the default if invalid
*/
@SuppressWarnings("deprecation")
public static Sound parseSound(String soundName, Sound defaultSound) {
if (soundName == null || soundName.isEmpty()) {
return defaultSound;
}
try {
return Sound.valueOf(soundName.toUpperCase());
} catch (IllegalArgumentException e) {
return defaultSound;
}
}
/**
* Находит игрока по имени (онлайн или оффлайн)
*
* @param name имя игрока
* @return UUID игрока или null если не найден
*/
@ -36,6 +59,7 @@ public final class PlayerUtil {
/**
* Проверяет, существует ли игрок (играл на сервере)
*
* @param name имя игрока
* @return true если игрок существует
*/
@ -45,6 +69,7 @@ public final class PlayerUtil {
/**
* Получает имя игрока по UUID
*
* @param uuid UUID игрока
* @return имя игрока или null
*/
@ -60,6 +85,7 @@ public final class PlayerUtil {
/**
* Проверяет, онлайн ли игрок
*
* @param uuid UUID игрока
* @return true если игрок онлайн
*/

View file

@ -28,7 +28,7 @@ help:
<gradient:#87CEEB:#4682B4>/website</gradient> <gray>- официальный сайт</gray>
<gradient:#87CEEB:#4682B4>/coords</gradient> <gray>- ваши координаты</gray>
<gradient:#87CEEB:#4682B4>/time</gradient> <gray>- время на сервере</gray>
aliases: ["помощь", "хелп"]
aliases: [ "помощь", "хелп" ]
type: "chat"
target: "sender"
@ -42,7 +42,7 @@ rules:
<white>5.</white> <gray>Не используйте мат и оскорбления</gray>
<gradient:#FFA726:#FF9800>Нарушение правил карается мутом или баном!</gradient>
aliases: ["правила", "rule"]
aliases: [ "правила", "rule" ]
type: "chat"
target: "sender"
@ -57,7 +57,7 @@ discord:
<white>•</white> <gray>Общаться с игроками</gray>
<white>•</white> <gray>Получать новости сервера</gray>
<white>•</white> <gray>Участвовать в событиях</gray>
aliases: ["дискорд", "ds"]
aliases: [ "дискорд", "ds" ]
type: "chat"
target: "sender"
@ -70,7 +70,7 @@ website:
<white>•</white> <gray>Карта сервера</gray>
<white>•</white> <gray>Статистика игроков</gray>
<white>•</white> <gray>Форум и новости</gray>
aliases: ["сайт", "site"]
aliases: [ "сайт", "site" ]
type: "chat"
target: "sender"
@ -85,7 +85,7 @@ coords:
<gradient:#87CEEB:#4682B4>Y:</gradient> <white>{y}</white>
<gradient:#87CEEB:#4682B4>Z:</gradient> <white>{z}</white>
<gradient:#87CEEB:#4682B4>Мир:</gradient> <white>{world}</white>
aliases: ["координаты", "pos", "position"]
aliases: [ "координаты", "pos", "position" ]
type: "actionbar"
target: "sender"
@ -94,7 +94,7 @@ time:
<gradient:#87CEEB:#4682B4>Время на сервере:</gradient> <white>%server_time_24%</white>
<gradient:#87CEEB:#4682B4>Дата:</gradient> <white>%server_date%</white>
<gradient:#87CEEB:#4682B4>Онлайн:</gradient> <white>%server_online%</white> <gray>игроков</gray>
aliases: ["время", "дата"]
aliases: [ "время", "дата" ]
type: "chat"
target: "sender"
@ -108,7 +108,7 @@ restart-warning:
<gradient:#FF6B6B:#FF4757>⚠ ВНИМАНИЕ! ⚠</gradient>
<gradient:#FFA726:#FF9800>Сервер будет перезагружен через 5 минут!</gradient>
<gray>Сохраните свой прогресс и подготовьтесь к перезагрузке.</gray>
aliases: ["рестарт", "reboot"]
aliases: [ "рестарт", "reboot" ]
type: "title"
target: "all"
@ -117,7 +117,7 @@ maintenance:
message: |
<gradient:#FF6B6B:#FF4757>Сервер переходит в режим технических работ</gradient>
<gray>Ожидаемое время: 30 минут</gray>
aliases: ["тех-работы", "maintenance"]
aliases: [ "тех-работы", "maintenance" ]
type: "broadcast"
target: "all"
@ -146,7 +146,7 @@ welcome:
message: |
<gradient:#FFD700:#FFA500>Добро пожаловать на сервер!</gradient>
<gradient:#87CEEB:#4682B4>Игрок {player} присоединился к игре</gradient>
aliases: ["привет", "hello"]
aliases: [ "привет", "hello" ]
type: "title"
target: "sender"

View file

@ -76,16 +76,16 @@ playerpoints:
# Предустановленные градиенты
presets:
fire: ["#FF4500", "#FF6347", "#FFD700"]
ocean: ["#00CED1", "#1E90FF", "#4169E1"]
forest: ["#228B22", "#32CD32", "#90EE90"]
sunset: ["#FF69B4", "#FF1493", "#DC143C"]
rainbow: ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"]
ice: ["#B0E0E6", "#87CEEB", "#4682B4"]
gold: ["#FFD700", "#FFA500", "#FF8C00"]
purple: ["#9370DB", "#8A2BE2", "#4B0082"]
pink: ["#FFB6C1", "#FF69B4", "#FF1493"]
green: ["#00FF7F", "#00FA9A", "#98FB98"]
fire: [ "#FF4500", "#FF6347", "#FFD700" ]
ocean: [ "#00CED1", "#1E90FF", "#4169E1" ]
forest: [ "#228B22", "#32CD32", "#90EE90" ]
sunset: [ "#FF69B4", "#FF1493", "#DC143C" ]
rainbow: [ "#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3" ]
ice: [ "#B0E0E6", "#87CEEB", "#4682B4" ]
gold: [ "#FFD700", "#FFA500", "#FF8C00" ]
purple: [ "#9370DB", "#8A2BE2", "#4B0082" ]
pink: [ "#FFB6C1", "#FF69B4", "#FF1493" ]
green: [ "#00FF7F", "#00FA9A", "#98FB98" ]
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ GUI НАСТРОЙКИ │

View file

@ -18,127 +18,127 @@ commands:
description: Send global message
usage: /g <message>
permission: chat.global.use
aliases: [п]
aliases: [ п ]
globalchat:
description: Toggle global chat
usage: /globalchat toggle
permission: chat.global.toggle
aliases: [пдщифдсрфе]
aliases: [ пдщифдсрфе ]
l:
description: Send local message
usage: /l <message>
permission: chat.local.use
aliases: [д]
aliases: [ д ]
msg:
description: Send private message
usage: /msg <player> <message>
permission: chat.pm.use
aliases: [tell, w, pm, m, ьып, ееддж, цж, зьж, ьж]
aliases: [ tell, w, pm, m, ьып, ееддж, цж, зьж, ьж ]
reply:
description: Reply to last message
usage: /reply <message>
permission: chat.pm.use
aliases: [r, ответить, кузду, к]
aliases: [ r, ответить, кузду, к ]
ignore:
description: Ignore player
usage: /ignore <player>
permission: chat.pm.ignore
aliases: [игнор, шптщку]
aliases: [ игнор, шптщку ]
unignore:
description: Unignore player
usage: /unignore <player>
permission: chat.pm.ignore
aliases: [разигнор, гтшптщку]
aliases: [ разигнор, гтшптщку ]
announce:
description: Send announcement
usage: /announce <message>
permission: chat.announce.use
aliases: [объявление, анонс, фттщгтсу]
aliases: [ объявление, анонс, фттщгтсу ]
nick:
description: Set custom nickname
usage: /nick [nickname|reset]
permission: lochat.nick
aliases: [ник, тшсл]
aliases: [ ник, тшсл ]
chatspy:
description: Toggle chat spy mode
usage: /chatspy
permission: chat.spy
aliases: [spy, шпион, срфезуж, зуж]
aliases: [ spy, шпион, срфезуж, зуж ]
clearchat:
description: Clear chat for all players
usage: /clearchat
permission: chat.clear
aliases: [cc, очиститьчат, сдуфксрфе, сс]
aliases: [ cc, очиститьчат, сдуфксрфе, сс ]
clearchatconfig:
description: Configure clear chat message
usage: /clearchatconfig <enable|disable|message|status> [text]
permission: lochat.clearchat.config
aliases: [ccc, настройкаочистки]
aliases: [ ccc, настройкаочистки ]
lochat:
description: LoChat admin command
usage: /lochat [reload]
permission: lochat.admin
aliases: [дщсрфе]
aliases: [ дщсрфе ]
color:
description: Set gradient nick color
usage: /color <hex1> [hex2] ...
permission: gradient.color
aliases: [gradient, цвет, градиент, сщдщк, пкфвшуте]
aliases: [ gradient, цвет, градиент, сщдщк, пкфвшуте ]
prefix:
description: Set custom prefix
usage: /prefix <text>
permission: gradient.prefix
aliases: [префикс, зкуашч]
aliases: [ префикс, зкуашч ]
aprefix:
description: Admin gradient commands
usage: /aprefix <subcommand>
permission: gradient.admin
aliases: [gradientadmin, азкуашч, пкфвшуетефвьшт]
aliases: [ gradientadmin, азкуашч, пкфвшуетефвьшт ]
lmute:
description: Mute a player
usage: /lmute <player> [time] [-s] [reason]
permission: lochat.mute
aliases: [mute, мут, ьгеу]
aliases: [ mute, мут, ьгеу ]
lunmute:
description: Unmute a player
usage: /lunmute <player> [-s]
permission: lochat.unmute
aliases: [unmute, размут, гтьгеу]
aliases: [ unmute, размут, гтьгеу ]
lmutelist:
description: List all active mutes
usage: /lmutelist
permission: lochat.mutelist
aliases: [mutelist, мутлист, ьгеудшые]
aliases: [ mutelist, мутлист, ьгеудшые ]
lmutehistory:
description: View mute history of a player
usage: /lmutehistory <player>
permission: lochat.mutehistory
aliases: [mutehistory, мутистория, ьгеершыещку]
aliases: [ mutehistory, мутистория, ьгеершыещку ]
lmuteblame:
description: View mutes issued by a moderator
usage: /lmuteblame <moderator>
permission: lochat.muteblame
aliases: [muteblame, мутблейм, ьгеуидфьу]
aliases: [ muteblame, мутблейм, ьгеуидфьу ]
permissions:
chat.global.use: