Skip to content

Commit

Permalink
feat: Generate Holystone ruins on terrain surface, extending downward…
Browse files Browse the repository at this point in the history
…s to Bronze Dungeon entrance rooms (The-Aether-Team#2112)
  • Loading branch information
Drullkus authored Apr 24, 2024
1 parent d522ec4 commit 422dc12
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 1.20.4 2024-04-01T22:26:45.2905608 Registries
// 1.20.4 2024-04-15T17:58:29.068443 Registries
82530938d7de52efd594f6bae0aaef2428f09853 data/aether/aether/moa_type/black.json
2db7c2a7f294f52b13239ecb53053bd4ffa4bb69 data/aether/aether/moa_type/blue.json
5eb707f4fb2ff05d33f1718f3e8ca61371af5a62 data/aether/aether/moa_type/white.json
Expand Down Expand Up @@ -72,7 +72,7 @@ e9682cb0ca5e9c56fecaa7056d4a2c97ce020929 data/aether/worldgen/placed_feature/sky
9faf4f67796cf1beefbffca6684bbcd570bf6104 data/aether/worldgen/placed_feature/water_spring.json
fe3ea4829de750180b45fa66467bf3ebe03f4699 data/aether/worldgen/placed_feature/white_flower_patch.json
7ef232b5965cbd857c69c2c39f5981cad49cb48b data/aether/worldgen/placed_feature/zanite_ore.json
de4ee0a66f6890f054fea2a350ea805e2e248bbe data/aether/worldgen/structure/bronze_dungeon.json
e31e83eab18ce3590edc164a5076286aba9b06eb data/aether/worldgen/structure/bronze_dungeon.json
e534342882ca8b052eed85db4d9ca34f01e094d1 data/aether/worldgen/structure/gold_dungeon.json
745c868f238581112eee9397e9a196e1b9d28266 data/aether/worldgen/structure/large_aercloud.json
c9e538d5b18f28c8a8f968815a7ee2294a33f19b data/aether/worldgen/structure/silver_dungeon.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@
"spawns": []
}
},
"step": "surface_structures"
"step": "top_layer_modification"
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static void bootstrap(BootstapContext<Structure> context) {
context.register(BRONZE_DUNGEON, new BronzeDungeonStructure(AetherStructureBuilders.structure(
biomes.getOrThrow(AetherTags.Biomes.HAS_BRONZE_DUNGEON),
mobSpawnsPiece,
GenerationStep.Decoration.SURFACE_STRUCTURES,
GenerationStep.Decoration.TOP_LAYER_MODIFICATION,
TerrainAdjustment.NONE),
8, 32, 24));
context.register(SILVER_DUNGEON, new SilverDungeonStructure(AetherStructureBuilders.structure(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public Optional<GenerationStub> findGenerationPoint(GenerationContext context) {

private void generatePieces(StructurePiecesBuilder builder, Structure.GenerationContext context, BlockPos startPos) {
BronzeDungeonBuilder graph = new BronzeDungeonBuilder(context, this.maxRooms);
graph.initializeDungeon(startPos, context.chunkPos(), builder);
graph.initializeDungeon(startPos, context, builder);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.aetherteam.aether.Aether;
import com.aetherteam.aether.world.structurepiece.bronzedungeon.BronzeBossRoom;
import com.aetherteam.aether.world.structurepiece.bronzedungeon.BronzeDungeonRoom;
import com.aetherteam.aether.world.structurepiece.bronzedungeon.BronzeDungeonSurfaceRuins;
import com.aetherteam.aether.world.structurepiece.bronzedungeon.BronzeTunnel;
import com.aetherteam.aether.world.structurepiece.golddungeon.*;
import com.aetherteam.aether.world.structurepiece.silverdungeon.SilverBossRoom;
Expand All @@ -23,6 +24,7 @@ public class AetherStructurePieceTypes {
public static final DeferredHolder<StructurePieceType, StructurePieceType> BRONZE_BOSS_ROOM = register("BBossRoom", BronzeBossRoom::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> BRONZE_DUNGEON_ROOM = register("BDungeonRoom", BronzeDungeonRoom::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> BRONZE_TUNNEL = register("BTunnel", BronzeTunnel::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> BRONZE_SURFACE_RUINS = register("BSurface", BronzeDungeonSurfaceRuins::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> SILVER_TEMPLE_PIECE = register("STemplePiece", SilverTemplePiece::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> SILVER_FLOOR_PIECE = register("SFloorPiece", SilverFloorPiece::new);
public static final DeferredHolder<StructurePieceType, StructurePieceType> SILVER_DUNGEON_ROOM = register("SDungeonRoom", SilverDungeonRoom::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
Expand Down Expand Up @@ -62,7 +63,7 @@ public BronzeDungeonBuilder(Structure.GenerationContext context, int maxSize) {
this.maxSize = Math.max(3, maxSize);
}

public void initializeDungeon(BlockPos startPos, ChunkPos chunkPos, StructurePiecesBuilder builder) {
public void initializeDungeon(BlockPos startPos, Structure.GenerationContext genContext, StructurePiecesBuilder builder) {
StructureTemplate bossTemplate = this.context.structureTemplateManager().getOrCreate(new ResourceLocation(Aether.MODID, "bronze_dungeon/boss_room"));

Rotation rotation = getBossRoomRotation(startPos, startPos.offset(bossTemplate.getSize()));
Expand All @@ -81,13 +82,16 @@ public void initializeDungeon(BlockPos startPos, ChunkPos chunkPos, StructurePie
this.nodes.add(chestRoom);
new Connection(bossRoom, chestRoom, hallway, direction);

ChunkPos chunkPos = genContext.chunkPos();

for (int i = 2; i < this.maxSize - 1; ++i) {
this.propagateRooms(chestRoom, chunkPos, false);
}

this.propagateRooms(chestRoom, chunkPos, true);
StructurePiece lobby = this.nodes.get(this.nodes.size() - 1);
this.buildEndTunnel(lobby, startPos);
this.buildSurfaceTunnel(lobby, genContext.heightAccessor(), genContext.chunkGenerator(), genContext.randomState());

this.populatePiecesBuilder(builder);
}
Expand Down Expand Up @@ -164,6 +168,23 @@ private void buildEndTunnel(StructurePiece lobby, BlockPos origin) {
this.nodes.addAll(longestTunnel);
}

private void buildSurfaceTunnel(StructurePiece lobby, LevelHeightAccessor level, ChunkGenerator chunkGenerator, RandomState randomState) {
BoundingBox lobbyBounds = lobby.getBoundingBox();
BlockPos entranceRoomCenter = lobbyBounds.getCenter();
int topYSolid = chunkGenerator.getFirstOccupiedHeight(entranceRoomCenter.getX(), entranceRoomCenter.getZ(), Heightmap.Types.OCEAN_FLOOR_WG, level, randomState);

int shrink = 3;
BoundingBox upwardsTunnelBox = new BoundingBox(
lobbyBounds.minX() + shrink,
lobbyBounds.maxY() + 1, // Right above the lobby's ceiling blocks
lobbyBounds.minZ() + shrink,
lobbyBounds.maxX() - shrink,
topYSolid + 4, // A few extra blocks above the surface centered in this box
lobbyBounds.maxZ() - shrink
);
this.nodes.add(new BronzeDungeonSurfaceRuins(upwardsTunnelBox));
}

/**
* Builds a tunnel from a symmetrical room to make an entrance.
*
Expand Down Expand Up @@ -356,4 +377,4 @@ public StructurePiece endPiece() {
return this.end;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.aetherteam.aether.world.structurepiece.bronzedungeon;

import com.aetherteam.aether.AetherTags;
import com.aetherteam.aether.block.AetherBlockStateProperties;
import com.aetherteam.aether.block.AetherBlocks;
import com.aetherteam.aether.data.resources.AetherFeatureStates;
import com.aetherteam.aether.world.structurepiece.AetherStructurePieceTypes;
import com.aetherteam.nitrogen.data.resources.builders.NitrogenConfiguredFeatureBuilders;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.RandomSource;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import net.minecraft.world.level.levelgen.feature.stateproviders.WeightedStateProvider;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;

import java.util.ArrayList;
import java.util.List;

public class BronzeDungeonSurfaceRuins extends StructurePiece {
private static final BlockStateProvider BLOCKS = new WeightedStateProvider(SimpleWeightedRandomList.<BlockState>builder()
.add(AetherBlocks.HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true), 3)
.add(AetherBlocks.MOSSY_HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true))
.build());
private static final BlockStateProvider TOPS = new WeightedStateProvider(SimpleWeightedRandomList.<BlockState>builder()
.add(AetherBlocks.HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true), 6)
.add(AetherBlocks.HOLYSTONE_SLAB.get().defaultBlockState(), 6)
.add(AetherBlocks.MOSSY_HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true), 3)
.add(AetherBlocks.MOSSY_HOLYSTONE_SLAB.get().defaultBlockState(), 3)
.build());
private static final BlockStateProvider BOTTOMS = new WeightedStateProvider(SimpleWeightedRandomList.<BlockState>builder()
.add(AetherBlocks.HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true), 6)
.add(AetherBlocks.HOLYSTONE_SLAB.get().defaultBlockState().setValue(BlockStateProperties.SLAB_TYPE, SlabType.TOP), 6)
.add(AetherBlocks.MOSSY_HOLYSTONE.get().defaultBlockState().setValue(AetherBlockStateProperties.DOUBLE_DROPS, true), 3)
.add(AetherBlocks.MOSSY_HOLYSTONE_SLAB.get().defaultBlockState().setValue(BlockStateProperties.SLAB_TYPE, SlabType.TOP), 3)
.build());
private static final ConfiguredFeature<?, ? extends Feature<?>> MIXED_FLOWER_PATCH = new ConfiguredFeature<>(Feature.FLOWER, NitrogenConfiguredFeatureBuilders.grassPatch(new WeightedStateProvider(SimpleWeightedRandomList.<BlockState>builder()
.add(AetherFeatureStates.PURPLE_FLOWER, 1)
.add(AetherFeatureStates.WHITE_FLOWER, 1)
), 24));

public BronzeDungeonSurfaceRuins(BoundingBox horizontalBounds) {
super(AetherStructurePieceTypes.BRONZE_SURFACE_RUINS.value(), 0, horizontalBounds);

this.setOrientation(Direction.SOUTH);
}

public BronzeDungeonSurfaceRuins(StructurePieceSerializationContext context, CompoundTag nbt) {
super(AetherStructurePieceTypes.BRONZE_SURFACE_RUINS.value(), nbt);

this.setOrientation(Direction.SOUTH);
}

@Override
public void postProcess(WorldGenLevel level, StructureManager manager, ChunkGenerator chunkGen, RandomSource random, BoundingBox chunkBounds, ChunkPos chunkPos, BlockPos structureBottomCenter) {
for (int x = this.boundingBox.minX(); x <= this.boundingBox.maxX(); x++) {
this.generateTunnelWallColumn(level, random, chunkBounds, x, this.boundingBox.minZ());
this.generateTunnelWallColumn(level, random, chunkBounds, x, this.boundingBox.maxZ());
}

for (int z = this.boundingBox.minZ() + 1; z < this.boundingBox.maxZ(); z++) {
this.generateTunnelWallColumn(level, random, chunkBounds, this.boundingBox.minX(), z);
this.generateTunnelWallColumn(level, random, chunkBounds, this.boundingBox.maxX(), z);
}

BlockPos pieceCenter = this.boundingBox.getCenter();
if (chunkBounds.isInside(pieceCenter)) {
BlockPos aboveTopBlock = level.getHeightmapPos(Heightmap.Types.OCEAN_FLOOR_WG, chunkBounds.getCenter());

MIXED_FLOWER_PATCH.place(level, chunkGen, random, aboveTopBlock);
}
}

private void generateTunnelWallColumn(WorldGenLevel level, RandomSource random, BoundingBox chunkBounds, int x, int z) {
if (!chunkBounds.isInside(x, this.boundingBox.minY(), z))
return;

// Trimmer is applied only in a checkerboard pattern plus some random extra, but generates suspicious rocks on the surface using tips of the ragged tunnel walls
// Calling the random twice changes the shape of rng distribution, combinations favoring values towards average than outliers
int trimmer = ((x + z) & 0b1) == 1 || random.nextBoolean() ? random.nextInt(4) + random.nextInt(4) + 1 : 0;
int wallColumnHeight = Math.max(random.nextInt(2) - random.nextInt(2) - trimmer, -2);

int ySurfaceAir = level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z);
BlockPos wallColumnStart = new BlockPos(x, ySurfaceAir, z);

this.placeColumnBlocks(level, random, this.scanColumnForPlacement(level, random, wallColumnHeight, ySurfaceAir, wallColumnStart));

BlockPos topPos = wallColumnStart.above(wallColumnHeight);
// Use full block only if the pillar top is too short (or is underground)
BlockStateProvider topProvider = wallColumnHeight < 1 ? BLOCKS : TOPS;
level.setBlock(topPos, topProvider.getState(random, topPos), 0b11);
}

private void placeColumnBlocks(WorldGenLevel level, RandomSource random, List<BlockPos> forPlacement) {
if (forPlacement.isEmpty())
return;

// The list must be ordered with lowest Y values first. scanColumnForPlacement() already handles that, so no sorting is necessary
BlockPos lastPos = forPlacement.get(0).below();

for (BlockPos posAt : forPlacement) {
boolean hasSkippedGap = posAt.getY() - lastPos.getY() != 1; // If the delta spikes when iterating elements, then the scanning loop has likely skipped over a gap of air

if (hasSkippedGap) {
BlockStateProvider tailProvider = level.getBlockState(posAt).isAir() ? BOTTOMS : BLOCKS;
level.setBlock(posAt, tailProvider.getState(random, posAt), 0b11);

BlockPos capPos = lastPos.above();
BlockStateProvider capProvider = level.getBlockState(capPos).isAir() ? TOPS : BLOCKS;
level.setBlock(capPos, capProvider.getState(random, capPos), 0b11);
} else {
level.setBlock(posAt, BLOCKS.getState(random, posAt), 0b11);
}

lastPos = posAt;
}
}

private List<BlockPos> scanColumnForPlacement(WorldGenLevel level, RandomSource random, int wallColumnHeight, int ySurfaceAir, BlockPos wallColumnStart) {
// The Y-displacement for assigning an offset to this column. Combined with other columns to be a part of the tunnel walls, it produces a ragged edge
int depthOffset = Math.round(random.nextFloat() * wallColumnHeight);
// Begin several blocks underground to lure players down to the Carved Stone ceiling
List<BlockPos> toPlace = new ArrayList<>();
for (int dY = this.boundingBox.minY() - ySurfaceAir; dY < wallColumnHeight; dY++) {
BlockPos posAt = wallColumnStart.above(dY);

BlockState blockAt = level.getBlockState(posAt);
if (dY >= -4 // Is the dY above the transition boundary between dirt and stone?
// Creates a jagged break if there exists a terrain overhang between the surface & the dungeon lobby room
|| (blockAt.is(AetherTags.Blocks.AETHER_ISLAND_BLOCKS) || blockAt.isAir()) && level.getBlockState(posAt.below(depthOffset)).is(AetherTags.Blocks.AETHER_ISLAND_BLOCKS)
) {
// Add passing block to list as position instead of immediately replacing, to avoid interfering with the prior displaced positional check
toPlace.add(posAt);
}
}

return toPlace;
}

@Override
protected void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag nbt) {
// No-op
}
}

0 comments on commit 422dc12

Please sign in to comment.