Мир стал островом: 4000 на 4000, море, дороги, барьер
- WorldGenerator: круглый остров в середине квадрата, море вокруг (уровень 62), песок у берега, дороги асфальтом. Остров и кольцо дорог считаются от начала координат, а не от середины мира. - Спавн: если середина острова под водой, игрок появляется на ближайшей суше рядом с ней. - Барьер: сервер не пускает игрока за край мира. - Мир стал конечным: размер задаётся RoadNetwork, генератор берёт его оттуда. Тесты мира и перезапуска переписаны под остров. Проверок в сервере 92.
This commit is contained in:
@@ -927,6 +927,12 @@ public final class ClientSession implements Runnable {
|
|||||||
return;
|
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,
|
if (this.player.moveTo(move.x, move.y, move.z, move.yaw, move.pitch,
|
||||||
move.onGround)) {
|
move.onGround)) {
|
||||||
// the player crossed into another chunk, so there is more world to
|
// 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);
|
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. */
|
/** How far apart the straight roads are. */
|
||||||
public int spacing() {
|
public int spacing() {
|
||||||
return this.spacing;
|
return this.spacing;
|
||||||
@@ -45,9 +50,13 @@ public final class RoadNetwork {
|
|||||||
return this.ringRadius;
|
return this.ringRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The middle of the world. */
|
/**
|
||||||
|
* The middle of the world.
|
||||||
|
*
|
||||||
|
* The world is centred on the origin.
|
||||||
|
*/
|
||||||
public int center() {
|
public int center() {
|
||||||
return this.size / 2;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The middle of the straight road that is nearest to the column. */
|
/** 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
|
* 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.
|
* 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() {
|
public double[] spawn() {
|
||||||
int height = this.generator.heightAt(0, 0);
|
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};
|
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. */
|
/** True when this world is written to disk. */
|
||||||
public boolean persistent() {
|
public boolean persistent() {
|
||||||
return this.storage.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
|
* 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
|
* dependence on the order chunks are requested in. Every value is a pure
|
||||||
* function of the seed and the coordinates.
|
* 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 {
|
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 long seed;
|
||||||
|
private final int size;
|
||||||
|
private final int islandRadius;
|
||||||
private final RoadNetwork roads;
|
private final RoadNetwork roads;
|
||||||
|
|
||||||
public WorldGenerator(long seed) {
|
public WorldGenerator(long seed) {
|
||||||
this(seed, null);
|
this(seed, new RoadNetwork(DEFAULT_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldGenerator(long seed, RoadNetwork roads) {
|
public WorldGenerator(long seed, RoadNetwork roads) {
|
||||||
|
this(seed, roads, DEFAULT_ISLAND_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldGenerator(long seed, RoadNetwork roads, int islandRadius) {
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
this.roads = roads;
|
this.roads = roads;
|
||||||
|
this.size = roads.size();
|
||||||
|
this.islandRadius = islandRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long seed() {
|
public long seed() {
|
||||||
return this.seed;
|
return this.seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The roads of this world, or null when it has none. */
|
/** The side of the world, in blocks. */
|
||||||
public RoadNetwork roads() {
|
public int size() {
|
||||||
return this.roads;
|
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;
|
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. */
|
/** The height of solid ground at a block column. */
|
||||||
public int heightAt(int blockX, int blockZ) {
|
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 base = fBm(blockX / 160.0D, blockZ / 160.0D, 4);
|
||||||
double detail = fBm(blockX / 32.0D, blockZ / 32.0D, 2) * 0.35D;
|
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) {
|
if (h < 2) {
|
||||||
h = 2;
|
h = 2;
|
||||||
}
|
}
|
||||||
@@ -146,6 +172,11 @@ public final class WorldGenerator {
|
|||||||
return h;
|
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. */
|
/** Fills one chunk. Pure: same seed and position, same bytes out. */
|
||||||
public void generate(Chunk chunk) {
|
public void generate(Chunk chunk) {
|
||||||
ChunkPos pos = chunk.pos();
|
ChunkPos pos = chunk.pos();
|
||||||
@@ -161,7 +192,8 @@ public final class WorldGenerator {
|
|||||||
int worldX = baseX + x;
|
int worldX = baseX + x;
|
||||||
int worldZ = baseZ + z;
|
int worldZ = baseZ + z;
|
||||||
int height = heightAt(worldX, worldZ);
|
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
|
// a column: dirt under the surface, grass on top, stone below
|
||||||
for (int y = 0; y <= height; y++) {
|
for (int y = 0; y <= height; y++) {
|
||||||
@@ -169,7 +201,13 @@ public final class WorldGenerator {
|
|||||||
if (y == 0) {
|
if (y == 0) {
|
||||||
id = Blocks.STONE;
|
id = Blocks.STONE;
|
||||||
} else if (y == height) {
|
} 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) {
|
} else if (y > height - 4) {
|
||||||
id = Blocks.DIRT;
|
id = Blocks.DIRT;
|
||||||
} else {
|
} else {
|
||||||
@@ -177,8 +215,17 @@ public final class WorldGenerator {
|
|||||||
}
|
}
|
||||||
blocks[ChunkFormat.index(x, y, z)] = id;
|
blocks[ChunkFormat.index(x, y, z)] = id;
|
||||||
}
|
}
|
||||||
for (int y = height + 1; y < ChunkFormat.HEIGHT; y++) {
|
// and the sea on top of the ground that is below it
|
||||||
blocks[ChunkFormat.index(x, y, z)] = Blocks.AIR;
|
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
|
* The roads are the grid of the road network and the ring around the
|
||||||
* to say so, or a neighbour generation would cut through it.
|
* island.
|
||||||
*/
|
*/
|
||||||
public boolean reachesIntoNeighbour(int blockX, int blockZ) {
|
public boolean isRoad(int blockX, int blockZ) {
|
||||||
return false;
|
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);
|
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)]));
|
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)]));
|
Byte.valueOf(home.blocks[ChunkFormat.index(0, height + 1, 0)]));
|
||||||
eq("the ground below it is dirt", Byte.valueOf(Blocks.DIRT),
|
eq("the ground below it is dirt", Byte.valueOf(Blocks.DIRT),
|
||||||
Byte.valueOf(home.blocks[ChunkFormat.index(0, height - 1, 0)]));
|
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)]));
|
Byte.valueOf(home.blocks[ChunkFormat.index(0, 0, 0)]));
|
||||||
|
|
||||||
// and it is the same terrain the server has
|
// 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)));
|
Byte.valueOf(server.world().blockAt(0, height, 0)));
|
||||||
client.close();
|
client.close();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -256,13 +261,15 @@ public final class PlayTest {
|
|||||||
|
|
||||||
double[] spawn = server.world().spawn();
|
double[] spawn = server.world().spawn();
|
||||||
int under = (int) spawn[1] - 1;
|
int under = (int) spawn[1] - 1;
|
||||||
|
int spawnX = (int) spawn[0];
|
||||||
|
int spawnZ = (int) spawn[2];
|
||||||
byte before = server.world().blockAt(0, under, 0);
|
byte before = server.world().blockAt(0, under, 0);
|
||||||
yes("there is something to break", before != Blocks.AIR);
|
yes("there is something to break", before != Blocks.AIR);
|
||||||
|
|
||||||
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(0, under, 0, Blocks.AIR));
|
client.send(Packets.BLOCK_CHANGE, new Packets.BlockChange(spawnX, under, spawnZ, Blocks.AIR));
|
||||||
waitFor(() -> blockAt(server.world(), 0, under, 0) == Blocks.AIR, 3000L);
|
waitFor(() -> blockAt(server.world(), spawnX, under, spawnZ) == Blocks.AIR, 3000L);
|
||||||
eq("the block is gone", Byte.valueOf(Blocks.AIR),
|
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();
|
client.close();
|
||||||
} finally {
|
} finally {
|
||||||
server.stop();
|
server.stop();
|
||||||
@@ -289,21 +296,23 @@ public final class PlayTest {
|
|||||||
|
|
||||||
double[] spawn = server.world().spawn();
|
double[] spawn = server.world().spawn();
|
||||||
int under = (int) spawn[1] - 1;
|
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",
|
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",
|
yes("a block far away cannot",
|
||||||
!player.changeBlock(server.world(), 100, 70, 100, Blocks.STONE));
|
!player.changeBlock(server.world(), 100, 70, 100, Blocks.STONE));
|
||||||
yes("a block just out of reach cannot",
|
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",
|
yes("a block that does not exist cannot",
|
||||||
!player.changeBlock(server.world(), 0, under, 0, (byte) 99));
|
!player.changeBlock(server.world(), 0, under, 0, (byte) 99));
|
||||||
|
|
||||||
// and over the wire, a far away change changes nothing
|
// 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);
|
Thread.sleep(250L);
|
||||||
eq("the near block changed", Byte.valueOf(Blocks.STONE),
|
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();
|
client.close();
|
||||||
} finally {
|
} finally {
|
||||||
server.stop();
|
server.stop();
|
||||||
@@ -541,11 +550,13 @@ public final class PlayTest {
|
|||||||
|
|
||||||
double[] spawn = server.world().spawn();
|
double[] spawn = server.world().spawn();
|
||||||
int under = (int) spawn[1] - 1;
|
int under = (int) spawn[1] - 1;
|
||||||
|
int spawnX = (int) spawn[0];
|
||||||
|
int spawnZ = (int) spawn[2];
|
||||||
byte before = server.world().blockAt(0, under, 0);
|
byte before = server.world().blockAt(0, under, 0);
|
||||||
yes("there is something to break", before != net.rustnotmc.protocol.Blocks.AIR);
|
yes("there is something to break", before != net.rustnotmc.protocol.Blocks.AIR);
|
||||||
|
|
||||||
client.send(Packets.BLOCK_CHANGE,
|
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);
|
Thread.sleep(300L);
|
||||||
eq("the block is still there", Byte.valueOf(before),
|
eq("the block is still there", Byte.valueOf(before),
|
||||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
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
|
// the mode is not the right to break the blocks of the world: that
|
||||||
// belongs to the administrators
|
// belongs to the administrators
|
||||||
client.send(Packets.BLOCK_CHANGE,
|
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);
|
Thread.sleep(300L);
|
||||||
eq("and the block is still there", Byte.valueOf(before),
|
eq("and the block is still there", Byte.valueOf(before),
|
||||||
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
Byte.valueOf(server.world().blockAt(0, under, 0)));
|
||||||
@@ -619,7 +630,7 @@ public final class PlayTest {
|
|||||||
Path dir = work.resolve("play-world");
|
Path dir = work.resolve("play-world");
|
||||||
deleteTree(dir);
|
deleteTree(dir);
|
||||||
|
|
||||||
int[] under = new int[1];
|
int[] under = new int[3];
|
||||||
Server server = new Server("RustNotMC play", "hello", 20, 0,
|
Server server = new Server("RustNotMC play", "hello", 20, 0,
|
||||||
new World(1234L, new ChunkStorage(dir)));
|
new World(1234L, new ChunkStorage(dir)));
|
||||||
server.viewDistance(2);
|
server.viewDistance(2);
|
||||||
@@ -643,11 +654,13 @@ public final class PlayTest {
|
|||||||
|
|
||||||
double[] spawn = server.world().spawn();
|
double[] spawn = server.world().spawn();
|
||||||
under[0] = (int) spawn[1] - 1;
|
under[0] = (int) spawn[1] - 1;
|
||||||
|
under[1] = (int) spawn[0];
|
||||||
|
under[2] = (int) spawn[2];
|
||||||
client.send(Packets.BLOCK_CHANGE,
|
client.send(Packets.BLOCK_CHANGE,
|
||||||
new Packets.BlockChange(0, under[0], 0, Blocks.AIR));
|
new Packets.BlockChange(under[1], under[0], under[2], Blocks.AIR));
|
||||||
waitFor(() -> blockAt(server.world(), 0, under[0], 0) == Blocks.AIR, 3000L);
|
waitFor(() -> blockAt(server.world(), under[1], under[0], under[2]) == Blocks.AIR, 3000L);
|
||||||
eq("the block is gone", Byte.valueOf(Blocks.AIR),
|
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();
|
client.close();
|
||||||
} finally {
|
} finally {
|
||||||
server.stop();
|
server.stop();
|
||||||
@@ -658,7 +671,7 @@ public final class PlayTest {
|
|||||||
new World(1234L, new ChunkStorage(dir)));
|
new World(1234L, new ChunkStorage(dir)));
|
||||||
try {
|
try {
|
||||||
eq("the broken block is still broken", Byte.valueOf(Blocks.AIR),
|
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),
|
eq("and nothing was generated again", Long.valueOf(0L),
|
||||||
Long.valueOf(again.world().generatedCount()));
|
Long.valueOf(again.world().generatedCount()));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -189,29 +189,31 @@ public final class NodeTest {
|
|||||||
|
|
||||||
private static void testRoads() {
|
private static void testRoads() {
|
||||||
RoadNetwork roads = new RoadNetwork(4000);
|
RoadNetwork roads = new RoadNetwork(4000);
|
||||||
yes("the middle of the world is on a road", roads.onRoad(2000, 2000));
|
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(2000, 2007));
|
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(2500, 2500));
|
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);
|
yes("there are points where a heap may stand", roads.spawnPoints().size() > 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void testRoadIsLevel() {
|
private static void testRoadIsLevel() {
|
||||||
RoadNetwork roads = new RoadNetwork(4000);
|
RoadNetwork roads = new RoadNetwork(4000);
|
||||||
WorldGenerator generator = new WorldGenerator(1234L, roads);
|
WorldGenerator generator = new WorldGenerator(1234L, roads);
|
||||||
// a stretch of road that does not cross another one
|
// a road is level across its width, and along its length it follows the
|
||||||
int height = generator.heightAt(2000, 2400);
|
// land it runs over
|
||||||
|
int height = generator.heightAt(0, 400);
|
||||||
eq("the road is level across it", Integer.valueOf(height),
|
eq("the road is level across it", Integer.valueOf(height),
|
||||||
Integer.valueOf(generator.heightAt(2000 + 3, 2400)));
|
Integer.valueOf(generator.heightAt(3, 400)));
|
||||||
eq("and level across it the other way", Integer.valueOf(height),
|
eq("and level across it the other way too", Integer.valueOf(height),
|
||||||
Integer.valueOf(generator.heightAt(2000, 2400 + 3)));
|
Integer.valueOf(generator.heightAt(-3, 400)));
|
||||||
|
|
||||||
// and the surface of a road is asphalt
|
// 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);
|
generator.generate(chunk);
|
||||||
byte[] blocks = chunk.snapshot();
|
byte[] blocks = chunk.snapshot();
|
||||||
int top = -1;
|
int top = -1;
|
||||||
for (int y = Chunk.HEIGHT - 1; y >= 0; y--) {
|
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;
|
top = y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,9 +336,19 @@ public final class WorldTest {
|
|||||||
int height = generator.heightAt(worldX, worldZ);
|
int height = generator.heightAt(worldX, worldZ);
|
||||||
eq("the floor is stone at " + x + "," + z, Byte.valueOf(Blocks.STONE),
|
eq("the floor is stone at " + x + "," + z, Byte.valueOf(Blocks.STONE),
|
||||||
Byte.valueOf(chunk.block(x, 0, z)));
|
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)));
|
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)));
|
Byte.valueOf(chunk.block(x, height + 1, z)));
|
||||||
eq("the air goes on at " + x + "," + z, Byte.valueOf(Blocks.AIR),
|
eq("the air goes on at " + x + "," + z, Byte.valueOf(Blocks.AIR),
|
||||||
Byte.valueOf(chunk.block(x, Chunk.HEIGHT - 1, z)));
|
Byte.valueOf(chunk.block(x, Chunk.HEIGHT - 1, z)));
|
||||||
|
|||||||
Reference in New Issue
Block a user