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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ jobs:
if-no-files-found: error
retention-days: 30
path: |
build/libs/SkysGrassSlabs-1.2.0.2603001.jar
build/libs/SkysGrassSlabs-1.2.0.2603001-sources.jar
build/libs/SkysGrassSlabs-1.2.0.2603001-javadoc.jar
build/libs/SkysGrassSlabs-1.2.1.2603001.jar
build/libs/SkysGrassSlabs-1.2.1.2603001-sources.jar
build/libs/SkysGrassSlabs-1.2.1.2603001-javadoc.jar
build/release/SHA256SUMS
CHANGELOG.md

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.2.1.2603001

- Improve snow caps beside higher snowy ground and on exposed cold slopes.
- Keep different crafting results on separate recipe-book buttons.
- Prevent slab joining through entities and respect building permissions when using turf.


## 1.2.0.2603001 - Minecraft 26.3

- Craft two matching dirt or grass slabs back into a full block.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ thin layer of turf. Grass spreads naturally between full blocks and slabs,
while new Overworld terrain uses grass slabs to soften suitable one block
steps.

This branch is for Minecraft 26.3. Version `1.2.0.2603001` requires Forge
This branch is for Minecraft 26.3. Version `1.2.1.2603001` requires Forge
66.0.2 or a compatible Forge 66 build and Java 25.

## Installation
Expand Down Expand Up @@ -38,7 +38,7 @@ generation setting still decides whether grass is included.
- [Gameplay and recipes](docs/GAMEPLAY.md)
- [Configuration](docs/CONFIGURATION.md)
- [Upgrading an older world](docs/WORLD-UPGRADES.md)
- [Release notes](docs/RELEASE-1.2.0.2603001.md)
- [Release notes](docs/RELEASE-1.2.1.2603001.md)

## License

Expand Down
11 changes: 11 additions & 0 deletions docs/RELEASE-1.2.1.2603001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Sky's Grass Slabs 1.2.1 for Forge 26.3

Grass and dirt slabs pick up snow from nearby higher terraces and exposed cold
ground. Different crafting results have their own recipe-book buttons, so the
book no longer cycles between unrelated shapes.

Slabs cannot combine into a full block while an entity occupies the space. Turf
also respects the player's building permissions when greening a dirt slab.

Existing blocks, items, settings and saved worlds keep their identities. Terrain
Smoother remains optional; Grass Slabs still works on its own.
2 changes: 1 addition & 1 deletion docs/WORLD-UPGRADES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ world has been checked in game.

## Sky's Grass Slabs content

Install `SkysGrassSlabs-1.2.0.2603001.jar` before opening a world that used an
Install `SkysGrassSlabs-1.2.1.2603001.jar` before opening a world that used an
earlier release. The permanent dirt slab, grass slab, path slab and turf IDs
are unchanged.

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ mod_name=Sky's Grass Slabs
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=LGPL-2.1-only
# The mod version. See https://semver.org/
mod_version=1.2.0.2603001
mod_version=1.2.1.2603001
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,20 @@ public static void snowPlantsBonemealAndWaterAreTopAware(GameTestHelper helper)
require(helper, !clearDirt.getValue(net.minecraft.world.level.block.SnowyBlock.SNOWY),
"dirt slab retained its snowy appearance after snow was removed");

BlockPos terrace = pos.east();
helper.getLevel().setBlock(terrace, Blocks.GRASS_BLOCK.defaultBlockState(), Block.UPDATE_ALL);
helper.getLevel().setBlock(terrace.above(), Blocks.SNOW.defaultBlockState(), Block.UPDATE_ALL);
helper.getLevel().setBlock(pos, bottom, Block.UPDATE_ALL);
BlockState raisedSnow = bottom.updateShape(helper.getLevel(), helper.getLevel(), pos,
Direction.EAST, terrace, helper.getLevel().getBlockState(terrace), RandomSource.create(4));
require(helper, raisedSnow.getValue(GrassSlabBlock.SNOWY),
"Snow on a higher terrace must cover the slab");
helper.getLevel().setBlock(terrace.above(), Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
BlockState clearedSnow = raisedSnow.updateShape(helper.getLevel(), helper.getLevel(), pos,
Direction.EAST, terrace, helper.getLevel().getBlockState(terrace), RandomSource.create(4));
require(helper, !clearedSnow.getValue(GrassSlabBlock.SNOWY),
"The slab snow cap must clear with the terrace snow");

BlockState wetGrass = top.setValue(SlabBlock.WATERLOGGED, true);
helper.getLevel().setBlock(pos, wetGrass, Block.UPDATE_ALL);
grass.randomTick(wetGrass, helper.getLevel(), pos, RandomSource.create(3L));
Expand Down Expand Up @@ -798,6 +812,45 @@ private static void require(GameTestHelper helper, boolean condition, String mes
}
}

