diff --git a/src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java b/src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java
index 02f4961..2d65cc0 100644
--- a/src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java
+++ b/src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java
@@ -127,9 +127,13 @@ final class BlueMapBridge {
if (!mapMatchesWorld(map, loader.world())) {
continue;
}
+ String statusHtml = loader.enabled()
+ ? "Ativo"
+ : "Pausado";
var marker = de.bluecolored.bluemap.api.markers.POIMarker.builder()
- .label("Âncora #" + loader.id() + " (" + loader.ownerName() + ")")
- .detail("Âncora de Chunk #" + loader.id() + "
Dono: "
+ .label(loader.simpleName() + " (" + loader.ownerName() + ")" + (loader.enabled() ? "" : " [Pausado]"))
+ .detail("" + escape(loader.displayName()) + "
Status: "
+ + statusHtml + "
Dono: "
+ escape(loader.ownerName()) + "
Chunk: " + loader.chunkCoords())
.position(loader.x(), loader.y(), loader.z())
.build();
diff --git a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java
index 5e7f3ae..af32693 100644
--- a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java
+++ b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java
@@ -1823,6 +1823,9 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
case "remover", "remove", "del" -> chunkloaderRemove(sender, rest);
case "receita", "craft" -> chunkloaderRecipe(sender);
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 "dar", "give" -> {
if (admin(sender)) {
@@ -1855,13 +1858,30 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
boolean bedrock = isBedrock(sender);
for (ChunkLoader loader : mine) {
- Component line = Component.text(" #" + loader.id() + " ", NamedTextColor.AQUA)
- .append(Component.text(loader.place() + " " + loader.chunkCoords(), NamedTextColor.WHITE));
+ String statusText = loader.enabled() ? "[Ativa]" : "[Pausada]";
+ 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) {
- line = line.append(Component.text(" [Remover]", NamedTextColor.RED)
+ 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)
.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(
"/chunkloader remover " + loader.id())));
}
@@ -1891,9 +1911,11 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
.append(Component.text(" em " + world, NamedTextColor.WHITE)));
if (here != null) {
+ String status = here.enabled() ? "Ativa" : "Pausada";
+ NamedTextColor statusCol = here.enabled() ? NamedTextColor.GREEN : NamedTextColor.RED;
sender.sendMessage(Component.text(" Status: ", NamedTextColor.GRAY)
- .append(Component.text("Carregada continuamente por Âncora #" + here.id(), NamedTextColor.GREEN))
- .append(Component.text(" (" + here.ownerName() + ")", NamedTextColor.WHITE)));
+ .append(Component.text(here.displayName() + " (" + status + ")", statusCol))
+ .append(Component.text(" de " + here.ownerName(), NamedTextColor.WHITE)));
} else {
sender.sendMessage(Component.text(" Status: ", NamedTextColor.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)));
}
+ private void chunkloaderRename(CommandSender sender, String[] args) {
+ if (args.length < 2) {
+ Msg.error(sender, "Uso: /chunkloader 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") + " ");
+ 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) {
if (args.length < 1) {
- Msg.error(sender, "Uso: /chunkloader remover ");
+ Msg.error(sender, "Uso: /chunkloader remover ");
return;
}
- long id = parseLong(args[0]);
- if (id <= 0) {
- Msg.error(sender, "ID inválido.");
- return;
- }
-
- ChunkLoader loader = plugin.chunkLoaders().byId(id);
+ ChunkLoader loader = plugin.chunkLoaders().find(args[0]);
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;
}
+ long id = loader.id();
boolean isOwner = sender instanceof Player player && player.getUniqueId().toString().equalsIgnoreCase(loader.ownerUuid());
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()) {
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 {
try {
org.bukkit.World w = Bukkit.getWorld(loader.world());
@@ -1958,7 +2026,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
}
} 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)
.append(Component.text(" — Informações sobre a chunk onde você está", NamedTextColor.GRAY)));
sender.sendMessage(Component.text(" /chunkloader listar", NamedTextColor.AQUA)
- .append(Component.text(" — Lista suas âncoras ativas e coordenadas", NamedTextColor.GRAY)));
- sender.sendMessage(Component.text(" /chunkloader remover ", NamedTextColor.AQUA)
+ .append(Component.text(" — Lista suas âncoras, nomes, status e coordenadas", NamedTextColor.GRAY)));
+ sender.sendMessage(Component.text(" /chunkloader nome ", NamedTextColor.AQUA)
+ .append(Component.text(" — Define um nome/rótulo para a âncora", NamedTextColor.GRAY)));
+ sender.sendMessage(Component.text(" /chunkloader ativar ", NamedTextColor.AQUA)
+ .append(Component.text(" — Reativa o carregamento contínuo da âncora", NamedTextColor.GRAY)));
+ sender.sendMessage(Component.text(" /chunkloader desativar ", NamedTextColor.AQUA)
+ .append(Component.text(" — Pausa o carregamento sem quebrar o bloco", NamedTextColor.GRAY)));
+ sender.sendMessage(Component.text(" /chunkloader remover ", NamedTextColor.AQUA)
.append(Component.text(" — Desativa e recolhe uma de suas âncoras", NamedTextColor.GRAY)));
sender.sendMessage(Component.text(" /chunkloader receita", NamedTextColor.AQUA)
.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 (args.length == 1) {
- List options = new ArrayList<>(List.of("info", "listar", "remover", "receita", "limite"));
+ List options = new ArrayList<>(List.of("info", "listar", "nome", "ativar", "desativar", "remover", "receita", "limite"));
if (sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin")) {
options.addAll(List.of("admin", "dar"));
}
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) {
- List ids = new ArrayList<>();
+ List identifiers = new ArrayList<>();
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")) {
diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoader.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoader.java
index 463125c..d80ef1f 100644
--- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoader.java
+++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoader.java
@@ -8,6 +8,8 @@ package dev.marcospaulo.canalhandia;
*/
record ChunkLoader(
long id,
+ String name,
+ boolean enabled,
String ownerUuid,
String ownerName,
String world,
@@ -18,6 +20,10 @@ record ChunkLoader(
int chunkZ,
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() {
return x + ", " + y + ", " + z;
}
@@ -29,4 +35,20 @@ record ChunkLoader(
String place() {
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);
+ }
}
diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java
index 60f92f6..a4bc90b 100644
--- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java
+++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java
@@ -78,13 +78,27 @@ final class ChunkLoaderListener implements Listener {
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(
player.getUniqueId().toString(),
player.getName(),
world,
loc.getBlockX(),
loc.getBlockY(),
- loc.getBlockZ()
+ loc.getBlockZ(),
+ customName
);
try {
@@ -96,7 +110,7 @@ final class ChunkLoaderListener implements Listener {
plugin.blueMap().syncChunkLoaders();
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 + ").");
}
@@ -129,13 +143,13 @@ final class ChunkLoaderListener implements Listener {
event.setExpToDrop(0);
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 {
Map overflow = player.getInventory().addItem(ChunkAnchorItem.create(plugin, 1));
for (ItemStack drop : overflow.values()) {
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 {
@@ -169,10 +183,36 @@ final class ChunkLoaderListener implements Listener {
block.getZ()
);
if (loader != null) {
- // Prevent respawn anchor exploding or depleting charges when used as chunk loader
event.setCancelled(true);
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)");
}
}
diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java
index 37bd90e..52f6cb0 100644
--- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java
+++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java
@@ -69,6 +69,8 @@ final class ChunkLoaders {
for (String key : root.getKeys(false)) {
try {
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 ownerName = root.getString(key + ".owner_name", "?");
String world = root.getString(key + ".world", "world");
@@ -79,7 +81,7 @@ final class ChunkLoaders {
int chunkZ = root.getInt(key + ".chunk_z", z >> 4);
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) {
maxId = id;
}
@@ -94,10 +96,14 @@ final class ChunkLoaders {
* 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) {
+ 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 chunkZ = z >> 4;
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) {
loaders.add(loader);
@@ -107,6 +113,79 @@ final class ChunkLoaders {
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.
*/
@@ -219,7 +298,9 @@ final class ChunkLoaders {
void loadAllTickets() {
synchronized (loaders) {
for (ChunkLoader loader : loaders) {
- addTicket(loader);
+ if (loader.enabled()) {
+ addTicket(loader);
+ }
}
}
}
@@ -256,6 +337,9 @@ final class ChunkLoaders {
}
for (ChunkLoader loader : currentLoaders) {
+ if (!loader.enabled()) {
+ continue;
+ }
try {
World w = Bukkit.getWorld(loader.world());
if (w == null || !w.isChunkLoaded(loader.chunkX(), loader.chunkZ())) {
@@ -394,6 +478,8 @@ final class ChunkLoaders {
synchronized (loaders) {
for (ChunkLoader l : loaders) {
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_name", l.ownerName());
yaml.set(key + ".world", l.world());
diff --git a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java
index 4ea4c7e..e5d37ea 100644
--- a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java
+++ b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java
@@ -105,6 +105,36 @@ class ChunkLoaderTest {
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
void simulationSafelyNoopsWithNullPlugin() {
loaders.startSimulation();