package net.minecraft.world.level.levelgen.structure.placement; import com.mojang.datafixers.Products.P5; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder.Instance; import com.mojang.serialization.codecs.RecordCodecBuilder.Mu; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Vec3i; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.RegistryFileCodec; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.structure.StructureSet; public abstract class StructurePlacement { public static final Codec CODEC = BuiltInRegistries.STRUCTURE_PLACEMENT .byNameCodec() .dispatch(StructurePlacement::type, StructurePlacementType::codec); private static final int HIGHLY_ARBITRARY_RANDOM_SALT = 10387320; private final Vec3i locateOffset; private final StructurePlacement.FrequencyReductionMethod frequencyReductionMethod; private final float frequency; private final int salt; private final Optional exclusionZone; protected static P5, Vec3i, StructurePlacement.FrequencyReductionMethod, Float, Integer, Optional> placementCodec( Instance instance ) { return instance.group( Vec3i.offsetCodec(16).optionalFieldOf("locate_offset", Vec3i.ZERO).forGetter(StructurePlacement::locateOffset), StructurePlacement.FrequencyReductionMethod.CODEC .optionalFieldOf("frequency_reduction_method", StructurePlacement.FrequencyReductionMethod.DEFAULT) .forGetter(StructurePlacement::frequencyReductionMethod), Codec.floatRange(0.0F, 1.0F).optionalFieldOf("frequency", 1.0F).forGetter(StructurePlacement::frequency), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("salt").forGetter(StructurePlacement::salt), StructurePlacement.ExclusionZone.CODEC.optionalFieldOf("exclusion_zone").forGetter(StructurePlacement::exclusionZone) ); } protected StructurePlacement( Vec3i locateOffset, StructurePlacement.FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, Optional exclusionZone ) { this.locateOffset = locateOffset; this.frequencyReductionMethod = frequencyReductionMethod; this.frequency = frequency; this.salt = salt; this.exclusionZone = exclusionZone; } protected Vec3i locateOffset() { return this.locateOffset; } protected StructurePlacement.FrequencyReductionMethod frequencyReductionMethod() { return this.frequencyReductionMethod; } protected float frequency() { return this.frequency; } protected int salt() { return this.salt; } protected Optional exclusionZone() { return this.exclusionZone; } public boolean isStructureChunk(ChunkGeneratorStructureState structureState, int x, int z) { return this.isPlacementChunk(structureState, x, z) && this.applyAdditionalChunkRestrictions(x, z, structureState.getLevelSeed()) && this.applyInteractionsWithOtherStructures(structureState, x, z); } public boolean applyAdditionalChunkRestrictions(int regionX, int regionZ, long levelSeed) { return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(levelSeed, this.salt, regionX, regionZ, this.frequency); } public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState structureState, int x, int z) { return !this.exclusionZone.isPresent() || !((StructurePlacement.ExclusionZone)this.exclusionZone.get()).isPlacementForbidden(structureState, x, z); } protected abstract boolean isPlacementChunk(ChunkGeneratorStructureState structureState, int x, int z); public BlockPos getLocatePos(ChunkPos chunkPos) { return new BlockPos(chunkPos.getMinBlockX(), 0, chunkPos.getMinBlockZ()).offset(this.locateOffset()); } public abstract StructurePlacementType type(); private static boolean probabilityReducer(long levelSeed, int regionX, int regionZ, int salt, float probability) { WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, salt); return worldgenRandom.nextFloat() < probability; } private static boolean legacyProbabilityReducerWithDouble(long baseSeed, int salt, int chunkX, int chunkZ, float probability) { WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); worldgenRandom.setLargeFeatureSeed(baseSeed, chunkX, chunkZ); return worldgenRandom.nextDouble() < probability; } private static boolean legacyArbitrarySaltProbabilityReducer(long levelSeed, int salt, int regionX, int regionZ, float probability) { WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); worldgenRandom.setLargeFeatureWithSalt(levelSeed, regionX, regionZ, 10387320); return worldgenRandom.nextFloat() < probability; } private static boolean legacyPillagerOutpostReducer(long levelSeed, int salt, int regionX, int regionZ, float probability) { int i = regionX >> 4; int j = regionZ >> 4; WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); worldgenRandom.setSeed(i ^ j << 4 ^ levelSeed); worldgenRandom.nextInt(); return worldgenRandom.nextInt((int)(1.0F / probability)) == 0; } @Deprecated public record ExclusionZone(Holder otherSet, int chunkCount) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( RegistryFileCodec.create(Registries.STRUCTURE_SET, StructureSet.DIRECT_CODEC, false) .fieldOf("other_set") .forGetter(StructurePlacement.ExclusionZone::otherSet), Codec.intRange(1, 16).fieldOf("chunk_count").forGetter(StructurePlacement.ExclusionZone::chunkCount) ) .apply(instance, StructurePlacement.ExclusionZone::new) ); boolean isPlacementForbidden(ChunkGeneratorStructureState structureState, int x, int z) { return structureState.hasStructureChunkInRange(this.otherSet, x, z, this.chunkCount); } } @FunctionalInterface public interface FrequencyReducer { boolean shouldGenerate(long l, int i, int j, int k, float f); } public static enum FrequencyReductionMethod implements StringRepresentable { DEFAULT("default", StructurePlacement::probabilityReducer), LEGACY_TYPE_1("legacy_type_1", StructurePlacement::legacyPillagerOutpostReducer), LEGACY_TYPE_2("legacy_type_2", StructurePlacement::legacyArbitrarySaltProbabilityReducer), LEGACY_TYPE_3("legacy_type_3", StructurePlacement::legacyProbabilityReducerWithDouble); public static final Codec CODEC = StringRepresentable.fromEnum( StructurePlacement.FrequencyReductionMethod::values ); private final String name; private final StructurePlacement.FrequencyReducer reducer; private FrequencyReductionMethod(final String name, final StructurePlacement.FrequencyReducer reducer) { this.name = name; this.reducer = reducer; } public boolean shouldGenerate(long levelSeed, int salt, int regionX, int regionZ, float probability) { return this.reducer.shouldGenerate(levelSeed, salt, regionX, regionZ, probability); } @Override public String getSerializedName() { return this.name; } } }