feat: add example mine config, &#RRGGBB color support, up to 100 selection points

This commit is contained in:
loki5512344 2026-06-01 11:38:53 +02:00
parent 0f2735f171
commit f2be6d6ece
6 changed files with 445 additions and 28 deletions

182
mines/example_mine.yml Normal file
View file

@ -0,0 +1,182 @@
# ============================================
# LoMines - Пример конфигурации шахты
# ============================================
# Название шахты (для отображения в сообщениях и голограммах)
# Поддерживает цвета в формате &#RRGGBB (HEX)
# Пример: &#FF0000Красная &#00FF00шахта
name: "&#FFD700Золотая шахта&#FFFFFF"
# ============================================
# РЕГИОНЫ (области спавна блоков)
# ============================================
# Можно указать несколько регионов (до 100)
# Каждый регион = пара точек (от угла до угла)
# Все регионы должны быть в ОДНОМ мире
#
# Формат: мир;x;y;z;yaw;pitch
# yaw/pitch можно опустить (будет 0;0)
region:
selection:
'1': "world;-100.0;64.0;-100.0"
'2': "world;-90.0;74.0;-90.0"
# Можно добавить больше регионов:
# '3': "world;-80.0;64.0;-100.0"
# '4': "world;-70.0;74.0;-90.0"
# ============================================
# БЛОКИ И ШАНСЫ
# ============================================
# Шансы суммируются и нормализуются автоматически
# Можно использовать материалы из Minecraft
# Для Oraxen: oraxen:id_предмета
# Для ItemsAdder: ia:id_предмета
blocks:
contents:
stone: 50.0
coal_ore: 20.0
iron_ore: 15.0
gold_ore: 10.0
diamond_ore: 4.0
emerald_ore: 1.0
# Режим заполнения:
# - cuboid: заполнить весь кубоид блоками
# - mask: только определённые позиции (см. ниже)
fill-mode: cuboid
# Для режима mask (опционально):
# mask:
# marker: pink_concrete # Блок-маркер для позиций
# positions: [] # Позиции будут заполнены при сканировании
# ============================================
# СБРОС ШАХТЫ
# ============================================
reset:
# Интервал сброса
# Форматы: 30s (секунды), 5m (минуты), 2h (часы), 1d (дни)
interval: "10m"
# Процентный триггер (сброс при % заполненности)
# -1 или 0 = отключено
percent-trigger: -1.0
percent-enabled: false
# Сообщение при сбросе (пусто = стандартное)
# Поддерживает плейсхолдеры: {mine}, {time}
broadcast: "&#00FF00Шахта &#FFFF00{mine} &#00FF00сброшена!"
# Команды при сбросе (опционально)
commands: []
# - "eco give <player> 100"
# - "broadcast Шахта {mine} сброшена!"
# ============================================
# ТЕЛЕПОРТАЦИЯ ПРИ СБРОСЕ
# ============================================
teleport:
enabled: false
location: "" # Формат: мир;x;y;z;yaw;pitch
# Точка спавна для "застрявших" игроков
# (если игрок оказался внутри блока после сброса)
spawn:
enabled: false
location: "" # Если не задано - используется teleport.location
# ============================================
# НАГРАДЫ ПРИ ДОБЫЧЕ
# ============================================
# Выдаются с определённым шансом при ломании блоков
rewards: []
# Пример награды:
# - chance: 0.001 # 0.1% шанс
# prevent-drops: false # Отменить дроп блока?
# blocks: # На каких блоках работает
# - diamond_ore
# - emerald_ore
# items: # Предметы (опционально)
# - type: diamond
# amount: 1
# name: "&#00FFFFРедкий алмаз"
# commands: # Команды (опционально)
# - "eco give <player> 1000"
# ============================================
# ИНТЕРФЕЙС (UI)
# ============================================
ui:
# ActionBar (полоска над хотбаром)
actionbar:
enabled: true
# Дистанция показа (в блоках)
range: 50.0
# Формат строки
# Плейсхолдеры: {mine}, {percent}, {time}, {blocks}, {total}
format: "&#FFD700{mine} &#AAAAAA| &#00FF00{percent}% &#AAAAAA| &#FFFF00{time}"
# Голограмма над шахтой (требует HolographicDisplays или DecentHolograms)
hologram:
enabled: false
# Высота голограммы от центра региона
height: 3.0
format:
- "&#FFD700★ {mine} ★"
- "&#AAAAAAЗаполнено: &#00FF00{percent}%"
- "&#AAAAAAСброс через: &#FFFF00{time}"
# Формат времени для плейсхолдеров
# 1 = HH:MM:SS (01:25:35)
# 2 = короткий (25m 35s)
# 3 = текстовый (1ч 25м 35с)
timer-format: 2
# ============================================
# WORLDGUARD ИНТЕГРАЦИЯ (опционально)
# ============================================
worldguard:
enabled: false
# Шаблон названия региона
# {mine_name} = название шахты
# {random_4} = 4 случайных символа
region-template: "mine_{mine_name}_{random_4}"
# Владельцы региона (uuid:xxx или имя)
owners: []
# Участники региона
members: []
# Флаги WorldGuard
flags:
- "passthrough deny"
- "build -g members allow"
- "pvp deny"
# ============================================
# ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ
# ============================================
# Задержка между установкой блоков (в тиках, 20 тиков = 1 сек)
# 0 = мгновенно (может лагать на больших шахтах)
# Больше = плавнее, но медленнее
block-place-delay: 0
# Тип установки блоков:
# - fast: быстрый (рекомендуется)
# - bukkit: стандартный (медленнее, но стабильнее)
# - parallel: параллельный (экспериментально)
setter: fast
# Разрешить физику блоков (песок, гравий падают)?
block-physics: false
# Разрешить спавн сущностей в шахте?
entity-spawning: false
# Разрешить течение жидкостей?
liquid-flow: false

