feat: add info, teleport, and copy commands

This commit is contained in:
loki5512344 2026-06-01 11:12:42 +02:00
parent f6cccbcd33
commit 957468b6b6
6 changed files with 305 additions and 6 deletions

View file

@ -1,11 +1,14 @@
package dev.loki.lomines;
import dev.loki.lomines.command.admin.AdminCommands;
import dev.loki.lomines.command.admin.CopyCommand;
import dev.loki.lomines.command.admin.HologramCommands;
import dev.loki.lomines.command.admin.InfoCommand;
import dev.loki.lomines.command.admin.MaskCommands;
import dev.loki.lomines.command.admin.StatsCommands;
import dev.loki.lomines.command.common.LoMinesTabCompleter;
import dev.loki.lomines.command.player.PlayerCommands;
import dev.loki.lomines.command.player.TeleportCommand;
import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.listener.BlockBreakListener;
import dev.loki.lomines.listener.GroupGuiListener;
@ -31,6 +34,9 @@ final class RegistrationManager {
commandManager.register(new StatsCommands(plugin));
commandManager.register(new MaskCommands(plugin));
commandManager.register(new HologramCommands(plugin));
commandManager.register(new InfoCommand(plugin));
commandManager.register(new TeleportCommand(plugin));
commandManager.register(new CopyCommand(plugin));
plugin.loLogger().info("Commands registered");
}

View file

@ -0,0 +1,108 @@
package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.mine.MineConfig;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import java.util.List;
import java.util.Map;
/**
* Command to copy mine configuration from one mine to another.
*/
public class CopyCommand {
private final LoMinesPlugin plugin;
public CopyCommand(LoMinesPlugin plugin) {
this.plugin = plugin;
}
/**
* Copies mine configuration (blocks, reset settings, rewards, etc.)
* from source mine to target mine. Regions and positions are NOT copied.
* Usage: /lm copy <from> <to>
*/
@Subcommand(value = "copy", permission = "lomines.admin.copy")
void copy(CommandSender sender,
@Arg("from-mine") String fromMineName,
@Arg("to-mine") String toMineName) {
Mine fromMine = plugin.getMines().find(fromMineName).orElse(null);
if (fromMine == null) {
sender.sendMessage(Component.text("Исходная шахта не найдена: " + fromMineName, NamedTextColor.RED));
return;
}
Mine toMine = plugin.getMines().find(toMineName).orElse(null);
if (toMine == null) {
sender.sendMessage(Component.text("Целевая шахта не найдена: " + toMineName, NamedTextColor.RED));
return;
}
if (fromMine.getName().equalsIgnoreCase(toMine.getName())) {
sender.sendMessage(Component.text("Нельзя копировать шахту саму в себя!", NamedTextColor.RED));
return;
}
var sourceConfig = fromMine.getConfig();
var targetConfig = toMine.getConfig();
// Copy blocks configuration
targetConfig.blocks().weights().clear();
targetConfig.blocks().weights().putAll(sourceConfig.blocks().weights());
// Copy reset settings
var targetReset = targetConfig.reset();
var sourceReset = sourceConfig.reset();
targetReset.intervalSeconds(sourceReset.intervalSeconds());
targetReset.percentTrigger(sourceReset.percentTrigger());
targetReset.percentTriggerEnabled(sourceReset.isPercentTriggerEnabled());
targetReset.intervalDisplay(sourceReset.intervalDisplay());
// Copy teleport and spawn settings
var targetTeleport = targetConfig.teleport();
var sourceTeleport = sourceConfig.teleport();
targetTeleport.enabled(sourceTeleport.enabled());
if (sourceTeleport.getLocation().isPresent()) {
targetTeleport.setLocation(sourceTeleport.getLocation().get());
} else {
targetTeleport.setLocation(null);
}
var targetSpawn = targetConfig.playerSpawn();
var sourceSpawn = sourceConfig.playerSpawn();
targetSpawn.enabled(sourceSpawn.enabled());
if (sourceSpawn.getLocation().isPresent()) {
targetSpawn.setLocation(sourceSpawn.getLocation().get());
} else {
targetSpawn.setLocation(null);
}
// Copy rewards
targetConfig.rewards().entries().clear();
targetConfig.rewards().entries().addAll(sourceConfig.rewards().entries());
// Copy other settings
targetConfig.blockPhysics(sourceConfig.isBlockPhysicsEnabled());
targetConfig.entitySpawning(sourceConfig.isEntitySpawningEnabled());
targetConfig.liquidFlow(sourceConfig.isLiquidFlowEnabled());
// Save target mine
toMine.save();
sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() +
" §aв §f" + toMine.getName()));
sender.sendMessage(Component.text("§7Скопировано:"));
sender.sendMessage(Component.text(" §7• Блоки: §f" + sourceConfig.blocks().weights().size() + " типов"));
sender.sendMessage(Component.text(" §7• Настройки сброса"));
sender.sendMessage(Component.text(" §7• Точки телепорта/спавна"));
sender.sendMessage(Component.text(" §7• Награды: §f" + sourceConfig.rewards().entries().size() + " записей"));
sender.sendMessage(Component.text("§7§oПримечание: регионы и позиции не скопированы"));
}
}

