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

@ -1,21 +1,11 @@
package dev.loki.lomines;
import dev.loki.lomines.command.admin.AdminCommands;
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.admin.*;
import dev.loki.lomines.command.common.LoMinesTabCompleter;
import dev.loki.lomines.command.player.PlayerCommands;
import dev.loki.lomines.command.player.TeleportCommand;
import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.listener.BlockBreakListener;
import dev.loki.lomines.listener.GroupGuiListener;
import dev.loki.lomines.listener.MineEditGuiListener;
import dev.loki.lomines.listener.PlayerInteractListener;
import dev.loki.lomines.listener.PlayerJoinListener;
import dev.loki.lomines.listener.*;
import dev.lolib.commands.CommandManager;
/**

View file

@ -106,8 +106,8 @@ public final class BukkitBlockSetter extends BlockSetter {
currentWeight += entry.getValue();
if (random <= currentWeight) {
// BlockKey is already validated to be a block
if (entry.getKey() instanceof BlockKey.Vanilla vanilla) {
return Bukkit.createBlockData(vanilla.material());
if (entry.getKey() instanceof BlockKey.Vanilla(Material material)) {
return Bukkit.createBlockData(material);
}
// Fallback for non-vanilla keys in vanilla setter (shouldn't happen)
return Bukkit.createBlockData(Material.STONE);
@ -116,8 +116,8 @@ public final class BukkitBlockSetter extends BlockSetter {
// Fallback to first entry
BlockKey firstKey = weights.keySet().iterator().next();
if (firstKey instanceof BlockKey.Vanilla vanilla) {
return Bukkit.createBlockData(vanilla.material());
if (firstKey instanceof BlockKey.Vanilla(Material material)) {
return Bukkit.createBlockData(material);
}
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.core.mine.Mine;
import dev.loki.lomines.core.mine.MineConfig;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import dev.loki.lomines.data.config.MineConfig;
import dev.lolib.commands.annotation.Arg;
import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import java.util.List;
import java.util.Map;
/**
* Command to copy mine configuration from one mine to another.
*/
@ -53,49 +50,25 @@ public class CopyCommand {
var sourceConfig = fromMine.getConfig();
var targetConfig = toMine.getConfig();
// Copy blocks configuration
targetConfig.blocks().weights().clear();
targetConfig.blocks().weights().putAll(sourceConfig.blocks().weights());
// Keep target region/name, copy all other sections from source.
MineConfig newTargetConfig = MineConfig.builder(toMine.getName())
.region(targetConfig.region())
.blocks(sourceConfig.blocks())
.reset(sourceConfig.reset())
.rewards(sourceConfig.rewards())
.teleport(sourceConfig.teleport())
.ui(sourceConfig.ui())
.worldGuard(sourceConfig.worldGuard())
.playerSpawn(sourceConfig.playerSpawn())
.build();
// Copy reset settings
var targetReset = targetConfig.reset();
var sourceReset = sourceConfig.reset();
targetReset.intervalSeconds(sourceReset.intervalSeconds());
targetReset.percentTrigger(sourceReset.percentTrigger());
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);
try {
plugin.getMines().updateMineConfig(toMine.getName(), newTargetConfig);
} catch (Exception e) {
sender.sendMessage(Component.text("Ошибка сохранения: " + e.getMessage(), NamedTextColor.RED));
return;
}
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() +
" §aв §f" + toMine.getName()));
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.core.mine.Mine;
import dev.loki.lomines.integration.hologram.HologramManager;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import dev.lolib.commands.annotation.Arg;
import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* Commands for managing holograms above mines.
*/
class HologramCommands {
public class HologramCommands {
private final LoMinesPlugin plugin;
private final HologramManager hologramManager;
HologramCommands(LoMinesPlugin plugin) {
public HologramCommands(LoMinesPlugin plugin) {
this.plugin = plugin;
this.hologramManager = plugin.getHologramManager();
}
@ -55,6 +54,6 @@ class HologramCommands {
}
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.data.config.block.BlockKey;
import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import dev.lolib.commands.annotation.Arg;
import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
@ -65,10 +64,10 @@ public class InfoCommand {
sender.sendMessage(Component.text("§6§lБлоки:"));
Map<BlockKey, Double> weights = config.blocks().weights();
weights.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(10)
.forEach(e -> sender.sendMessage(Component.text(
"§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%")));
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(10)
.forEach(e -> sender.sendMessage(Component.text(
"§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%")));
if (weights.size() > 10) {
sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков"));
}

View file

@ -2,7 +2,7 @@ package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
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.MessageFormatter;
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.region.RegionConfig;
import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import dev.lolib.commands.annotation.Arg;
import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@ -73,14 +72,15 @@ public class RegionCommands {
}
var wandManager = plugin.getGroupWandManager();
var session = wandManager.getSession(player);
if (!session.isComplete()) {
var session = wandManager.getSession(player.getUniqueId());
int slot = session.getActiveSlot();
var pos1 = session.getPos1(slot);
var pos2 = session.getPos2(slot);
if (pos1 == null || pos2 == null) {
player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка"));
return;
}
Cuboid newRegion = session.toCuboid();
Cuboid newRegion = new Cuboid(pos1, pos2);
String mineWorld = mine.getConfig().region().worldName();
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.getVolume() + " §7блоков"));
session.clear();
session.clearCorners(slot);
}
/**
@ -144,18 +144,22 @@ public class RegionCommands {
private void updateMineRegions(Mine mine, RegionConfig newConfig) {
var oldConfig = mine.getConfig();
var newMineConfig = new MineConfig(
oldConfig.name(),
newConfig,
oldConfig.blocks(),
oldConfig.reset(),
oldConfig.teleport(),
oldConfig.playerSpawn(),
oldConfig.rewards(),
oldConfig.teleport(),
oldConfig.ui(),
oldConfig.worldGuard()
oldConfig.worldGuard(),
oldConfig.playerSpawn()
);
// Update and save
plugin.getMines().updateMineConfig(mine.getName(), newMineConfig);
mine.save();
try {
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 {
private final LoMinesPlugin plugin;
private static final List<String> ALL_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "wand", "group",
"stats", "top", "maskscan", "edit", "setteleport", "setspawn",
"clearspawn", "info", "tp", "copy", "regions", "addregion", "removeregion", "help"
);
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 final LoMinesPlugin plugin;
public LoMinesTabCompleter(LoMinesPlugin plugin) {
this.plugin = plugin;
@ -49,9 +47,9 @@ public class LoMinesTabCompleter implements TabCompleter {
private boolean isLoMinesCommand(String name) {
return name.equalsIgnoreCase("lm") ||
name.equalsIgnoreCase("lomines") ||
name.equalsIgnoreCase("mine") ||
name.equalsIgnoreCase("mines");
name.equalsIgnoreCase("lomines") ||
name.equalsIgnoreCase("mine") ||
name.equalsIgnoreCase("mines");
}
private List<String> completeSubcommands(CommandSender sender, String partial) {
@ -68,8 +66,7 @@ public class LoMinesTabCompleter implements TabCompleter {
return switch (subcommand) {
case "create" -> completeCreate(partial);
case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info",
"regions", "addregion" ->
filterStartsWith(getMineNames(), partial);
"regions", "addregion" -> filterStartsWith(getMineNames(), partial);
case "reset" -> completeReset(partial);
case "stats" -> completeStats(sender, 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.core.mine.Mine;
import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import dev.lolib.commands.annotation.Arg;
import dev.lolib.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;

View file

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

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.core.mine.Mine;
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 java.io.IOException;
@ -14,19 +14,19 @@ import java.util.stream.Stream;
/**
* Loads mine configurations from disk.
*/
final class MineLoader {
public final class MineLoader {
private final LoMinesPlugin plugin;
private final MineFileManager fileManager;
private final MineRepository repository;
MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
public MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
this.plugin = plugin;
this.fileManager = fileManager;
this.repository = repository;
}
void loadAll() throws IOException {
public void loadAll() throws IOException {
repository.clear();
Path minesFolder = fileManager.getMinesFolder();

View file

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

View file

@ -1,11 +1,10 @@
package dev.loki.lomines.core.mine;
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.MineFileManager;
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.integration.worldguard.WorldGuardRegionService;
import org.bukkit.Location;
@ -122,7 +121,11 @@ public final class Mines {
}
// 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
try {

View file

@ -1,7 +1,7 @@
package dev.loki.lomines.core.service;
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.util.ValidationUtils;
import dev.loki.lomines.util.location.Cuboid;
@ -57,8 +57,8 @@ public final class MaskScanService {
}
private Material parseMarkerMaterial(BlockKey key) {
if (key instanceof BlockKey.Vanilla vanilla) {
return vanilla.material();
if (key instanceof BlockKey.Vanilla(Material material)) {
return 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

View file

@ -1,6 +1,6 @@
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.block.BlockConfig;
import dev.loki.lomines.data.config.block.BlockKey;
@ -82,7 +82,11 @@ public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
.build();
// 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.
*
* @param name the mine name
* @param name the mine name
* @param config the configuration to save
* @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.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 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.region.RegionConfigLoader;
import dev.loki.lomines.data.config.loader.region.WorldGuardConfigLoader;
import dev.loki.lomines.data.config.loader.reset.ResetConfigLoader;
import dev.loki.lomines.data.config.loader.reward.RewardConfigLoader;
import dev.loki.lomines.data.config.loader.system.ResetConfigLoader;
import dev.loki.lomines.data.config.loader.system.UIConfigLoader;
import org.bukkit.configuration.file.YamlConfiguration;
@ -156,6 +156,7 @@ public final class ConfigLoader {
public ConfigLoadException(String message) {
super(message);
}
public ConfigLoadException(String message, Throwable 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.
*/
@ -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.
* If playerSpawn is not set, falls back to teleport location.

View file

@ -1,5 +1,7 @@
package dev.loki.lomines.data.config.block;
import org.bukkit.Material;
import java.util.Collections;
import java.util.HashMap;
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 {
/**
* 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.
*
@ -43,6 +37,12 @@ public sealed interface BlockKey permits BlockKey.Vanilla, BlockKey.Oraxen, Bloc
return new Vanilla(material);
}
/**
* Serializes this key to string format for YAML storage.
* Format: "stone" (vanilla), "oraxen:my_block", "itemsadder:custom_block"
*/
String serialize();
// --- Implementations ---
record Vanilla(Material material) implements BlockKey {

View file

@ -80,9 +80,11 @@ public final class RegionConfigLoader {
public void save(YamlConfiguration yaml, RegionConfig region) {
int i = 1;
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++;
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++;
}
}

View file

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

View file

@ -36,7 +36,8 @@ public final class RewardConfigLoader {
}
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<?> blockList = (List<?>) map.get("blocks");
@ -56,9 +57,17 @@ public final class RewardConfigLoader {
}
}
@SuppressWarnings("unchecked")
List<String> commands = (List<String>) map.getOrDefault("commands", List.of());
boolean preventDrops = (Boolean) map.getOrDefault("prevent-drops", false);
List<String> commands = new ArrayList<>();
Object commandsObj = map.get("commands");
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);
}

View file

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

View file

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

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;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Configuration for mine reset behavior.
@ -41,6 +39,26 @@ public record ResetConfig(
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).
*/
@ -69,26 +87,6 @@ public record ResetConfig(
return percentEnabled && percentTrigger > 0;
}
/**
* Default config: 5 minutes, 10% trigger disabled.
*/
public static ResetConfig defaults() {
return new ResetConfig(
DEFAULT_INTERVAL,
DEFAULT_PERCENT_TRIGGER,
false,
List.of(),
""
);
}
/**
* Builder for fluent construction.
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Duration interval = DEFAULT_INTERVAL;
private double percentTrigger = DEFAULT_PERCENT_TRIGGER;
@ -96,6 +94,26 @@ public record ResetConfig(
private List<String> commands = List.of();
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) {
this.interval = interval;
return this;
@ -134,25 +152,5 @@ public record ResetConfig(
public ResetConfig build() {
return new ResetConfig(interval, percentTrigger, percentEnabled, commands, broadcastMessage);
}
private static Duration parseDuration(String s) {
s = s.trim().toLowerCase();
try {
if (s.endsWith("s")) {
return Duration.ofSeconds(Long.parseLong(s.substring(0, s.length() - 1)));
} else if (s.endsWith("m")) {
return Duration.ofMinutes(Long.parseLong(s.substring(0, s.length() - 1)));
} else if (s.endsWith("h")) {
return Duration.ofHours(Long.parseLong(s.substring(0, s.length() - 1)));
} else if (s.endsWith("d")) {
return Duration.ofDays(Long.parseLong(s.substring(0, s.length() - 1)));
} else {
// Assume seconds if no suffix
return Duration.ofSeconds(Long.parseLong(s));
}
} catch (NumberFormatException e) {
return DEFAULT_INTERVAL;
}
}
}
}

View file

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

View file

@ -12,10 +12,7 @@ import java.util.Optional;
* Supports multiple spawn points for a single mine.
* Used to teleport players out when they are suffocating after mine reset.
*/
public final class PlayerSpawnConfig {
private final boolean enabled;
private final List<Location> locations;
public record PlayerSpawnConfig(boolean enabled, List<Location> locations) {
public PlayerSpawnConfig(boolean enabled, List<Location> locations) {
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).
*/
@ -84,6 +53,36 @@ public final class PlayerSpawnConfig {
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.
*/

View file

@ -3,7 +3,6 @@ package dev.loki.lomines.data.config.teleport;
import org.bukkit.Location;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -12,10 +11,7 @@ import java.util.Optional;
* Configuration for mine teleportation on reset.
* Supports multiple teleport locations for a single mine.
*/
public final class TeleportConfig {
private final boolean enabled;
private final List<Location> locations;
public record TeleportConfig(boolean enabled, List<Location> locations) {
public TeleportConfig(boolean enabled, List<Location> locations) {
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.
*/
@ -84,6 +52,36 @@ public final class TeleportConfig {
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.
*/

View file

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

View file

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

View file

@ -13,11 +13,12 @@ import java.util.List;
/**
* 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);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
@ -32,17 +33,17 @@ final class ItemStackFactory {
return stack;
}
static String loc(Location l) {
public static String loc(Location l) {
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());
}
static String trunc(String s, int max) {
public static String trunc(String s, int max) {
if (s == null || s.isEmpty()) return "§7нет";
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);
ItemMeta meta = pane.getItemMeta();
if (meta != null) {

View file

@ -28,8 +28,8 @@ public final class ConfirmDeleteGui {
/**
* Opens the confirmation GUI for deleting a mine.
*
* @param plugin the plugin instance
* @param player the player to open for
* @param plugin the plugin instance
* @param player the player to open for
* @param mineName the name of the mine to delete
*/
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[] MINE_SLOTS = {10, 11, 12, 19, 20, 21, 28, 29, 30};
private GroupCreateGui() {}
private GroupCreateGui() {
}
public static void open(LoMinesPlugin plugin, Player player) {
GroupCreateGuiHolder holder = new GroupCreateGuiHolder(player.getUniqueId());

View file

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

View file

@ -10,7 +10,8 @@ import org.bukkit.inventory.ItemStack;
*/
final class BlocksGuiItems {
private BlocksGuiItems() {}
private BlocksGuiItems() {
}
static ItemStack blockItem(BlockKey key, double weight) {
Material material = getMaterialForKey(key);
@ -18,40 +19,40 @@ final class BlocksGuiItems {
double percent = weight * 100.0;
return ItemStackFactory.create(material, "§a§l" + name,
"§8───────────────",
"§7Вес: §f" + String.format("%.1f%%", percent),
"",
"§e▸ ЛКМ §7+5%",
"§e▸ ПКМ §7-5%",
"§e▸ Shift+ЛКМ §7+1%",
"§e▸ Shift+ПКМ §7-1% §8(удалить если 0%)",
"",
"§8ID: §7" + key.serialize()
"§8───────────────",
"§7Вес: §f" + String.format("%.1f%%", percent),
"",
"§e▸ ЛКМ §7+5%",
"§e▸ ПКМ §7-5%",
"§e▸ Shift+ЛКМ §7+1%",
"§e▸ Shift+ПКМ §7-1% §8(удалить если 0%)",
"",
"§8ID: §7" + key.serialize()
);
}
static ItemStack addBlockItem() {
return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить блок",
"§8───────────────",
"§7Добавить новый блок",
"§7в конфигурацию",
"",
"§e▸ Нажмите для выбора материала"
"§8───────────────",
"§7Добавить новый блок",
"§7в конфигурацию",
"",
"§e▸ Нажмите для выбора материала"
);
}
static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lНазад",
"§8───────────────",
"§7Вернуться в редактор шахты",
"",
"§e▸ Нажмите для возврата"
"§8───────────────",
"§7Вернуться в редактор шахты",
"",
"§e▸ Нажмите для возврата"
);
}
static Material getMaterialForKey(BlockKey key) {
if (key instanceof BlockKey.Vanilla vanilla) {
return vanilla.material();
if (key instanceof BlockKey.Vanilla(Material material)) {
return material;
}
if (key instanceof BlockKey.Oraxen) {
return Material.NETHER_STAR;
@ -80,8 +81,8 @@ final class BlocksGuiItems {
for (String word : input.split(" ")) {
if (!word.isEmpty()) {
result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1))
.append(" ");
.append(word.substring(1))
.append(" ");
}
}
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_COMMANDS = 16;
private ResetGui() {}
private ResetGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) {
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,
String mineName, boolean leftClick, boolean rightClick,
boolean shiftClick) {
String mineName, boolean leftClick, boolean rightClick,
boolean shiftClick) {
if (rawSlot < 0 || rawSlot >= SIZE) return false;
if (rawSlot == SLOT_BACK) {
@ -122,7 +123,7 @@ public final class ResetGui {
}
private static void togglePercentTrigger(LoMinesPlugin plugin, Player player,
String mineName, boolean leftClick, boolean rightClick) {
String mineName, boolean leftClick, boolean rightClick) {
if (leftClick) {
player.sendMessage(Component.text("§aТриггер по % переключен."));
} else if (rightClick) {

View file

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

View file

@ -2,8 +2,10 @@ package dev.loki.lomines.gui.mine.edit.rewards;
import dev.loki.lomines.LoMinesPlugin;
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.mine.holder.RewardsGuiHolder;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@ -25,7 +27,8 @@ public final class RewardsGui {
private static final int SLOT_BACK = 49;
private static final int SLOT_ADD_REWARD = 52;
private RewardsGui() {}
private RewardsGui() {
}
public static void open(LoMinesPlugin plugin, Player player, String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -72,47 +75,47 @@ public final class RewardsGui {
int commandsCount = entry.commands().size();
String blocksList = String.join(", ", entry.blocks().stream()
.limit(3)
.map(b -> b.name().toLowerCase())
.map(b -> b.serialize().toLowerCase())
.toList());
if (entry.blocks().size() > 3) {
blocksList += "...";
}
return ItemStackFactory.create(Material.GOLD_INGOT, "§a§lНаграда #" + index,
"§8───────────────",
"§7Шанс: §f" + String.format("%.1f%%", entry.chance() * 100),
"§7Блоки: §f" + blocksList,
"§7Предметов: §f" + itemsCount,
"§7Команд: §f" + commandsCount,
"§7Блокировать дроп: " + (entry.preventVanillaDrops() ? "§aда" : "§7нет"),
"",
"§e▸ ЛКМ §7редактировать",
"§e▸ ПКМ §7удалить"
"§8───────────────",
"§7Шанс: §f" + String.format("%.1f%%", entry.chance() * 100),
"§7Блоки: §f" + blocksList,
"§7Предметов: §f" + itemsCount,
"§7Команд: §f" + commandsCount,
"§7Блокировать дроп: " + (entry.preventVanillaDrops() ? "§aда" : "§7нет"),
"",
"§e▸ ЛКМ §7редактировать",
"§e▸ ПКМ §7удалить"
);
}
private static ItemStack addRewardItem() {
return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить награду",
"§8───────────────",
"§7Создать новую запись",
"§7награды",
"",
"§e▸ Нажмите для добавления"
"§8───────────────",
"§7Создать новую запись",
"§7награды",
"",
"§e▸ Нажмите для добавления"
);
}
private static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lНазад",
"§8───────────────",
"§7Вернуться в редактор шахты",
"",
"§e▸ Нажмите для возврата"
"§8───────────────",
"§7Вернуться в редактор шахты",
"",
"§e▸ Нажмите для возврата"
);
}
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot,
String mineName, boolean leftClick, boolean rightClick,
int rewardIndex) {
String mineName, boolean leftClick, boolean rightClick,
int rewardIndex) {
if (rawSlot < 0 || rawSlot >= SIZE) return false;
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.core.mine.Mine;
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.mine.edit.blocks.BlocksGui;
import dev.loki.lomines.gui.mine.edit.reset.ResetGui;
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.format.NamedTextColor;
import org.bukkit.Bukkit;
@ -21,9 +21,6 @@ import org.bukkit.inventory.Inventory;
*/
public final class MineEditGui {
private static final int SIZE = 54;
private static final String TITLE_PREFIX = "Редактор шахты: ";
// Slot constants
public static final int SLOT_BACK = 49;
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_DELETE = 34;
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) {
Mine mine = plugin.getMines().find(mineName).orElse(null);
@ -45,8 +45,7 @@ public final class MineEditGui {
return;
}
MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName);
Inventory inv = Bukkit.createInventory(holder, SIZE,
Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
Inventory inv = Bukkit.createInventory(holder, SIZE, Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
holder.setInventory(inv);
fill(plugin, mine, inv);
player.openInventory(inv);
@ -118,3 +117,4 @@ public final class MineEditGui {
}
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.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 org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@ -21,7 +21,8 @@ final class MineEditItems {
private static final String SAVE = "§a▸ Нажмите для сохранения";
private static final String DEL = "§c▸ Нажмите для удаления";
private MineEditItems() {}
private MineEditItems() {
}
static ItemStack filler() {
return ItemStackFactory.filler();
@ -39,7 +40,7 @@ final class MineEditItems {
lore.add("");
lore.add("§7Топ блоков:");
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(EDIT);
@ -48,14 +49,14 @@ final class MineEditItems {
static ItemStack regionsItem(MineConfig config) {
return ItemStackFactory.create(Material.COMPASS, "§a§lРегионы",
SEP,
"§7Настройки:",
" §fКоличество: §7" + config.region().regionCount(),
" §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков",
"",
" §fМир: §7" + config.worldName(),
"",
"§8Изменяется через палочку"
SEP,
"§7Настройки:",
" §fКоличество: §7" + config.region().regionCount(),
" §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков",
"",
" §fМир: §7" + config.worldName(),
"",
"§8Изменяется через палочку"
);
}
@ -122,39 +123,39 @@ final class MineEditItems {
static ItemStack uiItem(MineConfig config) {
return ItemStackFactory.create(Material.PAINTING, "§a§lИнтерфейс",
SEP,
"§7Настройки:",
" §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"),
" §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков",
"",
"§7Формат ActionBar:",
" §8" + ItemStackFactory.trunc(config.ui().actionBarFormat(), 28),
"",
"§7Формат таймера: §8" + config.ui().timerFormat(),
"",
EDIT
SEP,
"§7Настройки:",
" §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"),
" §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков",
"",
"§7Формат ActionBar:",
" §8" + ItemStackFactory.trunc(config.ui().actionBarFormat(), 28),
"",
"§7Формат таймера: §8" + config.ui().timerFormat(),
"",
EDIT
);
}
static ItemStack saveItem() {
return ItemStackFactory.create(Material.LIME_DYE, "§a§lСохранить изменения",
SEP,
"§7Сохраняет конфигурацию шахты",
"§7в файл на диске",
"",
SAVE
SEP,
"§7Сохраняет конфигурацию шахты",
"§7в файл на диске",
"",
SAVE
);
}
static ItemStack deleteItem(String mineName) {
return ItemStackFactory.create(Material.TNT, "§c§lУдалить шахту",
SEP,
"§c§lВнимание!",
"§cЭто действие нельзя отменить!",
"",
"§7Шахта: §f" + mineName,
"",
DEL
SEP,
"§c§lВнимание!",
"§cЭто действие нельзя отменить!",
"",
"§7Шахта: §f" + mineName,
"",
DEL
);
}
@ -166,24 +167,24 @@ final class MineEditItems {
int filled = (int) Math.round(percent / 100.0 * 10);
String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(10 - filled);
return ItemStackFactory.create(Material.BOOK, "§b§lИнформация",
SEP,
"§7Базовые данные:",
" §fНазвание: §7" + mine.getName(),
" §fМир: §7" + mine.getConfig().worldName(),
"",
"§7Состояние заполнения:",
" §fБлоков: " + c + blocks + "§8/§7" + total,
" §fПроцент: " + c + String.format("%.1f%%", percent),
" " + bar
SEP,
"§7Базовые данные:",
" §fНазвание: §7" + mine.getName(),
" §fМир: §7" + mine.getConfig().worldName(),
"",
"§7Состояние заполнения:",
" §fБлоков: " + c + blocks + "§8/§7" + total,
" §fПроцент: " + c + String.format("%.1f%%", percent),
" " + bar
);
}
static ItemStack backItem() {
return ItemStackFactory.create(Material.ARROW, "§c§lЗакрыть",
SEP,
"§7Закрыть редактор",
"",
CLOSE
SEP,
"§7Закрыть редактор",
"",
CLOSE
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.integration.worldguard;
import java.util.List;
import java.util.Objects;
import java.util.Random;
/**
@ -32,6 +31,43 @@ public record WorldGuardConfig(
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.
*
@ -74,43 +110,6 @@ public record WorldGuardConfig(
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 {
private boolean enabled = true;
private String template = DEFAULT_TEMPLATE;
@ -153,3 +152,4 @@ public record WorldGuardConfig(
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.
*
* @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"
*/
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.reset.ResetGui;
import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui;
import dev.loki.lomines.gui.mine.holder.BlockMaterialSelectionGuiHolder;
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.holder.*;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;

View file

@ -17,7 +17,8 @@ import java.util.Collection;
*/
public final class BlockUpdateUtil {
private BlockUpdateUtil() {}
private BlockUpdateUtil() {
}
/**
* 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);
if (chunk.isLoaded()) {
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;
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.
*/
private record ChunkCoordinate(int x, int z) {
* Simple immutable holder for chunk coordinates.
*/
private record ChunkCoordinate(int x, int z) {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ChunkCoordinate other)) return false;
return x == other.x && z == other.z;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ChunkCoordinate(int x1, int z1))) return false;
return x == x1 && z == z1;
}
}
}

View file

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

View file

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

View file

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