@GameTest(structure = EMPTY)
public static void slabJoiningRespectsEntityCollision(GameTestHelper helper) {
BlockPos position = helper.absolutePos(new BlockPos(2, 2, 2));
BlockState slab = ModBlocks.DIRT_SLAB.get().defaultBlockState();
helper.getLevel().setBlock(position, slab, Block.UPDATE_ALL);
var occupant = EntityTypes.COW.create(helper.getLevel(), EntitySpawnReason.COMMAND);
helper.getLevel().addFreshEntity(occupant);
occupant.setPos(position.getX() + .5, position.getY() + .5, position.getZ() + .5);
require(helper, !helper.getLevel().isUnobstructed(Blocks.DIRT.defaultBlockState(), position,
CollisionContext.empty()), "The join fixture needs an obstructed upper half");
Player player = helper.makeMockPlayer(GameType.SURVIVAL);
ItemStack held = new ItemStack(ModBlocks.DIRT_SLAB_ITEM.get());
player.setItemInHand(InteractionHand.MAIN_HAND, held);
held.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND,
new BlockHitResult(Vec3.atCenterOf(position), Direction.UP, position, false)));
require(helper, helper.getLevel().getBlockState(position).equals(slab) && held.getCount() == 1,
"Joining slabs must not put a full block inside an entity or consume the item");
occupant.discard();
helper.succeed();
}

@GameTest(structure = EMPTY)
public static void turfGreeningRespectsAdventurePermissions(GameTestHelper helper) {
BlockPos position = helper.absolutePos(new BlockPos(2, 2, 2));
BlockState dirt = ModBlocks.DIRT_SLAB.get().defaultBlockState();
helper.getLevel().setBlock(position, dirt, Block.UPDATE_ALL);
Player player = helper.makeMockPlayer(GameType.ADVENTURE);
player.getAbilities().mayBuild = false;
require(helper, !player.getAbilities().mayBuild, "The permission fixture must be in adventure mode");
ItemStack held = new ItemStack(ModBlocks.TURF_ITEM.get());
player.setItemInHand(InteractionHand.MAIN_HAND, held);
held.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND,
new BlockHitResult(Vec3.atCenterOf(position), Direction.UP, position, false)));
require(helper, helper.getLevel().getBlockState(position).equals(dirt) && held.getCount() == 1,
"Turf greening must respect adventure placement permissions");
helper.succeed();
}


