Refactor: Restructure packages to follow KISS/SOLID principles - gui reorganized into mine/group/confirm/common, core into mine/service/common, command into admin/player/common, data/config/loader into block/entity/region/reward/system. All packages now follow max 6 files per folder rule. Extracted utility classes to reduce file sizes.
This commit is contained in:
parent
9692965a3e
commit
1b85bc5329
58 changed files with 1356 additions and 1364 deletions
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines;
|
||||
|
||||
import dev.loki.lomines.core.MineTicker;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.core.mine.MineTicker;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.data.stats.StatsManager;
|
||||
import dev.loki.lomines.integration.IntegrationManager;
|
||||
import dev.loki.lomines.wand.GroupWandManager;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines;
|
||||
|
||||
import dev.loki.lomines.core.MineTicker;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.core.mine.MineTicker;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.data.stats.StatsManager;
|
||||
import dev.loki.lomines.integration.IntegrationManager;
|
||||
import dev.loki.lomines.wand.GroupWandManager;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package dev.loki.lomines;
|
||||
|
||||
import dev.loki.lomines.command.AdminCommands;
|
||||
import dev.loki.lomines.command.LoMinesTabCompleter;
|
||||
import dev.loki.lomines.command.MaskCommands;
|
||||
import dev.loki.lomines.command.PlayerCommands;
|
||||
import dev.loki.lomines.command.StatsCommands;
|
||||
import dev.loki.lomines.command.admin.AdminCommands;
|
||||
import dev.loki.lomines.command.admin.MaskCommands;
|
||||
import dev.loki.lomines.command.admin.StatsCommands;
|
||||
import dev.loki.lomines.command.common.LoMinesTabCompleter;
|
||||
import dev.loki.lomines.command.player.PlayerCommands;
|
||||
import dev.loki.lomines.integration.IntegrationManager;
|
||||
import dev.loki.lomines.listener.BlockBreakListener;
|
||||
import dev.loki.lomines.listener.GroupGuiListener;
|
||||
|
|
|
|||
161
src/main/java/dev/loki/lomines/command/admin/AdminCommands.java
Normal file
161
src/main/java/dev/loki/lomines/command/admin/AdminCommands.java
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package dev.loki.lomines.command.admin;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.gui.mine.MineEditGui;
|
||||
import dev.loki.lomines.util.ErrorHandler;
|
||||
import dev.loki.lomines.util.MessageFormatter;
|
||||
import dev.lolib.commands.annotation.Arg;
|
||||
import dev.lolib.commands.annotation.Command;
|
||||
import dev.lolib.commands.annotation.Subcommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Admin commands for mine management.
|
||||
* Provides commands for creating, deleting, resetting, reloading, listing, and editing mines.
|
||||
*/
|
||||
@Command(value = "lm", permission = "lomines.use")
|
||||
public class AdminCommands {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Mines mines;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
public AdminCommands(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.mines = plugin.getMines();
|
||||
this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new mine with the given name.
|
||||
* Usage: /lm create <name>
|
||||
*/
|
||||
@Subcommand(value = "create", permission = "lomines.admin.create")
|
||||
public void create(CommandSender sender, @Arg("name") String name) {
|
||||
try {
|
||||
mines.create(name);
|
||||
sender.sendMessage(MessageFormatter.success("Successfully created mine: " + name));
|
||||
sender.sendMessage(Component.text(
|
||||
"Edit selection & contents in plugins/LoMines/mines/" + name + ".yml — "
|
||||
+ "for shape-based ore (not full cuboid), paint mask.marker blocks, then: /lm maskscan " + name));
|
||||
if (sender instanceof Player player) {
|
||||
sender.sendMessage(Component.text(
|
||||
"Or use: /lm edit " + name + " to open GUI editor", NamedTextColor.YELLOW));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine already exists", name);
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to create mine: " + e.getMessage(),
|
||||
"Failed to create mine " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing mine.
|
||||
* Usage: /lm delete <name>
|
||||
*/
|
||||
@Subcommand(value = "delete", permission = "lomines.admin.delete")
|
||||
public void delete(CommandSender sender, @Arg("name") String name) {
|
||||
try {
|
||||
mines.delete(name);
|
||||
sender.sendMessage(MessageFormatter.success("Successfully deleted mine: " + name));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine", name);
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to delete mine: " + e.getMessage(),
|
||||
"Failed to delete mine " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the GUI editor for a mine.
|
||||
* Usage: /lm edit <mine>
|
||||
*/
|
||||
@Subcommand(value = "edit", permission = "lomines.admin.edit")
|
||||
public void edit(CommandSender sender, @Arg("mine") String mineName) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Mine mine = mines.find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
errorHandler.handleNotFound(sender, "Mine", mineName);
|
||||
return;
|
||||
}
|
||||
|
||||
MineEditGui.open(plugin, player, mineName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a mine by refilling all its regions.
|
||||
* Usage: /lm reset <mine> [silent]
|
||||
*/
|
||||
@Subcommand(value = "reset", permission = "lomines.admin.reset")
|
||||
public void reset(
|
||||
CommandSender sender,
|
||||
@Arg("mine") String mineName,
|
||||
@Arg(value = "silent", optional = true, defaultValue = "false") boolean silent
|
||||
) {
|
||||
try {
|
||||
Mine mine = mines.get(mineName);
|
||||
mine.reset(silent);
|
||||
sender.sendMessage(MessageFormatter.success("Resetting mine: " + mineName));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine", mineName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all mine configurations from disk.
|
||||
* Usage: /lm reload
|
||||
*/
|
||||
@Subcommand(value = "reload", permission = "lomines.admin.reload")
|
||||
public void reload(CommandSender sender) {
|
||||
try {
|
||||
mines.getAll().forEach(Mine::stop);
|
||||
mines.loadAll();
|
||||
int count = mines.getAll().size();
|
||||
sender.sendMessage(MessageFormatter.success("Successfully reloaded " + count + " mine(s)"));
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to reload mines: " + e.getMessage(),
|
||||
"Failed to reload mines", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all active mines.
|
||||
* Usage: /lm list
|
||||
*/
|
||||
@Subcommand(value = "list", permission = "lomines.admin.list")
|
||||
public void list(CommandSender sender) {
|
||||
Collection<Mine> allMines = mines.getAll();
|
||||
|
||||
if (allMines.isEmpty()) {
|
||||
sender.sendMessage(Component.text("No mines found"));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("=== Mines (" + allMines.size() + ") ==="));
|
||||
|
||||
for (Mine mine : allMines) {
|
||||
String status = formatMineStatus(mine);
|
||||
sender.sendMessage(Component.text("- " + mine.getName() + " " + status));
|
||||
}
|
||||
}
|
||||
|
||||
private String formatMineStatus(Mine mine) {
|
||||
int blocks = mine.getBlocks();
|
||||
int total = mine.getTotalVolume();
|
||||
double percent = mine.getPercentFilled();
|
||||
return String.format("(%d/%d blocks, %.1f%%)", blocks, total, percent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.admin;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.util.ErrorHandler;
|
||||
import dev.loki.lomines.util.MessageFormatter;
|
||||
import dev.lolib.commands.annotation.Arg;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.admin;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.data.stats.Leaderboard;
|
||||
import dev.loki.lomines.data.stats.LeaderboardEntry;
|
||||
import dev.loki.lomines.data.stats.PlayerStats;
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.admin;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.gui.MineEditGui;
|
||||
import dev.loki.lomines.util.ErrorHandler;
|
||||
import dev.loki.lomines.util.MessageFormatter;
|
||||
import dev.lolib.commands.annotation.Arg;
|
||||
import dev.lolib.commands.annotation.Command;
|
||||
import dev.lolib.commands.annotation.Subcommand;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
|
@ -18,142 +16,19 @@ import org.bukkit.Location;
|
|||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Admin commands for mine management.
|
||||
* Provides commands for creating, deleting, resetting, reloading, listing, and editing mines.
|
||||
* Commands for managing teleport and spawn locations for mines.
|
||||
*/
|
||||
@Command(value = "lm", permission = "lomines.use")
|
||||
public class AdminCommands {
|
||||
class TeleportCommands {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Mines mines;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
public AdminCommands(LoMinesPlugin plugin) {
|
||||
TeleportCommands(LoMinesPlugin plugin, Mines mines, ErrorHandler errorHandler) {
|
||||
this.plugin = plugin;
|
||||
this.mines = plugin.getMines();
|
||||
this.errorHandler = new ErrorHandler(plugin.getLogger(), "[LoMines] ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new mine with the given name.
|
||||
* Usage: /lm create <name>
|
||||
*/
|
||||
@Subcommand(value = "create", permission = "lomines.admin.create")
|
||||
public void create(CommandSender sender, @Arg("name") String name) {
|
||||
try {
|
||||
mines.create(name);
|
||||
sender.sendMessage(MessageFormatter.success("Successfully created mine: " + name));
|
||||
sender.sendMessage(Component.text(
|
||||
"Edit selection & contents in plugins/LoMines/mines/" + name + ".yml — "
|
||||
+ "for shape-based ore (not full cuboid), paint mask.marker blocks, then: /lm maskscan " + name));
|
||||
if (sender instanceof Player player) {
|
||||
sender.sendMessage(Component.text(
|
||||
"Or use: /lm edit " + name + " to open GUI editor", NamedTextColor.YELLOW));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine already exists", name);
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to create mine: " + e.getMessage(),
|
||||
"Failed to create mine " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing mine.
|
||||
* Usage: /lm delete <name>
|
||||
*/
|
||||
@Subcommand(value = "delete", permission = "lomines.admin.delete")
|
||||
public void delete(CommandSender sender, @Arg("name") String name) {
|
||||
try {
|
||||
mines.delete(name);
|
||||
sender.sendMessage(MessageFormatter.success("Successfully deleted mine: " + name));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine", name);
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to delete mine: " + e.getMessage(),
|
||||
"Failed to delete mine " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the GUI editor for a mine.
|
||||
* Usage: /lm edit <mine>
|
||||
*/
|
||||
@Subcommand(value = "edit", permission = "lomines.admin.edit")
|
||||
public void edit(CommandSender sender, @Arg("mine") String mineName) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Mine mine = mines.find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
errorHandler.handleNotFound(sender, "Mine", mineName);
|
||||
return;
|
||||
}
|
||||
|
||||
MineEditGui.open(plugin, player, mineName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a mine by refilling all its regions.
|
||||
* Usage: /lm reset <mine> [silent]
|
||||
*/
|
||||
@Subcommand(value = "reset", permission = "lomines.admin.reset")
|
||||
public void reset(
|
||||
CommandSender sender,
|
||||
@Arg("mine") String mineName,
|
||||
@Arg(value = "silent", optional = true, defaultValue = "false") boolean silent
|
||||
) {
|
||||
try {
|
||||
Mine mine = mines.get(mineName);
|
||||
mine.reset(silent);
|
||||
sender.sendMessage(MessageFormatter.success("Resetting mine: " + mineName));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine", mineName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads all mine configurations from disk.
|
||||
* Usage: /lm reload
|
||||
*/
|
||||
@Subcommand(value = "reload", permission = "lomines.admin.reload")
|
||||
public void reload(CommandSender sender) {
|
||||
try {
|
||||
mines.getAll().forEach(Mine::stop);
|
||||
mines.loadAll();
|
||||
int count = mines.getAll().size();
|
||||
sender.sendMessage(MessageFormatter.success("Successfully reloaded " + count + " mine(s)"));
|
||||
} catch (IOException e) {
|
||||
errorHandler.handleError(sender, "Failed to reload mines: " + e.getMessage(),
|
||||
"Failed to reload mines", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all active mines.
|
||||
* Usage: /lm list
|
||||
*/
|
||||
@Subcommand(value = "list", permission = "lomines.admin.list")
|
||||
public void list(CommandSender sender) {
|
||||
Collection<Mine> allMines = mines.getAll();
|
||||
|
||||
if (allMines.isEmpty()) {
|
||||
sender.sendMessage(Component.text("No mines found"));
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("=== Mines (" + allMines.size() + ") ==="));
|
||||
|
||||
for (Mine mine : allMines) {
|
||||
String status = formatMineStatus(mine);
|
||||
sender.sendMessage(Component.text("- " + mine.getName() + " " + status));
|
||||
}
|
||||
this.mines = mines;
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,7 +36,7 @@ public class AdminCommands {
|
|||
* Usage: /lm setteleport <mine>
|
||||
*/
|
||||
@Subcommand(value = "setteleport", permission = "lomines.admin.setteleport")
|
||||
public void setTeleport(CommandSender sender, @Arg("mine") String mineName) {
|
||||
void setTeleport(CommandSender sender, @Arg("mine") String mineName) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
|
||||
return;
|
||||
|
|
@ -174,7 +49,6 @@ public class AdminCommands {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get current player location and update config
|
||||
var config = mine.getConfig();
|
||||
var newTeleport = TeleportConfig.at(player.getLocation());
|
||||
|
||||
|
|
@ -203,7 +77,7 @@ public class AdminCommands {
|
|||
* Usage: /lm setspawn <mine>
|
||||
*/
|
||||
@Subcommand(value = "setspawn", permission = "lomines.admin.setspawn")
|
||||
public void setSpawn(CommandSender sender, @Arg("mine") String mineName) {
|
||||
void setSpawn(CommandSender sender, @Arg("mine") String mineName) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage(Component.text("This command can only be used by players!", NamedTextColor.RED));
|
||||
return;
|
||||
|
|
@ -216,7 +90,6 @@ public class AdminCommands {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get current player location and update config
|
||||
var config = mine.getConfig();
|
||||
var newSpawn = PlayerSpawnConfig.at(player.getLocation());
|
||||
|
||||
|
|
@ -246,7 +119,7 @@ public class AdminCommands {
|
|||
* Usage: /lm clearspawn <mine>
|
||||
*/
|
||||
@Subcommand(value = "clearspawn", permission = "lomines.admin.setspawn")
|
||||
public void clearSpawn(CommandSender sender, @Arg("mine") String mineName) {
|
||||
void clearSpawn(CommandSender sender, @Arg("mine") String mineName) {
|
||||
try {
|
||||
Mine mine = mines.find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
|
|
@ -275,22 +148,8 @@ public class AdminCommands {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats location for display.
|
||||
*/
|
||||
private String formatLocation(Location loc) {
|
||||
return String.format("%.1f, %.1f, %.1f in %s",
|
||||
loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats mine status information for display.
|
||||
*/
|
||||
private String formatMineStatus(Mine mine) {
|
||||
int blocks = mine.getBlocks();
|
||||
int total = mine.getTotalVolume();
|
||||
double percent = mine.getPercentFilled();
|
||||
|
||||
return String.format("(%d/%d blocks, %.1f%%)", blocks, total, percent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.common;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import org.bukkit.command.Command;
|
||||
|
|
@ -10,4 +10,4 @@
|
|||
* <li>{@code WorldGuardCommands} - WorldGuard integration commands</li>
|
||||
* </ul>
|
||||
*/
|
||||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.common;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.command;
|
||||
package dev.loki.lomines.command.player;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.util.ErrorHandler;
|
||||
|
|
@ -8,4 +8,4 @@
|
|||
* <li>{@code Mine} - Representation of a single mine with its configuration and state</li>
|
||||
* </ul>
|
||||
*/
|
||||
package dev.loki.lomines.core;
|
||||
package dev.loki.lomines.core.common;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package dev.loki.lomines.core;
|
||||
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;
|
||||
|
|
@ -10,6 +11,7 @@ import dev.loki.lomines.handler.MineBlockHandler;
|
|||
import dev.loki.lomines.handler.MineResetHandler;
|
||||
import dev.loki.lomines.util.location.BlockKeys;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
import dev.lolib.scheduler.ScheduledTask;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import org.bukkit.Location;
|
||||
|
|
@ -26,73 +28,57 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
*/
|
||||
public final class Mine {
|
||||
|
||||
// Immutable configuration
|
||||
private final String name;
|
||||
private final List<Cuboid> regions;
|
||||
private final MineConfig config;
|
||||
private final BlockSetter blockSetter;
|
||||
private final LoMinesPlugin plugin;
|
||||
private final int totalVolume;
|
||||
/**
|
||||
* Block keys for {@link FillMode#MASK}; empty when not mask mode.
|
||||
*/
|
||||
private final Set<String> maskBlockKeys;
|
||||
|
||||
// Mutable state (thread-safe)
|
||||
private final AtomicInteger blocks;
|
||||
private final AtomicInteger ticks;
|
||||
|
||||
// Handlers
|
||||
private final MineResetHandler resetHandler;
|
||||
private final MineBlockHandler blockHandler;
|
||||
private final ActionBarHandler actionBarHandler;
|
||||
|
||||
// Tasks
|
||||
private ScheduledTask actionBarTask;
|
||||
|
||||
/**
|
||||
* Creates a new mine instance with new configuration system.
|
||||
*/
|
||||
public Mine(String name, MineConfig config, LoMinesPlugin plugin) {
|
||||
this.name = name;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.regions = config.region().regions();
|
||||
this.blockSetter = createBlockSetter(config, plugin);
|
||||
|
||||
if (config.blocks().fillMode() == FillMode.MASK) {
|
||||
this.maskBlockKeys = new HashSet<>();
|
||||
if (config.blocks().mask() != null) {
|
||||
for (String pos : config.blocks().mask().positions().keySet()) {
|
||||
Location loc = parseLocation(pos);
|
||||
if (loc != null) {
|
||||
maskBlockKeys.add(BlockKeys.key(loc));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.totalVolume = maskBlockKeys.size();
|
||||
} else {
|
||||
this.maskBlockKeys = Set.of();
|
||||
this.totalVolume = config.region().totalVolume();
|
||||
}
|
||||
this.maskBlockKeys = initMaskBlockKeys(config);
|
||||
this.totalVolume = config.blocks().fillMode() == FillMode.MASK
|
||||
? maskBlockKeys.size()
|
||||
: config.region().totalVolume();
|
||||
this.blocks = new AtomicInteger(Math.max(0, totalVolume));
|
||||
this.ticks = new AtomicInteger(0);
|
||||
|
||||
this.resetHandler = new MineResetHandler(this, plugin);
|
||||
this.blockHandler = new MineBlockHandler(this, plugin);
|
||||
this.actionBarHandler = new ActionBarHandler(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts periodic tasks for this mine.
|
||||
*/
|
||||
private Set<String> initMaskBlockKeys(MineConfig config) {
|
||||
if (config.blocks().fillMode() != FillMode.MASK) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<String> keys = new HashSet<>();
|
||||
if (config.blocks().mask() != null) {
|
||||
for (String pos : config.blocks().mask().positions().keySet()) {
|
||||
Location loc = LocationParser.parseSimple(pos);
|
||||
if (loc != null) {
|
||||
keys.add(BlockKeys.key(loc));
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
startActionBarTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all tasks for this mine.
|
||||
*/
|
||||
public void stop() {
|
||||
if (actionBarTask != null) {
|
||||
actionBarTask.cancel();
|
||||
|
|
@ -100,116 +86,53 @@ public final class Mine {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the mine by filling all regions with blocks.
|
||||
*/
|
||||
public void reset(boolean silent) {
|
||||
resetHandler.reset(silent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a block break event in this mine.
|
||||
*/
|
||||
public void onBlockBreak(Player player, Block block) {
|
||||
blockHandler.handle(player, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given location is within any region of this mine.
|
||||
*/
|
||||
public boolean contains(Location location) {
|
||||
return config.region().contains(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether breaking this block counts toward mine progress ({@link FillMode#MASK} ignores blocks outside painted cells).
|
||||
*/
|
||||
public boolean appliesToBlock(Location location) {
|
||||
if (!contains(location)) {
|
||||
return false;
|
||||
}
|
||||
if (config.blocks().fillMode() != FillMode.MASK) {
|
||||
return true;
|
||||
}
|
||||
if (!contains(location)) return false;
|
||||
if (config.blocks().fillMode() != FillMode.MASK) return true;
|
||||
return maskBlockKeys.contains(BlockKeys.key(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total volume of all regions in this mine.
|
||||
*/
|
||||
public int getTotalVolume() {
|
||||
return totalVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the percentage of blocks remaining in the mine.
|
||||
*/
|
||||
public double getPercentFilled() {
|
||||
if (totalVolume == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
if (totalVolume == 0) return 0.0;
|
||||
return (double) blocks.get() / totalVolume * 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the appropriate BlockSetter based on block configuration.
|
||||
*/
|
||||
private BlockSetter createBlockSetter(MineConfig config, LoMinesPlugin plugin) {
|
||||
var weights = config.blocks().weights();
|
||||
|
||||
if (weights.isEmpty()) {
|
||||
throw new IllegalArgumentException("Mine must have at least one block type");
|
||||
}
|
||||
|
||||
// Get the first block key to determine the setter type
|
||||
BlockKey firstKey = weights.keySet().iterator().next();
|
||||
|
||||
return switch (firstKey) {
|
||||
case BlockKey.Oraxen oraxen -> {
|
||||
// TODO: Enable when OraxenBlockSetter is ready
|
||||
throw new IllegalArgumentException("Oraxen integration is currently disabled");
|
||||
}
|
||||
case BlockKey.ItemsAdder itemsAdder -> {
|
||||
// TODO: Enable when ItemsAdderBlockSetter is ready
|
||||
throw new IllegalArgumentException("ItemsAdder integration is currently disabled");
|
||||
}
|
||||
case BlockKey.Vanilla vanilla ->
|
||||
new dev.loki.lomines.block.BukkitBlockSetter(weights, plugin);
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the action bar update task.
|
||||
*/
|
||||
private void startActionBarTask() {
|
||||
if (!config.ui().actionBarEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.ui().actionBarEnabled()) return;
|
||||
actionBarTask = Scheduler.get(plugin).runTimer(() -> {
|
||||
actionBarHandler.sendToNearbyPlayers();
|
||||
}, 10L, 10L);
|
||||
}
|
||||
|
||||
private Location parseLocation(String str) {
|
||||
String[] parts = str.split(";");
|
||||
if (parts.length < 4) return null;
|
||||
|
||||
var world = org.bukkit.Bukkit.getWorld(parts[0]);
|
||||
if (world == null) return null;
|
||||
|
||||
try {
|
||||
double x = Double.parseDouble(parts[1]);
|
||||
double y = Double.parseDouble(parts[2]);
|
||||
double z = Double.parseDouble(parts[3]);
|
||||
return new Location(world, x, y, z);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
57
src/main/java/dev/loki/lomines/core/mine/MineLoader.java
Normal file
57
src/main/java/dev/loki/lomines/core/mine/MineLoader.java
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package dev.loki.lomines.core.common;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.core.service.MineFileManager;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Loads mine configurations from disk.
|
||||
*/
|
||||
final class MineLoader {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final MineFileManager fileManager;
|
||||
private final MineRepository repository;
|
||||
|
||||
MineLoader(LoMinesPlugin plugin, MineFileManager fileManager, MineRepository repository) {
|
||||
this.plugin = plugin;
|
||||
this.fileManager = fileManager;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
void loadAll() throws IOException {
|
||||
repository.clear();
|
||||
|
||||
Path minesFolder = fileManager.getMinesFolder();
|
||||
if (!Files.exists(minesFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> paths = Files.list(minesFolder)) {
|
||||
paths.filter(path -> path.toString().endsWith(".yml"))
|
||||
.filter(path -> !path.getFileName().toString().startsWith("_"))
|
||||
.forEach(this::loadMineFromFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMineFromFile(Path configFile) {
|
||||
try {
|
||||
String fileName = configFile.getFileName().toString();
|
||||
String mineName = fileName.substring(0, fileName.length() - 4);
|
||||
|
||||
MineConfig config = fileManager.loadConfig(mineName);
|
||||
repository.createAndStart(mineName, config);
|
||||
|
||||
plugin.loLogger().info("Loaded mine: " + mineName);
|
||||
} catch (IOException | ConfigLoader.ConfigLoadException e) {
|
||||
plugin.loLogger().error("Failed to load mine from " + configFile.getFileName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.core;
|
||||
package dev.loki.lomines.core.mine;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.lolib.scheduler.ScheduledTask;
|
||||
|
|
@ -1,87 +1,49 @@
|
|||
package dev.loki.lomines.core;
|
||||
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.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardRegionService;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Registry for all active mines.
|
||||
* Manages the lifecycle of mines including loading, creating, and deleting.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class Mines {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final MineFileManager fileManager;
|
||||
private final MineRepository repository;
|
||||
private final MineLoader loader;
|
||||
private final MaskScanService maskScanService;
|
||||
private final WorldGuardRegionService worldGuardService;
|
||||
|
||||
public Mines(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
Path minesFolder = plugin.getDataFolder().toPath().resolve("mines");
|
||||
this.fileManager = new MineFileManager(minesFolder);
|
||||
this.fileManager = new MineFileManager(plugin.getDataFolder().toPath().resolve("mines"));
|
||||
this.repository = new MineRepository(plugin, fileManager);
|
||||
this.loader = new MineLoader(plugin, fileManager, repository);
|
||||
this.maskScanService = new MaskScanService(repository, fileManager);
|
||||
this.worldGuardService = new WorldGuardRegionService(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all mines from the mines/ folder.
|
||||
* Clears and replaces any previously loaded mines (used for full reload).
|
||||
*/
|
||||
public void loadAll() throws IOException {
|
||||
repository.clear();
|
||||
|
||||
Path minesFolder = fileManager.getMinesFolder();
|
||||
if (!Files.exists(minesFolder)) {
|
||||
return;
|
||||
loader.loadAll();
|
||||
}
|
||||
|
||||
try (Stream<Path> paths = Files.list(minesFolder)) {
|
||||
paths.filter(path -> path.toString().endsWith(".yml"))
|
||||
.filter(path -> !path.getFileName().toString().startsWith("_")) // Skip defaults
|
||||
.forEach(configFile -> loadMineFromFile(configFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMineFromFile(Path configFile) {
|
||||
try {
|
||||
String fileName = configFile.getFileName().toString();
|
||||
String mineName = fileName.substring(0, fileName.length() - 4);
|
||||
|
||||
MineConfig config = fileManager.loadConfig(mineName);
|
||||
repository.createAndStart(mineName, config);
|
||||
|
||||
plugin.loLogger().info("Loaded mine: " + mineName);
|
||||
} catch (IOException | ConfigLoader.ConfigLoadException e) {
|
||||
plugin.loLogger().error("Failed to load mine from " + configFile.getFileName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new mine with the given name and default cuboid corners.
|
||||
*/
|
||||
public void create(String name) throws IOException {
|
||||
create(name, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new mine; optional corners define {@code selection.1} and {@code selection.2} (same world required).
|
||||
* If WorldGuard integration is enabled, creates a region for the mine.
|
||||
*/
|
||||
public void create(String name, Location corner1, Location corner2) throws IOException {
|
||||
if (repository.exists(name)) {
|
||||
throw new IllegalArgumentException("Mine already exists: " + name);
|
||||
|
|
@ -100,90 +62,48 @@ public final class Mines {
|
|||
throw new IOException("Failed to parse created mine configuration: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Create WorldGuard region if enabled
|
||||
String regionName = worldGuardService.createRegion(name, config);
|
||||
if (regionName != null) {
|
||||
plugin.loLogger().info("Created WorldGuard region '" + regionName + "' for mine: " + name);
|
||||
}
|
||||
|
||||
worldGuardService.createRegion(name, config);
|
||||
repository.createAndStart(name, config);
|
||||
plugin.loLogger().info("Created mine: " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a mine by name.
|
||||
* Also deletes the associated WorldGuard region if it exists.
|
||||
*/
|
||||
public void delete(String name) throws IOException {
|
||||
Mine mine = repository.remove(name);
|
||||
|
||||
if (mine == null) {
|
||||
throw new IllegalArgumentException("Mine not found: " + name);
|
||||
}
|
||||
|
||||
mine.stop();
|
||||
|
||||
// Delete WorldGuard region if exists
|
||||
MineConfig config = mine.getConfig();
|
||||
boolean wgDeleted = worldGuardService.deleteRegion(name, config);
|
||||
if (wgDeleted) {
|
||||
plugin.loLogger().info("Deleted WorldGuard region for mine: " + name);
|
||||
}
|
||||
|
||||
worldGuardService.deleteRegion(name, mine.getConfig());
|
||||
fileManager.deleteConfig(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a mine by name.
|
||||
*
|
||||
* @throws IllegalArgumentException if mine not found
|
||||
*/
|
||||
public Mine get(String name) {
|
||||
return repository.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a mine by name, returning Optional.
|
||||
*/
|
||||
public Optional<Mine> find(String name) {
|
||||
return repository.find(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all mines.
|
||||
*/
|
||||
public Collection<Mine> getAll() {
|
||||
return repository.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a mine that contains the given location.
|
||||
*/
|
||||
public Optional<Mine> findByLocation(Location location) {
|
||||
return repository.findByLocation(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans cuboid regions for {@code mask.marker} blocks, writes {@code fill-mode: mask} and positions to disk, then reloads the mine.
|
||||
* Must run on the main thread (world access).
|
||||
*/
|
||||
public int scanAndSaveMask(String mineName) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
int count = maskScanService.scanAndSave(mineName);
|
||||
plugin.loLogger().info("Mask scan for '" + mineName + "': " + count + " marker block(s)");
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads one mine from disk (stop, replace instance, start tasks).
|
||||
* Updates WorldGuard region if mine regions changed.
|
||||
*/
|
||||
public void reloadMine(String name) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
// Get old config before reload
|
||||
Mine oldMine = repository.find(name).orElse(null);
|
||||
|
||||
repository.reload(name);
|
||||
|
||||
// Update WorldGuard region if mine exists
|
||||
if (oldMine != null) {
|
||||
Mine newMine = repository.find(name).orElse(null);
|
||||
if (newMine != null) {
|
||||
|
|
@ -192,16 +112,10 @@ public final class Mines {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WorldGuard region service for direct access.
|
||||
*/
|
||||
public WorldGuardRegionService getWorldGuardService() {
|
||||
return worldGuardService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file manager for saving mine configurations.
|
||||
*/
|
||||
public MineFileManager getFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.core.service;
|
||||
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package dev.loki.lomines.core.service;
|
||||
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.block.BlockConfig;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package dev.loki.lomines.core.service;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.data.config.loader.common.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import org.bukkit.Location;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
package dev.loki.lomines.data.config;
|
||||
|
||||
import dev.loki.lomines.data.config.loader.*;
|
||||
import dev.loki.lomines.data.config.loader.block.BlockConfigLoader;
|
||||
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.UIConfigLoader;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.block;
|
||||
|
||||
import dev.loki.lomines.data.config.block.BlockConfig;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.entity;
|
||||
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.entity;
|
||||
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.region;
|
||||
|
||||
import dev.loki.lomines.data.config.region.RegionConfig;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.region;
|
||||
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.reward;
|
||||
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.data.config.reward.RewardConfig;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.system;
|
||||
|
||||
import dev.loki.lomines.data.config.reset.ResetConfig;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config.loader;
|
||||
package dev.loki.lomines.data.config.loader.system;
|
||||
|
||||
import dev.loki.lomines.data.config.ui.UIConfig;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses reward commands from configuration.
|
||||
*/
|
||||
final class RewardCommandParser {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> parseCommands(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
List<String> commands = new ArrayList<>();
|
||||
if (!rewardMap.containsKey("commands")) {
|
||||
return commands;
|
||||
}
|
||||
Object commandsObj = rewardMap.get("commands");
|
||||
if (!(commandsObj instanceof List)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid 'commands' type: expected list, got " +
|
||||
(commandsObj != null ? commandsObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
List<String> commandsList = (List<String>) commandsObj;
|
||||
for (String command : commandsList) {
|
||||
if (command == null || command.trim().isEmpty()) {
|
||||
throw new ConfigParseException("Command cannot be null or empty");
|
||||
}
|
||||
commands.add(command);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses reward materials (blocks) from configuration.
|
||||
*/
|
||||
final class RewardMaterialParser {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Material> parseMaterials(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
if (!rewardMap.containsKey("blocks")) {
|
||||
throw new ConfigParseException("Missing required 'blocks' field in reward");
|
||||
}
|
||||
Object blocksObj = rewardMap.get("blocks");
|
||||
if (!(blocksObj instanceof List)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid 'blocks' type: expected list, got " +
|
||||
(blocksObj != null ? blocksObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
List<String> blocksList = (List<String>) blocksObj;
|
||||
List<Material> materials = new ArrayList<>();
|
||||
for (String blockName : blocksList) {
|
||||
if (blockName == null || blockName.trim().isEmpty()) {
|
||||
throw new ConfigParseException("Block name cannot be null or empty");
|
||||
}
|
||||
try {
|
||||
Material material = ValidationUtils.validateMaterial(blockName);
|
||||
materials.add(material);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigParseException(
|
||||
"Unknown material in reward blocks: '" + blockName + "'",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
return materials;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
|
|
@ -18,18 +15,15 @@ import java.util.Map;
|
|||
public final class RewardParser {
|
||||
|
||||
private final RewardItemParser itemParser;
|
||||
private final RewardMaterialParser materialParser;
|
||||
private final RewardCommandParser commandParser;
|
||||
|
||||
public RewardParser() {
|
||||
this.itemParser = new RewardItemParser();
|
||||
this.materialParser = new RewardMaterialParser();
|
||||
this.commandParser = new RewardCommandParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all rewards from the YAML configuration.
|
||||
*
|
||||
* @param yaml the YAML configuration
|
||||
* @return list of parsed Reward objects
|
||||
* @throws ConfigParseException if reward configuration is invalid
|
||||
*/
|
||||
public List<Reward> parseRewards(YamlConfiguration yaml) throws ConfigParseException {
|
||||
List<Reward> rewards = new ArrayList<>();
|
||||
|
||||
|
|
@ -69,20 +63,13 @@ public final class RewardParser {
|
|||
return rewards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single reward from a map configuration.
|
||||
*
|
||||
* @param rewardMap the reward configuration map
|
||||
* @return the parsed Reward object
|
||||
* @throws ConfigParseException if the reward configuration is invalid
|
||||
*/
|
||||
private Reward parseReward(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
double chance = parseRewardField(rewardMap, "chance", Double.class);
|
||||
boolean preventDrops = parseRewardField(rewardMap, "prevent-drops", Boolean.class, false);
|
||||
|
||||
List<Material> materials = parseRewardMaterials(rewardMap);
|
||||
var materials = materialParser.parseMaterials(rewardMap);
|
||||
List<ItemStack> items = parseRewardItems(rewardMap);
|
||||
List<String> commands = parseRewardCommands(rewardMap);
|
||||
List<String> commands = commandParser.parseCommands(rewardMap);
|
||||
|
||||
return new Reward(materials, chance, items, commands, preventDrops);
|
||||
}
|
||||
|
|
@ -137,38 +124,6 @@ public final class RewardParser {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Material> parseRewardMaterials(Map<String, Object> rewardMap)
|
||||
throws ConfigParseException {
|
||||
if (!rewardMap.containsKey("blocks")) {
|
||||
throw new ConfigParseException("Missing required 'blocks' field in reward");
|
||||
}
|
||||
Object blocksObj = rewardMap.get("blocks");
|
||||
if (!(blocksObj instanceof List)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid 'blocks' type: expected list, got " +
|
||||
(blocksObj != null ? blocksObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> blocksList = (List<String>) blocksObj;
|
||||
List<Material> materials = new ArrayList<>();
|
||||
for (String blockName : blocksList) {
|
||||
if (blockName == null || blockName.trim().isEmpty()) {
|
||||
throw new ConfigParseException("Block name cannot be null or empty");
|
||||
}
|
||||
try {
|
||||
Material material = ValidationUtils.validateMaterial(blockName);
|
||||
materials.add(material);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigParseException(
|
||||
"Unknown material in reward blocks: '" + blockName + "'",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
return materials;
|
||||
}
|
||||
|
||||
private List<ItemStack> parseRewardItems(Map<String, Object> rewardMap)
|
||||
throws ConfigParseException {
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
|
|
@ -197,28 +152,4 @@ public final class RewardParser {
|
|||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<String> parseRewardCommands(Map<String, Object> rewardMap)
|
||||
throws ConfigParseException {
|
||||
List<String> commands = new ArrayList<>();
|
||||
if (!rewardMap.containsKey("commands")) {
|
||||
return commands;
|
||||
}
|
||||
Object commandsObj = rewardMap.get("commands");
|
||||
if (!(commandsObj instanceof List)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid 'commands' type: expected list, got " +
|
||||
(commandsObj != null ? commandsObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> commandsList = (List<String>) commandsObj;
|
||||
for (String command : commandsList) {
|
||||
if (command == null || command.trim().isEmpty()) {
|
||||
throw new ConfigParseException("Command cannot be null or empty");
|
||||
}
|
||||
commands.add(command);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,216 +0,0 @@
|
|||
package dev.loki.lomines.gui;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.wand.GroupWandManager;
|
||||
import dev.loki.lomines.wand.GroupWandSession;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 6-row chest: left column = create; center 3×3 = nine sub-mines with shared settings.
|
||||
*/
|
||||
public final class GroupCreateGui {
|
||||
|
||||
/**
|
||||
* Create group (same YAML defaults for every created mine).
|
||||
*/
|
||||
public static final int SLOT_CREATE = 0;
|
||||
/**
|
||||
* Shows /lm group prefix.
|
||||
*/
|
||||
public static final int SLOT_INFO = 8;
|
||||
private static final int SIZE = 54;
|
||||
/**
|
||||
* 3×3 mine slots mapping index 0..8
|
||||
*/
|
||||
private static final int[] MINE_SLOTS = {10, 11, 12, 19, 20, 21, 28, 29, 30};
|
||||
|
||||
private GroupCreateGui() {
|
||||
}
|
||||
|
||||
public static void open(LoMinesPlugin plugin, Player player) {
|
||||
GroupCreateGuiHolder holder = new GroupCreateGuiHolder(player.getUniqueId());
|
||||
Inventory inv = Bukkit.createInventory(holder, SIZE,
|
||||
Component.text("LoMines · группа (9 шахт)", NamedTextColor.DARK_GREEN));
|
||||
holder.setInventory(inv);
|
||||
fill(plugin, player, inv);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
public static void refresh(LoMinesPlugin plugin, Player player, Inventory inv) {
|
||||
fill(plugin, player, inv);
|
||||
}
|
||||
|
||||
private static void fill(LoMinesPlugin plugin, Player player, Inventory inv) {
|
||||
GroupWandSession session = plugin.getGroupWandManager().getSession(player.getUniqueId());
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
inv.setItem(i, filler());
|
||||
}
|
||||
inv.setItem(SLOT_CREATE, createButton());
|
||||
inv.setItem(SLOT_INFO, infoPaper(session));
|
||||
for (int i = 0; i < MINE_SLOTS.length; i++) {
|
||||
inv.setItem(MINE_SLOTS[i], mineSlotItem(session, i));
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemStack filler() {
|
||||
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = pane.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(" "));
|
||||
pane.setItemMeta(meta);
|
||||
}
|
||||
return pane;
|
||||
}
|
||||
|
||||
private static ItemStack createButton() {
|
||||
ItemStack stack = new ItemStack(Material.LIME_CONCRETE);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("Создать группу", NamedTextColor.GREEN));
|
||||
meta.lore(List.of(
|
||||
Component.text("Создаёт шахты с префиксом_1 … _9", NamedTextColor.GRAY),
|
||||
Component.text("для слотов, где заданы оба угла", NamedTextColor.GRAY)
|
||||
));
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static ItemStack infoPaper(GroupWandSession session) {
|
||||
ItemStack stack = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
if (session.hasBaseName()) {
|
||||
meta.displayName(Component.text("Префикс: " + session.getBaseName(), NamedTextColor.AQUA));
|
||||
meta.lore(List.of(
|
||||
Component.text("Файлы: " + session.getBaseName() + "_1.yml … _9.yml", NamedTextColor.GRAY),
|
||||
Component.text("Настройки одинаковые; правьте один и копируйте при нужде", NamedTextColor.DARK_GRAY)
|
||||
));
|
||||
} else {
|
||||
meta.displayName(Component.text("Задайте префикс", NamedTextColor.RED));
|
||||
meta.lore(List.of(Component.text("/lm group <префикс>", NamedTextColor.GRAY)));
|
||||
}
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static ItemStack mineSlotItem(GroupWandSession session, int index) {
|
||||
boolean active = session.getActiveSlot() == index;
|
||||
boolean ready = session.isSlotReady(index);
|
||||
Material mat;
|
||||
NamedTextColor nameColor;
|
||||
if (ready) {
|
||||
mat = Material.LIME_STAINED_GLASS_PANE;
|
||||
nameColor = NamedTextColor.GREEN;
|
||||
} else if (session.getPos1(index) != null) {
|
||||
mat = Material.YELLOW_STAINED_GLASS_PANE;
|
||||
nameColor = NamedTextColor.YELLOW;
|
||||
} else {
|
||||
mat = Material.RED_STAINED_GLASS_PANE;
|
||||
nameColor = NamedTextColor.RED;
|
||||
}
|
||||
if (active) {
|
||||
mat = Material.LIGHT_BLUE_STAINED_GLASS_PANE;
|
||||
}
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
String label = "Шахта " + (index + 1);
|
||||
meta.displayName(Component.text(label, nameColor));
|
||||
List<Component> lore = new ArrayList<>();
|
||||
lore.add(Component.text(active ? "Активный слот палочки" : "ПКМ — выбрать активным", NamedTextColor.GRAY));
|
||||
lore.add(Component.text(
|
||||
ready ? "Готово (2 точки)" : (session.getPos1(index) != null ? "Нужна 2-я точка" : "Нужны 2 точки"),
|
||||
NamedTextColor.DARK_GRAY));
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
public static int mineSlotIndex(int rawSlot) {
|
||||
for (int i = 0; i < MINE_SLOTS.length; i++) {
|
||||
if (MINE_SLOTS[i] == rawSlot) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if handled (caller should cancel event)
|
||||
*/
|
||||
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot) {
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) {
|
||||
return false;
|
||||
}
|
||||
GroupWandManager mgr = plugin.getGroupWandManager();
|
||||
GroupWandSession session = mgr.getSession(player.getUniqueId());
|
||||
if (rawSlot == SLOT_CREATE) {
|
||||
runCreate(plugin, player, session);
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
int mineIdx = mineSlotIndex(rawSlot);
|
||||
if (mineIdx >= 0) {
|
||||
session.setActiveSlot(mineIdx);
|
||||
Inventory top = player.getOpenInventory().getTopInventory();
|
||||
if (top.getHolder() instanceof GroupCreateGuiHolder) {
|
||||
refresh(plugin, player, top);
|
||||
}
|
||||
player.sendMessage(Component.text("Активный слот палочки: " + (mineIdx + 1), NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void runCreate(LoMinesPlugin plugin, Player player, GroupWandSession session) {
|
||||
if (!session.hasBaseName()) {
|
||||
player.sendMessage(Component.text("Сначала: /lm group <префикс>", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
String base = session.getBaseName();
|
||||
if (!base.matches("^[a-zA-Z0-9_-]+$")) {
|
||||
player.sendMessage(Component.text("Префикс: только латиница, цифры, _ -", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
Mines mines = plugin.getMines();
|
||||
int created = 0;
|
||||
int skipped = 0;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (!session.isSlotReady(i)) {
|
||||
continue;
|
||||
}
|
||||
String mineName = base + "_" + (i + 1);
|
||||
try {
|
||||
mines.create(mineName, session.getPos1(i), session.getPos2(i));
|
||||
created++;
|
||||
} catch (IllegalArgumentException e) {
|
||||
player.sendMessage(Component.text("Пропуск (уже есть): " + mineName, NamedTextColor.GOLD));
|
||||
skipped++;
|
||||
} catch (Exception e) {
|
||||
player.sendMessage(Component.text("Ошибка " + mineName + ": " + e.getMessage(), NamedTextColor.RED));
|
||||
plugin.loLogger().error("Group create failed for " + mineName, e);
|
||||
}
|
||||
}
|
||||
if (created == 0) {
|
||||
player.sendMessage(Component.text("Нет готовых слотов (2 точки в каждом). Пустые слоты пропущены.", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(Component.text(
|
||||
"Создано шахт: " + created + (skipped > 0 ? " (пропущено существующих: " + skipped + ")" : ""),
|
||||
NamedTextColor.GREEN));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
package dev.loki.lomines.gui;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.RewardConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main GUI for editing a mine.
|
||||
* Provides navigation to sub-editors for different configuration sections.
|
||||
* Improved UX with better materials, enhanced lore, and clearer information display.
|
||||
*/
|
||||
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;
|
||||
public static final int SLOT_REGIONS = 12;
|
||||
public static final int SLOT_RESET = 14;
|
||||
public static final int SLOT_REWARDS = 16;
|
||||
public static final int SLOT_TELEPORT = 28;
|
||||
public static final int SLOT_UI = 30;
|
||||
public static final int SLOT_SAVE = 32;
|
||||
public static final int SLOT_DELETE = 34;
|
||||
public static final int SLOT_INFO = 40;
|
||||
|
||||
// UI Constants
|
||||
private static final String SEP = "§8───────────────";
|
||||
private static final String EDIT = "§e▸ Нажмите для редактирования";
|
||||
private static final String CLOSE = "§c▸ Нажмите чтобы закрыть";
|
||||
private static final String SAVE = "§a▸ Нажмите для сохранения";
|
||||
private static final String DEL = "§c▸ Нажмите для удаления";
|
||||
|
||||
private MineEditGui() {}
|
||||
|
||||
public static void open(LoMinesPlugin plugin, Player player, String mineName) {
|
||||
Mine mine = plugin.getMines().find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName);
|
||||
Inventory inv = Bukkit.createInventory(holder, SIZE,
|
||||
Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
|
||||
holder.setInventory(inv);
|
||||
fill(plugin, mine, inv);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
public static void refresh(LoMinesPlugin plugin, Player player, Inventory inv, String mineName) {
|
||||
Mine mine = plugin.getMines().find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
fill(plugin, mine, inv);
|
||||
}
|
||||
|
||||
private static void fill(LoMinesPlugin plugin, Mine mine, Inventory inv) {
|
||||
MineConfig config = mine.getConfig();
|
||||
for (int i = 0; i < SIZE; i++) inv.setItem(i, filler());
|
||||
inv.setItem(SLOT_BLOCKS, blocksItem(config));
|
||||
inv.setItem(SLOT_REGIONS, regionsItem(config));
|
||||
inv.setItem(SLOT_RESET, resetItem(config));
|
||||
inv.setItem(SLOT_REWARDS, rewardsItem(config));
|
||||
inv.setItem(SLOT_TELEPORT, teleportItem(config));
|
||||
inv.setItem(SLOT_UI, uiItem(config));
|
||||
inv.setItem(SLOT_SAVE, saveItem());
|
||||
inv.setItem(SLOT_DELETE, deleteItem(mine.getName()));
|
||||
inv.setItem(SLOT_INFO, infoItem(mine));
|
||||
inv.setItem(SLOT_BACK, backItem());
|
||||
}
|
||||
|
||||
private static ItemStack filler() {
|
||||
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = pane.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(" "));
|
||||
pane.setItemMeta(meta);
|
||||
}
|
||||
return pane;
|
||||
}
|
||||
|
||||
private 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());
|
||||
}
|
||||
|
||||
private static String trunc(String s, int max) {
|
||||
if (s == null || s.isEmpty()) return "§7нет";
|
||||
return s.length() > max ? s.substring(0, max - 3) + "..." : s;
|
||||
}
|
||||
|
||||
private static ItemStack create(Material mat, String name, String... loreLines) {
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(name, NamedTextColor.WHITE));
|
||||
List<Component> lore = new ArrayList<>();
|
||||
for (String line : loreLines) {
|
||||
lore.add(Component.text(line, NamedTextColor.GRAY));
|
||||
}
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static ItemStack blocksItem(MineConfig config) {
|
||||
var w = config.blocks().weights();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТипов: §7" + config.blocks().blockCount());
|
||||
lore.add(" §fРежим: §7" + config.blocks().fillMode().name());
|
||||
lore.add(" §fКастомные: " + (config.blocks().hasCustomBlocks() ? "§aДа" : "§7Нет"));
|
||||
if (!w.isEmpty()) {
|
||||
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)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.DIAMOND_PICKAXE, "§a§lБлоки", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack regionsItem(MineConfig config) {
|
||||
return create(Material.COMPASS, "§a§lРегионы",
|
||||
SEP,
|
||||
"§7Настройки:",
|
||||
" §fКоличество: §7" + config.region().regionCount(),
|
||||
" §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков",
|
||||
"",
|
||||
" §fМир: §7" + config.worldName(),
|
||||
"",
|
||||
"§8Изменяется через палочку"
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack resetItem(MineConfig config) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fИнтервал: §7" + config.reset().intervalDisplay());
|
||||
lore.add(" §fТриггер по %: " + (config.reset().isPercentTriggerEnabled() ? "§a" + config.reset().percentTrigger() + "%" : "§7выкл"));
|
||||
lore.add(" §fКоманд: §7" + config.reset().commands().size());
|
||||
lore.add("");
|
||||
lore.add(" §fСообщение: " + (config.reset().broadcastMessage().isEmpty() ? "§7нет" : "§aесть"));
|
||||
if (!config.reset().broadcastMessage().isEmpty()) {
|
||||
lore.add(" §8" + trunc(config.reset().broadcastMessage(), 25));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.CLOCK, "§a§lСброс", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack rewardsItem(MineConfig config) {
|
||||
var entries = config.rewards().entries();
|
||||
long items = entries.stream().flatMap(e -> e.items().stream()).count();
|
||||
long cmds = entries.stream().filter(e -> !e.commands().isEmpty()).count();
|
||||
long blockDrop = entries.stream().filter(RewardConfig.RewardEntry::preventVanillaDrops).count();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fЗаписей: §7" + entries.size());
|
||||
if (!entries.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("§7Типы наград:");
|
||||
lore.add(" §8• §fПредметов: §7" + items);
|
||||
lore.add(" §8• §fКоманд: §7" + cmds);
|
||||
if (blockDrop > 0) lore.add(" §8• §cБлокируют дроп: §7" + blockDrop);
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.GOLD_INGOT, "§a§lНаграды", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack teleportItem(MineConfig config) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТелепорт: " + (config.teleport().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.teleport().enabled()) {
|
||||
config.teleport().getLocation().ifPresent(loc -> lore.add(" " + loc(loc)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(" §fСпавн (застрявших): " + (config.playerSpawn().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.playerSpawn().enabled()) {
|
||||
config.playerSpawn().getLocation().ifPresent(loc -> lore.add(" " + loc(loc)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add("§8Команды:");
|
||||
lore.add(" §8• §7/lm setteleport §f<mine>");
|
||||
lore.add(" §8• §7/lm setspawn §f<mine>");
|
||||
lore.add(" §8• §7/lm clearspawn §f<mine>");
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.ENDER_PEARL, "§a§lТелепорт", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack uiItem(MineConfig config) {
|
||||
return create(Material.PAINTING, "§a§lИнтерфейс",
|
||||
SEP,
|
||||
"§7Настройки:",
|
||||
" §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"),
|
||||
" §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков",
|
||||
"",
|
||||
"§7Формат ActionBar:",
|
||||
" §8" + trunc(config.ui().actionBarFormat(), 28),
|
||||
"",
|
||||
"§7Формат таймера: §8" + config.ui().timerFormat(),
|
||||
"",
|
||||
EDIT
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack saveItem() {
|
||||
return create(Material.LIME_DYE, "§a§lСохранить изменения",
|
||||
SEP,
|
||||
"§7Сохраняет конфигурацию шахты",
|
||||
"§7в файл на диске",
|
||||
"",
|
||||
SAVE
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack deleteItem(String mineName) {
|
||||
return create(Material.TNT, "§c§lУдалить шахту",
|
||||
SEP,
|
||||
"§c§lВнимание!",
|
||||
"§cЭто действие нельзя отменить!",
|
||||
"",
|
||||
"§7Шахта: §f" + mineName,
|
||||
"",
|
||||
DEL
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack infoItem(Mine mine) {
|
||||
int blocks = mine.getBlocks();
|
||||
int total = mine.getTotalVolume();
|
||||
double percent = mine.getPercentFilled();
|
||||
String c = percent > 50 ? "§a" : percent > 25 ? "§e" : "§c";
|
||||
int filled = (int) Math.round(percent / 100.0 * 10);
|
||||
String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(10 - filled);
|
||||
return 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
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack backItem() {
|
||||
return create(Material.ARROW, "§c§lЗакрыть",
|
||||
SEP,
|
||||
"§7Закрыть редактор",
|
||||
"",
|
||||
CLOSE
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, String mineName) {
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) return false;
|
||||
switch (rawSlot) {
|
||||
case SLOT_BACK -> {
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
case SLOT_BLOCKS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора блоков... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_RESET -> {
|
||||
player.sendMessage(Component.text("Открытие редактора сброса... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_REWARDS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора наград... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_TELEPORT -> {
|
||||
player.sendMessage(Component.text("Открытие редактора телепорта... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_UI -> {
|
||||
player.sendMessage(Component.text("Открытие редактора интерфейса... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_SAVE -> {
|
||||
player.sendMessage(Component.text("Конфигурация сохранена для шахты: " + mineName, NamedTextColor.GREEN));
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
case SLOT_DELETE -> {
|
||||
ConfirmDeleteGui.open(plugin, player, mineName);
|
||||
return true;
|
||||
}
|
||||
case SLOT_INFO, SLOT_REGIONS -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package dev.loki.lomines.gui.common;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Factory for creating ItemStacks with name and lore.
|
||||
*/
|
||||
final class ItemStackFactory {
|
||||
|
||||
private ItemStackFactory() {}
|
||||
|
||||
static ItemStack create(Material mat, String name, String... loreLines) {
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(name, NamedTextColor.WHITE));
|
||||
List<Component> lore = new ArrayList<>();
|
||||
for (String line : loreLines) {
|
||||
lore.add(Component.text(line, NamedTextColor.GRAY));
|
||||
}
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (s == null || s.isEmpty()) return "§7нет";
|
||||
return s.length() > max ? s.substring(0, max - 3) + "..." : s;
|
||||
}
|
||||
|
||||
static ItemStack filler() {
|
||||
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = pane.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(" "));
|
||||
pane.setItemMeta(meta);
|
||||
}
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.gui;
|
||||
package dev.loki.lomines.gui.confirm;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.gui;
|
||||
package dev.loki.lomines.gui.confirm;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
123
src/main/java/dev/loki/lomines/gui/group/GroupCreateGui.java
Normal file
123
src/main/java/dev/loki/lomines/gui/group/GroupCreateGui.java
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package dev.loki.lomines.gui.group;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.mine.Mines;
|
||||
import dev.loki.lomines.wand.GroupWandManager;
|
||||
import dev.loki.lomines.wand.GroupWandSession;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
|
||||
/**
|
||||
* 6-row chest: left column = create; center 3×3 = nine sub-mines with shared settings.
|
||||
*/
|
||||
public final class GroupCreateGui {
|
||||
|
||||
public static final int SLOT_CREATE = 0;
|
||||
public static final int SLOT_INFO = 8;
|
||||
private static final int SIZE = 54;
|
||||
private static final int[] MINE_SLOTS = {10, 11, 12, 19, 20, 21, 28, 29, 30};
|
||||
|
||||
private GroupCreateGui() {}
|
||||
|
||||
public static void open(LoMinesPlugin plugin, Player player) {
|
||||
GroupCreateGuiHolder holder = new GroupCreateGuiHolder(player.getUniqueId());
|
||||
Inventory inv = Bukkit.createInventory(holder, SIZE,
|
||||
Component.text("LoMines · группа (9 шахт)", NamedTextColor.DARK_GREEN));
|
||||
holder.setInventory(inv);
|
||||
fill(plugin, player, inv);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
public static void refresh(LoMinesPlugin plugin, Player player, Inventory inv) {
|
||||
fill(plugin, player, inv);
|
||||
}
|
||||
|
||||
private static void fill(LoMinesPlugin plugin, Player player, Inventory inv) {
|
||||
GroupWandSession session = plugin.getGroupWandManager().getSession(player.getUniqueId());
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
inv.setItem(i, GroupCreateItems.filler());
|
||||
}
|
||||
inv.setItem(SLOT_CREATE, GroupCreateItems.createButton());
|
||||
inv.setItem(SLOT_INFO, GroupCreateItems.infoPaper(session));
|
||||
for (int i = 0; i < MINE_SLOTS.length; i++) {
|
||||
inv.setItem(MINE_SLOTS[i], GroupCreateItems.mineSlotItem(session, i));
|
||||
}
|
||||
}
|
||||
|
||||
public static int mineSlotIndex(int rawSlot) {
|
||||
for (int i = 0; i < MINE_SLOTS.length; i++) {
|
||||
if (MINE_SLOTS[i] == rawSlot) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot) {
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) {
|
||||
return false;
|
||||
}
|
||||
GroupWandManager mgr = plugin.getGroupWandManager();
|
||||
GroupWandSession session = mgr.getSession(player.getUniqueId());
|
||||
|
||||
if (rawSlot == SLOT_CREATE) {
|
||||
runCreate(plugin, player, session);
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
|
||||
int mineIdx = mineSlotIndex(rawSlot);
|
||||
if (mineIdx >= 0) {
|
||||
session.setActiveSlot(mineIdx);
|
||||
Inventory top = player.getOpenInventory().getTopInventory();
|
||||
if (top.getHolder() instanceof GroupCreateGuiHolder) {
|
||||
refresh(plugin, player, top);
|
||||
}
|
||||
player.sendMessage(Component.text("Активный слот палочки: " + (mineIdx + 1), NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void runCreate(LoMinesPlugin plugin, Player player, GroupWandSession session) {
|
||||
if (!session.hasBaseName()) {
|
||||
player.sendMessage(Component.text("Сначала: /lm group <префикс>", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
String base = session.getBaseName();
|
||||
if (!base.matches("^[a-zA-Z0-9_-]+$")) {
|
||||
player.sendMessage(Component.text("Префикс: только латиница, цифры, _ -", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Mines mines = plugin.getMines();
|
||||
int created = 0;
|
||||
int skipped = 0;
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (!session.isSlotReady(i)) continue;
|
||||
String mineName = base + "_" + (i + 1);
|
||||
try {
|
||||
mines.create(mineName, session.getPos1(i), session.getPos2(i));
|
||||
created++;
|
||||
} catch (IllegalArgumentException e) {
|
||||
player.sendMessage(Component.text("Пропуск (уже есть): " + mineName, NamedTextColor.GOLD));
|
||||
skipped++;
|
||||
} catch (Exception e) {
|
||||
player.sendMessage(Component.text("Ошибка " + mineName + ": " + e.getMessage(), NamedTextColor.RED));
|
||||
plugin.loLogger().error("Group create failed for " + mineName, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (created == 0) {
|
||||
player.sendMessage(Component.text("Нет готовых слотов (2 точки в каждом).", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(Component.text(
|
||||
"Создано шахт: " + created + (skipped > 0 ? " (пропущено: " + skipped + ")" : ""),
|
||||
NamedTextColor.GREEN));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.gui;
|
||||
package dev.loki.lomines.gui.group;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
103
src/main/java/dev/loki/lomines/gui/group/GroupCreateItems.java
Normal file
103
src/main/java/dev/loki/lomines/gui/group/GroupCreateItems.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package dev.loki.lomines.gui.group;
|
||||
|
||||
import dev.loki.lomines.wand.GroupWandSession;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Item factory for GroupCreateGui.
|
||||
*/
|
||||
final class GroupCreateItems {
|
||||
|
||||
private GroupCreateItems() {}
|
||||
|
||||
static ItemStack filler() {
|
||||
ItemStack pane = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = pane.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(" "));
|
||||
pane.setItemMeta(meta);
|
||||
}
|
||||
return pane;
|
||||
}
|
||||
|
||||
static ItemStack createButton() {
|
||||
ItemStack stack = new ItemStack(Material.LIME_CONCRETE);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("Создать группу", NamedTextColor.GREEN));
|
||||
meta.lore(List.of(
|
||||
Component.text("Создаёт шахты с префиксом_1 … _9", NamedTextColor.GRAY),
|
||||
Component.text("для слотов, где заданы оба угла", NamedTextColor.GRAY)
|
||||
));
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
static ItemStack infoPaper(GroupWandSession session) {
|
||||
ItemStack stack = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
if (session.hasBaseName()) {
|
||||
meta.displayName(Component.text("Префикс: " + session.getBaseName(), NamedTextColor.AQUA));
|
||||
meta.lore(List.of(
|
||||
Component.text("Файлы: " + session.getBaseName() + "_1.yml … _9.yml", NamedTextColor.GRAY),
|
||||
Component.text("Настройки одинаковые; правьте один и копируйте при нужде", NamedTextColor.DARK_GRAY)
|
||||
));
|
||||
} else {
|
||||
meta.displayName(Component.text("Задайте префикс", NamedTextColor.RED));
|
||||
meta.lore(List.of(Component.text("/lm group <префикс>", NamedTextColor.GRAY)));
|
||||
}
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
static ItemStack mineSlotItem(GroupWandSession session, int index) {
|
||||
boolean active = session.getActiveSlot() == index;
|
||||
boolean ready = session.isSlotReady(index);
|
||||
|
||||
Material mat = getMaterialForSlot(session, index, ready, active);
|
||||
NamedTextColor nameColor = ready ? NamedTextColor.GREEN :
|
||||
(session.getPos1(index) != null ? NamedTextColor.YELLOW : NamedTextColor.RED);
|
||||
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
String label = "Шахта " + (index + 1);
|
||||
meta.displayName(Component.text(label, nameColor));
|
||||
meta.lore(buildLore(session, index, active, ready));
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static Material getMaterialForSlot(GroupWandSession session, int index, boolean ready, boolean active) {
|
||||
if (active) {
|
||||
return Material.LIGHT_BLUE_STAINED_GLASS_PANE;
|
||||
}
|
||||
if (ready) {
|
||||
return Material.LIME_STAINED_GLASS_PANE;
|
||||
}
|
||||
if (session.getPos1(index) != null) {
|
||||
return Material.YELLOW_STAINED_GLASS_PANE;
|
||||
}
|
||||
return Material.RED_STAINED_GLASS_PANE;
|
||||
}
|
||||
|
||||
private static List<Component> buildLore(GroupWandSession session, int index, boolean active, boolean ready) {
|
||||
List<Component> lore = new ArrayList<>();
|
||||
lore.add(Component.text(active ? "Активный слот палочки" : "ПКМ — выбрать активным", NamedTextColor.GRAY));
|
||||
lore.add(Component.text(
|
||||
ready ? "Готово (2 точки)" : (session.getPos1(index) != null ? "Нужна 2-я точка" : "Нужны 2 точки"),
|
||||
NamedTextColor.DARK_GRAY));
|
||||
return lore;
|
||||
}
|
||||
}
|
||||
118
src/main/java/dev/loki/lomines/gui/mine/MineEditGui.java
Normal file
118
src/main/java/dev/loki/lomines/gui/mine/MineEditGui.java
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package dev.loki.lomines.gui.mine;
|
||||
|
||||
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 net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
|
||||
/**
|
||||
* Main GUI for editing a mine.
|
||||
* Provides navigation to sub-editors for different configuration sections.
|
||||
* Improved UX with better materials, enhanced lore, and clearer information display.
|
||||
*/
|
||||
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;
|
||||
public static final int SLOT_REGIONS = 12;
|
||||
public static final int SLOT_RESET = 14;
|
||||
public static final int SLOT_REWARDS = 16;
|
||||
public static final int SLOT_TELEPORT = 28;
|
||||
public static final int SLOT_UI = 30;
|
||||
public static final int SLOT_SAVE = 32;
|
||||
public static final int SLOT_DELETE = 34;
|
||||
public static final int SLOT_INFO = 40;
|
||||
|
||||
private MineEditGui() {}
|
||||
|
||||
public static void open(LoMinesPlugin plugin, Player player, String mineName) {
|
||||
Mine mine = plugin.getMines().find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName);
|
||||
Inventory inv = Bukkit.createInventory(holder, SIZE,
|
||||
Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
|
||||
holder.setInventory(inv);
|
||||
fill(plugin, mine, inv);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
public static void refresh(LoMinesPlugin plugin, Player player, Inventory inv, String mineName) {
|
||||
Mine mine = plugin.getMines().find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
fill(plugin, mine, inv);
|
||||
}
|
||||
|
||||
private static void fill(LoMinesPlugin plugin, Mine mine, Inventory inv) {
|
||||
MineConfig config = mine.getConfig();
|
||||
for (int i = 0; i < SIZE; i++) inv.setItem(i, MineEditItems.filler());
|
||||
inv.setItem(SLOT_BLOCKS, MineEditItems.blocksItem(config));
|
||||
inv.setItem(SLOT_REGIONS, MineEditItems.regionsItem(config));
|
||||
inv.setItem(SLOT_RESET, MineEditItems.resetItem(config));
|
||||
inv.setItem(SLOT_REWARDS, MineEditItems.rewardsItem(config));
|
||||
inv.setItem(SLOT_TELEPORT, MineEditItems.teleportItem(config));
|
||||
inv.setItem(SLOT_UI, MineEditItems.uiItem(config));
|
||||
inv.setItem(SLOT_SAVE, MineEditItems.saveItem());
|
||||
inv.setItem(SLOT_DELETE, MineEditItems.deleteItem(mine.getName()));
|
||||
inv.setItem(SLOT_INFO, MineEditItems.infoItem(mine));
|
||||
inv.setItem(SLOT_BACK, MineEditItems.backItem());
|
||||
}
|
||||
|
||||
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, String mineName) {
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) return false;
|
||||
switch (rawSlot) {
|
||||
case SLOT_BACK -> {
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
case SLOT_BLOCKS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора блоков... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_RESET -> {
|
||||
player.sendMessage(Component.text("Открытие редактора сброса... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_REWARDS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора наград... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_TELEPORT -> {
|
||||
player.sendMessage(Component.text("Открытие редактора телепорта... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_UI -> {
|
||||
player.sendMessage(Component.text("Открытие редактора интерфейса... (в разработке)", NamedTextColor.YELLOW));
|
||||
return true;
|
||||
}
|
||||
case SLOT_SAVE -> {
|
||||
player.sendMessage(Component.text("Конфигурация сохранена для шахты: " + mineName, NamedTextColor.GREEN));
|
||||
player.closeInventory();
|
||||
return true;
|
||||
}
|
||||
case SLOT_DELETE -> {
|
||||
ConfirmDeleteGui.open(plugin, player, mineName);
|
||||
return true;
|
||||
}
|
||||
case SLOT_INFO, SLOT_REGIONS -> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.gui;
|
||||
package dev.loki.lomines.gui.mine;
|
||||
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
189
src/main/java/dev/loki/lomines/gui/mine/MineEditItems.java
Normal file
189
src/main/java/dev/loki/lomines/gui/mine/MineEditItems.java
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package dev.loki.lomines.gui.mine;
|
||||
|
||||
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.gui.common.ItemStackFactory;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Item factory for MineEditGui. Creates all inventory items.
|
||||
*/
|
||||
final class MineEditItems {
|
||||
|
||||
private static final String SEP = "§8───────────────";
|
||||
private static final String EDIT = "§e▸ Нажмите для редактирования";
|
||||
private static final String CLOSE = "§c▸ Нажмите чтобы закрыть";
|
||||
private static final String SAVE = "§a▸ Нажмите для сохранения";
|
||||
private static final String DEL = "§c▸ Нажмите для удаления";
|
||||
|
||||
private MineEditItems() {}
|
||||
|
||||
static ItemStack filler() {
|
||||
return ItemStackFactory.filler();
|
||||
}
|
||||
|
||||
static ItemStack blocksItem(MineConfig config) {
|
||||
var w = config.blocks().weights();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТипов: §7" + config.blocks().blockCount());
|
||||
lore.add(" §fРежим: §7" + config.blocks().fillMode().name());
|
||||
lore.add(" §fКастомные: " + (config.blocks().hasCustomBlocks() ? "§aДа" : "§7Нет"));
|
||||
if (!w.isEmpty()) {
|
||||
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)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return ItemStackFactory.create(Material.DIAMOND_PICKAXE, "§a§lБлоки", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
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Изменяется через палочку"
|
||||
);
|
||||
}
|
||||
|
||||
static ItemStack resetItem(MineConfig config) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fИнтервал: §7" + config.reset().intervalDisplay());
|
||||
lore.add(" §fТриггер по %: " + (config.reset().isPercentTriggerEnabled() ? "§a" + config.reset().percentTrigger() + "%" : "§7выкл"));
|
||||
lore.add(" §fКоманд: §7" + config.reset().commands().size());
|
||||
lore.add("");
|
||||
lore.add(" §fСообщение: " + (config.reset().broadcastMessage().isEmpty() ? "§7нет" : "§aесть"));
|
||||
if (!config.reset().broadcastMessage().isEmpty()) {
|
||||
lore.add(" §8" + ItemStackFactory.trunc(config.reset().broadcastMessage(), 25));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return ItemStackFactory.create(Material.CLOCK, "§a§lСброс", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
static ItemStack rewardsItem(MineConfig config) {
|
||||
var entries = config.rewards().entries();
|
||||
long items = entries.stream().flatMap(e -> e.items().stream()).count();
|
||||
long cmds = entries.stream().filter(e -> !e.commands().isEmpty()).count();
|
||||
long blockDrop = entries.stream().filter(RewardConfig.RewardEntry::preventVanillaDrops).count();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fЗаписей: §7" + entries.size());
|
||||
if (!entries.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("§7Типы наград:");
|
||||
lore.add(" §8• §fПредметов: §7" + items);
|
||||
lore.add(" §8• §fКоманд: §7" + cmds);
|
||||
if (blockDrop > 0) lore.add(" §8• §cБлокируют дроп: §7" + blockDrop);
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return ItemStackFactory.create(Material.GOLD_INGOT, "§a§lНаграды", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
static ItemStack teleportItem(MineConfig config) {
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТелепорт: " + (config.teleport().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.teleport().enabled()) {
|
||||
config.teleport().getLocation().ifPresent(loc -> lore.add(" " + ItemStackFactory.loc(loc)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add(" §fСпавн (застрявших): " + (config.playerSpawn().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.playerSpawn().enabled()) {
|
||||
config.playerSpawn().getLocation().ifPresent(loc -> lore.add(" " + ItemStackFactory.loc(loc)));
|
||||
}
|
||||
lore.add("");
|
||||
lore.add("§8Команды:");
|
||||
lore.add(" §8• §7/lm setteleport §f<mine>");
|
||||
lore.add(" §8• §7/lm setspawn §f<mine>");
|
||||
lore.add(" §8• §7/lm clearspawn §f<mine>");
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return ItemStackFactory.create(Material.ENDER_PEARL, "§a§lТелепорт", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
static ItemStack saveItem() {
|
||||
return ItemStackFactory.create(Material.LIME_DYE, "§a§lСохранить изменения",
|
||||
SEP,
|
||||
"§7Сохраняет конфигурацию шахты",
|
||||
"§7в файл на диске",
|
||||
"",
|
||||
SAVE
|
||||
);
|
||||
}
|
||||
|
||||
static ItemStack deleteItem(String mineName) {
|
||||
return ItemStackFactory.create(Material.TNT, "§c§lУдалить шахту",
|
||||
SEP,
|
||||
"§c§lВнимание!",
|
||||
"§cЭто действие нельзя отменить!",
|
||||
"",
|
||||
"§7Шахта: §f" + mineName,
|
||||
"",
|
||||
DEL
|
||||
);
|
||||
}
|
||||
|
||||
static ItemStack infoItem(Mine mine) {
|
||||
int blocks = mine.getBlocks();
|
||||
int total = mine.getTotalVolume();
|
||||
double percent = mine.getPercentFilled();
|
||||
String c = percent > 50 ? "§a" : percent > 25 ? "§e" : "§c";
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
static ItemStack backItem() {
|
||||
return ItemStackFactory.create(Material.ARROW, "§c§lЗакрыть",
|
||||
SEP,
|
||||
"§7Закрыть редактор",
|
||||
"",
|
||||
CLOSE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package dev.loki.lomines.handler;
|
||||
|
||||
import dev.loki.lomines.core.Mine;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.handler;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.handler;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.data.config.block.FillMode;
|
||||
import dev.loki.lomines.util.block.BlockUpdateUtil;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.integration.placeholder;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package dev.loki.lomines.integration.worldguard;
|
||||
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handles adding owners and members to WorldGuard regions.
|
||||
*/
|
||||
final class WorldGuardMemberHandler {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
|
||||
WorldGuardMemberHandler(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
void applyOwnersAndMembers(ProtectedCuboidRegion region, WorldGuardConfig wgConfig) {
|
||||
applyOwners(region, wgConfig);
|
||||
applyMembers(region, wgConfig);
|
||||
}
|
||||
|
||||
private void applyOwners(ProtectedCuboidRegion region, WorldGuardConfig wgConfig) {
|
||||
for (String owner : wgConfig.owners()) {
|
||||
if (owner.startsWith("uuid:")) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(owner.substring(5));
|
||||
region.getOwners().addPlayer(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.loLogger().warn("Invalid owner UUID: " + owner);
|
||||
}
|
||||
} else {
|
||||
region.getOwners().addPlayer(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyMembers(ProtectedCuboidRegion region, WorldGuardConfig wgConfig) {
|
||||
for (String member : wgConfig.members()) {
|
||||
if (member.startsWith("uuid:")) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(member.substring(5));
|
||||
region.getMembers().addPlayer(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.loLogger().warn("Invalid member UUID: " + member);
|
||||
}
|
||||
} else {
|
||||
region.getMembers().addPlayer(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,21 +11,21 @@ import org.bukkit.Bukkit;
|
|||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service for managing WorldGuard regions for mines.
|
||||
* Creates, updates, and deletes WorldGuard regions when mines are managed.
|
||||
*/
|
||||
public final class WorldGuardRegionService {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final WorldGuardFlagParser flagParser;
|
||||
private final WorldGuardMemberHandler memberHandler;
|
||||
private boolean worldGuardEnabled;
|
||||
|
||||
public WorldGuardRegionService(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.flagParser = new WorldGuardFlagParser(plugin);
|
||||
this.memberHandler = new WorldGuardMemberHandler(plugin);
|
||||
checkWorldGuard();
|
||||
}
|
||||
|
||||
|
|
@ -34,30 +34,21 @@ public final class WorldGuardRegionService {
|
|||
&& Bukkit.getPluginManager().isPluginEnabled("WorldGuard");
|
||||
|
||||
if (worldGuardEnabled) {
|
||||
plugin.loLogger().info("WorldGuard integration enabled - auto-region creation active");
|
||||
plugin.loLogger().info("WorldGuard integration enabled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WorldGuard region for a mine if enabled in config.
|
||||
*/
|
||||
public String createRegion(String mineName, MineConfig config) {
|
||||
if (!worldGuardEnabled) {
|
||||
return null;
|
||||
}
|
||||
if (!worldGuardEnabled) return null;
|
||||
|
||||
WorldGuardConfig wgConfig = config.worldGuard();
|
||||
if (wgConfig == null || !wgConfig.enabled()) {
|
||||
return null;
|
||||
}
|
||||
if (wgConfig == null || !wgConfig.enabled()) return null;
|
||||
|
||||
String regionName = wgConfig.generateRegionName(mineName);
|
||||
|
||||
try {
|
||||
RegionManager regionManager = getRegionManager(config.worldName());
|
||||
if (regionManager == null) {
|
||||
return null;
|
||||
}
|
||||
if (regionManager == null) return null;
|
||||
|
||||
if (regionManager.hasRegion(regionName)) {
|
||||
plugin.loLogger().info("WorldGuard region already exists: " + regionName);
|
||||
|
|
@ -70,7 +61,7 @@ public final class WorldGuardRegionService {
|
|||
return null;
|
||||
}
|
||||
|
||||
applyOwnersAndMembers(region, wgConfig);
|
||||
memberHandler.applyOwnersAndMembers(region, wgConfig);
|
||||
applyFlags(region, wgConfig.flags());
|
||||
|
||||
regionManager.addRegion(region);
|
||||
|
|
@ -79,31 +70,21 @@ public final class WorldGuardRegionService {
|
|||
|
||||
} catch (Exception e) {
|
||||
plugin.loLogger().error("Failed to create WorldGuard region for mine " + mineName + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing WorldGuard region when mine regions change.
|
||||
*/
|
||||
public boolean updateRegion(String mineName, MineConfig config) {
|
||||
if (!worldGuardEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (!worldGuardEnabled) return false;
|
||||
|
||||
WorldGuardConfig wgConfig = config.worldGuard();
|
||||
if (wgConfig == null || !wgConfig.enabled()) {
|
||||
return false;
|
||||
}
|
||||
if (wgConfig == null || !wgConfig.enabled()) return false;
|
||||
|
||||
String regionName = wgConfig.generateRegionName(mineName);
|
||||
|
||||
try {
|
||||
RegionManager regionManager = getRegionManager(config.worldName());
|
||||
if (regionManager == null) {
|
||||
return false;
|
||||
}
|
||||
if (regionManager == null) return false;
|
||||
|
||||
if (regionManager.hasRegion(regionName)) {
|
||||
regionManager.removeRegion(regionName);
|
||||
|
|
@ -117,26 +98,17 @@ public final class WorldGuardRegionService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a WorldGuard region for a mine.
|
||||
*/
|
||||
public boolean deleteRegion(String mineName, MineConfig config) {
|
||||
if (!worldGuardEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (!worldGuardEnabled) return false;
|
||||
|
||||
WorldGuardConfig wgConfig = config.worldGuard();
|
||||
if (wgConfig == null) {
|
||||
return false;
|
||||
}
|
||||
if (wgConfig == null) return false;
|
||||
|
||||
String regionName = wgConfig.generateRegionName(mineName);
|
||||
|
||||
try {
|
||||
RegionManager regionManager = getRegionManager(config.worldName());
|
||||
if (regionManager == null) {
|
||||
return false;
|
||||
}
|
||||
if (regionManager == null) return false;
|
||||
|
||||
if (regionManager.hasRegion(regionName)) {
|
||||
regionManager.removeRegion(regionName);
|
||||
|
|
@ -153,9 +125,7 @@ public final class WorldGuardRegionService {
|
|||
|
||||
private RegionManager getRegionManager(String worldName) {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
if (world == null) return null;
|
||||
|
||||
return WorldGuard.getInstance()
|
||||
.getPlatform()
|
||||
|
|
@ -165,9 +135,7 @@ public final class WorldGuardRegionService {
|
|||
|
||||
private ProtectedCuboidRegion createRegionFromCuboids(String regionName, MineConfig config) {
|
||||
List<Cuboid> regions = config.region().regions();
|
||||
if (regions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (regions.isEmpty()) return null;
|
||||
|
||||
Cuboid first = regions.get(0);
|
||||
BlockVector3 min = BlockVector3.at(first.getMinX(), first.getMinY(), first.getMinZ());
|
||||
|
|
@ -183,36 +151,6 @@ public final class WorldGuardRegionService {
|
|||
return mainRegion;
|
||||
}
|
||||
|
||||
private void applyOwnersAndMembers(ProtectedCuboidRegion region, WorldGuardConfig wgConfig) {
|
||||
// Set owners
|
||||
for (String owner : wgConfig.owners()) {
|
||||
if (owner.startsWith("uuid:")) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(owner.substring(5));
|
||||
region.getOwners().addPlayer(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.loLogger().warn("Invalid owner UUID: " + owner);
|
||||
}
|
||||
} else {
|
||||
region.getOwners().addPlayer(owner);
|
||||
}
|
||||
}
|
||||
|
||||
// Set members
|
||||
for (String member : wgConfig.members()) {
|
||||
if (member.startsWith("uuid:")) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(member.substring(5));
|
||||
region.getMembers().addPlayer(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.loLogger().warn("Invalid member UUID: " + member);
|
||||
}
|
||||
} else {
|
||||
region.getMembers().addPlayer(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyFlags(ProtectedCuboidRegion region, List<String> flags) {
|
||||
for (String flagEntry : flags) {
|
||||
flagParser.parseAndSetFlag(region, flagEntry);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.listener;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package dev.loki.lomines.listener;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.gui.GroupCreateGui;
|
||||
import dev.loki.lomines.gui.GroupCreateGuiHolder;
|
||||
import dev.loki.lomines.gui.group.GroupCreateGui;
|
||||
import dev.loki.lomines.gui.group.GroupCreateGuiHolder;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package dev.loki.lomines.listener;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.gui.ConfirmDeleteGui;
|
||||
import dev.loki.lomines.gui.ConfirmDeleteGuiHolder;
|
||||
import dev.loki.lomines.gui.MineEditGui;
|
||||
import dev.loki.lomines.gui.MineEditGuiHolder;
|
||||
import dev.loki.lomines.gui.confirm.ConfirmDeleteGui;
|
||||
import dev.loki.lomines.gui.confirm.ConfirmDeleteGuiHolder;
|
||||
import dev.loki.lomines.gui.mine.MineEditGui;
|
||||
import dev.loki.lomines.gui.mine.MineEditGuiHolder;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.listener;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.gui.GroupCreateGui;
|
||||
import dev.loki.lomines.gui.group.GroupCreateGui;
|
||||
import dev.loki.lomines.wand.GroupWandItem;
|
||||
import dev.loki.lomines.wand.GroupWandSession;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ 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.
|
||||
|
|
@ -27,15 +26,11 @@ public final class BlockUpdateUtil {
|
|||
* @param block the block to update
|
||||
*/
|
||||
public static void sendBlockUpdate(Block block) {
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
if (block == null) return;
|
||||
|
||||
// Get chunk and players in it
|
||||
Chunk chunk = block.getChunk();
|
||||
World world = block.getWorld();
|
||||
|
||||
// Send block change to all players in the world who can see this chunk
|
||||
for (Player player : world.getPlayers()) {
|
||||
if (isChunkVisibleToPlayer(player, chunk)) {
|
||||
player.sendBlockChange(block.getLocation(), block.getBlockData());
|
||||
|
|
@ -63,7 +58,6 @@ public final class BlockUpdateUtil {
|
|||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
|
||||
for (Player player : players) {
|
||||
if (isLocationVisibleToPlayer(player, block.getLocation())) {
|
||||
player.sendBlockChange(block.getLocation(), block.getBlockData());
|
||||
|
|
@ -81,16 +75,11 @@ public final class BlockUpdateUtil {
|
|||
* @param locations the locations to update
|
||||
*/
|
||||
public static void sendLocationsUpdate(java.util.List<Location> locations) {
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (locations == null || locations.isEmpty()) return;
|
||||
|
||||
for (Location loc : locations) {
|
||||
if (loc.getWorld() == null) {
|
||||
continue;
|
||||
}
|
||||
if (loc.getWorld() == null) continue;
|
||||
Block block = loc.getBlock();
|
||||
|
||||
for (Player player : loc.getWorld().getPlayers()) {
|
||||
if (isLocationVisibleToPlayer(player, loc)) {
|
||||
player.sendBlockChange(loc, block.getBlockData());
|
||||
|
|
@ -127,109 +116,8 @@ public final class BlockUpdateUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a safe teleport location near the given destination.
|
||||
* Checks for suffocation hazards (blocks at head/body level).
|
||||
* <p>Will not teleport too high - limited to maxUpOffset blocks above original.</p>
|
||||
*
|
||||
* @param destination the desired destination
|
||||
* @param maxUpOffset maximum blocks to search upward (to avoid teleporting too high)
|
||||
* @return a safe location (may be the same as destination if safe)
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination, int maxUpOffset) {
|
||||
if (destination == null || destination.getWorld() == null) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
World world = destination.getWorld();
|
||||
int x = destination.getBlockX();
|
||||
int y = destination.getBlockY();
|
||||
int z = destination.getBlockZ();
|
||||
float yaw = destination.getYaw();
|
||||
float pitch = destination.getPitch();
|
||||
|
||||
// Clamp maxUpOffset to reasonable range (2-10)
|
||||
maxUpOffset = Math.max(2, Math.min(maxUpOffset, 10));
|
||||
|
||||
// Check if original location is safe
|
||||
if (isSafeLocation(world, x, y, z)) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
// Search nearby blocks first (same Y level, closer distance)
|
||||
int[][] nearby = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (int[] offset : nearby) {
|
||||
if (isSafeLocation(world, x + offset[0], y, z + offset[1])) {
|
||||
return new Location(world, x + offset[0] + 0.5, y, z + offset[1] + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search upward (limited by maxUpOffset to avoid teleporting too high)
|
||||
for (int offset = 1; offset <= maxUpOffset; offset++) {
|
||||
if (isSafeLocation(world, x, y + offset, z)) {
|
||||
return new Location(world, x + 0.5, y + offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search downward
|
||||
for (int offset = 1; offset <= 5 && y - offset >= world.getMinHeight(); offset++) {
|
||||
if (isSafeLocation(world, x, y - offset, z)) {
|
||||
return new Location(world, x + 0.5, y - offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
// Search nearby with small Y offset
|
||||
int[][] nearbyDiagonal = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
|
||||
for (int[] offset : nearbyDiagonal) {
|
||||
for (int yOffset = -1; yOffset <= maxUpOffset; yOffset++) {
|
||||
int newY = y + yOffset;
|
||||
if (newY < world.getMinHeight() || newY >= world.getMaxHeight()) {
|
||||
continue;
|
||||
}
|
||||
if (isSafeLocation(world, x + offset[0], newY, z + offset[1])) {
|
||||
return new Location(world, x + offset[0] + 0.5, newY, z + offset[1] + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing else works, return original but at least center it
|
||||
return new Location(world, x + 0.5, y, z + 0.5, yaw, pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a safe teleport location with default max up offset of 3 blocks.
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination) {
|
||||
return findSafeTeleportLocation(destination, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a location is safe for teleport (no suffocation).
|
||||
*/
|
||||
private static boolean isSafeLocation(World world, int x, int y, int z) {
|
||||
// Check feet position (can be passable)
|
||||
Block feetBlock = world.getBlockAt(x, y, z);
|
||||
// Check head position (must be passable)
|
||||
Block headBlock = world.getBlockAt(x, y + 1, z);
|
||||
|
||||
// Safe if feet is passable and head is passable
|
||||
return isPassable(feetBlock) && isPassable(headBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a block is passable (not solid).
|
||||
*/
|
||||
private static boolean isPassable(Block block) {
|
||||
return !block.getType().isSolid() || block.getType().isAir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player can see a chunk (is within render distance).
|
||||
*/
|
||||
private static boolean isChunkVisibleToPlayer(Player player, Chunk chunk) {
|
||||
if (!player.getWorld().equals(chunk.getWorld())) {
|
||||
return false;
|
||||
}
|
||||
if (!player.getWorld().equals(chunk.getWorld())) return false;
|
||||
|
||||
int renderDistance = player.getClientViewDistance();
|
||||
int playerChunkX = player.getLocation().getBlockX() >> 4;
|
||||
|
|
@ -238,19 +126,13 @@ public final class BlockUpdateUtil {
|
|||
int dx = Math.abs(playerChunkX - chunk.getX());
|
||||
int dz = Math.abs(playerChunkZ - chunk.getZ());
|
||||
|
||||
// Check if chunk is within player's view distance
|
||||
return dx <= renderDistance && dz <= renderDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a location is visible to a player (within render distance).
|
||||
*/
|
||||
private static boolean isLocationVisibleToPlayer(Player player, Location loc) {
|
||||
if (!player.getWorld().equals(loc.getWorld())) {
|
||||
return false;
|
||||
}
|
||||
if (!player.getWorld().equals(loc.getWorld())) return false;
|
||||
|
||||
double renderDistance = player.getClientViewDistance() * 16; // blocks
|
||||
double renderDistance = player.getClientViewDistance() * 16;
|
||||
return player.getLocation().distanceSquared(loc) <= renderDistance * renderDistance;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,33 @@ public final class LocationParser {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a simple location string in the format "world;x;y;z" without yaw/pitch.
|
||||
* Returns null if parsing fails.
|
||||
*
|
||||
* @param locationString the location string to parse
|
||||
* @return the parsed Location object, or null if parsing fails
|
||||
*/
|
||||
public static Location parseSimple(String locationString) {
|
||||
if (locationString == null || locationString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = locationString.split(";");
|
||||
if (parts.length < 4) return null;
|
||||
|
||||
World world = Bukkit.getWorld(parts[0]);
|
||||
if (world == null) return null;
|
||||
|
||||
try {
|
||||
double x = Double.parseDouble(parts[1]);
|
||||
double y = Double.parseDouble(parts[2]);
|
||||
double z = Double.parseDouble(parts[3]);
|
||||
return new Location(world, x, y, z);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Location object to a string in the format "world;x;y;z;yaw;pitch".
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
package dev.loki.lomines.util.location;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
/**
|
||||
* Utility for finding safe teleport locations.
|
||||
* Checks for suffocation hazards and finds alternative safe positions.
|
||||
*/
|
||||
public final class SafeTeleportUtil {
|
||||
|
||||
private SafeTeleportUtil() {}
|
||||
|
||||
/**
|
||||
* Finds a safe teleport location near the given destination.
|
||||
* Checks for suffocation hazards (blocks at head/body level).
|
||||
* <p>Will not teleport too high - limited to maxUpOffset blocks above original.</p>
|
||||
*
|
||||
* @param destination the desired destination
|
||||
* @param maxUpOffset maximum blocks to search upward (to avoid teleporting too high)
|
||||
* @return a safe location (may be the same as destination if safe)
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination, int maxUpOffset) {
|
||||
if (destination == null || destination.getWorld() == null) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
World world = destination.getWorld();
|
||||
int x = destination.getBlockX();
|
||||
int y = destination.getBlockY();
|
||||
int z = destination.getBlockZ();
|
||||
float yaw = destination.getYaw();
|
||||
float pitch = destination.getPitch();
|
||||
|
||||
maxUpOffset = Math.max(2, Math.min(maxUpOffset, 10));
|
||||
|
||||
if (isSafeLocation(world, x, y, z)) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
Location result = findNearbySafe(world, x, y, z, yaw, pitch);
|
||||
if (result != null) return result;
|
||||
|
||||
result = searchUpward(world, x, y, z, yaw, pitch, maxUpOffset);
|
||||
if (result != null) return result;
|
||||
|
||||
result = searchDownward(world, x, y, z, yaw, pitch);
|
||||
if (result != null) return result;
|
||||
|
||||
result = searchDiagonal(world, x, y, z, yaw, pitch, maxUpOffset);
|
||||
if (result != null) return result;
|
||||
|
||||
return new Location(world, x + 0.5, y, z + 0.5, yaw, pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a safe teleport location with default max up offset of 3 blocks.
|
||||
*/
|
||||
public static Location findSafeTeleportLocation(Location destination) {
|
||||
return findSafeTeleportLocation(destination, 3);
|
||||
}
|
||||
|
||||
private static Location findNearbySafe(World world, int x, int y, int z, float yaw, float pitch) {
|
||||
int[][] nearby = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
|
||||
for (int[] offset : nearby) {
|
||||
if (isSafeLocation(world, x + offset[0], y, z + offset[1])) {
|
||||
return new Location(world, x + offset[0] + 0.5, y, z + offset[1] + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Location searchUpward(World world, int x, int y, int z, float yaw, float pitch, int maxUpOffset) {
|
||||
for (int offset = 1; offset <= maxUpOffset; offset++) {
|
||||
if (isSafeLocation(world, x, y + offset, z)) {
|
||||
return new Location(world, x + 0.5, y + offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Location searchDownward(World world, int x, int y, int z, float yaw, float pitch) {
|
||||
for (int offset = 1; offset <= 5 && y - offset >= world.getMinHeight(); offset++) {
|
||||
if (isSafeLocation(world, x, y - offset, z)) {
|
||||
return new Location(world, x + 0.5, y - offset, z + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Location searchDiagonal(World world, int x, int y, int z, float yaw, float pitch, int maxUpOffset) {
|
||||
int[][] nearbyDiagonal = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
|
||||
for (int[] offset : nearbyDiagonal) {
|
||||
for (int yOffset = -1; yOffset <= maxUpOffset; yOffset++) {
|
||||
int newY = y + yOffset;
|
||||
if (newY < world.getMinHeight() || newY >= world.getMaxHeight()) {
|
||||
continue;
|
||||
}
|
||||
if (isSafeLocation(world, x + offset[0], newY, z + offset[1])) {
|
||||
return new Location(world, x + offset[0] + 0.5, newY, z + offset[1] + 0.5, yaw, pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSafeLocation(World world, int x, int y, int z) {
|
||||
Block feetBlock = world.getBlockAt(x, y, z);
|
||||
Block headBlock = world.getBlockAt(x, y + 1, z);
|
||||
return isPassable(feetBlock) && isPassable(headBlock);
|
||||
}
|
||||
|
||||
private static boolean isPassable(Block block) {
|
||||
return !block.getType().isSolid() || block.getType().isAir();
|
||||
}
|
||||
}
|
||||
120
src/main/java/dev/loki/lomines/wand/ParticleUtil.java
Normal file
120
src/main/java/dev/loki/lomines/wand/ParticleUtil.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package dev.loki.lomines.wand;
|
||||
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Utility class for spawning particles to visualize wand selections.
|
||||
*/
|
||||
final class ParticleUtil {
|
||||
|
||||
private ParticleUtil() {}
|
||||
|
||||
static void showCornerParticles(Player player, Location loc, Color color) {
|
||||
World world = loc.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double angle = i * Math.PI / 4;
|
||||
double x = loc.getX() + 0.5 + Math.cos(angle) * 0.5;
|
||||
double z = loc.getZ() + 0.5 + Math.sin(angle) * 0.5;
|
||||
double y = loc.getY() + 0.5;
|
||||
|
||||
Location particleLoc = new Location(world, x, y, z);
|
||||
player.spawnParticle(Particle.DUST, particleLoc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
|
||||
static void showEdgeParticles(Player player, Location pos1, Location pos2, Color color) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.8f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
double step = 1.0;
|
||||
|
||||
// Bottom face edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, maxX, minY, minZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, maxX, minY, maxZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, minY, maxZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, minY, maxZ, step, dustOptions);
|
||||
|
||||
// Top face edges
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, maxX, maxY, minZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, minX, maxY, maxZ, maxX, maxY, maxZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, minX, maxY, maxZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, maxX, maxY, minZ, maxX, maxY, maxZ, step, dustOptions);
|
||||
|
||||
// Vertical edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, maxY, minZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, maxY, minZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, minX, maxY, maxZ, step, dustOptions);
|
||||
spawnEdgeParticles(player, world, maxX, minY, maxZ, maxX, maxY, maxZ, step, dustOptions);
|
||||
}
|
||||
|
||||
private static void spawnEdgeParticles(Player player, World world,
|
||||
double x1, double y1, double z1,
|
||||
double x2, double y2, double z2,
|
||||
double step, Particle.DustOptions dustOptions) {
|
||||
double distance = Math.sqrt(
|
||||
Math.pow(x2 - x1, 2) +
|
||||
Math.pow(y2 - y1, 2) +
|
||||
Math.pow(z2 - z1, 2)
|
||||
);
|
||||
|
||||
int count = (int) (distance / step) + 1;
|
||||
|
||||
for (int i = 0; i <= count; i++) {
|
||||
double t = (double) i / count;
|
||||
double x = x1 + (x2 - x1) * t + 0.5;
|
||||
double y = y1 + (y2 - y1) * t + 0.5;
|
||||
double z = z1 + (z2 - z1) * t + 0.5;
|
||||
|
||||
Location loc = new Location(world, x, y, z);
|
||||
if (loc.getWorld() != null && loc.getWorld().equals(player.getWorld())) {
|
||||
player.spawnParticle(Particle.DUST, loc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void showOutlineParticles(Player player, Location pos1, Location pos2, Color color, int density) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null || !world.equals(player.getWorld())) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.5f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
Location[] corners = {
|
||||
new Location(world, minX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, maxZ + 0.5)
|
||||
};
|
||||
|
||||
for (Location corner : corners) {
|
||||
player.spawnParticle(Particle.DUST, corner, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ package dev.loki.lomines.wand;
|
|||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
|
|
@ -21,23 +19,17 @@ public final class WandParticleService {
|
|||
private final LoMinesPlugin plugin;
|
||||
private final Map<UUID, BukkitRunnable> activeTasks = new ConcurrentHashMap<>();
|
||||
|
||||
// Particle colors for different states
|
||||
private static final Color COLOR_POS1 = Color.LIME; // Green for corner 1
|
||||
private static final Color COLOR_POS2 = Color.AQUA; // Cyan for corner 2
|
||||
private static final Color COLOR_EDGE = Color.YELLOW; // Yellow for edges
|
||||
private static final Color COLOR_READY = Color.GREEN; // Green when both corners set
|
||||
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;
|
||||
|
||||
public WandParticleService(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts showing particles for a player holding the wand.
|
||||
*/
|
||||
public void startShowingParticles(Player player) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
// Stop existing task if any
|
||||
stopShowingParticles(playerId);
|
||||
|
||||
BukkitRunnable task = new BukkitRunnable() {
|
||||
|
|
@ -48,28 +40,19 @@ public final class WandParticleService {
|
|||
activeTasks.remove(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
showParticles(player);
|
||||
}
|
||||
};
|
||||
|
||||
task.runTaskTimer(plugin, 0L, 10L); // Every 10 ticks (0.5 seconds)
|
||||
task.runTaskTimer(plugin, 0L, 10L);
|
||||
activeTasks.put(playerId, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops showing particles for a player.
|
||||
*/
|
||||
public void stopShowingParticles(UUID playerId) {
|
||||
BukkitRunnable task = activeTasks.remove(playerId);
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
if (task != null) task.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all particle tasks.
|
||||
*/
|
||||
public void stopAll() {
|
||||
for (BukkitRunnable task : activeTasks.values()) {
|
||||
task.cancel();
|
||||
|
|
@ -88,141 +71,34 @@ public final class WandParticleService {
|
|||
Location pos1 = session.getPos1(activeSlot);
|
||||
Location pos2 = session.getPos2(activeSlot);
|
||||
|
||||
// Show particles for corner 1 if set
|
||||
if (pos1 != null && pos1.getWorld() != null) {
|
||||
showCornerParticles(player, pos1, COLOR_POS1);
|
||||
ParticleUtil.showCornerParticles(player, pos1, COLOR_POS1);
|
||||
}
|
||||
|
||||
// Show particles for corner 2 if set
|
||||
if (pos2 != null && pos2.getWorld() != null) {
|
||||
showCornerParticles(player, pos2, COLOR_POS2);
|
||||
ParticleUtil.showCornerParticles(player, pos2, COLOR_POS2);
|
||||
}
|
||||
|
||||
// Show edge particles if both corners are set
|
||||
if (pos1 != null && pos2 != null && pos1.getWorld() != null && pos2.getWorld() != null) {
|
||||
if (pos1.getWorld().equals(pos2.getWorld())) {
|
||||
showEdgeParticles(player, pos1, pos2, session.isSlotReady(activeSlot) ? COLOR_READY : COLOR_EDGE);
|
||||
Color edgeColor = session.isSlotReady(activeSlot) ? COLOR_READY : COLOR_EDGE;
|
||||
ParticleUtil.showEdgeParticles(player, pos1, pos2, edgeColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Also show other slots that are ready (with lower intensity)
|
||||
showOtherSlots(player, session, activeSlot);
|
||||
}
|
||||
|
||||
private void showOtherSlots(Player player, GroupWandSession session, int activeSlot) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (i == activeSlot) continue;
|
||||
if (session.isSlotReady(i)) {
|
||||
Location p1 = session.getPos1(i);
|
||||
Location p2 = session.getPos2(i);
|
||||
if (p1 != null && p2 != null && p1.getWorld() != null && p1.getWorld().equals(player.getWorld())) {
|
||||
showOutlineParticles(player, p1, p2, Color.GRAY, 5);
|
||||
ParticleUtil.showOutlineParticles(player, p1, p2, Color.GRAY, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showCornerParticles(Player player, Location loc, Color color) {
|
||||
World world = loc.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f);
|
||||
|
||||
// Show particles in a small sphere around the corner
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double angle = i * Math.PI / 4;
|
||||
double x = loc.getX() + 0.5 + Math.cos(angle) * 0.5;
|
||||
double z = loc.getZ() + 0.5 + Math.sin(angle) * 0.5;
|
||||
double y = loc.getY() + 0.5;
|
||||
|
||||
Location particleLoc = new Location(world, x, y, z);
|
||||
player.spawnParticle(Particle.DUST, particleLoc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private void showEdgeParticles(Player player, Location pos1, Location pos2, Color color) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.8f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
// Show particles along the edges of the cuboid
|
||||
double step = 1.0; // Every block
|
||||
|
||||
// Bottom face edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, maxX, minY, minZ, step, dustOptions); // Bottom front
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, maxX, minY, maxZ, step, dustOptions); // Bottom back
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, minY, maxZ, step, dustOptions); // Bottom left
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, minY, maxZ, step, dustOptions); // Bottom right
|
||||
|
||||
// Top face edges
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, maxX, maxY, minZ, step, dustOptions); // Top front
|
||||
spawnEdgeParticles(player, world, minX, maxY, maxZ, maxX, maxY, maxZ, step, dustOptions); // Top back
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, minX, maxY, maxZ, step, dustOptions); // Top left
|
||||
spawnEdgeParticles(player, world, maxX, maxY, minZ, maxX, maxY, maxZ, step, dustOptions); // Top right
|
||||
|
||||
// Vertical edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, maxY, minZ, step, dustOptions); // Front left
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, maxY, minZ, step, dustOptions); // Front right
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, minX, maxY, maxZ, step, dustOptions); // Back left
|
||||
spawnEdgeParticles(player, world, maxX, minY, maxZ, maxX, maxY, maxZ, step, dustOptions); // Back right
|
||||
}
|
||||
|
||||
private void spawnEdgeParticles(Player player, World world,
|
||||
double x1, double y1, double z1,
|
||||
double x2, double y2, double z2,
|
||||
double step, Particle.DustOptions dustOptions) {
|
||||
double distance = Math.sqrt(
|
||||
Math.pow(x2 - x1, 2) +
|
||||
Math.pow(y2 - y1, 2) +
|
||||
Math.pow(z2 - z1, 2)
|
||||
);
|
||||
|
||||
int count = (int) (distance / step) + 1;
|
||||
|
||||
for (int i = 0; i <= count; i++) {
|
||||
double t = (double) i / count;
|
||||
double x = x1 + (x2 - x1) * t + 0.5;
|
||||
double y = y1 + (y2 - y1) * t + 0.5;
|
||||
double z = z1 + (z2 - z1) * t + 0.5;
|
||||
|
||||
Location loc = new Location(world, x, y, z);
|
||||
if (loc.getWorld() != null && loc.getWorld().equals(player.getWorld())) {
|
||||
player.spawnParticle(Particle.DUST, loc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showOutlineParticles(Player player, Location pos1, Location pos2, Color color, int density) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null || !world.equals(player.getWorld())) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.5f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
// Just show corners for non-active slots (lighter)
|
||||
Location[] corners = {
|
||||
new Location(world, minX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, maxZ + 0.5)
|
||||
};
|
||||
|
||||
for (Location corner : corners) {
|
||||
player.spawnParticle(Particle.DUST, corner, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.command;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.core.mine.Mine;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue