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"));