%` (blocksmined/rank), `%lomines_count%`. Регистрируется в `IntegrationManager.initPlaceholderAPI`.
-- **WorldGuard** — см. выше.
-- **Hologram** (`integration/hologram/`): `HologramManager` управляет жизненным циклом, `HologramProvider` — интерфейс, `provider/DecentHologramsProvider` и `provider/HolographicDisplaysProvider` — реализации, `HologramRenderer` — форматирование строк.
-
---
## Принципы
@@ -368,18 +749,23 @@ Tab-complete — `LoMinesTabCompleter` (+ `SubcommandCompleter`, `PermissionPred
- `BlockSetter` — новые реализации добавляются наследованием (OCP)
- Все `BlockSetter` взаимозаменяемы в `MineResetHandler` (LSP)
- Handler-классы зависят только от нужного (ISP)
-- `Mine` зависит от абстракции `BlockSetter`, не от конкретной реализации (DIP)
+- `Mine` зависит от абстракции `BlockSetter`, не от конкретного NMS (DIP)
**Ограничения на код:**
-- Максимум 150 строк на файл, 30 строк на метод (план) — фактически проверяется Checkstyle (см. `config/checkstyle/checkstyle.xml`)
-- Асинхронность — только через `Bukkit.getScheduler()`, никогда `new Thread()` напрямую
-- Bukkit API — только в main thread (кроме async-fill и async-save статистики)
-- Внутренние зависимости — конструкторы, без static-синглтонов
+- Максимум 200 строк на файл (кроме сгенерированных/библиотечных)
+- Асинхронность — только через `Scheduler.get(plugin)`, никогда `new Thread()`
+- Bukkit API — только в main thread (кроме async-save статистики)
+- Изменяемое static состояние — только в `Mines.TYPES` и `StatsManager.stats`
---
-## Тесты
+## Известные проблемы
-JUnit 5 + Kotest + Mockito (`src/test/java`), 31 файл, все ≤150 строк. Покрывают: `Mines` (registry/lifecycle/loading), `Mine`/`MineTicker`, конфиги (`MineConfig`, `BlockConfig`, `BlockKey`, `ResetConfig`, `UIConfig`), статистику/лидерборды, утилиты (LocationParser, TimeFormatter, ChunkUtils, Selection), команды.
-
-> Часть тестов отключена (`@Disabled`) — требуют Paper `RegistryAccess` или WorldGuard, недоступных в unit-среде. Запуск: `./gradlew test`.
+| # | Проблема | Файл | Приоритет |
+|---|---|---|---|
+| 1 | `Leaderboard.getTopTotal()` — O(n log n) без кэша | `Leaderboard.java` | средний |
+| 2 | `mine.blocks = mine.volume` выставляется до завершения fill — ложный процент-reset во время сброса | `Mine.java` | средний |
+| 3 | `MineResetHandler.reset()` принимает `BlockSetter` параметром — лишняя связность | `MineResetHandler.java` | низкий |
+| 4 | Нет тестов на `MineResetHandler`, `MineBlockHandler`, `BlockListener`, `Mines` | `test/` | высокий |
+| 5 | `ChunkUtils` определяет Paper через `Class.forName` — хрупко | `ChunkUtils.java` | низкий |
+| 6 | `SelectionWand.drawLine` не ограничивает частицы по Y — спам для высоких шахт | `SelectionWand.java` | низкий |
diff --git a/README.md b/README.md
index f2719b7..dd1679d 100644
--- a/README.md
+++ b/README.md
@@ -1,141 +1,219 @@
-
-
# LoMines
-Multi-region mine management plugin with WorldGuard integration and player statistics for Paper servers.
+Advanced mine management plugin for Paper 1.21.4+
-
-
-
-
-
+## Features
-[English](#english) | [Русский](#russian)
+- **Multiple fill modes**: Cuboid (fill entire region) or Mask (fill marked positions only)
+- **Weighted block system**: Configure different blocks with spawn chances
+- **Automatic resets**: Timer-based or percentage-based triggers
+- **WorldGuard integration**: Auto-create regions for new mines
+- **PlaceholderAPI support**: Display mine info in other plugins
+- **GUI editor**: In-game mine configuration editor
+- **Player statistics**: Track blocks mined, mines reset, playtime
-
+## Commands
----
-
-
-
-## English
-
-### Overview
-
-LoMines is an advanced mine management plugin for Paper 1.19.2+ servers. It supports multiple fill modes, weighted block systems, automatic resets, WorldGuard integration, in-game GUI editor, and player statistics.
-
-### Features
-
-| Feature | Description |
-|---------|-------------|
-| Multiple fill modes | Cuboid (fill entire region) or Mask (fill marked positions) |
-| Weighted blocks | Configure spawn chances per block type |
-| Automatic resets | Timer-based or percentage-based triggers |
-| WorldGuard integration | Auto-create regions for new mines |
-| GUI editor | In-game mine configuration |
-| Player statistics | Track blocks mined, resets, playtime |
-| Leaderboards | Top players per mine or global |
-| Group wand | Create multiple mines from batch selections |
-| PlaceholderAPI | Display mine info in other plugins |
-| Holograms | DecentHolograms and HolographicDisplays support |
-
-### Commands
+### Admin Commands
| Command | Permission | Description |
|---------|------------|-------------|
-| `/lm create ` | `lomines.admin` | Create a new mine |
-| `/lm delete ` | `lomines.admin` | Delete a mine |
-| `/lm reset [silent]` | `lomines.admin` | Reset a mine |
-| `/lm reload` | `lomines.admin` | Reload configs |
-| `/lm list` | `lomines.admin` | List all mines |
-| `/lm edit ` | `lomines.admin.edit` | Open GUI editor |
-| `/lm wand` | `lomines.admin.wand` | Get selection wand |
-| `/lm group ` | `lomines.admin.wand` | Batch mine creation |
-| `/lm setteleport ` | `lomines.admin.setteleport` | Set teleport point |
-| `/lm setspawn ` | `lomines.admin.setspawn` | Set safe spawn |
-| `/lm maskscan ` | `lomines.admin.maskscan` | Scan mask markers |
-| `/lm stats [player]` | `lomines.stats` | View statistics |
+| `/lm create ` | `lomines.admin.create` | Create a new mine |
+| `/lm delete ` | `lomines.admin.delete` | Delete a mine |
+| `/lm reset [silent]` | `lomines.admin.reset` | Reset a mine manually |
+| `/lm reload` | `lomines.admin.reload` | Reload all mine configs |
+| `/lm list` | `lomines.admin.list` | List all mines |
+| `/lm edit ` | `lomines.admin.edit` | Open GUI editor for a mine |
+| `/lm setteleport ` | `lomines.admin.setteleport` | Set teleport location (your current position) |
+| `/lm setspawn ` | `lomines.admin.setspawn` | Set safe spawn location for stuck players |
+| `/lm clearspawn ` | `lomines.admin.setspawn` | Clear spawn location |
+| `/lm maskscan ` | `lomines.admin.maskscan` | Scan mask marker blocks |
+
+### Player Commands
+
+| Command | Permission | Description |
+|---------|------------|-------------|
+| `/lm stats [player]` | `lomines.stats` | View mining statistics |
| `/lm top [mine] [limit]` | `lomines.stats` | View leaderboard |
-### Placeholders
+### Wand Commands
-| Placeholder | Description |
-|-------------|-------------|
-| `%lomines_mine__blocks%` | Current blocks in mine |
-| `%lomines_mine__percent%` | Fill percentage |
-| `%lomines_mine__remaining%` | Remaining blocks |
-| `%lomines_mine__resettime%` | Time since reset |
-| `%lomines_player_blocksmined%` | Total blocks mined |
-| `%lomines_player_rank%` | Leaderboard rank |
-| `%lomines_count%` | Total mines count |
+| Command | Permission | Description |
+|---------|------------|-------------|
+| `/lm wand` | `lomines.admin.wand` | Get the region selection wand |
+| `/lm group ` | `lomines.admin.wand` | Create mines from group wand selections |
-### Dependencies
+## Permissions
-- Required: Paper 1.19.2+, Java 21+
-- Optional: PlaceholderAPI, WorldGuard, DecentHolograms, HolographicDisplays, Oraxen, ItemsAdder
+```yaml
+lomines.admin:
+ description: All admin commands
+ default: op
+ children:
+ lomines.use: true
+ lomines.admin.create: true
+ lomines.admin.delete: true
+ lomines.admin.reset: true
+ lomines.admin.reload: true
+ lomines.admin.list: true
+ lomines.admin.edit: true
+ lomines.admin.setteleport: true
+ lomines.admin.setspawn: true
+ lomines.admin.maskscan: true
+ lomines.admin.wand: true
+ lomines.stats: true
+ lomines.stats.others: true
-### Installation
+lomines.use:
+ description: Basic usage
+ default: true
-1. Drop the jar into `plugins/`
-2. Restart the server
-3. Configure `plugins/LoMines/mines/` and `plugins/LoMines/defaults.yml`
+lomines.stats:
+ description: View own stats and leaderboards
+ default: true
----
+lomines.stats.others:
+ description: View other players' stats
+ default: op
+```
-
+## Placeholders (PlaceholderAPI)
-## Русский
+### Mine Information
-### Обзор
+| Placeholder | Description | Example |
+|-------------|-------------|---------|
+| `%lomines_mine__name%` | Mine name | `mymine` |
+| `%lomines_mine__blocks%` | Current blocks | `8542` |
+| `%lomines_mine__total%` | Total volume | `10000` |
+| `%lomines_mine__percent%` | Fill percentage (1 decimal) | `85.4` |
+| `%lomines_mine__percentint%` | Fill percentage (integer) | `85` |
+| `%lomines_mine__world%` | World name | `world` |
+| `%lomines_mine__remaining%` | Remaining blocks | `1458` |
+| `%lomines_mine__resettime%` | Time since reset (mm:ss) | `03:45` |
+| `%lomines_mine__resetseconds%` | Seconds since reset | `225` |
-LoMines - продвинутый плагин управления шахтами для Paper 1.19.2+ серверов. Поддерживает несколько режимов заполнения, взвешенные блоки, автоматические сбросы, интеграцию с WorldGuard, GUI-редактор и статистику игроков.
+### Player Statistics
-### Возможности
+| Placeholder | Description | Example |
+|-------------|-------------|---------|
+| `%lomines_player_blocksmined%` | Total blocks mined | `15234` |
+| `%lomines_player_minesreset%` | Mines reset count | `45` |
+| `%lomines_player_playtime%` | Formatted play time | `2h 15m` |
+| `%lomines_player_rank%` | Leaderboard rank | `3` |
-| Возможность | Описание |
-|-------------|----------|
-| Режимы заполнения | Cuboid (весь регион) или Mask (по меткам) |
-| Взвешенные блоки | Настройка шансов появления для каждого блока |
-| Авто-сбросы | По таймеру или по проценту добычи |
-| WorldGuard | Авто-создание регионов для новых шахт |
-| GUI-редактор | Настройка шахт прямо в игре |
-| Статистика | Добыто блоков, сбросы, время игры |
-| Лидерборды | Топ игроков по шахте или глобально |
-| Групповая палка | Создание нескольких шахт за раз |
-| PlaceholderAPI | Информация о шахтах в других плагинах |
-| Голограммы | Поддержка DecentHolograms и HolographicDisplays |
+### Global
-### Команды
+| Placeholder | Description | Example |
+|-------------|-------------|---------|
+| `%lomines_count%` | Total number of mines | `12` |
-| Команда | Право | Описание |
-|---------|-------|----------|
-| `/lm create <имя>` | `lomines.admin` | Создать шахту |
-| `/lm delete <имя>` | `lomines.admin` | Удалить шахту |
-| `/lm reset <имя>` | `lomines.admin` | Сбросить шахту |
-| `/lm edit <имя>` | `lomines.admin.edit` | Открыть GUI-редактор |
-| `/lm wand` | `lomines.admin.wand` | Получить палку выделения |
-| `/lm stats [игрок]` | `lomines.stats` | Статистика |
-| `/lm top [шахта]` | `lomines.stats` | Лидерборд |
+## Configuration
-### Зависимости
+### defaults.yml
-- Обязательные: Paper 1.19.2+, Java 21+
-- Опциональные: PlaceholderAPI, WorldGuard, DecentHolograms, Oraxen, ItemsAdder
+Default configuration applied to all mines:
-### Установка
+```yaml
+# Reset settings
+reset:
+ interval: "5m" # Reset interval (30s, 5m, 2h, 1d)
+ percent-trigger: 10.0 # Trigger reset when X% of blocks mined
+ percent-enabled: false # Enable percentage trigger
+ commands: [] # Commands to run on reset
+ broadcast: "" # Broadcast message on reset
-1. Положите jar в папку `plugins/`
-2. Перезапустите сервер
-3. Настройте `plugins/LoMines/mines/` и `plugins/LoMines/defaults.yml`
+# Block settings
+blocks:
+ fill-mode: CUBOID # CUBOID or MASK
+ mask:
+ marker: "pink_concrete" # Marker block for mask mode
----
+# Teleport settings
+teleport:
+ enabled: false
+ location: ""
-### Links
+# Safe spawn for stuck players
+player-spawn:
+ enabled: false
+ location: ""
-- [Releases](../../releases)
-- [Issues](../../issues)
-- [License](LICENSE)
+# UI settings
+ui:
+ actionbar:
+ enabled: true
+ format: "{mine} {percent}% ({time})"
+ range: 50.0
-### License
+# WorldGuard integration
+worldguard:
+ enabled: false
+ region-template: "{mine_name}_{random_4}"
+ owners: []
+ members: []
+ flags:
+ - "passthrough=deny"
+ - "build=allow"
+```
-GNU General Public License v3.0
\ No newline at end of file
+## Usage Examples
+
+### Creating a Simple Mine
+
+```bash
+# 1. Get the wand
+/lm wand
+
+# 2. Select two corners (left and right click)
+# 3. Create the mine
+/lm create mymine
+
+# 4. Edit the config file or use GUI
+/lm edit mymine
+```
+
+### Setting Up Teleport
+
+```bash
+# Stand where players should be teleported
+/lm setteleport mymine
+
+# Stand where STUCK players should spawn (optional, falls back to teleport location)
+/lm setspawn mymine
+```
+
+### Using Mask Fill Mode
+
+```bash
+# 1. Paint positions with pink_concrete (or configured marker)
+# 2. Scan the markers
+/lm maskscan mymine
+
+# Mine will now only fill at marked positions
+```
+
+### WorldGuard Auto-Regions
+
+Enable in `plugins/LoMines/mines/_defaults.yml`:
+
+```yaml
+worldguard:
+ enabled: true
+ region-template: "mine_{mine_name}_{random_4}"
+ flags:
+ - "passthrough=deny"
+ - "build=allow"
+```
+
+When you create a mine, a WorldGuard region `mine_mymine_7392` is automatically created.
+
+## Support
+
+- Paper 1.21.4+
+- Java 21+
+- Optional: PlaceholderAPI, WorldGuard
+
+## License
+
+MIT License
diff --git a/build.gradle.kts b/build.gradle.kts
index 1dd9a63..0caa85a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,6 @@
plugins {
java
id("com.github.johnrengelman.shadow") version "8.1.1"
- checkstyle
}
group = "com.loki"
@@ -14,88 +13,79 @@ java {
}
}
-tasks.withType {
- options.release.set(21)
-}
-
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
- maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
- maven("https://maven.enginehub.org/repo/")
- maven("https://repo.codemc.io/repository/maven-public/")
maven("https://jitpack.io")
}
-dependencies {
- compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
- implementation("org.apache.commons:commons-math3:3.6.1")
- implementation("commons-io:commons-io:2.18.0")
- compileOnly("me.clip:placeholderapi:2.11.6")
- compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.13")
- compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.10")
+// LoAPI: libs/lolib*.jar — fileTree индексируется Gradle / Java LS стабильнее, чем files(singleJar)
+val lolibTree = fileTree("libs") { include("lolib*.jar") }
+if (lolibTree.files.isEmpty()) {
+ throw GradleException(
+ "Missing libs/lolib*.jar — add LoAPI fat jar to ${rootProject.file("libs").absolutePath}"
+ )
+}
+dependencies {
+ // Paper API 1.21.4 (includes Adventure API)
+ compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
+
+ // LoAPI (local)
+ implementation(lolibTree)
+
+ // Runtime dependencies (loaded via DependencyManager from LoAPI)
+ // These are marked as compileOnly because they will be downloaded at runtime
+ compileOnly("org.apache.commons:commons-math3:3.6.1")
+ compileOnly("commons-io:commons-io:2.18.0")
+
+ // Adventure API (included in Paper, but explicit for clarity)
+ compileOnly("net.kyori:adventure-api:4.17.0")
+ compileOnly("net.kyori:adventure-text-minimessage:4.17.0")
+
+ // Optional plugin integrations (soft dependencies)
+ // compileOnly("com.github.oraxen:oraxen:1.180.0")
+ // compileOnly("com.github.LoneDev6:api-itemsadder:3.6.4")
+
+ // Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
testImplementation("io.kotest:kotest-assertions-core:5.9.1")
testImplementation("io.kotest:kotest-property:5.9.1")
testImplementation("org.mockito:mockito-core:5.14.2")
- testImplementation("org.mockito:mockito-inline:5.2.0")
- testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.110.0") {
- exclude(group = "org.junit.jupiter", module = "junit-jupiter-api")
- }
- testImplementation("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
- testImplementation("net.kyori:adventure-api:4.17.0")
- testImplementation("net.kyori:adventure-text-minimessage:4.17.0")
- testImplementation("com.sk89q.worldguard:worldguard-bukkit:7.0.13")
-}
-
-checkstyle {
- toolVersion = "10.21.4"
- configFile = file("config/checkstyle/checkstyle.xml")
- isIgnoreFailures = false
- maxErrors = 0
- maxWarnings = 0
-}
-
-tasks.withType().configureEach {
- reports {
- xml.required.set(true)
- html.required.set(true)
- }
+ testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
+ testImplementation("org.apache.commons:commons-math3:3.6.1")
+ testImplementation("commons-io:commons-io:2.18.0")
}
tasks {
-
shadowJar {
archiveClassifier.set("")
-
- // Temporary: disable relocate to avoid ASM Java 21 bytecode issues
- // relocate(
- // "dev.loki.lomines.libs.lolib",
- // "com.loki.lomines.libs.lolib"
- // )
+
+ // Relocate LoAPI to avoid conflicts
+ relocate("dev.loki.lomines.libs.lolib", "com.loki.lomines.libs.lolib")
+
+ // Don't minimize - causes issues with Java 21
+ // minimize()
}
-
+
build {
dependsOn(shadowJar)
}
-
+
test {
useJUnitPlatform()
}
-
+
processResources {
val props = mapOf(
"version" to version,
"description" to description
)
-
inputs.properties(props)
filteringCharset = "UTF-8"
-
filesMatching("plugin.yml") {
expand(props)
}
}
-}
\ No newline at end of file
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
deleted file mode 100644
index 946b028..0000000
--- a/config/checkstyle/checkstyle.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 1b33c55..63f2184 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradlew b/gradlew
deleted file mode 100755
index 23d15a9..0000000
--- a/gradlew
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/bin/sh
-
-#
-# Copyright © 2015-2021 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
-
-CLASSPATH="\\\"\\\""
-
-
-# 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" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
-
- 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" \
- -classpath "$CLASSPATH" \
- -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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index db3a6ac..c8f7d56 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,94 +1,7 @@
-@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
-
+@rem Gradle startup script for Windows
@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
-
-set CLASSPATH=
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -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
+java -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
diff --git a/libs/.gitkeep b/libs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/java/dev/loki/lomines/ComponentInitializer.java b/src/main/java/dev/loki/lomines/ComponentInitializer.java
index 42a9dc7..eb5952b 100644
--- a/src/main/java/dev/loki/lomines/ComponentInitializer.java
+++ b/src/main/java/dev/loki/lomines/ComponentInitializer.java
@@ -1,11 +1,11 @@
package dev.loki.lomines;
-import dev.loki.lomines.core.mine.service.MineTicker;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.stats.service.StatsManager;
+import dev.loki.lomines.core.mine.MineTicker;
+import dev.loki.lomines.core.mine.Mines;
+import dev.loki.lomines.data.stats.StatsManager;
import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.wand.group.GroupWandManager;
-
+import dev.lolib.commands.CommandManager;
import java.io.IOException;
import java.nio.file.Files;
@@ -28,7 +28,7 @@ final class ComponentInitializer {
if (!Files.exists(minesFolder)) {
Files.createDirectories(minesFolder);
- plugin.getLogger().info("Created mines directory");
+ plugin.loLogger().info("Created mines directory");
}
}
@@ -36,19 +36,20 @@ final class ComponentInitializer {
Mines mines = new Mines(plugin);
GroupWandManager groupWandManager = new GroupWandManager();
StatsManager statsManager = new StatsManager(plugin);
+ CommandManager commandManager = new CommandManager(plugin);
IntegrationManager integrationManager = new IntegrationManager(plugin);
- plugin.getLogger().info("Core components initialized");
+ plugin.loLogger().info("Core components initialized");
- return new Components(mines, groupWandManager, statsManager, integrationManager);
+ return new Components(mines, groupWandManager, statsManager, commandManager, integrationManager);
}
void loadMines(Mines mines) {
try {
mines.loadAll();
- plugin.getLogger().info("Loaded " + mines.getAll().size() + " mine(s)");
+ plugin.loLogger().info("Loaded " + mines.getAll().size() + " mine(s)");
} catch (IOException e) {
- plugin.getLogger().severe("Failed to load mines: " + e.getMessage());
+ plugin.loLogger().error("Failed to load mines: " + e.getMessage());
e.printStackTrace();
}
}
@@ -64,9 +65,9 @@ final class ComponentInitializer {
try {
statsManager.load();
statsManager.startAutoSave();
- plugin.getLogger().info("Statistics system enabled");
+ plugin.loLogger().info("Statistics system enabled");
} catch (IOException e) {
- plugin.getLogger().warning("Failed to load statistics: " + e.getMessage());
+ plugin.loLogger().warn("Failed to load statistics: " + e.getMessage());
}
}
}
@@ -75,6 +76,7 @@ final class ComponentInitializer {
Mines mines,
GroupWandManager groupWandManager,
StatsManager statsManager,
+ CommandManager commandManager,
IntegrationManager integrationManager
) {
}
diff --git a/src/main/java/dev/loki/lomines/LoMinesPlugin.java b/src/main/java/dev/loki/lomines/LoMinesPlugin.java
index e6d46f9..b80a7ec 100644
--- a/src/main/java/dev/loki/lomines/LoMinesPlugin.java
+++ b/src/main/java/dev/loki/lomines/LoMinesPlugin.java
@@ -1,26 +1,33 @@
package dev.loki.lomines;
-import dev.loki.lomines.core.mine.service.MineTicker;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.stats.service.StatsManager;
+import dev.loki.lomines.core.mine.MineTicker;
+import dev.loki.lomines.core.mine.Mines;
+import dev.loki.lomines.data.stats.StatsManager;
import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.integration.hologram.HologramManager;
import dev.loki.lomines.wand.WandParticleService;
import dev.loki.lomines.wand.group.GroupWandManager;
-import org.bukkit.plugin.java.JavaPlugin;
+import dev.lolib.commands.CommandManager;
+import dev.lolib.core.LoPlugin;
+import dev.lolib.core.dependency.DependencyManager;
-public final class LoMinesPlugin extends JavaPlugin {
+/**
+ * Main plugin class for LoMines.
+ * Extends LoPlugin from LoAPI for Paper/Folia compatibility.
+ */
+public final class LoMinesPlugin extends LoPlugin {
private Mines mines;
private MineTicker mineTicker;
private GroupWandManager groupWandManager;
private StatsManager statsManager;
+ private CommandManager commandManager;
private IntegrationManager integrationManager;
private WandParticleService wandParticleService;
private HologramManager hologramManager;
@Override
- public void onEnable() {
+ protected void enable() {
try {
saveDefaultConfig();
@@ -31,6 +38,7 @@ public final class LoMinesPlugin extends JavaPlugin {
this.mines = components.mines();
this.groupWandManager = components.groupWandManager();
this.statsManager = components.statsManager();
+ this.commandManager = components.commandManager();
this.integrationManager = components.integrationManager();
this.wandParticleService = new WandParticleService(this);
this.hologramManager = new HologramManager(this);
@@ -39,51 +47,65 @@ public final class LoMinesPlugin extends JavaPlugin {
this.mineTicker = initializer.startTicker(mines);
RegistrationManager registrationManager = new RegistrationManager(this);
- registrationManager.registerCommands();
+ registrationManager.registerCommands(commandManager);
+ registrationManager.registerTabCompleter();
registrationManager.registerListeners();
registrationManager.initializeIntegrations(integrationManager);
initializer.startStatistics(statsManager);
- getLogger().info("LoMines has been enabled!");
+ loLogger().info("LoMines has been enabled!");
} catch (Exception e) {
- getLogger().severe("Failed to enable LoMines: " + e.getMessage());
+ loLogger().error("Failed to enable LoMines: " + e.getMessage());
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
}
}
@Override
- public void onDisable() {
+ protected void disable() {
try {
if (mineTicker != null) {
mineTicker.stop();
mineTicker = null;
}
+
if (mines != null) {
mines.getAll().forEach(mine -> mine.stop());
}
+
if (statsManager != null) {
statsManager.stopAutoSave();
statsManager.save();
- getLogger().info("Statistics saved successfully");
+ loLogger().info("Statistics saved successfully");
}
+
if (integrationManager != null) {
integrationManager.shutdown();
}
+
if (wandParticleService != null) {
wandParticleService.stopAll();
}
+
if (hologramManager != null) {
hologramManager.shutdown();
}
- getLogger().info("LoMines has been disabled!");
+
+ loLogger().info("LoMines has been disabled!");
} catch (Exception e) {
- getLogger().severe("Error during plugin shutdown: " + e.getMessage());
+ loLogger().error("Error during plugin shutdown: " + e.getMessage());
e.printStackTrace();
}
}
+ @Override
+ @SuppressWarnings("removal")
+ protected void dependencies(DependencyManager dependencyManager) {
+ dependencyManager.add("org.apache.commons", "commons-math3", "3.6.1");
+ dependencyManager.add("commons-io", "commons-io", "2.18.0");
+ }
+
public Mines getMines() {
return mines;
}
@@ -96,6 +118,10 @@ public final class LoMinesPlugin extends JavaPlugin {
return statsManager;
}
+ public CommandManager getCommandManager() {
+ return commandManager;
+ }
+
public IntegrationManager getIntegrationManager() {
return integrationManager;
}
diff --git a/src/main/java/dev/loki/lomines/RegistrationManager.java b/src/main/java/dev/loki/lomines/RegistrationManager.java
index 32b878e..d8e7436 100644
--- a/src/main/java/dev/loki/lomines/RegistrationManager.java
+++ b/src/main/java/dev/loki/lomines/RegistrationManager.java
@@ -1,13 +1,22 @@
package dev.loki.lomines;
-import dev.loki.lomines.command.LmCommand;
+import dev.loki.lomines.command.admin.AdminCommands;
+import dev.loki.lomines.command.admin.HologramCommands;
+import dev.loki.lomines.command.admin.MaskCommands;
+import dev.loki.lomines.command.admin.StatsCommands;
+import dev.loki.lomines.command.common.LoMinesTabCompleter;
+import dev.loki.lomines.command.player.PlayerCommands;
import dev.loki.lomines.integration.IntegrationManager;
-import dev.loki.lomines.listener.block.BlockBreakListener;
-import dev.loki.lomines.listener.gui.GroupGuiListener;
-import dev.loki.lomines.listener.gui.MineEditGuiListener;
-import dev.loki.lomines.listener.player.PlayerInteractListener;
-import dev.loki.lomines.listener.player.PlayerJoinListener;
+import dev.loki.lomines.listener.BlockBreakListener;
+import dev.loki.lomines.listener.GroupGuiListener;
+import dev.loki.lomines.listener.MineEditGuiListener;
+import dev.loki.lomines.listener.PlayerInteractListener;
+import dev.loki.lomines.listener.PlayerJoinListener;
+import dev.lolib.commands.CommandManager;
+/**
+ * Handles registration of commands, listeners, and integrations.
+ */
final class RegistrationManager {
private final LoMinesPlugin plugin;
@@ -16,16 +25,24 @@ final class RegistrationManager {
this.plugin = plugin;
}
- void registerCommands() {
- LmCommand executor = new LmCommand(plugin);
- for (String alias : new String[]{"lm", "lomines", "mine", "mines"}) {
- var cmd = plugin.getCommand(alias);
- if (cmd != null) {
- cmd.setExecutor(executor);
- cmd.setTabCompleter(executor);
- }
- }
- plugin.getLogger().info("Commands registered");
+ void registerCommands(CommandManager commandManager) {
+ commandManager.register(new AdminCommands(plugin));
+ commandManager.register(new PlayerCommands(plugin));
+ commandManager.register(new StatsCommands(plugin));
+ commandManager.register(new MaskCommands(plugin));
+ commandManager.register(new HologramCommands(plugin));
+
+ plugin.loLogger().info("Commands registered");
+ }
+
+ void registerTabCompleter() {
+ LoMinesTabCompleter tabCompleter = new LoMinesTabCompleter(plugin);
+ plugin.getCommand("lm").setTabCompleter(tabCompleter);
+ plugin.getCommand("lomines").setTabCompleter(tabCompleter);
+ plugin.getCommand("mine").setTabCompleter(tabCompleter);
+ plugin.getCommand("mines").setTabCompleter(tabCompleter);
+
+ plugin.loLogger().info("Tab completer registered");
}
void registerListeners() {
@@ -37,11 +54,12 @@ final class RegistrationManager {
if (plugin.getConfig().getBoolean("statistics-enabled", true)) {
plugin.getServer().getPluginManager().registerEvents(new PlayerJoinListener(plugin), plugin);
}
- plugin.getLogger().info("Event listeners registered");
+
+ plugin.loLogger().info("Event listeners registered");
}
void initializeIntegrations(IntegrationManager integrationManager) {
integrationManager.initAll();
- plugin.getLogger().info("Integrations initialized");
+ plugin.loLogger().info("Integrations initialized");
}
}
diff --git a/src/main/java/dev/loki/lomines/block/BlockSetter.java b/src/main/java/dev/loki/lomines/block/BlockSetter.java
index be5fcb0..537ac5a 100644
--- a/src/main/java/dev/loki/lomines/block/BlockSetter.java
+++ b/src/main/java/dev/loki/lomines/block/BlockSetter.java
@@ -1,6 +1,6 @@
package dev.loki.lomines.block;
-import dev.loki.lomines.util.location.geo.Cuboid;
+import dev.loki.lomines.util.location.Cuboid;
import org.bukkit.Location;
import java.util.List;
diff --git a/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java b/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java
index 2c93c22..e336bcd 100644
--- a/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java
+++ b/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java
@@ -3,8 +3,8 @@ package dev.loki.lomines.block;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.util.block.BlockUpdateUtil;
-import dev.loki.lomines.util.location.geo.Cuboid;
-
+import dev.loki.lomines.util.location.Cuboid;
+import dev.lolib.scheduler.Scheduler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -35,11 +35,11 @@ public final class BukkitBlockSetter extends BlockSetter {
@Override
public void fill(Cuboid region, IntConsumer callback) {
- Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ Scheduler.get(plugin).runAsync(() -> {
int count = fillSync(region);
// Send block updates on main thread to prevent ghost blocks
- Bukkit.getScheduler().runTask(plugin, () -> {
+ Scheduler.get(plugin).run(() -> {
BlockUpdateUtil.sendRegionUpdate(
region.getWorld(),
region.getMinX(), region.getMinY(), region.getMinZ(),
@@ -53,14 +53,14 @@ public final class BukkitBlockSetter extends BlockSetter {
@Override
public void fillAtLocations(List locations, IntConsumer callback) {
if (locations == null || locations.isEmpty()) {
- Bukkit.getScheduler().runTask(plugin, () -> callback.accept(0));
+ Scheduler.get(plugin).run(() -> callback.accept(0));
return;
}
- Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ Scheduler.get(plugin).runAsync(() -> {
int count = fillAtLocationsSync(locations);
// Send block updates on main thread to prevent ghost blocks
- Bukkit.getScheduler().runTask(plugin, () -> {
+ Scheduler.get(plugin).run(() -> {
BlockUpdateUtil.sendLocationsUpdate(locations);
callback.accept(count);
});
@@ -106,8 +106,8 @@ public final class BukkitBlockSetter extends BlockSetter {
currentWeight += entry.getValue();
if (random <= currentWeight) {
// BlockKey is already validated to be a block
- if (entry.getKey() instanceof BlockKey.Vanilla(Material material)) {
- return Bukkit.createBlockData(material);
+ if (entry.getKey() instanceof BlockKey.Vanilla vanilla) {
+ return Bukkit.createBlockData(vanilla.material());
}
// Fallback for non-vanilla keys in vanilla setter (shouldn't happen)
return Bukkit.createBlockData(Material.STONE);
@@ -116,8 +116,8 @@ public final class BukkitBlockSetter extends BlockSetter {
// Fallback to first entry
BlockKey firstKey = weights.keySet().iterator().next();
- if (firstKey instanceof BlockKey.Vanilla(Material material)) {
- return Bukkit.createBlockData(material);
+ if (firstKey instanceof BlockKey.Vanilla vanilla) {
+ return Bukkit.createBlockData(vanilla.material());
}
return Bukkit.createBlockData(Material.STONE);
}
diff --git a/src/main/java/dev/loki/lomines/command/LmCommand.java b/src/main/java/dev/loki/lomines/command/LmCommand.java
deleted file mode 100644
index edcde6c..0000000
--- a/src/main/java/dev/loki/lomines/command/LmCommand.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package dev.loki.lomines.command;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.command.admin.info.InfoCommand;
-import dev.loki.lomines.command.admin.info.RegionCommands;
-import dev.loki.lomines.command.admin.manage.AdminCommands;
-import dev.loki.lomines.command.admin.manage.CopyCommand;
-import dev.loki.lomines.command.admin.misc.MaskCommands;
-import dev.loki.lomines.command.admin.misc.TeleportCommands;
-import dev.loki.lomines.command.admin.stats.HologramCommands;
-import dev.loki.lomines.command.admin.stats.StatsCommands;
-import dev.loki.lomines.command.player.PlayerCommands;
-import dev.loki.lomines.command.player.TeleportCommand;
-import dev.loki.lomines.command.common.LoMinesTabCompleter;
-import dev.loki.lomines.util.ErrorHandler;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.TabCompleter;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.List;
-
-public class LmCommand implements CommandExecutor, TabCompleter {
-
- private final LoMinesPlugin plugin;
- private final AdminCommands adminCommands;
- private final InfoCommand infoCommand;
- private final RegionCommands regionCommands;
- private final CopyCommand copyCommand;
- private final MaskCommands maskCommands;
- private final TeleportCommands teleportCommands;
- private final HologramCommands hologramCommands;
- private final StatsCommands statsCommands;
- private final PlayerCommands playerCommands;
- private final TeleportCommand tpCommand;
- private final LoMinesTabCompleter tabCompleter;
-
- public LmCommand(LoMinesPlugin plugin) {
- this.plugin = plugin;
- this.adminCommands = new AdminCommands(plugin);
- this.infoCommand = new InfoCommand(plugin);
- this.regionCommands = new RegionCommands(plugin);
- this.copyCommand = new CopyCommand(plugin);
- this.maskCommands = new MaskCommands(plugin);
- this.teleportCommands = new TeleportCommands(plugin, plugin.getMines(), new ErrorHandler(plugin.getLogger(), "[LoMines] "));
- this.hologramCommands = new HologramCommands(plugin);
- this.statsCommands = new StatsCommands(plugin);
- this.playerCommands = new PlayerCommands(plugin);
- this.tpCommand = new TeleportCommand(plugin);
- this.tabCompleter = new LoMinesTabCompleter(plugin);
- }
-
- @Override
- public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
- if (args.length == 0) {
- sender.sendMessage("§6/lm help §f- Show help");
- return true;
- }
-
- String sub = args[0].toLowerCase();
- String[] rest = new String[args.length - 1];
- System.arraycopy(args, 1, rest, 0, rest.length);
-
- switch (sub) {
- case "create", "delete", "edit", "reset", "reload", "list" -> adminCommands.handle(sender, sub, rest);
- case "info" -> infoCommand.handle(sender, rest);
- case "regions", "addregion", "removeregion" -> regionCommands.handle(sender, sub, rest);
- case "copy" -> copyCommand.handle(sender, rest);
- case "maskscan" -> maskCommands.handle(sender, rest);
- case "setteleport", "setspawn", "clearspawn" -> teleportCommands.handle(sender, sub, rest);
- case "hologram" -> hologramCommands.handle(sender, rest);
- case "stats", "top" -> statsCommands.handle(sender, sub, rest);
- case "tp" -> tpCommand.handle(sender, rest);
- case "wand", "group", "help" -> playerCommands.handle(sender, sub, rest);
- default -> sender.sendMessage("§cUnknown command: " + sub);
- }
- return true;
- }
-
- @Override
- public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
- return tabCompleter.onTabComplete(sender, command, alias, args);
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/manage/MineActionHandler.java b/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java
similarity index 52%
rename from src/main/java/dev/loki/lomines/command/admin/manage/MineActionHandler.java
rename to src/main/java/dev/loki/lomines/command/admin/AdminCommands.java
index 261883d..5560b01 100644
--- a/src/main/java/dev/loki/lomines/command/admin/manage/MineActionHandler.java
+++ b/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java
@@ -1,31 +1,45 @@
-package dev.loki.lomines.command.admin.manage;
+package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.registry.Mines;
+import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.core.mine.Mines;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import dev.loki.lomines.util.ErrorHandler;
import dev.loki.lomines.util.MessageFormatter;
+import dev.lolib.commands.annotation.Arg;
+import dev.lolib.commands.annotation.Command;
+import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.io.IOException;
+import java.util.Collection;
-public final class MineActionHandler {
+/**
+ * Admin commands for mine management.
+ * Provides commands for creating, deleting, resetting, reloading, listing, and editing mines.
+ */
+@Command(value = "lm", permission = "lomines.use")
+public class AdminCommands {
private final LoMinesPlugin plugin;
private final Mines mines;
private final ErrorHandler errorHandler;
- public MineActionHandler(LoMinesPlugin plugin, Mines mines, ErrorHandler errorHandler) {
+ public AdminCommands(LoMinesPlugin plugin) {
this.plugin = plugin;
- this.mines = mines;
- this.errorHandler = errorHandler;
+ this.mines = plugin.getMines();
+ this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
}
- public void create(CommandSender sender, String name) {
+ /**
+ * Creates a new mine with the given name.
+ * Usage: /lm create
+ */
+ @Subcommand(value = "create", permission = "lomines.admin.create")
+ public void create(CommandSender sender, @Arg("name") String name) {
try {
mines.create(name);
sender.sendMessage(MessageFormatter.success("Successfully created mine: " + name));
@@ -44,7 +58,12 @@ public final class MineActionHandler {
}
}
- public void delete(CommandSender sender, String name) {
+ /**
+ * Deletes an existing mine.
+ * Usage: /lm delete
+ */
+ @Subcommand(value = "delete", permission = "lomines.admin.delete")
+ public void delete(CommandSender sender, @Arg("name") String name) {
try {
mines.delete(name);
sender.sendMessage(MessageFormatter.success("Successfully deleted mine: " + name));
@@ -56,7 +75,12 @@ public final class MineActionHandler {
}
}
- public void edit(CommandSender sender, String mineName) {
+ /**
+ * Opens the GUI editor for a mine.
+ * Usage: /lm edit
+ */
+ @Subcommand(value = "edit", permission = "lomines.admin.edit")
+ public void edit(CommandSender sender, @Arg("mine") String mineName) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
return;
@@ -71,7 +95,16 @@ public final class MineActionHandler {
MineEditGui.open(plugin, player, mineName);
}
- public void reset(CommandSender sender, String mineName, boolean silent) {
+ /**
+ * Resets a mine by refilling all its regions.
+ * Usage: /lm reset [silent]
+ */
+ @Subcommand(value = "reset", permission = "lomines.admin.reset")
+ public void reset(
+ CommandSender sender,
+ @Arg("mine") String mineName,
+ @Arg(value = "silent", optional = true, defaultValue = "false") boolean silent
+ ) {
try {
Mine mine = mines.get(mineName);
mine.reset(silent);
@@ -81,6 +114,11 @@ public final class MineActionHandler {
}
}
+ /**
+ * Reloads all mine configurations from disk.
+ * Usage: /lm reload
+ */
+ @Subcommand(value = "reload", permission = "lomines.admin.reload")
public void reload(CommandSender sender) {
try {
mines.getAll().forEach(Mine::stop);
@@ -92,4 +130,32 @@ public final class MineActionHandler {
"Failed to reload mines", e);
}
}
+
+ /**
+ * Lists all active mines.
+ * Usage: /lm list
+ */
+ @Subcommand(value = "list", permission = "lomines.admin.list")
+ public void list(CommandSender sender) {
+ Collection allMines = mines.getAll();
+
+ if (allMines.isEmpty()) {
+ sender.sendMessage(Component.text("No mines found"));
+ return;
+ }
+
+ sender.sendMessage(Component.text("=== Mines (" + allMines.size() + ") ==="));
+
+ for (Mine mine : allMines) {
+ String status = formatMineStatus(mine);
+ sender.sendMessage(Component.text("- " + mine.getName() + " " + status));
+ }
+ }
+
+ private String formatMineStatus(Mine mine) {
+ int blocks = mine.getBlocks();
+ int total = mine.getTotalVolume();
+ double percent = mine.getPercentFilled();
+ return String.format("(%d/%d blocks, %.1f%%)", blocks, total, percent);
+ }
}
diff --git a/src/main/java/dev/loki/lomines/command/admin/stats/HologramCommands.java b/src/main/java/dev/loki/lomines/command/admin/HologramCommands.java
similarity index 58%
rename from src/main/java/dev/loki/lomines/command/admin/stats/HologramCommands.java
rename to src/main/java/dev/loki/lomines/command/admin/HologramCommands.java
index abb9a20..57ee1ce 100644
--- a/src/main/java/dev/loki/lomines/command/admin/stats/HologramCommands.java
+++ b/src/main/java/dev/loki/lomines/command/admin/HologramCommands.java
@@ -1,30 +1,35 @@
-package dev.loki.lomines.command.admin.stats;
+package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
+import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.integration.hologram.HologramManager;
+import dev.lolilb.commands.annotation.Arg;
+import dev.lolilb.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
-public class HologramCommands {
+/**
+ * Commands for managing holograms above mines.
+ */
+class HologramCommands {
private final LoMinesPlugin plugin;
private final HologramManager hologramManager;
- public HologramCommands(LoMinesPlugin plugin) {
+ HologramCommands(LoMinesPlugin plugin) {
this.plugin = plugin;
this.hologramManager = plugin.getHologramManager();
}
- public void handle(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm hologram [on|off]", NamedTextColor.RED));
- return;
- }
- String mineName = args[0];
- String state = args.length > 1 ? args[1] : null;
-
+ /**
+ * Toggles hologram for a mine.
+ * Usage: /lm hologram [on|off]
+ */
+ @Subcommand(value = "hologram", permission = "lomines.admin.hologram")
+ void hologram(CommandSender sender, @Arg("mine") String mineName,
+ @Arg(value = "state", optional = true) String state) {
Mine mine = plugin.getMines().find(mineName).orElse(null);
if (mine == null) {
sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
@@ -38,9 +43,10 @@ public class HologramCommands {
boolean enable;
if (state == null) {
+ // Toggle
enable = hologramManager.toggleHologram(mine);
} else {
- enable = "on".equalsIgnoreCase(state) || "true".equalsIgnoreCase(state) || "enable".equalsIgnoreCase(state);
+ enable = state.equalsIgnoreCase("on") || state.equalsIgnoreCase("true") || state.equalsIgnoreCase("enable");
if (enable) {
hologramManager.createMineHologram(mine);
} else {
@@ -49,6 +55,6 @@ public class HologramCommands {
}
sender.sendMessage(Component.text(
- "Голограмма для шахты " + mineName + " " + (enable ? "§aвключена" : "§cвыключена")));
+ "Голограмма для шахты " + mineName + " " + (enable ? "§aвключена" : "§cвыключена")));
}
}
diff --git a/src/main/java/dev/loki/lomines/command/admin/misc/MaskCommands.java b/src/main/java/dev/loki/lomines/command/admin/MaskCommands.java
similarity index 64%
rename from src/main/java/dev/loki/lomines/command/admin/misc/MaskCommands.java
rename to src/main/java/dev/loki/lomines/command/admin/MaskCommands.java
index d647610..c91fbe5 100644
--- a/src/main/java/dev/loki/lomines/command/admin/misc/MaskCommands.java
+++ b/src/main/java/dev/loki/lomines/command/admin/MaskCommands.java
@@ -1,16 +1,23 @@
-package dev.loki.lomines.command.admin.misc;
+package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.config.ConfigLoader;
+import dev.loki.lomines.core.mine.Mines;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
import dev.loki.lomines.util.ErrorHandler;
import dev.loki.lomines.util.MessageFormatter;
-
-import org.bukkit.Bukkit;
+import dev.lolib.commands.annotation.Arg;
+import dev.lolib.commands.annotation.Command;
+import dev.lolib.commands.annotation.Subcommand;
+import dev.lolib.scheduler.Scheduler;
import org.bukkit.command.CommandSender;
import java.io.IOException;
+/**
+ * Mask commands for mine shape management.
+ * Provides commands for scanning and saving mask positions.
+ */
+@Command(value = "lm", permission = "lomines.use")
public class MaskCommands {
private final LoMinesPlugin plugin;
@@ -23,13 +30,9 @@ public class MaskCommands {
this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
}
- public void handle(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(MessageFormatter.error("Usage: /lm maskscan "));
- return;
- }
- String name = args[0];
- Bukkit.getScheduler().runTask(plugin, () -> {
+ @Subcommand(value = "maskscan", permission = "lomines.admin.maskscan")
+ public void maskScan(CommandSender sender, @Arg("mine") String name) {
+ Scheduler.get(plugin).run(() -> {
try {
int n = mines.scanAndSaveMask(name);
sender.sendMessage(MessageFormatter.success(
diff --git a/src/main/java/dev/loki/lomines/command/admin/StatsCommands.java b/src/main/java/dev/loki/lomines/command/admin/StatsCommands.java
new file mode 100644
index 0000000..d6d293a
--- /dev/null
+++ b/src/main/java/dev/loki/lomines/command/admin/StatsCommands.java
@@ -0,0 +1,186 @@
+package dev.loki.lomines.command.admin;
+
+import dev.loki.lomines.LoMinesPlugin;
+import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.core.mine.Mines;
+import dev.loki.lomines.data.stats.Leaderboard;
+import dev.loki.lomines.data.stats.LeaderboardEntry;
+import dev.loki.lomines.data.stats.PlayerStats;
+import dev.loki.lomines.data.stats.StatsManager;
+import dev.loki.lomines.util.ErrorHandler;
+import dev.lolib.commands.annotation.Arg;
+import dev.lolib.commands.annotation.Command;
+import dev.lolib.commands.annotation.Subcommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Statistics commands for player mining stats and leaderboards.
+ * Provides commands for viewing stats and top players.
+ */
+@Command(value = "lm", permission = "lomines.use")
+public class StatsCommands {
+
+ private final LoMinesPlugin plugin;
+ private final Mines mines;
+ private final ErrorHandler errorHandler;
+
+ public StatsCommands(LoMinesPlugin plugin) {
+ this.plugin = plugin;
+ this.mines = plugin.getMines();
+ this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
+ }
+
+ private static void sendTopRows(CommandSender sender, List rows) {
+ if (rows.isEmpty()) {
+ sender.sendMessage(Component.text("Пока нет данных.", NamedTextColor.GRAY));
+ return;
+ }
+ for (int i = 0; i < rows.size(); i++) {
+ LeaderboardEntry e = rows.get(i);
+ String name = formatPlayerName(e.playerId());
+ sender.sendMessage(Component.text(
+ (i + 1) + ". " + name + " — " + e.count(),
+ NamedTextColor.WHITE));
+ }
+ }
+
+ private static String formatPlayerName(UUID id) {
+ String name = Bukkit.getOfflinePlayer(id).getName();
+ return name != null ? name : id.toString();
+ }
+
+ private static int clampLimit(int limit) {
+ return Math.max(1, Math.min(50, limit));
+ }
+
+ private static Integer tryParsePositiveInt(String s) {
+ try {
+ int v = Integer.parseInt(s);
+ return v > 0 ? v : null;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Shows mining stats for self or another player.
+ * Usage: /lm stats [player]
+ */
+ @Subcommand(value = "stats", permission = "lomines.stats")
+ public void stats(CommandSender sender, @Arg(value = "player", optional = true, defaultValue = "") String targetName) {
+ if (isStatsDisabled(sender)) {
+ return;
+ }
+ String name = targetName == null ? "" : targetName.trim();
+ UUID uuid;
+ if (name.isEmpty()) {
+ if (!(sender instanceof Player self)) {
+ errorHandler.sendError(sender, "Укажите игрока: /lm stats <ник>");
+ return;
+ }
+ uuid = self.getUniqueId();
+ } else {
+ if (!sender.hasPermission("lomines.stats.others")) {
+ errorHandler.handlePermissionDenied(sender, "смотреть чужую статистику");
+ return;
+ }
+ Player online = Bukkit.getPlayerExact(name);
+ if (online != null) {
+ uuid = online.getUniqueId();
+ } else {
+ OfflinePlayer off = Bukkit.getOfflinePlayer(name);
+ uuid = off.getUniqueId();
+ }
+ }
+ StatsManager sm = plugin.getStatsManager();
+ PlayerStats ps = sm.getOrCreate(uuid);
+ String displayName = formatPlayerName(uuid);
+ sender.sendMessage(Component.text("=== Статистика: " + displayName + " ===", NamedTextColor.GOLD));
+ sender.sendMessage(Component.text("Всего блоков: " + ps.getTotalBlocks(), NamedTextColor.WHITE));
+ int pos = sm.getLeaderboard().getPosition(uuid);
+ if (pos > 0) {
+ sender.sendMessage(Component.text("Место в общем топе: #" + pos, NamedTextColor.GRAY));
+ }
+ Map byMine = ps.getMineStatsSnapshot();
+ if (byMine.isEmpty()) {
+ return;
+ }
+ sender.sendMessage(Component.text("По шахтам:", NamedTextColor.GRAY));
+ byMine.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue().reversed())
+ .limit(12)
+ .forEach(e -> sender.sendMessage(Component.text(
+ " " + e.getKey() + ": " + e.getValue(), NamedTextColor.DARK_AQUA)));
+ }
+
+ /**
+ * Leaderboard: global or per-mine.
+ * Usage: /lm top — глобально 10; /lm top 25 — глобально 25; /lm top <шахта> — по шахте;
+ * /lm top <шахта> <лимит>
+ */
+ @Subcommand(value = "top", permission = "lomines.stats")
+ public void top(
+ CommandSender sender,
+ @Arg(value = "arg1", optional = true, defaultValue = "") String arg1,
+ @Arg(value = "arg2", optional = true, defaultValue = "") String arg2
+ ) {
+ if (isStatsDisabled(sender)) {
+ return;
+ }
+ String a = arg1 == null ? "" : arg1.trim();
+ String b = arg2 == null ? "" : arg2.trim();
+ int limit;
+ String mineName;
+ if (a.isEmpty()) {
+ limit = clampLimit(10);
+ mineName = null;
+ } else if (b.isEmpty()) {
+ Integer asLimit = tryParsePositiveInt(a);
+ if (asLimit != null) {
+ limit = clampLimit(asLimit);
+ mineName = null;
+ } else {
+ limit = clampLimit(10);
+ mineName = a;
+ }
+ } else {
+ mineName = a;
+ Integer limParsed = tryParsePositiveInt(b);
+ limit = clampLimit(limParsed != null ? limParsed : 10);
+ }
+ Leaderboard lb = plugin.getStatsManager().getLeaderboard();
+ if (mineName != null) {
+ var found = mines.find(mineName);
+ if (found.isEmpty()) {
+ errorHandler.handleNotFound(sender, "Шахта", mineName);
+ return;
+ }
+ Mine m = found.get();
+ String key = m.getName();
+ List rows = lb.getTopByMine(key, limit);
+ sender.sendMessage(Component.text("=== Топ по шахте «" + key + "» ===", NamedTextColor.GOLD));
+ sendTopRows(sender, rows);
+ return;
+ }
+ List rows = lb.getTopTotal(limit);
+ sender.sendMessage(Component.text("=== Общий топ ===", NamedTextColor.GOLD));
+ sendTopRows(sender, rows);
+ }
+
+ private boolean isStatsDisabled(CommandSender sender) {
+ if (!plugin.getConfig().getBoolean("statistics-enabled", true)) {
+ sender.sendMessage(Component.text("Статистика отключена в config.yml.", NamedTextColor.RED));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/dev/loki/lomines/command/admin/misc/TeleportActionHandler.java b/src/main/java/dev/loki/lomines/command/admin/TeleportCommands.java
similarity index 56%
rename from src/main/java/dev/loki/lomines/command/admin/misc/TeleportActionHandler.java
rename to src/main/java/dev/loki/lomines/command/admin/TeleportCommands.java
index dd974d5..5bfdb74 100644
--- a/src/main/java/dev/loki/lomines/command/admin/misc/TeleportActionHandler.java
+++ b/src/main/java/dev/loki/lomines/command/admin/TeleportCommands.java
@@ -1,32 +1,42 @@
-package dev.loki.lomines.command.admin.misc;
+package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.config.model.MineConfig;
+import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.core.mine.Mines;
+import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
import dev.loki.lomines.data.config.teleport.TeleportConfig;
import dev.loki.lomines.util.ErrorHandler;
import dev.loki.lomines.util.MessageFormatter;
+import dev.lolib.commands.annotation.Arg;
+import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-public final class TeleportActionHandler {
+/**
+ * Commands for managing teleport and spawn locations for mines.
+ */
+class TeleportCommands {
private final LoMinesPlugin plugin;
private final Mines mines;
private final ErrorHandler errorHandler;
- public TeleportActionHandler(LoMinesPlugin plugin, Mines mines, ErrorHandler errorHandler) {
+ TeleportCommands(LoMinesPlugin plugin, Mines mines, ErrorHandler errorHandler) {
this.plugin = plugin;
this.mines = mines;
this.errorHandler = errorHandler;
}
- public void setTeleport(CommandSender sender, String mineName) {
+ /**
+ * Sets the teleport location for players when mine resets.
+ * Usage: /lm setteleport
+ */
+ @Subcommand(value = "setteleport", permission = "lomines.admin.setteleport")
+ void setTeleport(CommandSender sender, @Arg("mine") String mineName) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
return;
@@ -40,7 +50,18 @@ public final class TeleportActionHandler {
}
var config = mine.getConfig();
- var updatedConfig = withTeleport(config, TeleportConfig.at(player.getLocation()));
+ var newTeleport = TeleportConfig.at(player.getLocation());
+
+ var updatedConfig = MineConfig.builder(mineName)
+ .region(config.region())
+ .blocks(config.blocks())
+ .reset(config.reset())
+ .rewards(config.rewards())
+ .teleport(newTeleport)
+ .ui(config.ui())
+ .worldGuard(config.worldGuard())
+ .playerSpawn(config.playerSpawn())
+ .build();
plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Teleport location for mine '" + mineName + "' set to your current position"));
@@ -51,7 +72,12 @@ public final class TeleportActionHandler {
}
}
- public void setSpawn(CommandSender sender, String mineName) {
+ /**
+ * Sets the safe spawn location for stuck players after mine reset.
+ * Usage: /lm setspawn
+ */
+ @Subcommand(value = "setspawn", permission = "lomines.admin.setspawn")
+ void setSpawn(CommandSender sender, @Arg("mine") String mineName) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
return;
@@ -65,7 +91,18 @@ public final class TeleportActionHandler {
}
var config = mine.getConfig();
- var updatedConfig = withPlayerSpawn(config, PlayerSpawnConfig.at(player.getLocation()));
+ var newSpawn = PlayerSpawnConfig.at(player.getLocation());
+
+ var updatedConfig = MineConfig.builder(mineName)
+ .region(config.region())
+ .blocks(config.blocks())
+ .reset(config.reset())
+ .rewards(config.rewards())
+ .teleport(config.teleport())
+ .ui(config.ui())
+ .worldGuard(config.worldGuard())
+ .playerSpawn(newSpawn)
+ .build();
plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Safe spawn location for mine '" + mineName + "' set to your current position"));
@@ -77,7 +114,12 @@ public final class TeleportActionHandler {
}
}
- public void clearSpawn(CommandSender sender, String mineName) {
+ /**
+ * Clears the spawn location for stuck players.
+ * Usage: /lm clearspawn
+ */
+ @Subcommand(value = "clearspawn", permission = "lomines.admin.setspawn")
+ void clearSpawn(CommandSender sender, @Arg("mine") String mineName) {
try {
Mine mine = mines.find(mineName).orElse(null);
if (mine == null) {
@@ -86,7 +128,16 @@ public final class TeleportActionHandler {
}
var config = mine.getConfig();
- var updatedConfig = withPlayerSpawn(config, PlayerSpawnConfig.disabled());
+ var updatedConfig = MineConfig.builder(mineName)
+ .region(config.region())
+ .blocks(config.blocks())
+ .reset(config.reset())
+ .rewards(config.rewards())
+ .teleport(config.teleport())
+ .ui(config.ui())
+ .worldGuard(config.worldGuard())
+ .playerSpawn(PlayerSpawnConfig.disabled())
+ .build();
plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Spawn location for mine '" + mineName + "' cleared"));
@@ -97,33 +148,7 @@ public final class TeleportActionHandler {
}
}
- private MineConfig withTeleport(MineConfig config, TeleportConfig teleport) {
- return MineConfig.builder(config.name())
- .region(config.region())
- .blocks(config.blocks())
- .reset(config.reset())
- .rewards(config.rewards())
- .teleport(teleport)
- .ui(config.ui())
- .worldGuard(config.worldGuard())
- .playerSpawn(config.playerSpawn())
- .build();
- }
-
- private MineConfig withPlayerSpawn(MineConfig config, PlayerSpawnConfig spawn) {
- return MineConfig.builder(config.name())
- .region(config.region())
- .blocks(config.blocks())
- .reset(config.reset())
- .rewards(config.rewards())
- .teleport(config.teleport())
- .ui(config.ui())
- .worldGuard(config.worldGuard())
- .playerSpawn(spawn)
- .build();
- }
-
- public String formatLocation(Location loc) {
+ private String formatLocation(Location loc) {
return String.format("%.1f, %.1f, %.1f in %s",
loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName());
}
diff --git a/src/main/java/dev/loki/lomines/command/admin/info/InfoCommand.java b/src/main/java/dev/loki/lomines/command/admin/info/InfoCommand.java
deleted file mode 100644
index 2c82737..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/info/InfoCommand.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package dev.loki.lomines.command.admin.info;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.data.config.block.BlockKey;
-import dev.loki.lomines.data.config.model.MineConfig;
-import dev.loki.lomines.util.location.geo.Cuboid;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-
-import java.util.List;
-import java.util.Map;
-
-public class InfoCommand {
-
- private final LoMinesPlugin plugin;
-
- public InfoCommand(LoMinesPlugin plugin) {
- this.plugin = plugin;
- }
-
- public void handle(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm info ", NamedTextColor.RED));
- return;
- }
- String mineName = args[0];
-
- Mine mine = plugin.getMines().find(mineName).orElse(null);
- if (mine == null) {
- sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
-
- var config = mine.getConfig();
- sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
- sender.sendMessage(Component.text("§6§lИнформация о шахте: §f" + mine.getName()));
- sender.sendMessage(Component.text(""));
- sender.sendMessage(Component.text("§7Мир: §f" + config.worldName()));
- sender.sendMessage(Component.text("§7Блоков: §f" + mine.getBlocks() + "§7/§f" + mine.getTotalVolume() +
- " §7(§f" + String.format("%.1f", mine.getPercentFilled()) + "%§7)"));
-
- printRegions(sender, mine.getRegions());
- printBlocks(sender, config.blocks().weights());
- printResetAndTeleport(sender, config);
-
- sender.sendMessage(Component.text("§6§lНаграды: §f" + config.rewards().entries().size() + " записей"));
- sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
- }
-
- private void printRegions(CommandSender sender, List regions) {
- sender.sendMessage(Component.text("§6§lРегионы:"));
- for (int i = 0; i < regions.size(); i++) {
- Cuboid r = regions.get(i);
- sender.sendMessage(Component.text("§7 #" + (i + 1) + ": §f" +
- r.getMinX() + "," + r.getMinY() + "," + r.getMinZ() + " §7-> §f" +
- r.getMaxX() + "," + r.getMaxY() + "," + r.getMaxZ() +
- " §7(§f" + r.getVolume() + " §7блоков)"));
- }
- sender.sendMessage(Component.text(""));
- }
-
- private void printBlocks(CommandSender sender, Map weights) {
- sender.sendMessage(Component.text("§6§lБлоки:"));
- weights.entrySet().stream()
- .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
- .limit(10)
- .forEach(e -> sender.sendMessage(Component.text(
- "§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%")));
- if (weights.size() > 10) {
- sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков"));
- }
- }
-
- private void printResetAndTeleport(CommandSender sender, MineConfig config) {
- sender.sendMessage(Component.text("§6§lНастройки сброса:"));
- sender.sendMessage(Component.text("§7 Интервал: §f" + config.reset().intervalDisplay()));
- sender.sendMessage(Component.text("§7 Триггер по %: §f" +
- (config.reset().isPercentTriggerEnabled() ? config.reset().percentTrigger() + "%" : "выкл")));
-
- sender.sendMessage(Component.text("§6§lТелепортация:"));
- sender.sendMessage(Component.text("§7 Точка телепорта: §f" +
- (config.teleport().enabled() ? "§aустановлена" : "§7не задана")));
- sender.sendMessage(Component.text("§7 Точка спавна: §f" +
- (config.playerSpawn().enabled() ? "§aустановлена" : "§7используется точка телепорта")));
- }
-
- private String formatBlockKey(BlockKey key) {
- return switch (key) {
- case BlockKey.Vanilla v -> v.material().name().toLowerCase();
- case BlockKey.Oraxen o -> "oraxen:" + o.id();
- case BlockKey.ItemsAdder i -> "ia:" + i.id();
- };
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/info/RegionActionHandler.java b/src/main/java/dev/loki/lomines/command/admin/info/RegionActionHandler.java
deleted file mode 100644
index db92fe2..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/info/RegionActionHandler.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package dev.loki.lomines.command.admin.info;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.data.config.model.MineConfig;
-import dev.loki.lomines.data.config.region.RegionConfig;
-import dev.loki.lomines.util.location.geo.Cuboid;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public final class RegionActionHandler {
-
- private final LoMinesPlugin plugin;
-
- public RegionActionHandler(LoMinesPlugin plugin) {
- this.plugin = plugin;
- }
-
- public void addRegion(CommandSender sender, String mineName) {
- if (!(sender instanceof Player player)) {
- sender.sendMessage(Component.text("Эта команда только для игроков!", NamedTextColor.RED));
- return;
- }
-
- Mine mine = plugin.getMines().find(mineName).orElse(null);
- if (mine == null) {
- player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
-
- Cuboid newRegion = resolveNewRegion(player, mine);
- if (newRegion == null) {
- return;
- }
-
- List currentRegions = new ArrayList<>(mine.getRegions());
- currentRegions.add(newRegion);
-
- updateMineRegions(mine, new RegionConfig(currentRegions));
-
- sendRegionAdded(player, newRegion, currentRegions.size());
- }
-
- private Cuboid resolveNewRegion(Player player, Mine mine) {
- var wandManager = plugin.getGroupWandManager();
- var session = wandManager.getSession(player.getUniqueId());
- int slot = session.getActiveSlot();
- var pos1 = session.getPos1(slot);
- var pos2 = session.getPos2(slot);
- if (pos1 == null || pos2 == null) {
- player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка"));
- return null;
- }
- Cuboid newRegion = new Cuboid(pos1, pos2);
- String mineWorld = mine.getConfig().region().worldName();
- String regionWorld = newRegion.getWorld().getName();
-
- if (!mineWorld.equals(regionWorld)) {
- player.sendMessage(Component.text("§cРегион должен быть в мире §f" + mineWorld + "§c, а не §f" + regionWorld));
- return null;
- }
- session.clearCorners(slot);
- return newRegion;
- }
-
- private void sendRegionAdded(Player player, Cuboid newRegion, int count) {
- player.sendMessage(Component.text("§aДобавлен регион #" + count));
- player.sendMessage(Component.text("§7От: §f" + newRegion.getMinX() + ", " + newRegion.getMinY() + ", " + newRegion.getMinZ()));
- player.sendMessage(Component.text("§7До: §f" + newRegion.getMaxX() + ", " + newRegion.getMaxY() + ", " + newRegion.getMaxZ()));
- player.sendMessage(Component.text("§7Объём: §f" + newRegion.getVolume() + " §7блоков"));
- }
-
- public void removeRegion(CommandSender sender, String mineName, int index) {
- Mine mine = plugin.getMines().find(mineName).orElse(null);
- if (mine == null) {
- sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
-
- List currentRegions = new ArrayList<>(mine.getRegions());
-
- if (currentRegions.size() <= 1) {
- sender.sendMessage(Component.text("§cНельзя удалить последний регион! Удалите всю шахту."));
- return;
- }
-
- if (index < 1 || index > currentRegions.size()) {
- sender.sendMessage(Component.text("§cНеверный номер региона. Доступны: 1-" + currentRegions.size()));
- return;
- }
-
- Cuboid removed = currentRegions.remove(index - 1);
- var newRegionConfig = new RegionConfig(currentRegions);
- updateMineRegions(mine, newRegionConfig);
-
- sender.sendMessage(Component.text("§aУдалён регион #" + index));
- sender.sendMessage(Component.text("§7Был: §f" + removed.getMinX() + ", " + removed.getMinY() + ", " + removed.getMinZ() +
- " §7-> §f" + removed.getMaxX() + ", " + removed.getMaxY() + ", " + removed.getMaxZ()));
- sender.sendMessage(Component.text("§7Осталось регионов: §f" + currentRegions.size()));
- }
-
- private void updateMineRegions(Mine mine, RegionConfig newConfig) {
- var oldConfig = mine.getConfig();
- var newMineConfig = new MineConfig(
- oldConfig.name(),
- newConfig,
- oldConfig.blocks(),
- oldConfig.reset(),
- oldConfig.rewards(),
- oldConfig.teleport(),
- oldConfig.ui(),
- oldConfig.worldGuard(),
- oldConfig.playerSpawn()
- );
-
- try {
- plugin.getMines().updateMineConfig(mine.getName(), newMineConfig);
- } catch (Exception e) {
- throw new IllegalStateException("Failed to update mine regions", e);
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/info/RegionCommands.java b/src/main/java/dev/loki/lomines/command/admin/info/RegionCommands.java
deleted file mode 100644
index 95fca1f..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/info/RegionCommands.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package dev.loki.lomines.command.admin.info;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.util.location.geo.Cuboid;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-
-import java.util.List;
-
-public class RegionCommands {
-
- private final LoMinesPlugin plugin;
- private final RegionActionHandler actionHandler;
-
- public RegionCommands(LoMinesPlugin plugin) {
- this.plugin = plugin;
- this.actionHandler = new RegionActionHandler(plugin);
- }
-
- public void handle(CommandSender sender, String subcommand, String[] args) {
- switch (subcommand.toLowerCase()) {
- case "regions" -> handleRegions(sender, args);
- case "addregion" -> handleAddRegion(sender, args);
- case "removeregion" -> handleRemoveRegion(sender, args);
- default -> {}
- }
- }
-
- private void handleRegions(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm regions ", NamedTextColor.RED));
- return;
- }
- String mineName = args[0];
- Mine mine = plugin.getMines().find(mineName).orElse(null);
- if (mine == null) {
- sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
-
- List regions = mine.getRegions();
- sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
- sender.sendMessage(Component.text("§6§lРегионы шахты §f" + mine.getName()));
- sender.sendMessage(Component.text("§7Всего регионов: §f" + regions.size()));
- sender.sendMessage(Component.text(""));
-
- for (int i = 0; i < regions.size(); i++) {
- Cuboid r = regions.get(i);
- sender.sendMessage(Component.text("§e#" + (i + 1) + " §7(§f" + r.getVolume() + " §7блоков)"));
- sender.sendMessage(Component.text(" §7Мир: §f" + r.getWorld().getName()));
- sender.sendMessage(Component.text(" §7От: §f" + r.getMinX() + ", " + r.getMinY() + ", " + r.getMinZ()));
- sender.sendMessage(Component.text(" §7До: §f" + r.getMaxX() + ", " + r.getMaxY() + ", " + r.getMaxZ()));
- }
- sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
- }
-
- private void handleAddRegion(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm addregion ", NamedTextColor.RED));
- return;
- }
- actionHandler.addRegion(sender, args[0]);
- }
-
- private void handleRemoveRegion(CommandSender sender, String[] args) {
- if (args.length < 2) {
- sender.sendMessage(Component.text("Usage: /lm removeregion ", NamedTextColor.RED));
- return;
- }
- try {
- int index = Integer.parseInt(args[1]);
- actionHandler.removeRegion(sender, args[0], index);
- } catch (NumberFormatException e) {
- sender.sendMessage(Component.text("Индекс должен быть числом: " + args[1], NamedTextColor.RED));
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/manage/AdminCommands.java b/src/main/java/dev/loki/lomines/command/admin/manage/AdminCommands.java
deleted file mode 100644
index 1f51a8d..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/manage/AdminCommands.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package dev.loki.lomines.command.admin.manage;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.util.ErrorHandler;
-import net.kyori.adventure.text.Component;
-import org.bukkit.command.CommandSender;
-
-import java.util.Collection;
-
-public class AdminCommands {
-
- private final LoMinesPlugin plugin;
- private final Mines mines;
- private final ErrorHandler errorHandler;
- private final MineActionHandler actionHandler;
-
- public AdminCommands(LoMinesPlugin plugin) {
- this.plugin = plugin;
- this.mines = plugin.getMines();
- this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
- this.actionHandler = new MineActionHandler(plugin, mines, errorHandler);
- }
-
- public void handle(CommandSender sender, String subcommand, String[] args) {
- switch (subcommand.toLowerCase()) {
- case "create" -> handleCreate(sender, args);
- case "delete" -> handleDelete(sender, args);
- case "edit" -> handleEdit(sender, args);
- case "reset" -> handleReset(sender, args);
- case "reload" -> actionHandler.reload(sender);
- case "list" -> handleList(sender);
- default -> {}
- }
- }
-
- private void handleCreate(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm create "));
- return;
- }
- actionHandler.create(sender, args[0]);
- }
-
- private void handleDelete(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm delete "));
- return;
- }
- actionHandler.delete(sender, args[0]);
- }
-
- private void handleEdit(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm edit "));
- return;
- }
- actionHandler.edit(sender, args[0]);
- }
-
- private void handleReset(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm reset [silent]"));
- return;
- }
- boolean silent = args.length > 1 && (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("silent"));
- actionHandler.reset(sender, args[0], silent);
- }
-
- private void handleList(CommandSender sender) {
- Collection allMines = mines.getAll();
- if (allMines.isEmpty()) {
- sender.sendMessage(Component.text("No mines found"));
- return;
- }
- sender.sendMessage(Component.text("=== Mines (" + allMines.size() + ") ==="));
- for (Mine mine : allMines) {
- String status = formatMineStatus(mine);
- sender.sendMessage(Component.text("- " + mine.getName() + " " + status));
- }
- }
-
- private String formatMineStatus(Mine mine) {
- int blocks = mine.getBlocks();
- int total = mine.getTotalVolume();
- double percent = mine.getPercentFilled();
- return String.format("(%d/%d blocks, %.1f%%)", blocks, total, percent);
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/manage/CopyCommand.java b/src/main/java/dev/loki/lomines/command/admin/manage/CopyCommand.java
deleted file mode 100644
index 4608feb..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/manage/CopyCommand.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package dev.loki.lomines.command.admin.manage;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.data.config.model.MineConfig;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-
-public class CopyCommand {
-
- private final LoMinesPlugin plugin;
-
- public CopyCommand(LoMinesPlugin plugin) {
- this.plugin = plugin;
- }
-
- public void handle(CommandSender sender, String[] args) {
- if (args.length < 2) {
- sender.sendMessage(Component.text("Usage: /lm copy ", NamedTextColor.RED));
- return;
- }
- String fromMineName = args[0];
- String toMineName = args[1];
-
- MinePair pair = resolveMines(sender, fromMineName, toMineName);
- if (pair == null) {
- return;
- }
-
- var sourceConfig = pair.fromMine().getConfig();
-
- MineConfig newTargetConfig = buildCopiedConfig(pair.toMine(), sourceConfig);
-
- try {
- plugin.getMines().updateMineConfig(pair.toMine().getName(), newTargetConfig);
- } catch (Exception e) {
- sender.sendMessage(Component.text("Ошибка сохранения: " + e.getMessage(), NamedTextColor.RED));
- return;
- }
-
- sendCopySummary(sender, pair.fromMine(), pair.toMine(), sourceConfig);
- }
-
- private record MinePair(Mine fromMine, Mine toMine) {
- }
-
- private MinePair resolveMines(CommandSender sender, String fromMineName, String toMineName) {
- Mine fromMine = plugin.getMines().find(fromMineName).orElse(null);
- if (fromMine == null) {
- sender.sendMessage(Component.text("Исходная шахта не найдена: " + fromMineName, NamedTextColor.RED));
- return null;
- }
-
- Mine toMine = plugin.getMines().find(toMineName).orElse(null);
- if (toMine == null) {
- sender.sendMessage(Component.text("Целевая шахта не найдена: " + toMineName, NamedTextColor.RED));
- return null;
- }
-
- if (fromMine.getName().equalsIgnoreCase(toMine.getName())) {
- sender.sendMessage(Component.text("Нельзя копировать шахту саму в себя!", NamedTextColor.RED));
- return null;
- }
- return new MinePair(fromMine, toMine);
- }
-
- private MineConfig buildCopiedConfig(Mine toMine, MineConfig sourceConfig) {
- var targetConfig = toMine.getConfig();
- return MineConfig.builder(toMine.getName())
- .region(targetConfig.region())
- .blocks(sourceConfig.blocks())
- .reset(sourceConfig.reset())
- .rewards(sourceConfig.rewards())
- .teleport(sourceConfig.teleport())
- .ui(sourceConfig.ui())
- .worldGuard(sourceConfig.worldGuard())
- .playerSpawn(sourceConfig.playerSpawn())
- .build();
- }
-
- private void sendCopySummary(CommandSender sender, Mine fromMine, Mine toMine, MineConfig sourceConfig) {
- sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() +
- " §aв §f" + toMine.getName()));
- sender.sendMessage(Component.text("§7Скопировано:"));
- sender.sendMessage(Component.text(" §7• Блоки: §f" + sourceConfig.blocks().weights().size() + " типов"));
- sender.sendMessage(Component.text(" §7• Настройки сброса"));
- sender.sendMessage(Component.text(" §7• Точки телепорта/спавна"));
- sender.sendMessage(Component.text(" §7• Награды: §f" + sourceConfig.rewards().entries().size() + " записей"));
- sender.sendMessage(Component.text("§7§oПримечание: регионы и позиции не скопированы"));
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/misc/TeleportCommands.java b/src/main/java/dev/loki/lomines/command/admin/misc/TeleportCommands.java
deleted file mode 100644
index 77f7ada..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/misc/TeleportCommands.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.loki.lomines.command.admin.misc;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.util.ErrorHandler;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-
-public class TeleportCommands {
-
- private final TeleportActionHandler actionHandler;
-
- public TeleportCommands(LoMinesPlugin plugin, Mines mines, ErrorHandler errorHandler) {
- this.actionHandler = new TeleportActionHandler(plugin, mines, errorHandler);
- }
-
- public void handle(CommandSender sender, String subcommand, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm " + subcommand + " ", NamedTextColor.RED));
- return;
- }
- switch (subcommand.toLowerCase()) {
- case "setteleport" -> actionHandler.setTeleport(sender, args[0]);
- case "setspawn" -> actionHandler.setSpawn(sender, args[0]);
- case "clearspawn" -> actionHandler.clearSpawn(sender, args[0]);
- default -> {}
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/stats/LeaderboardRenderer.java b/src/main/java/dev/loki/lomines/command/admin/stats/LeaderboardRenderer.java
deleted file mode 100644
index f86c39b..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/stats/LeaderboardRenderer.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package dev.loki.lomines.command.admin.stats;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.stats.model.LeaderboardEntry;
-import dev.loki.lomines.util.ErrorHandler;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.Bukkit;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.List;
-import java.util.UUID;
-
-public final class LeaderboardRenderer {
-
- private LeaderboardRenderer() {
- }
-
- public static void sendTopRows(CommandSender sender, List rows) {
- if (rows.isEmpty()) {
- sender.sendMessage(Component.text("Пока нет данных.", NamedTextColor.GRAY));
- return;
- }
- for (int i = 0; i < rows.size(); i++) {
- LeaderboardEntry e = rows.get(i);
- String name = formatPlayerName(e.playerId());
- sender.sendMessage(Component.text(
- (i + 1) + ". " + name + " \u2014 " + e.count(),
- NamedTextColor.WHITE));
- }
- }
-
- public static String formatPlayerName(UUID id) {
- String name = Bukkit.getOfflinePlayer(id).getName();
- return name != null ? name : id.toString();
- }
-
- public static int clampLimit(int limit) {
- return Math.max(1, Math.min(50, limit));
- }
-
- public static Integer tryParsePositiveInt(String s) {
- try {
- int v = Integer.parseInt(s);
- return v > 0 ? v : null;
- } catch (NumberFormatException e) {
- return null;
- }
- }
-
- public static UUID resolvePlayerUuid(CommandSender sender, String targetName, ErrorHandler errorHandler) {
- String name = targetName == null ? "" : targetName.trim();
- if (name.isEmpty()) {
- if (!(sender instanceof Player self)) {
- errorHandler.sendError(sender, "Укажите игрока: /lm stats <ник>");
- return null;
- }
- return self.getUniqueId();
- } else {
- if (!sender.hasPermission("lomines.stats.others")) {
- errorHandler.handlePermissionDenied(sender, "смотреть чужую статистику");
- return null;
- }
- Player online = Bukkit.getPlayerExact(name);
- if (online != null) {
- return online.getUniqueId();
- } else {
- OfflinePlayer off = Bukkit.getOfflinePlayer(name);
- return off.getUniqueId();
- }
- }
- }
-
- public static void renderTopCommand(CommandSender sender, String arg1, String arg2, Mines mines, LoMinesPlugin plugin) {
- TopRequest req = parseTopRequest(arg1, arg2);
- if (req.mineName() != null) {
- renderMineTop(sender, req.mineName(), req.limit(), mines, plugin);
- return;
- }
- List rows = plugin.getStatsManager().getLeaderboard().getTopTotal(req.limit());
- sender.sendMessage(Component.text("=== Общий топ ===", NamedTextColor.GOLD));
- sendTopRows(sender, rows);
- }
-
- private record TopRequest(int limit, String mineName) {
- }
-
- private static TopRequest parseTopRequest(String arg1, String arg2) {
- String a = arg1 == null ? "" : arg1.trim();
- String b = arg2 == null ? "" : arg2.trim();
- if (a.isEmpty()) {
- return new TopRequest(clampLimit(10), null);
- }
- if (b.isEmpty()) {
- Integer asLimit = tryParsePositiveInt(a);
- if (asLimit != null) {
- return new TopRequest(clampLimit(asLimit), null);
- }
- return new TopRequest(clampLimit(10), a);
- }
- Integer limParsed = tryParsePositiveInt(b);
- return new TopRequest(clampLimit(limParsed != null ? limParsed : 10), a);
- }
-
- private static void renderMineTop(CommandSender sender, String mineName, int limit, Mines mines, LoMinesPlugin plugin) {
- var found = mines.find(mineName);
- if (found.isEmpty()) {
- sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
- Mine m = found.get();
- String key = m.getName();
- List rows = plugin.getStatsManager().getLeaderboard().getTopByMine(key, limit);
- sender.sendMessage(Component.text("=== Топ по шахте \u00ab" + key + "\u00bb ===", NamedTextColor.GOLD));
- sendTopRows(sender, rows);
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/admin/stats/StatsCommands.java b/src/main/java/dev/loki/lomines/command/admin/stats/StatsCommands.java
deleted file mode 100644
index 4cd0bb0..0000000
--- a/src/main/java/dev/loki/lomines/command/admin/stats/StatsCommands.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package dev.loki.lomines.command.admin.stats;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.registry.Mines;
-import dev.loki.lomines.data.stats.model.PlayerStats;
-import dev.loki.lomines.data.stats.service.StatsManager;
-import dev.loki.lomines.util.ErrorHandler;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.command.CommandSender;
-
-import java.util.Map;
-import java.util.UUID;
-
-public class StatsCommands {
-
- private final LoMinesPlugin plugin;
- private final Mines mines;
- private final ErrorHandler errorHandler;
-
- public StatsCommands(LoMinesPlugin plugin) {
- this.plugin = plugin;
- this.mines = plugin.getMines();
- this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
- }
-
- public void handle(CommandSender sender, String subcommand, String[] args) {
- switch (subcommand.toLowerCase()) {
- case "stats" -> handleStats(sender, args);
- case "top" -> {
- if (isStatsDisabled(sender)) {
- return;
- }
- String arg1 = args.length > 0 ? args[0] : "";
- String arg2 = args.length > 1 ? args[1] : "";
- LeaderboardRenderer.renderTopCommand(sender, arg1, arg2, mines, plugin);
- }
- default -> {}
- }
- }
-
- private void handleStats(CommandSender sender, String[] args) {
- if (isStatsDisabled(sender)) {
- return;
- }
- String targetName = args.length > 0 ? args[0] : "";
- UUID uuid = LeaderboardRenderer.resolvePlayerUuid(sender, targetName, errorHandler);
- if (uuid == null) {
- return;
- }
-
- StatsManager sm = plugin.getStatsManager();
- PlayerStats ps = sm.getOrCreate(uuid);
- String displayName = LeaderboardRenderer.formatPlayerName(uuid);
- sender.sendMessage(Component.text("=== Статистика: " + displayName + " ===", NamedTextColor.GOLD));
- sender.sendMessage(Component.text("Всего блоков: " + ps.getTotalBlocks(), NamedTextColor.WHITE));
- int pos = sm.getLeaderboard().getPosition(uuid);
- if (pos > 0) {
- sender.sendMessage(Component.text("Место в общем топе: #" + pos, NamedTextColor.GRAY));
- }
- Map byMine = ps.getMineStatsSnapshot();
- if (byMine.isEmpty()) {
- return;
- }
- sender.sendMessage(Component.text("По шахтам:", NamedTextColor.GRAY));
- byMine.entrySet().stream()
- .sorted(Map.Entry.comparingByValue().reversed())
- .limit(12)
- .forEach(e -> sender.sendMessage(Component.text(
- " " + e.getKey() + ": " + e.getValue(), NamedTextColor.DARK_AQUA)));
- }
-
- private boolean isStatsDisabled(CommandSender sender) {
- if (!plugin.getConfig().getBoolean("statistics-enabled", true)) {
- sender.sendMessage(Component.text("Статистика отключена в config.yml.", NamedTextColor.RED));
- return true;
- }
- return false;
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java b/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java
index 23400ee..0ac51f4 100644
--- a/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java
+++ b/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java
@@ -4,26 +4,31 @@ import dev.loki.lomines.LoMinesPlugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
+import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
+/**
+ * Tab completer for LoMines commands.
+ * Provides auto-completion suggestions for all subcommands and arguments.
+ */
public class LoMinesTabCompleter implements TabCompleter {
+ private final LoMinesPlugin plugin;
+
private static final List ALL_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "wand", "group",
"stats", "top", "maskscan", "edit", "setteleport", "setspawn",
- "clearspawn", "info", "tp", "copy", "regions", "addregion", "removeregion", "help"
+ "clearspawn", "help"
);
- private final LoMinesPlugin plugin;
- private final SubcommandCompleter completer;
- private final PermissionPredicate permission;
+ private static final List BOOLEAN_VALUES = List.of("true", "false");
+ private static final List TOP_LIMITS = List.of("5", "10", "15", "20", "25", "50");
public LoMinesTabCompleter(LoMinesPlugin plugin) {
this.plugin = plugin;
- this.completer = new SubcommandCompleter(plugin);
- this.permission = new PermissionPredicate();
}
@Override
@@ -36,27 +41,130 @@ public class LoMinesTabCompleter implements TabCompleter {
return switch (args.length) {
case 1 -> completeSubcommands(sender, partial);
- case 2 -> completer.completeSecondArg(sender, args[0].toLowerCase(), partial);
- case 3 -> completer.completeThirdArg(args[0].toLowerCase(), args[1], partial);
+ case 2 -> completeSecondArg(sender, args[0].toLowerCase(), partial);
+ case 3 -> completeThirdArg(args[0].toLowerCase(), args[1], partial);
default -> new ArrayList<>();
};
}
private boolean isLoMinesCommand(String name) {
- return "lm".equalsIgnoreCase(name) ||
- "lomines".equalsIgnoreCase(name) ||
- "mine".equalsIgnoreCase(name) ||
- "mines".equalsIgnoreCase(name);
+ return name.equalsIgnoreCase("lm") ||
+ name.equalsIgnoreCase("lomines") ||
+ name.equalsIgnoreCase("mine") ||
+ name.equalsIgnoreCase("mines");
}
private List completeSubcommands(CommandSender sender, String partial) {
List completions = new ArrayList<>();
for (String cmd : ALL_COMMANDS) {
- if (cmd.startsWith(partial) && permission.hasPermission(sender, cmd)) {
+ if (cmd.startsWith(partial) && hasPermission(sender, cmd)) {
completions.add(cmd);
}
}
return completions;
}
-}
+ private List completeSecondArg(CommandSender sender, String subcommand, String partial) {
+ return switch (subcommand) {
+ case "create" -> completeCreate(partial);
+ case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn" ->
+ filterStartsWith(getMineNames(), partial);
+ case "reset" -> completeReset(partial);
+ case "stats" -> completeStats(sender, partial);
+ case "top" -> completeTopFirstArg(partial);
+ default -> new ArrayList<>();
+ };
+ }
+
+ private List completeThirdArg(String subcommand, String arg2, String partial) {
+ return switch (subcommand) {
+ case "reset" -> filterStartsWith(BOOLEAN_VALUES, partial);
+ case "top" -> completeTopSecondArg(arg2, partial);
+ default -> new ArrayList<>();
+ };
+ }
+
+ private List completeCreate(String partial) {
+ if (partial.isEmpty()) {
+ return new ArrayList<>(List.of(""));
+ }
+ return new ArrayList<>();
+ }
+
+ private List completeReset(String partial) {
+ List mines = filterStartsWith(getMineNames(), partial);
+ if (partial.isEmpty() || "true".startsWith(partial) || "false".startsWith(partial)) {
+ mines.addAll(filterStartsWith(BOOLEAN_VALUES, partial));
+ }
+ return mines;
+ }
+
+ private List completeStats(CommandSender sender, String partial) {
+ if (!sender.hasPermission("lomines.stats.others")) {
+ return new ArrayList<>();
+ }
+ return filterStartsWith(getOnlinePlayerNames(), partial);
+ }
+
+ private List completeTopFirstArg(String partial) {
+ List completions = new ArrayList<>();
+
+ completions.addAll(filterStartsWith(getMineNames(), partial));
+
+ if (isNumericPartial(partial)) {
+ completions.addAll(filterStartsWith(TOP_LIMITS, partial));
+ }
+
+ return completions;
+ }
+
+ private List completeTopSecondArg(String firstArg, String partial) {
+ boolean firstArgWasMine = plugin.getMines().find(firstArg).isPresent();
+
+ if (firstArgWasMine && isNumericPartial(partial)) {
+ return filterStartsWith(TOP_LIMITS, partial);
+ }
+
+ if (!firstArgWasMine && isNumericPartial(firstArg)) {
+ return filterStartsWith(getMineNames(), partial);
+ }
+
+ return new ArrayList<>();
+ }
+
+ private boolean isNumericPartial(String partial) {
+ return partial.isEmpty() || partial.matches("\\d*");
+ }
+
+ private List filterStartsWith(List options, String partial) {
+ return options.stream()
+ .filter(s -> s.toLowerCase().startsWith(partial))
+ .collect(Collectors.toList());
+ }
+
+ private List getMineNames() {
+ return plugin.getMines().getAll().stream()
+ .map(mine -> mine.getName())
+ .collect(Collectors.toList());
+ }
+
+ private List getOnlinePlayerNames() {
+ return plugin.getServer().getOnlinePlayers().stream()
+ .map(Player::getName)
+ .collect(Collectors.toList());
+ }
+
+ private boolean hasPermission(CommandSender sender, String cmd) {
+ return switch (cmd) {
+ case "create", "delete", "reload", "list", "maskscan", "edit" ->
+ sender.hasPermission("lomines.admin");
+ case "reset" -> sender.hasPermission("lomines.admin.reset");
+ case "setteleport" -> sender.hasPermission("lomines.admin.setteleport");
+ case "setspawn", "clearspawn" -> sender.hasPermission("lomines.admin.setspawn");
+ case "wand", "group" -> sender.hasPermission("lomines.admin.wand");
+ case "stats", "top" -> sender.hasPermission("lomines.stats");
+ case "help" -> sender.hasPermission("lomines.use");
+ default -> true;
+ };
+ }
+}
diff --git a/src/main/java/dev/loki/lomines/command/common/PermissionPredicate.java b/src/main/java/dev/loki/lomines/command/common/PermissionPredicate.java
deleted file mode 100644
index 14c2ced..0000000
--- a/src/main/java/dev/loki/lomines/command/common/PermissionPredicate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package dev.loki.lomines.command.common;
-
-import org.bukkit.command.CommandSender;
-
-public final class PermissionPredicate {
-
- public boolean hasPermission(CommandSender sender, String cmd) {
- return switch (cmd) {
- case "create", "delete", "reload", "list", "maskscan", "edit", "info" ->
- sender.hasPermission("lomines.admin");
- case "reset" -> sender.hasPermission("lomines.admin.reset");
- case "setteleport" -> sender.hasPermission("lomines.admin.setteleport");
- case "setspawn", "clearspawn" -> sender.hasPermission("lomines.admin.setspawn");
- case "wand", "group" -> sender.hasPermission("lomines.admin.wand");
- case "stats", "top" -> sender.hasPermission("lomines.stats");
- case "tp" -> sender.hasPermission("lomines.teleport");
- case "copy" -> sender.hasPermission("lomines.admin.copy");
- case "regions", "addregion", "removeregion" -> sender.hasPermission("lomines.admin.regions");
- case "help" -> sender.hasPermission("lomines.use");
- default -> true;
- };
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/common/SubcommandCompleter.java b/src/main/java/dev/loki/lomines/command/common/SubcommandCompleter.java
deleted file mode 100644
index 2bc1f26..0000000
--- a/src/main/java/dev/loki/lomines/command/common/SubcommandCompleter.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package dev.loki.lomines.command.common;
-
-import dev.loki.lomines.LoMinesPlugin;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public final class SubcommandCompleter {
-
- private static final List BOOLEAN_VALUES = List.of("true", "false");
- private static final List TOP_LIMITS = List.of("5", "10", "15", "20", "25", "50");
-
- private final LoMinesPlugin plugin;
-
- public SubcommandCompleter(LoMinesPlugin plugin) {
- this.plugin = plugin;
- }
-
- public List completeSecondArg(CommandSender sender, String subcommand, String partial) {
- return switch (subcommand) {
- case "create" -> completeCreate(partial);
- case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info",
- "regions", "addregion" -> filterStartsWith(getMineNames(), partial);
- case "reset" -> completeReset(partial);
- case "stats" -> completeStats(sender, partial);
- case "top" -> completeTopFirstArg(partial);
- case "tp" -> completeTeleport(sender, partial);
- case "copy" -> filterStartsWith(getMineNames(), partial);
- case "removeregion" -> filterStartsWith(getMineNames(), partial);
- default -> new ArrayList<>();
- };
- }
-
- public List completeThirdArg(String subcommand, String arg2, String partial) {
- return switch (subcommand) {
- case "reset" -> filterStartsWith(BOOLEAN_VALUES, partial);
- case "top" -> completeTopSecondArg(arg2, partial);
- case "copy" -> filterStartsWith(getMineNames(), partial);
- case "removeregion" -> completeRegionIndex(arg2, partial);
- default -> new ArrayList<>();
- };
- }
-
- private List completeRegionIndex(String mineName, String partial) {
- var mine = plugin.getMines().find(mineName);
- if (mine.isEmpty()) {
- return new ArrayList<>();
- }
- int regionCount = mine.get().getRegions().size();
- List indices = new ArrayList<>();
- for (int i = 1; i <= regionCount; i++) {
- indices.add(String.valueOf(i));
- }
- return filterStartsWith(indices, partial);
- }
-
- private List completeCreate(String partial) {
- if (partial.isEmpty()) {
- return new ArrayList<>(List.of(""));
- }
- return new ArrayList<>();
- }
-
- private List completeReset(String partial) {
- List mines = filterStartsWith(getMineNames(), partial);
- if (partial.isEmpty() || "true".startsWith(partial) || "false".startsWith(partial)) {
- mines.addAll(filterStartsWith(BOOLEAN_VALUES, partial));
- }
- return mines;
- }
-
- private List completeStats(CommandSender sender, String partial) {
- if (!sender.hasPermission("lomines.stats.others")) {
- return new ArrayList<>();
- }
- return filterStartsWith(getOnlinePlayerNames(), partial);
- }
-
- private List completeTopFirstArg(String partial) {
- List completions = new ArrayList<>();
- completions.addAll(filterStartsWith(getMineNames(), partial));
- if (isNumericPartial(partial)) {
- completions.addAll(filterStartsWith(TOP_LIMITS, partial));
- }
- return completions;
- }
-
- private List completeTopSecondArg(String firstArg, String partial) {
- boolean firstArgWasMine = plugin.getMines().find(firstArg).isPresent();
- if (firstArgWasMine && isNumericPartial(partial)) {
- return filterStartsWith(TOP_LIMITS, partial);
- }
- if (!firstArgWasMine && isNumericPartial(firstArg)) {
- return filterStartsWith(getMineNames(), partial);
- }
- return new ArrayList<>();
- }
-
- private List completeTeleport(CommandSender sender, String partial) {
- if (!sender.hasPermission("lomines.teleport")) {
- return new ArrayList<>();
- }
- return filterStartsWith(getMineNames(), partial);
- }
-
- private boolean isNumericPartial(String partial) {
- return partial.isEmpty() || partial.matches("\\d*");
- }
-
- public List filterStartsWith(List options, String partial) {
- return options.stream()
- .filter(s -> s.toLowerCase().startsWith(partial))
- .collect(Collectors.toList());
- }
-
- public List getMineNames() {
- return plugin.getMines().getAll().stream()
- .map(mine -> mine.getName())
- .collect(Collectors.toList());
- }
-
- private List getOnlinePlayerNames() {
- return plugin.getServer().getOnlinePlayers().stream()
- .map(Player::getName)
- .collect(Collectors.toList());
- }
-}
diff --git a/src/main/java/dev/loki/lomines/command/player/PlayerCommands.java b/src/main/java/dev/loki/lomines/command/player/PlayerCommands.java
index ea1bd63..ccf9178 100644
--- a/src/main/java/dev/loki/lomines/command/player/PlayerCommands.java
+++ b/src/main/java/dev/loki/lomines/command/player/PlayerCommands.java
@@ -3,11 +3,19 @@ package dev.loki.lomines.command.player;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.util.ErrorHandler;
import dev.loki.lomines.wand.group.GroupWandItem;
+import dev.lolib.commands.annotation.Arg;
+import dev.lolib.commands.annotation.Command;
+import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
+/**
+ * Player commands for mine interaction.
+ * Provides commands for wand and group management.
+ */
+@Command(value = "lm", permission = "lomines.use")
public class PlayerCommands {
private final LoMinesPlugin plugin;
@@ -18,32 +26,26 @@ public class PlayerCommands {
this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
}
- public void handle(CommandSender sender, String subcommand, String[] args) {
- switch (subcommand.toLowerCase()) {
- case "wand" -> {
- if (!(sender instanceof Player player)) {
- errorHandler.sendError(sender, "Только для игрока.");
- return;
- }
- player.getInventory().addItem(GroupWandItem.create(plugin));
- player.sendMessage(Component.text(
- "Палочка группы: ЛКМ/ПКМ по блоку — углы, Shift+ПКМ — меню (9 шахт). Задайте /lm group <префикс>",
- NamedTextColor.GREEN));
- }
- case "group" -> {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm group ", NamedTextColor.RED));
- return;
- }
- if (!(sender instanceof Player player)) {
- errorHandler.sendError(sender, "Префикс задаётся в игре.");
- return;
- }
- plugin.getGroupWandManager().getSession(player.getUniqueId()).setBaseName(args[0]);
- String p = args[0].trim();
- player.sendMessage(Component.text("Префикс: " + p + " → файлы " + p + "_1.yml … " + p + "_9.yml", NamedTextColor.AQUA));
- }
- default -> {}
+ @Subcommand(value = "wand", permission = "lomines.admin.wand")
+ public void wand(CommandSender sender) {
+ if (!(sender instanceof Player player)) {
+ errorHandler.sendError(sender, "Только для игрока.");
+ return;
}
+ player.getInventory().addItem(GroupWandItem.create(plugin));
+ player.sendMessage(Component.text(
+ "Палочка группы: ЛКМ/ПКМ по блоку — углы, Shift+ПКМ — меню (9 шахт). Задайте /lm group <префикс>",
+ NamedTextColor.GREEN));
+ }
+
+ @Subcommand(value = "group", permission = "lomines.admin.wand")
+ public void group(CommandSender sender, @Arg("prefix") String prefix) {
+ if (!(sender instanceof Player player)) {
+ errorHandler.sendError(sender, "Префикс задаётся в игре.");
+ return;
+ }
+ plugin.getGroupWandManager().getSession(player.getUniqueId()).setBaseName(prefix);
+ String p = prefix.trim();
+ player.sendMessage(Component.text("Префикс: " + p + " → файлы " + p + "_1.yml … " + p + "_9.yml", NamedTextColor.AQUA));
}
}
diff --git a/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java b/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java
deleted file mode 100644
index 75d0d8d..0000000
--- a/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package dev.loki.lomines.command.player;
-
-import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.util.location.geo.Cuboid;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.Location;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-public class TeleportCommand {
-
- private final LoMinesPlugin plugin;
-
- public TeleportCommand(LoMinesPlugin plugin) {
- this.plugin = plugin;
- }
-
- public void handle(CommandSender sender, String[] args) {
- if (args.length < 1) {
- sender.sendMessage(Component.text("Usage: /lm tp ", NamedTextColor.RED));
- return;
- }
- String mineName = args[0];
-
- if (!(sender instanceof Player player)) {
- sender.sendMessage(Component.text("Эта команда только для игроков!", NamedTextColor.RED));
- return;
- }
-
- Mine mine = plugin.getMines().find(mineName).orElse(null);
- if (mine == null) {
- player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
- return;
- }
-
- Location target = resolveTarget(player, mine);
- if (target == null) {
- return;
- }
-
- player.teleport(target);
- player.sendMessage(Component.text("§aТелепортировано к шахте §f" + mine.getName() +
- " §a(§f" + String.format("%.0f, %.0f, %.0f", target.getX(), target.getY(), target.getZ()) + "§a)"));
- }
-
- private Location resolveTarget(Player player, Mine mine) {
- var config = mine.getConfig();
- if (config.teleport().enabled() && config.teleport().getLocation().isPresent()) {
- return config.teleport().getLocation().get();
- }
- if (!mine.getRegions().isEmpty()) {
- Cuboid region = mine.getRegions().get(0);
- double centerX = (region.getMinX() + region.getMaxX()) / 2.0 + 0.5;
- double centerZ = (region.getMinZ() + region.getMaxZ()) / 2.0 + 0.5;
- double centerY = region.getMaxY() + 1;
- return new Location(region.getWorld(), centerX, centerY, centerZ);
- }
- player.sendMessage(Component.text("У шахты нет настроенной точки телепорта!", NamedTextColor.RED));
- return null;
- }
-}
diff --git a/src/main/java/dev/loki/lomines/core/common/package-info.java b/src/main/java/dev/loki/lomines/core/common/package-info.java
new file mode 100644
index 0000000..2228c11
--- /dev/null
+++ b/src/main/java/dev/loki/lomines/core/common/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Core components of the LoMines plugin.
+ *
+ * This package contains the main plugin class and core system classes:
+ *
+ * - {@code LoMinesPlugin} - Main plugin class extending LoPlugin
+ * - {@code Mines} - Registry for managing all active mines
+ * - {@code Mine} - Representation of a single mine with its configuration and state
+ *
+ */
+package dev.loki.lomines.core.common;
diff --git a/src/main/java/dev/loki/lomines/core/mine/model/Mine.java b/src/main/java/dev/loki/lomines/core/mine/Mine.java
similarity index 59%
rename from src/main/java/dev/loki/lomines/core/mine/model/Mine.java
rename to src/main/java/dev/loki/lomines/core/mine/Mine.java
index ad4aea0..800faaa 100644
--- a/src/main/java/dev/loki/lomines/core/mine/model/Mine.java
+++ b/src/main/java/dev/loki/lomines/core/mine/Mine.java
@@ -1,28 +1,31 @@
-package dev.loki.lomines.core.mine.model;
+package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin;
+import dev.loki.lomines.core.service.MineFileManager;
import dev.loki.lomines.block.BlockSetter;
-import dev.loki.lomines.data.config.model.MineConfig;
+import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.data.config.block.FillMode;
-import dev.loki.lomines.handler.ui.ActionBarHandler;
-import dev.loki.lomines.handler.block.MineBlockHandler;
-import dev.loki.lomines.handler.reset.MineResetHandler;
+import dev.loki.lomines.handler.ActionBarHandler;
+import dev.loki.lomines.handler.MineBlockHandler;
+import dev.loki.lomines.handler.MineResetHandler;
import dev.loki.lomines.util.location.BlockKeys;
-import dev.loki.lomines.util.location.geo.Cuboid;
+import dev.loki.lomines.util.location.Cuboid;
import dev.loki.lomines.util.location.LocationParser;
-import org.bukkit.Bukkit;
-import org.bukkit.scheduler.BukkitTask;
+import dev.lolib.scheduler.ScheduledTask;
+import dev.lolib.scheduler.Scheduler;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
+/**
+ * Represents a single mine with its configuration and state.
+ * Thread-safe implementation using AtomicInteger for mutable state.
+ * Updated for new section-based configuration (v2).
+ */
public final class Mine {
private final String name;
@@ -30,12 +33,14 @@ public final class Mine {
private final MineConfig config;
private final BlockSetter blockSetter;
private final LoMinesPlugin plugin;
- private final MineState state;
+ private final int totalVolume;
+ private final Set maskBlockKeys;
+ private final AtomicInteger blocks;
+ private final AtomicInteger ticks;
private final MineResetHandler resetHandler;
private final MineBlockHandler blockHandler;
private final ActionBarHandler actionBarHandler;
- private BukkitTask actionBarTask;
- private final Set maskBlockKeys;
+ private ScheduledTask actionBarTask;
public Mine(String name, MineConfig config, LoMinesPlugin plugin) {
this.name = name;
@@ -44,21 +49,30 @@ public final class Mine {
this.regions = config.region().regions();
this.blockSetter = createBlockSetter(config, plugin);
this.maskBlockKeys = initMaskBlockKeys(config);
- this.state = new MineState(config);
+ this.totalVolume = config.blocks().fillMode() == FillMode.MASK
+ ? maskBlockKeys.size()
+ : config.region().totalVolume();
+ this.blocks = new AtomicInteger(Math.max(0, totalVolume));
+ this.ticks = new AtomicInteger(0);
this.resetHandler = new MineResetHandler(this, plugin);
this.blockHandler = new MineBlockHandler(this, plugin);
this.actionBarHandler = new ActionBarHandler(this);
}
private Set initMaskBlockKeys(MineConfig config) {
- if (config.blocks().fillMode() != FillMode.MASK || config.blocks().mask() == null) {
+ if (config.blocks().fillMode() != FillMode.MASK) {
return Set.of();
}
- return config.blocks().mask().positions().keySet().stream()
- .map(LocationParser::parseSimple)
- .filter(Objects::nonNull)
- .map(BlockKeys::key)
- .collect(Collectors.toSet());
+ Set keys = new HashSet<>();
+ if (config.blocks().mask() != null) {
+ for (String pos : config.blocks().mask().positions().keySet()) {
+ Location loc = LocationParser.parseSimple(pos);
+ if (loc != null) {
+ keys.add(BlockKeys.key(loc));
+ }
+ }
+ }
+ return keys;
}
public void start() {
@@ -85,15 +99,18 @@ public final class Mine {
}
public boolean appliesToBlock(Location location) {
- return contains(location) && (config.blocks().fillMode() != FillMode.MASK || maskBlockKeys.contains(BlockKeys.key(location)));
+ if (!contains(location)) return false;
+ if (config.blocks().fillMode() != FillMode.MASK) return true;
+ return maskBlockKeys.contains(BlockKeys.key(location));
}
public int getTotalVolume() {
- return state.getTotalVolume();
+ return totalVolume;
}
public double getPercentFilled() {
- return state.getPercentFilled();
+ if (totalVolume == 0) return 0.0;
+ return (double) blocks.get() / totalVolume * 100.0;
}
private BlockSetter createBlockSetter(MineConfig config, LoMinesPlugin plugin) {
@@ -103,16 +120,17 @@ public final class Mine {
}
BlockKey firstKey = weights.keySet().iterator().next();
return switch (firstKey) {
- case BlockKey.Oraxen oraxen -> throw new IllegalArgumentException("External plugins disabled");
- case BlockKey.ItemsAdder itemsAdder -> throw new IllegalArgumentException("External plugins disabled");
+ case BlockKey.Oraxen oraxen -> throw new IllegalArgumentException("Oraxen integration is currently disabled");
+ case BlockKey.ItemsAdder itemsAdder -> throw new IllegalArgumentException("ItemsAdder integration is currently disabled");
case BlockKey.Vanilla vanilla -> new dev.loki.lomines.block.BukkitBlockSetter(weights, plugin);
};
}
private void startActionBarTask() {
- if (config.ui().actionBarEnabled()) {
- actionBarTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> actionBarHandler.sendToNearbyPlayers(), 10L, 10L);
- }
+ if (!config.ui().actionBarEnabled()) return;
+ actionBarTask = Scheduler.get(plugin).runTimer(() -> {
+ actionBarHandler.sendToNearbyPlayers();
+ }, 10L, 10L);
}
public String getName() {
@@ -131,19 +149,19 @@ public final class Mine {
return regions;
}
+ public AtomicInteger getBlocksAtomic() {
+ return blocks;
+ }
+
public int getBlocks() {
- return state.getBlocks();
+ return blocks.get();
}
public int getTicks() {
- return state.getTicks();
- }
-
- public AtomicInteger getBlocksAtomic() {
- return state.getBlocksAtomic();
+ return ticks.get();
}
public AtomicInteger getTicksAtomic() {
- return state.getTicksAtomic();
+ return ticks;
}
}
diff --git a/src/main/java/dev/loki/lomines/core/mine/service/MineLoader.java b/src/main/java/dev/loki/lomines/core/mine/MineLoader.java
similarity index 70%
rename from src/main/java/dev/loki/lomines/core/mine/service/MineLoader.java
rename to src/main/java/dev/loki/lomines/core/mine/MineLoader.java
index f69f6e1..2646503 100644
--- a/src/main/java/dev/loki/lomines/core/mine/service/MineLoader.java
+++ b/src/main/java/dev/loki/lomines/core/mine/MineLoader.java
@@ -1,10 +1,10 @@
-package dev.loki.lomines.core.mine.service;
+package dev.loki.lomines.core.common;
import dev.loki.lomines.LoMinesPlugin;
+import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.service.MineFileManager;
-import dev.loki.lomines.core.service.MineRepository;
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
+import dev.loki.lomines.data.config.MineConfig;
import java.io.IOException;
import java.nio.file.Files;
@@ -14,19 +14,19 @@ import java.util.stream.Stream;
/**
* Loads mine configurations from disk.
*/
-public final class MineLoader {
+final class MineLoader {
private final LoMinesPlugin plugin;
private final MineFileManager fileManager;
private final MineRepository repository;
- public MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
+ MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
this.plugin = plugin;
this.fileManager = fileManager;
this.repository = repository;
}
- public void loadAll() throws IOException {
+ void loadAll() throws IOException {
repository.clear();
Path minesFolder = fileManager.getMinesFolder();
@@ -49,9 +49,9 @@ public final class MineLoader {
MineConfig config = fileManager.loadConfig(mineName);
repository.createAndStart(mineName, config);
- plugin.getLogger().info("Loaded mine: " + mineName);
+ plugin.loLogger().info("Loaded mine: " + mineName);
} catch (IOException | ConfigLoader.ConfigLoadException e) {
- plugin.getLogger().severe("Failed to load mine from " + configFile.getFileName() + ": " + e.getMessage());
+ plugin.loLogger().error("Failed to load mine from " + configFile.getFileName() + ": " + e.getMessage());
}
}
}
diff --git a/src/main/java/dev/loki/lomines/core/mine/service/MineTicker.java b/src/main/java/dev/loki/lomines/core/mine/MineTicker.java
similarity index 76%
rename from src/main/java/dev/loki/lomines/core/mine/service/MineTicker.java
rename to src/main/java/dev/loki/lomines/core/mine/MineTicker.java
index bb10629..59dc3b5 100644
--- a/src/main/java/dev/loki/lomines/core/mine/service/MineTicker.java
+++ b/src/main/java/dev/loki/lomines/core/mine/MineTicker.java
@@ -1,10 +1,8 @@
-package dev.loki.lomines.core.mine.service;
+package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.registry.Mines;
-import org.bukkit.Bukkit;
-import org.bukkit.scheduler.BukkitTask;
+import dev.lolib.scheduler.ScheduledTask;
+import dev.lolib.scheduler.Scheduler;
/**
* Centralized ticker for all mines.
@@ -15,7 +13,7 @@ public final class MineTicker {
private final Mines mines;
private final LoMinesPlugin plugin;
- private BukkitTask tickerTask;
+ private ScheduledTask tickerTask;
public MineTicker(Mines mines, LoMinesPlugin plugin) {
this.mines = mines;
@@ -31,7 +29,7 @@ public final class MineTicker {
return; // Already running
}
- tickerTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
+ tickerTask = Scheduler.get(plugin).runTimer(() -> {
for (Mine mine : mines.getAll()) {
tickMine(mine);
}
@@ -54,7 +52,7 @@ public final class MineTicker {
private void tickMine(Mine mine) {
int currentTicks = mine.getTicksAtomic().incrementAndGet();
- if (currentTicks >= mine.getConfig().reset().intervalTicks()) {
+ if (currentTicks >= mine.getConfig().getResetTicks()) {
mine.getTicksAtomic().set(0);
mine.reset(false);
}
diff --git a/src/main/java/dev/loki/lomines/core/mine/registry/Mines.java b/src/main/java/dev/loki/lomines/core/mine/Mines.java
similarity index 63%
rename from src/main/java/dev/loki/lomines/core/mine/registry/Mines.java
rename to src/main/java/dev/loki/lomines/core/mine/Mines.java
index 5b55f1c..6c08932 100644
--- a/src/main/java/dev/loki/lomines/core/mine/registry/Mines.java
+++ b/src/main/java/dev/loki/lomines/core/mine/Mines.java
@@ -1,20 +1,23 @@
-package dev.loki.lomines.core.mine.registry;
+package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.mine.service.MineLoader;
-import dev.loki.lomines.core.service.mask.MaskScanService;
+import dev.loki.lomines.core.common.MineLoader;
+import dev.loki.lomines.core.service.MaskScanService;
import dev.loki.lomines.core.service.MineFileManager;
import dev.loki.lomines.core.service.MineRepository;
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
-import dev.loki.lomines.integration.worldguard.region.WorldGuardRegionService;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
+import dev.loki.lomines.data.config.MineConfig;
+import dev.loki.lomines.integration.worldguard.WorldGuardRegionService;
import org.bukkit.Location;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
+/**
+ * Registry for all active mines.
+ * Manages the lifecycle of mines including loading, creating, and deleting.
+ */
public final class Mines {
private final LoMinesPlugin plugin;
@@ -23,7 +26,6 @@ public final class Mines {
private final MineLoader loader;
private final MaskScanService maskScanService;
private final WorldGuardRegionService worldGuardService;
- private final MineFinder finder;
public Mines(LoMinesPlugin plugin) {
this.plugin = plugin;
@@ -32,7 +34,6 @@ public final class Mines {
this.loader = new MineLoader(plugin, fileManager, repository);
this.maskScanService = new MaskScanService(repository, fileManager);
this.worldGuardService = new WorldGuardRegionService(plugin);
- this.finder = new MineFinder(repository);
}
public void loadAll() throws IOException {
@@ -44,7 +45,6 @@ public final class Mines {
}
public void create(String name, Location corner1, Location corner2) throws IOException {
- name = name.toLowerCase().trim();
if (repository.exists(name)) {
throw new IllegalArgumentException("Mine already exists: " + name);
}
@@ -64,7 +64,7 @@ public final class Mines {
worldGuardService.createRegion(name, config);
repository.createAndStart(name, config);
- plugin.getLogger().info("Created mine: " + name);
+ plugin.loLogger().info("Created mine: " + name);
}
public void delete(String name) throws IOException {
@@ -79,65 +79,39 @@ public final class Mines {
}
public Mine get(String name) {
- return finder.get(name);
+ return repository.get(name);
}
public Optional find(String name) {
- return finder.find(name);
+ return repository.find(name);
}
public Collection getAll() {
- return finder.getAll();
+ return repository.getAll();
}
public Optional findByLocation(Location location) {
- return finder.findByLocation(location);
+ return repository.findByLocation(location);
}
public int scanAndSaveMask(String mineName) throws IOException, ConfigLoader.ConfigLoadException {
int count = maskScanService.scanAndSave(mineName);
- plugin.getLogger().info("Mask scan for '" + mineName + "': " + count + " marker block(s)");
+ plugin.loLogger().info("Mask scan for '" + mineName + "': " + count + " marker block(s)");
return count;
}
public void reloadMine(String name) throws IOException, ConfigLoader.ConfigLoadException {
- Mine oldMine = finder.find(name).orElse(null);
+ Mine oldMine = repository.find(name).orElse(null);
repository.reload(name);
if (oldMine != null) {
- Mine newMine = finder.find(name).orElse(null);
+ Mine newMine = repository.find(name).orElse(null);
if (newMine != null) {
worldGuardService.updateRegion(name, newMine.getConfig());
}
}
}
- public void updateMineConfig(String name, MineConfig newConfig) throws IOException {
- if (!repository.exists(name)) {
- throw new IllegalArgumentException("Mine not found: " + name);
- }
-
- try {
- fileManager.saveConfig(name, newConfig);
- } catch (ConfigLoader.ConfigLoadException e) {
- throw new IOException("Failed to save mine config: " + e.getMessage(), e);
- }
-
- try {
- Mine oldMine = finder.find(name).orElse(null);
- repository.reload(name);
-
- if (oldMine != null) {
- Mine newMine = finder.find(name).orElse(null);
- if (newMine != null) {
- worldGuardService.updateRegion(name, newMine.getConfig());
- }
- }
- } catch (ConfigLoader.ConfigLoadException e) {
- throw new IOException("Failed to reload mine after config update: " + e.getMessage(), e);
- }
- }
-
public WorldGuardRegionService getWorldGuardService() {
return worldGuardService;
}
diff --git a/src/main/java/dev/loki/lomines/core/mine/model/MineState.java b/src/main/java/dev/loki/lomines/core/mine/model/MineState.java
deleted file mode 100644
index 99b5e3f..0000000
--- a/src/main/java/dev/loki/lomines/core/mine/model/MineState.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package dev.loki.lomines.core.mine.model;
-
-import dev.loki.lomines.data.config.model.MineConfig;
-import dev.loki.lomines.data.config.block.FillMode;
-import dev.loki.lomines.util.location.BlockKeys;
-import dev.loki.lomines.util.location.LocationParser;
-import org.bukkit.Location;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public final class MineState {
-
- private final AtomicInteger blocks;
- private final AtomicInteger ticks;
- private final int totalVolume;
- private final Set maskBlockKeys;
-
- public MineState(MineConfig config) {
- this.maskBlockKeys = initMaskBlockKeys(config);
- this.totalVolume = config.blocks().fillMode() == FillMode.MASK
- ? maskBlockKeys.size()
- : config.region().totalVolume();
- this.blocks = new AtomicInteger(Math.max(0, totalVolume));
- this.ticks = new AtomicInteger(0);
- }
-
- private static Set initMaskBlockKeys(MineConfig config) {
- if (config.blocks().fillMode() != FillMode.MASK) {
- return Set.of();
- }
- Set keys = new HashSet<>();
- if (config.blocks().mask() != null) {
- for (String pos : config.blocks().mask().positions().keySet()) {
- Location loc = LocationParser.parseSimple(pos);
- if (loc != null) {
- keys.add(BlockKeys.key(loc));
- }
- }
- }
- return keys;
- }
-
- public int getTotalVolume() {
- return totalVolume;
- }
-
- public double getPercentFilled() {
- if (totalVolume == 0) {
- return 0.0;
- }
- return (double) blocks.get() / totalVolume * 100.0;
- }
-
- public AtomicInteger getBlocksAtomic() {
- return blocks;
- }
-
- public int getBlocks() {
- return blocks.get();
- }
-
- public int getTicks() {
- return ticks.get();
- }
-
- public AtomicInteger getTicksAtomic() {
- return ticks;
- }
-
- public Set getMaskBlockKeys() {
- return maskBlockKeys;
- }
-
- public boolean appliesToBlock(Location location) {
- if (maskBlockKeys.isEmpty()) {
- return true;
- }
- return maskBlockKeys.contains(BlockKeys.key(location));
- }
-}
diff --git a/src/main/java/dev/loki/lomines/core/mine/registry/MineFinder.java b/src/main/java/dev/loki/lomines/core/mine/registry/MineFinder.java
deleted file mode 100644
index ffef0ef..0000000
--- a/src/main/java/dev/loki/lomines/core/mine/registry/MineFinder.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package dev.loki.lomines.core.mine.registry;
-
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.core.service.MineRepository;
-import org.bukkit.Location;
-
-import java.util.Collection;
-import java.util.Optional;
-
-public final class MineFinder {
-
- private final MineRepository repository;
-
- public MineFinder(MineRepository repository) {
- this.repository = repository;
- }
-
- public Mine get(String name) {
- return repository.get(name);
- }
-
- public Optional find(String name) {
- return repository.find(name);
- }
-
- public Collection getAll() {
- return repository.getAll();
- }
-
- public Optional findByLocation(Location location) {
- return repository.findByLocation(location);
- }
-}
diff --git a/src/main/java/dev/loki/lomines/core/service/mask/MaskScanService.java b/src/main/java/dev/loki/lomines/core/service/MaskScanService.java
similarity index 83%
rename from src/main/java/dev/loki/lomines/core/service/mask/MaskScanService.java
rename to src/main/java/dev/loki/lomines/core/service/MaskScanService.java
index bbce989..9c4e7a5 100644
--- a/src/main/java/dev/loki/lomines/core/service/mask/MaskScanService.java
+++ b/src/main/java/dev/loki/lomines/core/service/MaskScanService.java
@@ -1,12 +1,10 @@
-package dev.loki.lomines.core.service.mask;
+package dev.loki.lomines.core.service;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.core.service.MineFileManager;
-import dev.loki.lomines.core.service.MineRepository;
+import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.util.ValidationUtils;
-import dev.loki.lomines.util.location.geo.Cuboid;
+import dev.loki.lomines.util.location.Cuboid;
import dev.loki.lomines.util.selection.MaskScanner;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -59,8 +57,8 @@ public final class MaskScanService {
}
private Material parseMarkerMaterial(BlockKey key) {
- if (key instanceof BlockKey.Vanilla(Material material)) {
- return material;
+ if (key instanceof BlockKey.Vanilla vanilla) {
+ return vanilla.material();
}
// For custom blocks (Oraxen/ItemsAdder), we need to get the underlying material
// This would need integration with those plugins to get the actual material
diff --git a/src/main/java/dev/loki/lomines/core/service/MineFileManager.java b/src/main/java/dev/loki/lomines/core/service/MineFileManager.java
index bfe1fbc..3b77c63 100644
--- a/src/main/java/dev/loki/lomines/core/service/MineFileManager.java
+++ b/src/main/java/dev/loki/lomines/core/service/MineFileManager.java
@@ -1,25 +1,38 @@
package dev.loki.lomines.core.service;
-import dev.loki.lomines.core.service.write.MineConfigWriter;
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
+import dev.loki.lomines.data.config.MineConfig;
+import dev.loki.lomines.data.config.block.BlockConfig;
import dev.loki.lomines.data.config.block.BlockKey;
+import dev.loki.lomines.data.config.block.FillMode;
+import dev.loki.lomines.data.config.region.RegionConfig;
+import dev.loki.lomines.data.config.reset.ResetConfig;
+import dev.loki.lomines.data.config.reward.RewardConfig;
+import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
+import dev.loki.lomines.data.config.teleport.TeleportConfig;
+import dev.loki.lomines.data.config.ui.UIConfig;
+import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
+import dev.loki.lomines.util.location.Cuboid;
+import dev.loki.lomines.util.location.LocationParser;
import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
-public final class MineFileManager {
-
- private final Path minesFolder;
- private final ConfigLoader configLoader;
- private final MineConfigWriter configWriter;
+/**
+ * Handles file operations for mine configurations.
+ * Updated for new section-based configuration (v2).
+ */
+public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
public MineFileManager(Path minesFolder) {
- this.minesFolder = minesFolder;
- this.configLoader = new ConfigLoader(minesFolder.getParent());
- this.configWriter = new MineConfigWriter(minesFolder, configLoader);
+ this(minesFolder, new ConfigLoader(minesFolder.getParent()));
}
public Path getMinesFolder() {
@@ -32,9 +45,82 @@ public final class MineFileManager {
}
}
+ /**
+ * Creates a default mine configuration with new format.
+ * WorldGuard region settings are loaded from defaults.yml.
+ */
public void createDefaultConfig(String name, Location corner1, Location corner2) throws IOException {
ensureFolderExists();
- configWriter.createDefaultConfig(name, corner1, corner2);
+
+ // Create default region
+ Location loc1 = corner1 != null ? corner1 : new Location(
+ org.bukkit.Bukkit.getWorlds().get(0), 0, 64, 0);
+ Location loc2 = corner2 != null ? corner2 : new Location(
+ org.bukkit.Bukkit.getWorlds().get(0), 10, 74, 10);
+
+ Cuboid cuboid = new Cuboid(loc1, loc2);
+ RegionConfig region = RegionConfig.single(cuboid);
+
+ // Create default blocks (stone only)
+ Map weights = new HashMap<>();
+ weights.put(new BlockKey.Vanilla(Material.STONE), 1.0);
+ BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
+
+ // Load WorldGuard defaults from _defaults.yml
+ WorldGuardConfig wgConfig = loadWorldGuardDefaults();
+
+ // Build mine config with defaults
+ MineConfig config = MineConfig.builder(name)
+ .region(region)
+ .blocks(blocks)
+ .reset(ResetConfig.defaults())
+ .rewards(RewardConfig.empty())
+ .teleport(TeleportConfig.disabled())
+ .ui(UIConfig.defaults())
+ .worldGuard(wgConfig)
+ .playerSpawn(PlayerSpawnConfig.disabled())
+ .build();
+
+ // Save using new loader
+ configLoader.save(config);
+ }
+
+ /**
+ * Loads WorldGuard configuration from defaults.yml.
+ */
+ private WorldGuardConfig loadWorldGuardDefaults() {
+ Path defaultsPath = minesFolder.resolve("_defaults.yml");
+ if (!Files.exists(defaultsPath)) {
+ return WorldGuardConfig.disabled();
+ }
+
+ try {
+ YamlConfiguration yaml = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
+ ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard");
+
+ if (wgSection == null) {
+ return WorldGuardConfig.disabled();
+ }
+
+ boolean enabled = wgSection.getBoolean("enabled", false);
+ if (!enabled) {
+ return WorldGuardConfig.disabled();
+ }
+
+ String template = wgSection.getString("region-template", "{mine_name}_{random_4}");
+
+ return WorldGuardConfig.builder()
+ .enabled(true)
+ .template(template)
+ .owners(wgSection.getStringList("owners"))
+ .members(wgSection.getStringList("members"))
+ .flags(wgSection.getStringList("flags"))
+ .protectOnCreate(wgSection.getBoolean("protect-on-create", true))
+ .build();
+
+ } catch (Exception e) {
+ return WorldGuardConfig.disabled();
+ }
}
public MineConfig loadConfig(String name) throws IOException, ConfigLoader.ConfigLoadException {
@@ -48,16 +134,48 @@ public final class MineFileManager {
}
}
+ /**
+ * Saves a mine configuration to file.
+ *
+ * @param config the configuration to save
+ * @throws ConfigLoader.ConfigLoadException if saving fails
+ */
public void saveConfig(MineConfig config) throws ConfigLoader.ConfigLoadException {
configLoader.save(config);
}
- public void saveConfig(String name, MineConfig config) throws ConfigLoader.ConfigLoadException {
- configLoader.save(config);
- }
+ /**
+ * Saves mask positions for mask fill mode.
+ */
+ public void saveMaskPositions(String name, BlockKey markerMaterial, java.util.List positions) throws IOException, ConfigLoader.ConfigLoadException {
+ MineConfig config = loadConfig(name);
- public void saveMaskPositions(String name, BlockKey markerMaterial,
- java.util.List positions) throws IOException, ConfigLoader.ConfigLoadException {
- configWriter.saveMaskPositions(name, markerMaterial, positions);
+ // Build new mask config
+ Map posMap = new HashMap<>();
+ for (Location loc : positions) {
+ posMap.put(LocationParser.format(loc), true);
+ }
+ BlockConfig.MaskConfig mask = new BlockConfig.MaskConfig(markerMaterial, posMap);
+
+ // Create new block config with mask
+ BlockConfig newBlocks = new BlockConfig(
+ config.blocks().weights(),
+ FillMode.MASK,
+ mask
+ );
+
+ // Build updated config
+ MineConfig updated = MineConfig.builder(name)
+ .region(config.region())
+ .blocks(newBlocks)
+ .reset(config.reset())
+ .rewards(config.rewards())
+ .teleport(config.teleport())
+ .ui(config.ui())
+ .worldGuard(config.worldGuard())
+ .playerSpawn(config.playerSpawn())
+ .build();
+
+ configLoader.save(updated);
}
}
diff --git a/src/main/java/dev/loki/lomines/core/service/MineRepository.java b/src/main/java/dev/loki/lomines/core/service/MineRepository.java
index 862377f..28c8224 100644
--- a/src/main/java/dev/loki/lomines/core/service/MineRepository.java
+++ b/src/main/java/dev/loki/lomines/core/service/MineRepository.java
@@ -1,17 +1,13 @@
package dev.loki.lomines.core.service;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.model.Mine;
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
+import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.data.config.loader.common.ConfigLoader;
+import dev.loki.lomines.data.config.MineConfig;
import org.bukkit.Location;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -88,6 +84,6 @@ public final class MineRepository {
stopAndRemove(name);
MineConfig config = fileManager.loadConfig(name);
Mine mine = createAndStart(name, config);
- plugin.getLogger().info("Reloaded mine: " + name);
+ plugin.loLogger().info("Reloaded mine: " + name);
}
}
diff --git a/src/main/java/dev/loki/lomines/core/service/write/MineConfigWriter.java b/src/main/java/dev/loki/lomines/core/service/write/MineConfigWriter.java
deleted file mode 100644
index a683563..0000000
--- a/src/main/java/dev/loki/lomines/core/service/write/MineConfigWriter.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package dev.loki.lomines.core.service.write;
-
-import dev.loki.lomines.data.config.ConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
-import dev.loki.lomines.data.config.block.BlockConfig;
-import dev.loki.lomines.data.config.block.BlockKey;
-import dev.loki.lomines.data.config.block.FillMode;
-import dev.loki.lomines.data.config.region.RegionConfig;
-import dev.loki.lomines.data.config.reset.ResetConfig;
-import dev.loki.lomines.data.config.reward.RewardConfig;
-import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
-import dev.loki.lomines.data.config.teleport.TeleportConfig;
-import dev.loki.lomines.data.config.ui.UIConfig;
-import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
-import dev.loki.lomines.util.location.geo.Cuboid;
-import dev.loki.lomines.util.location.LocationParser;
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-public final class MineConfigWriter {
-
- private final Path minesFolder;
- private final ConfigLoader configLoader;
-
- public MineConfigWriter(Path minesFolder, ConfigLoader configLoader) {
- this.minesFolder = minesFolder;
- this.configLoader = configLoader;
- }
-
- public void createDefaultConfig(String name, Location corner1, Location corner2) throws IOException {
- Location loc1 = corner1 != null ? corner1 : new Location(
- org.bukkit.Bukkit.getWorlds().get(0), 0, 64, 0);
- Location loc2 = corner2 != null ? corner2 : new Location(
- org.bukkit.Bukkit.getWorlds().get(0), 10, 74, 10);
-
- Cuboid cuboid = new Cuboid(loc1, loc2);
- RegionConfig region = RegionConfig.single(cuboid);
-
- Map weights = new HashMap<>();
- weights.put(new BlockKey.Vanilla(Material.STONE), 1.0);
- BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
-
- WorldGuardConfig wgConfig = loadWorldGuardDefaults();
-
- MineConfig config = MineConfig.builder(name)
- .region(region)
- .blocks(blocks)
- .reset(ResetConfig.defaults())
- .rewards(RewardConfig.empty())
- .teleport(TeleportConfig.disabled())
- .ui(UIConfig.defaults())
- .worldGuard(wgConfig)
- .playerSpawn(PlayerSpawnConfig.disabled())
- .build();
-
- try {
- configLoader.save(config);
- } catch (ConfigLoader.ConfigLoadException e) {
- throw new IOException("Failed to save default mine config: " + e.getMessage(), e);
- }
- }
-
- public void saveMaskPositions(String name, BlockKey markerMaterial,
- java.util.List positions) throws IOException, ConfigLoader.ConfigLoadException {
- MineConfig config = configLoader.load(name);
-
- Map posMap = new HashMap<>();
- for (Location loc : positions) {
- posMap.put(LocationParser.format(loc), true);
- }
- BlockConfig.MaskConfig mask = new BlockConfig.MaskConfig(markerMaterial, posMap);
-
- BlockConfig newBlocks = new BlockConfig(
- config.blocks().weights(),
- FillMode.MASK,
- mask
- );
-
- MineConfig updated = MineConfig.builder(name)
- .region(config.region())
- .blocks(newBlocks)
- .reset(config.reset())
- .rewards(config.rewards())
- .teleport(config.teleport())
- .ui(config.ui())
- .worldGuard(config.worldGuard())
- .playerSpawn(config.playerSpawn())
- .build();
-
- configLoader.save(updated);
- }
-
- private WorldGuardConfig loadWorldGuardDefaults() {
- Path defaultsPath = minesFolder.resolve("_defaults.yml");
- if (!Files.exists(defaultsPath)) {
- return WorldGuardConfig.disabled();
- }
- try {
- YamlConfiguration yaml = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
- ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard");
- if (wgSection == null) {
- return WorldGuardConfig.disabled();
- }
- boolean enabled = wgSection.getBoolean("enabled", false);
- if (!enabled) {
- return WorldGuardConfig.disabled();
- }
- String template = wgSection.getString("region-template", "{mine_name}_{random_4}");
- return WorldGuardConfig.builder()
- .enabled(true)
- .template(template)
- .owners(wgSection.getStringList("owners"))
- .members(wgSection.getStringList("members"))
- .flags(wgSection.getStringList("flags"))
- .protectOnCreate(wgSection.getBoolean("protect-on-create", true))
- .build();
- } catch (Exception e) {
- return WorldGuardConfig.disabled();
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
index fb41bae..c0e32ff 100644
--- a/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
@@ -5,21 +5,27 @@ import dev.loki.lomines.data.config.loader.entity.PlayerSpawnConfigLoader;
import dev.loki.lomines.data.config.loader.entity.TeleportConfigLoader;
import dev.loki.lomines.data.config.loader.region.RegionConfigLoader;
import dev.loki.lomines.data.config.loader.region.WorldGuardConfigLoader;
+import dev.loki.lomines.data.config.loader.reset.ResetConfigLoader;
import dev.loki.lomines.data.config.loader.reward.RewardConfigLoader;
-import dev.loki.lomines.data.config.loader.system.ResetConfigLoader;
import dev.loki.lomines.data.config.loader.system.UIConfigLoader;
-import dev.loki.lomines.data.config.model.MineConfig;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.List;
+/**
+ * Clean config loader for section-based configuration.
+ * Supports YAML inheritance from defaults.
+ * Delegates to specialized loaders for each section.
+ */
public final class ConfigLoader {
private final Path dataFolder;
- private final DefaultsMerger defaultsMerger;
+ private YamlConfiguration defaults;
+ // Section loaders
private RegionConfigLoader regionLoader;
private BlockConfigLoader blockLoader;
private ResetConfigLoader resetLoader;
@@ -31,29 +37,69 @@ public final class ConfigLoader {
public ConfigLoader(Path dataFolder) {
this.dataFolder = dataFolder;
- this.defaultsMerger = new DefaultsMerger(dataFolder);
+ loadDefaults();
initLoaders();
}
+ private void loadDefaults() {
+ Path defaultsPath = dataFolder.resolve("mines").resolve("_defaults.yml");
+ defaults = new YamlConfiguration();
+
+ if (Files.exists(defaultsPath)) {
+ defaults = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
+ } else {
+ setDefaults(defaults);
+ try {
+ Files.createDirectories(defaultsPath.getParent());
+ defaults.save(defaultsPath.toFile());
+ } catch (IOException e) {
+ // Ignore, use in-memory defaults
+ }
+ }
+ }
+
+ private void setDefaults(YamlConfiguration yaml) {
+ yaml.set("reset.interval", "5m");
+ yaml.set("reset.percent-trigger", 10.0);
+ yaml.set("reset.percent-enabled", false);
+ yaml.set("reset.commands", List.of());
+ yaml.set("reset.broadcast", "");
+
+ yaml.set("ui.actionbar.enabled", true);
+ yaml.set("ui.actionbar.format", "{mine} {percent}% ({time})");
+ yaml.set("ui.actionbar.range", 50.0);
+ yaml.set("ui.timer-format", "mm:ss");
+
+ yaml.set("teleport.enabled", false);
+ yaml.set("rewards", List.of());
+
+ worldGuardLoader.setDefaults(yaml);
+ playerSpawnLoader.setDefaults(yaml);
+ }
+
private void initLoaders() {
regionLoader = new RegionConfigLoader();
blockLoader = new BlockConfigLoader();
- resetLoader = new ResetConfigLoader(defaultsMerger.getDefaults());
+ resetLoader = new ResetConfigLoader(defaults);
rewardLoader = new RewardConfigLoader();
teleportLoader = new TeleportConfigLoader();
- uiLoader = new UIConfigLoader(defaultsMerger.getDefaults());
- worldGuardLoader = new WorldGuardConfigLoader(defaultsMerger.getDefaults());
+ uiLoader = new UIConfigLoader(defaults);
+ worldGuardLoader = new WorldGuardConfigLoader(defaults);
playerSpawnLoader = new PlayerSpawnConfigLoader();
}
+ /**
+ * Load a mine configuration from file.
+ */
public MineConfig load(String mineName) throws ConfigLoadException {
- if (!Files.exists(dataFolder.resolve("mines").resolve(mineName + ".yml"))) {
+ Path configPath = dataFolder.resolve("mines").resolve(mineName + ".yml");
+
+ if (!Files.exists(configPath)) {
throw new ConfigLoadException("Mine not found: " + mineName);
}
- Path configPath = dataFolder.resolve("mines").resolve(mineName + ".yml");
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configPath.toFile());
- defaultsMerger.mergeDefaults(yaml);
+ mergeDefaults(yaml);
try {
return MineConfig.builder(mineName)
@@ -71,7 +117,11 @@ public final class ConfigLoader {
}
}
+ /**
+ * Save a mine configuration to file.
+ */
public void save(MineConfig config) throws ConfigLoadException {
+ Path configPath = dataFolder.resolve("mines").resolve(config.name() + ".yml");
YamlConfiguration yaml = new YamlConfiguration();
regionLoader.save(yaml, config.region());
@@ -84,7 +134,6 @@ public final class ConfigLoader {
playerSpawnLoader.save(yaml, config.playerSpawn());
try {
- Path configPath = dataFolder.resolve("mines").resolve(config.name() + ".yml");
Files.createDirectories(configPath.getParent());
yaml.save(configPath.toFile());
} catch (IOException e) {
@@ -92,11 +141,21 @@ public final class ConfigLoader {
}
}
+ private void mergeDefaults(YamlConfiguration yaml) {
+ for (String key : defaults.getKeys(true)) {
+ if (!yaml.contains(key)) {
+ yaml.set(key, defaults.get(key));
+ }
+ }
+ }
+
+ /**
+ * Exception for config loading errors.
+ */
public static class ConfigLoadException extends Exception {
public ConfigLoadException(String message) {
super(message);
}
-
public ConfigLoadException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/dev/loki/lomines/data/config/DefaultsMerger.java b/src/main/java/dev/loki/lomines/data/config/DefaultsMerger.java
deleted file mode 100644
index a454c19..0000000
--- a/src/main/java/dev/loki/lomines/data/config/DefaultsMerger.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package dev.loki.lomines.data.config;
-
-import dev.loki.lomines.data.config.loader.region.WorldGuardConfigLoader;
-import dev.loki.lomines.data.config.loader.entity.PlayerSpawnConfigLoader;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-public final class DefaultsMerger {
-
- private final Path dataFolder;
- private YamlConfiguration defaults;
-
- public DefaultsMerger(Path dataFolder) {
- this.dataFolder = dataFolder;
- loadDefaults();
- }
-
- public YamlConfiguration getDefaults() {
- return defaults;
- }
-
- private void loadDefaults() {
- Path defaultsPath = dataFolder.resolve("mines").resolve("_defaults.yml");
- defaults = new YamlConfiguration();
-
- if (Files.exists(defaultsPath)) {
- defaults = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
- } else {
- setDefaults(defaults);
- try {
- Files.createDirectories(defaultsPath.getParent());
- defaults.save(defaultsPath.toFile());
- } catch (IOException e) {
- // Ignore, use in-memory defaults
- }
- }
- }
-
- private void setDefaults(YamlConfiguration yaml) {
- yaml.set("reset.interval", "5m");
- yaml.set("reset.percent-trigger", 10.0);
- yaml.set("reset.percent-enabled", false);
- yaml.set("reset.commands", List.of());
- yaml.set("reset.broadcast", "");
-
- yaml.set("ui.actionbar.enabled", true);
- yaml.set("ui.actionbar.format", "{mine} {percent}% ({time})");
- yaml.set("ui.actionbar.range", 50.0);
- yaml.set("ui.timer-format", "mm:ss");
-
- yaml.set("teleport.enabled", false);
- yaml.set("rewards", List.of());
-
- WorldGuardConfigLoader wgLoader = new WorldGuardConfigLoader(new YamlConfiguration());
- wgLoader.setDefaults(yaml);
- PlayerSpawnConfigLoader psLoader = new PlayerSpawnConfigLoader();
- psLoader.setDefaults(yaml);
- }
-
- public void mergeDefaults(YamlConfiguration yaml) {
- for (String key : defaults.getKeys(true)) {
- if (!yaml.contains(key)) {
- yaml.set(key, defaults.get(key));
- }
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/data/config/model/MineConfigDefaults.java b/src/main/java/dev/loki/lomines/data/config/MineConfig.java
similarity index 52%
rename from src/main/java/dev/loki/lomines/data/config/model/MineConfigDefaults.java
rename to src/main/java/dev/loki/lomines/data/config/MineConfig.java
index 593c5fa..2a1c8bb 100644
--- a/src/main/java/dev/loki/lomines/data/config/model/MineConfigDefaults.java
+++ b/src/main/java/dev/loki/lomines/data/config/MineConfig.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.data.config.model;
+package dev.loki.lomines.data.config;
import dev.loki.lomines.data.config.block.BlockConfig;
import dev.loki.lomines.data.config.region.RegionConfig;
@@ -7,13 +7,72 @@ import dev.loki.lomines.data.config.reward.RewardConfig;
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
import dev.loki.lomines.data.config.teleport.TeleportConfig;
import dev.loki.lomines.data.config.ui.UIConfig;
-import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
+import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
-public final class MineConfigDefaults {
+import org.bukkit.Location;
- private MineConfigDefaults() {
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Clean, type-safe, section-based mine configuration.
+ * Proper separation of concerns: each section handles one aspect.
+ */
+public record MineConfig(
+ String name,
+ RegionConfig region,
+ BlockConfig blocks,
+ ResetConfig reset,
+ RewardConfig rewards,
+ TeleportConfig teleport,
+ UIConfig ui,
+ WorldGuardConfig worldGuard,
+ PlayerSpawnConfig playerSpawn
+) {
+
+ public MineConfig {
+ Objects.requireNonNull(name, "Mine name cannot be null");
+ if (name.isBlank()) {
+ throw new IllegalArgumentException("Mine name cannot be blank");
+ }
+ name = name.toLowerCase().trim();
+
+ Objects.requireNonNull(region, "Region config cannot be null");
+ Objects.requireNonNull(blocks, "Block config cannot be null");
+ Objects.requireNonNull(reset, "Reset config cannot be null");
+ Objects.requireNonNull(rewards, "Reward config cannot be null");
+ Objects.requireNonNull(teleport, "Teleport config cannot be null");
+ Objects.requireNonNull(ui, "UI config cannot be null");
+ // worldGuard and playerSpawn can be null (disabled by default)
+ if (playerSpawn == null) {
+ playerSpawn = PlayerSpawnConfig.disabled();
+ }
}
+ /**
+ * Total volume of the mine (for progress calculation).
+ */
+ public int volume() {
+ return region.totalVolume();
+ }
+
+ /**
+ * World name where the mine is located.
+ */
+ public String worldName() {
+ return region.worldName();
+ }
+
+ /**
+ * Creates builder for fluent construction.
+ */
+ public static Builder builder(String name) {
+ return new Builder(name);
+ }
+
+ /**
+ * Creates config with sensible defaults.
+ */
public static MineConfig defaults(String name, RegionConfig region, BlockConfig blocks) {
return new MineConfig(
name,
@@ -28,6 +87,18 @@ public final class MineConfigDefaults {
);
}
+ /**
+ * Returns the spawn location for stuck players.
+ * If playerSpawn is not set, falls back to teleport location.
+ */
+ public Optional getSpawnForStuckPlayer() {
+ return playerSpawn.enabled()
+ ? playerSpawn.getLocation()
+ : teleport.getLocation();
+ }
+
+ // --- Builder ---
+
public static class Builder {
private final String name;
private RegionConfig region;
@@ -39,7 +110,7 @@ public final class MineConfigDefaults {
private WorldGuardConfig worldGuard = WorldGuardConfig.disabled();
private PlayerSpawnConfig playerSpawn = PlayerSpawnConfig.disabled();
- public Builder(String name) {
+ private Builder(String name) {
this.name = name;
}
diff --git a/src/main/java/dev/loki/lomines/data/config/block/BlockConfig.java b/src/main/java/dev/loki/lomines/data/config/block/BlockConfig.java
index 2fa8a06..fb3cbe6 100644
--- a/src/main/java/dev/loki/lomines/data/config/block/BlockConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/block/BlockConfig.java
@@ -1,7 +1,5 @@
package dev.loki.lomines.data.config.block;
-import org.bukkit.Material;
-
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
diff --git a/src/main/java/dev/loki/lomines/data/config/block/BlockKey.java b/src/main/java/dev/loki/lomines/data/config/block/BlockKey.java
index 31d552b..eecce4e 100644
--- a/src/main/java/dev/loki/lomines/data/config/block/BlockKey.java
+++ b/src/main/java/dev/loki/lomines/data/config/block/BlockKey.java
@@ -10,6 +10,12 @@ import java.util.Objects;
*/
public sealed interface BlockKey permits BlockKey.Vanilla, BlockKey.Oraxen, BlockKey.ItemsAdder {
+ /**
+ * Serializes this key to string format for YAML storage.
+ * Format: "stone" (vanilla), "oraxen:my_block", "itemsadder:custom_block"
+ */
+ String serialize();
+
/**
* Deserializes a string to appropriate BlockKey type.
*
@@ -37,12 +43,6 @@ public sealed interface BlockKey permits BlockKey.Vanilla, BlockKey.Oraxen, Bloc
return new Vanilla(material);
}
- /**
- * Serializes this key to string format for YAML storage.
- * Format: "stone" (vanilla), "oraxen:my_block", "itemsadder:custom_block"
- */
- String serialize();
-
// --- Implementations ---
record Vanilla(Material material) implements BlockKey {
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/block/BlockConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/block/BlockConfigLoader.java
index 61b8e24..352d471 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/block/BlockConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/block/BlockConfigLoader.java
@@ -32,9 +32,7 @@ public final class BlockConfigLoader {
Map weights = new HashMap<>();
for (String key : contents.getKeys(false)) {
double weight = contents.getDouble(key);
- if (weight <= 0) {
- continue;
- }
+ if (weight <= 0) continue;
weights.put(BlockKey.deserialize(key), weight);
}
@@ -42,23 +40,22 @@ public final class BlockConfigLoader {
yaml.getString("blocks.fill-mode", "CUBOID").toUpperCase()
);
- return new BlockConfig(weights, fillMode, parseMaskConfig(yaml, fillMode));
- }
+ // Parse mask config if applicable
+ BlockConfig.MaskConfig mask = null;
+ if (fillMode == FillMode.MASK) {
+ String markerStr = yaml.getString("blocks.mask.marker", "pink_concrete");
+ BlockKey marker = BlockKey.deserialize(markerStr);
- private BlockConfig.MaskConfig parseMaskConfig(YamlConfiguration yaml, FillMode fillMode) {
- if (fillMode != FillMode.MASK) {
- return null;
- }
- String markerStr = yaml.getString("blocks.mask.marker", "pink_concrete");
- BlockKey marker = BlockKey.deserialize(markerStr);
+ Map positions = new HashMap<>();
+ List posList = yaml.getStringList("blocks.mask.positions");
+ for (String pos : posList) {
+ positions.put(pos, true);
+ }
- Map positions = new HashMap<>();
- List posList = yaml.getStringList("blocks.mask.positions");
- for (String pos : posList) {
- positions.put(pos, true);
+ mask = new BlockConfig.MaskConfig(marker, positions);
}
- return new BlockConfig.MaskConfig(marker, positions);
+ return new BlockConfig(weights, fillMode, mask);
}
/**
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/region/RegionConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/region/RegionConfigLoader.java
index b3eaf94..409407e 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/region/RegionConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/region/RegionConfigLoader.java
@@ -17,17 +17,32 @@ public final class RegionConfigLoader {
/**
* Parses region configuration from YAML.
- * Supports up to 100 selection points (50 regions).
*/
public RegionConfig parse(YamlConfiguration yaml) {
List selections = new ArrayList<>();
- // Try new format first (region.selection.X)
- selections.addAll(parseSelections(yaml, "region.selection.", 100));
+ // 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));
+ }
+ }
+ }
- // Legacy fallback (selection.X)
+ // Legacy fallback
if (selections.isEmpty()) {
- selections.addAll(parseSelections(yaml, "selection.", 100));
+ 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));
+ }
+ }
+ }
}
if (selections.size() < 2) {
@@ -37,56 +52,15 @@ 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 parseSelections(YamlConfiguration yaml, String prefix, int maxPoints) {
- List 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.
*/
public void save(YamlConfiguration yaml, RegionConfig region) {
int i = 1;
for (var cuboid : region.regions()) {
- Location min = new Location(cuboid.getWorld(), cuboid.getMinX(), cuboid.getMinY(), cuboid.getMinZ());
- yaml.set("region.selection." + i, LocationParser.format(min));
+ yaml.set("region.selection." + i, LocationParser.format(cuboid.getMin()));
i++;
- Location max = new Location(cuboid.getWorld(), cuboid.getMaxX(), cuboid.getMaxY(), cuboid.getMaxZ());
- yaml.set("region.selection." + i, LocationParser.format(max));
+ yaml.set("region.selection." + i, LocationParser.format(cuboid.getMax()));
i++;
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/region/WorldGuardConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/region/WorldGuardConfigLoader.java
index 7be99d1..021da2b 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/region/WorldGuardConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/region/WorldGuardConfigLoader.java
@@ -1,10 +1,14 @@
package dev.loki.lomines.data.config.loader.region;
-import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
+import dev.loki.lomines.data.config.ConfigLoader;
+import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* Loader for WorldGuard configuration section.
@@ -19,72 +23,56 @@ public final class WorldGuardConfigLoader {
/**
* Parses WorldGuard configuration from YAML.
- * Supports any WorldGuard flags. Default: block-break=allow.
*/
public WorldGuardConfig parse(YamlConfiguration yaml) {
- if (!isEnabled(yaml)) {
+ ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard");
+ if (wgSection == null) {
+ // Check defaults
+ boolean defaultEnabled = defaults.getBoolean("worldguard.enabled", false);
+ if (!defaultEnabled) {
+ return WorldGuardConfig.disabled();
+ }
+ }
+
+ boolean enabled = yaml.getBoolean("worldguard.enabled",
+ defaults.getBoolean("worldguard.enabled", false));
+
+ if (!enabled) {
return WorldGuardConfig.disabled();
}
- List owners = resolveOwners(yaml);
- List members = resolveMembers(yaml);
- List flags = resolveFlags(yaml);
- boolean protect = resolveProtect(yaml);
- String tmpl = yaml.getString("worldguard.region-template",
+ String template = yaml.getString("worldguard.region-template",
defaults.getString("worldguard.region-template", "{mine_name}_{random_4}"));
- return WorldGuardConfig.builder()
- .enabled(true)
- .template(tmpl)
- .owners(owners)
- .members(members)
- .flags(flags)
- .protectOnCreate(protect)
- .build();
- }
-
- private boolean isEnabled(YamlConfiguration yaml) {
- ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard");
- if (wgSection == null) {
- boolean defaultEnabled = defaults.getBoolean("worldguard.enabled", false);
- if (!defaultEnabled) {
- return false;
- }
- }
- return yaml.getBoolean("worldguard.enabled", defaults.getBoolean("worldguard.enabled", false));
- }
-
- private List resolveOwners(YamlConfiguration yaml) {
List owners = yaml.getStringList("worldguard.owners");
if (owners.isEmpty()) {
owners = defaults.getStringList("worldguard.owners");
}
- return owners;
- }
- private List resolveMembers(YamlConfiguration yaml) {
List members = yaml.getStringList("worldguard.members");
if (members.isEmpty()) {
members = defaults.getStringList("worldguard.members");
}
- return members;
- }
- private List resolveFlags(YamlConfiguration yaml) {
- // Support any WorldGuard flags. Default: only block-break=allow
List flags = yaml.getStringList("worldguard.flags");
if (flags.isEmpty()) {
flags = defaults.getStringList("worldguard.flags");
}
if (flags.isEmpty()) {
- flags = List.of("block-break=allow");
+ flags = List.of("passthrough=deny", "build=allow");
}
- return flags;
- }
- private boolean resolveProtect(YamlConfiguration yaml) {
- return yaml.getBoolean("worldguard.protect-on-create",
+ boolean protect = yaml.getBoolean("worldguard.protect-on-create",
defaults.getBoolean("worldguard.protect-on-create", true));
+
+ return WorldGuardConfig.builder()
+ .enabled(true)
+ .template(template)
+ .owners(owners)
+ .members(members)
+ .flags(flags)
+ .protectOnCreate(protect)
+ .build();
}
/**
@@ -104,14 +92,13 @@ public final class WorldGuardConfigLoader {
/**
* Sets default values for WorldGuard configuration.
- * Default: only block-break=allow. User can add any flags.
*/
public void setDefaults(YamlConfiguration yaml) {
yaml.set("worldguard.enabled", false);
yaml.set("worldguard.region-template", "{mine_name}_{random_4}");
yaml.set("worldguard.owners", List.of());
yaml.set("worldguard.members", List.of());
- yaml.set("worldguard.flags", List.of("block-break=allow"));
+ yaml.set("worldguard.flags", List.of("passthrough=deny", "build=allow"));
yaml.set("worldguard.protect-on-create", true);
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/reward/RewardConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/reward/RewardConfigLoader.java
index e4b149a..bb4defd 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/reward/RewardConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/reward/RewardConfigLoader.java
@@ -36,31 +36,18 @@ public final class RewardConfigLoader {
}
private RewardEntry parseRewardEntry(Map, ?> map) {
- List blocks = parseBlocks(map.get("blocks"));
- List items = parseItems(map.get("items"));
- List commands = parseCommands(map.get("commands"));
- Object preventDropsObj = map.get("prevent-drops");
- boolean preventDrops = preventDropsObj instanceof Boolean b && b;
+ double chance = ((Number) map.getOrDefault("chance", 0)).doubleValue();
- Object chanceObj = map.get("chance");
- double chance = chanceObj instanceof Number n ? n.doubleValue() : 0.0;
- return new RewardEntry(blocks, chance, items, commands, preventDrops);
- }
-
- private List parseBlocks(Object rawBlocks) {
List blocks = new ArrayList<>();
- List> blockList = (List>) rawBlocks;
+ List> blockList = (List>) map.get("blocks");
if (blockList != null) {
for (Object b : blockList) {
blocks.add(BlockKey.deserialize(b.toString()));
}
}
- return blocks;
- }
- private List parseItems(Object rawItems) {
List items = new ArrayList<>();
- List> itemList = (List>) rawItems;
+ List> itemList = (List>) map.get("items");
if (itemList != null) {
for (Object i : itemList) {
@SuppressWarnings("unchecked")
@@ -68,19 +55,12 @@ public final class RewardConfigLoader {
items.add(parseItemReward(itemMap));
}
}
- return items;
- }
- private List parseCommands(Object commandsObj) {
- List commands = new ArrayList<>();
- if (commandsObj instanceof List> list) {
- for (Object obj : list) {
- if (obj != null) {
- commands.add(obj.toString());
- }
- }
- }
- return commands;
+ @SuppressWarnings("unchecked")
+ List commands = (List) map.getOrDefault("commands", List.of());
+ boolean preventDrops = (Boolean) map.getOrDefault("prevent-drops", false);
+
+ return new RewardEntry(blocks, chance, items, commands, preventDrops);
}
private ItemReward parseItemReward(Map map) {
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/system/ResetConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/system/ResetConfigLoader.java
index 8667025..d939529 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/system/ResetConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/system/ResetConfigLoader.java
@@ -3,6 +3,8 @@ package dev.loki.lomines.data.config.loader.system;
import dev.loki.lomines.data.config.reset.ResetConfig;
import org.bukkit.configuration.file.YamlConfiguration;
+import java.util.List;
+
/**
* Loader for reset configuration section.
*/
diff --git a/src/main/java/dev/loki/lomines/data/config/loader/system/UIConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/loader/system/UIConfigLoader.java
index 12cae6c..d8ba26b 100644
--- a/src/main/java/dev/loki/lomines/data/config/loader/system/UIConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/loader/system/UIConfigLoader.java
@@ -1,9 +1,7 @@
package dev.loki.lomines.data.config.loader.system;
import dev.loki.lomines.data.config.ui.UIConfig;
-import dev.loki.lomines.data.config.ui.HologramConfig;
import org.bukkit.configuration.file.YamlConfiguration;
-import java.util.List;
/**
* Loader for UI configuration section.
@@ -27,21 +25,10 @@ public final class UIConfigLoader {
actionBarEnabled,
yaml.getString("ui.actionbar.format", UIConfig.DEFAULT_ACTIONBAR_FORMAT),
yaml.getDouble("ui.actionbar.range", 50.0),
- yaml.getString("ui.timer-format", UIConfig.DEFAULT_TIMER_FORMAT),
- parseHologram(yaml)
+ yaml.getString("ui.timer-format", UIConfig.DEFAULT_TIMER_FORMAT)
);
}
- private HologramConfig parseHologram(YamlConfiguration yaml) {
- boolean enabled = yaml.getBoolean("ui.hologram.enabled", false);
- List format = yaml.getStringList("ui.hologram.format");
- if (format.isEmpty()) {
- format = HologramConfig.defaults().format();
- }
- double height = yaml.getDouble("ui.hologram.height", 2.5);
- return new HologramConfig(enabled, format, height);
- }
-
/**
* Saves UI configuration to YAML.
*/
@@ -50,8 +37,5 @@ public final class UIConfigLoader {
yaml.set("ui.actionbar.format", ui.actionBarFormat());
yaml.set("ui.actionbar.range", ui.actionBarRange());
yaml.set("ui.timer-format", ui.timerFormat());
- yaml.set("ui.hologram.enabled", ui.hologram().enabled());
- yaml.set("ui.hologram.format", ui.hologram().format());
- yaml.set("ui.hologram.height", ui.hologram().height());
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/model/MineConfig.java b/src/main/java/dev/loki/lomines/data/config/model/MineConfig.java
deleted file mode 100644
index 14b1638..0000000
--- a/src/main/java/dev/loki/lomines/data/config/model/MineConfig.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package dev.loki.lomines.data.config.model;
-
-import dev.loki.lomines.data.config.block.BlockConfig;
-import dev.loki.lomines.data.config.region.RegionConfig;
-import dev.loki.lomines.data.config.reset.ResetConfig;
-import dev.loki.lomines.data.config.reward.RewardConfig;
-import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
-import dev.loki.lomines.data.config.teleport.TeleportConfig;
-import dev.loki.lomines.data.config.ui.UIConfig;
-import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
-
-import org.bukkit.Location;
-
-import java.util.Objects;
-import java.util.Optional;
-
-public record MineConfig(
- String name,
- RegionConfig region,
- BlockConfig blocks,
- ResetConfig reset,
- RewardConfig rewards,
- TeleportConfig teleport,
- UIConfig ui,
- WorldGuardConfig worldGuard,
- PlayerSpawnConfig playerSpawn
-) {
-
- public MineConfig {
- Objects.requireNonNull(name, "Mine name cannot be null");
- if (name.isBlank()) {
- throw new IllegalArgumentException("Mine name cannot be blank");
- }
- name = name.toLowerCase().trim();
-
- Objects.requireNonNull(region, "Region config cannot be null");
- Objects.requireNonNull(blocks, "Block config cannot be null");
- Objects.requireNonNull(reset, "Reset config cannot be null");
- Objects.requireNonNull(rewards, "Reward config cannot be null");
- Objects.requireNonNull(teleport, "Teleport config cannot be null");
- Objects.requireNonNull(ui, "UI config cannot be null");
- if (playerSpawn == null) {
- playerSpawn = PlayerSpawnConfig.disabled();
- }
- }
-
- public static MineConfigDefaults.Builder builder(String name) {
- return new MineConfigDefaults.Builder(name);
- }
-
- public int volume() {
- return region.totalVolume();
- }
-
- public String worldName() {
- return region.worldName();
- }
-
- public Optional getSpawnForStuckPlayer() {
- return playerSpawn.enabled()
- ? playerSpawn.getLocation()
- : teleport.getLocation();
- }
-}
diff --git a/src/main/java/dev/loki/lomines/data/config/parser/ConfigParseException.java b/src/main/java/dev/loki/lomines/data/config/parser/ConfigParseException.java
deleted file mode 100644
index d2bf87f..0000000
--- a/src/main/java/dev/loki/lomines/data/config/parser/ConfigParseException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package dev.loki.lomines.data.config.parser;
-
-/**
- * Exception for configuration parsing errors.
- */
-public class ConfigParseException extends Exception {
-
- public ConfigParseException(String message) {
- super(message);
- }
-
- public ConfigParseException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/dev/loki/lomines/data/config/region/RegionConfig.java b/src/main/java/dev/loki/lomines/data/config/region/RegionConfig.java
index 0380f97..25dd60c 100644
--- a/src/main/java/dev/loki/lomines/data/config/region/RegionConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/region/RegionConfig.java
@@ -1,6 +1,6 @@
package dev.loki.lomines.data.config.region;
-import dev.loki.lomines.util.location.geo.Cuboid;
+import dev.loki.lomines.util.location.Cuboid;
import org.bukkit.Location;
import java.util.Collections;
diff --git a/src/main/java/dev/loki/lomines/data/config/reset/ResetConfig.java b/src/main/java/dev/loki/lomines/data/config/reset/ResetConfig.java
index bb14f83..c97ee17 100644
--- a/src/main/java/dev/loki/lomines/data/config/reset/ResetConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/reset/ResetConfig.java
@@ -1,8 +1,13 @@
package dev.loki.lomines.data.config.reset;
import java.time.Duration;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+/**
+ * Configuration for mine reset behavior.
+ */
public record ResetConfig(
Duration interval,
double percentTrigger,
@@ -11,34 +16,41 @@ public record ResetConfig(
String broadcastMessage
) {
+ public static final Duration DEFAULT_INTERVAL = Duration.ofMinutes(5);
+ public static final double DEFAULT_PERCENT_TRIGGER = 10.0;
+
public ResetConfig {
- interval = ResetConfigValidator.validateInterval(interval);
- percentTrigger = ResetConfigValidator.validatePercentTrigger(percentTrigger);
+ // Normalize and validate interval
+ if (interval == null || interval.isZero() || interval.isNegative()) {
+ interval = DEFAULT_INTERVAL;
+ }
+
+ // Ensure interval is reasonable (1 second to 24 hours)
+ if (interval.getSeconds() < 1) {
+ interval = Duration.ofSeconds(1);
+ } else if (interval.getSeconds() > 86400) {
+ interval = Duration.ofHours(24);
+ }
+
+ // Validate percent trigger
+ if (percentTrigger < 0 || percentTrigger > 100) {
+ percentTrigger = DEFAULT_PERCENT_TRIGGER;
+ }
+
commands = commands != null ? List.copyOf(commands) : List.of();
broadcastMessage = broadcastMessage != null ? broadcastMessage : "";
}
- public static final Duration DEFAULT_INTERVAL = ResetConfigValidator.DEFAULT_INTERVAL;
- public static final double DEFAULT_PERCENT_TRIGGER = ResetConfigValidator.DEFAULT_PERCENT_TRIGGER;
-
- public static ResetConfig defaults() {
- return new ResetConfig(
- DEFAULT_INTERVAL,
- DEFAULT_PERCENT_TRIGGER,
- false,
- List.of(),
- ""
- );
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
+ /**
+ * Interval in ticks (20 ticks = 1 second).
+ */
public long intervalTicks() {
return interval.getSeconds() * 20;
}
+ /**
+ * Human-readable interval string.
+ */
public String intervalDisplay() {
long seconds = interval.getSeconds();
if (seconds < 60) {
@@ -50,10 +62,33 @@ public record ResetConfig(
}
}
+ /**
+ * Whether reset should trigger based on percent mined.
+ */
public boolean isPercentTriggerEnabled() {
return percentEnabled && percentTrigger > 0;
}
+ /**
+ * Default config: 5 minutes, 10% trigger disabled.
+ */
+ public static ResetConfig defaults() {
+ return new ResetConfig(
+ DEFAULT_INTERVAL,
+ DEFAULT_PERCENT_TRIGGER,
+ false,
+ List.of(),
+ ""
+ );
+ }
+
+ /**
+ * Builder for fluent construction.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
public static class Builder {
private Duration interval = DEFAULT_INTERVAL;
private double percentTrigger = DEFAULT_PERCENT_TRIGGER;
@@ -66,13 +101,13 @@ public record ResetConfig(
return this;
}
- public Builder interval(String humanReadable) {
- this.interval = ResetConfigValidator.parseDuration(humanReadable);
+ public Builder intervalTicks(long ticks) {
+ this.interval = Duration.ofSeconds(ticks / 20);
return this;
}
- public Builder intervalTicks(long ticks) {
- this.interval = Duration.ofSeconds(ticks / 20);
+ public Builder interval(String humanReadable) {
+ this.interval = parseDuration(humanReadable);
return this;
}
@@ -99,5 +134,25 @@ public record ResetConfig(
public ResetConfig build() {
return new ResetConfig(interval, percentTrigger, percentEnabled, commands, broadcastMessage);
}
+
+ private static Duration parseDuration(String s) {
+ s = s.trim().toLowerCase();
+ try {
+ if (s.endsWith("s")) {
+ return Duration.ofSeconds(Long.parseLong(s.substring(0, s.length() - 1)));
+ } else if (s.endsWith("m")) {
+ return Duration.ofMinutes(Long.parseLong(s.substring(0, s.length() - 1)));
+ } else if (s.endsWith("h")) {
+ return Duration.ofHours(Long.parseLong(s.substring(0, s.length() - 1)));
+ } else if (s.endsWith("d")) {
+ return Duration.ofDays(Long.parseLong(s.substring(0, s.length() - 1)));
+ } else {
+ // Assume seconds if no suffix
+ return Duration.ofSeconds(Long.parseLong(s));
+ }
+ } catch (NumberFormatException e) {
+ return DEFAULT_INTERVAL;
+ }
+ }
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/reset/ResetConfigValidator.java b/src/main/java/dev/loki/lomines/data/config/reset/ResetConfigValidator.java
deleted file mode 100644
index 6f3d5d4..0000000
--- a/src/main/java/dev/loki/lomines/data/config/reset/ResetConfigValidator.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package dev.loki.lomines.data.config.reset;
-
-import java.time.Duration;
-
-public final class ResetConfigValidator {
-
- public static final Duration DEFAULT_INTERVAL = Duration.ofMinutes(5);
- public static final double DEFAULT_PERCENT_TRIGGER = 10.0;
-
- private ResetConfigValidator() {
- }
-
- public static Duration validateInterval(Duration interval) {
- if (interval == null || interval.isZero() || interval.isNegative()) {
- interval = DEFAULT_INTERVAL;
- }
- if (interval.getSeconds() < 1) {
- interval = Duration.ofSeconds(1);
- } else if (interval.getSeconds() > 86400) {
- interval = Duration.ofHours(24);
- }
- return interval;
- }
-
- public static double validatePercentTrigger(double percentTrigger) {
- if (percentTrigger < 0 || percentTrigger > 100) {
- return DEFAULT_PERCENT_TRIGGER;
- }
- return percentTrigger;
- }
-
- public static Duration parseDuration(String s) {
- s = s.trim().toLowerCase();
- try {
- if (s.endsWith("s")) {
- return Duration.ofSeconds(Long.parseLong(s.substring(0, s.length() - 1)));
- } else if (s.endsWith("m")) {
- return Duration.ofMinutes(Long.parseLong(s.substring(0, s.length() - 1)));
- } else if (s.endsWith("h")) {
- return Duration.ofHours(Long.parseLong(s.substring(0, s.length() - 1)));
- } else if (s.endsWith("d")) {
- return Duration.ofDays(Long.parseLong(s.substring(0, s.length() - 1)));
- } else {
- return Duration.ofSeconds(Long.parseLong(s));
- }
- } catch (NumberFormatException e) {
- return DEFAULT_INTERVAL;
- }
- }
-}
diff --git a/src/main/java/dev/loki/lomines/data/config/reward/RewardConfig.java b/src/main/java/dev/loki/lomines/data/config/reward/RewardConfig.java
index 1cecb4c..9d8c2b7 100644
--- a/src/main/java/dev/loki/lomines/data/config/reward/RewardConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/reward/RewardConfig.java
@@ -4,7 +4,9 @@ import dev.loki.lomines.data.config.block.BlockKey;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -16,20 +18,6 @@ public record RewardConfig(List entries) {
entries = entries != null ? List.copyOf(entries) : List.of();
}
- /**
- * Empty reward config.
- */
- public static RewardConfig empty() {
- return new RewardConfig(List.of());
- }
-
- /**
- * Single entry reward config builder.
- */
- public static Builder builder() {
- return new Builder();
- }
-
/**
* Returns rewards applicable for the given block.
*/
@@ -46,6 +34,20 @@ public record RewardConfig(List entries) {
return !entries.isEmpty();
}
+ /**
+ * Empty reward config.
+ */
+ public static RewardConfig empty() {
+ return new RewardConfig(List.of());
+ }
+
+ /**
+ * Single entry reward config builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
// --- Reward Entry ---
public record RewardEntry(
diff --git a/src/main/java/dev/loki/lomines/data/config/spawn/PlayerSpawnConfig.java b/src/main/java/dev/loki/lomines/data/config/spawn/PlayerSpawnConfig.java
index f3ef227..8cecd00 100644
--- a/src/main/java/dev/loki/lomines/data/config/spawn/PlayerSpawnConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/spawn/PlayerSpawnConfig.java
@@ -2,36 +2,35 @@ package dev.loki.lomines.data.config.spawn;
import org.bukkit.Location;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
- * Configuration for player spawn locations when stuck in blocks.
- * Supports multiple spawn points for a single mine.
+ * Configuration for player spawn location when stuck in blocks.
* Used to teleport players out when they are suffocating after mine reset.
*/
-public record PlayerSpawnConfig(boolean enabled, List locations) {
+public record PlayerSpawnConfig(
+ boolean enabled,
+ Location location
+) {
- public PlayerSpawnConfig(boolean enabled, List locations) {
- this.enabled = enabled;
- if (!enabled || locations == null) {
- this.locations = List.of();
- } else {
- // Defensive copy with validation
- List copy = new ArrayList<>();
- for (Location loc : locations) {
- if (loc != null) {
- Location cloned = loc.clone();
- Objects.requireNonNull(cloned.getWorld(), "Spawn world cannot be null");
- copy.add(cloned);
- }
- }
- this.locations = List.copyOf(copy);
+ public PlayerSpawnConfig {
+ if (!enabled) {
+ location = null;
+ } else if (location != null) {
+ // Defensive copy
+ location = location.clone();
+ Objects.requireNonNull(location.getWorld(), "Spawn world cannot be null");
}
}
+ /**
+ * Returns the spawn location if enabled.
+ */
+ public Optional getLocation() {
+ return Optional.ofNullable(enabled ? location : null);
+ }
+
/**
* Disabled spawn config (default).
*/
@@ -40,74 +39,9 @@ public record PlayerSpawnConfig(boolean enabled, List locations) {
}
/**
- * Creates enabled config with single location.
+ * Creates enabled config with location.
*/
public static PlayerSpawnConfig at(Location location) {
- return new PlayerSpawnConfig(true, List.of(location));
- }
-
- /**
- * Creates enabled config with multiple locations.
- */
- public static PlayerSpawnConfig at(List locations) {
- return new PlayerSpawnConfig(true, locations);
- }
-
- /**
- * Returns the primary spawn location (first in list) if enabled.
- */
- public Optional getLocation() {
- return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
- }
-
- /**
- * Returns all spawn locations.
- */
- @Override
- public List locations() {
- return locations;
- }
-
- /**
- * Returns true if spawn is enabled and has at least one location.
- */
- @Override
- public boolean enabled() {
- return enabled && !locations.isEmpty();
- }
-
- /**
- * Returns the number of configured spawn locations.
- */
- public int count() {
- return locations.size();
- }
-
- /**
- * Creates a new config with an additional location.
- */
- public PlayerSpawnConfig addLocation(Location location) {
- List newList = new ArrayList<>(locations);
- newList.add(location);
- return new PlayerSpawnConfig(true, newList);
- }
-
- /**
- * Creates a new config without the location at given index.
- */
- public PlayerSpawnConfig removeLocation(int index) {
- if (index < 0 || index >= locations.size()) {
- return this;
- }
- List newList = new ArrayList<>(locations);
- newList.remove(index);
- return new PlayerSpawnConfig(!newList.isEmpty(), newList);
- }
-
- /**
- * Clears all locations (disables spawn).
- */
- public PlayerSpawnConfig clear() {
- return disabled();
+ return new PlayerSpawnConfig(true, location);
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/teleport/TeleportConfig.java b/src/main/java/dev/loki/lomines/data/config/teleport/TeleportConfig.java
index 615b3bb..c468b63 100644
--- a/src/main/java/dev/loki/lomines/data/config/teleport/TeleportConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/teleport/TeleportConfig.java
@@ -2,35 +2,34 @@ package dev.loki.lomines.data.config.teleport;
import org.bukkit.Location;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Configuration for mine teleportation on reset.
- * Supports multiple teleport locations for a single mine.
*/
-public record TeleportConfig(boolean enabled, List locations) {
+public record TeleportConfig(
+ boolean enabled,
+ Location location
+) {
- public TeleportConfig(boolean enabled, List locations) {
- this.enabled = enabled;
- if (!enabled || locations == null) {
- this.locations = List.of();
- } else {
- // Defensive copy with validation
- List copy = new ArrayList<>();
- for (Location loc : locations) {
- if (loc != null) {
- Location cloned = loc.clone();
- Objects.requireNonNull(cloned.getWorld(), "Teleport world cannot be null");
- copy.add(cloned);
- }
- }
- this.locations = List.copyOf(copy);
+ public TeleportConfig {
+ if (!enabled) {
+ location = null;
+ } else if (location != null) {
+ // Defensive copy
+ location = location.clone();
+ Objects.requireNonNull(location.getWorld(), "Teleport world cannot be null");
}
}
+ /**
+ * Returns the teleport location if enabled.
+ */
+ public Optional getLocation() {
+ return Optional.ofNullable(enabled ? location : null);
+ }
+
/**
* Disabled teleport config.
*/
@@ -39,74 +38,9 @@ public record TeleportConfig(boolean enabled, List locations) {
}
/**
- * Creates enabled config with single location.
+ * Creates enabled config with location.
*/
public static TeleportConfig at(Location location) {
- return new TeleportConfig(true, List.of(location));
- }
-
- /**
- * Creates enabled config with multiple locations.
- */
- public static TeleportConfig at(List locations) {
- return new TeleportConfig(true, locations);
- }
-
- /**
- * Returns the primary teleport location (first in list) if enabled.
- */
- public Optional getLocation() {
- return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
- }
-
- /**
- * Returns all teleport locations.
- */
- @Override
- public List locations() {
- return locations;
- }
-
- /**
- * Returns true if teleport is enabled and has at least one location.
- */
- @Override
- public boolean enabled() {
- return enabled && !locations.isEmpty();
- }
-
- /**
- * Returns the number of configured teleport locations.
- */
- public int count() {
- return locations.size();
- }
-
- /**
- * Creates a new config with an additional location.
- */
- public TeleportConfig addLocation(Location location) {
- List newList = new ArrayList<>(locations);
- newList.add(location);
- return new TeleportConfig(true, newList);
- }
-
- /**
- * Creates a new config without the location at given index.
- */
- public TeleportConfig removeLocation(int index) {
- if (index < 0 || index >= locations.size()) {
- return this;
- }
- List newList = new ArrayList<>(locations);
- newList.remove(index);
- return new TeleportConfig(!newList.isEmpty(), newList);
- }
-
- /**
- * Clears all locations (disables teleport).
- */
- public TeleportConfig clear() {
- return disabled();
+ return new TeleportConfig(true, location);
}
}
diff --git a/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java b/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java
index bdfbcd7..e829ef3 100644
--- a/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java
@@ -6,8 +6,8 @@ import java.util.List;
* Configuration for holograms displayed above mines.
*
* @param enabled whether holograms are enabled
- * @param format list of lines with placeholders {mine}, {percent}, {bar}, {time}
- * @param height offset above the mine region (in blocks)
+ * @param format list of lines with placeholders {mine}, {percent}, {bar}, {time}
+ * @param height offset above the mine region (in blocks)
*/
public record HologramConfig(
boolean enabled,
diff --git a/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java b/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java
index e4bcfc2..c3eb373 100644
--- a/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java
+++ b/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java
@@ -1,9 +1,9 @@
package dev.loki.lomines.data.config.ui;
-import dev.loki.lomines.util.format.color.ColorUtils;
+import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.Component;
-import java.util.Locale;
+import java.util.Objects;
/**
* Configuration for UI elements (action bar, messages).
@@ -16,11 +16,14 @@ public record UIConfig(
HologramConfig hologram
) {
+ public UIConfig {
+ hologram = hologram != null ? hologram : HologramConfig.disabled();
+ }
+
public static final String DEFAULT_ACTIONBAR_FORMAT = "{mine} {percent}% ({time})";
public static final String DEFAULT_TIMER_FORMAT = "mm:ss";
public UIConfig {
- hologram = hologram != null ? hologram : HologramConfig.disabled();
actionBarFormat = actionBarFormat != null && !actionBarFormat.isBlank()
? actionBarFormat
: DEFAULT_ACTIONBAR_FORMAT;
@@ -30,34 +33,19 @@ public record UIConfig(
actionBarRange = Math.max(1, actionBarRange);
}
- /**
- * Default configuration.
- */
- public static UIConfig defaults() {
- return new UIConfig(true, DEFAULT_ACTIONBAR_FORMAT, 50.0, DEFAULT_TIMER_FORMAT, HologramConfig.defaults());
- }
-
- /**
- * Disabled configuration (no UI).
- */
- public static UIConfig disabled() {
- return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT, HologramConfig.disabled());
- }
-
/**
* Parses the action bar format with placeholders.
* Placeholders: {mine}, {percent}, {time}, {blocks}, {total}
- * Supports colors: RRGGBB, &a..&f,
*/
public Component formatActionBar(String mineName, double percent, String timeStr, int blocks, int total) {
String parsed = actionBarFormat
.replace("{mine}", mineName)
- .replace("{percent}", String.format(Locale.US, "%.1f", percent))
+ .replace("{percent}", String.format("%.1f", percent))
.replace("{time}", timeStr)
.replace("{blocks}", String.valueOf(blocks))
.replace("{total}", String.valueOf(total));
- return ColorUtils.format(parsed);
+ return MiniMessage.miniMessage().deserialize(parsed);
}
/**
@@ -82,4 +70,18 @@ public record UIConfig(
public double actionBarRangeSquared() {
return actionBarRange * actionBarRange;
}
+
+ /**
+ * Default configuration.
+ */
+ public static UIConfig defaults() {
+ return new UIConfig(true, DEFAULT_ACTIONBAR_FORMAT, 50.0, DEFAULT_TIMER_FORMAT, HologramConfig.defaults());
+ }
+
+ /**
+ * Disabled configuration (no UI).
+ */
+ public static UIConfig disabled() {
+ return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT, HologramConfig.disabled());
+ }
}
diff --git a/src/main/java/dev/loki/lomines/data/reward/entity/Reward.java b/src/main/java/dev/loki/lomines/data/reward/Reward.java
similarity index 83%
rename from src/main/java/dev/loki/lomines/data/reward/entity/Reward.java
rename to src/main/java/dev/loki/lomines/data/reward/Reward.java
index 7b15d36..9b5bed4 100644
--- a/src/main/java/dev/loki/lomines/data/reward/entity/Reward.java
+++ b/src/main/java/dev/loki/lomines/data/reward/Reward.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.data.reward.entity;
+package dev.loki.lomines.data.reward;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@@ -49,12 +49,8 @@ public record Reward(List materials, double chance, List it
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
Reward reward = (Reward) o;
return Double.compare(reward.chance, chance) == 0 &&
preventDrops == reward.preventDrops &&
@@ -63,8 +59,4 @@ public record Reward(List materials, double chance, List it
Objects.equals(commands, reward.commands);
}
- @Override
- public int hashCode() {
- return Objects.hash(materials, chance, items, commands, preventDrops);
- }
}
diff --git a/src/main/java/dev/loki/lomines/data/reward/parse/command/RewardCommandParser.java b/src/main/java/dev/loki/lomines/data/reward/RewardCommandParser.java
similarity index 84%
rename from src/main/java/dev/loki/lomines/data/reward/parse/command/RewardCommandParser.java
rename to src/main/java/dev/loki/lomines/data/reward/RewardCommandParser.java
index d058411..a93ab1b 100644
--- a/src/main/java/dev/loki/lomines/data/reward/parse/command/RewardCommandParser.java
+++ b/src/main/java/dev/loki/lomines/data/reward/RewardCommandParser.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.data.reward.parse.command;
+package dev.loki.lomines.data.reward;
import dev.loki.lomines.data.config.parser.ConfigParseException;
@@ -9,10 +9,10 @@ import java.util.Map;
/**
* Parses reward commands from configuration.
*/
-public final class RewardCommandParser {
+final class RewardCommandParser {
@SuppressWarnings("unchecked")
- public List parseCommands(Map rewardMap) throws ConfigParseException {
+ List parseCommands(Map rewardMap) throws ConfigParseException {
List commands = new ArrayList<>();
if (!rewardMap.containsKey("commands")) {
return commands;
diff --git a/src/main/java/dev/loki/lomines/data/reward/parse/item/RewardItemParser.java b/src/main/java/dev/loki/lomines/data/reward/RewardItemParser.java
similarity index 93%
rename from src/main/java/dev/loki/lomines/data/reward/parse/item/RewardItemParser.java
rename to src/main/java/dev/loki/lomines/data/reward/RewardItemParser.java
index 49c7d7e..b52bf30 100644
--- a/src/main/java/dev/loki/lomines/data/reward/parse/item/RewardItemParser.java
+++ b/src/main/java/dev/loki/lomines/data/reward/RewardItemParser.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.data.reward.parse.item;
+package dev.loki.lomines.data.reward;
import dev.loki.lomines.data.config.parser.ConfigParseException;
import dev.loki.lomines.util.ValidationUtils;
@@ -12,7 +12,7 @@ import java.util.Map;
* Parses ItemStack objects from YAML reward item configurations.
* Handles material type, amount, and basic item properties.
*/
-public final class RewardItemParser {
+final class RewardItemParser {
/**
* Parses a single reward item from a map configuration.
@@ -22,7 +22,7 @@ public final class RewardItemParser {
* @return the parsed ItemStack
* @throws ConfigParseException if the item configuration is invalid
*/
- public ItemStack parseItem(Map itemMap, int index) throws ConfigParseException {
+ ItemStack parseItem(Map itemMap, int index) throws ConfigParseException {
if (!itemMap.containsKey("type")) {
throw new ConfigParseException("Missing required 'type' field in item at index " + index);
}
diff --git a/src/main/java/dev/loki/lomines/data/reward/parse/item/RewardMaterialParser.java b/src/main/java/dev/loki/lomines/data/reward/RewardMaterialParser.java
similarity index 88%
rename from src/main/java/dev/loki/lomines/data/reward/parse/item/RewardMaterialParser.java
rename to src/main/java/dev/loki/lomines/data/reward/RewardMaterialParser.java
index 1085935..9850d04 100644
--- a/src/main/java/dev/loki/lomines/data/reward/parse/item/RewardMaterialParser.java
+++ b/src/main/java/dev/loki/lomines/data/reward/RewardMaterialParser.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.data.reward.parse.item;
+package dev.loki.lomines.data.reward;
import dev.loki.lomines.data.config.parser.ConfigParseException;
import dev.loki.lomines.util.ValidationUtils;
@@ -11,10 +11,10 @@ import java.util.Map;
/**
* Parses reward materials (blocks) from configuration.
*/
-public final class RewardMaterialParser {
+final class RewardMaterialParser {
@SuppressWarnings("unchecked")
- public List