Added world generation hints, altered ChunkIO slightly, fixed some bugs

- A custom Object can be appended to any ChunkData to save its
generation status
  - Handled by WorldGenerator
  - Persistent
  - Used to determine whether a chunk is ready to be sent to client
- ChunkIO now requires an IOConext
- ChunkIO now requires Data{In,Out}putStreams (as a combination of
Data{In,Out}put and {In,Out}putStream)
- Fixed some minor bugs
This commit is contained in:
OLEGSHA 2021-01-01 23:06:32 +03:00
parent 77599e857d
commit 06f4b4637d
15 changed files with 291 additions and 129 deletions

View File

@ -1,10 +1,11 @@
package ru.windcorp.progressia.common.io; package ru.windcorp.progressia.common.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.DecodingException;
@ -27,10 +28,10 @@ public abstract class ChunkCodec extends Namespaced {
return signature; return signature;
} }
public abstract ChunkData decode(WorldData world, Vec3i position, InputStream data) throws DecodingException, IOException; public abstract ChunkData decode(WorldData world, Vec3i position, DataInputStream input, IOContext context) throws DecodingException, IOException;
public abstract boolean shouldEncode(ChunkData chunk); public abstract boolean shouldEncode(ChunkData chunk, IOContext context);
public abstract void encode(ChunkData chunk, OutputStream output) throws IOException; public abstract void encode(ChunkData chunk, DataOutputStream output, IOContext context) throws IOException;
} }

View File

@ -1,9 +1,9 @@
package ru.windcorp.progressia.common.io; package ru.windcorp.progressia.common.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -11,6 +11,7 @@ import java.util.List;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import gnu.trove.map.TByteObjectMap; import gnu.trove.map.TByteObjectMap;
import gnu.trove.map.hash.TByteObjectHashMap; import gnu.trove.map.hash.TByteObjectHashMap;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.DecodingException;
@ -21,7 +22,7 @@ public class ChunkIO {
private static final TByteObjectMap<ChunkCodec> CODECS_BY_ID = new TByteObjectHashMap<>(); private static final TByteObjectMap<ChunkCodec> CODECS_BY_ID = new TByteObjectHashMap<>();
private static final List<ChunkCodec> CODECS_BY_PRIORITY = new ArrayList<>(); private static final List<ChunkCodec> CODECS_BY_PRIORITY = new ArrayList<>();
public static ChunkData load(WorldData world, Vec3i position, InputStream data) public static ChunkData load(WorldData world, Vec3i position, DataInputStream data, IOContext context)
throws DecodingException, IOException throws DecodingException, IOException
{ {
if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered"); if (CODECS_BY_ID.isEmpty()) throw new IllegalStateException("No codecs registered");
@ -35,7 +36,7 @@ public class ChunkIO {
} }
try { try {
return codec.decode(world, position, data); return codec.decode(world, position, data, context);
} catch (IOException | DecodingException e) { } catch (IOException | DecodingException e) {
throw e; throw e;
} catch (Throwable t) { } catch (Throwable t) {
@ -47,14 +48,14 @@ public class ChunkIO {
} }
} }
public static void save(ChunkData chunk, OutputStream output) public static void save(ChunkData chunk, DataOutputStream output, IOContext context)
throws IOException throws IOException
{ {
ChunkCodec codec = getCodec(chunk); ChunkCodec codec = getCodec(chunk, context);
try { try {
output.write(codec.getSignature()); output.write(codec.getSignature());
codec.encode(chunk, output); codec.encode(chunk, output, context);
} catch (IOException e) { } catch (IOException e) {
throw e; throw e;
} catch (Throwable t) { } catch (Throwable t) {
@ -70,9 +71,9 @@ public class ChunkIO {
return CODECS_BY_ID.get(signature); return CODECS_BY_ID.get(signature);
} }
public static ChunkCodec getCodec(ChunkData chunk) { public static ChunkCodec getCodec(ChunkData chunk, IOContext context) {
for (ChunkCodec codec : CODECS_BY_PRIORITY) { for (ChunkCodec codec : CODECS_BY_PRIORITY) {
if (codec.shouldEncode(chunk)) { if (codec.shouldEncode(chunk, context)) {
return codec; return codec;
} }
} }
@ -82,7 +83,7 @@ public class ChunkIO {
} }
/** /**
* Sorted is order of decreasing priority * Sorted in order of decreasing priority
* @return * @return
*/ */
public static List<ChunkCodec> getCodecs() { public static List<ChunkCodec> getCodecs() {

View File

@ -38,8 +38,8 @@ public class DataBuffer {
} }
}; };
private final DataInput reader = new DataInputStream(inputStream); private final DataInputStream reader = new DataInputStream(inputStream);
private final DataOutput writer = new DataOutputStream(outputStream); private final DataOutputStream writer = new DataOutputStream(outputStream);
public DataBuffer(int capacity) { public DataBuffer(int capacity) {
this.buffer = new TByteArrayList(capacity); this.buffer = new TByteArrayList(capacity);
@ -53,25 +53,23 @@ public class DataBuffer {
this.buffer = new TByteArrayList(copyFrom.buffer); this.buffer = new TByteArrayList(copyFrom.buffer);
} }
public InputStream getInputStream() { public DataInputStream getReader() {
position = 0;
return inputStream;
}
public OutputStream getOutputStream() {
buffer.resetQuick();
return outputStream;
}
public DataInput getReader() {
position = 0; position = 0;
return reader; return reader;
} }
public DataOutput getWriter() { public InputStream getInputStream() {
return getReader();
}
public DataOutputStream getWriter() {
buffer.resetQuick(); buffer.resetQuick();
return writer; return writer;
} }
public OutputStream getOutputStream() {
return getWriter();
}
public int getSize() { public int getSize() {
return buffer.size(); return buffer.size();

View File

@ -59,6 +59,8 @@ implements GenericChunk<
BLOCK_FACE_COUNT BLOCK_FACE_COUNT
]; ];
private Object generationHint = null;
private final Collection<ChunkDataListener> listeners = private final Collection<ChunkDataListener> listeners =
Collections.synchronizedCollection(new ArrayList<>()); Collections.synchronizedCollection(new ArrayList<>());
@ -238,6 +240,14 @@ implements GenericChunk<
getListeners().forEach(l -> l.beforeChunkUnloaded(this)); getListeners().forEach(l -> l.beforeChunkUnloaded(this));
} }
public Object getGenerationHint() {
return generationHint;
}
public void setGenerationHint(Object generationHint) {
this.generationHint = generationHint;
}
/** /**
* Implementation of {@link TileDataStack} used internally by {@link ChunkData} to * Implementation of {@link TileDataStack} used internally by {@link ChunkData} to
* actually store the tiles. This is basically an array wrapper with reporting * actually store the tiles. This is basically an array wrapper with reporting

View File

@ -6,6 +6,7 @@ import java.io.IOException;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.io.ChunkIO; import ru.windcorp.progressia.common.io.ChunkIO;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.util.DataBuffer; import ru.windcorp.progressia.common.util.DataBuffer;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
@ -26,7 +27,7 @@ public class PacketSendChunk extends PacketChunkChange {
this.position.set(chunk.getX(), chunk.getY(), chunk.getZ()); this.position.set(chunk.getX(), chunk.getY(), chunk.getZ());
try { try {
ChunkIO.save(chunk, this.data.getOutputStream()); ChunkIO.save(chunk, this.data.getWriter(), IOContext.COMMS);
} catch (IOException e) { } catch (IOException e) {
// Impossible // Impossible
} }
@ -50,7 +51,7 @@ public class PacketSendChunk extends PacketChunkChange {
@Override @Override
public void apply(WorldData world) { public void apply(WorldData world) {
try { try {
world.addChunk(ChunkIO.load(world, position, data.getInputStream())); world.addChunk(ChunkIO.load(world, position, data.getReader(), IOContext.COMMS));
} catch (DecodingException | IOException e) { } catch (DecodingException | IOException e) {
CrashReports.report(e, "Could not load chunk"); CrashReports.report(e, "Could not load chunk");
} }

View File

@ -101,8 +101,8 @@ public interface GenericWorld<
return stack.get(layer); return stack.get(layer);
} }
default boolean isChunkLoaded(Vec3i pos) { default boolean isChunkLoaded(Vec3i chunkPos) {
return getChunk(pos) != null; return getChunk(chunkPos) != null;
} }
default boolean isBlockLoaded(Vec3i blockInWorld) { default boolean isBlockLoaded(Vec3i blockInWorld) {

View File

@ -33,9 +33,12 @@ public class ChunkManager {
public void updateQueues(Player player) { public void updateQueues(Player player) {
toSend.clear(); toSend.clear();
toSend.addAll(requested);
toSend.removeAll(visible); requested.forEachIn(server.getWorld(), chunk -> {
toSend.retainAll(loaded); if (!chunk.isReady()) return;
if (visible.contains(chunk)) return;
toSend.add(chunk);
});
toRevoke.clear(); toRevoke.clear();
toRevoke.addAll(visible); toRevoke.addAll(visible);
@ -120,13 +123,13 @@ public class ChunkManager {
WorldData world = getServer().getWorld().getData(); WorldData world = getServer().getWorld().getData();
ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world); ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer());
if (chunk == null) { if (chunk != null) {
chunk = getServer().getWorld().generate(chunkPos); world.addChunk(chunk);
} else {
getServer().getWorld().generate(chunkPos);
} }
world.addChunk(chunk);
} }
public void unloadChunk(Vec3i chunkPos) { public void unloadChunk(Vec3i chunkPos) {
@ -143,7 +146,7 @@ public class ChunkManager {
world.removeChunk(chunk); world.removeChunk(chunk);
TestWorldDiskIO.saveChunk(chunk); TestWorldDiskIO.saveChunk(chunk, getServer());
} }

View File

@ -56,8 +56,8 @@ public class ServerThread implements Runnable {
try { try {
server.tick(); server.tick();
ticker.runOneTick(); ticker.runOneTick();
} catch (Exception e) { } catch (Throwable e) {
CrashReports.report(e, "Got an exception in the server thread"); CrashReports.report(e, "Got a throwable in the server thread");
} }
} }

View File

@ -4,8 +4,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import gnu.trove.TCollections; import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TIntObjectMap;
@ -65,7 +63,6 @@ public class ClientManager {
getServer().getPlayerManager().getPlayers().add(player); getServer().getPlayerManager().getPlayers().add(player);
PacketSetLocalPlayer packet = new PacketSetLocalPlayer(); PacketSetLocalPlayer packet = new PacketSetLocalPlayer();
LogManager.getLogger().info("Sending local player ID {}", EntityData.formatEntityId(entity.getEntityId()));
packet.set(entity.getEntityId()); packet.set(entity.getEntityId());
client.sendPacket(packet); client.sendPacket(packet);
} }

View File

@ -86,6 +86,10 @@ public class ChunkLogic implements GenericChunk<
return data; return data;
} }
public boolean isReady() {
return getWorld().getGenerator().isChunkReady(getData().getGenerationHint());
}
public boolean hasTickingBlocks() { public boolean hasTickingBlocks() {
return !tickingBlocks.isEmpty(); return !tickingBlocks.isEmpty();
} }

View File

@ -0,0 +1,48 @@
package ru.windcorp.progressia.server.world.generation;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Objects;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
public abstract class AbstractWorldGenerator<H> extends WorldGenerator {
private final Class<H> hintClass;
public AbstractWorldGenerator(String id, Class<H> hintClass) {
super(id);
this.hintClass = Objects.requireNonNull(hintClass, "hintClass");
}
@Override
public final Object readGenerationHint(DataInputStream input) throws IOException, DecodingException {
return doReadGenerationHint(input);
}
@Override
public final void writeGenerationHint(DataOutputStream output, Object hint) throws IOException {
doWriteGenerationHint(output, hintClass.cast(hint));
}
protected abstract H doReadGenerationHint(DataInputStream input) throws IOException, DecodingException;
protected abstract void doWriteGenerationHint(DataOutputStream output, H hint) throws IOException;
@Override
public final boolean isChunkReady(Object hint) {
return checkIsChunkReady(hintClass.cast(hint));
}
protected abstract boolean checkIsChunkReady(H hint);
protected H getHint(ChunkData chunk) {
return hintClass.cast(chunk.getGenerationHint());
}
protected void setHint(ChunkData chunk, H hint) {
chunk.setGenerationHint(hint);
}
}

View File

@ -1,16 +1,25 @@
package ru.windcorp.progressia.server.world.generation; package ru.windcorp.progressia.server.world.generation;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
public abstract class WorldGenerator extends Namespaced { public abstract class WorldGenerator extends Namespaced {
public WorldGenerator(String id) { WorldGenerator(String id) {
super(id); super(id);
// package-private constructor; extend AbstractWorldGeneration
} }
public abstract ChunkData generate(Vec3i chunkPos, WorldData world); public abstract ChunkData generate(Vec3i chunkPos, WorldData world);
public abstract Object readGenerationHint(DataInputStream input) throws IOException, DecodingException;
public abstract void writeGenerationHint(DataOutputStream output, Object hint) throws IOException;
public abstract boolean isChunkReady(Object hint);
} }

View File

@ -15,5 +15,11 @@ public abstract class CachedChunkChange<P extends PacketChunkChange> extends Cac
public void getRelevantChunk(Vec3i output) { public void getRelevantChunk(Vec3i output) {
getPacket().getAffectedChunk(output); getPacket().getAffectedChunk(output);
} }
@Override
protected Vec3i getAffectedChunk(Vec3i output) {
getPacket().getAffectedChunk(output);
return output;
}
} }

View File

@ -5,8 +5,6 @@ import java.io.DataInputStream;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -17,6 +15,7 @@ import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.map.hash.TObjectIntHashMap;
import ru.windcorp.jputil.functions.ThrowingConsumer; import ru.windcorp.jputil.functions.ThrowingConsumer;
import ru.windcorp.progressia.common.io.ChunkCodec; import ru.windcorp.progressia.common.io.ChunkCodec;
import ru.windcorp.progressia.common.state.IOContext;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
@ -57,7 +56,7 @@ public class TestChunkCodec extends ChunkCodec {
} }
@Override @Override
public boolean shouldEncode(ChunkData chunk) { public boolean shouldEncode(ChunkData chunk, IOContext context) {
return true; return true;
} }
@ -66,9 +65,7 @@ public class TestChunkCodec extends ChunkCodec {
*/ */
@Override @Override
public ChunkData decode(WorldData world, Vec3i position, InputStream inputStream) throws DecodingException, IOException { public ChunkData decode(WorldData world, Vec3i position, DataInputStream input, IOContext context) throws DecodingException, IOException {
DataInput input = new DataInputStream(inputStream);
BlockData[] blockPalette = readBlockPalette(input); BlockData[] blockPalette = readBlockPalette(input);
TileData[] tilePalette = readTilePalette(input); TileData[] tilePalette = readTilePalette(input);
@ -136,10 +133,7 @@ public class TestChunkCodec extends ChunkCodec {
*/ */
@Override @Override
public void encode(ChunkData chunk, OutputStream outputStream) throws IOException { public void encode(ChunkData chunk, DataOutputStream output, IOContext context) throws IOException {
DataOutput output = new DataOutputStream(outputStream);
Palette<BlockData> blockPalette = createBlockPalette(chunk); Palette<BlockData> blockPalette = createBlockPalette(chunk);
Palette<TileData> tilePalette = createTilePalette(chunk); Palette<TileData> tilePalette = createTilePalette(chunk);

View File

@ -1,8 +1,16 @@
package ru.windcorp.progressia.test.gen; package ru.windcorp.progressia.test.gen;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Random;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.Coordinates; import ru.windcorp.progressia.common.world.Coordinates;
import ru.windcorp.progressia.common.world.DecodingException;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.block.BlockDataRegistry; import ru.windcorp.progressia.common.world.block.BlockDataRegistry;
@ -10,28 +18,46 @@ import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataRegistry; import ru.windcorp.progressia.common.world.tile.TileDataRegistry;
import ru.windcorp.progressia.server.world.WorldLogic; import ru.windcorp.progressia.server.world.WorldLogic;
import ru.windcorp.progressia.server.world.generation.WorldGenerator; import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator;
public class TestWorldGenerator extends WorldGenerator {
public class TestWorldGenerator extends AbstractWorldGenerator<Boolean> {
public TestWorldGenerator(WorldLogic world) { public TestWorldGenerator(WorldLogic world) {
super("Test:WorldGenerator"); super("Test:WorldGenerator", Boolean.class);
}
@Override
protected Boolean doReadGenerationHint(DataInputStream input) throws IOException, DecodingException {
return input.readBoolean();
}
@Override
protected void doWriteGenerationHint(DataOutputStream output, Boolean hint) throws IOException {
output.writeBoolean(hint);
}
@Override
protected boolean checkIsChunkReady(Boolean hint) {
return hint;
} }
@Override @Override
public ChunkData generate(Vec3i chunkPos, WorldData world) { public ChunkData generate(Vec3i chunkPos, WorldData world) {
ChunkData chunk = generateUnpopulated(chunkPos, world);
world.addChunk(chunk);
findAndPopulate(chunkPos, world);
return chunk;
}
private ChunkData generateUnpopulated(Vec3i chunkPos, WorldData world) {
ChunkData chunk = new ChunkData(chunkPos, world); ChunkData chunk = new ChunkData(chunkPos, world);
chunk.setGenerationHint(false);
final int bpc = ChunkData.BLOCKS_PER_CHUNK; final int bpc = ChunkData.BLOCKS_PER_CHUNK;
BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt");
BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone"); BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone");
BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
TileData grass = TileDataRegistry.getInstance().get("Test:Grass");
TileData stones = TileDataRegistry.getInstance().get("Test:Stones");
TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers");
TileData sand = TileDataRegistry.getInstance().get("Test:Sand");
final float maxHeight = 32; final float maxHeight = 32;
final float rho = 2000; final float rho = 2000;
@ -48,70 +74,134 @@ public class TestWorldGenerator extends WorldGenerator {
} }
} }
Vec3i pos = new Vec3i(); VectorUtil.iterateCuboid(0, 0, 0, bpc, bpc, bpc, pos -> {
int layer = pos.z - heightMap[pos.x][pos.y];
for (pos.x = 0; pos.x < bpc; ++pos.x) {
for (pos.y = 0; pos.y < bpc; ++pos.y) { if (layer < -4) {
for (pos.z = 0; pos.z < bpc; ++pos.z) { chunk.setBlock(pos, stone, false);
} else if (layer < 0) {
int layer = pos.z - heightMap[pos.x][pos.y]; chunk.setBlock(pos, dirt, false);
} else {
if (layer < -4) { chunk.setBlock(pos, air, false);
chunk.setBlock(pos, stone, false);
} else if (layer < 0) {
chunk.setBlock(pos, dirt, false);
} else {
chunk.setBlock(pos, air, false);
}
}
} }
} });
for (int x = 0; x < bpc; ++x) {
for (int y = 0; y < bpc; ++y) {
// int z = heightMap[x][y];
for (int z = 0; z < bpc; ++z) {
pos.set(x, y, z);
int layer = pos.z - heightMap[x][y];
if (layer == -1) {
chunk.getTiles(pos, BlockFace.TOP).add(grass);
for (BlockFace face : BlockFace.getFaces()) {
if (face.getVector().z != 0) continue;
pos.add(face.getVector());
if (!ChunkData.isInBounds(pos) || (chunk.getBlock(pos) == air)) {
pos.sub(face.getVector());
chunk.getTiles(pos, face).add(grass);
} else {
pos.sub(face.getVector());
}
}
int hash = x*x * 19 ^ y*y * 41 ^ pos.z*pos.z * 147;
if (hash % 5 == 0) {
chunk.getTiles(pos, BlockFace.TOP).addFarthest(sand);
}
hash = x*x * 13 ^ y*y * 37 ^ pos.z*pos.z * 129;
if (hash % 5 == 0) {
chunk.getTiles(pos, BlockFace.TOP).addFarthest(stones);
}
hash = x*x * 17 ^ y*y * 39 ^ pos.z*pos.z * 131;
if (hash % 9 == 0) {
chunk.getTiles(pos, BlockFace.TOP).addFarthest(flowers);
}
}
}
}
}
return chunk; return chunk;
} }
private void findAndPopulate(Vec3i changePos, WorldData world) {
VectorUtil.iterateCuboidAround(changePos, 3, candidatePos -> {
if (canBePopulated(candidatePos, world)) {
populate(candidatePos, world);
}
});
}
private boolean canBePopulated(Vec3i candidatePos, WorldData world) {
Vec3i cursor = Vectors.grab3i();
ChunkData candidate = world.getChunk(candidatePos);
if (candidate == null || isChunkReady(candidate.getGenerationHint())) return false;
for (int dx = -1; dx <= 1; ++dx) {
cursor.x = candidatePos.x + dx;
for (int dy = -1; dy <= 1; ++dy) {
cursor.y = candidatePos.y + dy;
for (int dz = -1; dz <= 1; ++dz) {
if ((dx | dy | dz) == 0) continue;
cursor.z = candidatePos.z + dz;
ChunkData chunk = world.getChunk(cursor);
if (chunk == null) {
return false;
}
}
}
}
Vectors.release(cursor);
return true;
}
private void populate(Vec3i chunkPos, WorldData world) {
Random random = new Random(chunkPos.x + chunkPos.y + chunkPos.z);
ChunkData chunk = world.getChunk(chunkPos);
assert chunk != null : "Something went wrong when populating chunk at (" + chunkPos.x + "; " + chunkPos.y + "; " + chunkPos.z + ")";
BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
Vec3i biw = new Vec3i();
int minX = Coordinates.getInWorld(chunkPos.x, 0);
int maxX = Coordinates.getInWorld(chunkPos.x + 1, 0);
int minY = Coordinates.getInWorld(chunkPos.y, 0);
int maxY = Coordinates.getInWorld(chunkPos.y + 1, 0);
int minZ = Coordinates.getInWorld(chunkPos.z, 0);
int maxZ = Coordinates.getInWorld(chunkPos.z + 1, 0);
for (biw.x = minX; biw.x < maxX; ++biw.x) {
for (biw.y = minY; biw.y < maxY; ++biw.y) {
for (biw.z = minZ; biw.z < maxZ + 1 && world.getBlock(biw) != air; ++biw.z);
biw.z -= 1;
if (biw.z == maxZ) continue;
if (biw.z < minZ) continue;
addTiles(chunk, biw, world, random);
}
}
chunk.setGenerationHint(true);
}
private void addTiles(ChunkData chunk, Vec3i biw, WorldData world, Random random) {
addGrass(chunk, biw, world, random);
addDecor(chunk, biw, world, random);
}
private void addGrass(ChunkData chunk, Vec3i biw, WorldData world, Random random) {
BlockData air = BlockDataRegistry.getInstance().get("Test:Air");
TileData grass = TileDataRegistry.getInstance().get("Test:Grass");
world.getTiles(biw, BlockFace.TOP).add(grass);
for (BlockFace face : BlockFace.getFaces()) {
if (face.getVector().z != 0) continue;
biw.add(face.getVector());
if (world.getBlock(biw) == air) {
biw.sub(face.getVector());
world.getTiles(biw, face).add(grass);
} else {
biw.sub(face.getVector());
}
}
}
private void addDecor(ChunkData chunk, Vec3i biw, WorldData world, Random random) {
if (random.nextInt(8) == 0) {
world.getTiles(biw, BlockFace.TOP).addFarthest(
TileDataRegistry.getInstance().get("Test:Sand")
);
}
if (random.nextInt(8) == 0) {
world.getTiles(biw, BlockFace.TOP).addFarthest(
TileDataRegistry.getInstance().get("Test:Stones")
);
}
if (random.nextInt(8) == 0) {
world.getTiles(biw, BlockFace.TOP).addFarthest(
TileDataRegistry.getInstance().get("Test:YellowFlowers")
);
}
}
} }