private static final class FixedRandom extends LegacyRandomSource {
private final int[] values;
private int index;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "skysgrassslabs:slab/block_items_use_translated_block_description_ids",
"max_ticks": 100,
"structure": "forge:empty8x4x8"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "skysgrassslabs:slab/slab_joining_respects_entity_collision",
"max_ticks": 100,
"structure": "forge:empty8x4x8"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "skysgrassslabs:slab/turf_greening_respects_adventure_permissions",
"max_ticks": 100,
"structure": "forge:empty8x4x8"
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public final class SkysGrassSlabs {
/** Stable Forge mod identifier and resource namespace. */
public static final String MOD_ID = "skysgrassslabs";
public static final String NAME = "Sky's Grass Slabs";
public static final String VERSION = "1.2.0.2603001";
public static final String VERSION = "1.2.1.2603001";
public static final Logger LOGGER = LogManager.getLogger();

/** Registers content, configuration, world generation, and persistent state. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,49 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.tags.BlockTags;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.block.state.BlockState;

/** Computes the visual snow cap from nearby vanilla snow. */
/** Computes the visual snow cap from nearby snow and exposed cold ground. */
final class SnowySlabAppearance {
static boolean hasNearbySnow(BlockGetter level, BlockPos pos) {
if (isSnow(level.getBlockState(pos.above()).getBlock())) {
return true;
}
for (Direction direction : Direction.Plane.HORIZONTAL) {
if (isSnow(level.getBlockState(pos.relative(direction)).getBlock())) {
return true;
}
}
return false;
return hasNearbySnow(level, pos, null, null);
}

static boolean hasNearbySnow(BlockGetter level, BlockPos pos, Direction changedDirection,
BlockState changedState) {
if (changedDirection == Direction.UP) {
if (isSnow(changedState.getBlock())) {
return true;
}
} else if (isSnow(level.getBlockState(pos.above()).getBlock())) {
BlockState above = changedDirection == Direction.UP ? changedState : readState(level, pos.above());
if (above.is(BlockTags.SNOW)) {
return true;
}
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockPos neighbourPos = pos.relative(direction);
BlockState neighbour = direction == changedDirection
? changedState : level.getBlockState(pos.relative(direction));
if (isSnow(neighbour.getBlock())) {
? changedState : readState(level, neighbourPos);
// A higher terrace carries its snow one block above the piece's horizontal neighbour.
if (neighbour.is(BlockTags.SNOW) || neighbour.isCollisionShapeFullBlock(level, neighbourPos)
&& readState(level, neighbourPos.above()).is(BlockTags.SNOW)) {
return true;
}
}
return false;
// Partial tops cannot support vanilla snow layers, but exposed cold ground still needs its snow cap.
return level instanceof LevelReader reader
&& reader.getBiome(pos).value().coldEnoughToSnow(pos, reader.getSeaLevel())
&& reader.getHeight(Heightmap.Types.MOTION_BLOCKING, pos.getX(), pos.getZ()) <= pos.getY() + 1
&& reader.getBrightness(LightLayer.BLOCK, pos.above()) < 10;
}

private static boolean isSnow(Block block) {
return block == Blocks.SNOW || block == Blocks.SNOW_BLOCK;
private static BlockState readState(BlockGetter level, BlockPos pos) {
if (level instanceof Level world && !world.isLoaded(pos)
|| level instanceof WorldGenRegion region && !region.hasChunk(pos.getX() >> 4, pos.getZ() >> 4)) {
return net.minecraft.world.level.block.Blocks.AIR.defaultBlockState();
}
return level.getBlockState(pos);
}

private SnowySlabAppearance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public InteractionResult useOn(UseOnContext context) {
return InteractionResult.FAIL;
}

Player player = context.getPlayer();
ItemStack held = context.getItemInHand();
if (held.isEmpty() || player != null
&& !player.mayUseItemAt(pos, context.getClickedFace(), held)) {
return InteractionResult.FAIL;
}

BlockState grass = SlabTransitions.grassFor(current);

if (grass.hasProperty(GrassSlabBlock.SNOWY)) {
Expand All @@ -49,9 +56,6 @@ public InteractionResult useOn(UseOnContext context) {
return InteractionResult.FAIL;
}

Player player = context.getPlayer();
ItemStack held = context.getItemInHand();

if (player instanceof ServerPlayer serverPlayer) {
CriteriaTriggers.PLACED_BLOCK.trigger(serverPlayer, pos, held);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ private InteractionResult combine(UseOnContext context, BlockPos pos) {
return InteractionResult.FAIL;
}
BlockState combined = combinedBlock.defaultBlockState();
if (!level.setBlock(pos, combined, Block.UPDATE_ALL_IMMEDIATE)) {
if (!level.isUnobstructed(combined, pos, net.minecraft.world.phys.shapes.CollisionContext.empty())
|| !level.setBlock(pos, combined, Block.UPDATE_ALL_IMMEDIATE)) {
return InteractionResult.FAIL;
}
SoundType sound = combined.getSoundType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"result": {
"id": "minecraft:dirt"
},
"category": "building"
"category": "building",
"group": "skys/result/minecraft/dirt"
}
14 changes: 11 additions & 3 deletions src/main/resources/data/skysgrassslabs/recipe/dirt_slab.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"type": "minecraft:crafting_shaped",
"category": "building",
"pattern": ["###"],
"key": { "#": "minecraft:dirt" },
"result": { "id": "skysgrassslabs:dirt_slab", "count": 6 }
"pattern": [
"###"
],
"key": {
"#": "minecraft:dirt"
},
"result": {
"id": "skysgrassslabs:dirt_slab",
"count": 6
},
"group": "skys/result/skysgrassslabs/dirt_slab"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"minecraft:dirt",
"#c:seeds"
],
"result": { "id": "minecraft:grass_block" }
"result": {
"id": "minecraft:grass_block"
},
"group": "skys/result/minecraft/grass_block"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"result": {
"id": "minecraft:grass_block"
},
"category": "building"
"category": "building",
"group": "skys/result/minecraft/grass_block"
}
14 changes: 11 additions & 3 deletions src/main/resources/data/skysgrassslabs/recipe/grass_slab.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"type": "minecraft:crafting_shaped",
"category": "building",
"pattern": ["###"],
"key": { "#": "minecraft:grass_block" },
"result": { "id": "skysgrassslabs:grass_slab", "count": 6 }
"pattern": [
"###"
],
"key": {
"#": "minecraft:grass_block"
},
"result": {
"id": "skysgrassslabs:grass_slab",
"count": 6
},
"group": "skys/result/skysgrassslabs/grass_slab"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"skysgrassslabs:dirt_slab",
"#c:seeds"
],
"result": { "id": "skysgrassslabs:grass_slab" }
"result": {
"id": "skysgrassslabs:grass_slab"
},
"group": "skys/result/skysgrassslabs/grass_slab"
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ public void playerAndMaintainerDocumentsExist() {
assertTrue(Files.isRegularFile(Path.of("docs/GAMEPLAY.md")));
assertTrue(Files.isRegularFile(Path.of("docs/WORLD-UPGRADES.md")));
assertFalse(Files.exists(Path.of("docs/REPOSITORY.md")));
assertTrue(Files.isRegularFile(Path.of("docs/RELEASE-1.2.0.2603001.md")));
assertTrue(Files.isRegularFile(Path.of("docs/RELEASE-1.2.1.2603001.md")));
assertTrue(Files.isRegularFile(Path.of("docs/BETA-0.2.0.118021.md")));
}

@Test
public void releaseIdentityAndLicenseAreStable() throws Exception {
String properties = Files.readString(Path.of("gradle.properties"), StandardCharsets.UTF_8);
assertTrue(properties.contains("mod_version=1.2.0.2603001"));
assertTrue(properties.contains("mod_version=1.2.1.2603001"));
assertTrue(properties.contains("mod_license=LGPL-2.1-only"));
assertEquals("LGPL-2.1-only", Files.readString(Path.of("LICENSE.spdx"), StandardCharsets.UTF_8).trim());
assertTrue(Files.readString(Path.of("NOTICE"), StandardCharsets.UTF_8)
Expand Down Expand Up @@ -112,7 +112,7 @@ public void commonConfigAndWorldStateUsePermanentKeys() throws Exception {
assertTrue(config.contains("push(\"compat\")"));
assertTrue(config.contains("define(FORCE_REPLACE_BUILDINGBRICKS_SLABS, false)"));
assertTrue(config.contains("define(FORCE_REPLACE_GRASS_SLABS_MOD_CONTENT, false)"));
assertTrue(main.contains("VERSION = \"1.2.0.2603001\""));
assertTrue(main.contains("VERSION = \"1.2.1.2603001\""));
assertTrue(state.contains("skysgrassslabs_world_state"));
assertTrue(state.contains("SCHEMA_VERSION = 1"));
assertTrue(state.contains("schema_version"));
Expand Down Expand Up @@ -299,9 +299,9 @@ public void continuousIntegrationTargetsTheStableBranchAndArtifacts() throws Exc
}
String ci = Files.readString(Path.of(".github/workflows/ci.yml"),
StandardCharsets.UTF_8);
assertTrue(ci.contains("SkysGrassSlabs-1.2.0.2603001.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.0.2603001-sources.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.0.2603001-javadoc.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.2603001.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.2603001-sources.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.2603001-javadoc.jar"));
assertTrue(ci.contains("if-no-files-found: error"));
assertEquals(0, ci.lines()
.filter(line -> line.contains("java-version: '8.0.502+7'"))
Expand Down
Loading