View file

@ -0,0 +1,105 @@
package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
/**
* Command to display detailed information about a mine.
*/
public class InfoCommand {
private final LoMinesPlugin plugin;
public InfoCommand(LoMinesPlugin plugin) {
this.plugin = plugin;
}
/**
* Shows detailed information about a mine.
* Usage: /lm info <mine>
*/
@Subcommand(value = "info", permission = "lomines.admin.info")
void info(CommandSender sender, @Arg("mine") String mineName) {
Mine mine = plugin.getMines().find(mineName).orElse(null);
if (mine == null) {
sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
return;
}
var config = mine.getConfig();
sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
sender.sendMessage(Component.text("§6§lИнформация о шахте: §f" + mine.getName()));
sender.sendMessage(Component.text(""));
// Basic info
sender.sendMessage(Component.text("§7Мир: §f" + config.worldName()));
sender.sendMessage(Component.text("§7Блоков: §f" + mine.getBlocks() + "§7/§f" + mine.getTotalVolume() +
" §7(§f" + String.format("%.1f", mine.getPercentFilled()) + "%§7)"));
sender.sendMessage(Component.text(""));
// Regions
sender.sendMessage(Component.text("§6§lРегионы:"));
List<Cuboid> regions = mine.getRegions();
for (int i = 0; i < regions.size(); i++) {
Cuboid r = regions.get(i);
sender.sendMessage(Component.text("§7 #" + (i + 1) + ": §f" +
r.getMinX() + "," + r.getMinY() + "," + r.getMinZ() + " §7-> §f" +
r.getMaxX() + "," + r.getMaxY() + "," + r.getMaxZ() +
" §7(§f" + r.getVolume() + " §7блоков)"));
}
sender.sendMessage(Component.text(""));
// Blocks
sender.sendMessage(Component.text("§6§lБлоки:"));
Map<BlockKey, Double> weights = config.blocks().weights();
weights.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(10)
.forEach(e -> sender.sendMessage(Component.text(
"§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%")));
if (weights.size() > 10) {
sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков"));
}
sender.sendMessage(Component.text(""));
// Reset settings
sender.sendMessage(Component.text("§6§lНастройки сброса:"));
sender.sendMessage(Component.text("§7 Интервал: §f" + config.reset().intervalDisplay()));
sender.sendMessage(Component.text("§7 Триггер по %: §f" +
(config.reset().isPercentTriggerEnabled() ? config.reset().percentTrigger() + "%" : "выкл")));
sender.sendMessage(Component.text(""));
// Teleport/Spawn
sender.sendMessage(Component.text("§6§lТелепортация:"));
sender.sendMessage(Component.text("§7 Точка телепорта: §f" +
(config.teleport().enabled() ? "§aустановлена" : "§7не задана")));
sender.sendMessage(Component.text("§7 Точка спавна: §f" +
(config.playerSpawn().enabled() ? "§aустановлена" : "§7используется точка телепорта")));
sender.sendMessage(Component.text(""));
// Rewards
sender.sendMessage(Component.text("§6§lНаграды: §f" + config.rewards().entries().size() + " записей"));
sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
}
private String formatBlockKey(BlockKey key) {
return switch (key) {
case BlockKey.Vanilla v -> v.material().name().toLowerCase();
case BlockKey.Oraxen o -> "oraxen:" + o.id();
case BlockKey.ItemsAdder i -> "ia:" + i.id();
};
}
}

View file

