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 @@ -110,9 +110,9 @@ jobs:
if-no-files-found: error
retention-days: 30
path: |
build/libs/SkysGrassSlabs-1.2.0.121012.jar
build/libs/SkysGrassSlabs-1.2.0.121012-sources.jar
build/libs/SkysGrassSlabs-1.2.0.121012-javadoc.jar
build/libs/SkysGrassSlabs-1.2.1.121012.jar
build/libs/SkysGrassSlabs-1.2.1.121012-sources.jar
build/libs/SkysGrassSlabs-1.2.1.121012-javadoc.jar
build/release/SHA256SUMS
CHANGELOG.md

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ jobs:
with:
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
curseforge-id: ${{ needs.preflight.outputs.curseforge_project_id }}
curseforge-retry-attempts: 1
name: Sky's Grass Slabs ${{ needs.preflight.outputs.release_version }} for Minecraft ${{ needs.preflight.outputs.minecraft_version }} (${{ needs.preflight.outputs.loader_display }})
version: ${{ needs.preflight.outputs.release_version }}
version-type: ${{ inputs.curseforge_release_level }}
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.2.1.121012 - Minecraft 1.21.1 (NeoForge)

- Keep different crafting results on separate recipe-book buttons.
- Keep slab joins from placing a full block inside a player or creature.
- Respect adventure-mode placement permissions when applying turf to dirt slabs.
- Give slabs a snowy top beside a raised snow-covered terrace.
- Keep exposed slabs snowy in cold areas where snow layers cannot settle.

## 1.1.1.121012 - Minecraft 1.21.1 (NeoForge)

- Corrected snowy grass slabs so their exposed top uses the snow texture
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 1.21.1. Version `1.2.0.121012` requires NeoForge
This branch is for Minecraft 1.21.1. Version `1.2.1.121012` requires NeoForge
21.1.247 or a compatible NeoForge 21.1 build and Java 21.

## Installation
Expand Down Expand Up @@ -34,7 +34,7 @@ or updating world changing mods.
- [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.121012.md)
- [Release notes](docs/RELEASE-1.2.1.121012.md)

## License

Expand Down
3 changes: 2 additions & 1 deletion docs/GAMEPLAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ normal durability damage. Dirt path slabs do not spread grass or support
plants.

Grass and dirt slabs show a snow covered top and snowy side edges when vanilla
snow is nearby. This is a visual effect and does not add collision height.
snow is nearby or the slab is exposed to snowfall in a cold biome. This is a
visual effect and does not add collision height.

## Turf

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

Slabs beside a higher snow-covered terrace now keep their snowy top.
They also recognise the snow pieces supplied by Sky's Building Pieces.
Exposed slabs in cold areas keep a snow cap even where a snow layer cannot
settle. In warmer areas, removing the surrounding snow restores their green
or dirt appearance.

Different crafting results now have separate recipe-book buttons. Slab joins
also respect placement permissions and leave room for players and creatures.

This build uses Minecraft 1.21.1, NeoForge 21.1.247 and Java 21.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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.121012
mod_version=1.2.1.121012
# 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 @@ -299,6 +299,19 @@ public static void snowPlantsBonemealAndWaterAreTopAware(GameTestHelper helper)
require(helper, !clearDirt.getValue(net.minecraft.world.level.block.SnowyDirtBlock.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);
BlockState raisedSnow = grass.updateShape(bottom, Direction.EAST,
helper.getLevel().getBlockState(terrace), helper.getLevel(), pos, terrace);
require(helper, raisedSnow.getValue(GrassSlabBlock.SNOWY),
"snow on the higher terrace did not give the slab its snowy top");
helper.getLevel().setBlock(terrace.above(), Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL);
BlockState clearedTerrace = grass.updateShape(raisedSnow, Direction.EAST,
helper.getLevel().getBlockState(terrace), helper.getLevel(), pos, terrace);
require(helper, !clearedTerrace.getValue(GrassSlabBlock.SNOWY),
"slab retained snow after the higher terrace was cleared");

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 @@ -772,6 +785,44 @@ private static void require(GameTestHelper helper, boolean condition, String mes
}
}

