WIP structure stage work

This commit is contained in:
Astrash
2024-11-02 09:16:35 +11:00
parent a385a43250
commit 4388003f35
10 changed files with 509 additions and 15 deletions

View File

@@ -1,6 +1,11 @@
package com.dfsek.terra.addons.generation.structure;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.generation.structure.config.BiomeStructuresTemplate;
import com.dfsek.terra.addons.generation.structure.config.StructureGenerationStageTemplate;
import com.dfsek.terra.addons.generation.structure.config.StructureLayerGridDescription;
import com.dfsek.terra.addons.generation.structure.config.StructureLayerGridDescription.Template;
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.api.addon.BaseAddon;
@@ -8,11 +13,17 @@ import com.dfsek.terra.api.event.events.config.ConfigurationLoadEvent;
import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.inject.annotations.Inject;
import com.dfsek.terra.api.util.reflection.TypeKey;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.api.world.chunk.generation.util.provider.GenerationStageProvider;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
import java.util.function.Supplier;
public class StructureGenerationAddon implements AddonInitializer {
public static final TypeKey<Supplier<ObjectTemplate<GenerationStage>>> STAGE_TYPE_KEY = new TypeKey<>() {
};
@Inject
private Platform platform;
@@ -24,20 +35,24 @@ public class StructureGenerationAddon implements AddonInitializer {
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigPackPreLoadEvent.class)
.then(event -> event.getPack()
.getOrCreateRegistry(GenerationStageProvider.class)
.register(addon.key("STRUCTURE"), pack -> new StructureGenerationStage(platform)))
.failThrough();
platform.getEventManager()
.getHandler(FunctionalEventHandler.class)
.register(addon, ConfigurationLoadEvent.class)
.then(event -> {
if(event.is(Biome.class)) {
event.getLoadedObject(Biome.class).getContext().put(event.load(new BiomeStructuresTemplate()).get());
}
event.getPack().applyLoader(StructureLayerGridDescription.class, Template::new);
event.getPack()
.getOrCreateRegistry(STAGE_TYPE_KEY)
.register(addon.key("STRUCTURE"), StructureGenerationStageTemplate::new);
})
.failThrough();
// platform.getEventManager()
// .getHandler(FunctionalEventHandler.class)
// .register(addon, ConfigurationLoadEvent.class)
// .then(event -> {
// if(event.is(Biome.class)) {
// event.getLoadedObject(Biome.class).getContext().put(event.load(new BiomeStructuresTemplate()).get());
// }
// })
// .failThrough();
}
}

View File