View file

@ -17,32 +17,17 @@ public final class RegionConfigLoader {
/**
* Parses region configuration from YAML.
* Supports up to 100 selection points (50 regions).
*/
public RegionConfig parse(YamlConfiguration yaml) {
List<Location> selections = new ArrayList<>();
// Try new format first
for (int i = 1; i <= 10; i++) {
String key = "region.selection." + i;
if (yaml.contains(key)) {
String locStr = yaml.getString(key);
if (locStr != null && !locStr.isBlank()) {
selections.add(parseLocation(locStr));
}
}
}
// Try new format first (region.selection.X)
selections.addAll(parseSelections(yaml, "region.selection.", 100));
// Legacy fallback
// Legacy fallback (selection.X)
if (selections.isEmpty()) {
for (int i = 1; i <= 10; i++) {
String key = "selection." + i;
if (yaml.contains(key)) {
String locStr = yaml.getString(key);
if (locStr != null && !locStr.isBlank()) {
selections.add(parseLocation(locStr));
}
}
}
selections.addAll(parseSelections(yaml, "selection.", 100));
}
if (selections.size() < 2) {
@ -52,6 +37,43 @@ public final class RegionConfigLoader {
return RegionConfig.fromSelections(selections);
}
/**
* Parses selection points from YAML with given prefix.
* Supports indexed format: prefix.1, prefix.2, etc.
*/
private List<Location> parseSelections(YamlConfiguration yaml, String prefix, int maxPoints) {
List<Location> selections = new ArrayList<>();
for (int i = 1; i <= maxPoints; i++) {
String key = prefix + i;
if (yaml.contains(key)) {
String locStr = yaml.getString(key);
if (locStr != null && !locStr.isBlank()) {
try {
selections.add(parseLocation(locStr));
} catch (IllegalArgumentException e) {
// Skip invalid locations but continue parsing
}
}
} else {
// Stop at first missing index (assuming sequential)
// But check at least a few more for gaps
if (i > 20) {
boolean hasMore = false;
for (int j = i + 1; j <= Math.min(i + 5, maxPoints); j++) {
if (yaml.contains(prefix + j)) {
hasMore = true;
break;
}
}
if (!hasMore) break;
}
}
}
return selections;
}
/**
* Saves region configuration to YAML.
*/

View file

@ -1,6 +1,6 @@
package dev.loki.lomines.data.config.ui;
import net.kyori.adventure.text.minimessage.MiniMessage;
import dev.loki.lomines.util.format.ColorUtils;
import net.kyori.adventure.text.Component;
import java.util.Objects;
@ -36,6 +36,7 @@ public record UIConfig(
/**
* Parses the action bar format with placeholders.
* Placeholders: {mine}, {percent}, {time}, {blocks}, {total}
* Supports colors: &#RRGGBB, &a..&f, <color>
*/
public Component formatActionBar(String mineName, double percent, String timeStr, int blocks, int total) {
String parsed = actionBarFormat
@ -45,7 +46,7 @@ public record UIConfig(
.replace("{blocks}", String.valueOf(blocks))
.replace("{total}", String.valueOf(total));
return MiniMessage.miniMessage().deserialize(parsed);
return ColorUtils.format(parsed);
}
/**

View file

@ -5,7 +5,7 @@ import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.ui.HologramConfig;
import dev.loki.lomines.integration.hologram.provider.DecentHologramsProvider;
import dev.loki.lomines.integration.hologram.provider.HolographicDisplaysProvider;
import net.kyori.adventure.text.minimessage.MiniMessage;
import dev.loki.lomines.util.format.ColorUtils;
import org.bukkit.Location;
import java.util.*;
@ -144,7 +144,6 @@ public final class HologramManager {
private List<String> formatLines(List<String> format, Mine mine) {
List<String> result = new ArrayList<>();
MiniMessage miniMessage = MiniMessage.miniMessage();
for (String line : format) {
String formatted = line
@ -153,7 +152,8 @@ public final class HologramManager {
.replace("{bar}", createProgressBar(mine.getPercentFilled()))
.replace("{time}", formatResetTime(mine));
result.add(formatted);
// Convert &#RRGGBB and &codes to legacy for hologram plugins
result.add(ColorUtils.toLegacy(formatted));
}
return result;
@ -163,12 +163,12 @@ public final class HologramManager {
int filled = (int) Math.round(percent / 10.0);
int empty = 10 - filled;
// Use &#RRGGBB format which works with most hologram plugins
StringBuilder bar = new StringBuilder();
bar.append("<green>");
bar.append("&#00FF00"); // Green for filled
bar.append("█".repeat(Math.max(0, filled)));
bar.append("<gray>");
bar.append("&#808080"); // Gray for empty
bar.append("░".repeat(Math.max(0, empty)));
bar.append("</gray></green>");
return bar.toString();
}

View file

@ -0,0 +1,207 @@
package dev.loki.lomines.util.format;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility for converting color formats.
* Supports: &#RRGGBB (HEX), &x&R&R&G&G&B&B (Bukkit HEX), MiniMessage
*/
public final class ColorUtils {
private static final Pattern HEX_PATTERN = Pattern.compile("&#([0-9A-Fa-f]{6})");
private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("&x([&0-9A-Fa-f]){12}");
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
private ColorUtils() {
// Utility class
}
/**
* Converts a string with color codes to a Component.
* Supports:
* - &#RRGGBB - HEX colors
* - &x&R&R&G&G&B&B - Legacy Bukkit HEX
* - &a, &b, ... - Legacy colors
* - <color>, <#RRGGBB> - MiniMessage (pass-through)
*/
public static Component format(String text) {
if (text == null || text.isEmpty()) {
return Component.empty();
}
// Check if it's already MiniMessage format
if (text.contains("<") && text.contains(">")) {
try {
return MINI_MESSAGE.deserialize(text);
} catch (Exception e) {
// Fall through to legacy conversion
}
}
// Convert &#RRGGBB to MiniMessage <#RRGGBB>
String miniMessageText = convertHexToMiniMessage(text);
// Convert legacy &x format if present
miniMessageText = convertLegacyHexToMiniMessage(miniMessageText);
// Convert remaining legacy codes (&a, &l, etc.) to MiniMessage
miniMessageText = convertLegacyToMiniMessage(miniMessageText);
try {
return MINI_MESSAGE.deserialize(miniMessageText);
} catch (Exception e) {
// Fallback: use legacy serializer
return LEGACY_SERIALIZER.deserialize(text);
}
}
/**
* Converts &#RRGGBB to <#RRGGBB> for MiniMessage.
*/
private static String convertHexToMiniMessage(String text) {
Matcher matcher = HEX_PATTERN.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String hex = matcher.group(1);
matcher.appendReplacement(result, "<#" + hex + ">");
}
matcher.appendTail(result);
return result.toString();
}
/**
* Converts &x&R&R&G&G&B&B to <#RRGGBB> for MiniMessage.
*/
private static String convertLegacyHexToMiniMessage(String text) {
if (!text.contains("&x")) {
return text;
}
// Pattern for &x&R&R&G&G&B&B
Pattern bukkitHex = Pattern.compile("&x(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])");
Matcher matcher = bukkitHex.matcher(text);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String r1 = matcher.group(1).substring(1);
String r2 = matcher.group(2).substring(1);
String g1 = matcher.group(3).substring(1);
String g2 = matcher.group(4).substring(1);
String b1 = matcher.group(5).substring(1);
String b2 = matcher.group(6).substring(1);
String hex = r1 + r2 + g1 + g2 + b1 + b2;
matcher.appendReplacement(result, "<#" + hex + ">");
}
matcher.appendTail(result);
return result.toString();
}
/**
* Converts legacy & codes to MiniMessage tags.
*/
private static String convertLegacyToMiniMessage(String text) {
return text
.replace("&0", "<black>")
.replace("&1", "<dark_blue>")
.replace("&2", "<dark_green>")
.replace("&3", "<dark_aqua>")
.replace("&4", "<dark_red>")
.replace("&5", "<dark_purple>")
.replace("&6", "<gold>")
.replace("&7", "<gray>")
.replace("&8", "<dark_gray>")
.replace("&9", "<blue>")
.replace("&a", "<green>")
.replace("&b", "<aqua>")
.replace("&c", "<red>")
.replace("&d", "<light_purple>")
.replace("&e", "<yellow>")
.replace("&f", "<white>")
.replace("&k", "<obfuscated>")
.replace("&l", "<bold>")
.replace("&m", "<strikethrough>")
.replace("&n", "<underlined>")
.replace("&o", "<italic>")
.replace("&r", "<reset>");
}
/**
* Strips all color codes from text.
*/
public static String stripColors(String text) {
if (text == null || text.isEmpty()) {
return "";
}
return text.replaceAll("&#[0-9A-Fa-f]{6}", "")
.replaceAll("&[0-9A-Fa-fk-orK-OR]", "")
.replaceAll("<[^>]+>", "");
}
/**
* Converts to legacy color format (&a, &c, &x&R&R&G&G&B&B).
* Useful for plugins that don't support MiniMessage.
*/
public static String toLegacy(String text) {
if (text == null || text.isEmpty()) {
return "";
}
String result = text;
// Convert MiniMessage <#RRGGBB> to &x&R&R&G&G&B&B
Pattern hexTag = Pattern.compile("<#([0-9A-Fa-f]{6})>");
Matcher hexMatcher = hexTag.matcher(result);
StringBuffer sb = new StringBuffer();
while (hexMatcher.find()) {
String hex = hexMatcher.group(1);
StringBuilder legacy = new StringBuilder("&x");
for (char c : hex.toCharArray()) {
legacy.append("&").append(c);
}
hexMatcher.appendReplacement(sb, legacy.toString());
}
hexMatcher.appendTail(sb);
result = sb.toString();
// Convert MiniMessage named colors to legacy
result = result
.replace("<black>", "&0")
.replace("<dark_blue>", "&1")
.replace("<dark_green>", "&2")
.replace("<dark_aqua>", "&3")
.replace("<dark_red>", "&4")
.replace("<dark_purple>", "&5")
.replace("<gold>", "&6")
.replace("<gray>", "&7")
.replace("<dark_gray>", "&8")
.replace("<blue>", "&9")
.replace("<green>", "&a")
.replace("<aqua>", "&b")
.replace("<red>", "&c")
.replace("<light_purple>", "&d")
.replace("<yellow>", "&e")
.replace("<white>", "&f")
.replace("<obfuscated>", "&k")
.replace("<bold>", "&l")
.replace("<strikethrough>", "&m")
.replace("<underlined>", "&n")
.replace("<italic>", "&o")
.replace("<reset>", "&r");
// &#RRGGBB is already in a good format for most plugins
return result;
}
}

View file

@ -454,6 +454,11 @@ dev.loki.lomines/
- `/lm regions <mine>` — список регионов
- `/lm addregion <mine>` — добавить регион
- `/lm removeregion <mine> <index>` — удалить регион
- [x] Улучшить формат конфигурации ✅
- Пример конфига с комментариями (mines/example_mine.yml)
- Поддержка &#RRGGBB цветов
- До 100 точек селекшна (50 регионов)
- Чистый читаемый YAML формат
---