mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-27 14:13:24 +02:00
Low level generation API (#574)
This commit is contained in:
parent
1644a1e790
commit
a70bb15146
@ -5,12 +5,9 @@ import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.GlobalEventHandler;
|
||||
import net.minestom.server.event.player.PlayerLoginEvent;
|
||||
import net.minestom.server.instance.*;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MainDemo {
|
||||
|
||||
@ -22,7 +19,7 @@ public class MainDemo {
|
||||
// Create the instance
|
||||
InstanceContainer instanceContainer = instanceManager.createInstanceContainer();
|
||||
// Set the ChunkGenerator
|
||||
instanceContainer.setChunkGenerator(new GeneratorDemo());
|
||||
instanceContainer.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
|
||||
|
||||
// Add an event callback to specify the spawning instance (and the spawn position)
|
||||
GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler();
|
||||
@ -36,24 +33,4 @@ public class MainDemo {
|
||||
// Start the server on port 25565
|
||||
minecraftServer.start("0.0.0.0", 25565);
|
||||
}
|
||||
|
||||
private static class GeneratorDemo implements ChunkGenerator {
|
||||
|
||||
@Override
|
||||
public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {
|
||||
// Set chunk blocks
|
||||
for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++)
|
||||
for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (byte y = 0; y < 40; y++) {
|
||||
batch.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPopulator> getPopulators() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package net.minestom.demo;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.demo.generator.ChunkGeneratorDemo;
|
||||
import net.minestom.demo.generator.NoiseTestGenerator;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.adventure.audience.Audiences;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
@ -31,6 +29,7 @@ import net.minestom.server.item.metadata.BundleMeta;
|
||||
import net.minestom.server.monitoring.BenchmarkManager;
|
||||
import net.minestom.server.monitoring.TickMonitor;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
|
||||
@ -40,7 +39,6 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class PlayerInit {
|
||||
|
||||
@ -122,17 +120,13 @@ public class PlayerInit {
|
||||
|
||||
static {
|
||||
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
|
||||
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
||||
NoiseTestGenerator noiseTestGenerator = new NoiseTestGenerator();
|
||||
|
||||
InstanceContainer instanceContainer = instanceManager.createInstanceContainer(DimensionType.OVERWORLD);
|
||||
instanceContainer.setChunkGenerator(chunkGeneratorDemo);
|
||||
instanceContainer.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
|
||||
|
||||
if (false) {
|
||||
System.out.println("start");
|
||||
IntStream.range(0, 5000).forEach(value -> {
|
||||
instanceContainer.loadChunk(0, value).join();
|
||||
});
|
||||
ChunkUtils.forChunksInRange(0, 0, 10, (x, z) -> instanceContainer.loadChunk(x, z).join());
|
||||
System.out.println("load end");
|
||||
}
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
package net.minestom.demo.generator;
|
||||
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.ChunkGenerator;
|
||||
import net.minestom.server.instance.ChunkPopulator;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChunkGeneratorDemo implements ChunkGenerator {
|
||||
|
||||
@Override
|
||||
public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {
|
||||
for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++)
|
||||
for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (byte y = 0; y < 40; y++) {
|
||||
batch.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPopulator> getPopulators() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -2,24 +2,39 @@ package net.minestom.demo.generator;
|
||||
|
||||
import de.articdive.jnoise.JNoise;
|
||||
import de.articdive.jnoise.interpolation.InterpolationType;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.ChunkGenerator;
|
||||
import net.minestom.server.instance.ChunkPopulator;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class NoiseTestGenerator implements ChunkGenerator {
|
||||
public class NoiseTestGenerator implements Generator {
|
||||
|
||||
private Random random = new Random();
|
||||
private JNoise jNoise = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.4).build();
|
||||
private JNoise jNoise2 = JNoise.newBuilder().perlin().setInterpolation(InterpolationType.LINEAR).setSeed(random.nextInt()).setFrequency(0.6).build();
|
||||
private TreePopulator treeGen = new TreePopulator();
|
||||
public NoiseTestGenerator() {
|
||||
treeNoise.getNoise(0, 0);
|
||||
jNoise.getNoise(0, 0);
|
||||
}
|
||||
|
||||
private final JNoise treeNoise = JNoise.newBuilder()
|
||||
.fastSimplex()
|
||||
.setFrequency(999)
|
||||
.setSeed(123)
|
||||
.build();
|
||||
|
||||
private final JNoise jNoise = JNoise.newBuilder()
|
||||
.perlin()
|
||||
.setInterpolation(InterpolationType.LINEAR)
|
||||
.setSeed(123)
|
||||
.setFrequency(0.4).build();
|
||||
|
||||
public int getHeight(int x, int z) {
|
||||
double preHeight = jNoise.getNoise(x / 16.0, z / 16.0);
|
||||
@ -27,143 +42,111 @@ public class NoiseTestGenerator implements ChunkGenerator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {
|
||||
public void generate(@NotNull GenerationUnit unit) {
|
||||
Point start = unit.absoluteStart();
|
||||
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
final int height = getHeight(x + chunkX * 16, z + chunkZ * 16);
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (y == 0) {
|
||||
batch.setBlock(x, y, z, Block.BEDROCK);
|
||||
} else if (y == height - 1) {
|
||||
batch.setBlock(x, y, z, Block.GRASS_BLOCK);
|
||||
} else if (y > height - 7) {
|
||||
batch.setBlock(x, y, z, Block.DIRT);
|
||||
} else {
|
||||
batch.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
Point pos;
|
||||
{
|
||||
int absX = start.blockX() + x;
|
||||
int absZ = start.blockZ() + z;
|
||||
final int height = getHeight(absX, absZ);
|
||||
pos = new Vec(absX, height, absZ);
|
||||
}
|
||||
if (height < 61) {
|
||||
batch.setBlock(x, height - 1, z, Block.DIRT);
|
||||
for (int y = 0; y < 61 - height; y++) {
|
||||
batch.setBlock(x, y + height, z, Block.WATER);
|
||||
}
|
||||
Point posp1 = pos.add(1, 0, 1);
|
||||
|
||||
// Water
|
||||
if (pos.y() < 61) {
|
||||
unit.modifier().fill(pos, posp1.withY(61), Block.WATER);
|
||||
unit.modifier().fill(pos.withY(0), posp1, Block.AIR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular terrain
|
||||
unit.modifier().fill(pos.withY(0), posp1, Block.STONE);
|
||||
unit.modifier().fill(pos.withY(pos.y() - 7), posp1, Block.DIRT);
|
||||
unit.modifier().fill(pos.withY(pos.y() - 1), posp1, Block.GRASS_BLOCK);
|
||||
unit.modifier().fill(pos.withY(0), posp1.withY(1), Block.BEDROCK);
|
||||
|
||||
if (treeNoise.getNoise(pos.x(), pos.z()) > 0.8) {
|
||||
TreePopulator.populate(pos, unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPopulator> getPopulators() {
|
||||
List<ChunkPopulator> list = new ArrayList<>();
|
||||
list.add(treeGen);
|
||||
return list;
|
||||
}
|
||||
|
||||
private class TreePopulator implements ChunkPopulator {
|
||||
|
||||
final Structure tree;
|
||||
|
||||
public TreePopulator() {
|
||||
tree = new Structure();
|
||||
tree.addBlock(Block.DIRT, 0, -1, 0);
|
||||
tree.addBlock(Block.OAK_LOG, 0, 0, 0);
|
||||
tree.addBlock(Block.OAK_LOG, 0, 1, 0);
|
||||
tree.addBlock(Block.OAK_LOG, 0, 2, 0);
|
||||
tree.addBlock(Block.OAK_LOG, 0, 3, 0);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 1, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 1, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 1, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 1, 0);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 1, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 1, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 1, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 1, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 1, 1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 1, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 1, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 1, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 1, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 1, 2);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 1, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 1, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 1, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 1, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 1, -1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 1, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 1, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 1, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 1, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 1, -2);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 2, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 2, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 2, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 2, 0);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 2, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 2, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 2, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 2, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 2, 1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 2, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 2, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 2, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 2, 2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 2, 2);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 2, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 2, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 2, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 2, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 2, -1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 2, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 2, 2, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 2, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 2, -2);
|
||||
tree.addBlock(Block.OAK_LEAVES, -2, 2, -2);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 3, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 3, 0);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 3, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 3, 1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 3, 1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 3, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 3, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 3, -1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 1, 4, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 4, 0);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 4, 0);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 4, 1);
|
||||
|
||||
tree.addBlock(Block.OAK_LEAVES, 0, 4, -1);
|
||||
tree.addBlock(Block.OAK_LEAVES, -1, 4, -1);
|
||||
private static class TreePopulator {
|
||||
private static void populate(Point origin, GenerationUnit unit) {
|
||||
unit.fork(setter -> {
|
||||
setter.setBlock(origin.add(0, -1, 0), Block.DIRT);
|
||||
setter.setBlock(origin.add(0, -1, 0), Block.DIRT);
|
||||
setter.setBlock(origin.add(0, 0, 0), Block.OAK_LOG);
|
||||
setter.setBlock(origin.add(0, 1, 0), Block.OAK_LOG);
|
||||
setter.setBlock(origin.add(0, 2, 0), Block.OAK_LOG);
|
||||
setter.setBlock(origin.add(0, 3, 0), Block.OAK_LOG);
|
||||
setter.setBlock(origin.add(1, 1, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 1, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 1, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 1, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 1, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 1, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 1, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 1, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 1, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 1, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 1, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 1, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 1, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 1, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 1, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 1, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 1, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 1, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 1, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 1, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 1, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 1, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 1, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 1, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 2, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 2, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 2, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 2, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 2, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 2, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 2, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 2, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 2, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 2, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 2, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 2, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 2, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 2, 2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 2, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 2, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 2, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 2, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 2, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 2, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(2, 2, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 2, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 2, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-2, 2, -2), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 3, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 3, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 3, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 3, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 3, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 3, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 3, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 3, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(1, 4, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 4, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 4, 0), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 4, 1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(0, 4, -1), Block.OAK_LEAVES);
|
||||
setter.setBlock(origin.add(-1, 4, -1), Block.OAK_LEAVES);
|
||||
});
|
||||
}
|
||||
|
||||
//todo improve
|
||||
@Override
|
||||
public void populateChunk(ChunkBatch batch, Chunk chunk) {
|
||||
for (int i = -2; i < 18; i++) {
|
||||
for (int j = -2; j < 18; j++) {
|
||||
if (jNoise2.getNoise(i + chunk.getChunkX() * 16, j + chunk.getChunkZ() * 16) > 0.75) {
|
||||
int y = getHeight(i + chunk.getChunkX() * 16, j + chunk.getChunkZ() * 16);
|
||||
tree.build(batch, new Vec(i, y, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
package net.minestom.demo.generator;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Structure {
|
||||
|
||||
private final Map<Point, Block> blocks = new HashMap<>();
|
||||
|
||||
public void build(ChunkBatch batch, Point pos) {
|
||||
blocks.forEach((bPos, block) -> {
|
||||
if (bPos.x() + pos.x() >= Chunk.CHUNK_SIZE_X || bPos.x() + pos.x() < 0)
|
||||
return;
|
||||
if (bPos.z() + pos.z() >= Chunk.CHUNK_SIZE_Z || bPos.z() + pos.z() < 0)
|
||||
return;
|
||||
batch.setBlock(bPos.add(pos), block);
|
||||
});
|
||||
}
|
||||
|
||||
public void addBlock(Block block, int localX, int localY, int localZ) {
|
||||
blocks.put(new Vec(localX, localY, localZ), block);
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,9 @@ import java.util.List;
|
||||
* Responsible for the {@link Chunk} generation, can be set using {@link Instance#setChunkGenerator(ChunkGenerator)}.
|
||||
* <p>
|
||||
* Called if the instance {@link IChunkLoader} hasn't been able to load the chunk.
|
||||
* @deprecated Replaced by {@link net.minestom.server.instance.generator.Generator}
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ChunkGenerator {
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,42 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides full compatibility for the deprecated {@link ChunkGenerator}
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
record ChunkGeneratorCompatibilityLayer(@NotNull ChunkGenerator chunkGenerator) implements Generator {
|
||||
@Override
|
||||
public void generate(@NotNull GenerationUnit unit) {
|
||||
if (!(unit instanceof GeneratorImpl.UnitImpl impl) ||
|
||||
!(impl.modifier() instanceof GeneratorImpl.AreaModifierImpl modifier && modifier.chunk() != null)) {
|
||||
throw new IllegalArgumentException("Invalid unit");
|
||||
}
|
||||
|
||||
final int startY = unit.absoluteStart().blockY();
|
||||
ChunkBatch batch = new ChunkBatch() {
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
unit.modifier().setRelative(x, y - startY, z, block);
|
||||
}
|
||||
};
|
||||
final Point start = unit.absoluteStart();
|
||||
chunkGenerator.generateChunkData(batch, start.chunkX(), start.chunkZ());
|
||||
|
||||
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
||||
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
||||
if (hasPopulator) {
|
||||
for (ChunkPopulator chunkPopulator : populators) {
|
||||
chunkPopulator.populateChunk(batch, modifier.chunk());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
|
||||
@Deprecated
|
||||
public interface ChunkPopulator {
|
||||
|
||||
void populateChunk(ChunkBatch batch, Chunk chunk);
|
||||
|
518
src/main/java/net/minestom/server/instance/GeneratorImpl.java
Normal file
518
src/main/java/net/minestom/server/instance/GeneratorImpl.java
Normal file
@ -0,0 +1,518 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.instance.generator.UnitModifier;
|
||||
import net.minestom.server.instance.palette.Palette;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static net.minestom.server.utils.chunk.ChunkUtils.*;
|
||||
|
||||
final class GeneratorImpl {
|
||||
private static final Vec SECTION_SIZE = new Vec(16);
|
||||
|
||||
static GenerationUnit section(Section section, int sectionX, int sectionY, int sectionZ,
|
||||
boolean fork) {
|
||||
final Vec start = SECTION_SIZE.mul(sectionX, sectionY, sectionZ);
|
||||
final Vec end = start.add(SECTION_SIZE);
|
||||
final UnitModifier modifier = new SectionModifierImpl(SECTION_SIZE, start, end,
|
||||
section.blockPalette(), section.biomePalette(), new Int2ObjectOpenHashMap<>(0), fork);
|
||||
return unit(modifier, start, end, null);
|
||||
}
|
||||
|
||||
static GenerationUnit section(Section section, int sectionX, int sectionY, int sectionZ) {
|
||||
return section(section, sectionX, sectionY, sectionZ, false);
|
||||
}
|
||||
|
||||
static UnitImpl chunk(Chunk chunk, int minSection, int maxSection,
|
||||
List<Section> chunkSections, int chunkX, int chunkZ) {
|
||||
final int minY = minSection * 16;
|
||||
AtomicInteger sectionCounterY = new AtomicInteger(minSection);
|
||||
List<GenerationUnit> sections = chunkSections.stream()
|
||||
.map(section -> section(section, chunkX, sectionCounterY.getAndIncrement(), chunkZ))
|
||||
.toList();
|
||||
|
||||
final Vec size = new Vec(16, (maxSection - minSection) * 16, 16);
|
||||
final Vec start = new Vec(chunkX * 16, minY, chunkZ * 16);
|
||||
final Vec end = new Vec(chunkX * 16 + 16, size.y() + minY, chunkZ * 16 + 16);
|
||||
final UnitModifier modifier = new AreaModifierImpl(chunk,
|
||||
size, start, end, 1, sections.size(), 1, sections);
|
||||
return unit(modifier, start, end, sections);
|
||||
}
|
||||
|
||||
static UnitImpl chunk(int minSection, int maxSection,
|
||||
List<Section> chunkSections, int chunkX, int chunkZ) {
|
||||
return chunk(null, minSection, maxSection, chunkSections, chunkX, chunkZ);
|
||||
}
|
||||
|
||||
static UnitImpl chunk(Chunk chunk) {
|
||||
return chunk(chunk, chunk.minSection, chunk.maxSection, chunk.getSections(), chunk.getChunkX(), chunk.getChunkZ());
|
||||
}
|
||||
|
||||
static UnitImpl unit(UnitModifier modifier, Point start, Point end,
|
||||
List<GenerationUnit> divided) {
|
||||
if (start.x() > end.x() || start.y() > end.y() || start.z() > end.z()) {
|
||||
throw new IllegalArgumentException("absoluteStart must be before absoluteEnd");
|
||||
}
|
||||
if (start.x() % 16 != 0 || start.y() % 16 != 0 || start.z() % 16 != 0) {
|
||||
throw new IllegalArgumentException("absoluteStart must be a multiple of 16");
|
||||
}
|
||||
if (end.x() % 16 != 0 || end.y() % 16 != 0 || end.z() % 16 != 0) {
|
||||
throw new IllegalArgumentException("absoluteEnd must be a multiple of 16");
|
||||
}
|
||||
final Point size = end.sub(start);
|
||||
return new UnitImpl(modifier, size, start, end, divided, new CopyOnWriteArrayList<>());
|
||||
}
|
||||
|
||||
static final class DynamicFork implements Block.Setter {
|
||||
Vec minSection;
|
||||
int width, height, depth;
|
||||
List<GenerationUnit> sections;
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
resize(x, y, z);
|
||||
GenerationUnit section = findAbsolute(sections, minSection, width, height, depth, x, y, z);
|
||||
assert section.absoluteStart().chunkX() == getChunkCoordinate(x) &&
|
||||
section.absoluteStart().section() == getChunkCoordinate(y) &&
|
||||
section.absoluteStart().chunkZ() == getChunkCoordinate(z) :
|
||||
"Invalid section " + section.absoluteStart() + " for " + x + ", " + y + ", " + z;
|
||||
section.modifier().setBlock(x, y, z, block);
|
||||
}
|
||||
|
||||
private void resize(int x, int y, int z) {
|
||||
final int sectionX = getChunkCoordinate(x);
|
||||
final int sectionY = getChunkCoordinate(y);
|
||||
final int sectionZ = getChunkCoordinate(z);
|
||||
if (sections == null) {
|
||||
this.minSection = new Vec(sectionX * 16, sectionY * 16, sectionZ * 16);
|
||||
this.width = 1;
|
||||
this.height = 1;
|
||||
this.depth = 1;
|
||||
this.sections = List.of(section(new Section(), sectionX, sectionY, sectionZ, true));
|
||||
} else if (x < minSection.x() || y < minSection.y() || z < minSection.z() ||
|
||||
x >= minSection.x() + width * 16 || y >= minSection.y() + height * 16 || z >= minSection.z() + depth * 16) {
|
||||
// Resize necessary
|
||||
final Vec newMin = new Vec(Math.min(minSection.x(), sectionX * 16),
|
||||
Math.min(minSection.y(), sectionY * 16),
|
||||
Math.min(minSection.z(), sectionZ * 16));
|
||||
final Vec newMax = new Vec(Math.max(minSection.x() + width * 16, sectionX * 16 + 16),
|
||||
Math.max(minSection.y() + height * 16, sectionY * 16 + 16),
|
||||
Math.max(minSection.z() + depth * 16, sectionZ * 16 + 16));
|
||||
final int newWidth = getChunkCoordinate(newMax.x() - newMin.x());
|
||||
final int newHeight = getChunkCoordinate(newMax.y() - newMin.y());
|
||||
final int newDepth = getChunkCoordinate(newMax.z() - newMin.z());
|
||||
// Resize
|
||||
GenerationUnit[] newSections = new GenerationUnit[newWidth * newHeight * newDepth];
|
||||
// Copy old sections
|
||||
for (GenerationUnit s : sections) {
|
||||
final Point start = s.absoluteStart();
|
||||
final int newX = getChunkCoordinate(start.x() - newMin.x());
|
||||
final int newY = getChunkCoordinate(start.y() - newMin.y());
|
||||
final int newZ = getChunkCoordinate(start.z() - newMin.z());
|
||||
final int index = findIndex(newWidth, newHeight, newDepth, newX, newY, newZ);
|
||||
newSections[index] = s;
|
||||
}
|
||||
// Fill new sections
|
||||
final int startX = newMin.chunkX();
|
||||
final int startY = newMin.section();
|
||||
final int startZ = newMin.chunkZ();
|
||||
for (int i = 0; i < newSections.length; i++) {
|
||||
if (newSections[i] == null) {
|
||||
final Point coordinates = to3D(i, newWidth, newHeight, newDepth);
|
||||
final int newX = coordinates.blockX() + startX;
|
||||
final int newY = coordinates.blockY() + startY;
|
||||
final int newZ = coordinates.blockZ() + startZ;
|
||||
final GenerationUnit unit = section(new Section(), newX, newY, newZ, true);
|
||||
newSections[i] = unit;
|
||||
}
|
||||
}
|
||||
this.sections = List.of(newSections);
|
||||
this.minSection = newMin;
|
||||
this.width = newWidth;
|
||||
this.height = newHeight;
|
||||
this.depth = newDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record UnitImpl(UnitModifier modifier, Point size,
|
||||
Point absoluteStart, Point absoluteEnd,
|
||||
List<GenerationUnit> divided,
|
||||
List<UnitImpl> forks) implements GenerationUnit {
|
||||
@Override
|
||||
public @NotNull GenerationUnit fork(@NotNull Point start, @NotNull Point end) {
|
||||
final int minSectionX = floorSection(start.blockX()) / 16;
|
||||
final int minSectionY = floorSection(start.blockY()) / 16;
|
||||
final int minSectionZ = floorSection(start.blockZ()) / 16;
|
||||
|
||||
final int maxSectionX = ceilSection(end.blockX()) / 16;
|
||||
final int maxSectionY = ceilSection(end.blockY()) / 16;
|
||||
final int maxSectionZ = ceilSection(end.blockZ()) / 16;
|
||||
|
||||
final int width = maxSectionX - minSectionX;
|
||||
final int height = maxSectionY - minSectionY;
|
||||
final int depth = maxSectionZ - minSectionZ;
|
||||
|
||||
GenerationUnit[] units = new GenerationUnit[width * height * depth];
|
||||
int index = 0;
|
||||
for (int sectionX = minSectionX; sectionX < maxSectionX; sectionX++) {
|
||||
for (int sectionY = minSectionY; sectionY < maxSectionY; sectionY++) {
|
||||
for (int sectionZ = minSectionZ; sectionZ < maxSectionZ; sectionZ++) {
|
||||
final GenerationUnit unit = section(new Section(), sectionX, sectionY, sectionZ, true);
|
||||
units[index++] = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
final List<GenerationUnit> sections = List.of(units);
|
||||
final Point startSection = new Vec(minSectionX * 16, minSectionY * 16, minSectionZ * 16);
|
||||
return registerFork(startSection, sections, width, height, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fork(@NotNull Consumer<Block.@NotNull Setter> consumer) {
|
||||
DynamicFork dynamicFork = new DynamicFork();
|
||||
consumer.accept(dynamicFork);
|
||||
final int width = dynamicFork.width;
|
||||
final int height = dynamicFork.height;
|
||||
final int depth = dynamicFork.depth;
|
||||
final Point startSection = dynamicFork.minSection;
|
||||
final List<GenerationUnit> sections = dynamicFork.sections;
|
||||
registerFork(startSection, sections, width, height, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<GenerationUnit> subdivide() {
|
||||
return Objects.requireNonNullElseGet(divided, GenerationUnit.super::subdivide);
|
||||
}
|
||||
|
||||
private GenerationUnit registerFork(Point start, List<GenerationUnit> sections,
|
||||
int width, int height, int depth) {
|
||||
final Point end = start.add(width * 16, height * 16, depth * 16);
|
||||
final Point size = end.sub(start);
|
||||
final AreaModifierImpl modifier = new AreaModifierImpl(null,
|
||||
size, start, end, width, height, depth, sections);
|
||||
final UnitImpl fork = new UnitImpl(modifier, size, start, end, sections, forks);
|
||||
forks.add(fork);
|
||||
return fork;
|
||||
}
|
||||
}
|
||||
|
||||
record SectionModifierImpl(Point size, Point start, Point end,
|
||||
Palette blockPalette, Palette biomePalette,
|
||||
Int2ObjectMap<Block> cache, boolean fork) implements GenericModifier {
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, @NotNull Biome biome) {
|
||||
if (fork) throw new IllegalStateException("Cannot modify biomes of a fork");
|
||||
this.biomePalette.set(
|
||||
toSectionRelativeCoordinate(x) / 4,
|
||||
toSectionRelativeCoordinate(y) / 4,
|
||||
toSectionRelativeCoordinate(z) / 4, biome.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
final int localX = toSectionRelativeCoordinate(x);
|
||||
final int localY = toSectionRelativeCoordinate(y);
|
||||
final int localZ = toSectionRelativeCoordinate(z);
|
||||
handleCache(localX, localY, localZ, block);
|
||||
this.blockPalette.set(localX, localY, localZ, retrieveBlockId(block));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelative(int x, int y, int z, @NotNull Block block) {
|
||||
handleCache(x, y, z, block);
|
||||
this.blockPalette.set(x, y, z, retrieveBlockId(block));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllRelative(@NotNull Supplier supplier) {
|
||||
this.blockPalette.setAll((x, y, z) -> {
|
||||
final Block block = supplier.get(x, y, z);
|
||||
handleCache(x, y, z, block);
|
||||
return retrieveBlockId(block);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(@NotNull Block block) {
|
||||
if (requireCache(block)) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
this.cache.put(getBlockIndex(x, y, z), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.blockPalette.fill(retrieveBlockId(block));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiome(@NotNull Biome biome) {
|
||||
if (fork) throw new IllegalStateException("Cannot modify biomes of a fork");
|
||||
this.biomePalette.fill(biome.id());
|
||||
}
|
||||
|
||||
private int retrieveBlockId(Block block) {
|
||||
return fork ? block.stateId() + 1 : block.stateId();
|
||||
}
|
||||
|
||||
private void handleCache(int x, int y, int z, Block block) {
|
||||
if (requireCache(block)) {
|
||||
this.cache.put(getBlockIndex(x, y, z), block);
|
||||
} else if (!cache.isEmpty()) {
|
||||
this.cache.remove(getBlockIndex(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requireCache(Block block) {
|
||||
return block.hasNbt() || block.handler() != null || block.registry().isBlockEntity();
|
||||
}
|
||||
}
|
||||
|
||||
record AreaModifierImpl(Chunk chunk,
|
||||
Point size, Point start, Point end,
|
||||
int width, int height, int depth,
|
||||
List<GenerationUnit> sections) implements GenericModifier {
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
checkBorder(x, y, z);
|
||||
final GenerationUnit section = findAbsoluteSection(x, y, z);
|
||||
y -= start.y();
|
||||
section.modifier().setBlock(x, y, z, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, @NotNull Biome biome) {
|
||||
checkBorder(x, y, z);
|
||||
final GenerationUnit section = findAbsoluteSection(x, y, z);
|
||||
y -= start.y();
|
||||
section.modifier().setBiome(x, y, z, biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelative(int x, int y, int z, @NotNull Block block) {
|
||||
if (x < 0 || x >= size.x() || y < 0 || y >= size.y() || z < 0 || z >= size.z()) {
|
||||
throw new IllegalArgumentException("x, y and z must be in the chunk: " + x + ", " + y + ", " + z);
|
||||
}
|
||||
final GenerationUnit section = findRelativeSection(x, y, z);
|
||||
x = toSectionRelativeCoordinate(x);
|
||||
y = toSectionRelativeCoordinate(y);
|
||||
z = toSectionRelativeCoordinate(z);
|
||||
section.modifier().setBlock(x, y, z, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(@NotNull Supplier supplier) {
|
||||
for (GenerationUnit section : sections) {
|
||||
final var start = section.absoluteStart();
|
||||
final int startX = start.blockX();
|
||||
final int startY = start.blockY();
|
||||
final int startZ = start.blockZ();
|
||||
section.modifier().setAllRelative((x, y, z) ->
|
||||
supplier.get(x + startX, y + startY, z + startZ));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllRelative(@NotNull Supplier supplier) {
|
||||
final Point start = this.start;
|
||||
for (GenerationUnit section : sections) {
|
||||
final Point sectionStart = section.absoluteStart();
|
||||
final int offsetX = sectionStart.blockX() - start.blockX();
|
||||
final int offsetY = sectionStart.blockY() - start.blockY();
|
||||
final int offsetZ = sectionStart.blockZ() - start.blockZ();
|
||||
section.modifier().setAllRelative((x, y, z) ->
|
||||
supplier.get(x + offsetX, y + offsetY, z + offsetZ));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(@NotNull Block block) {
|
||||
for (GenerationUnit section : sections) {
|
||||
section.modifier().fill(block);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillBiome(@NotNull Biome biome) {
|
||||
for (GenerationUnit section : sections) {
|
||||
section.modifier().fillBiome(biome);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillHeight(int minHeight, int maxHeight, @NotNull Block block) {
|
||||
final Point start = this.start;
|
||||
final int width = this.width;
|
||||
final int depth = this.depth;
|
||||
final int startX = start.blockX();
|
||||
final int startZ = start.blockZ();
|
||||
final int minMultiple = floorSection(minHeight);
|
||||
final int maxMultiple = ceilSection(maxHeight);
|
||||
final boolean startOffset = minMultiple != minHeight;
|
||||
final boolean endOffset = maxMultiple != maxHeight;
|
||||
if (startOffset || endOffset) {
|
||||
final int firstFill = Math.min(minMultiple + 16, maxHeight);
|
||||
final int lastFill = floorSection(maxHeight);
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int z = 0; z < depth; z++) {
|
||||
final int sectionX = startX + x * 16;
|
||||
final int sectionZ = startZ + z * 16;
|
||||
// Fill start
|
||||
if (startOffset) {
|
||||
final GenerationUnit section = findAbsoluteSection(sectionX, minMultiple, sectionZ);
|
||||
section.modifier().fillHeight(minHeight, firstFill, block);
|
||||
}
|
||||
// Fill end
|
||||
if (endOffset) {
|
||||
final GenerationUnit section = findAbsoluteSection(sectionX, maxHeight, sectionZ);
|
||||
section.modifier().fillHeight(lastFill, maxHeight, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Middle sections (to fill)
|
||||
final int startSection = (minMultiple) / 16 + (startOffset ? 1 : 0);
|
||||
final int endSection = (maxMultiple) / 16 + (endOffset ? -1 : 0);
|
||||
for (int i = startSection; i < endSection; i++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int z = 0; z < depth; z++) {
|
||||
final GenerationUnit section = findAbsoluteSection(startX + x * 16, i * 16, startZ + z * 16);
|
||||
section.modifier().fill(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GenerationUnit findAbsoluteSection(int x, int y, int z) {
|
||||
return findAbsolute(sections, start, width, height, depth, x, y, z);
|
||||
}
|
||||
|
||||
private GenerationUnit findRelativeSection(int x, int y, int z) {
|
||||
final int sectionX = getChunkCoordinate(x);
|
||||
final int sectionY = getChunkCoordinate(y);
|
||||
final int sectionZ = getChunkCoordinate(z);
|
||||
final int index = sectionZ + sectionY * depth + sectionX * depth * height;
|
||||
return sections.get(index);
|
||||
}
|
||||
|
||||
private void checkBorder(int x, int y, int z) {
|
||||
if (x < start.x() || x >= end.x() ||
|
||||
y < start.y() || y >= end.y() ||
|
||||
z < start.z() || z >= end.z()) {
|
||||
final String format = String.format("Invalid coordinates: %d, %d, %d for area %s %s", x, y, z, start, end);
|
||||
throw new IllegalArgumentException(format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface GenericModifier extends UnitModifier
|
||||
permits AreaModifierImpl, SectionModifierImpl {
|
||||
Point size();
|
||||
|
||||
Point start();
|
||||
|
||||
Point end();
|
||||
|
||||
@Override
|
||||
default void setAll(@NotNull Supplier supplier) {
|
||||
final Point start = start();
|
||||
final Point end = end();
|
||||
final int endX = end.blockX();
|
||||
final int endY = end.blockY();
|
||||
final int endZ = end.blockZ();
|
||||
for (int x = start.blockX(); x < endX; x++) {
|
||||
for (int y = start.blockY(); y < endY; y++) {
|
||||
for (int z = start.blockZ(); z < endZ; z++) {
|
||||
setBlock(x, y, z, supplier.get(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setAllRelative(@NotNull Supplier supplier) {
|
||||
final Point size = size();
|
||||
final int endX = size.blockX();
|
||||
final int endY = size.blockY();
|
||||
final int endZ = size.blockZ();
|
||||
for (int x = 0; x < endX; x++) {
|
||||
for (int y = 0; y < endY; y++) {
|
||||
for (int z = 0; z < endZ; z++) {
|
||||
setRelative(x, y, z, supplier.get(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void fill(@NotNull Block block) {
|
||||
fill(start(), end(), block);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void fill(@NotNull Point start, @NotNull Point end, @NotNull Block block) {
|
||||
final int endX = end.blockX();
|
||||
final int endY = end.blockY();
|
||||
final int endZ = end.blockZ();
|
||||
for (int x = start.blockX(); x < endX; x++) {
|
||||
for (int y = start.blockY(); y < endY; y++) {
|
||||
for (int z = start.blockZ(); z < endZ; z++) {
|
||||
setBlock(x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void fillHeight(int minHeight, int maxHeight, @NotNull Block block) {
|
||||
final Point start = start();
|
||||
final Point end = end();
|
||||
final int startY = start.blockY();
|
||||
final int endY = end.blockY();
|
||||
if (startY >= minHeight && endY <= maxHeight) {
|
||||
// Fast path if the unit is fully contained in the height range
|
||||
fill(start, end, block);
|
||||
} else {
|
||||
// Slow path if the unit is not fully contained in the height range
|
||||
fill(start.withY(Math.max(minHeight, startY)), end.withY(Math.min(maxHeight, endY)), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerationUnit findAbsolute(List<GenerationUnit> units, Point start,
|
||||
int width, int height, int depth,
|
||||
int x, int y, int z) {
|
||||
final int sectionX = getChunkCoordinate(x - start.x());
|
||||
final int sectionY = getChunkCoordinate(y - start.y());
|
||||
final int sectionZ = getChunkCoordinate(z - start.z());
|
||||
final int index = findIndex(width, height, depth, sectionX, sectionY, sectionZ);
|
||||
return units.get(index);
|
||||
}
|
||||
|
||||
private static int findIndex(int width, int height, int depth,
|
||||
int x, int y, int z) {
|
||||
return (z * width * height) + (y * width) + x;
|
||||
}
|
||||
|
||||
private static Point to3D(int idx, int width, int height, int depth) {
|
||||
final int z = idx / (width * height);
|
||||
idx -= (z * width * height);
|
||||
final int y = idx / width;
|
||||
final int x = idx % width;
|
||||
return new Vec(x, y, z);
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.instance.InstanceTickEvent;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
||||
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
||||
import net.minestom.server.snapshot.ChunkSnapshot;
|
||||
@ -251,19 +252,30 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
|
||||
*/
|
||||
public abstract @NotNull CompletableFuture<Void> saveChunksToStorage();
|
||||
|
||||
/**
|
||||
* Gets the instance {@link ChunkGenerator}.
|
||||
*
|
||||
* @return the {@link ChunkGenerator} of the instance
|
||||
*/
|
||||
public abstract @Nullable ChunkGenerator getChunkGenerator();
|
||||
|
||||
/**
|
||||
* Changes the instance {@link ChunkGenerator}.
|
||||
*
|
||||
* @param chunkGenerator the new {@link ChunkGenerator} of the instance
|
||||
* @deprecated Use {@link #setGenerator(Generator)}
|
||||
*/
|
||||
public abstract void setChunkGenerator(@Nullable ChunkGenerator chunkGenerator);
|
||||
@Deprecated
|
||||
public void setChunkGenerator(@Nullable ChunkGenerator chunkGenerator) {
|
||||
setGenerator(chunkGenerator != null ? new ChunkGeneratorCompatibilityLayer(chunkGenerator) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generator associated with the instance
|
||||
*
|
||||
* @return the generator if any
|
||||
*/
|
||||
public abstract @Nullable Generator generator();
|
||||
|
||||
/**
|
||||
* Changes the generator of the instance
|
||||
*
|
||||
* @param generator the new generator, or null to disable generation
|
||||
*/
|
||||
public abstract void setGenerator(@Nullable Generator generator);
|
||||
|
||||
/**
|
||||
* Gets all the instance's loaded chunks.
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
@ -9,10 +10,11 @@ import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.instance.InstanceChunkLoadEvent;
|
||||
import net.minestom.server.event.instance.InstanceChunkUnloadEvent;
|
||||
import net.minestom.server.event.player.PlayerBlockBreakEvent;
|
||||
import net.minestom.server.instance.batch.ChunkGenerationBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.instance.block.rule.BlockPlacementRule;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import net.minestom.server.instance.palette.Palette;
|
||||
import net.minestom.server.network.packet.server.play.BlockChangePacket;
|
||||
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket;
|
||||
import net.minestom.server.network.packet.server.play.EffectPacket;
|
||||
@ -22,6 +24,7 @@ import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.utils.block.BlockUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkCache;
|
||||
import net.minestom.server.utils.chunk.ChunkSupplier;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.DimensionType;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@ -32,7 +35,9 @@ import space.vectrix.flare.fastutil.Long2ObjectSyncMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
@ -48,7 +53,7 @@ public class InstanceContainer extends Instance {
|
||||
private final List<SharedInstance> sharedInstances = new CopyOnWriteArrayList<>();
|
||||
|
||||
// the chunk generator used, can be null
|
||||
private ChunkGenerator chunkGenerator;
|
||||
private volatile Generator generator;
|
||||
// (chunk index -> chunk) map, contains all the chunks in the instance
|
||||
// used as a monitor when access is required
|
||||
private final Long2ObjectSyncMap<Chunk> chunks = Long2ObjectSyncMap.hashmap();
|
||||
@ -271,6 +276,10 @@ public class InstanceContainer extends Instance {
|
||||
final CompletableFuture<Chunk> future = this.loadingChunks.remove(index);
|
||||
assert future == completableFuture;
|
||||
future.complete(chunk);
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
MinecraftServer.getExceptionManager().handleException(throwable);
|
||||
return null;
|
||||
});
|
||||
if (loader.supportsParallelLoading()) {
|
||||
CompletableFuture.runAsync(retriever);
|
||||
@ -280,20 +289,106 @@ public class InstanceContainer extends Instance {
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
Map<Long, List<GeneratorImpl.SectionModifierImpl>> generationForks = new ConcurrentHashMap<>();
|
||||
|
||||
protected @NotNull CompletableFuture<@NotNull Chunk> createChunk(int chunkX, int chunkZ) {
|
||||
final ChunkGenerator generator = this.chunkGenerator;
|
||||
final Chunk chunk = chunkSupplier.createChunk(this, chunkX, chunkZ);
|
||||
Check.notNull(chunk, "Chunks supplied by a ChunkSupplier cannot be null.");
|
||||
Generator generator = generator();
|
||||
if (generator != null && chunk.shouldGenerate()) {
|
||||
// Execute the chunk generator to populate the chunk
|
||||
final ChunkGenerationBatch chunkBatch = new ChunkGenerationBatch(this, chunk);
|
||||
return chunkBatch.generate(generator);
|
||||
CompletableFuture<Chunk> resultFuture = new CompletableFuture<>();
|
||||
// TODO: virtual thread once Loom is available
|
||||
ForkJoinPool.commonPool().submit(() -> {
|
||||
var chunkUnit = GeneratorImpl.chunk(chunk);
|
||||
try {
|
||||
// Generate block/biome palette
|
||||
generator.generate(chunkUnit);
|
||||
// Apply nbt/handler
|
||||
if (chunkUnit.modifier() instanceof GeneratorImpl.AreaModifierImpl chunkModifier) {
|
||||
for (var section : chunkModifier.sections()) {
|
||||
if (section.modifier() instanceof GeneratorImpl.SectionModifierImpl sectionModifier) {
|
||||
applyGenerationData(chunk, sectionModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Register forks or apply locally
|
||||
for (var fork : chunkUnit.forks()) {
|
||||
var sections = ((GeneratorImpl.AreaModifierImpl) fork.modifier()).sections();
|
||||
for (var section : sections) {
|
||||
if (section.modifier() instanceof GeneratorImpl.SectionModifierImpl sectionModifier) {
|
||||
if (sectionModifier.blockPalette().count() == 0)
|
||||
continue;
|
||||
final Point start = section.absoluteStart();
|
||||
final Chunk forkChunk = start.chunkX() == chunkX && start.chunkZ() == chunkZ ? chunk : getChunkAt(start);
|
||||
if (forkChunk != null) {
|
||||
applyFork(forkChunk, sectionModifier);
|
||||
} else {
|
||||
final long index = ChunkUtils.getChunkIndex(start);
|
||||
this.generationForks.compute(index, (i, sectionModifiers) -> {
|
||||
if (sectionModifiers == null) sectionModifiers = new ArrayList<>();
|
||||
sectionModifiers.add(sectionModifier);
|
||||
return sectionModifiers;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply awaiting forks
|
||||
processFork(chunk);
|
||||
} catch (Throwable e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
} finally {
|
||||
// End generation
|
||||
chunk.sendChunk();
|
||||
refreshLastBlockChangeTime();
|
||||
resultFuture.complete(chunk);
|
||||
}
|
||||
});
|
||||
return resultFuture;
|
||||
} else {
|
||||
// No chunk generator, execute the callback with the empty chunk
|
||||
processFork(chunk);
|
||||
return CompletableFuture.completedFuture(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private void processFork(Chunk chunk) {
|
||||
this.generationForks.compute(ChunkUtils.getChunkIndex(chunk), (aLong, sectionModifiers) -> {
|
||||
if (sectionModifiers != null) {
|
||||
for (var sectionModifier : sectionModifiers) {
|
||||
applyFork(chunk, sectionModifier);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void applyFork(Chunk chunk, GeneratorImpl.SectionModifierImpl sectionModifier) {
|
||||
synchronized (chunk) {
|
||||
Section section = chunk.getSectionAt(sectionModifier.start().blockY());
|
||||
Palette currentBlocks = section.blockPalette();
|
||||
// -1 is necessary because forked units handle explicit changes by changing AIR 0 to 1
|
||||
sectionModifier.blockPalette().getAllPresent((x, y, z, value) -> currentBlocks.set(x, y, z, value - 1));
|
||||
applyGenerationData(chunk, sectionModifier);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyGenerationData(Chunk chunk, GeneratorImpl.SectionModifierImpl section) {
|
||||
var cache = section.cache();
|
||||
if (cache.isEmpty()) return;
|
||||
final int height = section.start().blockY();
|
||||
synchronized (chunk) {
|
||||
Int2ObjectMaps.fastForEach(cache, blockEntry -> {
|
||||
final int index = blockEntry.getIntKey();
|
||||
final Block block = blockEntry.getValue();
|
||||
final int x = ChunkUtils.blockIndexToChunkPositionX(index);
|
||||
final int y = ChunkUtils.blockIndexToChunkPositionY(index) + height;
|
||||
final int z = ChunkUtils.blockIndexToChunkPositionZ(index);
|
||||
chunk.setBlock(x, y, z, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAutoChunkLoad(boolean enable) {
|
||||
this.autoChunkLoad = enable;
|
||||
@ -419,13 +514,13 @@ public class InstanceContainer extends Instance {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator getChunkGenerator() {
|
||||
return chunkGenerator;
|
||||
public @Nullable Generator generator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
|
||||
this.chunkGenerator = chunkGenerator;
|
||||
public void setGenerator(@Nullable Generator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,9 @@ import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
@ -73,13 +75,13 @@ public class SharedInstance extends Instance {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
|
||||
this.instanceContainer.setChunkGenerator(chunkGenerator);
|
||||
public @Nullable Generator generator() {
|
||||
return instanceContainer.generator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator getChunkGenerator() {
|
||||
return instanceContainer.getChunkGenerator();
|
||||
public void setGenerator(@Nullable Generator generator) {
|
||||
instanceContainer.setGenerator(generator);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -1,60 +0,0 @@
|
||||
package net.minestom.server.instance.batch;
|
||||
|
||||
import net.minestom.server.instance.*;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ChunkGenerationBatch extends ChunkBatch {
|
||||
private final InstanceContainer instance;
|
||||
private final Chunk chunk;
|
||||
|
||||
public ChunkGenerationBatch(InstanceContainer instance, Chunk chunk) {
|
||||
super(new BatchOption());
|
||||
this.instance = instance;
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Block block) {
|
||||
chunk.setBlock(x, y, z, block);
|
||||
}
|
||||
|
||||
public @NotNull CompletableFuture<@NotNull Chunk> generate(@NotNull ChunkGenerator chunkGenerator) {
|
||||
final CompletableFuture<Chunk> completableFuture = new CompletableFuture<>();
|
||||
BLOCK_BATCH_POOL.execute(() -> {
|
||||
synchronized (chunk) {
|
||||
final List<ChunkPopulator> populators = chunkGenerator.getPopulators();
|
||||
final boolean hasPopulator = populators != null && !populators.isEmpty();
|
||||
|
||||
chunkGenerator.generateChunkData(this, chunk.getChunkX(), chunk.getChunkZ());
|
||||
|
||||
if (hasPopulator) {
|
||||
for (ChunkPopulator chunkPopulator : populators) {
|
||||
chunkPopulator.populateChunk(this, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the chunk.
|
||||
this.chunk.sendChunk();
|
||||
this.instance.refreshLastBlockChangeTime();
|
||||
completableFuture.complete(chunk);
|
||||
}
|
||||
});
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new IllegalStateException("#clear is not supported for chunk generation batch.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChunkBatch apply(@NotNull Instance instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) {
|
||||
throw new IllegalStateException("#apply is not supported for chunk generation batch.");
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package net.minestom.server.instance.generator;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface GenerationUnit {
|
||||
/**
|
||||
* This unit's modifier, used to place blocks and biomes within this unit.
|
||||
*
|
||||
* @return the modifier
|
||||
*/
|
||||
@NotNull UnitModifier modifier();
|
||||
|
||||
/**
|
||||
* The size of this unit in blocks.
|
||||
*
|
||||
* @return the size of this unit
|
||||
*/
|
||||
@NotNull Point size();
|
||||
|
||||
/**
|
||||
* The absolute start (min x, y, z) of this unit.
|
||||
*
|
||||
* @return the absolute start
|
||||
*/
|
||||
@NotNull Point absoluteStart();
|
||||
|
||||
/**
|
||||
* The absolute end (max x, y, z) of this unit.
|
||||
*
|
||||
* @return the absolute end
|
||||
*/
|
||||
@NotNull Point absoluteEnd();
|
||||
|
||||
/**
|
||||
* Creates a fork of this unit, which will be applied to the instance whenever possible.
|
||||
*
|
||||
* @param start the start of the fork
|
||||
* @param end the end of the fork
|
||||
* @return the fork
|
||||
*/
|
||||
@NotNull GenerationUnit fork(@NotNull Point start, @NotNull Point end);
|
||||
|
||||
/**
|
||||
* Creates a fork of this unit depending on the blocks placed within the consumer.
|
||||
*
|
||||
* @param consumer the consumer
|
||||
*/
|
||||
void fork(@NotNull Consumer<Block.@NotNull Setter> consumer);
|
||||
|
||||
/**
|
||||
* Divides this unit into the smallest independent units.
|
||||
*
|
||||
* @return an immutable list of independent units
|
||||
*/
|
||||
default @NotNull List<GenerationUnit> subdivide() {
|
||||
return List.of(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.minestom.server.instance.generator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Generator {
|
||||
/**
|
||||
* This method is called when this generator is requesting this unit to be filled with blocks or biomes.
|
||||
*
|
||||
* @param unit the unit to fill
|
||||
*/
|
||||
void generate(@NotNull GenerationUnit unit);
|
||||
|
||||
/**
|
||||
* Runs {@link #generate(GenerationUnit)} on each unit in the collection.
|
||||
*
|
||||
* @param units the list of units to fill
|
||||
*/
|
||||
default void generateAll(@NotNull Collection<@NotNull GenerationUnit> units) {
|
||||
units.forEach(this::generate);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package net.minestom.server.instance.generator;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface UnitModifier extends Block.Setter, Biome.Setter {
|
||||
/**
|
||||
* Sets the block relative to the absolute position of the unit.
|
||||
*
|
||||
* @param x the x coordinate
|
||||
* @param y the y coordinate
|
||||
* @param z the z coordinate
|
||||
* @param block the block to set
|
||||
*/
|
||||
void setRelative(int x, int y, int z, @NotNull Block block);
|
||||
|
||||
/**
|
||||
* Sets all blocks within the unit to the block given by the supplier.
|
||||
*
|
||||
* @param supplier the supplier of the block to set
|
||||
*/
|
||||
void setAll(@NotNull Supplier supplier);
|
||||
|
||||
/**
|
||||
* Sets all blocks within the unit to the block given by the supplier, relative to the absolute position of the unit.
|
||||
*
|
||||
* @param supplier the supplier of the block to set
|
||||
*/
|
||||
void setAllRelative(@NotNull Supplier supplier);
|
||||
|
||||
/**
|
||||
* Fills the unit with the given block.
|
||||
*
|
||||
* @param block the block to fill
|
||||
*/
|
||||
void fill(@NotNull Block block);
|
||||
|
||||
/**
|
||||
* Fills the 3d rectangular area with the given block.
|
||||
*
|
||||
* @param start the start (min) point of the area
|
||||
* @param end the end (max) point of the area
|
||||
* @param block the block to fill
|
||||
*/
|
||||
void fill(@NotNull Point start, @NotNull Point end, @NotNull Block block);
|
||||
|
||||
/**
|
||||
* Fills the 3d rectangular area with the given biome.
|
||||
*
|
||||
* @param minHeight the minimum height of the area
|
||||
* @param maxHeight the maximum height of the area
|
||||
* @param block the block to fill
|
||||
*/
|
||||
void fillHeight(int minHeight, int maxHeight, @NotNull Block block);
|
||||
|
||||
/**
|
||||
* Fills the 3d rectangular area with the given biome.
|
||||
*
|
||||
* @param biome the biome to fill
|
||||
*/
|
||||
void fillBiome(@NotNull Biome biome);
|
||||
|
||||
interface Supplier {
|
||||
@NotNull Block get(int x, int y, int z);
|
||||
}
|
||||
}
|
@ -262,4 +262,12 @@ public final class ChunkUtils {
|
||||
public static int toSectionRelativeCoordinate(int xyz) {
|
||||
return xyz & 0xF;
|
||||
}
|
||||
|
||||
public static int floorSection(int coordinate) {
|
||||
return coordinate - (coordinate & 0xF);
|
||||
}
|
||||
|
||||
public static int ceilSection(int coordinate) {
|
||||
return ((coordinate - 1) | 15) + 1;
|
||||
}
|
||||
}
|
||||
|
@ -5,16 +5,11 @@ import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.Event;
|
||||
import net.minestom.server.event.EventFilter;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.ChunkGenerator;
|
||||
import net.minestom.server.instance.ChunkPopulator;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public interface Env {
|
||||
@ -49,22 +44,7 @@ public interface Env {
|
||||
|
||||
default @NotNull Instance createFlatInstance() {
|
||||
var instance = process().instance().createInstanceContainer();
|
||||
instance.setChunkGenerator(new ChunkGenerator() {
|
||||
@Override
|
||||
public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) {
|
||||
for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++)
|
||||
for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||
for (byte y = 0; y < 40; y++) {
|
||||
batch.setBlock(x, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChunkPopulator> getPopulators() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
instance.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public class CoordinateTest {
|
||||
|
||||
@Test
|
||||
public void chunkCoordinate() {
|
||||
assertEquals(0, getChunkCoordinate(15));
|
||||
assertEquals(1, getChunkCoordinate(16));
|
||||
assertEquals(-1, getChunkCoordinate(-16));
|
||||
assertEquals(3, getChunkCoordinate(48));
|
||||
|
@ -0,0 +1,162 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.api.Env;
|
||||
import net.minestom.server.api.EnvTest;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@EnvTest
|
||||
public class GeneratorForkConsumerIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void local(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.fork(setter -> {
|
||||
var dynamic = (GeneratorImpl.DynamicFork) setter;
|
||||
assertNull(dynamic.minSection);
|
||||
assertEquals(0, dynamic.width);
|
||||
assertEquals(0, dynamic.height);
|
||||
assertEquals(0, dynamic.depth);
|
||||
setter.setBlock(unit.absoluteStart(), Block.STONE);
|
||||
assertEquals(unit.absoluteStart(), dynamic.minSection);
|
||||
assertEquals(1, dynamic.width);
|
||||
assertEquals(1, dynamic.height);
|
||||
assertEquals(1, dynamic.depth);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(0, -64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleLocal(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.fork(setter -> {
|
||||
setter.setBlock(unit.absoluteStart(), Block.STONE);
|
||||
setter.setBlock(unit.absoluteStart().add(1, 0, 0), Block.STONE);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(0, -64, 0));
|
||||
assertEquals(Block.STONE, instance.getBlock(1, -64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neighborZ(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.fork(setter -> {
|
||||
var dynamic = (GeneratorImpl.DynamicFork) setter;
|
||||
assertNull(dynamic.minSection);
|
||||
assertEquals(0, dynamic.width);
|
||||
assertEquals(0, dynamic.height);
|
||||
assertEquals(0, dynamic.depth);
|
||||
setter.setBlock(unit.absoluteStart(), Block.STONE);
|
||||
setter.setBlock(unit.absoluteStart().add(0, 0, 16), Block.GRASS);
|
||||
assertEquals(unit.absoluteStart(), dynamic.minSection);
|
||||
assertEquals(1, dynamic.width);
|
||||
assertEquals(1, dynamic.height);
|
||||
assertEquals(2, dynamic.depth);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(null);
|
||||
instance.loadChunk(0, 1).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(0, -64, 0));
|
||||
assertEquals(Block.GRASS, instance.getBlock(0, -64, 16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neighborX(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.fork(setter -> {
|
||||
var dynamic = (GeneratorImpl.DynamicFork) setter;
|
||||
assertNull(dynamic.minSection);
|
||||
assertEquals(0, dynamic.width);
|
||||
assertEquals(0, dynamic.height);
|
||||
assertEquals(0, dynamic.depth);
|
||||
setter.setBlock(unit.absoluteStart(), Block.STONE);
|
||||
setter.setBlock(unit.absoluteStart().add(16, 0, 0), Block.GRASS);
|
||||
assertEquals(unit.absoluteStart(), dynamic.minSection);
|
||||
assertEquals(2, dynamic.width);
|
||||
assertEquals(1, dynamic.height);
|
||||
assertEquals(1, dynamic.depth);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(null);
|
||||
instance.loadChunk(1, 0).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(0, -64, 0));
|
||||
assertEquals(Block.GRASS, instance.getBlock(16, -64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neighborY(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.fork(setter -> {
|
||||
var dynamic = (GeneratorImpl.DynamicFork) setter;
|
||||
assertNull(dynamic.minSection);
|
||||
assertEquals(0, dynamic.width);
|
||||
assertEquals(0, dynamic.height);
|
||||
assertEquals(0, dynamic.depth);
|
||||
setter.setBlock(unit.absoluteStart(), Block.STONE);
|
||||
setter.setBlock(unit.absoluteStart().add(0, 16, 0), Block.GRASS);
|
||||
assertEquals(unit.absoluteStart(), dynamic.minSection);
|
||||
assertEquals(1, dynamic.width);
|
||||
assertEquals(2, dynamic.height);
|
||||
assertEquals(1, dynamic.depth);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(0, -64, 0));
|
||||
assertEquals(Block.GRASS, instance.getBlock(0, -48, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalAndHorizontalSectionBorders(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
Set<Point> points = ConcurrentHashMap.newKeySet();
|
||||
instance.setGenerator(unit -> {
|
||||
final Point start = unit.absoluteStart().withY(96);
|
||||
unit.fork(setter -> {
|
||||
var dynamic = (GeneratorImpl.DynamicFork) setter;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
setter.setBlock(start.add(i, 0, 0), Block.STONE);
|
||||
setter.setBlock(start.add(-i, 0, 0), Block.STONE);
|
||||
setter.setBlock(start.add(0, i, 0), Block.STONE);
|
||||
setter.setBlock(start.add(0, -i, 0), Block.STONE);
|
||||
|
||||
points.add(start.add(i, 0, 0));
|
||||
points.add(start.add(-i, 0, 0));
|
||||
points.add(start.add(0, i, 0));
|
||||
points.add(start.add(0, -i, 0));
|
||||
}
|
||||
assertEquals(2, dynamic.width);
|
||||
assertEquals(2, dynamic.height);
|
||||
assertEquals(1, dynamic.depth);
|
||||
});
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
for (Point point : points) {
|
||||
if (!instance.isChunkLoaded(point)) continue;
|
||||
assertEquals(Block.STONE, instance.getBlock(point), point.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.api.Env;
|
||||
import net.minestom.server.api.EnvTest;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnvTest
|
||||
public class GeneratorForkIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void local(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
var block = Block.STONE;
|
||||
instance.setGenerator(unit -> {
|
||||
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd());
|
||||
assertEquals(unit.absoluteStart(), u.absoluteStart());
|
||||
assertEquals(unit.absoluteEnd(), u.absoluteEnd());
|
||||
u.modifier().setRelative(0, 0, 0, Block.STONE);
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(block, instance.getBlock(0, -64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void size(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
// Set the Generator
|
||||
instance.setGenerator(unit -> {
|
||||
Point start = unit.absoluteStart();
|
||||
GenerationUnit fork = unit.fork(start, start.add(18, 18, 18));
|
||||
assertDoesNotThrow(() -> fork.modifier().setBlock(start.add(17, 17, 17), Block.STONE));
|
||||
});
|
||||
// Load the chunks
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(null);
|
||||
instance.loadChunk(1, 1).join();
|
||||
assertEquals(Block.STONE, instance.getBlock(17, -64 + 17, 17));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signal(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
var block = Block.STONE;
|
||||
instance.setGenerator(unit -> {
|
||||
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd().add(16, 0, 16));
|
||||
assertEquals(unit.absoluteStart(), u.absoluteStart());
|
||||
assertEquals(unit.absoluteEnd().add(16, 0, 16), u.absoluteEnd());
|
||||
u.modifier().setRelative(16, 0, 0, Block.STONE);
|
||||
u.modifier().setRelative(16, 33, 0, Block.STONE);
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(null);
|
||||
instance.loadChunk(1, 0).join();
|
||||
assertEquals(block, instance.getBlock(16, -64, 0));
|
||||
assertEquals(block, instance.getBlock(16, -31, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void air(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd().add(16, 0, 16));
|
||||
u.modifier().setRelative(16, 39 + 64, 0, Block.AIR);
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(unit -> unit.modifier().fillHeight(0, 40, Block.STONE));
|
||||
instance.loadChunk(1, 0).join();
|
||||
assertEquals(Block.AIR, instance.getBlock(16, 39, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fillHeight(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd().add(16, 0, 16));
|
||||
u.modifier().fillHeight(0, 40, Block.STONE);
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
instance.setGenerator(null);
|
||||
instance.loadChunk(1, 0).join();
|
||||
for (int y = 0; y < 40; y++) {
|
||||
assertEquals(Block.STONE, instance.getBlock(16, y, 0), "y=" + y);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biome(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
var u = unit.fork(unit.absoluteStart(), unit.absoluteEnd().add(16, 0, 16));
|
||||
assertThrows(IllegalStateException.class, () -> u.modifier().setBiome(16, 0, 0, Biome.PLAINS));
|
||||
assertThrows(IllegalStateException.class, () -> u.modifier().fillBiome(Biome.PLAINS));
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.api.Env;
|
||||
import net.minestom.server.api.EnvTest;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
@EnvTest
|
||||
public class GeneratorIntegrationTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
public void loader(boolean data, Env env) {
|
||||
var manager = env.process().instance();
|
||||
var block = data ? Block.STONE.withNbt(NBT.Compound(Map.of("key", NBT.String("value")))) : Block.STONE;
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> unit.modifier().fill(block));
|
||||
instance.loadChunk(0, 0).join();
|
||||
assertEquals(block, instance.getBlock(0, 0, 0));
|
||||
assertEquals(block, instance.getBlock(15, 0, 0));
|
||||
assertEquals(block, instance.getBlock(0, 15, 0));
|
||||
assertEquals(block, instance.getBlock(0, 0, 15));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exceptionCatch(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
|
||||
var ref = new AtomicReference<Throwable>();
|
||||
env.process().exception().setExceptionHandler(ref::set);
|
||||
|
||||
var exception = new RuntimeException();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.modifier().fill(Block.STONE);
|
||||
throw exception;
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
assertSame(exception, ref.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fillHeightNegative(Env env) {
|
||||
var manager = env.process().instance();
|
||||
var instance = manager.createInstanceContainer();
|
||||
instance.setGenerator(unit -> {
|
||||
unit.modifier().fillHeight(-64, -60, Block.STONE);
|
||||
});
|
||||
instance.loadChunk(0, 0).join();
|
||||
for (int y = -64; y < -60; y++) {
|
||||
assertEquals(Block.STONE, instance.getBlock(0, y, 0));
|
||||
}
|
||||
for (int y = -60; y < 100; y++) {
|
||||
assertEquals(Block.AIR, instance.getBlock(0, y, 0));
|
||||
}
|
||||
}
|
||||
}
|
361
src/test/java/net/minestom/server/instance/GeneratorTest.java
Normal file
361
src/test/java/net/minestom/server/instance/GeneratorTest.java
Normal file
@ -0,0 +1,361 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.generator.GenerationUnit;
|
||||
import net.minestom.server.instance.generator.Generator;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minestom.server.instance.GeneratorImpl.unit;
|
||||
import static net.minestom.server.utils.chunk.ChunkUtils.ceilSection;
|
||||
import static net.minestom.server.utils.chunk.ChunkUtils.floorSection;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class GeneratorTest {
|
||||
|
||||
@Test
|
||||
public void unitSize() {
|
||||
assertDoesNotThrow(() -> dummyUnit(Vec.ZERO, new Vec(16)));
|
||||
assertDoesNotThrow(() -> dummyUnit(new Vec(16), new Vec(32)));
|
||||
assertThrows(IllegalArgumentException.class, () -> dummyUnit(new Vec(15), Vec.ZERO));
|
||||
assertThrows(IllegalArgumentException.class, () -> dummyUnit(new Vec(15), new Vec(32)));
|
||||
assertThrows(IllegalArgumentException.class, () -> dummyUnit(new Vec(15), new Vec(31)));
|
||||
assertThrows(IllegalArgumentException.class, () -> dummyUnit(Vec.ZERO, new Vec(15)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("sectionFloorParam")
|
||||
public void sectionFloor(int expected, int input) {
|
||||
assertEquals(expected, floorSection(input), "floorSection(" + input + ")");
|
||||
}
|
||||
|
||||
private static Stream<Arguments> sectionFloorParam() {
|
||||
return Stream.of(Arguments.of(-32, -32),
|
||||
Arguments.of(-32, -31),
|
||||
Arguments.of(-32, -17),
|
||||
Arguments.of(-16, -16),
|
||||
Arguments.of(-16, -15),
|
||||
Arguments.of(0, 0),
|
||||
Arguments.of(0, 1),
|
||||
Arguments.of(0, 2),
|
||||
Arguments.of(16, 16),
|
||||
Arguments.of(16, 17));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("sectionCeilParam")
|
||||
public void sectionCeil(int expected, int input) {
|
||||
assertEquals(expected, ceilSection(input), "ceilSection(" + input + ")");
|
||||
}
|
||||
|
||||
private static Stream<Arguments> sectionCeilParam() {
|
||||
return Stream.of(Arguments.of(-32, -32),
|
||||
Arguments.of(-16, -31),
|
||||
Arguments.of(-16, -17),
|
||||
Arguments.of(-16, -16),
|
||||
Arguments.of(-0, -15),
|
||||
Arguments.of(0, 0),
|
||||
Arguments.of(16, 1),
|
||||
Arguments.of(16, 2),
|
||||
Arguments.of(16, 16),
|
||||
Arguments.of(32, 17));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkSize() {
|
||||
final int minSection = 0;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
GenerationUnit chunk = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
assertEquals(new Vec(16, sectionCount * 16, 16), chunk.size());
|
||||
assertEquals(new Vec(chunkX * 16, minSection * 16, chunkZ * 16), chunk.absoluteStart());
|
||||
assertEquals(new Vec(chunkX * 16 + 16, maxSection * 16, chunkZ * 16 + 16), chunk.absoluteEnd());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkSizeNeg() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
GenerationUnit chunk = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
assertEquals(new Vec(16, sectionCount * 16, 16), chunk.size());
|
||||
assertEquals(new Vec(chunkX * 16, minSection * 16, chunkZ * 16), chunk.absoluteStart());
|
||||
assertEquals(new Vec(chunkX * 16 + 16, maxSection * 16, chunkZ * 16 + 16), chunk.absoluteEnd());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sectionSize() {
|
||||
final int sectionX = 3;
|
||||
final int sectionY = -5;
|
||||
final int sectionZ = -2;
|
||||
GenerationUnit section = GeneratorImpl.section(new Section(), sectionX, sectionY, sectionZ);
|
||||
assertEquals(new Vec(16), section.size());
|
||||
assertEquals(new Vec(sectionX * 16, sectionY * 16, sectionZ * 16), section.absoluteStart());
|
||||
assertEquals(new Vec(sectionX * 16 + 16, sectionY * 16 + 16, sectionZ * 16 + 16), section.absoluteEnd());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkSubdivide() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
GenerationUnit chunk = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
var subUnits = chunk.subdivide();
|
||||
assertEquals(sectionCount, subUnits.size());
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
var subUnit = subUnits.get(i);
|
||||
assertEquals(new Vec(16, 16, 16), subUnit.size());
|
||||
assertEquals(new Vec(chunkX * 16, (i + minSection) * 16, chunkZ * 16), subUnit.absoluteStart());
|
||||
assertEquals(subUnit.absoluteStart().add(16), subUnit.absoluteEnd());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkAbsolute() {
|
||||
final int minSection = 0;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
assertThrows(Exception.class, () -> modifier.setBlock(0, 0, 0, Block.STONE), "Block outside of chunk");
|
||||
modifier.setBlock(56, 0, -25, Block.STONE);
|
||||
modifier.setBlock(56, 17, -25, Block.STONE);
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
assertEquals(Block.STONE.stateId(), sections[0].blockPalette().get(8, 0, 7));
|
||||
assertEquals(Block.STONE.stateId(), sections[1].blockPalette().get(8, 1, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkAbsoluteAll() {
|
||||
final int minSection = 0;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
Set<Point> points = new HashSet<>();
|
||||
modifier.setAll((x, y, z) -> {
|
||||
assertTrue(points.add(new Vec(x, y, z)), "Duplicate point: " + x + ", " + y + ", " + z);
|
||||
assertEquals(chunkX, ChunkUtils.getChunkCoordinate(x));
|
||||
assertEquals(chunkZ, ChunkUtils.getChunkCoordinate(z));
|
||||
return Block.STONE;
|
||||
});
|
||||
assertEquals(16 * 16 * 16 * sectionCount, points.size());
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
for (var section : sections) {
|
||||
section.blockPalette().getAll((x, y, z, value) ->
|
||||
assertEquals(Block.STONE.stateId(), value));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkRelative() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
assertThrows(Exception.class, () -> modifier.setRelative(-1, 0, 0, Block.STONE));
|
||||
assertThrows(Exception.class, () -> modifier.setRelative(16, 0, 0, Block.STONE));
|
||||
assertThrows(Exception.class, () -> modifier.setRelative(17, 0, 0, Block.STONE));
|
||||
assertThrows(Exception.class, () -> modifier.setRelative(0, -1, 0, Block.STONE));
|
||||
assertThrows(Exception.class, () -> modifier.setRelative(0, 96, 0, Block.STONE));
|
||||
modifier.setRelative(0, 0, 0, Block.STONE);
|
||||
modifier.setRelative(0, 16, 2, Block.STONE);
|
||||
modifier.setRelative(5, 33, 5, Block.STONE);
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
assertEquals(Block.STONE.stateId(), sections[0].blockPalette().get(0, 0, 0));
|
||||
assertEquals(Block.STONE.stateId(), sections[1].blockPalette().get(0, 0, 2));
|
||||
assertEquals(Block.STONE.stateId(), sections[2].blockPalette().get(5, 1, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkRelativeAll() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
Set<Point> points = new HashSet<>();
|
||||
modifier.setAllRelative((x, y, z) -> {
|
||||
assertTrue(MathUtils.isBetween(x, 0, 16), "x out of bounds: " + x);
|
||||
assertTrue(MathUtils.isBetween(y, 0, sectionCount * 16), "y out of bounds: " + y);
|
||||
assertTrue(MathUtils.isBetween(z, 0, 16), "z out of bounds: " + z);
|
||||
assertTrue(points.add(new Vec(x, y, z)), "Duplicate point: " + x + ", " + y + ", " + z);
|
||||
return Block.STONE;
|
||||
});
|
||||
assertEquals(16 * 16 * 16 * sectionCount, points.size());
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
for (var section : sections) {
|
||||
section.blockPalette().getAll((x, y, z, value) ->
|
||||
assertEquals(Block.STONE.stateId(), value));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkBiomeSet() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
modifier.setBiome(48, 0, -32, Biome.PLAINS);
|
||||
modifier.setBiome(48 + 8, 0, -32, Biome.PLAINS);
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
assertEquals(Biome.PLAINS.id(), sections[0].biomePalette().get(0, 0, 0));
|
||||
assertEquals(0, sections[0].biomePalette().get(1, 0, 0));
|
||||
assertEquals(Biome.PLAINS.id(), sections[0].biomePalette().get(2, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkBiomeFill() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int chunkX = 3;
|
||||
final int chunkZ = -2;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), chunkX, chunkZ);
|
||||
Generator generator = chunk -> {
|
||||
var modifier = chunk.modifier();
|
||||
modifier.fillBiome(Biome.PLAINS);
|
||||
};
|
||||
generator.generate(chunkUnits);
|
||||
for (var section : sections) {
|
||||
section.biomePalette().getAll((x, y, z, value) ->
|
||||
assertEquals(Biome.PLAINS.id(), value));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkFillHeightExact() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), 3, -2);
|
||||
Generator generator = chunk -> chunk.modifier().fillHeight(0, 32, Block.STONE);
|
||||
generator.generate(chunkUnits);
|
||||
|
||||
AtomicInteger index = new AtomicInteger(minSection);
|
||||
for (var section : sections) {
|
||||
section.blockPalette().getAll((x, y, z, value) -> {
|
||||
if (index.get() == 0 || index.get() == 1) {
|
||||
assertEquals(Block.STONE.stateId(), value, "filling failed for section " + index.get());
|
||||
} else {
|
||||
assertEquals(0, value);
|
||||
}
|
||||
});
|
||||
index.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkFillHeightOneOff() {
|
||||
final int minSection = -1;
|
||||
final int maxSection = 5;
|
||||
final int sectionCount = maxSection - minSection;
|
||||
Section[] sections = new Section[sectionCount];
|
||||
Arrays.setAll(sections, i -> new Section());
|
||||
var chunkUnits = GeneratorImpl.chunk(minSection, maxSection, List.of(sections), 3, -2);
|
||||
Generator generator = chunk -> chunk.modifier().fillHeight(1, 33, Block.STONE);
|
||||
generator.generate(chunkUnits);
|
||||
|
||||
AtomicInteger index = new AtomicInteger(minSection);
|
||||
for (var section : sections) {
|
||||
section.blockPalette().getAll((x, y, z, value) -> {
|
||||
Block expected;
|
||||
if (index.get() == 0) {
|
||||
if (y > 0) {
|
||||
expected = Block.STONE;
|
||||
} else {
|
||||
expected = Block.AIR;
|
||||
}
|
||||
} else if (index.get() == 1) {
|
||||
expected = Block.STONE;
|
||||
} else if (index.get() == 2) {
|
||||
if (y == 0) {
|
||||
expected = Block.STONE;
|
||||
} else {
|
||||
expected = Block.AIR;
|
||||
}
|
||||
} else {
|
||||
expected = Block.AIR;
|
||||
}
|
||||
assertEquals(expected.stateId(), value, "fail for coordinate: " + x + "," + y + "," + z + " for index " + index.get());
|
||||
});
|
||||
index.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sectionFill() {
|
||||
Section section = new Section();
|
||||
var chunkUnit = GeneratorImpl.section(section, -1, -1, 0);
|
||||
Generator generator = chunk -> chunk.modifier().fill(Block.STONE);
|
||||
generator.generate(chunkUnit);
|
||||
section.blockPalette().getAll((x, y, z, value) ->
|
||||
assertEquals(Block.STONE.stateId(), value));
|
||||
}
|
||||
|
||||
static GenerationUnit dummyUnit(Point start, Point end) {
|
||||
return unit(null, start, end, null);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user