@@ -1,17 +1,35 @@
package com.dfsek.terra.addons.generation.structure;
import com.dfsek.terra.api.Platform;
import com.dfsek.terra.addons.generation.structure.impl.StructureLayerGrid;
import com.dfsek.terra.addons.generation.structure.impl.WorldChunk;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
public class StructureGenerationStage implements GenerationStage {
private final Platform platform;
private final StructureLayerGrid lastLayer;
public StructureGenerationStage(Platform platform) { this.platform = platform; }
public StructureGenerationStage(StructureLayerGrid lastLayer) {
this.lastLayer = lastLayer;
}
@Override
public void populate(ProtoWorld world) {
int chunkX = world.centerChunkX();
int chunkZ = world.centerChunkZ();
System.out.printf("Populating X%d Y%d%n", chunkX, chunkZ);
long seed = world.getSeed();
Chunk writeChunk = new WorldChunk(world, chunkX, chunkZ);
Chunk populatedChunk = lastLayer.getChunk(world, chunkX, chunkZ, seed);
for(int x = 0; x < 16; x++) {
for(int z = 0; z < 16; z++) {
for (int y = world.getMinHeight(); y <= world.getMaxHeight(); ++y) {
writeChunk.setBlock(x, y, z, populatedChunk.getBlock(x, y, z));
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
package com.dfsek.terra.addons.generation.structure.config;
import com.dfsek.tectonic.api.config.template.annotations.Default;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.generation.structure.StructureGenerationStage;
import com.dfsek.terra.addons.generation.structure.impl.StructureLayerGrid;
import com.dfsek.terra.addons.generation.structure.impl.StructureLayerGridImpl;
import com.dfsek.terra.addons.generation.structure.impl.WorldStructureLayerGrid;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
import java.util.ArrayList;
import java.util.List;
public class StructureGenerationStageTemplate implements ObjectTemplate<GenerationStage> {
@Value("layers")
@Default
List<StructureLayerGridDescription> layers = new ArrayList<>();
@Override
public GenerationStage get() {
StructureLayerGrid layer = new WorldStructureLayerGrid();
for (StructureLayerGridDescription description : layers) {
layer = new StructureLayerGridImpl(layer, description.size(), description.padding(), description.structures(), description.distribution());
}
return new StructureGenerationStage(layer);
}
}

View File

@@ -0,0 +1,31 @@
package com.dfsek.terra.addons.generation.structure.config;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
public record StructureLayerGridDescription(int padding, int size, ProbabilityCollection<Structure> structures, NoiseSampler distribution) {
public static class Template implements ObjectTemplate<StructureLayerGridDescription> {
@Value("padding")
private int padding;
@Value("size")
private int size;
@Value("structures.structures")
private ProbabilityCollection<Structure> structures;
@Value("structures.distribution")
private NoiseSampler distribution;
@Override
public StructureLayerGridDescription get() {
return new StructureLayerGridDescription(padding, size, structures, distribution);
}
}
}

View File

@@ -0,0 +1,101 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.chunk.Chunk;
import org.jetbrains.annotations.NotNull;
public class CopyOnWriteDelegatedChunk implements Chunk {
private Chunk delegate;
private boolean writeOccurred;
public CopyOnWriteDelegatedChunk(Chunk delegate) {
this.delegate = delegate;
}
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
if (!writeOccurred) {
writeOccurred = true;
delegate = new OverlayChunk(delegate);
}
// TODO - Tiered caching
// First sparse impl using hashmap, if number of set blocks exceeds certain threshold,
// then swap delegate to vertically segmented chunk using BlockState[vertChunk][x][z][y]
delegate.setBlock(x, y, z, data, physics);
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return delegate.getBlock(x, y, z);
}
@Override
public int getX() {
return delegate.getX();
}
@Override
public int getZ() {
return delegate.getZ();
}
@Override
public ServerWorld getWorld() {
return delegate.getWorld();
}
@Override
public Object getHandle() {
return delegate.getHandle();
}
public static class OverlayChunk implements Chunk {
private final Chunk delegate;
private final int worldMinHeight;
private final BlockState[][][] blocks;
public OverlayChunk(Chunk delegate) {
this.delegate = delegate;
ServerWorld world = delegate.getWorld();
this.worldMinHeight = world.getMinHeight();
this.blocks = new BlockState[16][16][world.getMaxHeight() - worldMinHeight];
}
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
blocks[x][z][y - worldMinHeight] = data;
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
BlockState state = blocks[x][z][y - worldMinHeight];
if (state == null) return delegate.getBlock(x, y, z);
return state;
}
@Override
public int getX() {
return delegate.getX();
}
@Override
public int getZ() {
return delegate.getZ();
}
@Override
public ServerWorld getWorld() {
return delegate.getWorld();
}
@Override
public Object getHandle() {
return delegate.getHandle();
}
}
}

View File

@@ -0,0 +1,158 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.Rotation;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
import com.dfsek.terra.api.util.vector.Vector3Int;
import com.dfsek.terra.api.world.WritableWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a section of the world that contains at most a single structure within the structure layer
*/
public class StructureCell {
private final Chunk[][] chunks;
private final int size;
private final int chunkOriginX;
private final int chunkOriginZ;
public StructureCell(StructureLayerGridImpl layer, ProtoWorld world, SeededVector2Key origin) {
System.out.println("Generating structure cell");
this.size = layer.getGridSizeChunks();
this.chunkOriginX = origin.x * size;
this.chunkOriginZ = origin.z * size;
this.chunks = new Chunk[size][size];
WorldView worldView = new WorldView(world);
for (int x = 0; x < size; ++x) {
for (int z = 0; z < size; ++z) {
int chunkX = origin.x + x;
int chunkZ = origin.z + z;
chunks[x][z] = new CopyOnWriteDelegatedChunk(layer.getPrevious().getChunk(world, chunkX, chunkZ, origin.seed));
}
}
int minCoord = (chunkOriginX + layer.getPadding()) * 16;
int maxCoord = (chunkOriginZ + layer.getGridSizeChunks() - layer.getPadding()) * 16;
// TODO - Make these random within the range
int structureX = (minCoord + maxCoord) / 2;
int structureZ = (minCoord + maxCoord) / 2;
int structureY = (world.getMinHeight() + world.getMaxHeight()) / 2;
Vector3Int structureOrigin = Vector3Int.of(structureX, structureY, structureZ);
Structure structure = layer.getStructures().get(layer.getDistribution(), structureOrigin, origin.seed);
structure.generate(structureOrigin, worldView, null /* TODO */, Rotation.NONE /* TODO */);
}
public Chunk getChunk(int chunkX, int chunkZ) {
return getChunkLocal(chunkX - chunkOriginX, chunkZ - chunkOriginZ);
}
private Chunk getChunkLocal(int ix, int iz) {
if (ix < 0 || ix >= size || iz < 0 || iz >= size)
throw new IndexOutOfBoundsException("Attempted to retrieve chunk outside of structure cell bounds");
return chunks[ix][iz];
}
class WorldView implements WritableWorld {
private static final Logger logger = LoggerFactory.getLogger(WorldView.class);
private final ProtoWorld worldDelegate;
private WorldView(ProtoWorld world) {
this.worldDelegate = world;
}
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
int chunkX = x >> 4;
int chunkZ = z >> 4;
Chunk chunk = getChunkSafe(chunkX, chunkZ);
int xInChunk = x - chunkX << 4;
int zInChunk = z - chunkZ << 4;
chunk.setBlock(xInChunk, y, zInChunk, data);
}
@Override
public Entity spawnEntity(double x, double y, double z, EntityType entityType) {
return worldDelegate.spawnEntity(x, y, z, entityType);
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int chunkX = x >> 4;
int chunkZ = z >> 4;
Chunk chunk = getChunkSafe(chunkX, chunkZ);
int xInChunk = x - chunkX << 4;
int zInChunk = z - chunkZ << 4;
return chunk.getBlock(xInChunk, y, zInChunk);
}
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return worldDelegate.getBlockEntity(x, y, z);
}
@Override
public ChunkGenerator getGenerator() {
return worldDelegate.getGenerator();
}
@Override
public BiomeProvider getBiomeProvider() {
return worldDelegate.getBiomeProvider();
}
@Override
public ConfigPack getPack() {
return worldDelegate.getPack();
}
@Override
public long getSeed() {
return worldDelegate.getSeed();
}
@Override
public int getMaxHeight() {
return worldDelegate.getMaxHeight();
}
@Override
public int getMinHeight() {
return worldDelegate.getMinHeight();
}
@Override
public Object getHandle() {
return worldDelegate.getHandle();
}
private Chunk getChunkSafe(int chunkX, int chunkZ) {
try {
return getChunk(chunkX, chunkZ);
} catch(IndexOutOfBoundsException e) {
logger.warn("Chunk accessed outside permissible structure boundaries, this may cause unexpected behaviour");
return new WorldChunk(worldDelegate, chunkX, chunkZ);
}
}
}
}

View File

@@ -0,0 +1,9 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
public interface StructureLayerGrid {
Chunk getChunk(ProtoWorld world, int chunkX, int chunkZ, long seed);
}

View File

@@ -0,0 +1,65 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.structure.Structure;
import com.dfsek.terra.api.util.cache.SeededVector2Key;
import com.dfsek.terra.api.util.collection.ProbabilityCollection;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class StructureLayerGridImpl implements StructureLayerGrid {
private final StructureLayerGrid previous;
private final Cache<SeededVector2Key, StructureCell> structureCells;
private final int padding;
private final int cellSize;
protected ProbabilityCollection<Structure> getStructures() {
return structures;
}
protected NoiseSampler getDistribution() {
return distribution;
}
private final ProbabilityCollection<Structure> structures;
private final NoiseSampler distribution;
public StructureLayerGridImpl(StructureLayerGrid previous, int size, int padding, ProbabilityCollection<Structure> structures,
NoiseSampler distribution) {
this.previous = previous;
this.padding = padding;
this.structures = structures;
this.distribution = distribution;
this.cellSize = 1 + (size + padding) * 2;
this.structureCells = Caffeine.newBuilder()
.build();
}
@Override
public Chunk getChunk(ProtoWorld world, int chunkX, int chunkZ, long seed) {
int cellX = Math.floorMod(chunkX, cellSize);
int cellZ = Math.floorMod(chunkZ, cellSize);
SeededVector2Key lookupPos = new SeededVector2Key(cellX, cellZ, seed);
StructureCell cell = structureCells.get(lookupPos, pos -> new StructureCell(this, world, pos));
return cell.getChunk(chunkX, chunkZ);
}
protected int getGridSizeChunks() {
return cellSize;
}
protected StructureLayerGrid getPrevious() {
return previous;
}
protected int getPadding() {
return padding;
}
}

View File

@@ -0,0 +1,55 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.WritableWorld;
import com.dfsek.terra.api.world.chunk.Chunk;
import org.jetbrains.annotations.NotNull;
/**
* Temp class that just delegates chunk operations to the world on a block-by-block basis
*/
public class WorldChunk implements Chunk {
private final WritableWorld world;
private final int x;
private final int z;
public WorldChunk(WritableWorld world, int x, int z) {
this.world = world;
this.x = x;
this.z = z;
}
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
world.setBlockState(x, y, z, data, physics);
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return world.getBlockState(x, y, z);
}
@Override
public int getX() {
return x;
}
@Override
public int getZ() {
return z;
}
@Override
public ServerWorld getWorld() {
return null;
}
@Override
public Object getHandle() {
return null;
}
}

View File

@@ -0,0 +1,12 @@
package com.dfsek.terra.addons.generation.structure.impl;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
public class WorldStructureLayerGrid implements StructureLayerGrid {
@Override
public Chunk getChunk(ProtoWorld world, int chunkX, int chunkZ, long seed) {
return new WorldChunk(world, chunkX, chunkZ);
}
}