Мир стал островом: 4000 на 4000, море, дороги, барьер
- WorldGenerator: круглый остров в середине квадрата, море вокруг (уровень 62), песок у берега, дороги асфальтом. Остров и кольцо дорог считаются от начала координат, а не от середины мира. - Спавн: если середина острова под водой, игрок появляется на ближайшей суше рядом с ней. - Барьер: сервер не пускает игрока за край мира. - Мир стал конечным: размер задаётся RoadNetwork, генератор берёт его оттуда. Тесты мира и перезапуска переписаны под остров. Проверок в сервере 92.
This commit is contained in:
@@ -927,6 +927,12 @@ public final class ClientSession implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
// The edge of the world is the edge of the world: the server does not
|
||||
// let a player walk over it.
|
||||
if (!this.server.world().inside((int) Math.floor(move.x), (int) Math.floor(move.z))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.player.moveTo(move.x, move.y, move.z, move.yaw, move.pitch,
|
||||
move.onGround)) {
|
||||
// the player crossed into another chunk, so there is more world to
|
||||
|
||||
@@ -35,6 +35,11 @@ public final class RoadNetwork {
|
||||
this.ringRadius = Math.max(128, size / 3);
|
||||
}
|
||||
|
||||
/** The side of the world the roads run in, in blocks. */
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/** How far apart the straight roads are. */
|
||||
public int spacing() {
|
||||
return this.spacing;
|
||||
@@ -45,9 +50,13 @@ public final class RoadNetwork {
|
||||
return this.ringRadius;
|
||||
}
|
||||
|
||||
/** The middle of the world. */
|
||||
/**
|
||||
* The middle of the world.
|
||||
*
|
||||
* The world is centred on the origin.
|
||||
*/
|
||||
public int center() {
|
||||
return this.size / 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The middle of the straight road that is nearest to the column. */
|
||||
|
||||
@@ -160,11 +160,42 @@ public final class World {
|
||||
* point every world has and the spawn has to be somewhere the server can
|
||||
* name before a client has said where it wants to be.
|
||||
*/
|
||||
/**
|
||||
* Where a player appears.
|
||||
*
|
||||
* The middle of the island, and if that happens to be under the sea, the
|
||||
* nearest dry piece of land around it. Nobody likes to start in the water.
|
||||
*/
|
||||
public double[] spawn() {
|
||||
int height = this.generator.heightAt(0, 0);
|
||||
if (height >= this.generator.SEA_LEVEL) {
|
||||
return new double[]{0.5D, height + 1.0D, 0.5D};
|
||||
}
|
||||
for (int radius = 8; radius <= 512; radius += 8) {
|
||||
for (int step = 0; step < 16; step++) {
|
||||
double angle = step * Math.PI * 2.0D / 16.0D;
|
||||
int x = (int) Math.round(Math.cos(angle) * radius);
|
||||
int z = (int) Math.round(Math.sin(angle) * radius);
|
||||
int candidate = this.generator.heightAt(x, z);
|
||||
if (candidate >= this.generator.SEA_LEVEL) {
|
||||
return new double[]{x + 0.5D, candidate + 1.0D, z + 0.5D};
|
||||
}
|
||||
}
|
||||
}
|
||||
return new double[]{0.5D, height + 1.0D, 0.5D};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a place is inside the world.
|
||||
*
|
||||
* The world is a square with the island in the middle, and the sea around
|
||||
* it. Past the shore is the edge of the world, and the server does not
|
||||
* let a player go over it.
|
||||
*/
|
||||
public boolean inside(int blockX, int blockZ) {
|
||||
return this.generator.insideWorld(blockX, blockZ);
|
||||
}
|
||||
|
||||
/** True when this world is written to disk. */
|
||||
public boolean persistent() {
|
||||
return this.storage.persistent();
|
||||
|
||||
@@ -15,28 +15,67 @@ import net.rustnotmc.protocol.ChunkFormat;
|
||||
* So no shared state, no {@link java.util.Random} carried between calls, and no
|
||||
* dependence on the order chunks are requested in. Every value is a pure
|
||||
* function of the seed and the coordinates.
|
||||
*
|
||||
* The shape of the world: a square of four thousand blocks with a round island
|
||||
* in the middle of it and the sea around it. The roads of the road network run
|
||||
* over it.
|
||||
*/
|
||||
public final class WorldGenerator {
|
||||
|
||||
/** The side of the world, in blocks. */
|
||||
public static final int DEFAULT_SIZE = 4000;
|
||||
|
||||
/** The level of the sea. Everything below it is under water. */
|
||||
public static final int SEA_LEVEL = 62;
|
||||
|
||||
/** How far the island reaches from the middle, in blocks. */
|
||||
public static final int DEFAULT_ISLAND_RADIUS = 1400;
|
||||
|
||||
/** How deep the sea is at its deepest. */
|
||||
public static final int SEA_DEPTH = 12;
|
||||
|
||||
private final long seed;
|
||||
private final int size;
|
||||
private final int islandRadius;
|
||||
private final RoadNetwork roads;
|
||||
|
||||
public WorldGenerator(long seed) {
|
||||
this(seed, null);
|
||||
this(seed, new RoadNetwork(DEFAULT_SIZE));
|
||||
}
|
||||
|
||||
public WorldGenerator(long seed, RoadNetwork roads) {
|
||||
this(seed, roads, DEFAULT_ISLAND_RADIUS);
|
||||
}
|
||||
|
||||
public WorldGenerator(long seed, RoadNetwork roads, int islandRadius) {
|
||||
this.seed = seed;
|
||||
this.roads = roads;
|
||||
this.size = roads.size();
|
||||
this.islandRadius = islandRadius;
|
||||
}
|
||||
|
||||
public long seed() {
|
||||
return this.seed;
|
||||
}
|
||||
|
||||
/** The roads of this world, or null when it has none. */
|
||||
public RoadNetwork roads() {
|
||||
return this.roads;
|
||||
/** The side of the world, in blocks. */
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/** How far the island reaches from the middle, in blocks. */
|
||||
public int islandRadius() {
|
||||
return this.islandRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* The middle of the world.
|
||||
*
|
||||
* The world is centred on the origin, so the middle is the origin and
|
||||
* the island stands around it.
|
||||
*/
|
||||
public int center() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,45 +137,32 @@ public final class WorldGenerator {
|
||||
return norm == 0.0D ? 0.0D : sum / norm;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much of the land there is at a column.
|
||||
*
|
||||
* One in the middle of the island and zero out at the shore, with a smooth
|
||||
* edge in between, so the coast is a beach and not a cliff.
|
||||
*/
|
||||
public double islandFalloff(int blockX, int blockZ) {
|
||||
double dx = blockX - this.center();
|
||||
double dz = blockZ - this.center();
|
||||
double distance = Math.sqrt(dx * dx + dz * dz) / this.islandRadius;
|
||||
if (distance >= 1.0D) {
|
||||
return 0.0D;
|
||||
}
|
||||
double falloff = 1.0D - distance * distance;
|
||||
// the very edge of the island is flat, so the shore is a beach
|
||||
return falloff * falloff * (3.0D - 2.0D * falloff);
|
||||
}
|
||||
|
||||
/** The height of solid ground at a block column. */
|
||||
public int heightAt(int blockX, int blockZ) {
|
||||
if (this.roads != null) {
|
||||
int roadHeight = roadHeightAt(blockX, blockZ);
|
||||
if (roadHeight >= 0) {
|
||||
return roadHeight;
|
||||
}
|
||||
}
|
||||
return terrainHeightAt(blockX, blockZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* The height of a road at a column, or -1 when the column is not on one.
|
||||
*
|
||||
* A road is level along its length: the ground under it is the ground of
|
||||
* the middle of the road, so that a road does not roll up and down with
|
||||
* every bump of the land.
|
||||
*/
|
||||
private int roadHeightAt(int blockX, int blockZ) {
|
||||
int middleX = this.roads.centerLine(blockX);
|
||||
int middleZ = this.roads.centerLine(blockZ);
|
||||
if (Math.abs(blockX - middleX) <= RoadNetwork.HALF_WIDTH) {
|
||||
return terrainHeightAt(middleX, blockZ);
|
||||
}
|
||||
if (Math.abs(blockZ - middleZ) <= RoadNetwork.HALF_WIDTH) {
|
||||
return terrainHeightAt(blockX, middleZ);
|
||||
}
|
||||
int[] ring = this.roads.onRing(blockX, blockZ);
|
||||
if (ring != null) {
|
||||
return terrainHeightAt(ring[0], ring[1]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** The height of the land itself, with no road on it. */
|
||||
private int terrainHeightAt(int blockX, int blockZ) {
|
||||
double base = fBm(blockX / 160.0D, blockZ / 160.0D, 4);
|
||||
double detail = fBm(blockX / 32.0D, blockZ / 32.0D, 2) * 0.35D;
|
||||
int h = (int) Math.round(62.0D + base * 26.0D + detail * 6.0D);
|
||||
double land = 62.0D + base * 26.0D + detail * 6.0D;
|
||||
double falloff = islandFalloff(blockX, blockZ);
|
||||
double height = (SEA_LEVEL - SEA_DEPTH) + falloff * (land - (SEA_LEVEL - SEA_DEPTH));
|
||||
int h = (int) Math.round(height);
|
||||
if (h < 2) {
|
||||
h = 2;
|
||||
}
|
||||
@@ -146,6 +172,11 @@ public final class WorldGenerator {
|
||||
return h;
|
||||
}
|
||||
|
||||
/** True when the column is under the sea. */
|
||||
public boolean underWater(int blockX, int blockZ) {
|
||||
return heightAt(blockX, blockZ) < SEA_LEVEL;
|
||||
}
|
||||
|
||||
/** Fills one chunk. Pure: same seed and position, same bytes out. */
|
||||
public void generate(Chunk chunk) {
|
||||
ChunkPos pos = chunk.pos();
|
||||
@@ -161,7 +192,8 @@ public final class WorldGenerator {
|
||||
int worldX = baseX + x;
|
||||
int worldZ = baseZ + z;
|
||||
int height = heightAt(worldX, worldZ);
|
||||
boolean road = this.roads != null && this.roads.onRoad(worldX, worldZ);
|
||||
boolean road = isRoad(worldX, worldZ);
|
||||
boolean beach = height <= SEA_LEVEL + 1;
|
||||
|
||||
// a column: dirt under the surface, grass on top, stone below
|
||||
for (int y = 0; y <= height; y++) {
|
||||
@@ -169,7 +201,13 @@ public final class WorldGenerator {
|
||||
if (y == 0) {
|
||||
id = Blocks.STONE;
|
||||
} else if (y == height) {
|
||||
id = road ? Blocks.ASPHALT : Blocks.GRASS;
|
||||
if (road) {
|
||||
id = Blocks.ASPHALT;
|
||||
} else if (beach) {
|
||||
id = Blocks.SAND;
|
||||
} else {
|
||||
id = Blocks.GRASS;
|
||||
}
|
||||
} else if (y > height - 4) {
|
||||
id = Blocks.DIRT;
|
||||
} else {
|
||||
@@ -177,8 +215,17 @@ public final class WorldGenerator {
|
||||
}
|
||||
blocks[ChunkFormat.index(x, y, z)] = id;
|
||||
}
|
||||
for (int y = height + 1; y < ChunkFormat.HEIGHT; y++) {
|
||||
blocks[ChunkFormat.index(x, y, z)] = Blocks.AIR;
|
||||
// and the sea on top of the ground that is below it
|
||||
for (int y = height + 1; y <= SEA_LEVEL; y++) {
|
||||
if (y > 0 && y < Chunk.HEIGHT) {
|
||||
blocks[ChunkFormat.index(x, y, z)] = Blocks.WATER;
|
||||
}
|
||||
}
|
||||
for (int y = height + 1; y < Chunk.HEIGHT; y++) {
|
||||
if (blocks[ChunkFormat.index(x, y, z)] == Blocks.AIR) {
|
||||
// the air above the water stays air
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,12 +234,24 @@ public final class WorldGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a block is within reach of the given column.
|
||||
* Whether a column is on one of the roads.
|
||||
*
|
||||
* A generator that produces something tall, such as a tree, has to be able
|
||||
* to say so, or a neighbour generation would cut through it.
|
||||
* The roads are the grid of the road network and the ring around the
|
||||
* island.
|
||||
*/
|
||||
public boolean reachesIntoNeighbour(int blockX, int blockZ) {
|
||||
return false;
|
||||
public boolean isRoad(int blockX, int blockZ) {
|
||||
RoadNetwork roads = new RoadNetwork(this.size);
|
||||
return roads.onRoad(blockX, blockZ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a block may be worn or taken off.
|
||||
*
|
||||
* The world is a square with the island in the middle, and the sea around
|
||||
* it. Everything past the shore is the edge of the world.
|
||||
*/
|
||||
public boolean insideWorld(int blockX, int blockZ) {
|
||||
int half = this.size / 2;
|
||||
return blockX >= -half && blockX <= half && blockZ >= -half && blockZ <= half;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,14 @@ public final class PlayTest {
|
||||
}
|
||||
|
||||
int height = server.world().generator().heightAt(0, 0);
|
||||
eq("the top of the ground is grass", Byte.valueOf(Blocks.GRASS),
|
||||
byte top = server.world().generator().isRoad(0, 0) ? Blocks.ASPHALT
|
||||
: (server.world().generator().underWater(0, 0) ? Blocks.SAND
|
||||
: Blocks.GRASS);
|
||||
eq("the top of the ground is the ground", Byte.valueOf(top),
|
||||
Byte.valueOf(home.blocks[ChunkFormat.index(0, height, 0)]));
|
||||
eq("the sky above it is air", Byte.valueOf(Blocks.AIR),
|
||||
byte sky = height < net.rustnotmc.server.world.WorldGenerator.SEA_LEVEL
|
||||
? Blocks.WATER : Blocks.AIR;
|
||||
eq("the sky above it is air or water", Byte.valueOf(sky),
|
||||
Byte.valueOf(home.blocks[ChunkFormat.index(0, height + 1, 0)]));
|
||||
eq("the ground below it is dirt", Byte.valueOf(Blocks.DIRT),
|
||||
Byte.valueOf(home.blocks[ChunkFormat.index(0, height - 1, 0)]));
|
||||
@@ -237,7 +242,7 @@ public final class PlayTest {
|
||||
Byte.valueOf(home.blocks[ChunkFormat.index(0, 0, 0)]));
|
||||
|
||||
// and it is the same terrain the server has
|
||||
eq("the server agrees", Byte.valueOf(Blocks.GRASS),
|
||||
eq("the server agrees", Byte.valueOf(top),
|
||||
Byte.valueOf(server.world().blockAt(0, height, 0)));
|
||||
client.close();
|
||||
} finally {
|
||||
@@ -256,13 +261,15 @@ public final class PlayTest {
|
||||
|
||||
double[] spawn = server.world().spawn();
|
||||
int under = (int) spawn[1] - 1;
|
||||
int spawnX = (int) spawn[0];
|
||||
int spawnZ = (int) spawn[2];
|
||||
byte before = server.world().blockAt(0, under, 0);
|
||||
yes("there is something to break", before != Blocks.AIR);
|
||||
|
||||
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(0, under, 0, Blocks.AIR));
|
||||
waitFor(() -> blockAt(server.world(), 0, under, 0) == Blocks.AIR, 3000L);
|
||||
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(spawnX, under, spawnZ, Blocks.AIR));
|
||||
waitFor(() -> blockAt(server.world(), spawnX, under, spawnZ) == Blocks.AIR, 3000L);
|
||||
eq("the block is gone", Byte.valueOf(Blocks.AIR),
|
||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
||||
Byte.valueOf(server.world().blockAt(spawnX, under, spawnZ)));
|
||||
client.close();
|
||||
} finally {
|
||||
server.stop();
|
||||
@@ -289,21 +296,23 @@ public final class PlayTest {
|
||||
|
||||
double[] spawn = server.world().spawn();
|
||||
int under = (int) spawn[1] - 1;
|
||||
int spawnX = (int) spawn[0];
|
||||
int spawnZ = (int) spawn[2];
|
||||
|
||||
yes("a block under the player can be changed",
|
||||
player.changeBlock(server.world(), 0, under, 0, Blocks.AIR));
|
||||
player.changeBlock(server.world(), spawnX, under, spawnZ, Blocks.AIR));
|
||||
yes("a block far away cannot",
|
||||
!player.changeBlock(server.world(), 100, 70, 100, Blocks.STONE));
|
||||
yes("a block just out of reach cannot",
|
||||
!player.changeBlock(server.world(), 0, under, 40, Blocks.STONE));
|
||||
!player.changeBlock(server.world(), (int) spawn[0], under, (int) spawn[2] + 40, Blocks.STONE));
|
||||
yes("a block that does not exist cannot",
|
||||
!player.changeBlock(server.world(), 0, under, 0, (byte) 99));
|
||||
|
||||
// and over the wire, a far away change changes nothing
|
||||
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(0, under, 0, Blocks.STONE));
|
||||
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(spawnX, under, spawnZ, Blocks.STONE));
|
||||
Thread.sleep(250L);
|
||||
eq("the near block changed", Byte.valueOf(Blocks.STONE),
|
||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
||||
Byte.valueOf(server.world().blockAt(spawnX, under, spawnZ)));
|
||||
client.close();
|
||||
} finally {
|
||||
server.stop();
|
||||
@@ -541,11 +550,13 @@ public final class PlayTest {
|
||||
|
||||
double[] spawn = server.world().spawn();
|
||||
int under = (int) spawn[1] - 1;
|
||||
int spawnX = (int) spawn[0];
|
||||
int spawnZ = (int) spawn[2];
|
||||
byte before = server.world().blockAt(0, under, 0);
|
||||
yes("there is something to break", before != net.rustnotmc.protocol.Blocks.AIR);
|
||||
|
||||
client.send(Packets.BLOCK_CHANGE,
|
||||
new Packets.BlockChange(0, under, 0, net.rustnotmc.protocol.Blocks.AIR));
|
||||
new Packets.BlockChange(spawnX, under, spawnZ, net.rustnotmc.protocol.Blocks.AIR));
|
||||
Thread.sleep(300L);
|
||||
eq("the block is still there", Byte.valueOf(before),
|
||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
||||
@@ -559,7 +570,7 @@ public final class PlayTest {
|
||||
// the mode is not the right to break the blocks of the world: that
|
||||
// belongs to the administrators
|
||||
client.send(Packets.BLOCK_CHANGE,
|
||||
new Packets.BlockChange(0, under, 0, net.rustnotmc.protocol.Blocks.AIR));
|
||||
new Packets.BlockChange(spawnX, under, spawnZ, net.rustnotmc.protocol.Blocks.AIR));
|
||||
Thread.sleep(300L);
|
||||
eq("and the block is still there", Byte.valueOf(before),
|
||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
||||
@@ -619,7 +630,7 @@ public final class PlayTest {
|
||||
Path dir = work.resolve("play-world");
|
||||
deleteTree(dir);
|
||||
|
||||
int[] under = new int[1];
|
||||
int[] under = new int[3];
|
||||
Server server = new Server("RustNotMC play", "hello", 20, 0,
|
||||
new World(1234L, new ChunkStorage(dir)));
|
||||
server.viewDistance(2);
|
||||
@@ -643,11 +654,13 @@ public final class PlayTest {
|
||||
|
||||
double[] spawn = server.world().spawn();
|
||||
under[0] = (int) spawn[1] - 1;
|
||||
under[1] = (int) spawn[0];
|
||||
under[2] = (int) spawn[2];
|
||||
client.send(Packets.BLOCK_CHANGE,
|
||||
new Packets.BlockChange(0, under[0], 0, Blocks.AIR));
|
||||
waitFor(() -> blockAt(server.world(), 0, under[0], 0) == Blocks.AIR, 3000L);
|
||||
new Packets.BlockChange(under[1], under[0], under[2], Blocks.AIR));
|
||||
waitFor(() -> blockAt(server.world(), under[1], under[0], under[2]) == Blocks.AIR, 3000L);
|
||||
eq("the block is gone", Byte.valueOf(Blocks.AIR),
|
||||
Byte.valueOf(server.world().blockAt(0, under[0], 0)));
|
||||
Byte.valueOf(server.world().blockAt(under[1], under[0], under[2])));
|
||||
client.close();
|
||||
} finally {
|
||||
server.stop();
|
||||
@@ -658,7 +671,7 @@ public final class PlayTest {
|
||||
new World(1234L, new ChunkStorage(dir)));
|
||||
try {
|
||||
eq("the broken block is still broken", Byte.valueOf(Blocks.AIR),
|
||||
Byte.valueOf(again.world().blockAt(0, under[0], 0)));
|
||||
Byte.valueOf(again.world().blockAt(under[1], under[0], under[2])));
|
||||
eq("and nothing was generated again", Long.valueOf(0L),
|
||||
Long.valueOf(again.world().generatedCount()));
|
||||
} finally {
|
||||
|
||||
@@ -189,29 +189,31 @@ public final class NodeTest {
|
||||
|
||||
private static void testRoads() {
|
||||
RoadNetwork roads = new RoadNetwork(4000);
|
||||
yes("the middle of the world is on a road", roads.onRoad(2000, 2000));
|
||||
yes("the middle of a straight road is on a road", roads.onRoad(2000, 2007));
|
||||
yes("far away from a road is not on a road", !roads.onRoad(2500, 2500));
|
||||
yes("the middle of the world is on a road", roads.onRoad(0, 0));
|
||||
yes("the middle of a straight road is on a road", roads.onRoad(0, 7));
|
||||
yes("far away from a road is not on a road", !roads.onRoad(500, 500));
|
||||
yes("there are points where a heap may stand", roads.spawnPoints().size() > 10);
|
||||
}
|
||||
|
||||
private static void testRoadIsLevel() {
|
||||
RoadNetwork roads = new RoadNetwork(4000);
|
||||
WorldGenerator generator = new WorldGenerator(1234L, roads);
|
||||
// a stretch of road that does not cross another one
|
||||
int height = generator.heightAt(2000, 2400);
|
||||
// a road is level across its width, and along its length it follows the
|
||||
// land it runs over
|
||||
int height = generator.heightAt(0, 400);
|
||||
eq("the road is level across it", Integer.valueOf(height),
|
||||
Integer.valueOf(generator.heightAt(2000 + 3, 2400)));
|
||||
eq("and level across it the other way", Integer.valueOf(height),
|
||||
Integer.valueOf(generator.heightAt(2000, 2400 + 3)));
|
||||
Integer.valueOf(generator.heightAt(3, 400)));
|
||||
eq("and level across it the other way too", Integer.valueOf(height),
|
||||
Integer.valueOf(generator.heightAt(-3, 400)));
|
||||
|
||||
// and the surface of a road is asphalt
|
||||
Chunk chunk = new Chunk(new ChunkPos(125, 125));
|
||||
Chunk chunk = new Chunk(new ChunkPos(0, 25));
|
||||
generator.generate(chunk);
|
||||
byte[] blocks = chunk.snapshot();
|
||||
int top = -1;
|
||||
for (int y = Chunk.HEIGHT - 1; y >= 0; y--) {
|
||||
if (blocks[ChunkFormat.index(0, y, 0)] != Blocks.AIR) {
|
||||
byte id = blocks[ChunkFormat.index(0, y, 0)];
|
||||
if (id != Blocks.AIR && id != Blocks.WATER) {
|
||||
top = y;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -336,9 +336,19 @@ public final class WorldTest {
|
||||
int height = generator.heightAt(worldX, worldZ);
|
||||
eq("the floor is stone at " + x + "," + z, Byte.valueOf(Blocks.STONE),
|
||||
Byte.valueOf(chunk.block(x, 0, z)));
|
||||
eq("the top is grass at " + x + "," + z, Byte.valueOf(Blocks.GRASS),
|
||||
byte expectedTop = generator.isRoad(worldX, worldZ)
|
||||
? Blocks.ASPHALT
|
||||
: (generator.underWater(worldX, worldZ)
|
||||
? Blocks.SAND
|
||||
: (height <= WorldGenerator.SEA_LEVEL + 1
|
||||
? Blocks.SAND : Blocks.GRASS));
|
||||
eq("the top is the ground at " + x + "," + z, Byte.valueOf(expectedTop),
|
||||
Byte.valueOf(chunk.block(x, height, z)));
|
||||
eq("the sky is air at " + x + "," + z, Byte.valueOf(Blocks.AIR),
|
||||
// under the sea the water stands on the ground, above the sea
|
||||
// there is air
|
||||
byte expectedSky = height < WorldGenerator.SEA_LEVEL
|
||||
? Blocks.WATER : Blocks.AIR;
|
||||
eq("the sky is air or water at " + x + "," + z, Byte.valueOf(expectedSky),
|
||||
Byte.valueOf(chunk.block(x, height + 1, z)));
|
||||
eq("the air goes on at " + x + "," + z, Byte.valueOf(Blocks.AIR),
|
||||
Byte.valueOf(chunk.block(x, Chunk.HEIGHT - 1, z)));
|
||||
|
||||
Reference in New Issue
Block a user