diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationAddon.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationAddon.java index 3b38c6439..aa0a37654 100644 --- a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationAddon.java +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationAddon.java @@ -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>> 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(); + } } diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationStage.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationStage.java index 18b474fa2..c9254bfaf 100644 --- a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationStage.java +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/StructureGenerationStage.java @@ -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)); + } + } + } } } diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureGenerationStageTemplate.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureGenerationStageTemplate.java new file mode 100644 index 000000000..82608f1fb --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureGenerationStageTemplate.java @@ -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 { + @Value("layers") + @Default + List 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); + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureLayerGridDescription.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureLayerGridDescription.java new file mode 100644 index 000000000..40433d29e --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/config/StructureLayerGridDescription.java @@ -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 structures, NoiseSampler distribution) { + public static class Template implements ObjectTemplate { + + @Value("padding") + private int padding; + + @Value("size") + private int size; + + @Value("structures.structures") + private ProbabilityCollection structures; + + @Value("structures.distribution") + private NoiseSampler distribution; + + @Override + public StructureLayerGridDescription get() { + return new StructureLayerGridDescription(padding, size, structures, distribution); + } + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/CopyOnWriteDelegatedChunk.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/CopyOnWriteDelegatedChunk.java new file mode 100644 index 000000000..2d54333c0 --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/CopyOnWriteDelegatedChunk.java @@ -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(); + } + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureCell.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureCell.java new file mode 100644 index 000000000..c64d79829 --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureCell.java @@ -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); + } + } + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGrid.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGrid.java new file mode 100644 index 000000000..ebd1761f5 --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGrid.java @@ -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); +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGridImpl.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGridImpl.java new file mode 100644 index 000000000..2154eb985 --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/StructureLayerGridImpl.java @@ -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 structureCells; + + private final int padding; + private final int cellSize; + + protected ProbabilityCollection getStructures() { + return structures; + } + + protected NoiseSampler getDistribution() { + return distribution; + } + + private final ProbabilityCollection structures; + private final NoiseSampler distribution; + + public StructureLayerGridImpl(StructureLayerGrid previous, int size, int padding, ProbabilityCollection 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; + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldChunk.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldChunk.java new file mode 100644 index 000000000..45f1a8363 --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldChunk.java @@ -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; + } +} diff --git a/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldStructureLayerGrid.java b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldStructureLayerGrid.java new file mode 100644 index 000000000..4f7ae700a --- /dev/null +++ b/common/addons/generation-stage-structure/src/main/java/com/dfsek/terra/addons/generation/structure/impl/WorldStructureLayerGrid.java @@ -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); + } +}