NBative Structure fixes.

- This fixes the native structures that are in iris from generating with pillaric problems
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-10 02:15:20 -04:00
1 parent 62e61970f8
commit e33e6a632a
10 files changed
+444 -35

No files matched your search

@@ -450,7 +450,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void flattenSupportsProjectedRoadsAcrossRoofCavitiesWithoutFillingAdjacentColumns() throws Exception {
public void flattenSupportsProjectedRoadsAndTheirSurroundingGradeAcrossRoofCavities() throws Exception {
ProtoChunk chunk = new ProtoChunk(new ChunkPos(0, 0), UpgradeData.EMPTY,
LevelHeightAccessor.create(0, 256), containerFactory(), null);
chunk.setPersistedStatus(ChunkStatus.CARVERS);
@@ -504,11 +504,57 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
assertTrue(chunk.getBlockState(new BlockPos(4, roadY - 33, 0)).isAir());
int adjacentY = chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, 5, 0);
assertTrue(chunk.getBlockState(new BlockPos(5, adjacentY - 1, 0)).isAir());
for (int y = adjacentY - 32; y < adjacentY; y++) {
assertTrue("Missing ground between road cells at " + y,
chunk.getBlockState(new BlockPos(5, y, 0)).isSolid());
}
assertEquals(Blocks.WATER.defaultBlockState(), chunk.getBlockState(new BlockPos(6, 50, 0)));
assertTrue(chunk.getBlockState(new BlockPos(6, 49, 0)).isAir());
}
@Test
public void flattenBuildsAContinuousVillageGradeBetweenSparseFoundations() throws Exception {
StructureTemplate foundation = template(List.of(block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
List<PoolElementStructurePiece> pieces = new ArrayList<>();
for (int x : List.of(0, 8)) {
for (int z : List.of(0, 8)) {
pieces.add(rigidTemplatePiece(new InlineSinglePoolElement(foundation),
new BoundingBox(x, 65, z, x, 75, z), 0, Rotation.NONE));
}
}
StructureStart start = rigidSurfaceStart(pieces);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24).setFlattenRange(64);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 8);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = 0; x <= 8; x++) {
for (int z = 0; z <= 8; z++) {
int originalY = (x == 0 || x == 8) && (z == 0 || z == 8) ? 64 : 96;
put(blocks, x, 20, z, Blocks.STONE.defaultBlockState());
put(blocks, x, originalY - 1, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, originalY, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
}
NativeStructureSurfaceFitter.SurfaceTerrainPlan plan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, List.of(target),
(x, z) -> (x == 0 || x == 8) && (z == 0 || z == 8) ? 64 : 96);
for (int x = 0; x <= 8; x++) {
for (int z = 0; z <= 8; z++) {
int groundY = plan.flattenedHeights().get(NativeStructureTemplateOccupancy.columnKey(x, z)) - 1;
assertTrue("Village grade at " + x + "," + z + ": " + groundY, groundY >= 64 && groundY <= 70);
for (int y = 20; y <= groundY; y++) {
assertTrue("Disconnected village ground at " + x + "," + y + "," + z,
state(blocks, x, y, z).isSolid());
}
for (int y = groundY + 1; y <= 96; y++) {
assertTrue("Roof remains above village at " + x + "," + y + "," + z,
state(blocks, x, y, z).isAir());
}
}
}
}
@Test
public void sourceBeardAdjustmentsPrepareOnlyAtTheSurfaceStructuresStep() {
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
@@ -750,14 +750,14 @@ public final class NativeStructureSurfaceFitter {
if (selected != null) {
int targetY = originalY + Math.max(-selected.range(),
Math.min(selected.range(), selected.source().meetY() - originalY));
return new FlattenResolution(targetY, selected.range(), selected.source().strength() > 1);
return new FlattenResolution(targetY, selected.range());
}
if (totalWeight == 0) {
return new FlattenResolution(originalY, 0, false);
return new FlattenResolution(originalY, 0);
}
int targetY = blendSurfaceTarget(originalY, weightedY / (double) totalWeight,
maximumInfluence / (double) SURFACE_TERRAIN_INFLUENCE_SCALE);
return new FlattenResolution(targetY, range, false);
return new FlattenResolution(targetY, range);
}
private static Map<Long, Integer> fitFlattenTerrain(
@@ -780,9 +780,7 @@ public final class NativeStructureSurfaceFitter {
int minimumY = Math.max(area.minY(), targetY - range);
applySurfaceColumn(world, position, x, z, originalY, targetY,
minimumY, Math.min(area.maxY(), originalY + range));
if (resolution.foundation() || projectedRange > 0) {
fillFlattenFoundation(world, position, x, z, targetY, minimumY);
}
fillFlattenFoundation(world, position, x, z, targetY, minimumY);
heights.put(column, targetY + 1);
}
}
@@ -1104,7 +1102,7 @@ public final class NativeStructureSurfaceFitter {
record FlattenAnchor(SurfaceAnchor source, int radius, int range) {
}
record FlattenResolution(int targetY, int range, boolean foundation) {
record FlattenResolution(int targetY, int range) {
}
private record FlattenFootprint(List<FlattenAnchor> anchors, Map<Long, Integer> projectedSupport) {
@@ -273,10 +273,33 @@ public final class ModdedEntitySpawner {
if (type == null) {
return true;
}
if (irisEntity.getSurface().isFluid()) {
return isFluidAreaClearForSpawn(blockX, blockY, blockZ, type.getWidth(), type.getHeight(),
position -> ModdedWorldManager.matchesSurface(irisEntity.getSurface(), level.getBlockState(position)));
}
return isAreaClearForSpawn(blockX, blockY, blockZ, type.getWidth(), type.getHeight(),
position -> level.getBlockState(position).is(Blocks.AIR));
}
static boolean isFluidAreaClearForSpawn(int blockX, int blockY, int blockZ,
float width, float height, Predicate<BlockPos> matchesFluid) {
int startX = (int) Math.floor(blockX + 0.5 - width / 2D);
int endX = (int) Math.floor(Math.nextDown(blockX + 0.5 + width / 2D));
int endY = (int) Math.floor(Math.nextDown(blockY + 0.5 + height));
int startZ = (int) Math.floor(blockZ + 0.5 - width / 2D);
int endZ = (int) Math.floor(Math.nextDown(blockZ + 0.5 + width / 2D));
for (int x = startX; x <= endX; x++) {
for (int y = blockY; y <= endY; y++) {
for (int z = startZ; z <= endZ; z++) {
if (!matchesFluid.test(new BlockPos(x, y, z))) {
return false;
}
}
}
}
return true;
}
static boolean isAreaClearForSpawn(int blockX, int blockY, int blockZ, float width, float height, Predicate<BlockPos> isAir) {
int radius = (int) (width / 2F);
int endY = blockY + (int) height;
@@ -604,9 +604,11 @@ public final class ModdedWorldManager implements EngineWorldManager {
worldZ = (chunkZ << 4) + RNG.r.i(16);
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, worldX, worldZ) - 1;
int solidY = level.getHeight(Heightmap.Types.OCEAN_FLOOR, worldX, worldZ) - 1;
worldY = group == IrisSpawnGroup.NORMAL
? surfaceY + 1
: RNG.r.i(solidY + 1, surfaceY);
Integer selectedY = IrisEntitySpawn.selectSurfaceSpawnY(group, irisEntity.getSurface(), solidY, surfaceY, RNG.r);
if (selectedY == null) {
continue;
}
worldY = selectedY;
}
if (worldY <= level.getMinY() || worldY >= level.getMaxY()) {
continue;
@@ -653,6 +655,11 @@ public final class ModdedWorldManager implements EngineWorldManager {
if (!lightAllowed(spawner, level, worldX, worldY, worldZ)) {
continue;
}
if (irisEntity.getSurface().isFluid()
&& (!surfaceMatches(irisEntity.getSurface(), level, worldX, worldY, worldZ)
|| !ModdedEntitySpawner.isAreaClearForSpawn(level, irisEntity, worldX, worldY, worldZ))) {
continue;
}
if (ModdedEntitySpawner.spawn(engine, irisEntity, level, worldX, worldY, worldZ, entityRng) != null) {
spawned++;
}
@@ -681,11 +688,11 @@ public final class ModdedWorldManager implements EngineWorldManager {
}
private boolean surfaceMatches(IrisSurface surface, ServerLevel level, int worldX, int worldY, int worldZ) {
BlockState below = level.getBlockState(new BlockPos(worldX, worldY - 1, worldZ));
BlockState below = level.getBlockState(new BlockPos(worldX, worldY - (surface.isFluid() ? 0 : 1), worldZ));
return matchesSurface(surface, below);
}
private static boolean matchesSurface(IrisSurface surface, BlockState below) {
static boolean matchesSurface(IrisSurface surface, BlockState below) {
if (ModdedBlockResolution.isSolid(below)) {
return surface == IrisSurface.LAND || surface == IrisSurface.OVERWORLD
|| (surface == IrisSurface.ANIMAL && isAnimalGround(below));
@@ -0,0 +1,101 @@
package art.arcane.iris.modded;
import art.arcane.iris.engine.object.IrisEntity;
import art.arcane.iris.engine.object.IrisEntitySpawn;
import art.arcane.iris.engine.object.IrisSpawnGroup;
import art.arcane.iris.engine.object.IrisSurface;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ModdedWaterEntitySpawnTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void tadpoleVolumeRequiresWaterAndAcceptsAquaticPlants() {
ServerLevel level = mock(ServerLevel.class);
IrisEntity entity = new IrisEntity().setType("tadpole").setSurface(IrisSurface.WATER);
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.WATER.defaultBlockState());
assertTrue(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 78, 0));
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.SEAGRASS.defaultBlockState());
assertTrue(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 78, 0));
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.AIR.defaultBlockState());
assertFalse(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 79, 0));
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.LAVA.defaultBlockState());
assertFalse(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 78, 0));
}
@Test
public void lavaSurfaceRequiresLavaInsteadOfAirOrWater() {
ServerLevel level = mock(ServerLevel.class);
IrisEntity entity = new IrisEntity().setType("tadpole").setSurface(IrisSurface.LAVA);
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.LAVA.defaultBlockState());
assertTrue(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 78, 0));
when(level.getBlockState(any(BlockPos.class))).thenReturn(Blocks.WATER.defaultBlockState());
assertFalse(ModdedEntitySpawner.isAreaClearForSpawn(level, entity, 0, 78, 0));
}
@Test
public void waterloggedSolidBlocksStillObstructWaterSpawns() {
assertTrue(ModdedWorldManager.matchesSurface(IrisSurface.WATER,
Blocks.SEA_PICKLE.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, true)));
assertFalse(ModdedWorldManager.matchesSurface(IrisSurface.WATER,
Blocks.OAK_FENCE.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, true)));
}
@Test
public void fractionalFluidVolumeIncludesHorizontalEdgesAndCenteredHeight() {
Set<BlockPos> checked = new HashSet<>();
assertTrue(ModdedEntitySpawner.isFluidAreaClearForSpawn(-1, 78, -1, 1.3F, 0.8F, position -> {
checked.add(position);
return true;
}));
assertEquals(18, checked.size());
assertTrue(checked.contains(new BlockPos(-2, 79, -2)));
assertTrue(checked.contains(new BlockPos(0, 78, 0)));
assertFalse(ModdedEntitySpawner.isFluidAreaClearForSpawn(-1, 78, -1, 1.3F, 0.8F,
position -> !position.equals(new BlockPos(-2, 79, -2))));
}
@Test
public void exactUpperBoundaryDoesNotRequireWaterOutsideTheBody() {
Set<BlockPos> checked = new HashSet<>();
assertTrue(ModdedEntitySpawner.isFluidAreaClearForSpawn(0, 78, 0, 1F, 0.5F, position -> {
checked.add(position);
return true;
}));
assertEquals(Set.of(new BlockPos(0, 78, 0)), checked);
}
@Test
public void mixedSpawnerUsesFluidDepthWithoutChangingLandMembers() {
RNG rng = new RNG(1L);
assertEquals(Integer.valueOf(78), IrisEntitySpawn.selectSurfaceSpawnY(
IrisSpawnGroup.NORMAL, IrisSurface.WATER, 77, 78, rng));
assertEquals(Integer.valueOf(79), IrisEntitySpawn.selectSurfaceSpawnY(
IrisSpawnGroup.NORMAL, IrisSurface.LAND, 77, 78, rng));
assertNull(IrisEntitySpawn.selectSurfaceSpawnY(IrisSpawnGroup.NORMAL, IrisSurface.WATER, 78, 78, rng));
assertNull(IrisEntitySpawn.selectSurfaceSpawnY(IrisSpawnGroup.UNDERWATER, IrisSurface.WATER, 78, 78, rng));
}
}