diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e4d44cc..ae6bc99 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -203,9 +203,9 @@ jobs:
if-no-files-found: error
retention-days: 30
path: |
- build/libs/SkysTerrainSmoother-0.1.0.2603001.jar
- build/libs/SkysTerrainSmoother-0.1.0.2603001-sources.jar
- build/libs/SkysTerrainSmoother-0.1.0.2603001-javadoc.jar
+ build/libs/SkysTerrainSmoother-0.1.1.2603001.jar
+ build/libs/SkysTerrainSmoother-0.1.1.2603001-sources.jar
+ build/libs/SkysTerrainSmoother-0.1.1.2603001-javadoc.jar
build/release/SHA256SUMS
CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28ea402..f0ca1a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 0.1.1.2603001
+
+- Gentle surface terraces use full-width slabs at smoothing levels 2 and 3, giving each rise a wider tread.
+
## 0.1.0.2603001
- Updated Terrain Smoother for Minecraft 26.3 on Forge.
diff --git a/README.md b/README.md
index bc8008b..643b862 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,10 @@ Set `worldgen.smoothingLevel` in `config/skysterrainsmoother-common.toml`:
3. **Slabs, steps and corners** - also uses pieces covering a quarter of a
slab's footprint.
-Every smoothing piece is half a block tall. If a material doesn't have steps
-or corners, it uses its slab at higher levels wherever a slab fits safely.
+Every smoothing piece is half a block tall. Gentle surface terraces use a
+full-width slab when there is room on both sides of a one-block rise. If a
+material doesn't have steps or corners, it uses its slab at higher levels
+wherever a slab fits safely.
Narrow mountain ledges keep their downhill edge at its original height, rather
than gaining a row of slabs that recreates a full-block step. Wider terraces
@@ -52,7 +54,7 @@ For **Minecraft 26.3**, use **Forge 66.0.2** and **Java 25**, with:
- Sky's Grass Slabs **1.2.0.2603001**.
- Sky's Building Pieces **0.3.9.2603001**.
-- Sky's Terrain Smoother **0.1.0.2603001**.
+- Sky's Terrain Smoother **0.1.1.2603001**.
Install all three main jars on both the client and server.
diff --git a/build.gradle b/build.gradle
index fdf265a..abd1a2f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -760,6 +760,11 @@ eclipse {
downloadSources = true
downloadJavadoc = true
file.whenMerged { classpath ->
+ // Forge supplies MixinExtras at launch. Its common jar is only needed for compilation.
+ classpath.entries.removeAll { entry ->
+ entry instanceof org.gradle.plugins.ide.eclipse.model.Library &&
+ file(entry.path).name.startsWith('mixinextras-common-')
+ }
// Eclipse copies these files on every build. Use Gradle's expanded metadata, never the template.
def resources = classpath.entries.findAll {
it instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder &&
@@ -889,6 +894,12 @@ tasks.register('verifyEclipseProductionClasspath') {
}
File metadata = file('bin/main/META-INF/mods.toml')
def eclipseClasspath = new groovy.xml.XmlSlurper().parse(file('.classpath'))
+ def mixinExtrasLibraries = eclipseClasspath.classpathentry.findAll {
+ it.@kind == 'lib' && file(it.@path.text()).name.startsWith('mixinextras-')
+ }.collect { file(it.@path.text()).name }
+ if (mixinExtrasLibraries.size() != 1 || !mixinExtrasLibraries.first().startsWith('mixinextras-forge-')) {
+ throw new GradleException('Eclipse must use the MixinExtras runtime supplied by Forge')
+ }
def piecesLibraries = eclipseClasspath.classpathentry.findAll {
it.@kind == 'lib' && it.@path.text().tokenize('/\\').last().startsWith('SkysBuildingPieces-')
}*.@path*.text().collect { file(it).canonicalFile }
diff --git a/gradle.properties b/gradle.properties
index 650777b..cc6edb3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -24,7 +24,7 @@ release_enabled=true
curseforge_project_id=1768953
mod_name=Sky's Terrain Smoother
mod_license=LGPL-2.1-only
-mod_version=0.1.0.2603001
+mod_version=0.1.1.2603001
mod_group=zone.moddev.mc.skysterrainsmoother
mod_authors=SkyBlade1978
mod_description=Smooths new Overworld surfaces with existing slabs, steps and corners. Optional Mineralogy support.
diff --git a/gradle/development-log4j2.xml b/gradle/development-log4j2.xml
index 1281087..4fe80ca 100644
--- a/gradle/development-log4j2.xml
+++ b/gradle/development-log4j2.xml
@@ -4,7 +4,8 @@
-
+
+
diff --git a/src/integrationTest/java/zone/moddev/mc/skysterrainsmoother/probe/TransitionProbe.java b/src/integrationTest/java/zone/moddev/mc/skysterrainsmoother/probe/TransitionProbe.java
index e8e37f3..2114e9f 100644
--- a/src/integrationTest/java/zone/moddev/mc/skysterrainsmoother/probe/TransitionProbe.java
+++ b/src/integrationTest/java/zone/moddev/mc/skysterrainsmoother/probe/TransitionProbe.java
@@ -38,6 +38,12 @@ static int run(ServerLevel world) {
BlockPos upperCap = origin.relative(uphill).above(2);
RuntimeProbe.require(MaterialCatalogue.instance().piece(world.getBlockState(lowCap)) == material,
"the foot of a steep slope still receives its matching transition");
+ BlockState step = material.step(direction(uphill));
+ if (level >= 2 && step != null) {
+ RuntimeProbe.require(world.getBlockState(lowCap).equals(step),
+ "the foot of consecutive rises keeps its uphill step");
+ checks++;
+ }
requireOpenDownhillEdge(world, upperCap, uphill.getOpposite(),
"consecutive mountain ledges, level " + level + " / " + uphill);
RuntimeProbe.require(world.getChunkSource().getLoadedChunksCount() == loadedBefore,
@@ -48,9 +54,11 @@ static int run(ServerLevel world) {
prepare(world, origin, uphill, source, true, roofed);
SkysTerrainSmoother.engine.smooth(world, owner, level);
- RuntimeProbe.require(MaterialCatalogue.instance()
- .piece(world.getBlockState(origin.relative(uphill).above())) == material,
- "the bottom of a wider terrace is smoothed");
+ BlockPos terraceCap = origin.relative(uphill).above();
+ BlockState expected = roofed && level >= 2 && step != null ? step : material.slab;
+ RuntimeProbe.require(world.getBlockState(terraceCap).equals(expected),
+ "gentle surface terraces use a slab and cave terraces keep their existing shape: "
+ + source + " / " + uphill + " / level " + level + " / roofed " + roofed);
RuntimeProbe.require(MaterialCatalogue.instance()
.piece(world.getBlockState(origin.relative(uphill, 3).above(2))) == material,
"a wider upper terrace retains room for its own transition");
@@ -75,7 +83,7 @@ static int run(ServerLevel world) {
checks += 2;
}
- checks += borders(world) + lateLowerFloor(world, origin);
+ checks += borders(world) + gentleBorders(world) + lateLowerFloor(world, origin);
zone.moddev.mc.skysterrainsmoother.SkysTerrainSmoother.logger.info("TERRAIN_SMOOTHER_TRANSITIONS_PASS checks=" + checks);
return checks;
}
@@ -117,6 +125,40 @@ private static int borders(ServerLevel world) {
return checks;
}
+ private static int gentleBorders(ServerLevel world) {
+ TerrainMaterial material = MaterialCatalogue.instance().source(Blocks.STONE.defaultBlockState());
+ int checks = 0;
+ for (Direction uphill : new Direction[] { Direction.EAST, Direction.SOUTH }) {
+ BlockPos origin = uphill == Direction.EAST ? new BlockPos(3614, 210, 3656)
+ : new BlockPos(3656, 210, 3614);
+ BlockPos low = origin.relative(uphill);
+ LevelChunk lowerOwner = world.getChunkAt(low);
+ LevelChunk upperOwner = world.getChunkAt(low.relative(uphill));
+ for (boolean reverse : new boolean[] { false, true }) {
+ prepare(world, origin, uphill, Blocks.STONE.defaultBlockState(), true, false);
+ int loadedBefore = world.getChunkSource().getLoadedChunksCount();
+ SkysTerrainSmoother.engine.smooth(world, reverse ? upperOwner : lowerOwner, 3);
+ SkysTerrainSmoother.engine.smooth(world, reverse ? lowerOwner : upperOwner, 3);
+ RuntimeProbe.require(world.getBlockState(low.above()).equals(material.slab),
+ "the same gentle slab fits in both chunk orders: " + uphill);
+ RuntimeProbe.require(world.getChunkSource().getLoadedChunksCount() == loadedBefore,
+ "terrace lookahead never loads a chunk");
+ checks += 2;
+ }
+ }
+ return checks;
+ }
+
+ private static int direction(Direction uphill) {
+ return switch (uphill) {
+ case NORTH -> 0;
+ case EAST -> 1;
+ case SOUTH -> 2;
+ case WEST -> 3;
+ default -> throw new IllegalArgumentException("Expected horizontal uphill direction");
+ };
+ }
+
private static int lateLowerFloor(ServerLevel world, BlockPos origin) {
LevelChunk owner = world.getChunkAt(origin);
int checks = 0;
diff --git a/src/main/java/zone/moddev/mc/skysterrainsmoother/SkysTerrainSmoother.java b/src/main/java/zone/moddev/mc/skysterrainsmoother/SkysTerrainSmoother.java
index 2df59e2..a571e47 100644
--- a/src/main/java/zone/moddev/mc/skysterrainsmoother/SkysTerrainSmoother.java
+++ b/src/main/java/zone/moddev/mc/skysterrainsmoother/SkysTerrainSmoother.java
@@ -29,7 +29,7 @@
public final class SkysTerrainSmoother {
public static final String ID = "skysterrainsmoother";
public static final String NAME = "Sky's Terrain Smoother";
- public static final String VERSION = "0.1.0.2603001";
+ public static final String VERSION = "0.1.1.2603001";
public static final Logger logger = LogManager.getLogger();
public static final SmoothingEngine engine = new SmoothingEngine(MaterialCatalogue.instance());
public static boolean enabled = true;
diff --git a/src/main/java/zone/moddev/mc/skysterrainsmoother/internal/SmoothingEngine.java b/src/main/java/zone/moddev/mc/skysterrainsmoother/internal/SmoothingEngine.java
index 2bf5eb3..10f8bba 100644
--- a/src/main/java/zone/moddev/mc/skysterrainsmoother/internal/SmoothingEngine.java
+++ b/src/main/java/zone/moddev/mc/skysterrainsmoother/internal/SmoothingEngine.java
@@ -244,8 +244,8 @@ private boolean validate(LevelAccessor world, int chunkX, int chunkZ, List= 2 && lowerEdges == 0 && result != material.slab && Integer.bitCount(cardinal) == 1) {
+ int uphill = Integer.numberOfTrailingZeros(cardinal);
+ if (safeMask == Footprint.EDGES[uphill]) {
+ // A full slab gives a broader tread where both sides of a one-block rise have room.
+ int dx = DX[uphill];
+ int dz = DZ[uphill];
+ if (hasTerrainFloor(world, owner, support.offset(-dx, 0, -dz))
+ && hasTerrainFloor(world, owner, support.offset(2 * dx, 1, 2 * dz))) {
+ return material.slab;
+ }
+ }
+ }
+
+ return result;
}
private static BlockState supportAfterPlacement(TerrainMaterial material, BlockState piece) {
@@ -586,14 +609,19 @@ private boolean higher(LevelAccessor world, ChunkAccess owner, int localX, int l
int worldX = (owner.getPos().x() << 4) + localX;
int worldZ = (owner.getPos().z() << 4) + localZ;
- ChunkAccess neighbour = loaded(world, worldX >> 4, worldZ >> 4);
+ return hasTerrainFloor(world, owner, new BlockPos(worldX, lowerY + 1, worldZ));
+ }
+ private boolean hasTerrainFloor(LevelAccessor world, ChunkAccess owner, BlockPos floor) {
+ int chunkX = floor.getX() >> 4;
+ int chunkZ = floor.getZ() >> 4;
+ ChunkAccess neighbour = chunkX == owner.getPos().x() && chunkZ == owner.getPos().z()
+ ? owner : loaded(world, chunkX, chunkZ);
if (neighbour == null) {
return false;
}
- // Compare logical floors at this height, including existing caps over BOP's native soil.
- BlockPos floor = new BlockPos(worldX, lowerY + 1, worldZ);
+ // Read the same logical height before and after a neighbouring column is capped.
BlockState state = neighbour.getBlockState(floor);
BlockState above = neighbour.getBlockState(floor.above());
@@ -601,8 +629,7 @@ private boolean higher(LevelAccessor world, ChunkAccess owner, int localX, int l
TerrainMaterial cap = catalogue.piece(above);
if (cap != null) {
- return source != null || catalogue.source(state) == cap
- || cap.supportAfterPlacement != null && state.equals(cap.supportAfterPlacement)
+ return source != null || cap.supportAfterPlacement != null && state.equals(cap.supportAfterPlacement)
|| oreSupport(state);
}
diff --git a/src/test/java/zone/moddev/mc/skysterrainsmoother/ProjectContractTest.java b/src/test/java/zone/moddev/mc/skysterrainsmoother/ProjectContractTest.java
index 9f40efe..99216ce 100644
--- a/src/test/java/zone/moddev/mc/skysterrainsmoother/ProjectContractTest.java
+++ b/src/test/java/zone/moddev/mc/skysterrainsmoother/ProjectContractTest.java
@@ -15,7 +15,7 @@ class ProjectContractTest {
void targetMetadataAndToolchainMatchTheNativeRuntime() throws Exception {
Properties metadata = new Properties();
metadata.load(new java.io.StringReader(text(Paths.get("gradle.properties"))));
- assertEquals("0.1.0.2603001", metadata.getProperty("mod_version"));
+ assertEquals("0.1.1.2603001", metadata.getProperty("mod_version"));
assertEquals(metadata.getProperty("mod_version"), SkysTerrainSmoother.VERSION);
assertEquals("26.3", metadata.getProperty("minecraft_version"));
assertEquals("66.0.2", metadata.getProperty("forge_version"));