Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.1.1.121012

- Smooth packed ice and powder snow with matching pieces.
- Keep powder snow's sinking, freezing, leather-boot support and fog within each piece's footprint.
- Add powder-snow slab, step and corner recipes with bucket returns and volume-preserving rebuilding.
- Keep different crafting results on separate recipe-book buttons.
- Smooth full snow-block terrain with matching snow slabs, steps and corners.
- Keep grass transitions snowy beside higher snow-covered ground.
- Gentle surface terraces use full-width slabs at smoothing levels 2 and 3, giving each rise a wider tread.
- Grass slabs, steps and corners take on their snowy look as the surrounding terrain generates.

## 0.1.0.121012

- Updated for Minecraft 1.21.1 on NeoForge.
Expand Down
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Sky's Terrain Smoother adds smaller transitions to new Overworld terrain,
including cave floors. It uses slabs, steps and corners from Sky's Grass Slabs
and Sky's Building Pieces, with matching materials for grass, dirt, stone,
sandstone, sand and hardened clay.
sandstone, sand, hardened clay, packed ice, full snow blocks and powder snow.

## Smoothing levels

Expand All @@ -14,16 +14,20 @@ 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
still get transitions. At levels 2 and 3, the engine keeps a fitting uphill step
or corner even when a whole slab would raise the downhill edge.

A single layer of snow doesn't block an otherwise valid transition. Deeper
snow, trees, crops and occupied blocks stay untouched.
A single layer of snow doesn't block an otherwise valid transition. Grass
slabs, steps and corners take on their snowy look alongside nearby snow and
on exposed cold ground as the terrain generates. Deeper snow, trees, crops
and occupied blocks stay untouched.

Only new, dry terrain is smoothed. Existing chunks, underwater floors and cave
ceilings stay unchanged. Cave passages need three clear blocks above the floor,
Expand All @@ -42,16 +46,33 @@ its slabs are enabled, Building Pieces provides the matching steps and corners.

Ores, gravel, wood and decorative stone aren't smoothing materials.

## Snow and powder snow

Packed ice and snow blocks use matching Building Pieces slabs, steps and corners.
Powder snow has its own horizontal pieces: they retain sinking, freezing and
slow movement, while leather boots let you stand on them. The empty part of a
step or corner stays empty, including its fog and freezing effects.

One powder-snow bucket makes two slabs and returns an empty bucket. To fill it
again, craft two slabs with an empty bucket. Slabs cut into steps, steps cut
into corners, and matching vertical pairs rebuild the larger pieces. Breaking
a partial piece returns that piece; it cannot fill a whole bucket on its own.
Matching halves placed together become an ordinary full powder-snow block.

For ordinary snow slabs, craft two snow blocks diagonally to make four slabs.
Vanilla's three-block row still makes snow layers. Recipe-book buttons show one
result each; right-click for alternative ways to make the same item.

