feat(chunkloader): support custom names, anvil placement, and pause/resume
This commit is contained in:
@@ -127,9 +127,13 @@ final class BlueMapBridge {
|
|||||||
if (!mapMatchesWorld(map, loader.world())) {
|
if (!mapMatchesWorld(map, loader.world())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
String statusHtml = loader.enabled()
|
||||||
|
? "<span style='color:#55ff55'>Ativo</span>"
|
||||||
|
: "<span style='color:#ff5555'>Pausado</span>";
|
||||||
var marker = de.bluecolored.bluemap.api.markers.POIMarker.builder()
|
var marker = de.bluecolored.bluemap.api.markers.POIMarker.builder()
|
||||||
.label("Âncora #" + loader.id() + " (" + loader.ownerName() + ")")
|
.label(loader.simpleName() + " (" + loader.ownerName() + ")" + (loader.enabled() ? "" : " [Pausado]"))
|
||||||
.detail("<b>Âncora de Chunk #" + loader.id() + "</b><br>Dono: "
|
.detail("<b>" + escape(loader.displayName()) + "</b><br>Status: "
|
||||||
|
+ statusHtml + "<br>Dono: "
|
||||||
+ escape(loader.ownerName()) + "<br>Chunk: " + loader.chunkCoords())
|
+ escape(loader.ownerName()) + "<br>Chunk: " + loader.chunkCoords())
|
||||||
.position(loader.x(), loader.y(), loader.z())
|
.position(loader.x(), loader.y(), loader.z())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -1823,6 +1823,9 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "remover", "remove", "del" -> chunkloaderRemove(sender, rest);
|
case "remover", "remove", "del" -> chunkloaderRemove(sender, rest);
|
||||||
case "receita", "craft" -> chunkloaderRecipe(sender);
|
case "receita", "craft" -> chunkloaderRecipe(sender);
|
||||||
case "limite", "limit" -> chunkloaderLimit(sender, rest);
|
case "limite", "limit" -> chunkloaderLimit(sender, rest);
|
||||||
|
case "nome", "renomear", "rename" -> chunkloaderRename(sender, rest);
|
||||||
|
case "ativar", "enable" -> chunkloaderSetEnabled(sender, rest, true);
|
||||||
|
case "desativar", "pausar", "disable", "pause" -> chunkloaderSetEnabled(sender, rest, false);
|
||||||
case "admin" -> chunkloaderAdmin(sender, rest);
|
case "admin" -> chunkloaderAdmin(sender, rest);
|
||||||
case "dar", "give" -> {
|
case "dar", "give" -> {
|
||||||
if (admin(sender)) {
|
if (admin(sender)) {
|
||||||
@@ -1855,13 +1858,30 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
boolean bedrock = isBedrock(sender);
|
boolean bedrock = isBedrock(sender);
|
||||||
for (ChunkLoader loader : mine) {
|
for (ChunkLoader loader : mine) {
|
||||||
Component line = Component.text(" #" + loader.id() + " ", NamedTextColor.AQUA)
|
String statusText = loader.enabled() ? "[Ativa]" : "[Pausada]";
|
||||||
.append(Component.text(loader.place() + " " + loader.chunkCoords(), NamedTextColor.WHITE));
|
NamedTextColor statusColor = loader.enabled() ? NamedTextColor.GREEN : NamedTextColor.RED;
|
||||||
|
|
||||||
|
Component line = Component.text(" " + loader.displayName() + " ", NamedTextColor.AQUA)
|
||||||
|
.append(Component.text(loader.place() + " " + loader.chunkCoords() + " ", NamedTextColor.WHITE))
|
||||||
|
.append(Component.text(statusText + " ", statusColor));
|
||||||
|
|
||||||
if (!bedrock) {
|
if (!bedrock) {
|
||||||
|
if (loader.enabled()) {
|
||||||
|
line = line.append(Component.text("[Pausar] ", NamedTextColor.YELLOW)
|
||||||
|
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(
|
||||||
|
Component.text("Clique para pausar " + loader.displayName(), NamedTextColor.GRAY)))
|
||||||
|
.clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand(
|
||||||
|
"/chunkloader desativar " + loader.id())));
|
||||||
|
} else {
|
||||||
|
line = line.append(Component.text("[Ativar] ", NamedTextColor.GREEN)
|
||||||
|
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(
|
||||||
|
Component.text("Clique para reativar " + loader.displayName(), NamedTextColor.GRAY)))
|
||||||
|
.clickEvent(net.kyori.adventure.text.event.ClickEvent.runCommand(
|
||||||
|
"/chunkloader ativar " + loader.id())));
|
||||||
|
}
|
||||||
line = line.append(Component.text("[Remover]", NamedTextColor.RED)
|
line = line.append(Component.text("[Remover]", NamedTextColor.RED)
|
||||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(
|
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(
|
||||||
Component.text("Clique para desativar a âncora #" + loader.id(), NamedTextColor.GRAY)))
|
Component.text("Clique para recolher " + loader.displayName(), NamedTextColor.GRAY)))
|
||||||
.clickEvent(net.kyori.adventure.text.event.ClickEvent.suggestCommand(
|
.clickEvent(net.kyori.adventure.text.event.ClickEvent.suggestCommand(
|
||||||
"/chunkloader remover " + loader.id())));
|
"/chunkloader remover " + loader.id())));
|
||||||
}
|
}
|
||||||
@@ -1891,9 +1911,11 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
.append(Component.text(" em " + world, NamedTextColor.WHITE)));
|
.append(Component.text(" em " + world, NamedTextColor.WHITE)));
|
||||||
|
|
||||||
if (here != null) {
|
if (here != null) {
|
||||||
|
String status = here.enabled() ? "Ativa" : "Pausada";
|
||||||
|
NamedTextColor statusCol = here.enabled() ? NamedTextColor.GREEN : NamedTextColor.RED;
|
||||||
sender.sendMessage(Component.text(" Status: ", NamedTextColor.GRAY)
|
sender.sendMessage(Component.text(" Status: ", NamedTextColor.GRAY)
|
||||||
.append(Component.text("Carregada continuamente por Âncora #" + here.id(), NamedTextColor.GREEN))
|
.append(Component.text(here.displayName() + " (" + status + ")", statusCol))
|
||||||
.append(Component.text(" (" + here.ownerName() + ")", NamedTextColor.WHITE)));
|
.append(Component.text(" de " + here.ownerName(), NamedTextColor.WHITE)));
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text(" Status: ", NamedTextColor.GRAY)
|
sender.sendMessage(Component.text(" Status: ", NamedTextColor.GRAY)
|
||||||
.append(Component.text("Carregamento normal (sem âncora)", NamedTextColor.DARK_GRAY)));
|
.append(Component.text("Carregamento normal (sem âncora)", NamedTextColor.DARK_GRAY)));
|
||||||
@@ -1903,24 +1925,70 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
.append(Component.text(used + "/" + limitStr, NamedTextColor.YELLOW)));
|
.append(Component.text(used + "/" + limitStr, NamedTextColor.YELLOW)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void chunkloaderRename(CommandSender sender, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
Msg.error(sender, "Uso: /chunkloader nome <id|nome_antigo> <novo_nome>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChunkLoader loader = plugin.chunkLoaders().find(args[0]);
|
||||||
|
if (loader == null) {
|
||||||
|
Msg.error(sender, "Nenhuma âncora encontrada para '" + args[0] + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean isOwner = sender instanceof Player player && player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
|
||||||
|
boolean isAdmin = sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin");
|
||||||
|
if (!isOwner && !isAdmin) {
|
||||||
|
Msg.error(sender, "Esta âncora pertence a " + loader.ownerName() + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String newName = String.join(" ", Arrays.copyOfRange(args, 1, args.length)).trim();
|
||||||
|
if (newName.length() > 32) {
|
||||||
|
Msg.error(sender, "Nome muito longo (máximo 32 caracteres).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.chunkLoaders().rename(loader.id(), newName);
|
||||||
|
plugin.blueMap().syncChunkLoaders();
|
||||||
|
Msg.ok(sender, "Âncora #" + loader.id() + " renomeada para '" + newName + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chunkloaderSetEnabled(CommandSender sender, String[] args, boolean enabled) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
Msg.error(sender, "Uso: /chunkloader " + (enabled ? "ativar" : "desativar") + " <id|nome>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChunkLoader loader = plugin.chunkLoaders().find(args[0]);
|
||||||
|
if (loader == null) {
|
||||||
|
Msg.error(sender, "Nenhuma âncora encontrada para '" + args[0] + "'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean isOwner = sender instanceof Player player && player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
|
||||||
|
boolean isAdmin = sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin");
|
||||||
|
if (!isOwner && !isAdmin) {
|
||||||
|
Msg.error(sender, "Esta âncora pertence a " + loader.ownerName() + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loader.enabled() == enabled) {
|
||||||
|
Msg.error(sender, "A âncora " + loader.displayName() + " já está " + (enabled ? "ativa" : "pausada") + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.chunkLoaders().setEnabled(loader.id(), enabled);
|
||||||
|
plugin.blueMap().syncChunkLoaders();
|
||||||
|
Msg.ok(sender, "Âncora " + loader.displayName() + " " + (enabled ? "ATIVADA." : "PAUSADA/DESATIVADA."));
|
||||||
|
}
|
||||||
|
|
||||||
private void chunkloaderRemove(CommandSender sender, String[] args) {
|
private void chunkloaderRemove(CommandSender sender, String[] args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
Msg.error(sender, "Uso: /chunkloader remover <id>");
|
Msg.error(sender, "Uso: /chunkloader remover <id|nome>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long id = parseLong(args[0]);
|
ChunkLoader loader = plugin.chunkLoaders().find(args[0]);
|
||||||
if (id <= 0) {
|
|
||||||
Msg.error(sender, "ID inválido.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkLoader loader = plugin.chunkLoaders().byId(id);
|
|
||||||
if (loader == null) {
|
if (loader == null) {
|
||||||
Msg.error(sender, "Nenhuma Âncora de Chunk encontrada com o ID #" + id);
|
Msg.error(sender, "Nenhuma Âncora de Chunk encontrada para '" + args[0] + "'.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long id = loader.id();
|
||||||
boolean isOwner = sender instanceof Player player && player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
|
boolean isOwner = sender instanceof Player player && player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
|
||||||
boolean isAdmin = sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin");
|
boolean isAdmin = sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin");
|
||||||
|
|
||||||
@@ -1948,7 +2016,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
for (ItemStack drop : overflow.values()) {
|
for (ItemStack drop : overflow.values()) {
|
||||||
player.getWorld().dropItemNaturally(player.getLocation(), drop);
|
player.getWorld().dropItemNaturally(player.getLocation(), drop);
|
||||||
}
|
}
|
||||||
Msg.ok(sender, "Âncora de Chunk #" + id + " desativada e devolvida ao inventário.");
|
Msg.ok(sender, "Âncora de Chunk " + loader.displayName() + " desativada e devolvida ao inventário.");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
org.bukkit.World w = Bukkit.getWorld(loader.world());
|
org.bukkit.World w = Bukkit.getWorld(loader.world());
|
||||||
@@ -1958,7 +2026,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
Msg.ok(sender, "Âncora de Chunk #" + id + " desativada e dropada em " + loader.place() + ".");
|
Msg.ok(sender, "Âncora de Chunk " + loader.displayName() + " desativada e dropada em " + loader.place() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2108,8 +2176,14 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
sender.sendMessage(Component.text(" /chunkloader info", NamedTextColor.AQUA)
|
sender.sendMessage(Component.text(" /chunkloader info", NamedTextColor.AQUA)
|
||||||
.append(Component.text(" — Informações sobre a chunk onde você está", NamedTextColor.GRAY)));
|
.append(Component.text(" — Informações sobre a chunk onde você está", NamedTextColor.GRAY)));
|
||||||
sender.sendMessage(Component.text(" /chunkloader listar", NamedTextColor.AQUA)
|
sender.sendMessage(Component.text(" /chunkloader listar", NamedTextColor.AQUA)
|
||||||
.append(Component.text(" — Lista suas âncoras ativas e coordenadas", NamedTextColor.GRAY)));
|
.append(Component.text(" — Lista suas âncoras, nomes, status e coordenadas", NamedTextColor.GRAY)));
|
||||||
sender.sendMessage(Component.text(" /chunkloader remover <id>", NamedTextColor.AQUA)
|
sender.sendMessage(Component.text(" /chunkloader nome <id|nome> <novo_nome>", NamedTextColor.AQUA)
|
||||||
|
.append(Component.text(" — Define um nome/rótulo para a âncora", NamedTextColor.GRAY)));
|
||||||
|
sender.sendMessage(Component.text(" /chunkloader ativar <id|nome>", NamedTextColor.AQUA)
|
||||||
|
.append(Component.text(" — Reativa o carregamento contínuo da âncora", NamedTextColor.GRAY)));
|
||||||
|
sender.sendMessage(Component.text(" /chunkloader desativar <id|nome>", NamedTextColor.AQUA)
|
||||||
|
.append(Component.text(" — Pausa o carregamento sem quebrar o bloco", NamedTextColor.GRAY)));
|
||||||
|
sender.sendMessage(Component.text(" /chunkloader remover <id|nome>", NamedTextColor.AQUA)
|
||||||
.append(Component.text(" — Desativa e recolhe uma de suas âncoras", NamedTextColor.GRAY)));
|
.append(Component.text(" — Desativa e recolhe uma de suas âncoras", NamedTextColor.GRAY)));
|
||||||
sender.sendMessage(Component.text(" /chunkloader receita", NamedTextColor.AQUA)
|
sender.sendMessage(Component.text(" /chunkloader receita", NamedTextColor.AQUA)
|
||||||
.append(Component.text(" — Mostra a receita de criação do bloco", NamedTextColor.GRAY)));
|
.append(Component.text(" — Mostra a receita de criação do bloco", NamedTextColor.GRAY)));
|
||||||
@@ -2284,19 +2358,25 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
if (name.equals("chunkloader") || name.equals("ancora") || name.equals("ancoras")) {
|
if (name.equals("chunkloader") || name.equals("ancora") || name.equals("ancoras")) {
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = new ArrayList<>(List.of("info", "listar", "remover", "receita", "limite"));
|
List<String> options = new ArrayList<>(List.of("info", "listar", "nome", "ativar", "desativar", "remover", "receita", "limite"));
|
||||||
if (sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin")) {
|
if (sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin")) {
|
||||||
options.addAll(List.of("admin", "dar"));
|
options.addAll(List.of("admin", "dar"));
|
||||||
}
|
}
|
||||||
return filter(options, args[0]);
|
return filter(options, args[0]);
|
||||||
}
|
}
|
||||||
if (args.length == 2 && (args[0].equalsIgnoreCase("remover") || args[0].equalsIgnoreCase("del"))) {
|
if (args.length == 2 && (args[0].equalsIgnoreCase("remover") || args[0].equalsIgnoreCase("del")
|
||||||
|
|| args[0].equalsIgnoreCase("nome") || args[0].equalsIgnoreCase("renomear")
|
||||||
|
|| args[0].equalsIgnoreCase("ativar") || args[0].equalsIgnoreCase("desativar")
|
||||||
|
|| args[0].equalsIgnoreCase("pausar"))) {
|
||||||
if (sender instanceof Player player) {
|
if (sender instanceof Player player) {
|
||||||
List<String> ids = new ArrayList<>();
|
List<String> identifiers = new ArrayList<>();
|
||||||
for (ChunkLoader loader : plugin.chunkLoaders().byOwner(player.getUniqueId().toString())) {
|
for (ChunkLoader loader : plugin.chunkLoaders().byOwner(player.getUniqueId().toString())) {
|
||||||
ids.add(String.valueOf(loader.id()));
|
identifiers.add(String.valueOf(loader.id()));
|
||||||
|
if (loader.name() != null && !loader.name().isBlank()) {
|
||||||
|
identifiers.add(loader.name());
|
||||||
}
|
}
|
||||||
return filter(ids, args[1]);
|
}
|
||||||
|
return filter(identifiers, args[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (args.length == 2 && args[0].equalsIgnoreCase("limite")) {
|
if (args.length == 2 && args[0].equalsIgnoreCase("limite")) {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ package dev.marcospaulo.canalhandia;
|
|||||||
*/
|
*/
|
||||||
record ChunkLoader(
|
record ChunkLoader(
|
||||||
long id,
|
long id,
|
||||||
|
String name,
|
||||||
|
boolean enabled,
|
||||||
String ownerUuid,
|
String ownerUuid,
|
||||||
String ownerName,
|
String ownerName,
|
||||||
String world,
|
String world,
|
||||||
@@ -18,6 +20,10 @@ record ChunkLoader(
|
|||||||
int chunkZ,
|
int chunkZ,
|
||||||
long createdAt) {
|
long createdAt) {
|
||||||
|
|
||||||
|
ChunkLoader(long id, String ownerUuid, String ownerName, String world, int x, int y, int z, int chunkX, int chunkZ, long createdAt) {
|
||||||
|
this(id, "", true, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
String blockCoords() {
|
String blockCoords() {
|
||||||
return x + ", " + y + ", " + z;
|
return x + ", " + y + ", " + z;
|
||||||
}
|
}
|
||||||
@@ -29,4 +35,20 @@ record ChunkLoader(
|
|||||||
String place() {
|
String place() {
|
||||||
return blockCoords() + (world == null || world.isBlank() ? "" : " (" + world + ")");
|
return blockCoords() + (world == null || world.isBlank() ? "" : " (" + world + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String displayName() {
|
||||||
|
return (name != null && !name.isBlank()) ? name + " (#" + id + ")" : "#" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
String simpleName() {
|
||||||
|
return (name != null && !name.isBlank()) ? name : "#" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkLoader withName(String newName) {
|
||||||
|
return new ChunkLoader(id, newName == null ? "" : newName.trim(), enabled, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkLoader withEnabled(boolean newEnabled) {
|
||||||
|
return new ChunkLoader(id, name, newEnabled, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, createdAt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,13 +78,27 @@ final class ChunkLoaderListener implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String customName = "";
|
||||||
|
var meta = event.getItemInHand().getItemMeta();
|
||||||
|
if (meta != null && meta.hasDisplayName()) {
|
||||||
|
try {
|
||||||
|
String raw = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText()
|
||||||
|
.serialize(meta.displayName()).trim();
|
||||||
|
if (!raw.equalsIgnoreCase("Âncora de Chunk") && !raw.isBlank()) {
|
||||||
|
customName = raw;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChunkLoader loader = plugin.chunkLoaders().add(
|
ChunkLoader loader = plugin.chunkLoaders().add(
|
||||||
player.getUniqueId().toString(),
|
player.getUniqueId().toString(),
|
||||||
player.getName(),
|
player.getName(),
|
||||||
world,
|
world,
|
||||||
loc.getBlockX(),
|
loc.getBlockX(),
|
||||||
loc.getBlockY(),
|
loc.getBlockY(),
|
||||||
loc.getBlockZ()
|
loc.getBlockZ(),
|
||||||
|
customName
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -96,7 +110,7 @@ final class ChunkLoaderListener implements Listener {
|
|||||||
plugin.blueMap().syncChunkLoaders();
|
plugin.blueMap().syncChunkLoaders();
|
||||||
|
|
||||||
String limitStr = limit == Integer.MAX_VALUE ? "ilimitado" : String.valueOf(limit);
|
String limitStr = limit == Integer.MAX_VALUE ? "ilimitado" : String.valueOf(limit);
|
||||||
Msg.ok(player, "Âncora de Chunk ativada! Esta chunk [" + chunkX + ", " + chunkZ + "] ficará carregada continuamente ("
|
Msg.ok(player, "Âncora de Chunk " + loader.displayName() + " ativada! Esta chunk [" + chunkX + ", " + chunkZ + "] ficará carregada continuamente ("
|
||||||
+ (current + 1) + "/" + limitStr + ").");
|
+ (current + 1) + "/" + limitStr + ").");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,13 +143,13 @@ final class ChunkLoaderListener implements Listener {
|
|||||||
event.setExpToDrop(0);
|
event.setExpToDrop(0);
|
||||||
|
|
||||||
if (player.getGameMode() == GameMode.CREATIVE) {
|
if (player.getGameMode() == GameMode.CREATIVE) {
|
||||||
Msg.ok(player, "Âncora de Chunk #" + loader.id() + " desativada (Modo Criativo).");
|
Msg.ok(player, "Âncora de Chunk " + loader.displayName() + " desativada (Modo Criativo).");
|
||||||
} else {
|
} else {
|
||||||
Map<Integer, ItemStack> overflow = player.getInventory().addItem(ChunkAnchorItem.create(plugin, 1));
|
Map<Integer, ItemStack> overflow = player.getInventory().addItem(ChunkAnchorItem.create(plugin, 1));
|
||||||
for (ItemStack drop : overflow.values()) {
|
for (ItemStack drop : overflow.values()) {
|
||||||
player.getWorld().dropItemNaturally(player.getLocation(), drop);
|
player.getWorld().dropItemNaturally(player.getLocation(), drop);
|
||||||
}
|
}
|
||||||
Msg.ok(player, "Âncora de Chunk #" + loader.id() + " desativada e recolhida para o seu inventário.");
|
Msg.ok(player, "Âncora de Chunk " + loader.displayName() + " desativada e recolhida para o seu inventário.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -169,10 +183,36 @@ final class ChunkLoaderListener implements Listener {
|
|||||||
block.getZ()
|
block.getZ()
|
||||||
);
|
);
|
||||||
if (loader != null) {
|
if (loader != null) {
|
||||||
// Prevent respawn anchor exploding or depleting charges when used as chunk loader
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
Msg.ok(player, "Âncora de Chunk #" + loader.id() + " (" + loader.ownerName() + ") está ativa.");
|
boolean isOwner = player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
|
||||||
|
boolean isAdmin = player.hasPermission("canalhandia.admin") || player.hasPermission("canalhandia.chunkloader.admin");
|
||||||
|
|
||||||
|
if (player.isSneaking() && (isOwner || isAdmin)) {
|
||||||
|
boolean newState = !loader.enabled();
|
||||||
|
plugin.chunkLoaders().setEnabled(loader.id(), newState);
|
||||||
|
Location l = block.getLocation().add(0.5, 0.5, 0.5);
|
||||||
|
if (newState) {
|
||||||
|
try {
|
||||||
|
l.getWorld().playSound(l, Sound.BLOCK_BEACON_ACTIVATE, 1.0f, 1.2f);
|
||||||
|
l.getWorld().spawnParticle(Particle.PORTAL, l, 25, 0.2, 0.2, 0.2, 0.05);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
Msg.ok(player, "Âncora " + loader.displayName() + " ATIVADA.");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
l.getWorld().playSound(l, Sound.BLOCK_BEACON_DEACTIVATE, 1.0f, 0.8f);
|
||||||
|
l.getWorld().spawnParticle(Particle.SMOKE, l, 20, 0.2, 0.2, 0.2, 0.02);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
Msg.ok(player, "Âncora " + loader.displayName() + " PAUSADA/DESATIVADA.");
|
||||||
|
}
|
||||||
|
plugin.blueMap().syncChunkLoaders();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String stateStr = loader.enabled() ? "ATIVA" : "PAUSADA";
|
||||||
|
Msg.ok(player, "Âncora " + loader.displayName() + " (" + loader.ownerName() + ") está " + stateStr + ". (Shift + Clique Direito para alternar)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ final class ChunkLoaders {
|
|||||||
for (String key : root.getKeys(false)) {
|
for (String key : root.getKeys(false)) {
|
||||||
try {
|
try {
|
||||||
long id = Long.parseLong(key);
|
long id = Long.parseLong(key);
|
||||||
|
String name = root.getString(key + ".name", "");
|
||||||
|
boolean enabled = root.getBoolean(key + ".enabled", true);
|
||||||
String ownerUuid = root.getString(key + ".owner_uuid", "");
|
String ownerUuid = root.getString(key + ".owner_uuid", "");
|
||||||
String ownerName = root.getString(key + ".owner_name", "?");
|
String ownerName = root.getString(key + ".owner_name", "?");
|
||||||
String world = root.getString(key + ".world", "world");
|
String world = root.getString(key + ".world", "world");
|
||||||
@@ -79,7 +81,7 @@ final class ChunkLoaders {
|
|||||||
int chunkZ = root.getInt(key + ".chunk_z", z >> 4);
|
int chunkZ = root.getInt(key + ".chunk_z", z >> 4);
|
||||||
long createdAt = root.getLong(key + ".created_at", System.currentTimeMillis());
|
long createdAt = root.getLong(key + ".created_at", System.currentTimeMillis());
|
||||||
|
|
||||||
loaders.add(new ChunkLoader(id, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, createdAt));
|
loaders.add(new ChunkLoader(id, name, enabled, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, createdAt));
|
||||||
if (id > maxId) {
|
if (id > maxId) {
|
||||||
maxId = id;
|
maxId = id;
|
||||||
}
|
}
|
||||||
@@ -94,10 +96,14 @@ final class ChunkLoaders {
|
|||||||
* Registers a new chunk loader and activates the ticket in the world.
|
* Registers a new chunk loader and activates the ticket in the world.
|
||||||
*/
|
*/
|
||||||
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z) {
|
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z) {
|
||||||
|
return add(ownerUuid, ownerName, world, x, y, z, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name) {
|
||||||
int chunkX = x >> 4;
|
int chunkX = x >> 4;
|
||||||
int chunkZ = z >> 4;
|
int chunkZ = z >> 4;
|
||||||
long id = nextId.getAndIncrement();
|
long id = nextId.getAndIncrement();
|
||||||
ChunkLoader loader = new ChunkLoader(id, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, System.currentTimeMillis());
|
ChunkLoader loader = new ChunkLoader(id, name == null ? "" : name.trim(), true, ownerUuid, ownerName, world, x, y, z, chunkX, chunkZ, System.currentTimeMillis());
|
||||||
|
|
||||||
synchronized (loaders) {
|
synchronized (loaders) {
|
||||||
loaders.add(loader);
|
loaders.add(loader);
|
||||||
@@ -107,6 +113,79 @@ final class ChunkLoaders {
|
|||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renames an existing chunk loader.
|
||||||
|
*/
|
||||||
|
boolean rename(long id, String newName) {
|
||||||
|
synchronized (loaders) {
|
||||||
|
for (int i = 0; i < loaders.size(); i++) {
|
||||||
|
ChunkLoader curr = loaders.get(i);
|
||||||
|
if (curr.id() == id) {
|
||||||
|
loaders.set(i, curr.withName(newName));
|
||||||
|
save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables (pauses) a chunk loader without destroying the block.
|
||||||
|
*/
|
||||||
|
boolean setEnabled(long id, boolean enabled) {
|
||||||
|
ChunkLoader oldLoader = null;
|
||||||
|
ChunkLoader updated = null;
|
||||||
|
synchronized (loaders) {
|
||||||
|
for (int i = 0; i < loaders.size(); i++) {
|
||||||
|
ChunkLoader curr = loaders.get(i);
|
||||||
|
if (curr.id() == id) {
|
||||||
|
oldLoader = curr;
|
||||||
|
updated = curr.withEnabled(enabled);
|
||||||
|
loaders.set(i, updated);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldLoader != null && updated != null) {
|
||||||
|
if (enabled) {
|
||||||
|
addTicket(updated);
|
||||||
|
} else {
|
||||||
|
removeTicket(oldLoader);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a chunk loader by its numeric ID (with or without '#') or custom name.
|
||||||
|
*/
|
||||||
|
ChunkLoader find(String query) {
|
||||||
|
if (query == null || query.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = query.trim();
|
||||||
|
try {
|
||||||
|
long id = Long.parseLong(trimmed.startsWith("#") ? trimmed.substring(1) : trimmed);
|
||||||
|
ChunkLoader loader = byId(id);
|
||||||
|
if (loader != null) {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (loaders) {
|
||||||
|
for (ChunkLoader loader : loaders) {
|
||||||
|
if (loader.name() != null && loader.name().equalsIgnoreCase(trimmed)) {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a chunk loader by its unique ID and releases the chunk ticket.
|
* Removes a chunk loader by its unique ID and releases the chunk ticket.
|
||||||
*/
|
*/
|
||||||
@@ -219,10 +298,12 @@ final class ChunkLoaders {
|
|||||||
void loadAllTickets() {
|
void loadAllTickets() {
|
||||||
synchronized (loaders) {
|
synchronized (loaders) {
|
||||||
for (ChunkLoader loader : loaders) {
|
for (ChunkLoader loader : loaders) {
|
||||||
|
if (loader.enabled()) {
|
||||||
addTicket(loader);
|
addTicket(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void unloadAllTickets() {
|
void unloadAllTickets() {
|
||||||
synchronized (loaders) {
|
synchronized (loaders) {
|
||||||
@@ -256,6 +337,9 @@ final class ChunkLoaders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (ChunkLoader loader : currentLoaders) {
|
for (ChunkLoader loader : currentLoaders) {
|
||||||
|
if (!loader.enabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
World w = Bukkit.getWorld(loader.world());
|
World w = Bukkit.getWorld(loader.world());
|
||||||
if (w == null || !w.isChunkLoaded(loader.chunkX(), loader.chunkZ())) {
|
if (w == null || !w.isChunkLoaded(loader.chunkX(), loader.chunkZ())) {
|
||||||
@@ -394,6 +478,8 @@ final class ChunkLoaders {
|
|||||||
synchronized (loaders) {
|
synchronized (loaders) {
|
||||||
for (ChunkLoader l : loaders) {
|
for (ChunkLoader l : loaders) {
|
||||||
String key = "loaders." + l.id();
|
String key = "loaders." + l.id();
|
||||||
|
yaml.set(key + ".name", l.name());
|
||||||
|
yaml.set(key + ".enabled", l.enabled());
|
||||||
yaml.set(key + ".owner_uuid", l.ownerUuid());
|
yaml.set(key + ".owner_uuid", l.ownerUuid());
|
||||||
yaml.set(key + ".owner_name", l.ownerName());
|
yaml.set(key + ".owner_name", l.ownerName());
|
||||||
yaml.set(key + ".world", l.world());
|
yaml.set(key + ".world", l.world());
|
||||||
|
|||||||
@@ -105,6 +105,36 @@ class ChunkLoaderTest {
|
|||||||
assertEquals("uuid-ana", l2.ownerUuid());
|
assertEquals("uuid-ana", l2.ownerUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void renamesAndTogglesEnabledState() {
|
||||||
|
ChunkLoader loader = loaders.add("uuid-marcos", "Marcos", "world", 100, 64, 200);
|
||||||
|
assertEquals("#1", loader.simpleName());
|
||||||
|
assertTrue(loader.enabled());
|
||||||
|
|
||||||
|
assertTrue(loaders.rename(loader.id(), "Farm de Ferro"));
|
||||||
|
ChunkLoader renamed = loaders.byId(loader.id());
|
||||||
|
assertNotNull(renamed);
|
||||||
|
assertEquals("Farm de Ferro", renamed.name());
|
||||||
|
assertEquals("Farm de Ferro (#1)", renamed.displayName());
|
||||||
|
assertEquals("Farm de Ferro", renamed.simpleName());
|
||||||
|
|
||||||
|
assertEquals(renamed, loaders.find("Farm de Ferro"));
|
||||||
|
assertEquals(renamed, loaders.find("#1"));
|
||||||
|
assertEquals(renamed, loaders.find("1"));
|
||||||
|
|
||||||
|
assertTrue(loaders.setEnabled(loader.id(), false));
|
||||||
|
ChunkLoader disabled = loaders.byId(loader.id());
|
||||||
|
assertNotNull(disabled);
|
||||||
|
assertFalse(disabled.enabled());
|
||||||
|
|
||||||
|
loaders.flush();
|
||||||
|
ChunkLoaders reloaded = new ChunkLoaders(null, file);
|
||||||
|
ChunkLoader reloadedLoader = reloaded.byId(loader.id());
|
||||||
|
assertNotNull(reloadedLoader);
|
||||||
|
assertEquals("Farm de Ferro", reloadedLoader.name());
|
||||||
|
assertFalse(reloadedLoader.enabled());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void simulationSafelyNoopsWithNullPlugin() {
|
void simulationSafelyNoopsWithNullPlugin() {
|
||||||
loaders.startSimulation();
|
loaders.startSimulation();
|
||||||
|
|||||||
Reference in New Issue
Block a user