Свалки у дорог: бочки серые/синие/красные и ящики еды как блоки, лут по детерминированным таблицам, синие на кольце дают продвинутые фрагменты
This commit is contained in:
@@ -48,6 +48,23 @@ public final class RsxBlockMap {
|
|||||||
net.minecraft.item.EnumDyeColor.GRAY);
|
net.minecraft.item.EnumDyeColor.GRAY);
|
||||||
STATES[net.rustnotmc.protocol.Blocks.SNOW] =
|
STATES[net.rustnotmc.protocol.Blocks.SNOW] =
|
||||||
Blocks.SNOW.getDefaultState();
|
Blocks.SNOW.getDefaultState();
|
||||||
|
// the junk piles of the roads: grey, blue and red barrels, and the
|
||||||
|
// wooden crate of food. A barrel is drawn as a barrel's colour clang
|
||||||
|
// of lantern hardened clay, the crate as a plank blocked square
|
||||||
|
STATES[net.rustnotmc.protocol.Blocks.BARREL] =
|
||||||
|
Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(
|
||||||
|
net.minecraft.block.BlockColored.COLOR,
|
||||||
|
net.minecraft.item.EnumDyeColor.SILVER);
|
||||||
|
STATES[net.rustnotmc.protocol.Blocks.BARREL_BLUE] =
|
||||||
|
Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(
|
||||||
|
net.minecraft.block.BlockColored.COLOR,
|
||||||
|
net.minecraft.item.EnumDyeColor.BLUE);
|
||||||
|
STATES[net.rustnotmc.protocol.Blocks.BARREL_RED] =
|
||||||
|
Blocks.STAINED_HARDENED_CLAY.getDefaultState().withProperty(
|
||||||
|
net.minecraft.block.BlockColored.COLOR,
|
||||||
|
net.minecraft.item.EnumDyeColor.RED);
|
||||||
|
STATES[net.rustnotmc.protocol.Blocks.CRATE] =
|
||||||
|
Blocks.PLANKS.getDefaultState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private RsxBlockMap() {
|
private RsxBlockMap() {
|
||||||
|
|||||||
@@ -33,9 +33,17 @@ public final class Blocks {
|
|||||||
/** The surface of the high and cold places. */
|
/** The surface of the high and cold places. */
|
||||||
public static final byte SNOW = 9;
|
public static final byte SNOW = 9;
|
||||||
|
|
||||||
|
/** A barrel that may be broken for what is in it. */
|
||||||
|
public static final byte BARREL = 10;
|
||||||
|
public static final byte BARREL_BLUE = 11;
|
||||||
|
public static final byte BARREL_RED = 12;
|
||||||
|
|
||||||
|
/** A wooden box of food, standing with the barrels. */
|
||||||
|
public static final byte CRATE = 13;
|
||||||
|
|
||||||
/** Lowest and highest legal block id. */
|
/** Lowest and highest legal block id. */
|
||||||
public static final byte MIN = AIR;
|
public static final byte MIN = AIR;
|
||||||
public static final byte MAX = SNOW;
|
public static final byte MAX = CRATE;
|
||||||
|
|
||||||
private Blocks() {
|
private Blocks() {
|
||||||
}
|
}
|
||||||
@@ -52,6 +60,10 @@ public final class Blocks {
|
|||||||
case LEAVES: return "leaves";
|
case LEAVES: return "leaves";
|
||||||
case ASPHALT: return "asphalt";
|
case ASPHALT: return "asphalt";
|
||||||
case SNOW: return "snow";
|
case SNOW: return "snow";
|
||||||
|
case BARREL: return "barrel";
|
||||||
|
case BARREL_BLUE: return "barrel_blue";
|
||||||
|
case BARREL_RED: return "barrel_red";
|
||||||
|
case CRATE: return "crate";
|
||||||
default: return "unknown(" + id + ")";
|
default: return "unknown(" + id + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +78,18 @@ public final class Blocks {
|
|||||||
return id == AIR || id == WATER;
|
return id == AIR || id == WATER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when breaking it gives what is inside instead of nothing.
|
||||||
|
*
|
||||||
|
* The barrels and the crates are the loot of the roads: a broken one
|
||||||
|
* empties its content into the hands of whoever broke it, whatever the
|
||||||
|
* game mode says.
|
||||||
|
*/
|
||||||
|
public static boolean isLootable(byte id) {
|
||||||
|
return id == BARREL || id == BARREL_BLUE || id == BARREL_RED
|
||||||
|
|| id == CRATE;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isValid(byte id) {
|
public static boolean isValid(byte id) {
|
||||||
return id >= MIN && id <= MAX;
|
return id >= MIN && id <= MAX;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,8 +178,9 @@ public final class ItemRegistry {
|
|||||||
define(ItemType.of(HIGH_QUALITY_METAL, "High Quality Metal").rid("hqm"));
|
define(ItemType.of(HIGH_QUALITY_METAL, "High Quality Metal").rid("hqm"));
|
||||||
define(ItemType.of(SULFUR, "Sulfur Ore").rid("sulfur_ore"));
|
define(ItemType.of(SULFUR, "Sulfur Ore").rid("sulfur_ore"));
|
||||||
define(ItemType.of(SCRAP, "Scrap").rid("scrap"));
|
define(ItemType.of(SCRAP, "Scrap").rid("scrap"));
|
||||||
// The fragments of the blueprints. The remake does not know them: they
|
// The fragments of the blueprints. They come out of the blue barrels
|
||||||
// come out of the loot crates, and the crates are still to come.
|
// of the junk piles on the roads, and the advanced ones out of the
|
||||||
|
// barrels on the ring road.
|
||||||
define(ItemType.of(BASIC_FRAGMENT, "Basic Blueprint Fragment"));
|
define(ItemType.of(BASIC_FRAGMENT, "Basic Blueprint Fragment"));
|
||||||
define(ItemType.of(ADVANCED_FRAGMENT, "Advanced Blueprint Fragment"));
|
define(ItemType.of(ADVANCED_FRAGMENT, "Advanced Blueprint Fragment"));
|
||||||
define(ItemType.of(METAL_PIPE, "Metal Pipe").rid("metalpipe"));
|
define(ItemType.of(METAL_PIPE, "Metal Pipe").rid("metalpipe"));
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ $suites = @(
|
|||||||
'net.rustnotmc.server.world.WorldTest',
|
'net.rustnotmc.server.world.WorldTest',
|
||||||
'net.rustnotmc.server.world.NodeTest',
|
'net.rustnotmc.server.world.NodeTest',
|
||||||
'net.rustnotmc.server.world.LodTest',
|
'net.rustnotmc.server.world.LodTest',
|
||||||
|
'net.rustnotmc.server.world.PilesTest',
|
||||||
'net.rustnotmc.server.item.InventoryTest',
|
'net.rustnotmc.server.item.InventoryTest',
|
||||||
'net.rustnotmc.server.craft.CraftTest',
|
'net.rustnotmc.server.craft.CraftTest',
|
||||||
'net.rustnotmc.server.hitbox.HitboxTest',
|
'net.rustnotmc.server.hitbox.HitboxTest',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import net.rustnotmc.protocol.ChunkFormat;
|
|||||||
import net.rustnotmc.protocol.Frame;
|
import net.rustnotmc.protocol.Frame;
|
||||||
import net.rustnotmc.protocol.FrameDecoder;
|
import net.rustnotmc.protocol.FrameDecoder;
|
||||||
import net.rustnotmc.protocol.ItemRegistry;
|
import net.rustnotmc.protocol.ItemRegistry;
|
||||||
|
import net.rustnotmc.protocol.ItemStack;
|
||||||
import net.rustnotmc.protocol.ItemType;
|
import net.rustnotmc.protocol.ItemType;
|
||||||
import net.rustnotmc.protocol.Packet;
|
import net.rustnotmc.protocol.Packet;
|
||||||
import net.rustnotmc.protocol.Packets;
|
import net.rustnotmc.protocol.Packets;
|
||||||
@@ -797,6 +798,34 @@ public final class ClientSession implements Runnable {
|
|||||||
send(Packets.BREAK_PROGRESS, new Packets.BreakProgress(dig.x, dig.y, dig.z, -1));
|
send(Packets.BREAK_PROGRESS, new Packets.BreakProgress(dig.x, dig.y, dig.z, -1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
byte at;
|
||||||
|
try {
|
||||||
|
at = this.server.world().blockAt(dig.x, dig.y, dig.z);
|
||||||
|
} catch (IOException lost) {
|
||||||
|
at = Blocks.AIR; // the place has no readable ground there
|
||||||
|
}
|
||||||
|
if (Blocks.isLootable(at)) {
|
||||||
|
// a barrel or a crate of the roads gives up what is in it when it
|
||||||
|
// is broken, in the survival mode as well as the creative one
|
||||||
|
if (this.server.world().setBlock(dig.x, dig.y, dig.z, Blocks.AIR)) {
|
||||||
|
this.server.broadcastBlockChange(dig.x, dig.y, dig.z, Blocks.AIR);
|
||||||
|
boolean took = false;
|
||||||
|
for (ItemStack stack : this.server.world().generator()
|
||||||
|
.pileLoot(at, dig.x, dig.y, dig.z)) {
|
||||||
|
if (this.player.inventory().add(stack)) {
|
||||||
|
took = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (took) {
|
||||||
|
this.server.log("pile: " + this.account + " emptied a "
|
||||||
|
+ (at == Blocks.CRATE ? "crate" : "barrel")
|
||||||
|
+ " at " + dig.x + "," + dig.y + "," + dig.z);
|
||||||
|
send(Packets.INVENTORY, this.player.inventory().toPacket());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send(Packets.BREAK_PROGRESS, new Packets.BreakProgress(dig.x, dig.y, dig.z, -1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.player.mayBreakBlocks()) {
|
if (this.player.mayBreakBlocks()) {
|
||||||
if (this.server.world().setBlock(dig.x, dig.y, dig.z, Blocks.AIR)) {
|
if (this.server.world().setBlock(dig.x, dig.y, dig.z, Blocks.AIR)) {
|
||||||
this.server.broadcastBlockChange(dig.x, dig.y, dig.z, Blocks.AIR);
|
this.server.broadcastBlockChange(dig.x, dig.y, dig.z, Blocks.AIR);
|
||||||
|
|||||||
@@ -156,6 +156,14 @@ public final class LodGenerator {
|
|||||||
return 0x6E6E6E;
|
return 0x6E6E6E;
|
||||||
case Blocks.ASPHALT:
|
case Blocks.ASPHALT:
|
||||||
return 0x3A3A3A;
|
return 0x3A3A3A;
|
||||||
|
case Blocks.BARREL:
|
||||||
|
return 0x53514C;
|
||||||
|
case Blocks.BARREL_BLUE:
|
||||||
|
return 0x3D5C85;
|
||||||
|
case Blocks.BARREL_RED:
|
||||||
|
return 0x7B3535;
|
||||||
|
case Blocks.CRATE:
|
||||||
|
return 0x8A7040;
|
||||||
case Blocks.SNOW:
|
case Blocks.SNOW:
|
||||||
return 0xE9EDEF;
|
return 0xE9EDEF;
|
||||||
case Blocks.WATER:
|
case Blocks.WATER:
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package net.rustnotmc.server.world;
|
|||||||
|
|
||||||
import net.rustnotmc.protocol.Blocks;
|
import net.rustnotmc.protocol.Blocks;
|
||||||
import net.rustnotmc.protocol.ChunkFormat;
|
import net.rustnotmc.protocol.ChunkFormat;
|
||||||
|
import net.rustnotmc.protocol.ItemRegistry;
|
||||||
|
import net.rustnotmc.protocol.ItemStack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns a seed and a position into terrain.
|
* Turns a seed and a position into terrain.
|
||||||
@@ -58,6 +63,14 @@ public final class WorldGenerator {
|
|||||||
/** How many blocks of canopy a tree may reach. */
|
/** How many blocks of canopy a tree may reach. */
|
||||||
private static final int TREE_REACH = 2;
|
private static final int TREE_REACH = 2;
|
||||||
|
|
||||||
|
/** The salts of the junk piles: usually a point stands empty. */
|
||||||
|
private static final int SALT_SITE = 21;
|
||||||
|
private static final int SALT_COUNT = 22;
|
||||||
|
private static final int SALT_COLOUR = 23;
|
||||||
|
private static final int SALT_CRATE = 24;
|
||||||
|
private static final int SALT_LOOT = 25;
|
||||||
|
private static final int SALT_ITEM = 26;
|
||||||
|
|
||||||
private final long seed;
|
private final long seed;
|
||||||
private final int size;
|
private final int size;
|
||||||
private final int islandRadius;
|
private final int islandRadius;
|
||||||
@@ -579,10 +592,170 @@ public final class WorldGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the junk piles, put up on the roads where they belong
|
||||||
|
layPiles(chunk, blocks, baseX, baseZ);
|
||||||
chunk.markGenerated();
|
chunk.markGenerated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the junk piles of the roads into a chunk.
|
||||||
|
*
|
||||||
|
* The heaps stand where the roads meet the network, at every point that
|
||||||
|
* the road keeps free for them: a quarter of those points carries a
|
||||||
|
* heap, and the rest stands empty so the roads do not read as one long
|
||||||
|
* wall of barrels. A heap is a short row of barrels, two to four of them,
|
||||||
|
* and every third row ends with a crate of food.
|
||||||
|
*
|
||||||
|
* Nothing here depends on a random number, the same way the roads
|
||||||
|
* themselves do not: the same world always has the same heaps, so a
|
||||||
|
* broken one stays broken and an untouched one stays untouched when the
|
||||||
|
* chunk is worked out again.
|
||||||
|
*/
|
||||||
|
private void layPiles(Chunk chunk, byte[] blocks, int baseX, int baseZ) {
|
||||||
|
ChunkPos pos = chunk.pos();
|
||||||
|
for (int[] point : this.roads.spawnPoints()) {
|
||||||
|
int sx = point[0];
|
||||||
|
int sz = point[1];
|
||||||
|
if (sx < baseX - 6 || sx > baseX + ChunkFormat.WIDTH + 6
|
||||||
|
|| sz < baseZ - 6 || sz > baseZ + ChunkFormat.WIDTH + 6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isRoad(sx, sz) || (hash(sx, SALT_SITE, sz) & 0xFFFF) % 3 != 0) {
|
||||||
|
continue; // this point stands empty
|
||||||
|
}
|
||||||
|
int count = 2 + (int) ((hash(sx, SALT_COUNT, sz) & 0xFFFF) % 3);
|
||||||
|
int[] dir = pileDirection(sx, sz);
|
||||||
|
for (int j = 0; j < count; j++) {
|
||||||
|
putPileBlock(blocks, baseX, baseZ,
|
||||||
|
sx + dir[0] * j, sz + dir[1] * j, barrelOf(sx, sz, j));
|
||||||
|
}
|
||||||
|
if ((hash(sx, SALT_CRATE, sz) & 0xFFFF) % 3 == 0) {
|
||||||
|
putPileBlock(blocks, baseX, baseZ,
|
||||||
|
sx + dir[0] * count, sz + dir[1] * count, Blocks.CRATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The way a row of barrels runs at a point of the roads.
|
||||||
|
*
|
||||||
|
* Along the road it stands on, so a heap reads as a little side of the
|
||||||
|
* road rather than a wall across it.
|
||||||
|
*/
|
||||||
|
private int[] pileDirection(int sx, int sz) {
|
||||||
|
int[] ring = this.roads.onRing(sx, sz);
|
||||||
|
if (ring == null) {
|
||||||
|
// the straight roads run along the axes, so a heap laid along the
|
||||||
|
// axis the road runs in lies along the road
|
||||||
|
if (Math.abs(sz - this.roads.centerLine(sz)) <= RoadNetwork.HALF_WIDTH
|
||||||
|
&& Math.abs(sx - this.roads.centerLine(sx)) > RoadNetwork.HALF_WIDTH) {
|
||||||
|
return new int[]{1, 0};
|
||||||
|
}
|
||||||
|
return new int[]{0, 1};
|
||||||
|
}
|
||||||
|
// the ring: a heap lies along the way the ring goes. The tangent is a
|
||||||
|
// quarter turn from the radius, and it is snapped to the axis it
|
||||||
|
// mostly goes in
|
||||||
|
int rx = ring[0] - this.roads.center();
|
||||||
|
int rz = ring[1] - this.roads.center();
|
||||||
|
int tx = -rz;
|
||||||
|
int tz = rx;
|
||||||
|
if (Math.abs(tx) >= Math.abs(tz)) {
|
||||||
|
return new int[]{Integer.signum(tx), 0};
|
||||||
|
}
|
||||||
|
return new int[]{0, Integer.signum(tz)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** What one barrel of a heap is, grey or blue or red. */
|
||||||
|
private byte barrelOf(int sx, int sz, int index) {
|
||||||
|
int pick = (int) ((hash(sx, SALT_COLOUR + index, sz) & 0xFFFF) % 100);
|
||||||
|
if (pick < 60) {
|
||||||
|
return Blocks.BARREL;
|
||||||
|
}
|
||||||
|
if (pick < 85) {
|
||||||
|
return Blocks.BARREL_BLUE;
|
||||||
|
}
|
||||||
|
return Blocks.BARREL_RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lays one barrel of a heap into a chunk that holds its square. */
|
||||||
|
private void putPileBlock(byte[] blocks, int baseX, int baseZ,
|
||||||
|
int px, int pz, byte block) {
|
||||||
|
if (!isRoad(px, pz)) {
|
||||||
|
return; // only a road carries the heaps
|
||||||
|
}
|
||||||
|
int height = heightAt(px, pz);
|
||||||
|
int y = height + 1;
|
||||||
|
if (height < SEA_LEVEL + 1 || y <= 0 || y >= Chunk.HEIGHT - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int x = px - baseX;
|
||||||
|
int z = pz - baseZ;
|
||||||
|
if (x < 0 || x >= ChunkFormat.WIDTH || z < 0 || z >= ChunkFormat.WIDTH) {
|
||||||
|
return; // the barrel belongs to the chunk next door
|
||||||
|
}
|
||||||
|
int index = ChunkFormat.index(x, y, z);
|
||||||
|
if (blocks[index] == Blocks.AIR) {
|
||||||
|
blocks[index] = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What comes out of a barrel or a crate of the roads.
|
||||||
|
*
|
||||||
|
* The loot is a pure function of the place as well, so the same barrel
|
||||||
|
* always gives the same content: the looting does not depend on who
|
||||||
|
* counted what when. A grey thing holds scrap with rags, a blue one
|
||||||
|
* parts with a fragment of a blueprint, a red one fuel, and a crate
|
||||||
|
* food and drink. The blue barrels of the ring road sometimes hold the
|
||||||
|
* advanced fragments, the rest of the world gives only the basic ones.
|
||||||
|
*/
|
||||||
|
public List<ItemStack> pileLoot(byte block, int x, int y, int z) {
|
||||||
|
List<ItemStack> out = new ArrayList<ItemStack>();
|
||||||
|
long loot = hash(x, y ^ SALT_LOOT, z);
|
||||||
|
long item = hash(x, SALT_ITEM, z ^ y);
|
||||||
|
switch (block) {
|
||||||
|
case Blocks.BARREL: {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.SCRAP,
|
||||||
|
2 + (int) ((loot & 0xFFFF) % 3)));
|
||||||
|
if ((item & 0xFF) < 64) {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.BANDANA_MASK
|
||||||
|
+ (int) (((item & 0xFF00) >>> 8) % 16), 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Blocks.BARREL_BLUE: {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.SCRAP,
|
||||||
|
1 + (int) ((loot & 0xFFFF) % 2)));
|
||||||
|
if ((item & 0xFF) < 115) {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.BASIC_FRAGMENT, 1));
|
||||||
|
}
|
||||||
|
if (this.roads.onRing(x, z) != null && (item & 0xFF00) < 0x2000) {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.ADVANCED_FRAGMENT, 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Blocks.BARREL_RED: {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.LOW_GRADE_FUEL,
|
||||||
|
1 + (int) ((loot & 0xFFFF) % 2)));
|
||||||
|
if ((item & 0xFF) < 102) {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.CRUDE_OIL, 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Blocks.CRATE: {
|
||||||
|
out.add(ItemStack.of(ItemRegistry.APPLE + (int) ((item & 0xFFFF) % 17),
|
||||||
|
1 + (int) ((loot & 0xFFFF) % 2)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lays the wood of one tree into a column of a chunk.
|
* Lays the wood of one tree into a column of a chunk.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,346 @@
|
|||||||
|
package net.rustnotmc.server.world;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import net.rustnotmc.protocol.Blocks;
|
||||||
|
import net.rustnotmc.protocol.ItemRegistry;
|
||||||
|
import net.rustnotmc.protocol.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the junk piles of the roads.
|
||||||
|
*
|
||||||
|
* The heaps are what the roads are for: someone put barrels and crates out
|
||||||
|
* there, and a player on foot breaks one and carries off what it kept. The
|
||||||
|
* heaps have to be on the roads, out of the water, and the same in every
|
||||||
|
* world of the same seed, and the blue ones have to pay in the fragments of
|
||||||
|
* the blueprints the workbenches want.
|
||||||
|
*/
|
||||||
|
public final class PilesTest {
|
||||||
|
|
||||||
|
private static int checks;
|
||||||
|
private static int failed;
|
||||||
|
private static final java.util.List<String> failures = new java.util.ArrayList<String>();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
run("the heaps stand on the roads", new Throwing() {
|
||||||
|
public void go() throws Exception { testPlacement(); }
|
||||||
|
});
|
||||||
|
run("a world has the same heaps again", new Throwing() {
|
||||||
|
public void go() throws Exception { testDeterminism(); }
|
||||||
|
});
|
||||||
|
run("no road keeps a barrel over the sea", new Throwing() {
|
||||||
|
public void go() throws Exception { testNoHeapOverTheSea(); }
|
||||||
|
});
|
||||||
|
run("a grey barrel gives scrap and rags", new Throwing() {
|
||||||
|
public void go() throws Exception { testGrey(); }
|
||||||
|
});
|
||||||
|
run("a blue barrel gives the pieces of a blueprint", new Throwing() {
|
||||||
|
public void go() throws Exception { testFragments(); }
|
||||||
|
});
|
||||||
|
run("a red barrel gives fuel", new Throwing() {
|
||||||
|
public void go() throws Exception { testFuel(); }
|
||||||
|
});
|
||||||
|
run("a crate gives food and drink", new Throwing() {
|
||||||
|
public void go() throws Exception { testCrate(); }
|
||||||
|
});
|
||||||
|
run("a walk over the middle of the island meets heaps", new Throwing() {
|
||||||
|
public void go() throws Exception { testRealWorldHeaps(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
for (int i = 0; i < failures.size(); i++) {
|
||||||
|
System.out.println(" FAIL " + failures.get(i));
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("checks: " + checks + ", failed: " + failed);
|
||||||
|
if (failed != 0) {
|
||||||
|
System.out.println("FAILED");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
System.out.println("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Throwing {
|
||||||
|
void go() throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void run(String name, Throwing body) {
|
||||||
|
checks++;
|
||||||
|
int before = failed;
|
||||||
|
try {
|
||||||
|
body.go();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
failed++;
|
||||||
|
failures.add(name + ": threw " + t);
|
||||||
|
t.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failed == before) {
|
||||||
|
System.out.println(" ok " + name);
|
||||||
|
} else {
|
||||||
|
System.out.println(" FAIL " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void eq(Object what, Object expected, Object actual) {
|
||||||
|
if (expected == null ? actual != null : !expected.equals(actual)) {
|
||||||
|
failed++;
|
||||||
|
failures.add(what + ": expected " + expected + " but got " + actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void yes(String what, boolean condition) {
|
||||||
|
if (!condition) {
|
||||||
|
failed++;
|
||||||
|
failures.add(what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Chunk chunkOf(WorldGenerator generator, int cx, int cz) {
|
||||||
|
Chunk chunk = new Chunk(new ChunkPos(cx, cz));
|
||||||
|
generator.generate(chunk);
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks the heaps of the chunks around the points the road network keeps
|
||||||
|
* for them, and says every barrel stands where a barrel should: on a
|
||||||
|
* road, on the ground, with nothing in the way over it.
|
||||||
|
*/
|
||||||
|
private static void testPlacement() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
RoadNetwork roads = new RoadNetwork(generator.size());
|
||||||
|
int seen = 0;
|
||||||
|
int heaps = 0;
|
||||||
|
java.util.Set<Long> visited = new java.util.HashSet<Long>();
|
||||||
|
for (int[] site : roads.spawnPoints()) {
|
||||||
|
int chunkX = Math.floorDiv(site[0], 16);
|
||||||
|
int chunkZ = Math.floorDiv(site[1], 16);
|
||||||
|
long key = ((long) chunkX << 32) ^ chunkZ;
|
||||||
|
// the same chunk may hold several points, and generation costs
|
||||||
|
// real seconds
|
||||||
|
if (!visited.add(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Chunk chunk = chunkOf(generator, chunkX, chunkZ);
|
||||||
|
boolean heap = false;
|
||||||
|
for (int y = 0; y < Chunk.HEIGHT; y++) {
|
||||||
|
for (int z = 0; z < Chunk.WIDTH; z++) {
|
||||||
|
for (int x = 0; x < Chunk.WIDTH; x++) {
|
||||||
|
byte id = chunk.block(x, y, z);
|
||||||
|
if (!Blocks.isLootable(id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
heap = true;
|
||||||
|
seen++;
|
||||||
|
int worldX = chunkX * 16 + x;
|
||||||
|
int worldZ = chunkZ * 16 + z;
|
||||||
|
yes("a barrel on the road at " + worldX + "," + worldZ,
|
||||||
|
generator.isRoad(worldX, worldZ));
|
||||||
|
eq("the ground under the barrel at " + worldX
|
||||||
|
+ "," + worldZ,
|
||||||
|
Integer.valueOf(y - 1),
|
||||||
|
Integer.valueOf(generator.heightAt(worldX, worldZ)));
|
||||||
|
eq("nothing wedged over the barrel at " + worldX
|
||||||
|
+ "," + worldZ,
|
||||||
|
Byte.valueOf(Blocks.AIR),
|
||||||
|
Byte.valueOf(chunk.block(x, y + 1, z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (heap) {
|
||||||
|
heaps++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yes("the roads carry heaps (" + seen + " barrels seen)", seen > 0);
|
||||||
|
yes("more heaps of one (" + heaps + " chunks with a heap)",
|
||||||
|
heaps > 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testDeterminism() throws Exception {
|
||||||
|
Chunk one = chunkOf(new WorldGenerator(1234L), 0, 0);
|
||||||
|
Chunk two = chunkOf(new WorldGenerator(1234L), 0, 0);
|
||||||
|
yes("same seed, same chunk, barrels and all",
|
||||||
|
java.util.Arrays.equals(one.raw(), two.raw()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** No barrel stands where the sea is, whatever the network plans. */
|
||||||
|
private static void testNoHeapOverTheSea() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
RoadNetwork roads = new RoadNetwork(generator.size());
|
||||||
|
for (int[] point : roads.spawnPoints()) {
|
||||||
|
if (!generator.isRoad(point[0], point[1])) {
|
||||||
|
continue; // the network does not keep a road here
|
||||||
|
}
|
||||||
|
yes("the road at " + point[0] + "," + point[1] + " is dry",
|
||||||
|
generator.heightAt(point[0], point[1]) >= WorldGenerator.SEA_LEVEL + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One broken barrel: what comes out of it puts a player on their way. */
|
||||||
|
private static void greyLoot(WorldGenerator generator, int x, int y, int z) {
|
||||||
|
List<ItemStack> loot = generator.pileLoot(Blocks.BARREL, x, y, z);
|
||||||
|
yes("a grey barrel gives something", !loot.isEmpty());
|
||||||
|
int scrap = 0;
|
||||||
|
int others = 0;
|
||||||
|
for (ItemStack stack : loot) {
|
||||||
|
if (stack.item == ItemRegistry.SCRAP) {
|
||||||
|
scrap = stack.count;
|
||||||
|
} else {
|
||||||
|
others++;
|
||||||
|
yes("the rags of a heap are worn things (" + stack.item + ")",
|
||||||
|
stack.item >= ItemRegistry.BANDANA_MASK
|
||||||
|
&& stack.item <= ItemRegistry.ROADSIGN_GLOVES);
|
||||||
|
eq("a rag is one", Integer.valueOf(1),
|
||||||
|
Integer.valueOf(stack.count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eq("a grey barrel keeps at most two stacks", Integer.valueOf(1),
|
||||||
|
Integer.valueOf(loot.size() <= 2 ? 1 : 0));
|
||||||
|
yes("the scrap is " + scrap + ", between two and four",
|
||||||
|
scrap >= 2 && scrap <= 4);
|
||||||
|
yes("at most one rag with the scrap", others <= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testGrey() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
int rags = 0;
|
||||||
|
for (int w = 0; w < 120; w++) {
|
||||||
|
List<ItemStack> loot = generator.pileLoot(Blocks.BARREL,
|
||||||
|
w * 37, 70, -w * 11);
|
||||||
|
for (ItemStack stack : loot) {
|
||||||
|
if (stack.item != ItemRegistry.SCRAP) {
|
||||||
|
rags++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
greyLoot(generator, w * 37, 70, -w * 11);
|
||||||
|
}
|
||||||
|
yes("now and then a rag rides with the scrap (" + rags + ")",
|
||||||
|
rags > 0 && rags < 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The blue barrels pay in the fragments of the blueprints. */
|
||||||
|
private static void testFragments() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
RoadNetwork roads = new RoadNetwork(generator.size());
|
||||||
|
int radius = roads.ringRadius();
|
||||||
|
int basic = 0;
|
||||||
|
int advanced = 0;
|
||||||
|
int offRing = 0;
|
||||||
|
// the straight roads of the grid, all the way out, and the ring road
|
||||||
|
// itself: the advanced fragments only belong to the ring
|
||||||
|
for (int w = 0; w < 800; w++) {
|
||||||
|
int x = w * 7 - 2800;
|
||||||
|
int z = (w * 13) % 2200;
|
||||||
|
List<ItemStack> loot = generator.pileLoot(Blocks.BARREL_BLUE, x, 70, z);
|
||||||
|
for (ItemStack stack : loot) {
|
||||||
|
if (stack.item == ItemRegistry.BASIC_FRAGMENT) {
|
||||||
|
basic++;
|
||||||
|
}
|
||||||
|
if (stack.item == ItemRegistry.ADVANCED_FRAGMENT) {
|
||||||
|
advanced++;
|
||||||
|
if (roads.onRing(x, z) == null) {
|
||||||
|
offRing++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ItemStack stack : generator.pileLoot(Blocks.BARREL_BLUE, x, 70, -z)) {
|
||||||
|
if (stack.item == ItemRegistry.BASIC_FRAGMENT) {
|
||||||
|
basic++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 720; i++) {
|
||||||
|
double angle = 2.0D * Math.PI * i / 720;
|
||||||
|
int x = (int) Math.round(Math.cos(angle) * radius);
|
||||||
|
int z = (int) Math.round(Math.sin(angle) * radius);
|
||||||
|
for (ItemStack stack : generator.pileLoot(Blocks.BARREL_BLUE, x, 70, z)) {
|
||||||
|
if (stack.item == ItemRegistry.ADVANCED_FRAGMENT) {
|
||||||
|
advanced++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yes("the blue barrels give the basic fragments (" + basic + ")",
|
||||||
|
basic > 200);
|
||||||
|
yes("the advanced fragments come out now and then (" + advanced + ")",
|
||||||
|
advanced > 0);
|
||||||
|
eq("none of them off the ring", Integer.valueOf(0),
|
||||||
|
Integer.valueOf(offRing));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The red barrels give fuel, the crude oil with it. */
|
||||||
|
private static void testFuel() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
boolean fuel = false;
|
||||||
|
int oils = 0;
|
||||||
|
for (int w = 0; w < 120; w++) {
|
||||||
|
List<ItemStack> loot = generator.pileLoot(Blocks.BARREL_RED,
|
||||||
|
-w * 19, 70, w * 29);
|
||||||
|
yes("a red barrel gives something", !loot.isEmpty());
|
||||||
|
for (ItemStack stack : loot) {
|
||||||
|
if (stack.item == ItemRegistry.LOW_GRADE_FUEL) {
|
||||||
|
fuel = true;
|
||||||
|
yes("the fuel is " + stack.count + ", one or two",
|
||||||
|
stack.count >= 1 && stack.count <= 2);
|
||||||
|
}
|
||||||
|
if (stack.item == ItemRegistry.CRUDE_OIL) {
|
||||||
|
oils++;
|
||||||
|
eq("a jug of oil is one", Integer.valueOf(1),
|
||||||
|
Integer.valueOf(stack.count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yes("a red barrel keeps at most two stacks at " + w,
|
||||||
|
loot.size() <= 2);
|
||||||
|
}
|
||||||
|
yes("the red barrels give fuel", fuel);
|
||||||
|
yes("some of them give crude oil (" + oils + ")",
|
||||||
|
oils > 0 && oils < 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A crate feeds a player: the thing inside is something to eat. */
|
||||||
|
private static void testCrate() throws Exception {
|
||||||
|
WorldGenerator generator = new WorldGenerator(1234L);
|
||||||
|
int foods = 0;
|
||||||
|
boolean drink = false;
|
||||||
|
for (int w = 0; w < 170; w++) {
|
||||||
|
List<ItemStack> loot = generator.pileLoot(Blocks.CRATE,
|
||||||
|
w * 23, 70, -w * 31);
|
||||||
|
eq("a crate gives one stack", Integer.valueOf(1),
|
||||||
|
Integer.valueOf(loot.size()));
|
||||||
|
int item = loot.get(0).item;
|
||||||
|
yes("a crate keeps food or drink (" + item + ")",
|
||||||
|
item >= ItemRegistry.APPLE && item <= ItemRegistry.MUSHROOM);
|
||||||
|
foods++;
|
||||||
|
if (item >= ItemRegistry.SMALL_WATER_BOTTLE
|
||||||
|
&& item <= ItemRegistry.WATER_JUG) {
|
||||||
|
drink = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yes("the crates are counted (" + foods + ")", foods > 0);
|
||||||
|
yes("now and then a crate keeps drink", drink);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The heaps a player meets on an actual walk: the chunks over the middle
|
||||||
|
* of the island carry roads, the roads carry heaps, and a heap carries
|
||||||
|
* barrels in a row of two to four.
|
||||||
|
*/
|
||||||
|
private static void testRealWorldHeaps() throws Exception {
|
||||||
|
int heaps = 0;
|
||||||
|
for (int cx = -2; cx <= 2; cx++) {
|
||||||
|
for (int cz = -2; cz <= 2; cz++) {
|
||||||
|
Chunk chunk = chunkOf(new WorldGenerator(1234L), cx, cz);
|
||||||
|
for (int y = 0; y < Chunk.HEIGHT; y++) {
|
||||||
|
for (int z = 0; z < Chunk.WIDTH; z++) {
|
||||||
|
for (int x = 0; x < Chunk.WIDTH; x++) {
|
||||||
|
if (Blocks.isLootable(chunk.block(x, y, z))) {
|
||||||
|
heaps++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yes("the roads about the middle carry heaps (" + heaps + " barrels)",
|
||||||
|
heaps >= 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user