feat(chunkloader): add comprehensive configuration for simulation, passive entities, mob cap, and fuel
This commit is contained in:
@@ -36,7 +36,7 @@ final class ChunkLoaders {
|
|||||||
|
|
||||||
private static final String PERM_LIMIT_PREFIX = "canalhandia.chunkloader.limite.";
|
private static final String PERM_LIMIT_PREFIX = "canalhandia.chunkloader.limite.";
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Canalhandia plugin;
|
||||||
private final File file;
|
private final File file;
|
||||||
private final List<ChunkLoader> loaders = new ArrayList<>();
|
private final List<ChunkLoader> loaders = new ArrayList<>();
|
||||||
private final AtomicLong nextId = new AtomicLong(1);
|
private final AtomicLong nextId = new AtomicLong(1);
|
||||||
@@ -48,7 +48,7 @@ final class ChunkLoaders {
|
|||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
|
||||||
ChunkLoaders(Plugin plugin, File file) {
|
ChunkLoaders(Canalhandia plugin, File file) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
load();
|
load();
|
||||||
@@ -97,11 +97,21 @@ 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, "", 0L);
|
return add(ownerUuid, ownerName, world, x, y, z, "", defaultInitialExpiration());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name) {
|
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name) {
|
||||||
return add(ownerUuid, ownerName, world, x, y, z, name, 0L);
|
return add(ownerUuid, ownerName, world, x, y, z, name, defaultInitialExpiration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long defaultInitialExpiration() {
|
||||||
|
if (plugin != null && plugin.settings() != null && plugin.settings().chunkLoaderRequiresTime()) {
|
||||||
|
double hours = plugin.settings().chunkLoaderInitialHours();
|
||||||
|
if (hours > 0) {
|
||||||
|
return System.currentTimeMillis() + (long) (hours * 3600_000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name, long expiresAt) {
|
ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name, long expiresAt) {
|
||||||
@@ -417,28 +427,34 @@ final class ChunkLoaders {
|
|||||||
}
|
}
|
||||||
Chunk chunk = w.getChunkAt(loader.chunkX(), loader.chunkZ());
|
Chunk chunk = w.getChunkAt(loader.chunkX(), loader.chunkZ());
|
||||||
|
|
||||||
// 1. Keep mob spawners (dungeon / blaze / skeleton cages) active
|
boolean spawnersActive = plugin.settings() == null || plugin.settings().chunkLoaderSpawnersActive();
|
||||||
for (BlockState state : chunk.getTileEntities()) {
|
boolean monsterSpawning = plugin.settings() == null || plugin.settings().chunkLoaderMonsterSpawning();
|
||||||
if (state instanceof CreatureSpawner spawner) {
|
int monsterCap = plugin.settings() != null ? plugin.settings().chunkLoaderMonsterCap() : 20;
|
||||||
if (spawner.getRequiredPlayerRange() < 1024) {
|
|
||||||
spawner.setRequiredPlayerRange(2048);
|
// 1. Keep mob spawners (dungeon / blaze / skeleton cages) active if enabled
|
||||||
spawner.update(true, false);
|
if (spawnersActive) {
|
||||||
|
for (BlockState state : chunk.getTileEntities()) {
|
||||||
|
if (state instanceof CreatureSpawner spawner) {
|
||||||
|
if (spawner.getRequiredPlayerRange() < 1024) {
|
||||||
|
spawner.setRequiredPlayerRange(2048);
|
||||||
|
spawner.update(true, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Simulate natural mob spawning (dark platforms / slime / nether farms)
|
// 2. Simulate natural mob spawning (dark platforms / slime / nether farms) if enabled
|
||||||
long mobCount = 0;
|
if (monsterSpawning) {
|
||||||
for (Entity entity : chunk.getEntities()) {
|
long mobCount = 0;
|
||||||
if (entity instanceof Mob) {
|
for (Entity entity : chunk.getEntities()) {
|
||||||
mobCount++;
|
if (entity instanceof Mob) {
|
||||||
|
mobCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mobCount < monsterCap) {
|
||||||
|
simulateNaturalSpawning(w, loader, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mobCount >= 20) {
|
|
||||||
continue; // Respect chunk mob cap
|
|
||||||
}
|
|
||||||
|
|
||||||
simulateNaturalSpawning(w, loader, chunk);
|
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -597,6 +597,54 @@ final class Settings {
|
|||||||
set("chunkloader.bluemap", enabled);
|
set("chunkloader.bluemap", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean chunkLoaderSpawnersActive() {
|
||||||
|
return plugin.getConfig().getBoolean("chunkloader.simulacao.gaiolas-ativas", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderSpawnersActive(boolean active) {
|
||||||
|
set("chunkloader.simulacao.gaiolas-ativas", active);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean chunkLoaderMonsterSpawning() {
|
||||||
|
return plugin.getConfig().getBoolean("chunkloader.simulacao.spawn-monstros", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderMonsterSpawning(boolean spawning) {
|
||||||
|
set("chunkloader.simulacao.spawn-monstros", spawning);
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunkLoaderMonsterCap() {
|
||||||
|
return Math.max(1, plugin.getConfig().getInt("chunkloader.simulacao.limite-monstros-chunk", 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderMonsterCap(int cap) {
|
||||||
|
set("chunkloader.simulacao.limite-monstros-chunk", Math.max(1, cap));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean chunkLoaderPassiveEntities() {
|
||||||
|
return plugin.getConfig().getBoolean("chunkloader.simulacao.tick-entidades-passivas", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderPassiveEntities(boolean active) {
|
||||||
|
set("chunkloader.simulacao.tick-entidades-passivas", active);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean chunkLoaderRequiresTime() {
|
||||||
|
return plugin.getConfig().getBoolean("chunkloader.combustivel.requer-tempo", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderRequiresTime(boolean required) {
|
||||||
|
set("chunkloader.combustivel.requer-tempo", required);
|
||||||
|
}
|
||||||
|
|
||||||
|
double chunkLoaderInitialHours() {
|
||||||
|
return Math.max(0.0, plugin.getConfig().getDouble("chunkloader.combustivel.tempo-inicial-horas", 24.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void chunkLoaderInitialHours(double hours) {
|
||||||
|
set("chunkloader.combustivel.tempo-inicial-horas", Math.max(0.0, hours));
|
||||||
|
}
|
||||||
|
|
||||||
// --- salvavoid ----------------------------------------------------------
|
// --- salvavoid ----------------------------------------------------------
|
||||||
|
|
||||||
int voidProtectionRadius() {
|
int voidProtectionRadius() {
|
||||||
|
|||||||
@@ -344,3 +344,43 @@ mortes:
|
|||||||
- "BONE | Osso da Sorte | Um ossinho pra você, campeão."
|
- "BONE | Osso da Sorte | Um ossinho pra você, campeão."
|
||||||
- "COOKIE | Cookie de Consolação | Cookie de consolação. Vai que melhora."
|
- "COOKIE | Cookie de Consolação | Cookie de consolação. Vai que melhora."
|
||||||
- "ROTTEN_FLESH | Carne Podre | É o que tinha sobrado na despensa."
|
- "ROTTEN_FLESH | Carne Podre | É o que tinha sobrado na despensa."
|
||||||
|
|
||||||
|
# --- Âncoras de Chunk (Chunk Loaders) ----------------------------------------
|
||||||
|
chunkloader:
|
||||||
|
# Limite máximo de âncoras ativas por jogador por padrão.
|
||||||
|
# Pode ser alterado por jogador com /chunkloader limite <jogador> <qtd>.
|
||||||
|
limite-padrao: 1
|
||||||
|
|
||||||
|
# Exibe os marcadores de âncoras ativas no BlueMap.
|
||||||
|
bluemap: true
|
||||||
|
|
||||||
|
# Configurações de simulação de entidades na chunk carregada:
|
||||||
|
simulacao:
|
||||||
|
# Se true, mantém ativas gaiolas de monstros (spawners de blaze, esqueletos, etc.)
|
||||||
|
# mesmo sem jogadores por perto.
|
||||||
|
gaiolas-ativas: true
|
||||||
|
|
||||||
|
# Se true, simula o surgimento natural de monstros em plataformas escuras,
|
||||||
|
# slime chunks e fortalezas do nether. Se false, apenas entidades existentes funcionam.
|
||||||
|
spawn-monstros: true
|
||||||
|
|
||||||
|
# Teto máximo de monstros na chunk antes de pausar o spawn natural (evita acúmulo de lag).
|
||||||
|
limite-monstros-chunk: 20
|
||||||
|
|
||||||
|
# Se true, mantém animais, aldeões, ferro-golems e colheitas sendo processados na chunk.
|
||||||
|
tick-entidades-passivas: true
|
||||||
|
|
||||||
|
# Sistema de combustível e tempo de expiração:
|
||||||
|
combustivel:
|
||||||
|
# Se true, âncoras requerem abastecimento com itens e têm tempo de expiração.
|
||||||
|
# Se false, todas as âncoras colocadas são permanentes por padrão.
|
||||||
|
requer-tempo: true
|
||||||
|
|
||||||
|
# Tempo inicial padrão dado ao criar e colocar uma nova âncora (em horas).
|
||||||
|
tempo-inicial-horas: 24
|
||||||
|
|
||||||
|
# --- Salva-Void (Resgate de Morte no Vazio) -----------------------------------
|
||||||
|
salvavoid:
|
||||||
|
# Raio máximo de busca em blocos a partir de onde o jogador caiu para achar
|
||||||
|
# terreno seguro e colocar o baú com os itens resgatados.
|
||||||
|
raio-busca: 32
|
||||||
|
|||||||
@@ -203,6 +203,15 @@ class ChunkLoaderTest {
|
|||||||
assertNull(ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 5, 10));
|
assertNull(ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 5, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsLoaderWithInitialExpiration() {
|
||||||
|
long targetExpiry = System.currentTimeMillis() + 86400_000L;
|
||||||
|
ChunkLoader loader = loaders.add("uuid-1", "Marcos", "world", 100, 64, 200, "Base", targetExpiry);
|
||||||
|
assertEquals(targetExpiry, loader.expiresAt());
|
||||||
|
assertFalse(loader.isExpired());
|
||||||
|
assertTrue(loader.timeLeft().contains("h"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void simulationSafelyNoopsWithNullPlugin() {
|
void simulationSafelyNoopsWithNullPlugin() {
|
||||||
loaders.startSimulation();
|
loaders.startSimulation();
|
||||||
|
|||||||
Reference in New Issue
Block a user