@GameTest(template = EMPTY, batch = "slabs025")
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 = helper.spawn(EntityType.COW, new BlockPos(2, 2, 2));
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(template = EMPTY, batch = "slabs025")
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
Expand Up @@ -35,7 +35,7 @@ public final class SkysGrassSlabs {
/** Stable 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.121012";
public static final String VERSION = "1.2.1.121012";
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. */
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.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 @@ -37,6 +37,11 @@ public InteractionResult useOn(UseOnContext context) {
if (current.getValue(SlabBlock.WATERLOGGED)) {
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);

Expand All @@ -49,9 +54,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 @@ -15,6 +15,7 @@
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.phys.shapes.CollisionContext;

/** Slab item that normalizes a matching pair to its vanilla full block. */
public final class NormalizingSlabItem extends BlockItem {
Expand Down Expand Up @@ -56,6 +57,9 @@ private InteractionResult combine(UseOnContext context, BlockPos pos) {
return InteractionResult.FAIL;
}
BlockState combined = combinedBlock.defaultBlockState();
if (!level.isUnobstructed(combined, pos, CollisionContext.empty())) {
return InteractionResult.FAIL;
}
if (!level.setBlock(pos, combined, Block.UPDATE_ALL_IMMEDIATE)) {
return InteractionResult.FAIL;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shaped",
"group": "skys/result/minecraft/dirt", "type": "minecraft:crafting_shaped",
"pattern": [
"SS"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shaped",
"group": "skys/result/skysgrassslabs/dirt_slab", "type": "minecraft:crafting_shaped",
"category": "building",
"pattern": ["###"],
"key": { "#": { "item": "minecraft:dirt" } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shapeless",
"group": "skys/result/minecraft/grass_block", "type": "minecraft:crafting_shapeless",
"category": "building",
"ingredients": [
{ "item": "minecraft:dirt" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shaped",
"group": "skys/result/minecraft/grass_block", "type": "minecraft:crafting_shaped",
"pattern": [
"SS"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shaped",
"group": "skys/result/skysgrassslabs/grass_slab", "type": "minecraft:crafting_shaped",
"category": "building",
"pattern": ["###"],
"key": { "#": { "item": "minecraft:grass_block" } },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "minecraft:crafting_shapeless",
"group": "skys/result/skysgrassslabs/grass_slab", "type": "minecraft:crafting_shapeless",
"category": "building",
"ingredients": [
{ "item": "skysgrassslabs:dirt_slab" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,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.121012.md")));
assertTrue(Files.isRegularFile(Path.of("docs/RELEASE-1.2.1.121012.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.121012"));
assertTrue(properties.contains("mod_version=1.2.1.121012"));
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 All @@ -88,6 +88,7 @@ public void releaseDispatcherIsExplicitAndUsesTheImmutableBundle() throws Except
assertTrue(properties.contains("loader_code=2"));
assertTrue(properties.contains("curseforge_project_id=1677588"));
assertTrue(workflow.contains("confirm_live_publication:"));
assertTrue(workflow.contains("curseforge-retry-attempts: 1"));
assertFalse(workflow.contains(" environment:\n name: release"));
assertTrue(workflow.contains("CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}"));
assertTrue(workflow.contains("MAVEN_UPLOAD_URL: ${{ secrets.MAVEN_UPLOAD_URL }}"));
Expand Down Expand Up @@ -126,7 +127,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.121012\""));
assertTrue(main.contains("VERSION = \"1.2.1.121012\""));
assertTrue(state.contains("skysgrassslabs_world_state"));
assertTrue(state.contains("SCHEMA_VERSION = 1"));
assertTrue(state.contains("schema_version"));
Expand Down Expand Up @@ -273,9 +274,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.121012.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.0.121012-sources.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.0.121012-javadoc.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.121012.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.121012-sources.jar"));
assertTrue(ci.contains("SkysGrassSlabs-1.2.1.121012-javadoc.jar"));
assertTrue(ci.contains("if-no-files-found: error"));
assertTrue(ci.contains("java-version: '21.0.7+6.0.LTS'"));
assertTrue(ci.contains("--offline --no-daemon"));
Expand Down
Loading