For BOP grass and soils, add the
[Terrain Smoother Biomes O' Plenty add-on](https://github.com/MinecraftModDevelopmentMods/SkysTerrainSmoother-BiomesOPlenty-Addon).

## Installation and settings

For **Minecraft 1.21.1**, use **NeoForge 21.1.247** and **Java 21**, with:

- Sky's Grass Slabs **1.2.0.121012**.
- Sky's Building Pieces **0.3.9.121012**.
- Sky's Terrain Smoother **0.1.0.121012**.
- Sky's Grass Slabs **1.2.1.121012**.
- Sky's Building Pieces **0.3.10.121012**.
- Sky's Terrain Smoother **0.1.1.121012**.

Install all three NeoForge jars on both the client and server. If you use
the BOP add-on, keep its matching dependencies installed too.
Expand Down
5 changes: 5 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ southwest. Steps occupy one quarter of a block's volume and corners one eighth.
Every output must be half a block tall, dry, non-tile, and fill that footprint.
Sources and converted supports must be dry full blocks without tile entities.

Powder snow is the one non-solid exception. Planning validates its full outline
and the partial outlines of the built-in powder pieces, while their actual
entity collision remains native powder snow. Other non-solid sources and
empty-collision outputs are still rejected.

```java
TerrainSmoothingApi.registerMaterial(new TerrainMaterial(
ResourceLocation.fromNamespaceAndPath("example", "natural_rock"),
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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.121012
mod_version=0.1.1.121012
mod_group=zone.moddev.mc.skysterrainsmoother
mod_authors=SkyBlade1978
mod_description=Smooths new Overworld surfaces with existing slabs, steps and corners. Optional Mineralogy support.
16 changes: 8 additions & 8 deletions gradle/content-dependencies.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
grass.version=1.2.0.121012
grass.commit=6061fa264f3d4e710fde7279ffe6d3958fc98929
grass.sha256=F095DD84AF9BFB6F90A9C3A091B526508C9516D3B76E0162ED67B470004322F8
grass.development.sha256=F095DD84AF9BFB6F90A9C3A091B526508C9516D3B76E0162ED67B470004322F8
pieces.version=0.3.9.121012
pieces.commit=98a8c72378829b7815527015c8aa124718beea04
pieces.sha256=6E992F1A5CC79235C4FBD3E8EFCC2A865E147B793D2C7D20D6EF1815630F3105
pieces.development.sha256=6E992F1A5CC79235C4FBD3E8EFCC2A865E147B793D2C7D20D6EF1815630F3105
grass.version=1.2.1.121012
grass.commit=dfc69bddd7f9aa9a8ddde74673d3e5b19286b763
grass.sha256=9A122E63CCAC190246ECA4DD6D5A8D84CA0150A10AF7768142F97EF5DEB8A3C8
grass.development.sha256=9A122E63CCAC190246ECA4DD6D5A8D84CA0150A10AF7768142F97EF5DEB8A3C8
pieces.version=0.3.10.121012
pieces.commit=e251b23632f49eb54573bd6ff7373f4d00eb9560
pieces.sha256=322C3D68E40630C6C4672CD74FB438FA08E15FB30FE524B421145A80F7E48573
pieces.development.sha256=322C3D68E40630C6C4672CD74FB438FA08E15FB30FE524B421145A80F7E48573
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public void tick(ClientTickEvent.Post event) {
&& !minecraft.getConnection().getRecipeManager().getRecipes().isEmpty()) {
if (++settledWorldTicks < 20) return;
RecipePreviewModels.check(minecraft);
PowderSnowClientProbe.run(minecraft);
checkBiomeTints(minecraft);
zone.moddev.mc.skysterrainsmoother.SkysTerrainSmoother.logger.info("TERRAIN_SMOOTHER_CLIENT_WORLD_PASS");
integrated = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,82 @@ static int run(ServerLevel world) {
checks += checkSupport(world, floor, corner, false, true);
}
checks += shrinkingSlab(world, material);
checks += snowyTransitions(world, material);
zone.moddev.mc.skysterrainsmoother.SkysTerrainSmoother.logger.info("TERRAIN_FINE_GRASS_SUPPORT_PASS checks=" + checks);
return checks;
}

private static int snowyTransitions(ServerLevel world, TerrainMaterial material) {
BlockPos floor = new BlockPos(4207, 210, 4200);
BlockPos target = floor.above();
BlockPos lowerSnow = target.east();
LevelChunk owner = world.getChunkAt(floor);
BlockState snow = Blocks.SNOW.defaultBlockState();
int checks = 0;

for (int level = 1; level <= 3; level++) {
BlockPos higher = level == 3 ? target.north().west() : target.north();
BlockState dry = switch (level) {
case 1 -> material.slab;
case 2 -> material.step(0);
default -> material.corner(0);
};
BlockState snowy = dry.setValue(GrassBlock.SNOWY, true);

for (boolean lateSnow : new boolean[] { false, true }) {
prepare(world, floor);
world.setBlock(higher, Blocks.GRASS_BLOCK.defaultBlockState(), 2);
LevelChunk snowChunk = world.getChunkAt(lowerSnow);
if (!lateSnow) {
world.setBlock(higher.above(), snow, 2);
snowChunk.setBlockState(lowerSnow, snow, false);
}

int loaded = world.getChunkSource().getLoadedChunksCount();
SkysTerrainSmoother.engine.smooth(world, owner, level);
RuntimeProbe.require(world.getBlockState(target).equals(lateSnow ? dry : snowy),
"generated grass has the right snow appearance before ticking at level " + level
+ " / snow placed later: " + lateSnow);

if (lateSnow) {
// Match decoration writes that haven't notified the piece of its new neighbour.
snowChunk.setBlockState(lowerSnow, snow, false);
world.getChunkAt(higher).setBlockState(higher.above(), snow, false);
world.getChunkAt(higher).setBlockState(higher,
Blocks.GRASS_BLOCK.defaultBlockState().setValue(GrassBlock.SNOWY, true), false);
owner.setBlockState(floor, Blocks.DIAMOND_ORE.defaultBlockState(), false);
RuntimeProbe.require(world.getBlockState(target).equals(dry),
"late snow test starts with the generated piece still green");
ProbeWorld.repair(world, floor);
RuntimeProbe.require(world.getBlockState(target).equals(snowy),
"late decoration gives the same piece its snow cap at level " + level);
RuntimeProbe.require(world.getBlockState(floor).is(Blocks.DIAMOND_ORE),
"refreshing the snow appearance preserves an ore placed under the piece");
checks += 3;
}

RuntimeProbe.require(world.getBlockState(lowerSnow).equals(snow)
&& world.getBlockState(higher.above()).equals(snow),
"the grass cap uses the snow around it without removing either layer");
RuntimeProbe.require(world.getChunkSource().getLoadedChunksCount() == loaded,
"snow appearance reads never load chunks");
world.setBlock(lowerSnow, Blocks.AIR.defaultBlockState(), 3);
if (level != 3) {
RuntimeProbe.require(world.getBlockState(target).getValue(GrassBlock.SNOWY),
"the higher terrace still supplies snow after the lower snow is removed");
checks++;
}
world.setBlock(higher.above(), Blocks.AIR.defaultBlockState(), 3);
world.getBlockState(target).randomTick(world, target, RandomSource.create(1));
RuntimeProbe.require(world.getBlockState(target).equals(dry),
"removing the surrounding snow restores the green piece at level " + level);
checks += 4;
}
}
SkysTerrainSmoother.logger.info("TERRAIN_SNOWY_TRANSITIONS_PASS checks={}", checks);
return checks;
}

private static int checkSupport(ServerLevel world, BlockPos floor, BlockState piece,
boolean fullCover, boolean snow) {
RuntimeProbe.require(world.getBlockState(floor).getBlock() == (fullCover ? Blocks.DIRT : Blocks.GRASS_BLOCK),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package zone.moddev.mc.skysterrainsmoother.probe;

import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Block;
import zone.moddev.mc.skysterrainsmoother.SkysTerrainSmoother;
import zone.moddev.mc.skysterrainsmoother.internal.MaterialCatalogue;
import zone.moddev.mc.skysterrainsmoother.internal.TerrainGeometry;

/** Frozen surfaces need explicit mappings; their similar appearance is not a substitute. */
final class FrozenTerrainProbe {
static int run(ServerLevel world) {
RuntimeProbe.require(MaterialCatalogue.instance().source(Blocks.PACKED_ICE.defaultBlockState()) != null,
"Packed ice has its own smoothing material");
int checks = 1 + PowderSnowProbe.run(world);
BlockPos floor = new BlockPos(5076, world.getMaxBuildHeight() - 10, 5076);
var owner = world.getChunkAt(floor);
int loaded = world.getChunkSource().getLoadedChunksCount();
for (Block source : new Block[] { Blocks.PACKED_ICE, Blocks.POWDER_SNOW }) {
var material = MaterialCatalogue.instance().source(source.defaultBlockState());
RuntimeProbe.require(material != null, "Every frozen surface has a distinct material");
for (int level = 1; level <= 3; level++) {
for (Direction direction : Direction.Plane.HORIZONTAL) {
for (int offsetZ = -3; offsetZ <= 3; offsetZ++) {
for (int offsetX = -3; offsetX <= 3; offsetX++) {
BlockPos column = floor.offset(offsetX, 0, offsetZ);
world.setBlock(column, source.defaultBlockState(), 2);
world.setBlock(column.below(), source.defaultBlockState(), 2);
for (int height = 1; height <= 8; height++) {
ProbeWorld.clear(world, column.above(height));
}
}
}
BlockPos higher = level == 3 ? floor.relative(direction).relative(direction.getClockWise()).above()
: floor.relative(direction).above();
world.setBlock(higher, source.defaultBlockState(), 2);
ProbeWorld.rebuildHeights(owner);
var support = world.getBlockState(floor);
SkysTerrainSmoother.engine.smooth(world, owner, level);
var piece = world.getBlockState(floor.above());
RuntimeProbe.require(MaterialCatalogue.instance().piece(piece) == material,
"Frozen terrain receives its own pieces at every level and rotation: " + source + " / " + level);
RuntimeProbe.require(world.getBlockState(floor).equals(support),
"Frozen terrain smoothing preserves the original full support");
RuntimeProbe.require(TerrainGeometry.shape(piece).bounds().maxY == .5,
"Every frozen transition stays half a block tall");
if (source == Blocks.POWDER_SNOW) {
RuntimeProbe.require(piece.getCollisionShape(world, floor.above()).isEmpty(),
"Generated powder pieces stay non-solid");
}
SkysTerrainSmoother.engine.smooth(world, owner, level);
RuntimeProbe.require(world.getBlockState(floor.above()).equals(piece),
"Repeating a frozen generation pass is idempotent");
checks += 5;
}
}
}
RuntimeProbe.require(world.getChunkSource().getLoadedChunksCount() == loaded,
"Frozen terrain reads do not force neighbouring chunks");
SkysTerrainSmoother.logger.info("TERRAIN_FROZEN_SURFACES_PASS checks={} materials=2 levels=3 rotations=4", checks);
return checks;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ static int generationOrder(ServerLevel world) throws Exception {

for (int order = 0; order < 2; order++) {
int origin = order == 0 ? 128 : 256;
ProbeWorld.coldBiome(world, origin, 128, 9);
List<LevelChunk> chunks = new ArrayList<>();

for (int blockZ = 0; blockZ < 9; blockZ++) {
Expand Down
Loading
Loading