mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-20 23:36:52 +00:00
Keep native structures out of static object cells
Vanilla structure and feature placement now runs through the bounded worldgen access with a protected-position predicate, so a jigsaw piece or an imported feature can read a static object's blocks but never overwrite them, and never schedules a tick or block entity there. Dimensions without static objects keep the plain level as before.
This commit is contained in:
10 files changed
+129
-36
No files matched your search
+10
-1
@@ -7,6 +7,7 @@ import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDecorationStep;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisImportedFeatureControl;
|
||||
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
@@ -26,6 +27,7 @@ import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
@@ -287,13 +289,20 @@ final class ImportedFeatureStage {
|
||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
|
||||
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
|
||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||
int staticMinY = engine.getMinHeight();
|
||||
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : NativeStructureWorldgenAccess.create(
|
||||
level, centerPos,
|
||||
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
|
||||
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
|
||||
try {
|
||||
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
||||
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
|
||||
continue;
|
||||
}
|
||||
placeStep(level, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
|
||||
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
|
||||
random, decorationSeed, origin, stepIndex);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
|
||||
+13
-7
@@ -13,6 +13,7 @@ import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.engine.object.IrisMaterialPalette;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||
import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.nativegen.NativeStructureStartInjector;
|
||||
import art.arcane.iris.nativegen.NativeStructureReferenceEnvelope;
|
||||
@@ -873,9 +874,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
"heightmap priming", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||
int staticMinY = engine.getMinHeight();
|
||||
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
try {
|
||||
NativeStructureVegetationClearer.clearIntersectingVegetation(
|
||||
world, chunk, area, vegetationTargets);
|
||||
boundedWorld, chunk, area, vegetationTargets);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
|
||||
@@ -884,7 +890,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
|
||||
try {
|
||||
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world, area, terrainTargets,
|
||||
boundedWorld, area, terrainTargets,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
@@ -893,7 +899,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.prepareTerrain(
|
||||
world, area, terrainTargets, this::resolvePaletteBlock);
|
||||
boundedWorld, area, terrainTargets, this::resolvePaletteBlock);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain preparation", nativeStructureBatchContext(placementGroups),
|
||||
@@ -903,7 +909,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
random.setFeatureSeed(decoSeed, group.featureIndex(), group.step());
|
||||
try {
|
||||
for (NativePlacement placement : group.placements()) {
|
||||
placeVanillaStructure(world, structureManager, random, area, chunkPos,
|
||||
placeVanillaStructure(boundedWorld, structureManager, random, area, chunkPos,
|
||||
group.structureId(), placement.start(), placement.decision());
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
@@ -913,7 +919,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
}
|
||||
try {
|
||||
NativeStructureSurfaceFitter.repairVacuumFoundations(
|
||||
world, area, vacuumFoundationPlan);
|
||||
boundedWorld, area, vacuumFoundationPlan);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"foundation repair", nativeStructureBatchContext(placementGroups),
|
||||
@@ -938,8 +944,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private void placeVanillaStructure(WorldGenLevel world, StructureManager structureManager, WorldgenRandom random,
|
||||
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
|
||||
IrisNativeStructureDecision decision) {
|
||||
WorldGenLevel boundedWorld = NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight());
|
||||
WorldGenLevel boundedWorld = world instanceof NativeStructureWorldgenAccess ? world : NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(), position -> false);
|
||||
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
||||
try {
|
||||
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
|
||||
|
||||
+11
-6
@@ -64,6 +64,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
private final ChunkPos generationCenter;
|
||||
private final IntBinaryOperator surfaceFirstFreeY;
|
||||
private final IntBinaryOperator floorFirstFreeY;
|
||||
private final Predicate<BlockPos> protectedPosition;
|
||||
private final Holder<Biome> fallbackBiome;
|
||||
private final BiomeManager biomeManager;
|
||||
private final AABB generationBounds;
|
||||
@@ -80,6 +81,8 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
|
||||
this.floorFirstFreeY = Objects.requireNonNull(
|
||||
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
|
||||
this.protectedPosition = Objects.requireNonNull(
|
||||
boundary.protectedPosition(), "World access requires a protected-position predicate");
|
||||
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
|
||||
this.fallbackBiome = delegate.getBiome(biomeSample);
|
||||
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
|
||||
@@ -99,9 +102,10 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY) {
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
return new NativeStructureWorldgenAccess(delegate, new Boundary(
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY));
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -359,13 +363,13 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos position) {
|
||||
return isReadable(position) ? delegate.getBlockEntity(position) : null;
|
||||
return isWritable(position) ? delegate.getBlockEntity(position) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends BlockEntity> Optional<T> getBlockEntity(
|
||||
BlockPos position, BlockEntityType<T> type) {
|
||||
return isReadable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
|
||||
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -453,7 +457,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
}
|
||||
|
||||
private boolean isWritable(BlockPos position) {
|
||||
return isReadable(position);
|
||||
return isReadable(position) && !protectedPosition.test(position);
|
||||
}
|
||||
|
||||
private boolean isWritableNeighbourhood(BlockPos position) {
|
||||
@@ -544,7 +548,8 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
private record Boundary(ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY) {
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
}
|
||||
|
||||
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
|
||||
|
||||
+29
-1
@@ -135,6 +135,34 @@ public class NativeStructureWorldgenAccessTest {
|
||||
assertEquals(1, recording.blockTicks.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
BlockPos position = generationCenter().getMiddleBlockPosition(70);
|
||||
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
|
||||
|
||||
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
|
||||
assertFalse(access.ensureCanWrite(position));
|
||||
assertFalse(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
|
||||
assertFalse(access.removeBlock(position, false));
|
||||
assertFalse(access.destroyBlock(position, false));
|
||||
assertNull(access.getBlockEntity(position));
|
||||
assertTrue(access.getBlockEntity(position, null).isEmpty());
|
||||
access.getBlockTicks().schedule(new ScheduledTick<>(Blocks.DIRT, position, 1L, 0L));
|
||||
access.updateNeighborsAt(position.east(), Blocks.DIRT);
|
||||
access.neighborShapeChanged(
|
||||
Direction.EAST, position, position.east(), Blocks.DIRT.defaultBlockState(), 2, 512);
|
||||
|
||||
assertEquals(1, recording.terrainReads);
|
||||
assertEquals(0, recording.mutations);
|
||||
assertEquals(0, recording.blockTicks.count());
|
||||
assertTrue(access.setBlock(position.above(), Blocks.STONE.defaultBlockState(), 2));
|
||||
assertEquals(1, recording.mutations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statuslessChunkReadsPreserveWorldgenDelegateSemantics() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
@@ -183,7 +211,7 @@ public class NativeStructureWorldgenAccessTest {
|
||||
return NativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y);
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
|
||||
}
|
||||
|
||||
private static ChunkPos generationCenter() {
|
||||
|
||||
+1
-1
@@ -226,7 +226,7 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource"))).replace("\r\n", "\n");
|
||||
int prepareTerrain = source.indexOf("NativeStructurePostProcessor.prepareTerrain(");
|
||||
int placementLoop = source.indexOf("for (NativePlacementGroup group : placementGroups)", prepareTerrain);
|
||||
int placement = source.indexOf("placeVanillaStructure(world, structureManager, random", placementLoop);
|
||||
int placement = source.indexOf("placeVanillaStructure(boundedWorld, structureManager, random", placementLoop);
|
||||
|
||||
assertTrue(prepareTerrain >= 0);
|
||||
assertTrue(placementLoop > prepareTerrain);
|
||||
|
||||
+10
-1
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.engine.object.IrisDecorationStep;
|
||||
import art.arcane.iris.engine.object.IrisImportedFeatureControl;
|
||||
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||
import art.arcane.iris.util.project.context.IrisContext;
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
@@ -43,6 +44,7 @@ import net.minecraft.world.level.biome.FeatureSorter;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
@@ -321,13 +323,20 @@ final class ModdedImportedFeatureStage {
|
||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
|
||||
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
|
||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||
int staticMinY = engine.getMinHeight();
|
||||
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : ModdedNativeStructureWorldgenAccess.create(
|
||||
level, centerPos,
|
||||
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
|
||||
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
|
||||
try {
|
||||
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
||||
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
|
||||
continue;
|
||||
}
|
||||
placeStep(level, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
|
||||
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
|
||||
random, decorationSeed, origin, stepIndex);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
|
||||
+13
-10
@@ -25,6 +25,7 @@ import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.IrisMaterialPalette;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||
import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.nativegen.NativeStructureLocatePersistence;
|
||||
import art.arcane.iris.nativegen.NativeStructureLocateResults;
|
||||
@@ -365,9 +366,14 @@ final class ModdedNativeStructureStage {
|
||||
"heightmap priming", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
IrisStaticObjectLayer staticObjects = current.getDimension().getStaticObjectLayer(current.getData());
|
||||
int staticMinY = current.getMinHeight();
|
||||
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : ModdedNativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
try {
|
||||
NativeStructureVegetationClearer.clearIntersectingVegetation(
|
||||
world, chunk, area, vegetationTargets);
|
||||
boundedWorld, chunk, area, vegetationTargets);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
|
||||
@@ -376,7 +382,7 @@ final class ModdedNativeStructureStage {
|
||||
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
|
||||
try {
|
||||
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world, area, terrainTargets,
|
||||
boundedWorld, area, terrainTargets,
|
||||
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
@@ -385,7 +391,7 @@ final class ModdedNativeStructureStage {
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.prepareTerrain(
|
||||
world, area, terrainTargets, this::resolvePaletteBlock);
|
||||
boundedWorld, area, terrainTargets, this::resolvePaletteBlock);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain preparation", nativeStructureBatchContext(placementGroups),
|
||||
@@ -395,7 +401,7 @@ final class ModdedNativeStructureStage {
|
||||
random.setFeatureSeed(decorationSeed, group.featureIndex(), group.step());
|
||||
try {
|
||||
for (NativePlacement placement : group.placements()) {
|
||||
placeVanillaStructure(world, structureManager, random, area, chunkPos,
|
||||
placeVanillaStructure(boundedWorld, structureManager, random, area, chunkPos,
|
||||
group.structureId(), placement.start(), placement.decision());
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
@@ -405,7 +411,7 @@ final class ModdedNativeStructureStage {
|
||||
}
|
||||
try {
|
||||
NativeStructureSurfaceFitter.repairVacuumFoundations(
|
||||
world, area, vacuumFoundationPlan);
|
||||
boundedWorld, area, vacuumFoundationPlan);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"foundation repair", nativeStructureBatchContext(placementGroups),
|
||||
@@ -436,11 +442,8 @@ final class ModdedNativeStructureStage {
|
||||
String structureId, StructureStart start,
|
||||
IrisNativeStructureDecision decision) {
|
||||
Engine current = generator.engine();
|
||||
int runtimeMinY = world.getMinY();
|
||||
WorldGenLevel boundedWorld = ModdedNativeStructureWorldgenAccess.create(
|
||||
world, chunkPos,
|
||||
worldgenSurfaceHeight(current, runtimeMinY),
|
||||
worldgenFloorHeight(current, runtimeMinY));
|
||||
WorldGenLevel boundedWorld = world instanceof ModdedNativeStructureWorldgenAccess ? world : ModdedNativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()), position -> false);
|
||||
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
||||
try {
|
||||
NativeStructurePostProcessor.place(
|
||||
|
||||
+11
-6
@@ -63,6 +63,7 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
private final ChunkPos generationCenter;
|
||||
private final IntBinaryOperator surfaceFirstFreeY;
|
||||
private final IntBinaryOperator floorFirstFreeY;
|
||||
private final Predicate<BlockPos> protectedPosition;
|
||||
private final Holder<Biome> fallbackBiome;
|
||||
private final BiomeManager biomeManager;
|
||||
private final AABB generationBounds;
|
||||
@@ -79,6 +80,8 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
|
||||
this.floorFirstFreeY = Objects.requireNonNull(
|
||||
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
|
||||
this.protectedPosition = Objects.requireNonNull(
|
||||
boundary.protectedPosition(), "World access requires a protected-position predicate");
|
||||
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
|
||||
this.fallbackBiome = delegate.getBiome(biomeSample);
|
||||
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
|
||||
@@ -98,9 +101,10 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
static ModdedNativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY) {
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
return new ModdedNativeStructureWorldgenAccess(delegate, new Boundary(
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY));
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -350,13 +354,13 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos position) {
|
||||
return isReadable(position) ? delegate.getBlockEntity(position) : null;
|
||||
return isWritable(position) ? delegate.getBlockEntity(position) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends BlockEntity> Optional<T> getBlockEntity(
|
||||
BlockPos position, BlockEntityType<T> type) {
|
||||
return isReadable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
|
||||
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -430,7 +434,7 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
}
|
||||
|
||||
private boolean isWritable(BlockPos position) {
|
||||
return isReadable(position);
|
||||
return isReadable(position) && !protectedPosition.test(position);
|
||||
}
|
||||
|
||||
private boolean isWritableNeighbourhood(BlockPos position) {
|
||||
@@ -521,7 +525,8 @@ final class ModdedNativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
private record Boundary(ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY) {
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
}
|
||||
|
||||
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
|
||||
|
||||
+29
-1
@@ -135,6 +135,34 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
assertEquals(1, recording.blockTicks.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
BlockPos position = generationCenter().getMiddleBlockPosition(70);
|
||||
ModdedNativeStructureWorldgenAccess access = ModdedNativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
|
||||
|
||||
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
|
||||
assertFalse(access.ensureCanWrite(position));
|
||||
assertFalse(access.setBlock(position, Blocks.STONE.defaultBlockState(), 2));
|
||||
assertFalse(access.removeBlock(position, false));
|
||||
assertFalse(access.destroyBlock(position, false));
|
||||
assertNull(access.getBlockEntity(position));
|
||||
assertTrue(access.getBlockEntity(position, null).isEmpty());
|
||||
access.getBlockTicks().schedule(new ScheduledTick<>(Blocks.DIRT, position, 1L, 0L));
|
||||
access.updateNeighborsAt(position.east(), Blocks.DIRT);
|
||||
access.neighborShapeChanged(
|
||||
Direction.EAST, position, position.east(), Blocks.DIRT.defaultBlockState(), 2, 512);
|
||||
|
||||
assertEquals(1, recording.terrainReads);
|
||||
assertEquals(0, recording.mutations);
|
||||
assertEquals(0, recording.blockTicks.count());
|
||||
assertTrue(access.setBlock(position.above(), Blocks.STONE.defaultBlockState(), 2));
|
||||
assertEquals(1, recording.mutations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void statuslessChunkReadsPreserveWorldgenDelegateSemantics() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
@@ -183,7 +211,7 @@ public class ModdedNativeStructureWorldgenAccessTest {
|
||||
return ModdedNativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y);
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
|
||||
}
|
||||
|
||||
private static ChunkPos generationCenter() {
|
||||
|
||||
+2
-2
@@ -103,12 +103,12 @@ public class NativeStructureFailureContractTest {
|
||||
@Test
|
||||
public void nativeStructurePostProcessingUsesBoundedWorldgenAccess() throws IOException {
|
||||
String source = moddedSource("ModdedNativeStructureStage.java");
|
||||
int placementStart = source.indexOf("private void placeVanillaStructure");
|
||||
int placementStart = source.indexOf("void placeVanillaStructures");
|
||||
int placementEnd = source.indexOf("private List<List<Structure>> structuresByStep", placementStart);
|
||||
String placement = source.substring(placementStart, placementEnd);
|
||||
|
||||
assertTrue(placement.contains("ModdedNativeStructureWorldgenAccess.create("));
|
||||
assertTrue(placement.contains("NativeStructurePostProcessor.place(\n boundedWorld"));
|
||||
assertTrue(placement.contains("placeVanillaStructure(boundedWorld, structureManager"));
|
||||
assertFalse(placement.contains("NativeStructurePostProcessor.place(world,"));
|
||||
assertTrue(placement.contains("world.setCurrentlyGenerating(null);"));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user