diff --git a/src/main/java/org/ultramine/commands/basic/GenWorldCommand.java b/src/main/java/org/ultramine/commands/basic/GenWorldCommand.java new file mode 100644 index 0000000..61b717a --- /dev/null +++ b/src/main/java/org/ultramine/commands/basic/GenWorldCommand.java @@ -0,0 +1,183 @@ +package org.ultramine.commands.basic; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.MathHelper; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.WorldServer; +import org.ultramine.commands.Command; +import org.ultramine.commands.CommandContext; +import org.ultramine.server.WorldConstants; +import org.ultramine.server.WorldsConfig; +import org.ultramine.server.util.SpiralCoordIterator; + +import java.util.ArrayList; +import java.util.List; + +public class GenWorldCommand +{ + private static final List generators = new ArrayList<>(); + + @Command( + name = "genworld", + group = "technical", + permissions = {"command.technical.genworld"}, + syntax = { + "", + "[stop]", + "", + " [stop]", + " " + } + ) + public static void genworld(CommandContext ctx) + { + WorldServer world = ctx.contains("world") ? ctx.get("world").asWorld() : ctx.getSenderAsPlayer().getServerForPlayer(); + + if(ctx.getAction().equals("stop")) + { + stop(ctx, world); + return; + } + + checkNotStarted(ctx, world); + if(!ctx.contains("radius")) + { + if(world.getConfig().borders == null || world.getConfig().borders.length == 0) + ctx.failure("command.genworld.noborder"); + startBordered(ctx, world); + } + else + { + int radius = ctx.get("radius").asInt(1); + int x = ctx.contains("x") ? ctx.get("x").asInt() : MathHelper.floor_double(ctx.getSenderAsPlayer().posX); + int z = ctx.contains("z") ? ctx.get("z").asInt() : MathHelper.floor_double(ctx.getSenderAsPlayer().posZ); + startRadius(ctx, world, x, z, radius); + } + + ctx.sendMessage("command.genworld.start"); + } + + private static void startBordered(CommandContext ctx, WorldServer world) + { + for(WorldsConfig.WorldConfig.Border border : world.getConfig().borders) + generators.add(new WorldGenerator(world.provider.dimensionId, border.x, border.z, border.radius)); + } + + private static void startRadius(CommandContext ctx, WorldServer world, int x, int z, int radius) + { + generators.add(new WorldGenerator(world.provider.dimensionId, x, z, radius)); + } + + private static void checkNotStarted(CommandContext ctx, WorldServer world) + { + int dim = world.provider.dimensionId; + for(WorldGenerator gen : generators) + if(gen.dim == dim) + ctx.failure("command.genworld.already"); + } + + private static void stop(CommandContext ctx, WorldServer world) + { + int genCurrent = 0; + int genTotal = 0; + + int dim = world.provider.dimensionId; + for(WorldGenerator gen : new ArrayList<>(generators)) + { + if(gen.dim == dim) + { + genCurrent += gen.getCurrentGen(); + genTotal += gen.getTotalGen(); + gen.stop(); + } + } + ctx.check(genTotal != 0, "command.genworld.stop.fail.notrun"); + ctx.sendMessage("command.genworld.stop", genCurrent, genTotal); + } + + public static void tick() + { + if(generators.size() != 0) + generators.get(0).tick(); + } + + public static class WorldGenerator + { + private static final int GEN_RADIUS = 4; + private static final int OVERLAP = WorldConstants.GENCHUNK_PRELOAD_RADIUS; + private static final int GEN_SIDE = GEN_RADIUS + GEN_RADIUS + 1; + private static final int BULK_RADIUS = GEN_RADIUS + OVERLAP; + + private final int dim; + private final SpiralCoordIterator iterator; + private boolean canGenerateNow = true; + + public WorldGenerator(int dim, int x, int z, int radius) + { + this.dim = dim; + int radiusGen = (radius >> 4) / GEN_SIDE; + int sideGens = radiusGen + radiusGen + 1; + iterator = new SpiralCoordIterator((x >> 4) / GEN_SIDE, (z >> 4) / GEN_SIDE, sideGens*sideGens); + } + + public int getTotalGen() + { + return iterator.getLimit() * GEN_SIDE * GEN_SIDE; + } + + public int getCurrentGen() + { + return iterator.getCounter() * GEN_SIDE * GEN_SIDE; + } + + public void tick() + { + WorldServer world = MinecraftServer.getServer().getMultiWorld().getWorldByID(dim); + if(world == null) + { + stop(); + return; + } + + if(MinecraftServer.getServer().getTickCounter() % 600 == 0) + MinecraftServer.getServer().getConfigurationManager().sendChatMsg( + new ChatComponentTranslation("command.genworld.process", getCurrentGen(), getTotalGen())); + + while(canGenerateNow && iterator.hasNext() && world.theChunkProviderServer.unloadQueue.size() < 512) + { + ChunkCoordIntPair coord = iterator.next(); + + int cx = coord.chunkXPos * GEN_SIDE; + int cz = coord.chunkZPos * GEN_SIDE; + if(world.getBorder().isChunkInsideBorder(cx, cz)) + { + canGenerateNow = false; + world.theChunkProviderServer.loadAsyncRadiusThenRun(cx, cz, BULK_RADIUS, () -> { + canGenerateNow = true; + world.theChunkProviderServer.loadAsyncRadius(cx, cz, BULK_RADIUS, c -> { + if(c.getBindState().canUnload()) + world.theChunkProviderServer.unloadChunksIfNotNearSpawn(c.xPosition, c.zPosition); + }); + }); + break; + } + } + + if(!iterator.hasNext()) + completed(); + } + + private void completed() + { + MinecraftServer.getServer().getConfigurationManager().sendChatMsg( + new ChatComponentTranslation("command.genworld.complete", getCurrentGen(), getTotalGen())); + stop(); + } + + public void stop() + { + generators.remove(this); + } + } +} diff --git a/src/main/java/org/ultramine/commands/basic/TechCommands.java b/src/main/java/org/ultramine/commands/basic/TechCommands.java index 4cbabea..b612d03 100644 --- a/src/main/java/org/ultramine/commands/basic/TechCommands.java +++ b/src/main/java/org/ultramine/commands/basic/TechCommands.java @@ -507,161 +507,6 @@ ctx.sendMessage("command.chunkgc.success"); } - private static WorldGenerator worldgen; - - @Command( - name = "genworld", - group = "technical", - permissions = {"command.genworld"}, - syntax = { - "", - "[stop]", - "", - "[radius] ", - "[radius] " - } - ) - public static void genworld(CommandContext ctx) - { - if(ctx.getAction().equals("stop")) - { - FMLCommonHandler.instance().bus().unregister(worldgen); - ctx.sendMessage("command.genworld.stop", worldgen.genCurrent, worldgen.genTotal); - worldgen = null; - return; - } - - if(worldgen != null) - ctx.failure("command.genworld.already"); - - WorldServer world = ctx.getSenderAsPlayer().getServerForPlayer(); - int dim = world.provider.dimensionId; - int radius = ctx.contains("radius") ? ctx.get("radius").asInt(1) : -1; - int cpt = ctx.contains("cpt") ? ctx.get("cpt").asInt(1) : 20; - - int x = MathHelper.floor_double(ctx.getSenderAsPlayer().posX); - int z = MathHelper.floor_double(ctx.getSenderAsPlayer().posZ); - - if(radius == -1 && world.getConfig().borders.length == 0) - ctx.failure("command.genworld.noborder"); - - worldgen = radius == -1 ? new WorldGenerator(dim, cpt) : new WorldGenerator(dim, cpt, x, z, radius); - FMLCommonHandler.instance().bus().register(worldgen); - ctx.sendMessage("command.genworld.start"); - } - - public static class WorldGenerator - { - private final int dim; - private final int chunksPerTick; - private final boolean isBorder; - - private int borderInd = 0; - private int minX = Integer.MIN_VALUE; - private int minZ; - private int maxX; - private int maxZ; - - private int x; - private int z; - - private int genCurrent; - private int genTotal; - - public WorldGenerator(int dim, int chunksPerTick) - { - this.isBorder = true; - this.dim = dim; - this.chunksPerTick = chunksPerTick; - } - - public WorldGenerator(int dim, int chunksPerTick, int centX, int centZ, int radius) - { - this.isBorder = false; - this.dim = dim; - this.chunksPerTick = chunksPerTick; - - minX = (centX - radius) >> 4; - minZ = (centZ - radius) >> 4; - maxX = (centX + radius) >> 4; - maxZ = (centZ + radius) >> 4; - - x = minX; - z = minZ; - - genTotal = (Math.abs(maxX - minX) + 8)*(Math.abs(maxZ - minZ) + 8); - } - - @SubscribeEvent - public void onTick(TickEvent.ServerTickEvent e) - { - if(e.phase == TickEvent.Phase.START) - { - if(MinecraftServer.getServer().getTickCounter() % 600 == 0) - MinecraftServer.getServer().getConfigurationManager().sendChatMsg(new ChatComponentTranslation("command.genworld.process", genCurrent, genTotal)); - - if(MinecraftServer.getServer().getTickCounter() % Math.max(1,169/chunksPerTick) != 0) - return; - WorldServer world = MinecraftServer.getServer().getMultiWorld().getWorldByID(dim); - if(world == null) - return; - Border[] borders = world.getConfig().borders; - int counter = 0; - l1: - while(borderInd < (isBorder ? borders.length : 1)) - { - if(minX == Integer.MIN_VALUE) - { - Border border = borders[borderInd]; - - minX = (border.x - border.radius) >> 4; - minZ = (border.z - border.radius) >> 4; - maxX = (border.x + border.radius) >> 4; - maxZ = (border.z + border.radius) >> 4; - - x = minX; - z = minZ; - - genTotal = (Math.abs(maxX - minX) + 8)*(Math.abs(maxZ - minZ) + 8); - } - - while(x <= maxX) - { - while(z <= maxZ) - { - if(world.getBorder().isChunkInsideBorder(x, z)) - { - if(++counter > Math.max(1, chunksPerTick/169)) break l1; - - world.theChunkProviderServer.loadAsyncWithRadius(x, z, 6, IChunkLoadCallback.EMPTY); - } - - z += 8; - } - - x += 8; - if(x <= maxX) z = minZ; - } - - if(x > maxX && z > maxZ) - { - borderInd++; - minX = Integer.MIN_VALUE; - } - } - - genCurrent += (counter-1)*81; - - if(borderInd >= (isBorder ? borders.length : 1)) - { - FMLCommonHandler.instance().bus().unregister(worldgen); - worldgen = null; - MinecraftServer.getServer().getConfigurationManager().sendChatMsg(new ChatComponentTranslation("command.genworld.complete", genCurrent, genTotal)); - } - } - } - } - @Command( name = "chunkdebug", group = "technical", diff --git a/src/main/java/org/ultramine/server/chunk/CallbackMultiChunkDependentTask.java b/src/main/java/org/ultramine/server/chunk/CallbackMultiChunkDependentTask.java index ae14699..218e80e 100644 --- a/src/main/java/org/ultramine/server/chunk/CallbackMultiChunkDependentTask.java +++ b/src/main/java/org/ultramine/server/chunk/CallbackMultiChunkDependentTask.java @@ -16,11 +16,11 @@ @Override public void onChunkLoaded(Chunk chunk) { - chunk.addDependency(this); - if(numChunksForLoad == 1) + // When task executes, this dependency is no longer binds chunks in loaded state + if(--numChunksForLoad == 0) task.run(); - - numChunksForLoad--; + else + chunk.addDependency(this); } @Override diff --git a/src/main/java/org/ultramine/server/internal/UMEventHandler.java b/src/main/java/org/ultramine/server/internal/UMEventHandler.java index a4abfef..0b2c352 100644 --- a/src/main/java/org/ultramine/server/internal/UMEventHandler.java +++ b/src/main/java/org/ultramine/server/internal/UMEventHandler.java @@ -1,6 +1,7 @@ package org.ultramine.server.internal; import net.minecraft.util.DamageSource; +import org.ultramine.commands.basic.GenWorldCommand; import org.ultramine.economy.CurrencyRegistry; import org.ultramine.economy.PlayerHoldingsEvent; import org.ultramine.server.ConfigurationHandler; @@ -132,6 +133,7 @@ MinecraftServer server = MinecraftServer.getServer(); Teleporter.tick(); + GenWorldCommand.tick(); ChunkProfiler.instance().tick(server.getTickCounter()); } } diff --git a/src/main/java/org/ultramine/server/util/SpiralCoordIterator.java b/src/main/java/org/ultramine/server/util/SpiralCoordIterator.java new file mode 100644 index 0000000..2d12f62 --- /dev/null +++ b/src/main/java/org/ultramine/server/util/SpiralCoordIterator.java @@ -0,0 +1,102 @@ +package org.ultramine.server.util; + +import net.minecraft.world.ChunkCoordIntPair; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class SpiralCoordIterator implements Iterator +{ + private final int baseX; + private final int baseZ; + private final int limit; + private int counter; + private int radius; + private int offset; + private int position; + + public SpiralCoordIterator(int baseX, int baseZ, int limit) + { + this.baseX = baseX; + this.baseZ = baseZ; + this.limit = limit; + } + + public SpiralCoordIterator(int baseX, int baseZ) + { + this(baseX, baseZ, Integer.MAX_VALUE); + } + + public SpiralCoordIterator() + { + this(0, 0); + } + + public int getLimit() + { + return limit; + } + + public int getCounter() + { + return counter; + } + + @Override + public boolean hasNext() + { + return counter < limit; + } + + @Override + public ChunkCoordIntPair next() + { + if(!hasNext()) + throw new NoSuchElementException("limit is exceeded"); + counter++; + int resX; + int resZ; + if(radius == 0) + { + resX = 0; + resZ = 0; + radius = 1; + offset = -1; + } + else + { + switch(position) + { + case 0: + resX = -radius; + resZ = offset; + break; + case 1: + resX = offset; + resZ = radius; + break; + case 2: + resX = radius; + resZ = -offset; + break; + case 3: + default: + resX = -offset; + resZ = -radius; + break; + } + + if(++offset == radius) + { + if(++position == 4) + { + ++radius; + position = 0; + } + offset = -radius; + } + } + + return new ChunkCoordIntPair(baseX + resX, baseZ + resZ); + } +} diff --git a/src/main/resources/assets/ultramine/lang/en_US.lang b/src/main/resources/assets/ultramine/lang/en_US.lang index ef6bf77..49180ae 100644 --- a/src/main/resources/assets/ultramine/lang/en_US.lang +++ b/src/main/resources/assets/ultramine/lang/en_US.lang @@ -294,12 +294,13 @@ command.custmsg.usage=/custmsg command.custmsg.description=Sends custom formatted message to player (support colors, use &) -command.genworld.usage=/genworld [chunks per tick] OR /genworld radius [chunks per tick] OR /genworld stop -command.genworld.description=Generateas area radially or inside world border -command.genworld.already=World is now generating. Type "/genworld stop" before +command.genworld.usage=/genworld OR /genworld OR /genworld OR /genworld [world] stop +command.genworld.description=Generates area radially or inside world border +command.genworld.already=Generation of specified world is already started. Type "/genworld stop" before command.genworld.noborder=The world don't contains any borders; add border or use radially generation command.genworld.start=World generation started command.genworld.stop=World generation stopped (generated %s/%s chunks) +command.genworld.stop.fail.notrun=Generation of specified world has not yet been started command.genworld.complete=World generation completed (generated %s/%s chunks) command.genworld.process=World generation: generated %s/%s chunks diff --git a/src/main/resources/assets/ultramine/lang/ru_RU.lang b/src/main/resources/assets/ultramine/lang/ru_RU.lang index 63ab54c..29f259c 100644 --- a/src/main/resources/assets/ultramine/lang/ru_RU.lang +++ b/src/main/resources/assets/ultramine/lang/ru_RU.lang @@ -294,12 +294,13 @@ command.custmsg.usage=/custmsg <игрок|ALL> <сообщение> command.custmsg.description=Отправляет кастомное сообщение игроку (поддерживает цвета) -command.genworld.usage=/genworld [чанков за тик] ИЛИ /genworld radius <радиус> [чанков за тик] ИЛИ /genworld stop +command.genworld.usage=/genworld ИЛИ /genworld <радиус> ИЛИ /genworld <мир> <радиус> ИЛИ /genworld [мир] stop command.genworld.description=Генерирует мир по радиусу или в пределах мирового барьера -command.genworld.already=Генерация мира уже запущена. Сначала введите "/genworld stop" +command.genworld.already=Генерация указанного мира уже запущена. Сначала введите "/genworld stop" command.genworld.noborder=Мир не содержит барьера; добавьте барьер или используйте генерацию по радиусу command.genworld.start=Генерация мира запущена command.genworld.stop=Генерация мира остановлена (сгенерировано %s/%s чанков) +command.genworld.stop.fail.notrun=Генерация указанного мира еще не была запущена command.genworld.complete=Генерация мира завершена (сгенерировано %s/%s чанков) command.genworld.process=Генерация мира: сгенерировано %s/%s чанков