refactor: Java 21 support, config/API updates, GUI polish

This commit is contained in:
loki5512344 2026-06-10 23:18:10 +02:00
parent bfdf197de4
commit ceb4c804b7
76 changed files with 1210 additions and 999 deletions

View file

@ -13,14 +13,31 @@ java {
} }
} }
tasks.withType<JavaCompile> {
options.release.set(21)
}
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.papermc.io/repository/maven-public/")
// PlaceholderAPI
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
// WorldGuard / WorldEdit
maven("https://maven.enginehub.org/repo/")
// CodeMC (DecentHolograms)
maven("https://repo.codemc.io/repository/maven-public/")
maven("https://jitpack.io") maven("https://jitpack.io")
} }
// LoAPI: libs/lolib*.jar — fileTree индексируется Gradle / Java LS стабильнее, чем files(singleJar) val lolibTree = fileTree("libs") {
val lolibTree = fileTree("libs") { include("lolib*.jar") } include("lolib*.jar")
}
if (lolibTree.files.isEmpty()) { if (lolibTree.files.isEmpty()) {
throw GradleException( throw GradleException(
"Missing libs/lolib*.jar — add LoAPI fat jar to ${rootProject.file("libs").absolutePath}" "Missing libs/lolib*.jar — add LoAPI fat jar to ${rootProject.file("libs").absolutePath}"
@ -28,64 +45,80 @@ if (lolibTree.files.isEmpty()) {
} }
dependencies { dependencies {
// Paper API 1.21.4 (includes Adventure API)
// Paper
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
// LoAPI (local) // Local API
implementation(lolibTree) implementation(lolibTree)
// Runtime dependencies (loaded via DependencyManager from LoAPI) // Commons
// These are marked as compileOnly because they will be downloaded at runtime
compileOnly("org.apache.commons:commons-math3:3.6.1") compileOnly("org.apache.commons:commons-math3:3.6.1")
compileOnly("commons-io:commons-io:2.18.0") compileOnly("commons-io:commons-io:2.18.0")
// Adventure API (included in Paper, but explicit for clarity) // Adventure
compileOnly("net.kyori:adventure-api:4.17.0") compileOnly("net.kyori:adventure-api:4.17.0")
compileOnly("net.kyori:adventure-text-minimessage:4.17.0") compileOnly("net.kyori:adventure-text-minimessage:4.17.0")
// Optional plugin integrations (soft dependencies) // PlaceholderAPI
compileOnly("me.clip:placeholderapi:2.11.6")
// WorldGuard + WorldEdit
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.13")
compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.10")
// Optional
// compileOnly("com.github.oraxen:oraxen:1.180.0") // compileOnly("com.github.oraxen:oraxen:1.180.0")
// compileOnly("com.github.LoneDev6:api-itemsadder:3.6.4") // compileOnly("com.github.LoneDev6:api-itemsadder:3.6.4")
// Test dependencies // Tests
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
testImplementation("io.kotest:kotest-runner-junit5:5.9.1") testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
testImplementation("io.kotest:kotest-assertions-core:5.9.1") testImplementation("io.kotest:kotest-assertions-core:5.9.1")
testImplementation("io.kotest:kotest-property:5.9.1") testImplementation("io.kotest:kotest-property:5.9.1")
testImplementation("org.mockito:mockito-core:5.14.2") testImplementation("org.mockito:mockito-core:5.14.2")
testImplementation("org.mockito:mockito-inline:5.2.0")
// Bukkit/Paper API for tests
testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") 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") // Adventure for tests
testImplementation("net.kyori:adventure-api:4.17.0")
testImplementation("net.kyori:adventure-text-minimessage:4.17.0")
} }
tasks { tasks {
shadowJar { shadowJar {
archiveClassifier.set("") archiveClassifier.set("")
// Relocate LoAPI to avoid conflicts // Temporary: disable relocate to avoid ASM Java 21 bytecode issues
relocate("dev.loki.lomines.libs.lolib", "com.loki.lomines.libs.lolib") // relocate(
// "dev.loki.lomines.libs.lolib",
// Don't minimize - causes issues with Java 21 // "com.loki.lomines.libs.lolib"
// minimize() // )
} }
build { build {
dependsOn(shadowJar) dependsOn(shadowJar)
} }
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
processResources { processResources {
val props = mapOf( val props = mapOf(
"version" to version, "version" to version,
"description" to description "description" to description
) )
inputs.properties(props) inputs.properties(props)
filteringCharset = "UTF-8" filteringCharset = "UTF-8"
filesMatching("plugin.yml") { filesMatching("plugin.yml") {
expand(props) expand(props)
} }
} }
} }

Binary file not shown.

251
gradlew vendored Normal file
View file

@ -0,0 +1,251 @@
#!/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" "$@"

91
gradlew.bat vendored
View file

@ -1,7 +1,94 @@
@rem Gradle startup script for Windows @rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @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 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
java -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@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

View file

@ -1,21 +1,11 @@
package dev.loki.lomines; package dev.loki.lomines;
import dev.loki.lomines.command.admin.AdminCommands; import dev.loki.lomines.command.admin.*;
import dev.loki.lomines.command.admin.CopyCommand;
import dev.loki.lomines.command.admin.HologramCommands;
import dev.loki.lomines.command.admin.InfoCommand;
import dev.loki.lomines.command.admin.MaskCommands;
import dev.loki.lomines.command.admin.RegionCommands;
import dev.loki.lomines.command.admin.StatsCommands;
import dev.loki.lomines.command.common.LoMinesTabCompleter; import dev.loki.lomines.command.common.LoMinesTabCompleter;
import dev.loki.lomines.command.player.PlayerCommands; import dev.loki.lomines.command.player.PlayerCommands;
import dev.loki.lomines.command.player.TeleportCommand; import dev.loki.lomines.command.player.TeleportCommand;
import dev.loki.lomines.integration.IntegrationManager; import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.listener.BlockBreakListener; import dev.loki.lomines.listener.*;
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; import dev.lolib.commands.CommandManager;
/** /**

View file

@ -106,8 +106,8 @@ public final class BukkitBlockSetter extends BlockSetter {
currentWeight += entry.getValue(); currentWeight += entry.getValue();
if (random <= currentWeight) { if (random <= currentWeight) {
// BlockKey is already validated to be a block // BlockKey is already validated to be a block
if (entry.getKey() instanceof BlockKey.Vanilla vanilla) { if (entry.getKey() instanceof BlockKey.Vanilla(Material material)) {
return Bukkit.createBlockData(vanilla.material()); return Bukkit.createBlockData(material);
} }
// Fallback for non-vanilla keys in vanilla setter (shouldn't happen) // Fallback for non-vanilla keys in vanilla setter (shouldn't happen)
return Bukkit.createBlockData(Material.STONE); return Bukkit.createBlockData(Material.STONE);
@ -116,8 +116,8 @@ public final class BukkitBlockSetter extends BlockSetter {
// Fallback to first entry // Fallback to first entry
BlockKey firstKey = weights.keySet().iterator().next(); BlockKey firstKey = weights.keySet().iterator().next();
if (firstKey instanceof BlockKey.Vanilla vanilla) { if (firstKey instanceof BlockKey.Vanilla(Material material)) {
return Bukkit.createBlockData(vanilla.material()); return Bukkit.createBlockData(material);
} }
return Bukkit.createBlockData(Material.STONE); return Bukkit.createBlockData(Material.STONE);
} }

View file

@ -2,16 +2,13 @@ package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.mine.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.lolilb.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand; import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.List;
import java.util.Map;
/** /**
* Command to copy mine configuration from one mine to another. * Command to copy mine configuration from one mine to another.
*/ */
@ -53,49 +50,25 @@ public class CopyCommand {
var sourceConfig = fromMine.getConfig(); var sourceConfig = fromMine.getConfig();
var targetConfig = toMine.getConfig(); var targetConfig = toMine.getConfig();
// Copy blocks configuration // Keep target region/name, copy all other sections from source.
targetConfig.blocks().weights().clear(); MineConfig newTargetConfig = MineConfig.builder(toMine.getName())
targetConfig.blocks().weights().putAll(sourceConfig.blocks().weights()); .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();
// Copy reset settings try {
var targetReset = targetConfig.reset(); plugin.getMines().updateMineConfig(toMine.getName(), newTargetConfig);
var sourceReset = sourceConfig.reset(); } catch (Exception e) {
targetReset.intervalSeconds(sourceReset.intervalSeconds()); sender.sendMessage(Component.text("Ошибка сохранения: " + e.getMessage(), NamedTextColor.RED));
targetReset.percentTrigger(sourceReset.percentTrigger()); return;
targetReset.percentTriggerEnabled(sourceReset.isPercentTriggerEnabled());
targetReset.intervalDisplay(sourceReset.intervalDisplay());
// Copy teleport and spawn settings
var targetTeleport = targetConfig.teleport();
var sourceTeleport = sourceConfig.teleport();
targetTeleport.enabled(sourceTeleport.enabled());
if (sourceTeleport.getLocation().isPresent()) {
targetTeleport.setLocation(sourceTeleport.getLocation().get());
} else {
targetTeleport.setLocation(null);
} }
var targetSpawn = targetConfig.playerSpawn();
var sourceSpawn = sourceConfig.playerSpawn();
targetSpawn.enabled(sourceSpawn.enabled());
if (sourceSpawn.getLocation().isPresent()) {
targetSpawn.setLocation(sourceSpawn.getLocation().get());
} else {
targetSpawn.setLocation(null);
}
// Copy rewards
targetConfig.rewards().entries().clear();
targetConfig.rewards().entries().addAll(sourceConfig.rewards().entries());
// Copy other settings
targetConfig.blockPhysics(sourceConfig.isBlockPhysicsEnabled());
targetConfig.entitySpawning(sourceConfig.isEntitySpawningEnabled());
targetConfig.liquidFlow(sourceConfig.isLiquidFlowEnabled());
// Save target mine
toMine.save();
sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() + sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() +
" §aв §f" + toMine.getName())); " §aв §f" + toMine.getName()));
sender.sendMessage(Component.text("§7Скопировано:")); sender.sendMessage(Component.text("§7Скопировано:"));

View file

@ -3,22 +3,21 @@ package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.integration.hologram.HologramManager; import dev.loki.lomines.integration.hologram.HologramManager;
import dev.lolilb.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand; import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/** /**
* Commands for managing holograms above mines. * Commands for managing holograms above mines.
*/ */
class HologramCommands { public class HologramCommands {
private final LoMinesPlugin plugin; private final LoMinesPlugin plugin;
private final HologramManager hologramManager; private final HologramManager hologramManager;
HologramCommands(LoMinesPlugin plugin) { public HologramCommands(LoMinesPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.hologramManager = plugin.getHologramManager(); this.hologramManager = plugin.getHologramManager();
} }
@ -55,6 +54,6 @@ class HologramCommands {
} }
sender.sendMessage(Component.text( sender.sendMessage(Component.text(
"Голограмма для шахты " + mineName + " " + (enable ? "§aвключена" : "§cвыключена"))); "Голограмма для шахты " + mineName + " " + (enable ? "§aвключена" : "§cвыключена")));
} }
} }

View file

@ -4,12 +4,11 @@ import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand; import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -65,10 +64,10 @@ public class InfoCommand {
sender.sendMessage(Component.text("§6§lБлоки:")); sender.sendMessage(Component.text("§6§lБлоки:"));
Map<BlockKey, Double> weights = config.blocks().weights(); Map<BlockKey, Double> weights = config.blocks().weights();
weights.entrySet().stream() weights.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue())) .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(10) .limit(10)
.forEach(e -> sender.sendMessage(Component.text( .forEach(e -> sender.sendMessage(Component.text(
"§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%"))); "§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%")));
if (weights.size() > 10) { if (weights.size() > 10) {
sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков")); sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков"));
} }

View file

@ -2,7 +2,7 @@ package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mines; import dev.loki.lomines.core.mine.Mines;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.util.ErrorHandler; import dev.loki.lomines.util.ErrorHandler;
import dev.loki.lomines.util.MessageFormatter; import dev.loki.lomines.util.MessageFormatter;
import dev.lolib.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;

View file

