minecraft-src/net/minecraft/world/level/levelgen/structure/StructurePiece.java
2025-07-04 03:45:38 +03:00

528 lines
17 KiB
Java

package net.minecraft.world.level.levelgen.structure;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.entity.DispenserBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext;
import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.loot.LootTable;
import org.jetbrains.annotations.Nullable;
public abstract class StructurePiece {
protected static final BlockState CAVE_AIR = Blocks.CAVE_AIR.defaultBlockState();
protected BoundingBox boundingBox;
@Nullable
private Direction orientation;
private Mirror mirror;
private Rotation rotation;
protected int genDepth;
private final StructurePieceType type;
private static final Set<Block> SHAPE_CHECK_BLOCKS = ImmutableSet.<Block>builder()
.add(Blocks.NETHER_BRICK_FENCE)
.add(Blocks.TORCH)
.add(Blocks.WALL_TORCH)
.add(Blocks.OAK_FENCE)
.add(Blocks.SPRUCE_FENCE)
.add(Blocks.DARK_OAK_FENCE)
.add(Blocks.PALE_OAK_FENCE)
.add(Blocks.ACACIA_FENCE)
.add(Blocks.BIRCH_FENCE)
.add(Blocks.JUNGLE_FENCE)
.add(Blocks.LADDER)
.add(Blocks.IRON_BARS)
.build();
protected StructurePiece(StructurePieceType type, int genDepth, BoundingBox boundingBox) {
this.type = type;
this.genDepth = genDepth;
this.boundingBox = boundingBox;
}
public StructurePiece(StructurePieceType type, CompoundTag tag) {
this(type, tag.getIntOr("GD", 0), (BoundingBox)tag.read("BB", BoundingBox.CODEC).orElseThrow());
int i = tag.getIntOr("O", 0);
this.setOrientation(i == -1 ? null : Direction.from2DDataValue(i));
}
protected static BoundingBox makeBoundingBox(int x, int y, int z, Direction direction, int offsetX, int offsetY, int offsetZ) {
return direction.getAxis() == Direction.Axis.Z
? new BoundingBox(x, y, z, x + offsetX - 1, y + offsetY - 1, z + offsetZ - 1)
: new BoundingBox(x, y, z, x + offsetZ - 1, y + offsetY - 1, z + offsetX - 1);
}
protected static Direction getRandomHorizontalDirection(RandomSource random) {
return Direction.Plane.HORIZONTAL.getRandomDirection(random);
}
public final CompoundTag createTag(StructurePieceSerializationContext context) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putString("id", BuiltInRegistries.STRUCTURE_PIECE.getKey(this.getType()).toString());
compoundTag.store("BB", BoundingBox.CODEC, this.boundingBox);
Direction direction = this.getOrientation();
compoundTag.putInt("O", direction == null ? -1 : direction.get2DDataValue());
compoundTag.putInt("GD", this.genDepth);
this.addAdditionalSaveData(context, compoundTag);
return compoundTag;
}
protected abstract void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag tag);
public void addChildren(StructurePiece piece, StructurePieceAccessor pieces, RandomSource random) {
}
public abstract void postProcess(
WorldGenLevel level, StructureManager structureManager, ChunkGenerator generator, RandomSource random, BoundingBox box, ChunkPos chunkPos, BlockPos pos
);
public BoundingBox getBoundingBox() {
return this.boundingBox;
}
public int getGenDepth() {
return this.genDepth;
}
public void setGenDepth(int genDepth) {
this.genDepth = genDepth;
}
public boolean isCloseToChunk(ChunkPos chunkPos, int distance) {
int i = chunkPos.getMinBlockX();
int j = chunkPos.getMinBlockZ();
return this.boundingBox.intersects(i - distance, j - distance, i + 15 + distance, j + 15 + distance);
}
public BlockPos getLocatorPosition() {
return new BlockPos(this.boundingBox.getCenter());
}
protected BlockPos.MutableBlockPos getWorldPos(int x, int y, int z) {
return new BlockPos.MutableBlockPos(this.getWorldX(x, z), this.getWorldY(y), this.getWorldZ(x, z));
}
protected int getWorldX(int x, int z) {
Direction direction = this.getOrientation();
if (direction == null) {
return x;
} else {
switch (direction) {
case NORTH:
case SOUTH:
return this.boundingBox.minX() + x;
case WEST:
return this.boundingBox.maxX() - z;
case EAST:
return this.boundingBox.minX() + z;
default:
return x;
}
}
}
protected int getWorldY(int y) {
return this.getOrientation() == null ? y : y + this.boundingBox.minY();
}
protected int getWorldZ(int x, int z) {
Direction direction = this.getOrientation();
if (direction == null) {
return z;
} else {
switch (direction) {
case NORTH:
return this.boundingBox.maxZ() - z;
case SOUTH:
return this.boundingBox.minZ() + z;
case WEST:
case EAST:
return this.boundingBox.minZ() + x;
default:
return z;
}
}
}
protected void placeBlock(WorldGenLevel level, BlockState blockstate, int x, int y, int z, BoundingBox boundingbox) {
BlockPos blockPos = this.getWorldPos(x, y, z);
if (boundingbox.isInside(blockPos)) {
if (this.canBeReplaced(level, x, y, z, boundingbox)) {
if (this.mirror != Mirror.NONE) {
blockstate = blockstate.mirror(this.mirror);
}
if (this.rotation != Rotation.NONE) {
blockstate = blockstate.rotate(this.rotation);
}
level.setBlock(blockPos, blockstate, 2);
FluidState fluidState = level.getFluidState(blockPos);
if (!fluidState.isEmpty()) {
level.scheduleTick(blockPos, fluidState.getType(), 0);
}
if (SHAPE_CHECK_BLOCKS.contains(blockstate.getBlock())) {
level.getChunk(blockPos).markPosForPostprocessing(blockPos);
}
}
}
}
protected boolean canBeReplaced(LevelReader level, int x, int y, int z, BoundingBox box) {
return true;
}
protected BlockState getBlock(BlockGetter level, int x, int y, int z, BoundingBox box) {
BlockPos blockPos = this.getWorldPos(x, y, z);
return !box.isInside(blockPos) ? Blocks.AIR.defaultBlockState() : level.getBlockState(blockPos);
}
protected boolean isInterior(LevelReader level, int x, int y, int z, BoundingBox box) {
BlockPos blockPos = this.getWorldPos(x, y + 1, z);
return !box.isInside(blockPos) ? false : blockPos.getY() < level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, blockPos.getX(), blockPos.getZ());
}
protected void generateAirBox(WorldGenLevel level, BoundingBox box, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
for (int i = minY; i <= maxY; i++) {
for (int j = minX; j <= maxX; j++) {
for (int k = minZ; k <= maxZ; k++) {
this.placeBlock(level, Blocks.AIR.defaultBlockState(), j, i, k, box);
}
}
}
}
/**
* Fill the given area with the selected blocks
*/
protected void generateBox(
WorldGenLevel level,
BoundingBox box,
int xMin,
int yMin,
int zMin,
int xMax,
int yMax,
int zMax,
BlockState boundaryBlockState,
BlockState insideBlockState,
boolean existingOnly
) {
for (int i = yMin; i <= yMax; i++) {
for (int j = xMin; j <= xMax; j++) {
for (int k = zMin; k <= zMax; k++) {
if (!existingOnly || !this.getBlock(level, j, i, k, box).isAir()) {
if (i != yMin && i != yMax && j != xMin && j != xMax && k != zMin && k != zMax) {
this.placeBlock(level, insideBlockState, j, i, k, box);
} else {
this.placeBlock(level, boundaryBlockState, j, i, k, box);
}
}
}
}
}
}
protected void generateBox(
WorldGenLevel level, BoundingBox boundingBox, BoundingBox box, BlockState boundaryBlockState, BlockState insideBlockState, boolean existingOnly
) {
this.generateBox(
level, boundingBox, box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ(), boundaryBlockState, insideBlockState, existingOnly
);
}
protected void generateBox(
WorldGenLevel level,
BoundingBox box,
int minX,
int minY,
int minZ,
int maxX,
int maxY,
int maxZ,
boolean alwaysReplace,
RandomSource random,
StructurePiece.BlockSelector blockSelector
) {
for (int i = minY; i <= maxY; i++) {
for (int j = minX; j <= maxX; j++) {
for (int k = minZ; k <= maxZ; k++) {
if (!alwaysReplace || !this.getBlock(level, j, i, k, box).isAir()) {
blockSelector.next(random, j, i, k, i == minY || i == maxY || j == minX || j == maxX || k == minZ || k == maxZ);
this.placeBlock(level, blockSelector.getNext(), j, i, k, box);
}
}
}
}
}
protected void generateBox(
WorldGenLevel level, BoundingBox boundingBox, BoundingBox box, boolean alwaysReplace, RandomSource random, StructurePiece.BlockSelector blockSelector
) {
this.generateBox(level, boundingBox, box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ(), alwaysReplace, random, blockSelector);
}
protected void generateMaybeBox(
WorldGenLevel level,
BoundingBox box,
RandomSource random,
float chance,
int x1,
int y1,
int z1,
int x2,
int y2,
int z2,
BlockState edgeState,
BlockState state,
boolean requireNonAir,
boolean requireSkylight
) {
for (int i = y1; i <= y2; i++) {
for (int j = x1; j <= x2; j++) {
for (int k = z1; k <= z2; k++) {
if (!(random.nextFloat() > chance)
&& (!requireNonAir || !this.getBlock(level, j, i, k, box).isAir())
&& (!requireSkylight || this.isInterior(level, j, i, k, box))) {
if (i != y1 && i != y2 && j != x1 && j != x2 && k != z1 && k != z2) {
this.placeBlock(level, state, j, i, k, box);
} else {
this.placeBlock(level, edgeState, j, i, k, box);
}
}
}
}
}
}
protected void maybeGenerateBlock(WorldGenLevel level, BoundingBox box, RandomSource random, float chance, int x, int y, int z, BlockState state) {
if (random.nextFloat() < chance) {
this.placeBlock(level, state, x, y, z, box);
}
}
protected void generateUpperHalfSphere(
WorldGenLevel level, BoundingBox box, int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state, boolean excludeAir
) {
float f = maxX - minX + 1;
float g = maxY - minY + 1;
float h = maxZ - minZ + 1;
float i = minX + f / 2.0F;
float j = minZ + h / 2.0F;
for (int k = minY; k <= maxY; k++) {
float l = (k - minY) / g;
for (int m = minX; m <= maxX; m++) {
float n = (m - i) / (f * 0.5F);
for (int o = minZ; o <= maxZ; o++) {
float p = (o - j) / (h * 0.5F);
if (!excludeAir || !this.getBlock(level, m, k, o, box).isAir()) {
float q = n * n + l * l + p * p;
if (q <= 1.05F) {
this.placeBlock(level, state, m, k, o, box);
}
}
}
}
}
}
protected void fillColumnDown(WorldGenLevel level, BlockState state, int x, int y, int z, BoundingBox box) {
BlockPos.MutableBlockPos mutableBlockPos = this.getWorldPos(x, y, z);
if (box.isInside(mutableBlockPos)) {
while (this.isReplaceableByStructures(level.getBlockState(mutableBlockPos)) && mutableBlockPos.getY() > level.getMinY() + 1) {
level.setBlock(mutableBlockPos, state, 2);
mutableBlockPos.move(Direction.DOWN);
}
}
}
protected boolean isReplaceableByStructures(BlockState state) {
return state.isAir() || state.liquid() || state.is(Blocks.GLOW_LICHEN) || state.is(Blocks.SEAGRASS) || state.is(Blocks.TALL_SEAGRASS);
}
protected boolean createChest(WorldGenLevel level, BoundingBox box, RandomSource random, int x, int y, int z, ResourceKey<LootTable> lootTable) {
return this.createChest(level, box, random, this.getWorldPos(x, y, z), lootTable, null);
}
public static BlockState reorient(BlockGetter level, BlockPos pos, BlockState state) {
Direction direction = null;
for (Direction direction2 : Direction.Plane.HORIZONTAL) {
BlockPos blockPos = pos.relative(direction2);
BlockState blockState = level.getBlockState(blockPos);
if (blockState.is(Blocks.CHEST)) {
return state;
}
if (blockState.isSolidRender()) {
if (direction != null) {
direction = null;
break;
}
direction = direction2;
}
}
if (direction != null) {
return state.setValue(HorizontalDirectionalBlock.FACING, direction.getOpposite());
} else {
Direction direction3 = state.getValue(HorizontalDirectionalBlock.FACING);
BlockPos blockPos2 = pos.relative(direction3);
if (level.getBlockState(blockPos2).isSolidRender()) {
direction3 = direction3.getOpposite();
blockPos2 = pos.relative(direction3);
}
if (level.getBlockState(blockPos2).isSolidRender()) {
direction3 = direction3.getClockWise();
blockPos2 = pos.relative(direction3);
}
if (level.getBlockState(blockPos2).isSolidRender()) {
direction3 = direction3.getOpposite();
blockPos2 = pos.relative(direction3);
}
return state.setValue(HorizontalDirectionalBlock.FACING, direction3);
}
}
protected boolean createChest(
ServerLevelAccessor level, BoundingBox box, RandomSource random, BlockPos pos, ResourceKey<LootTable> lootTable, @Nullable BlockState state
) {
if (box.isInside(pos) && !level.getBlockState(pos).is(Blocks.CHEST)) {
if (state == null) {
state = reorient(level, pos, Blocks.CHEST.defaultBlockState());
}
level.setBlock(pos, state, 2);
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof ChestBlockEntity) {
((ChestBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
}
return true;
} else {
return false;
}
}
protected boolean createDispenser(
WorldGenLevel level, BoundingBox box, RandomSource random, int x, int y, int z, Direction facing, ResourceKey<LootTable> lootTable
) {
BlockPos blockPos = this.getWorldPos(x, y, z);
if (box.isInside(blockPos) && !level.getBlockState(blockPos).is(Blocks.DISPENSER)) {
this.placeBlock(level, Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, box);
BlockEntity blockEntity = level.getBlockEntity(blockPos);
if (blockEntity instanceof DispenserBlockEntity) {
((DispenserBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
}
return true;
} else {
return false;
}
}
public void move(int x, int y, int z) {
this.boundingBox.move(x, y, z);
}
public static BoundingBox createBoundingBox(Stream<StructurePiece> pieces) {
return (BoundingBox)BoundingBox.encapsulatingBoxes(pieces.map(StructurePiece::getBoundingBox)::iterator)
.orElseThrow(() -> new IllegalStateException("Unable to calculate boundingbox without pieces"));
}
@Nullable
public static StructurePiece findCollisionPiece(List<StructurePiece> pieces, BoundingBox boundingBox) {
for (StructurePiece structurePiece : pieces) {
if (structurePiece.getBoundingBox().intersects(boundingBox)) {
return structurePiece;
}
}
return null;
}
@Nullable
public Direction getOrientation() {
return this.orientation;
}
public void setOrientation(@Nullable Direction orientation) {
this.orientation = orientation;
if (orientation == null) {
this.rotation = Rotation.NONE;
this.mirror = Mirror.NONE;
} else {
switch (orientation) {
case SOUTH:
this.mirror = Mirror.LEFT_RIGHT;
this.rotation = Rotation.NONE;
break;
case WEST:
this.mirror = Mirror.LEFT_RIGHT;
this.rotation = Rotation.CLOCKWISE_90;
break;
case EAST:
this.mirror = Mirror.NONE;
this.rotation = Rotation.CLOCKWISE_90;
break;
default:
this.mirror = Mirror.NONE;
this.rotation = Rotation.NONE;
}
}
}
public Rotation getRotation() {
return this.rotation;
}
public Mirror getMirror() {
return this.mirror;
}
public StructurePieceType getType() {
return this.type;
}
public abstract static class BlockSelector {
protected BlockState next = Blocks.AIR.defaultBlockState();
public abstract void next(RandomSource random, int x, int y, int z, boolean wall);
public BlockState getNext() {
return this.next;
}
}
}