@ -21,7 +21,7 @@ public class LoMinesTabCompleter implements TabCompleter {
private static final List<String> ALL_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "wand", "group",
"stats", "top", "maskscan", "edit", "setteleport", "setspawn",
"clearspawn", "help"
"clearspawn", "info", "tp", "copy", "help"
);
private static final List<String> BOOLEAN_VALUES = List.of("true", "false");
@ -67,11 +67,13 @@ public class LoMinesTabCompleter implements TabCompleter {
private List<String> completeSecondArg(CommandSender sender, String subcommand, String partial) {
return switch (subcommand) {
case "create" -> completeCreate(partial);
case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn" ->
case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info" ->
filterStartsWith(getMineNames(), partial);
case "reset" -> completeReset(partial);
case "stats" -> completeStats(sender, partial);
case "top" -> completeTopFirstArg(partial);
case "tp" -> completeTeleport(sender, partial);
case "copy" -> completeCopyFirstArg(partial);
default -> new ArrayList<>();
};
}
@ -80,6 +82,7 @@ public class LoMinesTabCompleter implements TabCompleter {
return switch (subcommand) {
case "reset" -> filterStartsWith(BOOLEAN_VALUES, partial);
case "top" -> completeTopSecondArg(arg2, partial);
case "copy" -> filterStartsWith(getMineNames(), partial);
default -> new ArrayList<>();
};
}
@ -132,6 +135,17 @@ public class LoMinesTabCompleter implements TabCompleter {
return new ArrayList<>();
}
private List<String> completeTeleport(CommandSender sender, String partial) {
if (!sender.hasPermission("lomines.teleport")) {
return new ArrayList<>();
}
return filterStartsWith(getMineNames(), partial);
}
private List<String> completeCopyFirstArg(String partial) {
return filterStartsWith(getMineNames(), partial);
}
private boolean isNumericPartial(String partial) {
return partial.isEmpty() || partial.matches("\\d*");
}
@ -156,13 +170,15 @@ public class LoMinesTabCompleter implements TabCompleter {
private boolean hasPermission(CommandSender sender, String cmd) {
return switch (cmd) {
case "create", "delete", "reload", "list", "maskscan", "edit" ->
case "create", "delete", "reload", "list", "maskscan", "edit", "info" ->
sender.hasPermission("lomines.admin");
case "reset" -> sender.hasPermission("lomines.admin.reset");
case "setteleport" -> sender.hasPermission("lomines.admin.setteleport");
case "setspawn", "clearspawn" -> sender.hasPermission("lomines.admin.setspawn");
case "wand", "group" -> sender.hasPermission("lomines.admin.wand");
case "stats", "top" -> sender.hasPermission("lomines.stats");
case "tp" -> sender.hasPermission("lomines.teleport");
case "copy" -> sender.hasPermission("lomines.admin.copy");
case "help" -> sender.hasPermission("lomines.use");
default -> true;
};

View file

@ -0,0 +1,64 @@
package dev.loki.lomines.command.player;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.util.location.Cuboid;
import dev.lolilb.commands.annotation.Arg;
import dev.lolilb.commands.annotation.Subcommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* Command to teleport to a mine.
*/
public class TeleportCommand {
private final LoMinesPlugin plugin;
public TeleportCommand(LoMinesPlugin plugin) {
this.plugin = plugin;
}
/**
* Teleports player to the mine's location.
* Usage: /lm tp <mine>
*/
@Subcommand(value = "tp", permission = "lomines.teleport")
void teleport(CommandSender sender, @Arg("mine") String mineName) {
if (!(sender instanceof Player player)) {
sender.sendMessage(Component.text("Эта команда только для игроков!", NamedTextColor.RED));
return;
}
Mine mine = plugin.getMines().find(mineName).orElse(null);
if (mine == null) {
player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
return;
}
var config = mine.getConfig();
Location target;
// Try teleport location first
if (config.teleport().enabled() && config.teleport().getLocation().isPresent()) {
target = config.teleport().getLocation().get();
} else if (!mine.getRegions().isEmpty()) {
// Use center of first region
Cuboid region = mine.getRegions().get(0);
double centerX = (region.getMinX() + region.getMaxX()) / 2.0 + 0.5;
double centerZ = (region.getMinZ() + region.getMaxZ()) / 2.0 + 0.5;
double centerY = region.getMaxY() + 1;
target = new Location(region.getWorld(), centerX, centerY, centerZ);
} else {
player.sendMessage(Component.text("У шахты нет настроенной точки телепорта!", NamedTextColor.RED));
return;
}
player.teleport(target);
player.sendMessage(Component.text("§aТелепортировано к шахте §f" + mine.getName() +
" §a(§f" + String.format("%.0f, %.0f, %.0f", target.getX(), target.getY(), target.getZ()) + "§a)"));
}
}

View file

@ -446,9 +446,9 @@ dev.loki.lomines/
- [x] PlaceholderAPI интеграция ✅
### 🟢 Низкий
- [ ] Добавить `/lm info <mine>` — детальная информация
- [ ] Добавить `/lm tp <mine>` — телепортация
- [ ] Добавить `/lm copy <from> <to>` — копирование
- [x] Добавить `/lm info <mine>` — детальная информация ✅
- [x] Добавить `/lm tp <mine>` — телепортация ✅
- [x] Добавить `/lm copy <from> <to>` — копирование ✅
- [x] Разбить большие GUI файлы (>200 строк) ✅
---