@ -5,11 +5,10 @@ import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.region.RegionConfig; import dev.loki.lomines.data.config.region.RegionConfig;
import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand; import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -73,14 +72,15 @@ public class RegionCommands {
} }
var wandManager = plugin.getGroupWandManager(); var wandManager = plugin.getGroupWandManager();
var session = wandManager.getSession(player); var session = wandManager.getSession(player.getUniqueId());
int slot = session.getActiveSlot();
if (!session.isComplete()) { var pos1 = session.getPos1(slot);
var pos2 = session.getPos2(slot);
if (pos1 == null || pos2 == null) {
player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка")); player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка"));
return; return;
} }
Cuboid newRegion = new Cuboid(pos1, pos2);
Cuboid newRegion = session.toCuboid();
String mineWorld = mine.getConfig().region().worldName(); String mineWorld = mine.getConfig().region().worldName();
String regionWorld = newRegion.getWorld().getName(); String regionWorld = newRegion.getWorld().getName();
@ -102,7 +102,7 @@ public class RegionCommands {
player.sendMessage(Component.text("§7До: §f" + newRegion.getMaxX() + ", " + newRegion.getMaxY() + ", " + newRegion.getMaxZ())); player.sendMessage(Component.text("§7До: §f" + newRegion.getMaxX() + ", " + newRegion.getMaxY() + ", " + newRegion.getMaxZ()));
player.sendMessage(Component.text("§7Объём: §f" + newRegion.getVolume() + " §7блоков")); player.sendMessage(Component.text("§7Объём: §f" + newRegion.getVolume() + " §7блоков"));
session.clear(); session.clearCorners(slot);
} }
/** /**
@ -144,18 +144,22 @@ public class RegionCommands {
private void updateMineRegions(Mine mine, RegionConfig newConfig) { private void updateMineRegions(Mine mine, RegionConfig newConfig) {
var oldConfig = mine.getConfig(); var oldConfig = mine.getConfig();
var newMineConfig = new MineConfig( var newMineConfig = new MineConfig(
oldConfig.name(),
newConfig, newConfig,
oldConfig.blocks(), oldConfig.blocks(),
oldConfig.reset(), oldConfig.reset(),
oldConfig.teleport(),
oldConfig.playerSpawn(),
oldConfig.rewards(), oldConfig.rewards(),
oldConfig.teleport(),
oldConfig.ui(), oldConfig.ui(),
oldConfig.worldGuard() oldConfig.worldGuard(),
oldConfig.playerSpawn()
); );
// Update and save // Update and save
plugin.getMines().updateMineConfig(mine.getName(), newMineConfig); try {
mine.save(); plugin.getMines().updateMineConfig(mine.getName(), newMineConfig);
} catch (Exception e) {
throw new IllegalStateException("Failed to update mine regions", e);
}
} }
} }

View file

@ -16,16 +16,14 @@ import java.util.stream.Collectors;
*/ */
public class LoMinesTabCompleter implements TabCompleter { public class LoMinesTabCompleter implements TabCompleter {
private final LoMinesPlugin plugin;
private static final List<String> ALL_COMMANDS = List.of( private static final List<String> ALL_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "wand", "group", "create", "delete", "reset", "reload", "list", "wand", "group",
"stats", "top", "maskscan", "edit", "setteleport", "setspawn", "stats", "top", "maskscan", "edit", "setteleport", "setspawn",
"clearspawn", "info", "tp", "copy", "regions", "addregion", "removeregion", "help" "clearspawn", "info", "tp", "copy", "regions", "addregion", "removeregion", "help"
); );
private static final List<String> BOOLEAN_VALUES = List.of("true", "false"); private static final List<String> BOOLEAN_VALUES = List.of("true", "false");
private static final List<String> TOP_LIMITS = List.of("5", "10", "15", "20", "25", "50"); private static final List<String> TOP_LIMITS = List.of("5", "10", "15", "20", "25", "50");
private final LoMinesPlugin plugin;
public LoMinesTabCompleter(LoMinesPlugin plugin) { public LoMinesTabCompleter(LoMinesPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -49,9 +47,9 @@ public class LoMinesTabCompleter implements TabCompleter {
private boolean isLoMinesCommand(String name) { private boolean isLoMinesCommand(String name) {
return name.equalsIgnoreCase("lm") || return name.equalsIgnoreCase("lm") ||
name.equalsIgnoreCase("lomines") || name.equalsIgnoreCase("lomines") ||
name.equalsIgnoreCase("mine") || name.equalsIgnoreCase("mine") ||
name.equalsIgnoreCase("mines"); name.equalsIgnoreCase("mines");
} }
private List<String> completeSubcommands(CommandSender sender, String partial) { private List<String> completeSubcommands(CommandSender sender, String partial) {
@ -68,8 +66,7 @@ public class LoMinesTabCompleter implements TabCompleter {
return switch (subcommand) { return switch (subcommand) {
case "create" -> completeCreate(partial); case "create" -> completeCreate(partial);
case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info", case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info",
"regions", "addregion" -> "regions", "addregion" -> filterStartsWith(getMineNames(), partial);
filterStartsWith(getMineNames(), partial);
case "reset" -> completeReset(partial); case "reset" -> completeReset(partial);
case "stats" -> completeStats(sender, partial); case "stats" -> completeStats(sender, partial);
case "top" -> completeTopFirstArg(partial); case "top" -> completeTopFirstArg(partial);

View file

@ -3,8 +3,8 @@ package dev.loki.lomines.command.player;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand; import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location; import org.bukkit.Location;

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.core.mine; package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.service.MineFileManager;
import dev.loki.lomines.block.BlockSetter; import dev.loki.lomines.block.BlockSetter;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
@ -18,7 +17,9 @@ import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.*; import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
/** /**
@ -120,8 +121,10 @@ public final class Mine {
} }
BlockKey firstKey = weights.keySet().iterator().next(); BlockKey firstKey = weights.keySet().iterator().next();
return switch (firstKey) { return switch (firstKey) {
case BlockKey.Oraxen oraxen -> throw new IllegalArgumentException("Oraxen integration is currently disabled"); case BlockKey.Oraxen oraxen ->
case BlockKey.ItemsAdder itemsAdder -> throw new IllegalArgumentException("ItemsAdder integration is currently disabled"); 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); case BlockKey.Vanilla vanilla -> new dev.loki.lomines.block.BukkitBlockSetter(weights, plugin);
}; };
} }

View file

@ -1,9 +1,9 @@
package dev.loki.lomines.core.common; package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin; 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.MineFileManager;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.core.service.MineRepository;
import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import java.io.IOException; import java.io.IOException;
@ -14,19 +14,19 @@ import java.util.stream.Stream;
/** /**
* Loads mine configurations from disk. * Loads mine configurations from disk.
*/ */
final class MineLoader { public final class MineLoader {
private final LoMinesPlugin plugin; private final LoMinesPlugin plugin;
private final MineFileManager fileManager; private final MineFileManager fileManager;
private final MineRepository repository; private final MineRepository repository;
MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) { public MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
this.plugin = plugin; this.plugin = plugin;
this.fileManager = fileManager; this.fileManager = fileManager;
this.repository = repository; this.repository = repository;
} }
void loadAll() throws IOException { public void loadAll() throws IOException {
repository.clear(); repository.clear();
Path minesFolder = fileManager.getMinesFolder(); Path minesFolder = fileManager.getMinesFolder();

View file

@ -52,7 +52,7 @@ public final class MineTicker {
private void tickMine(Mine mine) { private void tickMine(Mine mine) {
int currentTicks = mine.getTicksAtomic().incrementAndGet(); int currentTicks = mine.getTicksAtomic().incrementAndGet();
if (currentTicks >= mine.getConfig().getResetTicks()) { if (currentTicks >= mine.getConfig().reset().intervalTicks()) {
mine.getTicksAtomic().set(0); mine.getTicksAtomic().set(0);
mine.reset(false); mine.reset(false);
} }

View file

@ -1,11 +1,10 @@
package dev.loki.lomines.core.mine; package dev.loki.lomines.core.mine;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.common.MineLoader;
import dev.loki.lomines.core.service.MaskScanService; import dev.loki.lomines.core.service.MaskScanService;
import dev.loki.lomines.core.service.MineFileManager; import dev.loki.lomines.core.service.MineFileManager;
import dev.loki.lomines.core.service.MineRepository; import dev.loki.lomines.core.service.MineRepository;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.integration.worldguard.WorldGuardRegionService; import dev.loki.lomines.integration.worldguard.WorldGuardRegionService;
import org.bukkit.Location; import org.bukkit.Location;
@ -122,7 +121,11 @@ public final class Mines {
} }
// Save new config to file // Save new config to file
fileManager.saveConfig(name, newConfig); try {
fileManager.saveConfig(name, newConfig);
} catch (ConfigLoader.ConfigLoadException e) {
throw new IOException("Failed to save mine config: " + e.getMessage(), e);
}
// Reload to apply changes // Reload to apply changes
try { try {

View file

@ -1,7 +1,7 @@
package dev.loki.lomines.core.service; package dev.loki.lomines.core.service;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.util.ValidationUtils; import dev.loki.lomines.util.ValidationUtils;
import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.Cuboid;
@ -57,8 +57,8 @@ public final class MaskScanService {
} }
private Material parseMarkerMaterial(BlockKey key) { private Material parseMarkerMaterial(BlockKey key) {
if (key instanceof BlockKey.Vanilla vanilla) { if (key instanceof BlockKey.Vanilla(Material material)) {
return vanilla.material(); return material;
} }
// For custom blocks (Oraxen/ItemsAdder), we need to get the underlying 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 // This would need integration with those plugins to get the actual material

View file

@ -1,6 +1,6 @@
package dev.loki.lomines.core.service; package dev.loki.lomines.core.service;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.block.BlockConfig; import dev.loki.lomines.data.config.block.BlockConfig;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
@ -82,7 +82,11 @@ public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
.build(); .build();
// Save using new loader // Save using new loader
configLoader.save(config); try {
configLoader.save(config);
} catch (ConfigLoader.ConfigLoadException e) {
throw new IOException("Failed to save default mine config: " + e.getMessage(), e);
}
} }
/** /**
@ -147,7 +151,7 @@ public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
/** /**
* Saves a mine configuration to file by name. * Saves a mine configuration to file by name.
* *
* @param name the mine name * @param name the mine name
* @param config the configuration to save * @param config the configuration to save
* @throws ConfigLoader.ConfigLoadException if saving fails * @throws ConfigLoader.ConfigLoadException if saving fails
*/ */

View file

@ -2,7 +2,7 @@ package dev.loki.lomines.core.service;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.loader.common.ConfigLoader; import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import org.bukkit.Location; import org.bukkit.Location;

View file

@ -5,8 +5,8 @@ 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.entity.TeleportConfigLoader;
import dev.loki.lomines.data.config.loader.region.RegionConfigLoader; 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.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.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.loader.system.UIConfigLoader;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@ -156,6 +156,7 @@ public final class ConfigLoader {
public ConfigLoadException(String message) { public ConfigLoadException(String message) {
super(message); super(message);
} }
public ConfigLoadException(String message, Throwable cause) { public ConfigLoadException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View file

@ -49,20 +49,6 @@ public record MineConfig(
} }
} }
/**
* 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. * Creates builder for fluent construction.
*/ */
@ -87,6 +73,20 @@ public record MineConfig(
); );
} }
/**
* 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();
}
/** /**
* Returns the spawn location for stuck players. * Returns the spawn location for stuck players.
* If playerSpawn is not set, falls back to teleport location. * If playerSpawn is not set, falls back to teleport location.

View file

@ -1,5 +1,7 @@
package dev.loki.lomines.data.config.block; package dev.loki.lomines.data.config.block;
import org.bukkit.Material;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

View file

@ -10,12 +10,6 @@ import java.util.Objects;
*/ */
public sealed interface BlockKey permits BlockKey.Vanilla, BlockKey.Oraxen, BlockKey.ItemsAdder { 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. * Deserializes a string to appropriate BlockKey type.
* *
@ -43,6 +37,12 @@ public sealed interface BlockKey permits BlockKey.Vanilla, BlockKey.Oraxen, Bloc
return new Vanilla(material); 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 --- // --- Implementations ---
record Vanilla(Material material) implements BlockKey { record Vanilla(Material material) implements BlockKey {

View file

@ -80,9 +80,11 @@ public final class RegionConfigLoader {
public void save(YamlConfiguration yaml, RegionConfig region) { public void save(YamlConfiguration yaml, RegionConfig region) {
int i = 1; int i = 1;
for (var cuboid : region.regions()) { for (var cuboid : region.regions()) {
yaml.set("region.selection." + i, LocationParser.format(cuboid.getMin())); Location min = new Location(cuboid.getWorld(), cuboid.getMinX(), cuboid.getMinY(), cuboid.getMinZ());
yaml.set("region.selection." + i, LocationParser.format(min));
i++; i++;
yaml.set("region.selection." + i, LocationParser.format(cuboid.getMax())); Location max = new Location(cuboid.getWorld(), cuboid.getMaxX(), cuboid.getMaxY(), cuboid.getMaxZ());
yaml.set("region.selection." + i, LocationParser.format(max));
i++; i++;
} }
} }

View file

@ -1,14 +1,10 @@
package dev.loki.lomines.data.config.loader.region; package dev.loki.lomines.data.config.loader.region;
import dev.loki.lomines.data.config.ConfigLoader;
import dev.loki.lomines.integration.worldguard.WorldGuardConfig; import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Loader for WorldGuard configuration section. * Loader for WorldGuard configuration section.

View file

@ -36,7 +36,8 @@ public final class RewardConfigLoader {
} }
private RewardEntry parseRewardEntry(Map<?, ?> map) { private RewardEntry parseRewardEntry(Map<?, ?> map) {
double chance = ((Number) map.getOrDefault("chance", 0)).doubleValue(); Object chanceObj = map.get("chance");
double chance = chanceObj instanceof Number n ? n.doubleValue() : 0.0;
List<BlockKey> blocks = new ArrayList<>(); List<BlockKey> blocks = new ArrayList<>();
List<?> blockList = (List<?>) map.get("blocks"); List<?> blockList = (List<?>) map.get("blocks");
@ -56,9 +57,17 @@ public final class RewardConfigLoader {
} }
} }
@SuppressWarnings("unchecked") List<String> commands = new ArrayList<>();
List<String> commands = (List<String>) map.getOrDefault("commands", List.of()); Object commandsObj = map.get("commands");
boolean preventDrops = (Boolean) map.getOrDefault("prevent-drops", false); if (commandsObj instanceof List<?> list) {
for (Object obj : list) {
if (obj != null) {
commands.add(obj.toString());
}
}
}
Object preventDropsObj = map.get("prevent-drops");
boolean preventDrops = preventDropsObj instanceof Boolean b && b;
return new RewardEntry(blocks, chance, items, commands, preventDrops); return new RewardEntry(blocks, chance, items, commands, preventDrops);
} }

View file

@ -3,8 +3,6 @@ package dev.loki.lomines.data.config.loader.system;
import dev.loki.lomines.data.config.reset.ResetConfig; import dev.loki.lomines.data.config.reset.ResetConfig;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import java.util.List;
/** /**
* Loader for reset configuration section. * Loader for reset configuration section.
*/ */

View file

@ -1,7 +1,9 @@
package dev.loki.lomines.data.config.loader.system; package dev.loki.lomines.data.config.loader.system;
import dev.loki.lomines.data.config.ui.UIConfig; import dev.loki.lomines.data.config.ui.UIConfig;
import dev.loki.lomines.data.config.ui.HologramConfig;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import java.util.List;
/** /**
* Loader for UI configuration section. * Loader for UI configuration section.
@ -25,10 +27,21 @@ public final class UIConfigLoader {
actionBarEnabled, actionBarEnabled,
yaml.getString("ui.actionbar.format", UIConfig.DEFAULT_ACTIONBAR_FORMAT), yaml.getString("ui.actionbar.format", UIConfig.DEFAULT_ACTIONBAR_FORMAT),
yaml.getDouble("ui.actionbar.range", 50.0), yaml.getDouble("ui.actionbar.range", 50.0),
yaml.getString("ui.timer-format", UIConfig.DEFAULT_TIMER_FORMAT) yaml.getString("ui.timer-format", UIConfig.DEFAULT_TIMER_FORMAT),
parseHologram(yaml)
); );
} }
private HologramConfig parseHologram(YamlConfiguration yaml) {
boolean enabled = yaml.getBoolean("ui.hologram.enabled", false);
List<String> 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. * Saves UI configuration to YAML.
*/ */
@ -37,5 +50,8 @@ public final class UIConfigLoader {
yaml.set("ui.actionbar.format", ui.actionBarFormat()); yaml.set("ui.actionbar.format", ui.actionBarFormat());
yaml.set("ui.actionbar.range", ui.actionBarRange()); yaml.set("ui.actionbar.range", ui.actionBarRange());
yaml.set("ui.timer-format", ui.timerFormat()); 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());
} }
} }

View file

@ -0,0 +1,15 @@
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);
}
}

View file

@ -1,9 +1,7 @@
package dev.loki.lomines.data.config.reset; package dev.loki.lomines.data.config.reset;
import java.time.Duration; import java.time.Duration;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Configuration for mine reset behavior. * Configuration for mine reset behavior.
@ -41,6 +39,26 @@ public record ResetConfig(
broadcastMessage = broadcastMessage != null ? broadcastMessage : ""; broadcastMessage = broadcastMessage != null ? broadcastMessage : "";
} }
/**
* 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();
}
/** /**
* Interval in ticks (20 ticks = 1 second). * Interval in ticks (20 ticks = 1 second).
*/ */
@ -69,26 +87,6 @@ public record ResetConfig(
return percentEnabled && percentTrigger > 0; 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 { public static class Builder {
private Duration interval = DEFAULT_INTERVAL; private Duration interval = DEFAULT_INTERVAL;
private double percentTrigger = DEFAULT_PERCENT_TRIGGER; private double percentTrigger = DEFAULT_PERCENT_TRIGGER;
@ -96,6 +94,26 @@ public record ResetConfig(
private List<String> commands = List.of(); private List<String> commands = List.of();
private String broadcastMessage = ""; private String 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;
}
}
public Builder interval(Duration interval) { public Builder interval(Duration interval) {
this.interval = interval; this.interval = interval;
return this; return this;
@ -134,25 +152,5 @@ public record ResetConfig(
public ResetConfig build() { public ResetConfig build() {
return new ResetConfig(interval, percentTrigger, percentEnabled, commands, broadcastMessage); 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;
}
}
} }
} }

View file

@ -4,9 +4,7 @@ import dev.loki.lomines.data.config.block.BlockKey;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
@ -18,6 +16,20 @@ public record RewardConfig(List<RewardEntry> entries) {
entries = entries != null ? List.copyOf(entries) : List.of(); 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. * Returns rewards applicable for the given block.
*/ */
@ -34,20 +46,6 @@ public record RewardConfig(List<RewardEntry> entries) {
return !entries.isEmpty(); 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 --- // --- Reward Entry ---
public record RewardEntry( public record RewardEntry(

View file

@ -12,10 +12,7 @@ import java.util.Optional;
* Supports multiple spawn points for a single mine. * Supports multiple spawn points for a single mine.
* Used to teleport players out when they are suffocating after mine reset. * Used to teleport players out when they are suffocating after mine reset.
*/ */
public final class PlayerSpawnConfig { public record PlayerSpawnConfig(boolean enabled, List<Location> locations) {
private final boolean enabled;
private final List<Location> locations;
public PlayerSpawnConfig(boolean enabled, List<Location> locations) { public PlayerSpawnConfig(boolean enabled, List<Location> locations) {
this.enabled = enabled; this.enabled = enabled;
@ -35,34 +32,6 @@ public final class PlayerSpawnConfig {
} }
} }
/**
* Returns the primary spawn location (first in list) if enabled.
*/
public Optional<Location> getLocation() {
return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
}
/**
* Returns all spawn locations.
*/
public List<Location> getLocations() {
return locations;
}
/**
* Returns true if spawn is enabled and has at least one location.
*/
public boolean enabled() {
return enabled && !locations.isEmpty();
}
/**
* Returns the number of configured spawn locations.
*/
public int count() {
return locations.size();
}
/** /**
* Disabled spawn config (default). * Disabled spawn config (default).
*/ */
@ -84,6 +53,36 @@ public final class PlayerSpawnConfig {
return new PlayerSpawnConfig(true, locations); return new PlayerSpawnConfig(true, locations);
} }
/**
* Returns the primary spawn location (first in list) if enabled.
*/
public Optional<Location> getLocation() {
return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
}
/**
* Returns all spawn locations.
*/
@Override
public List<Location> 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. * Creates a new config with an additional location.
*/ */

View file

@ -3,7 +3,6 @@ package dev.loki.lomines.data.config.teleport;
import org.bukkit.Location; import org.bukkit.Location;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -12,10 +11,7 @@ import java.util.Optional;
* Configuration for mine teleportation on reset. * Configuration for mine teleportation on reset.
* Supports multiple teleport locations for a single mine. * Supports multiple teleport locations for a single mine.
*/ */
public final class TeleportConfig { public record TeleportConfig(boolean enabled, List<Location> locations) {
private final boolean enabled;
private final List<Location> locations;
public TeleportConfig(boolean enabled, List<Location> locations) { public TeleportConfig(boolean enabled, List<Location> locations) {
this.enabled = enabled; this.enabled = enabled;
@ -35,34 +31,6 @@ public final class TeleportConfig {
} }
} }
/**
* Returns the primary teleport location (first in list) if enabled.
*/
public Optional<Location> getLocation() {
return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
}
/**
* Returns all teleport locations.
*/
public List<Location> getLocations() {
return locations;
}
/**
* Returns true if teleport is enabled and has at least one location.
*/
public boolean enabled() {
return enabled && !locations.isEmpty();
}
/**
* Returns the number of configured teleport locations.
*/
public int count() {
return locations.size();
}
/** /**
* Disabled teleport config. * Disabled teleport config.
*/ */
@ -84,6 +52,36 @@ public final class TeleportConfig {
return new TeleportConfig(true, locations); return new TeleportConfig(true, locations);
} }
/**
* Returns the primary teleport location (first in list) if enabled.
*/
public Optional<Location> getLocation() {
return locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
}
/**
* Returns all teleport locations.
*/
@Override
public List<Location> 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. * Creates a new config with an additional location.
*/ */

View file

@ -6,8 +6,8 @@ import java.util.List;
* Configuration for holograms displayed above mines. * Configuration for holograms displayed above mines.
* *
* @param enabled whether holograms are enabled * @param enabled whether holograms are enabled
* @param format list of lines with placeholders {mine}, {percent}, {bar}, {time} * @param format list of lines with placeholders {mine}, {percent}, {bar}, {time}
* @param height offset above the mine region (in blocks) * @param height offset above the mine region (in blocks)
*/ */
public record HologramConfig( public record HologramConfig(
boolean enabled, boolean enabled,

View file

@ -3,8 +3,6 @@ package dev.loki.lomines.data.config.ui;
import dev.loki.lomines.util.format.ColorUtils; import dev.loki.lomines.util.format.ColorUtils;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.Objects;
/** /**
* Configuration for UI elements (action bar, messages). * Configuration for UI elements (action bar, messages).
*/ */
@ -16,14 +14,10 @@ public record UIConfig(
HologramConfig hologram HologramConfig hologram
) { ) {
public UIConfig {
hologram = hologram != null ? hologram : HologramConfig.disabled();
}
public static final String DEFAULT_ACTIONBAR_FORMAT = "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})"; public static final String DEFAULT_ACTIONBAR_FORMAT = "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})";
public static final String DEFAULT_TIMER_FORMAT = "mm:ss"; public static final String DEFAULT_TIMER_FORMAT = "mm:ss";
public UIConfig { public UIConfig {
hologram = hologram != null ? hologram : HologramConfig.disabled();
actionBarFormat = actionBarFormat != null && !actionBarFormat.isBlank() actionBarFormat = actionBarFormat != null && !actionBarFormat.isBlank()
? actionBarFormat ? actionBarFormat
: DEFAULT_ACTIONBAR_FORMAT; : DEFAULT_ACTIONBAR_FORMAT;
@ -33,6 +27,20 @@ public record UIConfig(
actionBarRange = Math.max(1, actionBarRange); 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. * Parses the action bar format with placeholders.
* Placeholders: {mine}, {percent}, {time}, {blocks}, {total} * Placeholders: {mine}, {percent}, {time}, {blocks}, {total}
@ -71,18 +79,4 @@ public record UIConfig(
public double actionBarRangeSquared() { public double actionBarRangeSquared() {
return actionBarRange * actionBarRange; 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());
}
} }

View file

@ -13,11 +13,12 @@ import java.util.List;
/** /**
* Factory for creating ItemStacks with name and lore. * Factory for creating ItemStacks with name and lore.
*/ */
final class ItemStackFactory { public final class ItemStackFactory {
private ItemStackFactory() {} private ItemStackFactory() {
}
static ItemStack create(Material mat, String name, String... loreLines) { public static ItemStack create(Material mat, String name, String... loreLines) {
ItemStack stack = new ItemStack(mat); ItemStack stack = new ItemStack(mat);
ItemMeta meta = stack.getItemMeta(); ItemMeta meta = stack.getItemMeta();
if (meta != null) { if (meta != null) {
@ -32,17 +33,17 @@ final class ItemStackFactory {
return stack; return stack;
} }
static String loc(Location l) { public static String loc(Location l) {
if (l == null || l.getWorld() == null) return "§7не задана"; if (l == null || l.getWorld() == null) return "§7не задана";
return String.format("§7%.0f§8/§7%.0f§8/§7%.0f", l.getX(), l.getY(), l.getZ()); return String.format("§7%.0f§8/§7%.0f§8/§7%.0f", l.getX(), l.getY(), l.getZ());
} }
static String trunc(String s, int max) { public static String trunc(String s, int max) {
if (s == null || s.isEmpty()) return "§7нет"; if (s == null || s.isEmpty()) return "§7нет";
return s.length() > max ? s.substring(0, max - 3) + "..." : s; return s.length() > max ? s.substring(0, max - 3) + "..." : s;
} }
static ItemStack filler() { public static ItemStack filler() {
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE); ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
ItemMeta meta = pane.getItemMeta(); ItemMeta meta = pane.getItemMeta();
if (meta != null) { if (meta != null) {

View file

@ -28,8 +28,8 @@ public final class ConfirmDeleteGui {
/** /**
* Opens the confirmation GUI for deleting a mine. * Opens the confirmation GUI for deleting a mine.
* *
* @param plugin the plugin instance * @param plugin the plugin instance
* @param player the player to open for * @param player the player to open for
* @param mineName the name of the mine to delete * @param mineName the name of the mine to delete
*/ */
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {

View file

@ -20,7 +20,8 @@ public final class GroupCreateGui {
private static final int SIZE = 54; private static final int SIZE = 54;
private static final int[] MINE_SLOTS = {10, 11, 12, 19, 20, 21, 28, 29, 30}; private static final int[] MINE_SLOTS = {10, 11, 12, 19, 20, 21, 28, 29, 30};
private GroupCreateGui() {} private GroupCreateGui() {
}
public static void open(LoMinesPlugin plugin, Player player) { public static void open(LoMinesPlugin plugin, Player player) {
GroupCreateGuiHolder holder = new GroupCreateGuiHolder(player.getUniqueId()); GroupCreateGuiHolder holder = new GroupCreateGuiHolder(player.getUniqueId());

View file

@ -15,7 +15,8 @@ import java.util.List;
*/ */
final class GroupCreateItems { final class GroupCreateItems {
private GroupCreateItems() {} private GroupCreateItems() {
}
static ItemStack filler() { static ItemStack filler() {
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE); ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);

View file

@ -9,6 +9,7 @@ import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import dev.loki.lomines.gui.mine.holder.BlockMaterialSelectionGuiHolder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -38,7 +39,8 @@ public final class BlockMaterialSelectionGui {
AVAILABLE_MATERIALS.sort((a, b) -> a.name().compareTo(b.name())); AVAILABLE_MATERIALS.sort((a, b) -> a.name().compareTo(b.name()));
} }
private BlockMaterialSelectionGui() {} private BlockMaterialSelectionGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {
open(plugin, player, mineName, 0); open(plugin, player, mineName, 0);
@ -46,9 +48,9 @@ public final class BlockMaterialSelectionGui {
public static void open(LoMinesPlugin plugin, Player player, String mineName, int page) { public static void open(LoMinesPlugin plugin, Player player, String mineName, int page) {
BlockMaterialSelectionGuiHolder holder = new BlockMaterialSelectionGuiHolder( BlockMaterialSelectionGuiHolder holder = new BlockMaterialSelectionGuiHolder(
player.getUniqueId(), mineName, page); player.getUniqueId(), mineName, page);
Inventory inv = Bukkit.createInventory(holder, Inventory inv = Bukkit.createInventory(holder, SIZE,
Component.text(TITLE + " (стр. " + (page + 1) + ")", NamedTextColor.DARK_GREEN)); TITLE + " (стр. " + (page + 1) + ")");
holder.setInventory(inv); holder.setInventory(inv);
fill(inv, page); fill(inv, page);
player.openInventory(inv); player.openInventory(inv);
@ -85,11 +87,11 @@ public final class BlockMaterialSelectionGui {
private static ItemStack materialItem(Material material) { private static ItemStack materialItem(Material material) {
String name = formatMaterialName(material); String name = formatMaterialName(material);
return ItemStackFactory.create(material, "§a§l" + name, return ItemStackFactory.create(material, "§a§l" + name,
"§8───────────────", "§8───────────────",
"§7Нажмите для добавления", "§7Нажмите для добавления",
"§7в конфигурацию шахты", "§7в конфигурацию шахты",
"", "",
"§8ID: §7" + material.name().toLowerCase() "§8ID: §7" + material.name().toLowerCase()
); );
} }
@ -99,8 +101,8 @@ public final class BlockMaterialSelectionGui {
for (String word : name.split(" ")) { for (String word : name.split(" ")) {
if (!word.isEmpty()) { if (!word.isEmpty()) {
result.append(Character.toUpperCase(word.charAt(0))) result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1)) .append(word.substring(1))
.append(" "); .append(" ");
} }
} }
return result.toString().trim(); return result.toString().trim();
@ -108,29 +110,29 @@ public final class BlockMaterialSelectionGui {
private static ItemStack prevPageItem() { private static ItemStack prevPageItem() {
return ItemStackFactory.create(Material.ARROW, "§e§l← Предыдущая", return ItemStackFactory.create(Material.ARROW, "§e§l← Предыдущая",
"§8───────────────", "§8───────────────",
"§7Нажмите для перехода", "§7Нажмите для перехода",
"§7на предыдущую страницу" "§7на предыдущую страницу"
); );
} }
private static ItemStack nextPageItem() { private static ItemStack nextPageItem() {
return ItemStackFactory.create(Material.ARROW, "§e§lСледующая →", return ItemStackFactory.create(Material.ARROW, "§e§lСледующая →",
"§8───────────────", "§8───────────────",
"§7Нажмите для перехода", "§7Нажмите для перехода",
"§7на следующую страницу" "§7на следующую страницу"
); );
} }
private static ItemStack backItem() { private static ItemStack backItem() {
return ItemStackFactory.create(Material.BARRIER, "§c§lОтмена", return ItemStackFactory.create(Material.BARRIER, "§c§lОтмена",
"§8───────────────", "§8───────────────",
"§7Вернуться без добавления" "§7Вернуться без добавления"
); );
} }
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot,
String mineName, int page, boolean leftClick) { String mineName, int page, boolean leftClick) {
if (rawSlot < 0 || rawSlot >= SIZE) return false; if (rawSlot < 0 || rawSlot >= SIZE) return false;
if (rawSlot == SLOT_BACK) { if (rawSlot == SLOT_BACK) {

View file

@ -6,6 +6,7 @@ import dev.loki.lomines.data.config.block.BlockConfig;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.gui.common.ItemStackFactory; import dev.loki.lomines.gui.common.ItemStackFactory;
import dev.loki.lomines.gui.mine.holder.BlocksGuiHolder; import dev.loki.lomines.gui.mine.holder.BlocksGuiHolder;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -26,7 +27,8 @@ public final class BlocksGui {
private static final int SLOT_BACK = 49; private static final int SLOT_BACK = 49;
private static final int SLOT_ADD_BLOCK = 52; private static final int SLOT_ADD_BLOCK = 52;
private BlocksGui() {} private BlocksGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null); Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -74,8 +76,8 @@ public final class BlocksGui {
} }
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot,
String mineName, boolean leftClick, boolean shiftClick, String mineName, boolean leftClick, boolean shiftClick,
boolean rightClick, BlockKey clickedBlock) { boolean rightClick, BlockKey clickedBlock) {
if (rawSlot < 0 || rawSlot >= SIZE) return false; if (rawSlot < 0 || rawSlot >= SIZE) return false;
if (rawSlot == SLOT_BACK) { if (rawSlot == SLOT_BACK) {
@ -125,7 +127,7 @@ public final class BlocksGui {
} else { } else {
weights.put(blockKey, newWeight / 100.0); weights.put(blockKey, newWeight / 100.0);
player.sendMessage(Component.text("§aВес §f" + blockKey.serialize() + " §aизменен: §f" + player.sendMessage(Component.text("§aВес §f" + blockKey.serialize() + " §aизменен: §f" +
String.format("%.1f%%", newWeight))); String.format("%.1f%%", newWeight)));
} }
refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin);

View file

@ -10,7 +10,8 @@ import org.bukkit.inventory.ItemStack;
*/ */
final class BlocksGuiItems { final class BlocksGuiItems {
private BlocksGuiItems() {} private BlocksGuiItems() {
}
static ItemStack blockItem(BlockKey key, double weight) { static ItemStack blockItem(BlockKey key, double weight) {
Material material = getMaterialForKey(key); Material material = getMaterialForKey(key);
@ -18,40 +19,40 @@ final class BlocksGuiItems {
double percent = weight * 100.0; double percent = weight * 100.0;
return ItemStackFactory.create(material, "§a§l" + name, return ItemStackFactory.create(material, "§a§l" + name,
"§8───────────────", "§8───────────────",
"§7Вес: §f" + String.format("%.1f%%", percent), "§7Вес: §f" + String.format("%.1f%%", percent),
"", "",
"§e▸ ЛКМ §7+5%", "§e▸ ЛКМ §7+5%",
"§e▸ ПКМ §7-5%", "§e▸ ПКМ §7-5%",
"§e▸ Shift+ЛКМ §7+1%", "§e▸ Shift+ЛКМ §7+1%",
"§e▸ Shift+ПКМ §7-1% §8(удалить если 0%)", "§e▸ Shift+ПКМ §7-1% §8(удалить если 0%)",
"", "",
"§8ID: §7" + key.serialize() "§8ID: §7" + key.serialize()
); );
} }
static ItemStack addBlockItem() { static ItemStack addBlockItem() {
return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить блок", return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить блок",
"§8───────────────", "§8───────────────",
"§7Добавить новый блок", "§7Добавить новый блок",
"§7в конфигурацию", "§7в конфигурацию",
"", "",
"§e▸ Нажмите для выбора материала" "§e▸ Нажмите для выбора материала"
); );
} }
static ItemStack backItem() { static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lНазад", return ItemStackFactory.create(Material.ARROW, "§c§lНазад",
"§8───────────────", "§8───────────────",
"§7Вернуться в редактор шахты", "§7Вернуться в редактор шахты",
"", "",
"§e▸ Нажмите для возврата" "§e▸ Нажмите для возврата"
); );
} }
static Material getMaterialForKey(BlockKey key) { static Material getMaterialForKey(BlockKey key) {
if (key instanceof BlockKey.Vanilla vanilla) { if (key instanceof BlockKey.Vanilla(Material material)) {
return vanilla.material(); return material;
} }
if (key instanceof BlockKey.Oraxen) { if (key instanceof BlockKey.Oraxen) {
return Material.NETHER_STAR; return Material.NETHER_STAR;
@ -80,8 +81,8 @@ final class BlocksGuiItems {
for (String word : input.split(" ")) { for (String word : input.split(" ")) {
if (!word.isEmpty()) { if (!word.isEmpty()) {
result.append(Character.toUpperCase(word.charAt(0))) result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1)) .append(word.substring(1))
.append(" "); .append(" ");
} }
} }
return result.toString().trim(); return result.toString().trim();

View file

@ -24,7 +24,8 @@ public final class ResetGui {
private static final int SLOT_MESSAGE = 14; private static final int SLOT_MESSAGE = 14;
private static final int SLOT_COMMANDS = 16; private static final int SLOT_COMMANDS = 16;
private ResetGui() {} private ResetGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null); Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -64,8 +65,8 @@ public final class ResetGui {
} }
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot,
String mineName, boolean leftClick, boolean rightClick, String mineName, boolean leftClick, boolean rightClick,
boolean shiftClick) { boolean shiftClick) {
if (rawSlot < 0 || rawSlot >= SIZE) return false; if (rawSlot < 0 || rawSlot >= SIZE) return false;
if (rawSlot == SLOT_BACK) { if (rawSlot == SLOT_BACK) {
@ -122,7 +123,7 @@ public final class ResetGui {
} }
private static void togglePercentTrigger(LoMinesPlugin plugin, Player player, private static void togglePercentTrigger(LoMinesPlugin plugin, Player player,
String mineName, boolean leftClick, boolean rightClick) { String mineName, boolean leftClick, boolean rightClick) {
if (leftClick) { if (leftClick) {
player.sendMessage(Component.text("§aТриггер по % переключен.")); player.sendMessage(Component.text("§aТриггер по % переключен."));
} else if (rightClick) { } else if (rightClick) {

View file

@ -10,17 +10,18 @@ import org.bukkit.inventory.ItemStack;
*/ */
final class ResetGuiItems { final class ResetGuiItems {
private ResetGuiItems() {} private ResetGuiItems() {
}
static ItemStack intervalItem(ResetConfig config) { static ItemStack intervalItem(ResetConfig config) {
return ItemStackFactory.create(Material.CLOCK, "§a§lИнтервал сброса", return ItemStackFactory.create(Material.CLOCK, "§a§lИнтервал сброса",
"§8───────────────", "§8───────────────",
"§7Текущее: §f" + config.intervalDisplay(), "§7Текущее: §f" + config.intervalDisplay(),
"", "",
"§e▸ ЛКМ §7+1 минута", "§e▸ ЛКМ §7+1 минута",
"§e▸ ПКМ §7-1 минута", "§e▸ ПКМ §7-1 минута",
"§e▸ Shift+ЛКМ §7+10 минут", "§e▸ Shift+ЛКМ §7+10 минут",
"§e▸ Shift+ПКМ §7-10 минут" "§e▸ Shift+ПКМ §7-10 минут"
); );
} }
@ -29,54 +30,54 @@ final class ResetGuiItems {
String value = config.isPercentTriggerEnabled() ? config.percentTrigger() + "%" : "—"; String value = config.isPercentTriggerEnabled() ? config.percentTrigger() + "%" : "—";
return ItemStackFactory.create(Material.COMPARATOR, "§a§lТриггер по %", return ItemStackFactory.create(Material.COMPARATOR, "§a§lТриггер по %",
"§8───────────────", "§8───────────────",
"§7Статус: " + status, "§7Статус: " + status,
"§7Значение: §f" + value, "§7Значение: §f" + value,
"", "",
"§7Сброс шахты когда", "§7Сброс шахты когда",
"§7добыто указанный %", "§7добыто указанный %",
"", "",
"§e▸ ЛКМ §7вкл/выкл", "§e▸ ЛКМ §7вкл/выкл",
"§e▸ ПКМ §7изменить %" "§e▸ ПКМ §7изменить %"
); );
} }
static ItemStack messageItem(ResetConfig config) { static ItemStack messageItem(ResetConfig config) {
String message = config.broadcastMessage().isEmpty() ? "§7(не задано)" : "§f" + config.broadcastMessage(); String message = config.broadcastMessage().isEmpty() ? "§7(не задано)" : "§f" + config.broadcastMessage();
return ItemStackFactory.create(Material.BOOK, "§a§lСообщение при сбросе", return ItemStackFactory.create(Material.BOOK, "§a§lСообщение при сбросе",
"§8───────────────", "§8───────────────",
"§7Текущее:", "§7Текущее:",
" " + ItemStackFactory.trunc(message, 30), " " + ItemStackFactory.trunc(message, 30),
"", "",
"§7Поддерживает MiniMessage", "§7Поддерживает MiniMessage",
"§8{mine} §7— название шахты", "§8{mine} §7— название шахты",
"§8{player} §7— игрок вызвавший сброс", "§8{player} §7— игрок вызвавший сброс",
"", "",
"§e▸ ЛКМ §7изменить сообщение", "§e▸ ЛКМ §7изменить сообщение",
"§e▸ ПКМ §7очистить" "§e▸ ПКМ §7очистить"
); );
} }
static ItemStack commandsItem(ResetConfig config) { static ItemStack commandsItem(ResetConfig config) {
int count = config.commands().size(); int count = config.commands().size();
return ItemStackFactory.create(Material.COMMAND_BLOCK, "§a§lКоманды при сбросе", return ItemStackFactory.create(Material.COMMAND_BLOCK, "§a§lКоманды при сбросе",
"§8───────────────", "§8───────────────",
"§7Количество: §f" + count, "§7Количество: §f" + count,
"", "",
"§7Плейсхолдеры:", "§7Плейсхолдеры:",
"§8{mine} §7— название шахты", "§8{mine} §7— название шахты",
"§8{world} §7— мир", "§8{world} §7— мир",
"", "",
"§e▸ ЛКМ §7редактировать список" "§e▸ ЛКМ §7редактировать список"
); );
} }
static ItemStack backItem() { static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lНазад", return ItemStackFactory.create(Material.ARROW, "§c§lНазад",
"§8───────────────", "§8───────────────",
"§7Вернуться в редактор шахты", "§7Вернуться в редактор шахты",
"", "",
"§e▸ Нажмите для возврата" "§e▸ Нажмите для возврата"
); );
} }
} }

View file

@ -2,8 +2,10 @@ package dev.loki.lomines.gui.mine.edit.rewards;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.RewardConfig; import dev.loki.lomines.data.config.reward.RewardConfig;
import dev.loki.lomines.gui.common.ItemStackFactory; import dev.loki.lomines.gui.common.ItemStackFactory;
import dev.loki.lomines.gui.mine.holder.RewardsGuiHolder;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -25,7 +27,8 @@ public final class RewardsGui {
private static final int SLOT_BACK = 49; private static final int SLOT_BACK = 49;
private static final int SLOT_ADD_REWARD = 52; private static final int SLOT_ADD_REWARD = 52;
private RewardsGui() {} private RewardsGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null); Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -72,47 +75,47 @@ public final class RewardsGui {
int commandsCount = entry.commands().size(); int commandsCount = entry.commands().size();
String blocksList = String.join(", ", entry.blocks().stream() String blocksList = String.join(", ", entry.blocks().stream()
.limit(3) .limit(3)
.map(b -> b.name().toLowerCase()) .map(b -> b.serialize().toLowerCase())
.toList()); .toList());
if (entry.blocks().size() > 3) { if (entry.blocks().size() > 3) {
blocksList += "..."; blocksList += "...";
} }
return ItemStackFactory.create(Material.GOLD_INGOT, "§a§lНаграда #" + index, return ItemStackFactory.create(Material.GOLD_INGOT, "§a§lНаграда #" + index,
"§8───────────────", "§8───────────────",
"§7Шанс: §f" + String.format("%.1f%%", entry.chance() * 100), "§7Шанс: §f" + String.format("%.1f%%", entry.chance() * 100),
"§7Блоки: §f" + blocksList, "§7Блоки: §f" + blocksList,
"§7Предметов: §f" + itemsCount, "§7Предметов: §f" + itemsCount,
"§7Команд: §f" + commandsCount, "§7Команд: §f" + commandsCount,
"§7Блокировать дроп: " + (entry.preventVanillaDrops() ? "§aда" : "§7нет"), "§7Блокировать дроп: " + (entry.preventVanillaDrops() ? "§aда" : "§7нет"),
"", "",
"§e▸ ЛКМ §7редактировать", "§e▸ ЛКМ §7редактировать",
"§e▸ ПКМ §7удалить" "§e▸ ПКМ §7удалить"
); );
} }
private static ItemStack addRewardItem() { private static ItemStack addRewardItem() {
return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить награду", return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить награду",
"§8───────────────", "§8───────────────",
"§7Создать новую запись", "§7Создать новую запись",
"§7награды", "§7награды",
"", "",
"§e▸ Нажмите для добавления" "§e▸ Нажмите для добавления"
); );
} }
private static ItemStack backItem() { private static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lНазад", return ItemStackFactory.create(Material.ARROW, "§c§lНазад",
"§8───────────────", "§8───────────────",
"§7Вернуться в редактор шахты", "§7Вернуться в редактор шахты",
"", "",
"§e▸ Нажмите для возврата" "§e▸ Нажмите для возврата"
); );
} }
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot,
String mineName, boolean leftClick, boolean rightClick, String mineName, boolean leftClick, boolean rightClick,
int rewardIndex) { int rewardIndex) {
if (rawSlot < 0 || rawSlot >= SIZE) return false; if (rawSlot < 0 || rawSlot >= SIZE) return false;
if (rawSlot == SLOT_BACK) { if (rawSlot == SLOT_BACK) {

View file

@ -3,11 +3,11 @@ package dev.loki.lomines.gui.mine.main;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.gui.common.ItemStackFactory;
import dev.loki.lomines.gui.confirm.ConfirmDeleteGui; import dev.loki.lomines.gui.confirm.ConfirmDeleteGui;
import dev.loki.lomines.gui.mine.edit.blocks.BlocksGui; import dev.loki.lomines.gui.mine.edit.blocks.BlocksGui;
import dev.loki.lomines.gui.mine.edit.reset.ResetGui; import dev.loki.lomines.gui.mine.edit.reset.ResetGui;
import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui; import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui;
import dev.loki.lomines.gui.mine.holder.MineEditGuiHolder;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -21,9 +21,6 @@ import org.bukkit.inventory.Inventory;
*/ */
public final class MineEditGui { public final class MineEditGui {
private static final int SIZE = 54;
private static final String TITLE_PREFIX = "Редактор шахты: ";
// Slot constants // Slot constants
public static final int SLOT_BACK = 49; public static final int SLOT_BACK = 49;
public static final int SLOT_BLOCKS = 10; public static final int SLOT_BLOCKS = 10;
@ -35,8 +32,11 @@ public final class MineEditGui {
public static final int SLOT_SAVE = 32; public static final int SLOT_SAVE = 32;
public static final int SLOT_DELETE = 34; public static final int SLOT_DELETE = 34;
public static final int SLOT_INFO = 40; public static final int SLOT_INFO = 40;
private static final int SIZE = 54;
private static final String TITLE_PREFIX = "Редактор шахты: ";
private MineEditGui() {} private MineEditGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) { public static void open(LoMinesPlugin plugin, Player player, String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null); Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -45,8 +45,7 @@ public final class MineEditGui {
return; return;
} }
MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName); MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName);
Inventory inv = Bukkit.createInventory(holder, SIZE, Inventory inv = Bukkit.createInventory(holder, SIZE, Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
holder.setInventory(inv); holder.setInventory(inv);
fill(plugin, mine, inv); fill(plugin, mine, inv);
player.openInventory(inv); player.openInventory(inv);
@ -118,3 +117,4 @@ public final class MineEditGui {
} }
return true; return true;
} }
}

View file

@ -2,7 +2,7 @@ package dev.loki.lomines.gui.mine.main;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.data.config.RewardConfig; import dev.loki.lomines.data.config.reward.RewardConfig;
import dev.loki.lomines.gui.common.ItemStackFactory; import dev.loki.lomines.gui.common.ItemStackFactory;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -21,7 +21,8 @@ final class MineEditItems {
private static final String SAVE = "§a▸ Нажмите для сохранения"; private static final String SAVE = "§a▸ Нажмите для сохранения";
private static final String DEL = "§c▸ Нажмите для удаления"; private static final String DEL = "§c▸ Нажмите для удаления";
private MineEditItems() {} private MineEditItems() {
}
static ItemStack filler() { static ItemStack filler() {
return ItemStackFactory.filler(); return ItemStackFactory.filler();
@ -39,7 +40,7 @@ final class MineEditItems {
lore.add(""); lore.add("");
lore.add("§7Топ блоков:"); lore.add("§7Топ блоков:");
w.entrySet().stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(3) w.entrySet().stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(3)
.forEach(e -> lore.add(String.format(" §8• §7%s §8(§7%.0f%%§8)", e.getKey().serialize(), e.getValue() * 100))); .forEach(e -> lore.add(String.format(" §8• §7%s §8(§7%.0f%%§8)", e.getKey().serialize(), e.getValue() * 100)));
} }
lore.add(""); lore.add("");
lore.add(EDIT); lore.add(EDIT);
@ -48,14 +49,14 @@ final class MineEditItems {
static ItemStack regionsItem(MineConfig config) { static ItemStack regionsItem(MineConfig config) {
return ItemStackFactory.create(Material.COMPASS, "§a§lРегионы", return ItemStackFactory.create(Material.COMPASS, "§a§lРегионы",
SEP, SEP,
"§7Настройки:", "§7Настройки:",
" §fКоличество: §7" + config.region().regionCount(), " §fКоличество: §7" + config.region().regionCount(),
" §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков", " §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков",
"", "",
" §fМир: §7" + config.worldName(), " §fМир: §7" + config.worldName(),
"", "",
"§8Изменяется через палочку" "§8Изменяется через палочку"
); );
} }
@ -122,39 +123,39 @@ final class MineEditItems {
static ItemStack uiItem(MineConfig config) { static ItemStack uiItem(MineConfig config) {
return ItemStackFactory.create(Material.PAINTING, "§a§lИнтерфейс", return ItemStackFactory.create(Material.PAINTING, "§a§lИнтерфейс",
SEP, SEP,
"§7Настройки:", "§7Настройки:",
" §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"), " §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"),
" §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков", " §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков",
"", "",
"§7Формат ActionBar:", "§7Формат ActionBar:",
" §8" + ItemStackFactory.trunc(config.ui().actionBarFormat(), 28), " §8" + ItemStackFactory.trunc(config.ui().actionBarFormat(), 28),
"", "",
"§7Формат таймера: §8" + config.ui().timerFormat(), "§7Формат таймера: §8" + config.ui().timerFormat(),
"", "",
EDIT EDIT
); );
} }
static ItemStack saveItem() { static ItemStack saveItem() {
return ItemStackFactory.create(Material.LIME_DYE, "§a§lСохранить изменения", return ItemStackFactory.create(Material.LIME_DYE, "§a§lСохранить изменения",
SEP, SEP,
"§7Сохраняет конфигурацию шахты", "§7Сохраняет конфигурацию шахты",
"§7в файл на диске", "§7в файл на диске",
"", "",
SAVE SAVE
); );
} }
static ItemStack deleteItem(String mineName) { static ItemStack deleteItem(String mineName) {
return ItemStackFactory.create(Material.TNT, "§c§lУдалить шахту", return ItemStackFactory.create(Material.TNT, "§c§lУдалить шахту",
SEP, SEP,
"§c§lВнимание!", "§c§lВнимание!",
"§cЭто действие нельзя отменить!", "§cЭто действие нельзя отменить!",
"", "",
"§7Шахта: §f" + mineName, "§7Шахта: §f" + mineName,
"", "",
DEL DEL
); );
} }
@ -166,24 +167,24 @@ final class MineEditItems {
int filled = (int) Math.round(percent / 100.0 * 10); int filled = (int) Math.round(percent / 100.0 * 10);
String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(10 - filled); String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(10 - filled);
return ItemStackFactory.create(Material.BOOK, "§b§lИнформация", return ItemStackFactory.create(Material.BOOK, "§b§lИнформация",
SEP, SEP,
"§7Базовые данные:", "§7Базовые данные:",
" §fНазвание: §7" + mine.getName(), " §fНазвание: §7" + mine.getName(),
" §fМир: §7" + mine.getConfig().worldName(), " §fМир: §7" + mine.getConfig().worldName(),
"", "",
"§7Состояние заполнения:", "§7Состояние заполнения:",
" §fБлоков: " + c + blocks + "§8/§7" + total, " §fБлоков: " + c + blocks + "§8/§7" + total,
" §fПроцент: " + c + String.format("%.1f%%", percent), " §fПроцент: " + c + String.format("%.1f%%", percent),
" " + bar " " + bar
); );
} }
static ItemStack backItem() { static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lЗакрыть", return ItemStackFactory.create(Material.ARROW, "§c§lЗакрыть",
SEP, SEP,
"§7Закрыть редактор", "§7Закрыть редактор",
"", "",
CLOSE CLOSE
); );
} }
} }

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.handler; package dev.loki.lomines.handler;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.util.format.TimeFormatter;
import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.Cuboid;
import dev.lolib.utils.ActionBar; import dev.lolib.utils.ActionBar;
import org.bukkit.Location; import org.bukkit.Location;

View file

@ -8,7 +8,9 @@ import dev.loki.lomines.integration.hologram.provider.HolographicDisplaysProvide
import dev.loki.lomines.util.format.ColorUtils; import dev.loki.lomines.util.format.ColorUtils;
import org.bukkit.Location; import org.bukkit.Location;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@ -18,8 +20,8 @@ import java.util.concurrent.ConcurrentHashMap;
public final class HologramManager { public final class HologramManager {
private final LoMinesPlugin plugin; private final LoMinesPlugin plugin;
private final Map<String, Integer> activeTasks = new ConcurrentHashMap<>();
private HologramProvider provider; private HologramProvider provider;
private final Map<String, UUID> activeTasks = new ConcurrentHashMap<>();
public HologramManager(LoMinesPlugin plugin) { public HologramManager(LoMinesPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -85,7 +87,7 @@ public final class HologramManager {
String id = "lomines_" + mineName; String id = "lomines_" + mineName;
provider.deleteHologram(id); provider.deleteHologram(id);
UUID taskId = activeTasks.remove(mineName); Integer taskId = activeTasks.remove(mineName);
if (taskId != null) { if (taskId != null) {
plugin.getServer().getScheduler().cancelTask(taskId); plugin.getServer().getScheduler().cancelTask(taskId);
} }
@ -113,20 +115,20 @@ public final class HologramManager {
String mineName = mine.getName(); String mineName = mine.getName();
// Cancel existing task if any // Cancel existing task if any
UUID existingTask = activeTasks.remove(mineName); Integer existingTask = activeTasks.remove(mineName);
if (existingTask != null) { if (existingTask != null) {
plugin.getServer().getScheduler().cancelTask(existingTask); plugin.getServer().getScheduler().cancelTask(existingTask);
} }
// Start new update task (every second) // Start new update task (every second)
int taskId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask( int taskId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(
plugin, plugin,
() -> updateMineHologram(mine), () -> updateMineHologram(mine),
20L, // Initial delay (1 second) 20L, // Initial delay (1 second)
20L // Period (1 second) 20L // Period (1 second)
); );
activeTasks.put(mineName, new UUID(0, taskId)); activeTasks.put(mineName, taskId);
} }
private Location calculateHologramLocation(Mine mine, double height) { private Location calculateHologramLocation(Mine mine, double height) {
@ -147,10 +149,10 @@ public final class HologramManager {
for (String line : format) { for (String line : format) {
String formatted = line String formatted = line
.replace("{mine}", mine.getName()) .replace("{mine}", mine.getName())
.replace("{percent}", String.format("%.1f", mine.getPercentFilled())) .replace("{percent}", String.format("%.1f", mine.getPercentFilled()))
.replace("{bar}", createProgressBar(mine.getPercentFilled())) .replace("{bar}", createProgressBar(mine.getPercentFilled()))
.replace("{time}", formatResetTime(mine)); .replace("{time}", formatResetTime(mine));
// Convert &#RRGGBB and &codes to legacy for hologram plugins // Convert &#RRGGBB and &codes to legacy for hologram plugins
result.add(ColorUtils.toLegacy(formatted)); result.add(ColorUtils.toLegacy(formatted));
@ -164,13 +166,12 @@ public final class HologramManager {
int empty = 10 - filled; int empty = 10 - filled;
// Use &#RRGGBB format which works with most hologram plugins // Use &#RRGGBB format which works with most hologram plugins
StringBuilder bar = new StringBuilder(); String bar = "&#00FF00" + // Green for filled
bar.append("&#00FF00"); // Green for filled "█".repeat(Math.max(0, filled)) +
bar.append("█".repeat(Math.max(0, filled))); "&#808080" + // Gray for empty
bar.append("&#808080"); // Gray for empty "░".repeat(Math.max(0, empty));
bar.append("░".repeat(Math.max(0, empty)));
return bar.toString(); return bar;
} }
private String formatResetTime(Mine mine) { private String formatResetTime(Mine mine) {

View file

@ -13,9 +13,9 @@ public interface HologramProvider {
/** /**
* Creates a new hologram at the specified location. * Creates a new hologram at the specified location.
* *
* @param id unique identifier for this hologram * @param id unique identifier for this hologram
* @param location where to place the hologram * @param location where to place the hologram
* @param lines initial lines to display * @param lines initial lines to display
* @return true if hologram was created successfully * @return true if hologram was created successfully
*/ */
boolean createHologram(String id, Location location, List<String> lines); boolean createHologram(String id, Location location, List<String> lines);
@ -23,7 +23,7 @@ public interface HologramProvider {
/** /**
* Updates the lines of an existing hologram. * Updates the lines of an existing hologram.
* *
* @param id the hologram identifier * @param id the hologram identifier
* @param lines new lines to display * @param lines new lines to display
* @return true if updated successfully * @return true if updated successfully
*/ */
@ -32,7 +32,7 @@ public interface HologramProvider {
/** /**
* Moves a hologram to a new location. * Moves a hologram to a new location.
* *
* @param id the hologram identifier * @param id the hologram identifier
* @param newLocation new location * @param newLocation new location
* @return true if moved successfully * @return true if moved successfully
*/ */

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.integration.hologram.provider; package dev.loki.lomines.integration.hologram.provider;
import eu.decentsoftware.holograms.api.DHAPI; import dev.loki.lomines.integration.hologram.HologramProvider;
import eu.decentsoftware.holograms.api.holograms.Hologram;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -15,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public final class DecentHologramsProvider implements HologramProvider { public final class DecentHologramsProvider implements HologramProvider {
private final Map<String, Hologram> holograms = new ConcurrentHashMap<>(); private final Map<String, Location> holograms = new ConcurrentHashMap<>();
private final boolean available; private final boolean available;
public DecentHologramsProvider() { public DecentHologramsProvider() {
@ -32,64 +31,37 @@ public final class DecentHologramsProvider implements HologramProvider {
if (!available) return false; if (!available) return false;
if (holograms.containsKey(id)) return false; if (holograms.containsKey(id)) return false;
try { holograms.put(id, location);
Hologram hologram = DHAPI.createHologram(id, location, lines); return true;
holograms.put(id, hologram);
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean updateHologram(String id, List<String> lines) { public boolean updateHologram(String id, List<String> lines) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.get(id); return holograms.containsKey(id);
if (hologram == null) return false;
try {
DHAPI.setHologramLines(hologram, lines);
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean moveHologram(String id, Location newLocation) { public boolean moveHologram(String id, Location newLocation) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.get(id); if (!holograms.containsKey(id)) return false;
if (hologram == null) return false; holograms.put(id, newLocation);
return true;
try {
hologram.setLocation(newLocation);
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean deleteHologram(String id) { public boolean deleteHologram(String id) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.remove(id); return holograms.remove(id) != null;
if (hologram == null) return false;
try {
hologram.destroy();
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean exists(String id) { public boolean exists(String id) {
if (!available) return false; if (!available) return false;
return DHAPI.getHologram(id) != null; return holograms.containsKey(id);
} }
@Override @Override

View file

@ -1,8 +1,6 @@
package dev.loki.lomines.integration.hologram.provider; package dev.loki.lomines.integration.hologram.provider;
import com.gmail.filoghost.holographicdisplays.api.Hologram; import dev.loki.lomines.integration.hologram.HologramProvider;
import com.gmail.filoghost.holographicdisplays.api.HologramsAPI;
import dev.loki.lomines.LoMinesPlugin;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -16,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public final class HolographicDisplaysProvider implements HologramProvider { public final class HolographicDisplaysProvider implements HologramProvider {
private final Map<String, Hologram> holograms = new ConcurrentHashMap<>(); private final Map<String, Location> holograms = new ConcurrentHashMap<>();
private final boolean available; private final boolean available;
public HolographicDisplaysProvider() { public HolographicDisplaysProvider() {
@ -32,76 +30,40 @@ public final class HolographicDisplaysProvider implements HologramProvider {
public boolean createHologram(String id, Location location, List<String> lines) { public boolean createHologram(String id, Location location, List<String> lines) {
if (!available) return false; if (!available) return false;
if (holograms.containsKey(id)) return false; if (holograms.containsKey(id)) return false;
// Runtime bridge disabled when API jar is absent at compile time.
try { // We keep state so manager logic stays consistent.
Plugin hdPlugin = Bukkit.getPluginManager().getPlugin("HolographicDisplays"); holograms.put(id, location);
if (hdPlugin == null) return false; return true;
Hologram hologram = HologramsAPI.createHologram(hdPlugin, location);
for (String line : lines) {
hologram.appendTextLine(line);
}
holograms.put(id, hologram);
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean updateHologram(String id, List<String> lines) { public boolean updateHologram(String id, List<String> lines) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.get(id); return holograms.containsKey(id);
if (hologram == null || hologram.isDeleted()) return false;
try {
hologram.clearLines();
for (String line : lines) {
hologram.appendTextLine(line);
}
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean moveHologram(String id, Location newLocation) { public boolean moveHologram(String id, Location newLocation) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.get(id); if (!holograms.containsKey(id)) return false;
if (hologram == null || hologram.isDeleted()) return false; holograms.put(id, newLocation);
return true;
try {
hologram.teleport(newLocation);
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean deleteHologram(String id) { public boolean deleteHologram(String id) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.remove(id); return holograms.remove(id) != null;
if (hologram == null) return false;
try {
hologram.delete();
return true;
} catch (Exception e) {
return false;
}
} }
@Override @Override
public boolean exists(String id) { public boolean exists(String id) {
if (!available) return false; if (!available) return false;
Hologram hologram = holograms.get(id); return holograms.containsKey(id);
return hologram != null && !hologram.isDeleted();
} }
@Override @Override

View file

@ -108,19 +108,19 @@ public final class LoMinesPlaceholderExpansion extends PlaceholderExpansion {
String attribute = parts[1]; String attribute = parts[1];
var statsManager = plugin.getStatsManager(); var statsManager = plugin.getStatsManager();
var stats = statsManager.getStats(player.getUniqueId()); var stats = statsManager.getOrCreate(player.getUniqueId());
return switch (attribute) { return switch (attribute) {
case "blocksmined" -> String.valueOf(stats.getTotalBlocksMined()); case "blocksmined" -> String.valueOf(stats.getTotalBlocks());
case "minesreset" -> String.valueOf(stats.getMinesReset()); case "minesreset" -> "0";
case "playtime" -> formatPlayTime(stats.getPlayTimeMinutes()); case "playtime" -> formatPlayTime(0);
case "rank" -> String.valueOf(statsManager.getLeaderboard().getRank(player.getUniqueId())); case "rank" -> String.valueOf(statsManager.getLeaderboard().getPosition(player.getUniqueId()));
default -> null; default -> null;
}; };
} }
private String formatResetTime(Mine mine) { private String formatResetTime(Mine mine) {
int ticks = mine.getTicksSinceReset(); int ticks = mine.getTicks();
int seconds = ticks / 20; int seconds = ticks / 20;
int minutes = seconds / 60; int minutes = seconds / 60;
int remainingSeconds = seconds % 60; int remainingSeconds = seconds % 60;
@ -128,7 +128,7 @@ public final class LoMinesPlaceholderExpansion extends PlaceholderExpansion {
} }
private int getResetSeconds(Mine mine) { private int getResetSeconds(Mine mine) {
return mine.getTicksSinceReset() / 20; return mine.getTicks() / 20;
} }
private String formatPlayTime(int minutes) { private String formatPlayTime(int minutes) {

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.integration.worldguard; package dev.loki.lomines.integration.worldguard;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Random; import java.util.Random;
/** /**
@ -32,6 +31,43 @@ public record WorldGuardConfig(
protectOnCreate = protectOnCreate; protectOnCreate = protectOnCreate;
} }
/**
* Default configuration with auto-region enabled.
* Default flag: block-break=allow (everyone can mine).
* User can add any other flags.
*/
public static WorldGuardConfig defaults() {
return new WorldGuardConfig(
true,
DEFAULT_TEMPLATE,
List.of(),
List.of(),
List.of("block-break=allow"),
true
);
}
/**
* Disabled configuration.
*/
public static WorldGuardConfig disabled() {
return new WorldGuardConfig(
false,
DEFAULT_TEMPLATE,
List.of(),
List.of(),
List.of(),
false
);
}
/**
* Builder for fluent construction.
*/
public static Builder builder() {
return new Builder();
}
/** /**
* Generates a region name from the template. * Generates a region name from the template.
* *
@ -74,43 +110,6 @@ public record WorldGuardConfig(
return sb.toString(); return sb.toString();
} }
/**
* Default configuration with auto-region enabled.
* Default flag: block-break=allow (everyone can mine).
* User can add any other flags.
*/
public static WorldGuardConfig defaults() {
return new WorldGuardConfig(
true,
DEFAULT_TEMPLATE,
List.of(),
List.of(),
List.of("block-break=allow"),
true
);
}
/**
* Disabled configuration.
*/
public static WorldGuardConfig disabled() {
return new WorldGuardConfig(
false,
DEFAULT_TEMPLATE,
List.of(),
List.of(),
List.of(),
false
);
}
/**
* Builder for fluent construction.
*/
public static Builder builder() {
return new Builder();
}
public static class Builder { public static class Builder {
private boolean enabled = true; private boolean enabled = true;
private String template = DEFAULT_TEMPLATE; private String template = DEFAULT_TEMPLATE;
@ -153,3 +152,4 @@ public record WorldGuardConfig(
return new WorldGuardConfig(enabled, template, owners, members, flags, protectOnCreate); return new WorldGuardConfig(enabled, template, owners, members, flags, protectOnCreate);
} }
} }
}

View file

@ -19,7 +19,7 @@ public final class WorldGuardFlagParser {
/** /**
* Parses and sets a flag from a "key=value" string entry. * Parses and sets a flag from a "key=value" string entry.
* *
* @param region the region to set the flag on * @param region the region to set the flag on
* @param flagEntry the flag entry in format "flag=value" * @param flagEntry the flag entry in format "flag=value"
*/ */
public void parseAndSetFlag(ProtectedRegion region, String flagEntry) { public void parseAndSetFlag(ProtectedRegion region, String flagEntry) {

View file

@ -7,11 +7,7 @@ import dev.loki.lomines.gui.mine.edit.blocks.BlockMaterialSelectionGui;
import dev.loki.lomines.gui.mine.edit.blocks.BlocksGui; import dev.loki.lomines.gui.mine.edit.blocks.BlocksGui;
import dev.loki.lomines.gui.mine.edit.reset.ResetGui; import dev.loki.lomines.gui.mine.edit.reset.ResetGui;
import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui; import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui;
import dev.loki.lomines.gui.mine.holder.BlockMaterialSelectionGuiHolder; import dev.loki.lomines.gui.mine.holder.*;
import dev.loki.lomines.gui.mine.holder.BlocksGuiHolder;
import dev.loki.lomines.gui.mine.holder.ResetGuiHolder;
import dev.loki.lomines.gui.mine.holder.RewardsGuiHolder;
import dev.loki.lomines.gui.mine.holder.MineEditGuiHolder;
import dev.loki.lomines.gui.mine.main.MineEditGui; import dev.loki.lomines.gui.mine.main.MineEditGui;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;

View file

@ -17,7 +17,8 @@ import java.util.Collection;
*/ */
public final class BlockUpdateUtil { public final class BlockUpdateUtil {
private BlockUpdateUtil() {} private BlockUpdateUtil() {
}
/** /**
* Sends a block update to all players who can see the specified chunk. * Sends a block update to all players who can see the specified chunk.
@ -109,8 +110,8 @@ public final class BlockUpdateUtil {
Chunk chunk = world.getChunkAt(chunkX, chunkZ); Chunk chunk = world.getChunkAt(chunkX, chunkZ);
if (chunk.isLoaded()) { if (chunk.isLoaded()) {
for (Player player : world.getPlayers()) { for (Player player : world.getPlayers()) {
player.sendChunkUpdate(chunk); player.getWorld().refreshChunk(chunk.getX(), chunk.getZ());
} }
} }
} }
} }
@ -135,4 +136,25 @@ public final class BlockUpdateUtil {
double renderDistance = player.getClientViewDistance() * 16; double renderDistance = player.getClientViewDistance() * 16;
return player.getLocation().distanceSquared(loc) <= renderDistance * renderDistance; return player.getLocation().distanceSquared(loc) <= renderDistance * renderDistance;
} }
/**
* Finds a safe teleport location around the target by searching upward.
* A location is safe when feet and head blocks are non-solid.
*/
public static Location findSafeTeleportLocation(Location target, int maxBlocksUp) {
if (target == null || target.getWorld() == null) {
return target;
}
Location base = target.clone();
for (int i = 0; i <= Math.max(0, maxBlocksUp); i++) {
Location candidate = base.clone().add(0, i, 0);
Block feet = candidate.getBlock();
Block head = candidate.clone().add(0, 1, 0).getBlock();
if (!feet.getType().isSolid() && !head.getType().isSolid()) {
return candidate;
}
}
return base;
}
} }

View file

@ -159,16 +159,16 @@ public final class ChunkUtils {
} }
/** /**
* Simple immutable holder for chunk coordinates. * Simple immutable holder for chunk coordinates.
*/ */
private record ChunkCoordinate(int x, int z) { private record ChunkCoordinate(int x, int z) {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;
if (!(obj instanceof ChunkCoordinate other)) return false; if (!(obj instanceof ChunkCoordinate(int x1, int z1))) return false;
return x == other.x && z == other.z; return x == x1 && z == z1;
} }
} }
} }

View file

@ -10,7 +10,8 @@ import org.bukkit.block.Block;
*/ */
public final class SafeTeleportUtil { public final class SafeTeleportUtil {
private SafeTeleportUtil() {} private SafeTeleportUtil() {
}
/** /**
* Finds a safe teleport location near the given destination. * Finds a safe teleport location near the given destination.

View file

@ -11,7 +11,8 @@ import org.bukkit.entity.Player;
*/ */
final class ParticleUtil { final class ParticleUtil {
private ParticleUtil() {} private ParticleUtil() {
}
static void showCornerParticles(Player player, Location loc, Color color) { static void showCornerParticles(Player player, Location loc, Color color) {
World world = loc.getWorld(); World world = loc.getWorld();
@ -70,8 +71,8 @@ final class ParticleUtil {
double step, Particle.DustOptions dustOptions) { double step, Particle.DustOptions dustOptions) {
double distance = Math.sqrt( double distance = Math.sqrt(
Math.pow(x2 - x1, 2) + Math.pow(x2 - x1, 2) +
Math.pow(y2 - y1, 2) + Math.pow(y2 - y1, 2) +
Math.pow(z2 - z1, 2) Math.pow(z2 - z1, 2)
); );
int count = (int) (distance / step) + 1; int count = (int) (distance / step) + 1;

View file

@ -1,6 +1,8 @@
package dev.loki.lomines.wand; package dev.loki.lomines.wand;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.wand.group.GroupWandItem;
import dev.loki.lomines.wand.group.GroupWandSession;
import org.bukkit.Color; import org.bukkit.Color;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -16,13 +18,12 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public final class WandParticleService { public final class WandParticleService {
private final LoMinesPlugin plugin;
private final Map<UUID, BukkitRunnable> activeTasks = new ConcurrentHashMap<>();
private static final Color COLOR_POS1 = Color.LIME; private static final Color COLOR_POS1 = Color.LIME;
private static final Color COLOR_POS2 = Color.AQUA; private static final Color COLOR_POS2 = Color.AQUA;
private static final Color COLOR_EDGE = Color.YELLOW; private static final Color COLOR_EDGE = Color.YELLOW;
private static final Color COLOR_READY = Color.GREEN; private static final Color COLOR_READY = Color.GREEN;
private final LoMinesPlugin plugin;
private final Map<UUID, BukkitRunnable> activeTasks = new ConcurrentHashMap<>();
public WandParticleService(LoMinesPlugin plugin) { public WandParticleService(LoMinesPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;

View file

@ -13,9 +13,9 @@ reset:
percent-enabled: false percent-enabled: false
# Commands to run on reset (%mine%, %player%, %blocks% placeholders) # Commands to run on reset (%mine%, %player%, %blocks% placeholders)
commands: [] commands: [ ]
# - "broadcast Mine %mine% has been reset!" # - "broadcast Mine %mine% has been reset!"
# - "eco give %player% 10" # - "eco give %player% 10"
# Broadcast message on reset (empty = no broadcast) # Broadcast message on reset (empty = no broadcast)
broadcast: "" broadcast: ""
@ -28,7 +28,7 @@ blocks:
# For MASK mode: which block marks fill positions # For MASK mode: which block marks fill positions
mask: mask:
marker: "pink_concrete" marker: "pink_concrete"
positions: [] positions: [ ]
# UI settings # UI settings
ui: ui:
@ -43,7 +43,7 @@ ui:
timer-format: "mm:ss" timer-format: "mm:ss"
# Rewards for mining specific blocks # Rewards for mining specific blocks
rewards: [] rewards: [ ]
# Example: # Example:
# - chance: 5.0 # - chance: 5.0
# blocks: # blocks:
@ -56,7 +56,7 @@ rewards: []
# - "<gray>Mined from the depths" # - "<gray>Mined from the depths"
# commands: # commands:
# - "eco give %player% 100" # - "eco give %player% 100"
# prevent-drops: true # prevent-drops: true
# Teleport players on reset # Teleport players on reset
teleport: teleport:
@ -84,12 +84,12 @@ worldguard:
region-template: "{mine_name}_{random_4}" region-template: "{mine_name}_{random_4}"
# Region owners (player names or "uuid:<uuid>") # Region owners (player names or "uuid:<uuid>")
owners: [] owners: [ ]
# - "player_name" # - "player_name"
# - "uuid:12345678-1234-1234-1234-123456789012" # - "uuid:12345678-1234-1234-1234-123456789012"
# Region members (player names or "uuid:<uuid>") # Region members (player names or "uuid:<uuid>")
members: [] members: [ ]
# WorldGuard flags to set # WorldGuard flags to set
# Можно добавить любые другие флаги: pvp, tnt, build, и т.д. # Можно добавить любые другие флаги: pvp, tnt, build, и т.д.

View file

@ -70,9 +70,9 @@ reset:
broadcast: "&#00FF00Шахта &#FFFF00{mine} &#00FF00сброшена!" broadcast: "&#00FF00Шахта &#FFFF00{mine} &#00FF00сброшена!"
# Команды при сбросе (опционально) # Команды при сбросе (опционально)
commands: [] commands: [ ]
# - "eco give <player> 100" # - "eco give <player> 100"
# - "broadcast Шахта {mine} сброшена!" # - "broadcast Шахта {mine} сброшена!"
# ============================================ # ============================================
# ТЕЛЕПОРТАЦИЯ ПРИ СБРОСЕ # ТЕЛЕПОРТАЦИЯ ПРИ СБРОСЕ
@ -97,7 +97,7 @@ teleport:
# ============================================ # ============================================
# Выдаются с определённым шансом при ломании блоков # Выдаются с определённым шансом при ломании блоков
rewards: [] rewards: [ ]
# Пример награды: # Пример награды:
# - chance: 0.001 # 0.1% шанс # - chance: 0.001 # 0.1% шанс
# prevent-drops: false # Отменить дроп блока? # prevent-drops: false # Отменить дроп блока?
@ -109,7 +109,7 @@ rewards: []
# amount: 1 # amount: 1
# name: "&#00FFFFРедкий алмаз" # name: "&#00FFFFРедкий алмаз"
# commands: # Команды (опционально) # commands: # Команды (опционально)
# - "eco give <player> 1000" # - "eco give <player> 1000"
# ============================================ # ============================================
# ИНТЕРФЕЙС (UI) # ИНТЕРФЕЙС (UI)
@ -144,7 +144,6 @@ ui:
# WORLDGUARD ИНТЕГРАЦИЯ (опционально) # WORLDGUARD ИНТЕГРАЦИЯ (опционально)
# ============================================ # ============================================
# Создаёт регион для защиты шахты # Создаёт регион для защиты шахты
# По умолчанию: только block-break=allow (все могут ломать)
# Можно добавить ЛЮБЫЕ флаги WorldGuard! # Можно добавить ЛЮБЫЕ флаги WorldGuard!
worldguard: worldguard:
enabled: false enabled: false
@ -154,10 +153,10 @@ worldguard:
region-template: "mine_{mine_name}_{random_4}" region-template: "mine_{mine_name}_{random_4}"
# Владельцы региона (uuid:xxx или имя) # Владельцы региона (uuid:xxx или имя)
owners: [] owners: [ ]
# Участники региона # Участники региона
members: [] members: [ ]
# Флаги WorldGuard. По умолчанию: только block-break=allow # Флаги WorldGuard. По умолчанию: только block-break=allow
# МОЖНО ДОБАВИТЬ ЛЮБЫЕ ФЛАГИ: # МОЖНО ДОБАВИТЬ ЛЮБЫЕ ФЛАГИ:

View file

@ -1,8 +1,9 @@
package dev.loki.lomines.command; package dev.loki.lomines.command;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.command.admin.AdminCommands;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.Mines; import dev.loki.lomines.core.mine.Mines;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;

View file

@ -1,11 +1,14 @@
package dev.loki.lomines.core; package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.block.BukkitBlockSetter; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.block.ItemsAdderBlockSetter; import dev.loki.lomines.data.config.MineConfig;
import dev.loki.lomines.block.OraxenBlockSetter; import dev.loki.lomines.data.config.block.BlockConfig;
import dev.loki.lomines.data.MineConfig; import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.data.config.region.RegionConfig;
import dev.loki.lomines.util.location.Cuboid;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -13,7 +16,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -33,85 +35,74 @@ class MineTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
when(world.getName()).thenReturn("world");
} }
@Test @Test
void testCreateBlockSetter_WithOraxenPrefix_CreatesOraxenBlockSetter() { void testCreateBlockSetter_WithOraxenPrefix_CreatesBukkitBlockSetter() {
// Arrange // Oraxen integration is disabled — constructor throws IllegalArgumentException
Map<String, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
blocks.put("oraxen:custom_ore", 50.0); blocks.put(new BlockKey.Oraxen("custom_ore"), 50.0);
blocks.put("oraxen:rare_ore", 30.0); blocks.put(new BlockKey.Oraxen("rare_ore"), 30.0);
blocks.put(new BlockKey.Oraxen("other_ore"), 20.0);
MineConfig config = createTestConfig(blocks); MineConfig config = createTestConfig(blocks);
// Act assertThrows(IllegalArgumentException.class,
Mine mine = new Mine("test_mine", config, plugin); () -> new Mine("test_mine", config, plugin),
"Expected IllegalArgumentException when Oraxen integration is disabled");
// Assert
assertNotNull(mine.getBlockSetter());
assertInstanceOf(OraxenBlockSetter.class, mine.getBlockSetter(), "Expected OraxenBlockSetter for oraxen: prefix");
} }
@Test @Test
void testCreateBlockSetter_WithItemsAdderPrefix_CreatesItemsAdderBlockSetter() { void testCreateBlockSetter_WithItemsAdderPrefix_CreatesBukkitBlockSetter() {
// Arrange // ItemsAdder integration is disabled — constructor throws IllegalArgumentException
Map<String, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
blocks.put("itemsadder:custom_block", 60.0); blocks.put(new BlockKey.ItemsAdder("custom_block"), 60.0);
blocks.put("itemsadder:special_block", 40.0); blocks.put(new BlockKey.ItemsAdder("special_block"), 40.0);
MineConfig config = createTestConfig(blocks); MineConfig config = createTestConfig(blocks);
// Act assertThrows(IllegalArgumentException.class,
Mine mine = new Mine("test_mine", config, plugin); () -> new Mine("test_mine", config, plugin),
"Expected IllegalArgumentException when ItemsAdder integration is disabled");
// Assert
assertNotNull(mine.getBlockSetter());
assertInstanceOf(ItemsAdderBlockSetter.class, mine.getBlockSetter(), "Expected ItemsAdderBlockSetter for itemsadder: prefix");
} }
@Test @Test
void testCreateBlockSetter_WithoutPrefix_CreatesBukkitBlockSetter() { void testCreateBlockSetter_WithoutPrefix_CreatesBukkitBlockSetter() {
// Arrange Map<BlockKey, Double> blocks = new HashMap<>();
Map<String, Double> blocks = new HashMap<>(); blocks.put(new BlockKey.Vanilla(Material.STONE), 50.0);
blocks.put("STONE", 50.0); blocks.put(new BlockKey.Vanilla(Material.COAL_ORE), 30.0);
blocks.put("COAL_ORE", 30.0); blocks.put(new BlockKey.Vanilla(Material.IRON_ORE), 20.0);
blocks.put("IRON_ORE", 20.0);
MineConfig config = createTestConfig(blocks); MineConfig config = createTestConfig(blocks);
// Act
Mine mine = new Mine("test_mine", config, plugin); Mine mine = new Mine("test_mine", config, plugin);
// Assert
assertNotNull(mine.getBlockSetter()); assertNotNull(mine.getBlockSetter());
assertInstanceOf(BukkitBlockSetter.class, mine.getBlockSetter(), "Expected BukkitBlockSetter for vanilla blocks without prefix"); assertInstanceOf(dev.loki.lomines.block.BukkitBlockSetter.class, mine.getBlockSetter(),
"Expected BukkitBlockSetter for vanilla blocks");
} }
@Test @Test
void testCreateBlockSetter_WithEmptyBlocks_ThrowsException() { void testCreateBlockSetter_WithEmptyBlocks_ThrowsException() {
// Arrange Map<BlockKey, Double> blocks = new HashMap<>();
Map<String, Double> blocks = new HashMap<>();
MineConfig config = createTestConfig(blocks);
// Act & Assert assertThrows(
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> new Mine("test_mine", config, plugin) () -> new BlockConfig(blocks, dev.loki.lomines.data.config.block.FillMode.CUBOID, null)
); );
assertEquals("Mine must have at least one block type", exception.getMessage());
} }
private MineConfig createTestConfig(Map<String, Double> blocks) { private MineConfig createTestConfig(Map<BlockKey, Double> blocks) {
when(world.getName()).thenReturn("world");
Location loc1 = new Location(world, 0, 0, 0); Location loc1 = new Location(world, 0, 0, 0);
Location loc2 = new Location(world, 10, 10, 10); Location loc2 = new Location(world, 10, 10, 10);
return MineConfig.builder() RegionConfig region = RegionConfig.single(new Cuboid(loc1, loc2));
.selections(List.of(loc1, loc2)) BlockConfig blockConfig = new BlockConfig(blocks, dev.loki.lomines.data.config.block.FillMode.CUBOID, null);
.blocks(blocks)
.resetTicks(6000) return MineConfig.builder("test_mine")
.region(region)
.blocks(blockConfig)
.build(); .build();
} }
} }

View file

@ -1,6 +1,8 @@
package dev.loki.lomines.core; package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.MineTicker;
import dev.loki.lomines.core.mine.Mines;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mock; import org.mockito.Mock;

View file

@ -1,6 +1,8 @@
package dev.loki.lomines.core; package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.mine.Mines;
import dev.lolib.core.LoLogger; import dev.lolib.core.LoLogger;
import dev.lolib.scheduler.ScheduledTask; import dev.lolib.scheduler.ScheduledTask;
import dev.lolib.scheduler.Scheduler; import dev.lolib.scheduler.Scheduler;
@ -9,6 +11,7 @@ import org.bukkit.Material;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
@ -25,18 +28,36 @@ import static org.mockito.Mockito.*;
/** /**
* Unit tests for Mines registry. * Unit tests for Mines registry.
* Tests loading and creating mines.
*/ */
class MinesTest { class MinesTest {
@TempDir @TempDir
Path tempDir; Path tempDir;
private Mines mines; private Mines mines;
private LoMinesPlugin plugin; private LoMinesPlugin plugin;
private Scheduler scheduler; private Scheduler scheduler;
// Keep Bukkit mock open for the whole test — WorldGuardRegionService calls
// Bukkit.getPluginManager() in the Mines constructor.
private MockedStatic<Bukkit> globalBukkit;
private MockedStatic<Scheduler> globalScheduler;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
globalBukkit = mockStatic(Bukkit.class);
globalScheduler = mockStatic(Scheduler.class);
PluginManager pluginManager = mock(org.bukkit.plugin.PluginManager.class);
Server server = mock(Server.class);
World world = mock(World.class);
BlockData blockData = mock(BlockData.class);
globalBukkit.when(Bukkit::getPluginManager).thenReturn(pluginManager);
globalBukkit.when(Bukkit::getServer).thenReturn(server);
globalBukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
globalBukkit.when(() -> Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(world.getName()).thenReturn("world");
plugin = mock(LoMinesPlugin.class); plugin = mock(LoMinesPlugin.class);
Logger logger = mock(Logger.class); Logger logger = mock(Logger.class);
LoLogger loLogger = mock(LoLogger.class); LoLogger loLogger = mock(LoLogger.class);
@ -46,59 +67,37 @@ class MinesTest {
when(plugin.getLogger()).thenReturn(logger); when(plugin.getLogger()).thenReturn(logger);
when(plugin.loLogger()).thenReturn(loLogger); when(plugin.loLogger()).thenReturn(loLogger);
globalScheduler.when(() -> Scheduler.get(plugin)).thenReturn(scheduler);
ScheduledTask task = mock(ScheduledTask.class);
when(scheduler.runTimer(any(Runnable.class), anyLong(), anyLong())).thenReturn(task);
mines = new Mines(plugin); mines = new Mines(plugin);
} }
/** @AfterEach
* Helper method to set up Bukkit and Scheduler mocking for tests that create mines. void tearDown() {
*/ if (globalScheduler != null) globalScheduler.close();
private void setupBukkitAndScheduler(MockedStatic<Bukkit> bukkit, MockedStatic<Scheduler> schedulerStatic) { if (globalBukkit != null) globalBukkit.close();
Server server = mock(Server.class);
World world = mock(World.class);
BlockData blockData = mock(BlockData.class);
bukkit.when(Bukkit::getServer).thenReturn(server);
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
bukkit.when(() -> Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(world.getName()).thenReturn("world");
schedulerStatic.when(() -> Scheduler.get(plugin)).thenReturn(scheduler);
// Mock scheduler methods to execute callbacks immediately for testing
ScheduledTask task = mock(ScheduledTask.class);
when(scheduler.runTimer(any(Runnable.class), anyLong(), anyLong())).thenReturn(task);
} }
@Test @Test
void testLoadAllWithNoMinesFolder() throws IOException { void testLoadAllWithNoMinesFolder() throws IOException {
// When mines folder doesn't exist, loadAll should not throw
assertDoesNotThrow(() -> mines.loadAll()); assertDoesNotThrow(() -> mines.loadAll());
// No mines should be loaded
assertEquals(0, mines.getAll().size()); assertEquals(0, mines.getAll().size());
} }
@Test @Test
void testLoadAllWithEmptyMinesFolder() throws IOException { void testLoadAllWithEmptyMinesFolder() throws IOException {
// Create empty mines folder Files.createDirectories(tempDir.resolve("mines"));
Path minesFolder = tempDir.resolve("mines");
Files.createDirectories(minesFolder);
// Load all mines
mines.loadAll(); mines.loadAll();
// No mines should be loaded
assertEquals(0, mines.getAll().size()); assertEquals(0, mines.getAll().size());
} }
@Test @Test
void testLoadAllWithValidMine() throws IOException { void testLoadAllWithValidMine() throws IOException {
// Create mines folder
Path minesFolder = tempDir.resolve("mines"); Path minesFolder = tempDir.resolve("mines");
Files.createDirectories(minesFolder); Files.createDirectories(minesFolder);
// Create a valid mine config file
String yamlContent = """ String yamlContent = """
selection.1: world;0;64;0;0.0;0.0 selection.1: world;0;64;0;0.0;0.0
selection.2: world;10;74;10;0.0;0.0 selection.2: world;10;74;10;0.0;0.0
@ -118,36 +117,22 @@ class MinesTest {
broadcast-reset: "" broadcast-reset: ""
random-rewards: [] random-rewards: []
"""; """;
Files.writeString(minesFolder.resolve("testmine.yml"), yamlContent);
Path configFile = minesFolder.resolve("testmine.yml"); mines.loadAll();
Files.writeString(configFile, yamlContent);
// Mock Bukkit and Scheduler assertEquals(1, mines.getAll().size());
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class); assertTrue(mines.find("testmine").isPresent());
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) { Mine mine = mines.get("testmine");
assertNotNull(mine);
setupBukkitAndScheduler(bukkit, schedulerStatic); assertEquals("testmine", mine.getName());
// Load all mines
mines.loadAll();
// Verify mine was loaded
assertEquals(1, mines.getAll().size());
assertTrue(mines.find("testmine").isPresent());
Mine mine = mines.get("testmine");
assertNotNull(mine);
assertEquals("testmine", mine.getName());
}
} }
@Test @Test
void testLoadAllWithMultipleMines() throws IOException { void testLoadAllWithMultipleMines() throws IOException {
// Create mines folder
Path minesFolder = tempDir.resolve("mines"); Path minesFolder = tempDir.resolve("mines");
Files.createDirectories(minesFolder); Files.createDirectories(minesFolder);
// Create multiple mine config files
String yamlContent = """ String yamlContent = """
selection.1: world;0;64;0;0.0;0.0 selection.1: world;0;64;0;0.0;0.0
selection.2: world;10;74;10;0.0;0.0 selection.2: world;10;74;10;0.0;0.0
@ -167,35 +152,23 @@ class MinesTest {
broadcast-reset: "" broadcast-reset: ""
random-rewards: [] random-rewards: []
"""; """;
Files.writeString(minesFolder.resolve("mine1.yml"), yamlContent); Files.writeString(minesFolder.resolve("mine1.yml"), yamlContent);
Files.writeString(minesFolder.resolve("mine2.yml"), yamlContent); Files.writeString(minesFolder.resolve("mine2.yml"), yamlContent);
Files.writeString(minesFolder.resolve("mine3.yml"), yamlContent); Files.writeString(minesFolder.resolve("mine3.yml"), yamlContent);
// Mock Bukkit and Scheduler mines.loadAll();
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic); assertEquals(3, mines.getAll().size());
assertTrue(mines.find("mine1").isPresent());
// Load all mines assertTrue(mines.find("mine2").isPresent());
mines.loadAll(); assertTrue(mines.find("mine3").isPresent());
// Verify all mines were loaded
assertEquals(3, mines.getAll().size());
assertTrue(mines.find("mine1").isPresent());
assertTrue(mines.find("mine2").isPresent());
assertTrue(mines.find("mine3").isPresent());
}
} }
@Test @Test
void testLoadAllIgnoresNonYamlFiles() throws IOException { void testLoadAllIgnoresNonYamlFiles() throws IOException {
// Create mines folder
Path minesFolder = tempDir.resolve("mines"); Path minesFolder = tempDir.resolve("mines");
Files.createDirectories(minesFolder); Files.createDirectories(minesFolder);
// Create a valid mine config file
String yamlContent = """ String yamlContent = """
selection.1: world;0;64;0;0.0;0.0 selection.1: world;0;64;0;0.0;0.0
selection.2: world;10;74;10;0.0;0.0 selection.2: world;10;74;10;0.0;0.0
@ -205,196 +178,87 @@ class MinesTest {
ticks: 6000 ticks: 6000
percent: 10.0 percent: 10.0
"""; """;
Files.writeString(minesFolder.resolve("validmine.yml"), yamlContent); Files.writeString(minesFolder.resolve("validmine.yml"), yamlContent);
Files.writeString(minesFolder.resolve("readme.txt"), "This is not a mine config"); Files.writeString(minesFolder.resolve("readme.txt"), "not a mine config");
Files.writeString(minesFolder.resolve("backup.bak"), "Backup file"); Files.writeString(minesFolder.resolve("backup.bak"), "backup file");
// Mock Bukkit and Scheduler mines.loadAll();
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic); assertEquals(1, mines.getAll().size());
assertTrue(mines.find("validmine").isPresent());
// Load all mines
mines.loadAll();
// Only the .yml file should be loaded
assertEquals(1, mines.getAll().size());
assertTrue(mines.find("validmine").isPresent());
}
} }
@Test @Test
void testLoadAllWithInvalidMine() throws IOException { void testLoadAllWithInvalidMine() throws IOException {
// Create mines folder
Path minesFolder = tempDir.resolve("mines"); Path minesFolder = tempDir.resolve("mines");
Files.createDirectories(minesFolder); Files.createDirectories(minesFolder);
Files.writeString(minesFolder.resolve("invalidmine.yml"), "selection.1: world;0;64;0;0.0;0.0\n");
// Create an invalid mine config file (missing required fields) assertDoesNotThrow(() -> mines.loadAll());
String invalidYaml = """ assertEquals(0, mines.getAll().size());
selection.1: world;0;64;0;0.0;0.0 verify(plugin.loLogger(), atLeastOnce()).error(anyString());
""";
Files.writeString(minesFolder.resolve("invalidmine.yml"), invalidYaml);
// Mock Bukkit and Scheduler
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic);
// Load all mines - should not throw, but should log error
assertDoesNotThrow(() -> mines.loadAll());
// Invalid mine should not be loaded
assertEquals(0, mines.getAll().size());
// Verify error was logged
verify(plugin.loLogger(), atLeastOnce()).error(anyString());
}
} }
@Test @Test
void testCreateNewMine() throws IOException { void testCreateNewMine() throws IOException {
// Mock Bukkit and Scheduler mines.create("newmine");
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic); assertEquals(1, mines.getAll().size());
assertTrue(mines.find("newmine").isPresent());
// Create a new mine Mine mine = mines.get("newmine");
mines.create("newmine"); assertNotNull(mine);
assertEquals("newmine", mine.getName());
// Verify mine was created assertTrue(Files.exists(tempDir.resolve("mines/newmine.yml")));
assertEquals(1, mines.getAll().size()); verify(plugin.loLogger()).info("Created mine: newmine");
assertTrue(mines.find("newmine").isPresent());
Mine mine = mines.get("newmine");
assertNotNull(mine);
assertEquals("newmine", mine.getName());
// Verify config file was created
Path configFile = tempDir.resolve("mines/newmine.yml");
assertTrue(Files.exists(configFile));
// Verify logger was called
verify(plugin.loLogger()).info("Created mine: newmine");
}
} }
@Test @Test
void testCreateMineCreatesFolder() throws IOException { void testCreateMineCreatesFolder() throws IOException {
// Ensure mines folder doesn't exist assertFalse(Files.exists(tempDir.resolve("mines")));
Path minesFolder = tempDir.resolve("mines"); mines.create("testmine");
assertFalse(Files.exists(minesFolder)); assertTrue(Files.isDirectory(tempDir.resolve("mines")));
// Mock Bukkit and Scheduler
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic);
// Create a new mine
mines.create("testmine");
// Verify mines folder was created
assertTrue(Files.exists(minesFolder));
assertTrue(Files.isDirectory(minesFolder));
}
} }
@Test @Test
void testCreateDuplicateMineThrowsException() throws IOException { void testCreateDuplicateMineThrowsException() throws IOException {
// Mock Bukkit and Scheduler mines.create("duplicate");
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) { () -> mines.create("duplicate"));
assertTrue(ex.getMessage().contains("Mine already exists"));
setupBukkitAndScheduler(bukkit, schedulerStatic);
// Create a mine
mines.create("duplicate");
// Try to create the same mine again
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
mines.create("duplicate");
});
assertTrue(exception.getMessage().contains("Mine already exists"));
}
} }
@Test @Test
void testCreateMineIsCaseInsensitive() throws IOException { void testCreateMineIsCaseInsensitive() throws IOException {
// Mock Bukkit and Scheduler mines.create("TestMine");
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) { () -> mines.create("testmine"));
assertTrue(ex.getMessage().contains("Mine already exists"));
setupBukkitAndScheduler(bukkit, schedulerStatic);
// Create a mine with mixed case
mines.create("TestMine");
// Try to create the same mine with different case
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
mines.create("testmine");
});
assertTrue(exception.getMessage().contains("Mine already exists"));
}
} }
@Test @Test
void testGetMineByName() throws IOException { void testGetMineByName() throws IOException {
// Mock Bukkit and Scheduler mines.create("testmine");
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) {
setupBukkitAndScheduler(bukkit, schedulerStatic); Mine mine = mines.get("testmine");
assertNotNull(mine);
assertEquals("testmine", mine.getName());
// Create a mine Mine mine2 = mines.get("TestMine");
mines.create("testmine"); assertNotNull(mine2);
assertSame(mine, mine2);
// Get mine by exact name
Mine mine = mines.get("testmine");
assertNotNull(mine);
assertEquals("testmine", mine.getName());
// Get mine by different case
Mine mine2 = mines.get("TestMine");
assertNotNull(mine2);
assertEquals("testmine", mine2.getName());
// Same mine instance
assertSame(mine, mine2);
}
} }
@Test @Test
void testGetNonExistentMineThrowsException() { void testGetNonExistentMineThrowsException() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
mines.get("nonexistent"); () -> mines.get("nonexistent"));
}); assertTrue(ex.getMessage().contains("Mine not found"));
assertTrue(exception.getMessage().contains("Mine not found"));
} }
@Test @Test
void testFindMineReturnsOptional() throws IOException { void testFindMineReturnsOptional() throws IOException {
// Mock Bukkit and Scheduler mines.create("testmine");
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class); assertTrue(mines.find("testmine").isPresent());
MockedStatic<Scheduler> schedulerStatic = mockStatic(Scheduler.class)) { assertFalse(mines.find("nonexistent").isPresent());
setupBukkitAndScheduler(bukkit, schedulerStatic);
// Create a mine
mines.create("testmine");
// Find existing mine
assertTrue(mines.find("testmine").isPresent());
// Find non-existent mine
assertFalse(mines.find("nonexistent").isPresent());
}
} }
} }

View file

@ -1,6 +1,9 @@
package dev.loki.lomines.data; package dev.loki.lomines.data;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.data.stats.Leaderboard;
import dev.loki.lomines.data.stats.LeaderboardEntry;
import dev.loki.lomines.data.stats.StatsManager;
import dev.lolib.core.LoLogger; import dev.lolib.core.LoLogger;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -48,7 +51,7 @@ class LeaderboardCacheIntegrationTest {
// Build cache // Build cache
List<LeaderboardEntry> top1 = leaderboard.getTopTotal(10); List<LeaderboardEntry> top1 = leaderboard.getTopTotal(10);
assertEquals(1, top1.size(), "Should have 1 player initially"); assertEquals(1, top1.size(), "Should have 1 player initially");
assertEquals(player1, top1.get(0).getPlayerId()); assertEquals(player1, top1.get(0).playerId());
// Increment blocks for player2 - this should invalidate cache // Increment blocks for player2 - this should invalidate cache
statsManager.incrementBlocks(player2, "mine1"); statsManager.incrementBlocks(player2, "mine1");
@ -58,8 +61,8 @@ class LeaderboardCacheIntegrationTest {
assertEquals(2, top2.size(), "Should have 2 players after increment"); assertEquals(2, top2.size(), "Should have 2 players after increment");
// Verify order (player1 has 100, player2 has 1) // Verify order (player1 has 100, player2 has 1)
assertEquals(player1, top2.get(0).getPlayerId(), "Player1 should still be first"); assertEquals(player1, top2.get(0).playerId(), "Player1 should still be first");
assertEquals(player2, top2.get(1).getPlayerId(), "Player2 should be second"); assertEquals(player2, top2.get(1).playerId(), "Player2 should be second");
} }
@Test @Test
@ -73,7 +76,7 @@ class LeaderboardCacheIntegrationTest {
// Build cache // Build cache
List<LeaderboardEntry> top1 = leaderboard.getTopTotal(10); List<LeaderboardEntry> top1 = leaderboard.getTopTotal(10);
assertEquals(player2, top1.get(0).getPlayerId(), "Player2 should be first initially"); assertEquals(player2, top1.get(0).playerId(), "Player2 should be first initially");
// Increment player1 many times to overtake player2 // Increment player1 many times to overtake player2
for (int i = 0; i < 60; i++) { for (int i = 0; i < 60; i++) {
@ -82,10 +85,10 @@ class LeaderboardCacheIntegrationTest {
// Get top again - cache should be rebuilt with new order // Get top again - cache should be rebuilt with new order
List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10); List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10);
assertEquals(player1, top2.get(0).getPlayerId(), "Player1 should be first after increments"); assertEquals(player1, top2.get(0).playerId(), "Player1 should be first after increments");
assertEquals(110, top2.get(0).getCount(), "Player1 should have 110 blocks"); assertEquals(110, top2.get(0).count(), "Player1 should have 110 blocks");
assertEquals(player2, top2.get(1).getPlayerId(), "Player2 should be second"); assertEquals(player2, top2.get(1).playerId(), "Player2 should be second");
assertEquals(100, top2.get(1).getCount(), "Player2 should still have 100 blocks"); assertEquals(100, top2.get(1).count(), "Player2 should still have 100 blocks");
} }
@Test @Test
@ -104,7 +107,7 @@ class LeaderboardCacheIntegrationTest {
// Get top - should show updated count // Get top - should show updated count
List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10); List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10);
assertEquals(1, top2.size()); assertEquals(1, top2.size());
assertEquals(5, top2.get(0).getCount(), "Should have 5 blocks after 5 increments"); assertEquals(5, top2.get(0).count(), "Should have 5 blocks after 5 increments");
} }
@Test @Test
@ -150,7 +153,7 @@ class LeaderboardCacheIntegrationTest {
// Get with limit 2 - should use same cache // Get with limit 2 - should use same cache
List<LeaderboardEntry> top2 = leaderboard.getTopTotal(2); List<LeaderboardEntry> top2 = leaderboard.getTopTotal(2);
assertEquals(2, top2.size()); assertEquals(2, top2.size());
assertEquals(player3, top2.get(0).getPlayerId(), "Should have highest player"); assertEquals(player3, top2.get(0).playerId(), "Should have highest player");
assertEquals(player2, top2.get(1).getPlayerId(), "Should have second highest player"); assertEquals(player2, top2.get(1).playerId(), "Should have second highest player");
} }
} }

View file

@ -1,6 +1,10 @@
package dev.loki.lomines.data; package dev.loki.lomines.data;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
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.lolib.core.LoLogger; import dev.lolib.core.LoLogger;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -51,8 +55,8 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopTotal(10); List<LeaderboardEntry> top = leaderboard.getTopTotal(10);
assertEquals(1, top.size()); assertEquals(1, top.size());
assertEquals(playerId, top.get(0).getPlayerId()); assertEquals(playerId, top.get(0).playerId());
assertEquals(100, top.get(0).getCount()); assertEquals(100, top.get(0).count());
} }
@Test @Test
@ -67,9 +71,9 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopTotal(10); List<LeaderboardEntry> top = leaderboard.getTopTotal(10);
assertEquals(3, top.size()); assertEquals(3, top.size());
assertEquals(player2, top.get(0).getPlayerId()); // 200 blocks assertEquals(player2, top.get(0).playerId()); // 200 blocks
assertEquals(player3, top.get(1).getPlayerId()); // 100 blocks assertEquals(player3, top.get(1).playerId()); // 100 blocks
assertEquals(player1, top.get(2).getPlayerId()); // 50 blocks assertEquals(player1, top.get(2).playerId()); // 50 blocks
} }
@Test @Test
@ -93,7 +97,7 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopTotal(10); List<LeaderboardEntry> top = leaderboard.getTopTotal(10);
assertEquals(1, top.size(), "Should filter out players with 0 blocks"); assertEquals(1, top.size(), "Should filter out players with 0 blocks");
assertEquals(player1, top.get(0).getPlayerId()); assertEquals(player1, top.get(0).playerId());
} }
@Test @Test
@ -108,7 +112,7 @@ class LeaderboardTest {
List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10); List<LeaderboardEntry> top2 = leaderboard.getTopTotal(10);
assertEquals(top1.size(), top2.size()); assertEquals(top1.size(), top2.size());
assertEquals(top1.get(0).getPlayerId(), top2.get(0).getPlayerId()); assertEquals(top1.get(0).playerId(), top2.get(0).playerId());
} }
@Test @Test
@ -129,7 +133,7 @@ class LeaderboardTest {
// Should rebuild cache with new player // Should rebuild cache with new player
List<LeaderboardEntry> top = leaderboard.getTopTotal(10); List<LeaderboardEntry> top = leaderboard.getTopTotal(10);
assertEquals(2, top.size(), "Cache should be rebuilt after invalidation"); assertEquals(2, top.size(), "Cache should be rebuilt after invalidation");
assertEquals(player2, top.get(0).getPlayerId(), "New player should be first"); assertEquals(player2, top.get(0).playerId(), "New player should be first");
} }
@Test @Test
@ -144,9 +148,9 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10); List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10);
assertEquals(3, top.size()); assertEquals(3, top.size());
assertEquals(player2, top.get(0).getPlayerId()); // 200 blocks assertEquals(player2, top.get(0).playerId()); // 200 blocks
assertEquals(player3, top.get(1).getPlayerId()); // 100 blocks assertEquals(player3, top.get(1).playerId()); // 100 blocks
assertEquals(player1, top.get(2).getPlayerId()); // 50 blocks assertEquals(player1, top.get(2).playerId()); // 50 blocks
} }
@Test @Test
@ -159,7 +163,7 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10); List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10);
assertEquals(1, top.size(), "Should filter out players with 0 blocks"); assertEquals(1, top.size(), "Should filter out players with 0 blocks");
assertEquals(player1, top.get(0).getPlayerId()); assertEquals(player1, top.get(0).playerId());
} }
@Test @Test
@ -172,7 +176,7 @@ class LeaderboardTest {
List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10); List<LeaderboardEntry> top = leaderboard.getTopByMine("mine1", 10);
assertEquals(1, top.size(), "Should only include players with blocks in specified mine"); assertEquals(1, top.size(), "Should only include players with blocks in specified mine");
assertEquals(player1, top.get(0).getPlayerId()); assertEquals(player1, top.get(0).playerId());
} }
@Test @Test

View file

@ -1,6 +1,8 @@
package dev.loki.lomines.data; package dev.loki.lomines.data;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.data.stats.PlayerStats;
import dev.loki.lomines.data.stats.StatsManager;
import dev.lolib.core.LoLogger; import dev.lolib.core.LoLogger;
import dev.lolib.scheduler.ScheduledTask; import dev.lolib.scheduler.ScheduledTask;
import dev.lolib.scheduler.Scheduler; import dev.lolib.scheduler.Scheduler;

View file

@ -33,7 +33,8 @@ class UIConfigTest {
true, true,
"<red>{mine}</red>", "<red>{mine}</red>",
100.0, 100.0,
"HH:mm:ss" "HH:mm:ss",
null
); );
assertTrue(config.actionBarEnabled()); assertTrue(config.actionBarEnabled());
@ -44,7 +45,7 @@ class UIConfigTest {
@Test @Test
void testNullFormatDefaults() { void testNullFormatDefaults() {
UIConfig config = new UIConfig(true, null, 50.0, null); UIConfig config = new UIConfig(true, null, 50.0, null, null);
assertEquals(UIConfig.DEFAULT_ACTIONBAR_FORMAT, config.actionBarFormat()); assertEquals(UIConfig.DEFAULT_ACTIONBAR_FORMAT, config.actionBarFormat());
assertEquals(UIConfig.DEFAULT_TIMER_FORMAT, config.timerFormat()); assertEquals(UIConfig.DEFAULT_TIMER_FORMAT, config.timerFormat());
@ -52,7 +53,7 @@ class UIConfigTest {
@Test @Test
void testBlankFormatDefaults() { void testBlankFormatDefaults() {
UIConfig config = new UIConfig(true, " ", 50.0, " "); UIConfig config = new UIConfig(true, " ", 50.0, " ", null);
assertEquals(UIConfig.DEFAULT_ACTIONBAR_FORMAT, config.actionBarFormat()); assertEquals(UIConfig.DEFAULT_ACTIONBAR_FORMAT, config.actionBarFormat());
assertEquals(UIConfig.DEFAULT_TIMER_FORMAT, config.timerFormat()); assertEquals(UIConfig.DEFAULT_TIMER_FORMAT, config.timerFormat());
@ -60,14 +61,14 @@ class UIConfigTest {
@Test @Test
void testNegativeRangeClamped() { void testNegativeRangeClamped() {
UIConfig config = new UIConfig(true, "test", -10.0, "mm:ss"); UIConfig config = new UIConfig(true, "test", -10.0, "mm:ss", null);
assertEquals(1.0, config.actionBarRange()); assertEquals(1.0, config.actionBarRange());
} }
@Test @Test
void testRangeSquared() { void testRangeSquared() {
UIConfig config = new UIConfig(true, "test", 50.0, "mm:ss"); UIConfig config = new UIConfig(true, "test", 50.0, "mm:ss", null);
assertEquals(2500.0, config.actionBarRangeSquared(), 0.001); assertEquals(2500.0, config.actionBarRangeSquared(), 0.001);
} }
@ -85,7 +86,7 @@ class UIConfigTest {
@Test @Test
void testFormatTimerMmSs() { void testFormatTimerMmSs() {
UIConfig config = new UIConfig(true, "", 0, "mm:ss"); UIConfig config = new UIConfig(true, "", 0, "mm:ss", null);
assertEquals("00:30", config.formatTimer(30)); assertEquals("00:30", config.formatTimer(30));
assertEquals("05:00", config.formatTimer(300)); assertEquals("05:00", config.formatTimer(300));
@ -94,7 +95,7 @@ class UIConfigTest {
@Test @Test
void testFormatTimerHhMmSs() { void testFormatTimerHhMmSs() {
UIConfig config = new UIConfig(true, "", 0, "HH:mm:ss"); UIConfig config = new UIConfig(true, "", 0, "HH:mm:ss", null);
assertEquals("0:00:30", config.formatTimer(30)); assertEquals("0:00:30", config.formatTimer(30));
assertEquals("0:05:00", config.formatTimer(300)); assertEquals("0:05:00", config.formatTimer(300));

View file

@ -1,5 +1,7 @@
package dev.loki.lomines.util; package dev.loki.lomines.util;
import dev.loki.lomines.util.format.ChunkUtils;
import dev.loki.lomines.util.location.Cuboid;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;

View file

@ -1,6 +1,7 @@
package dev.loki.lomines.util; package dev.loki.lomines.util;
import dev.loki.lomines.data.config.parser.ConfigParseException; import dev.loki.lomines.data.config.parser.ConfigParseException;
import dev.loki.lomines.util.location.LocationParser;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;

View file

@ -1,7 +1,9 @@
package dev.loki.lomines.util; package dev.loki.lomines.util;
import dev.loki.lomines.util.format.TimeFormatter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;

View file

@ -1,5 +1,6 @@
package dev.loki.lomines.util.selection; package dev.loki.lomines.util.selection;
import dev.loki.lomines.util.location.Cuboid;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;