Свалки у дорог: бочки серые/синие/красные и ящики еды как блоки, лут по детерминированным таблицам, синие на кольце дают продвинутые фрагменты

This commit is contained in:
sasheg dev
2026-10-23 07:50:22 +03:00
parent 07ed4ce02d
commit 5b81db7c39
8 changed files with 602 additions and 3 deletions
@@ -48,6 +48,23 @@ public final class RsxBlockMap {
net.minecraft.item.EnumDyeColor.GRAY);
STATES[net.rustnotmc.protocol.Blocks.SNOW] =
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() {
@@ -33,9 +33,17 @@ public final class Blocks {
/** The surface of the high and cold places. */
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. */
public static final byte MIN = AIR;
public static final byte MAX = SNOW;
public static final byte MAX = CRATE;
private Blocks() {
}
@@ -52,6 +60,10 @@ public final class Blocks {
case LEAVES: return "leaves";
case ASPHALT: return "asphalt";
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 + ")";
}
}
@@ -66,6 +78,18 @@ public final class Blocks {
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) {
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(SULFUR, "Sulfur Ore").rid("sulfur_ore"));
define(ItemType.of(SCRAP, "Scrap").rid("scrap"));
// The fragments of the blueprints. The remake does not know them: they
// come out of the loot crates, and the crates are still to come.
// The fragments of the blueprints. They come out of the blue barrels
// 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(ADVANCED_FRAGMENT, "Advanced Blueprint Fragment"));
define(ItemType.of(METAL_PIPE, "Metal Pipe").rid("metalpipe"));
+1
View File
@@ -50,6 +50,7 @@ $suites = @(
'net.rustnotmc.server.world.WorldTest',
'net.rustnotmc.server.world.NodeTest',
'net.rustnotmc.server.world.LodTest',
'net.rustnotmc.server.world.PilesTest',
'net.rustnotmc.server.item.InventoryTest',
'net.rustnotmc.server.craft.CraftTest',
'net.rustnotmc.server.hitbox.HitboxTest',
@@ -14,6 +14,7 @@ import net.rustnotmc.protocol.ChunkFormat;
import net.rustnotmc.protocol.Frame;
import net.rustnotmc.protocol.FrameDecoder;
import net.rustnotmc.protocol.ItemRegistry;
import net.rustnotmc.protocol.ItemStack;
import net.rustnotmc.protocol.ItemType;
import net.rustnotmc.protocol.Packet;
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));
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.server.world().setBlock(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;
case Blocks.ASPHALT:
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:
return 0xE9EDEF;
case Blocks.WATER:
@@ -2,6 +2,11 @@ package net.rustnotmc.server.world;
import net.rustnotmc.protocol.Blocks;
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.
@@ -58,6 +63,14 @@ public final class WorldGenerator {
/** How many blocks of canopy a tree may reach. */
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 int size;
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();
}
}
/**
* 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.
*
@@ -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);
}
}