From e7a37f68cd331c24c2ab7ebccb74816463a6699b Mon Sep 17 00:00:00 2001
From: JohnBraham <22159126+SkyBlade1978@users.noreply.github.com>
Date: Tue, 6 Oct 2026 12:25:19 +0100
Subject: [PATCH 1/3] Add shared grass lifecycle and safe legacy slab recovery
---
.github/workflows/ci.yml | 6 +-
CHANGELOG.md | 7 +
README.md | 4 +-
build.gradle | 14 ++
docs/CONFIGURATION.md | 5 +
docs/INTEGRATION.md | 16 ++
docs/RELEASE-1.0.2.110021.md | 11 ++
docs/RELEASE-1.1.0.110021.md | 11 ++
docs/WORLD-UPGRADES.md | 6 +
gradle.properties | 2 +-
.../integrationtest/AliasRecoveryChecks.java | 94 +++++++++++
.../integrationtest/IntegrationTestMod.java | 2 +
src/integrationTest/resources/log4j2.xml | 8 +
.../mc/skysgrassslabs/SkysGrassSlabs.java | 12 +-
.../mc/skysgrassslabs/api/GrassSlabsApi.java | 75 +++++++++
.../mc/skysgrassslabs/block/GrassSpread.java | 17 +-
.../compat/BuildingBricksCompat.java | 47 +++++-
.../compat/LegacyMigrationHandler.java | 153 ++++++++++++------
.../compat/LegacySlabAliasBlock.java | 11 ++
.../config/SkysGrassSlabsConfig.java | 4 +-
.../mc/skysgrassslabs/proxy/ClientProxy.java | 26 +++
.../skysgrassslabs/ProjectContractTest.java | 19 ++-
.../block/GrassFormsContractTest.java | 33 ++++
.../compat/MigrationMappingTest.java | 24 +++
24 files changed, 538 insertions(+), 69 deletions(-)
create mode 100644 docs/INTEGRATION.md
create mode 100644 docs/RELEASE-1.0.2.110021.md
create mode 100644 docs/RELEASE-1.1.0.110021.md
create mode 100644 src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java
create mode 100644 src/integrationTest/resources/log4j2.xml
create mode 100644 src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
create mode 100644 src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacySlabAliasBlock.java
create mode 100644 src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d861a3b..ec6febb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -56,9 +56,9 @@ jobs:
if-no-files-found: error
retention-days: 30
path: |
- build/libs/SkysGrassSlabs-1.0.0.110021.jar
- build/libs/SkysGrassSlabs-1.0.0.110021-sources.jar
- build/libs/SkysGrassSlabs-1.0.0.110021-javadoc.jar
+ build/libs/SkysGrassSlabs-1.1.0.110021.jar
+ build/libs/SkysGrassSlabs-1.1.0.110021-sources.jar
+ build/libs/SkysGrassSlabs-1.1.0.110021-javadoc.jar
build/release/SHA256SUMS
CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c401b8..8521f72 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 1.1.0.110021 - Minecraft 1.10.2
+
+- Added optional shared grass rules for Building Pieces and safe generation
+ ownership for Terrain Smoother. Standalone grass smoothing is retained.
+- Fixed legacy slab recovery in worlds containing both old and Sky slabs.
+- Preserved slab orientation, saved item data and earlier migration history.
+
## 1.0.1.110021 - Minecraft 1.10.2
- Corrected the side texture alignment on top and bottom path slabs so it
diff --git a/README.md b/README.md
index fd97c1b..0bc32ac 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Sky's Grass Slabs adds dirt, grass and lowered path slabs, along with a 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.10.2. Version `1.0.1.110021` requires Forge
+This branch is for Minecraft 1.10.2. Version `1.1.0.110021` requires Forge
12.18.3.2511 or a compatible Forge 12.18.3 build and Java 8.
## Installation
@@ -30,7 +30,7 @@ or updating world changing mods.
- [Gameplay and recipes](docs/GAMEPLAY.md)
- [Configuration](docs/CONFIGURATION.md)
- [Existing worlds and legacy slabs](docs/WORLD-UPGRADES.md)
-- [Release notes](docs/RELEASE-1.0.1.110021.md)
+- [Release notes](docs/RELEASE-1.1.0.110021.md)
## License
diff --git a/build.gradle b/build.gradle
index 1fc0eba..5c04908 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,6 +34,11 @@ ext.functional_version = versionParts[0..2].join('.')
ext.display_version = project.mod_version
ext.release_tag = project.mod_version
+def pinnedJava8 = providers.environmentVariable('SGS_JAVA8_HOME')
+def exactJava8Spec = pinnedJava8.isPresent() ?
+ org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec.fromJavaHome(
+ project.services.get(org.gradle.api.internal.provider.PropertyFactory), file(pinnedJava8.get())) : null
+
java {
toolchain.languageVersion = JavaLanguageVersion.of(8)
withSourcesJar()
@@ -48,6 +53,7 @@ tasks.withType(JavaCompile).configureEach {
targetCompatibility = JavaVersion.VERSION_1_8
options.encoding = 'UTF-8'
options.compilerArgs.addAll(['-Xmaxerrs', '1000'])
+ if (pinnedJava8.isPresent()) javaCompiler = javaToolchains.compilerFor(exactJava8Spec)
}
tasks.named('compileTestJava', JavaCompile) {
@@ -57,9 +63,15 @@ tasks.named('compileTestJava', JavaCompile) {
tasks.withType(Test).configureEach {
useJUnitPlatform()
workingDir = project.projectDir
+ if (pinnedJava8.isPresent()) javaLauncher = javaToolchains.launcherFor(exactJava8Spec)
+}
+
+tasks.withType(JavaExec).configureEach {
+ if (pinnedJava8.isPresent()) javaLauncher = javaToolchains.launcherFor(exactJava8Spec)
}
tasks.withType(Javadoc).configureEach {
+ if (pinnedJava8.isPresent()) javadocTool = javaToolchains.javadocToolFor(exactJava8Spec)
failOnError = false
options.encoding = 'UTF-8'
options.addStringOption('Xdoclint:none', '-quiet')
@@ -160,6 +172,7 @@ def configureFromForgeRun = { JavaExec process, String runTaskName ->
run.setClasspath(files(forge10DevelopmentOutput, run.classpath.filter {
!splitMainOutputs.contains(it)
}))
+ if (pinnedJava8.isPresent()) run.executable = javaToolchains.launcherFor(exactJava8Spec).get().executablePath.asFile.path
run.exec()
} finally {
runOptions.workingDir.set(originalConfiguredWorkingDir)
@@ -275,6 +288,7 @@ def createIntegrationProcess = { String phase, Object dependency ->
dependsOn dependency
workingDir integrationRunDirectory
systemProperty 'skysgrassslabs.integrationPhase', phase.toLowerCase(Locale.ROOT)
+ systemProperty 'log4j.configurationFile', file('src/integrationTest/resources/log4j2.xml').toURI().toString()
configureFromForgeRun(delegate, 'runServer')
doLast {
File marker = new File(integrationRunDirectory.get().asFile,
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 0754b3b..13c4886 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -13,6 +13,11 @@ When enabled, suitable one block slopes in newly generated Overworld chunks
may receive grass slabs. Set this to `false` to leave new terrain unchanged.
The setting never adds or removes slabs in chunks that already exist.
+With Sky's Terrain Smoother installed, this setting controls its grass material.
+Terrain Smoother owns the only Sky terrain pass and has its own master switch.
+Removing Terrain Smoother restores the standalone grass slab generator; no
+configuration file needs to be changed.
+
When the supported legacy slab mod is also installed, enabling Sky's world
smoothing turns off its overlapping grass slab generator. A backup of the
legacy configuration is made before it is changed. This affects generation
diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md
new file mode 100644
index 0000000..621f750
--- /dev/null
+++ b/docs/INTEGRATION.md
@@ -0,0 +1,16 @@
+# Optional content integration
+
+Grass Slabs remains standalone. Terrain Smoother 0.1 uses Grass Slabs 1.1's
+version-one API in `zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi`.
+
+A terrain owner calls `claimSmoothing(modid, enabled)` before Grass Slabs
+pre-initialization. Installed ownership suppresses the separate grass pass,
+including when the owner is disabled. `grassGenerationAllowed()` incorporates
+the player's grass setting and the legacy-generator arbitration result.
+
+Content mods register ordinary dirt/grass state pairs with `registerGrassForm`
+during initialization, one pair for each orientation. Registration rejects
+conflicts and closes at load completion. Never register coarse dirt or podzol.
+`tickGrass`, `tickDirt` and `repairSupport` retain matching state orientation,
+grass lighting rules and covered-target rejection. Integration must be optional
+when the other content mod is not installed.
diff --git a/docs/RELEASE-1.0.2.110021.md b/docs/RELEASE-1.0.2.110021.md
new file mode 100644
index 0000000..b8b6e98
--- /dev/null
+++ b/docs/RELEASE-1.0.2.110021.md
@@ -0,0 +1,11 @@
+# Sky's Grass Slabs 1.0.2 for Minecraft 1.10.2
+
+Improved recovery of legacy grass and dirt slabs in worlds that already contain
+Sky slabs. Both slab orientations and saved item data are preserved, including
+content in chunks visited by an earlier migration.
+
+Gameplay, recipes, terrain generation and configuration defaults are unchanged.
+Back up your world before removing any mod. Other legacy building pieces need
+their own supported replacements.
+
+Requires Minecraft 1.10.2, Forge 12.18.3.2511 and Java 8.
diff --git a/docs/RELEASE-1.1.0.110021.md b/docs/RELEASE-1.1.0.110021.md
new file mode 100644
index 0000000..79c9800
--- /dev/null
+++ b/docs/RELEASE-1.1.0.110021.md
@@ -0,0 +1,11 @@
+# Sky's Grass Slabs 1.1.0 for Minecraft 1.10.2
+
+Adds optional integration with Sky's Building Pieces and Sky's Terrain Smoother.
+Grass can spread between their matching dirt and grass pieces, and the smoother
+can manage terrain generation without running a second grass slab pass.
+
+The mod still works on its own. Existing blocks, recipes, configuration defaults
+and saved migration progress are retained. This release also includes improved
+recovery of legacy slabs in mixed worlds. Back up existing worlds before updating.
+
+Requires Minecraft 1.10.2, Forge 12.18.3.2511 and Java 8.
diff --git a/docs/WORLD-UPGRADES.md b/docs/WORLD-UPGRADES.md
index 0f406e4..5a58f94 100644
--- a/docs/WORLD-UPGRADES.md
+++ b/docs/WORLD-UPGRADES.md
@@ -49,6 +49,12 @@ historical grass slab IDs are recovered automatically as Sky slabs. Forge may
still warn about unsupported BuildingBricks content and may remove that
content after offering its normal backup and missing block confirmation.
+Recovery also works in mixed worlds containing both legacy and permanent Sky
+IDs, including chunks already marked by a previous migration. Hidden recovery
+holders keep those identities separate while loading; they are not creative
+items or normal recipe ingredients. Existing migration counters and reports
+remain historical and are not reset during absent-mod recovery.
+
Do not continue past that warning unless the listed unsupported blocks are no
longer needed. Keeping BuildingBricks installed is the safest choice for a
world that still uses its other shapes.
diff --git a/gradle.properties b/gradle.properties
index 96c7a36..cff9d61 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -23,7 +23,7 @@ curseforge_project_id=1677588
mod_id=skysgrassslabs
mod_name=Sky's Grass Slabs
mod_license=LGPL-2.1-only
-mod_version=1.0.1.110021
+mod_version=1.1.0.110021
mod_group=zone.moddev.mc.skysgrassslabs
mod_authors=SkyBlade1978
mod_description=Adds dirt, grass and path slabs, turf, natural grass spreading and smoother Overworld terrain.
diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java
new file mode 100644
index 0000000..f6035c1
--- /dev/null
+++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java
@@ -0,0 +1,94 @@
+package zone.moddev.mc.skysgrassslabs.integrationtest;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.item.EntityItem;
+import net.minecraft.entity.item.EntityItemFrame;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.init.Blocks;
+import net.minecraft.nbt.*;
+import net.minecraft.tileentity.TileEntityChest;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.WorldServer;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraftforge.event.world.ChunkDataEvent;
+import net.minecraftforge.event.entity.EntityJoinWorldEvent;
+import net.minecraftforge.common.util.FakePlayer;
+import net.minecraftforge.common.util.FakePlayerFactory;
+import net.minecraftforge.fml.common.gameevent.PlayerEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import net.minecraft.util.EnumFacing;
+import com.mojang.authlib.GameProfile;
+import java.util.UUID;
+import zone.moddev.mc.skysgrassslabs.compat.*;
+import zone.moddev.mc.skysgrassslabs.init.ModBlocks;
+import zone.moddev.mc.skysgrassslabs.world.ModWorldState;
+
+/** Mixed permanent/historical identities, including an already migrated chunk. */
+public final class AliasRecoveryChecks {
+ private static BlockPos pos(int x){return new BlockPos(4096+x,80,4096);}
+ public static void fresh(WorldServer world) {
+ Block[] aliases=BuildingBricksCompat.legacyAliases();require(aliases.length==3,"three distinct holders");
+ NBTTagCompound before=state(world);
+ for(int i=0;i<3;i++)for(int orientation=0;orientation<2;orientation++) {
+ Block alias=aliases[i];require(alias!=ModBlocks.GRASS_SLAB&&alias!=ModBlocks.DIRT_SLAB,"holder identity must not collide with permanent IDs");
+ world.setBlockState(pos(i*2+orientation),alias.getStateFromMeta(orientation),2);
+ }
+ world.setBlockState(pos(6),sky(true).getStateFromMeta(0),2);
+ world.setBlockState(pos(7),sky(false).getStateFromMeta(1),2);
+ world.setBlockState(pos(8),Blocks.CHEST.getDefaultState(),2);
+ TileEntityChest chest=(TileEntityChest)world.getTileEntity(pos(8));
+ for(int i=0;i<3;i++)chest.setInventorySlotContents(i,stack(aliases[i],i+7));
+ // A real entity join exercises the early live-item path without a counter reset.
+ EntityItem dropped=new EntityItem(world,4096,83,4096,stack(aliases[2],11));
+ new LegacyMigrationHandler().joinEntity(new EntityJoinWorldEvent(dropped,world));
+ require(dropped.getEntityItem().getItem()==Item.getItemFromBlock(ModBlocks.GRASS_SLAB)&&dropped.getEntityItem().stackSize==11&&dropped.getEntityItem().getTagCompound().getString("custom").equals("preserved"),"dropped stack recovery");
+ LegacyMigrationHandler events=new LegacyMigrationHandler();
+ EntityItemFrame frame=new EntityItemFrame(world,pos(9),EnumFacing.NORTH);
+ frame.setDisplayedItem(stack(aliases[1],1));
+ events.joinEntity(new EntityJoinWorldEvent(frame,world));
+ checkStack(frame.getDisplayedItem(),ModBlocks.DIRT_SLAB,1,"frame");
+ FakePlayer player=FakePlayerFactory.get(world,new GameProfile(
+ UUID.fromString("11111111-1111-4111-8111-111111111111"),"SlabRecoveryTest"));
+ player.inventory.setInventorySlotContents(0,stack(aliases[0],13));
+ player.getInventoryEnderChest().setInventorySlotContents(0,stack(aliases[1],17));
+ events.playerLogin(new PlayerEvent.PlayerLoggedInEvent(player));
+ checkStack(player.inventory.getStackInSlot(0),ModBlocks.GRASS_SLAB,13,"player");
+ checkStack(player.getInventoryEnderChest().getStackInSlot(0),ModBlocks.DIRT_SLAB,17,"ender chest");
+ // Queue a loaded chest, then put a historical stack through its live handler.
+ world.setBlockState(pos(10),Blocks.CHEST.getDefaultState(),2);
+ events.loadChunk(new ChunkDataEvent.Load(world.getChunkFromBlockCoords(pos(10)),new NBTTagCompound()));
+ TileEntityChest handlerChest=(TileEntityChest)world.getTileEntity(pos(10));
+ handlerChest.setInventorySlotContents(0,stack(aliases[2],19));
+ events.migrateLoadedHandlers(new TickEvent.ServerTickEvent(TickEvent.Phase.END));
+ checkStack(handlerChest.getStackInSlot(0),ModBlocks.GRASS_SLAB,19,"live item handler");
+ events.playerLogin(new PlayerEvent.PlayerLoggedInEvent(player));
+ checkStack(player.inventory.getStackInSlot(0),ModBlocks.GRASS_SLAB,13,"second login");
+ require(before.equals(state(world)),"absent recovery preserves historical counters");
+ System.out.println("SKY_ALIAS_FIXTURE_SEEDED historical_and_permanent_ids=true player_ender_frame_handler=true");
+ }
+ public static void reload(WorldServer world) {
+ NBTTagCompound before=state(world);
+ for(int i=0;i<3;i++)for(int orientation=0;orientation<2;orientation++) {
+ Block expected=i==1?ModBlocks.DIRT_SLAB:ModBlocks.GRASS_SLAB;
+ require(world.getBlockState(pos(i*2+orientation)).equals(expected.getStateFromMeta(orientation)),"alias block orientation "+i+" / "+orientation);
+ }
+ require(world.getBlockState(pos(6)).equals(sky(true).getStateFromMeta(0))&&world.getBlockState(pos(7)).equals(sky(false).getStateFromMeta(1)),"permanent mixed content unchanged");
+ TileEntityChest chest=(TileEntityChest)world.getTileEntity(pos(8));
+ for(int i=0;i<3;i++){ItemStack s=chest.getStackInSlot(i);require(s!=null&&s.stackSize==i+7&&s.getItem()==Item.getItemFromBlock(i==1?ModBlocks.DIRT_SLAB:ModBlocks.GRASS_SLAB)&&s.getTagCompound().getString("custom").equals("preserved"),"chest count and NBT");}
+ // Revisit a marker that predates the remaining historical content.
+ Chunk chunk=world.getChunkFromBlockCoords(pos(0));Block alias=BuildingBricksCompat.grassSlab();world.setBlockState(pos(0),alias.getStateFromMeta(0),2);
+ NBTTagCompound root=new NBTTagCompound(),level=new NBTTagCompound(),section=new NBTTagCompound(),marker=new NBTTagCompound();root.setTag("Level",level);marker.setInteger("buildingbricks_migration_version",1);marker.setString("future_field","preserved");root.setTag("skysgrassslabs",marker);
+ byte[] low=new byte[4096],high=new byte[2048],data=new byte[2048];int id=Block.getIdFromBlock(alias);low[0]=(byte)id;high[0]=(byte)(id>>8);section.setByte("Y",(byte)5);section.setByteArray("Blocks",low);section.setByteArray("Add",high);section.setByteArray("Data",data);NBTTagList sections=new NBTTagList();sections.appendTag(section);level.setTag("Sections",sections);
+ LegacyMigrationHandler events=new LegacyMigrationHandler();events.loadChunk(new ChunkDataEvent.Load(chunk,root));
+ require(world.getBlockState(pos(0)).equals(sky(true).getStateFromMeta(0)),"old marker cannot skip remaining recovery");
+ NBTTagCompound saved=new NBTTagCompound();events.saveChunk(new ChunkDataEvent.Save(chunk,saved));require(saved.getCompoundTag("skysgrassslabs").equals(marker),"complete old marker preserved");
+ require(before.equals(state(world)),"reload preserves counters");
+ System.out.println("SKY_ALIAS_RECOVERY_PASS orientations=2 aliases=3 counts_and_nbt=true old_marker=true");
+ }
+ private static ItemStack stack(Block alias,int count){ItemStack s=new ItemStack(alias,count,0);NBTTagCompound tag=new NBTTagCompound();tag.setString("custom","preserved");s.setTagCompound(tag);return s;}
+ private static void checkStack(ItemStack stack,Block block,int count,String context){require(stack!=null&&stack.getItem()==Item.getItemFromBlock(block)&&stack.stackSize==count&&stack.getTagCompound().getString("custom").equals("preserved"),context+" identity/count/NBT");}
+ private static NBTTagCompound state(WorldServer world){return ((net.minecraft.world.WorldSavedData)ModWorldState.get(world)).writeToNBT(new NBTTagCompound());}
+ private static Block sky(boolean grass){return grass?ModBlocks.GRASS_SLAB:ModBlocks.DIRT_SLAB;}
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+}
diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
index 9d3bd4f..eb6a61e 100644
--- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
+++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
@@ -104,6 +104,7 @@ public void serverStarted(FMLServerStartedEvent event) {
if ("fresh".equals(phase)) {
int gameplayChecks = verifyGameplay(server, world);
int worldgenChecks = verifyWorldgen(world);
+ if(BuildingBricksCompat.hasLegacyAliases())AliasRecoveryChecks.fresh(world);
evidence.setProperty("gameplay_checks", Integer.toString(gameplayChecks));
evidence.setProperty("worldgen_checks", Integer.toString(worldgenChecks));
if (Loader.isModLoaded(BuildingBricksCompat.MOD_ID)) {
@@ -122,6 +123,7 @@ public void serverStarted(FMLServerStartedEvent event) {
require(ModWorldState.SCHEMA_VERSION == 1,
"World-state schema changed during reload");
require(ModWorldState.get(world) != null, "World state did not reload");
+ if(BuildingBricksCompat.hasLegacyAliases())AliasRecoveryChecks.reload(world);
if ("true".equals(evidence.getProperty("compat_fixture_seeded"))) {
require(!SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(),
"Default coexistence reload unexpectedly enabled replacement");
diff --git a/src/integrationTest/resources/log4j2.xml b/src/integrationTest/resources/log4j2.xml
new file mode 100644
index 0000000..d15f663
--- /dev/null
+++ b/src/integrationTest/resources/log4j2.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java
index 2063a35..da52173 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java
@@ -5,6 +5,7 @@
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent;
import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
@@ -17,13 +18,14 @@
import zone.moddev.mc.skysgrassslabs.init.ModRecipes;
import zone.moddev.mc.skysgrassslabs.proxy.CommonProxy;
import zone.moddev.mc.skysgrassslabs.world.GrassSlabSmoothingHandler;
+import zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi;
@Mod(modid = SkysGrassSlabs.MOD_ID, name = SkysGrassSlabs.NAME,
version = SkysGrassSlabs.VERSION, dependencies = "before:buildingbricks")
public final class SkysGrassSlabs {
public static final String MOD_ID = "skysgrassslabs";
public static final String NAME = "Sky's Grass Slabs";
- public static final String VERSION = "1.0.1.110021";
+ public static final String VERSION = "1.1.0.110021";
@SidedProxy(
clientSide = "zone.moddev.mc.skysgrassslabs.proxy.ClientProxy",
@@ -37,13 +39,16 @@ public void preInit(FMLPreInitializationEvent event) {
logger = event.getModLog();
SkysGrassSlabsConfig.load(event.getSuggestedConfigurationFile());
ModBlocks.register();
+ for (int meta = 0; meta < 2; ++meta)
+ GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getStateFromMeta(meta),
+ ModBlocks.GRASS_SLAB.getStateFromMeta(meta));
BuildingBricksCompat.preInit(event.getModConfigurationDirectory());
MinecraftForge.EVENT_BUS.register(new CommonEvents());
LegacyMigrationHandler migration = new LegacyMigrationHandler();
MinecraftForge.EVENT_BUS.register(migration);
FMLCommonHandler.instance().bus().register(migration);
- if (SkysGrassSlabsConfig.isSmoothingActive()) {
+ if (!GrassSlabsApi.hasSmoothingOwner() && SkysGrassSlabsConfig.isSmoothingActive()) {
MinecraftForge.EVENT_BUS.register(new GrassSlabSmoothingHandler());
}
proxy.preInit();
@@ -60,6 +65,9 @@ public void postInit(FMLPostInitializationEvent event) {
BuildingBricksCompat.resolveBlocks();
}
+ @Mod.EventHandler
+ public void loadComplete(FMLLoadCompleteEvent event) { GrassSlabsApi.freeze(); }
+
@Mod.EventHandler
public void missingMappings(FMLMissingMappingsEvent event) {
LegacyMigrationHandler.remapMissingMappings(event);
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
new file mode 100644
index 0000000..4e0425d
--- /dev/null
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
@@ -0,0 +1,75 @@
+package zone.moddev.mc.skysgrassslabs.api;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.init.Blocks;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import zone.moddev.mc.skysgrassslabs.block.GrassSpread;
+import zone.moddev.mc.skysgrassslabs.config.SkysGrassSlabsConfig;
+
+/** Optional content integration. Registration closes after mod initialization. */
+public final class GrassSlabsApi {
+ public static final int VERSION = 1;
+ private static final Map GRASS = new LinkedHashMap<>();
+ private static final Map DIRT = new LinkedHashMap<>();
+ private static String smoothingOwner;
+ private static boolean ownerEnabled, configurationLoaded, frozen;
+
+ private GrassSlabsApi() { }
+
+ /** Claim before Grass Slabs pre-initializes; an installed disabled owner still owns the pass. */
+ public static void claimSmoothing(String owner, boolean enabled) {
+ if (configurationLoaded || owner == null || owner.isEmpty() || smoothingOwner != null)
+ throw new IllegalStateException("Smoothing ownership must be claimed once before configuration loads");
+ smoothingOwner = owner;
+ ownerEnabled = enabled;
+ }
+
+ public static boolean hasSmoothingOwner() { return smoothingOwner != null; }
+ public static boolean ownerAllowsGeneration() { return smoothingOwner == null || ownerEnabled; }
+ public static boolean grassGenerationAllowed() { return SkysGrassSlabsConfig.isSmoothingActive(); }
+
+ /** Register each ordinary dirt/grass orientation, never coarse dirt or podzol. */
+ public static void registerGrassForm(IBlockState dirt, IBlockState grass) {
+ if (frozen) throw new IllegalStateException("Grass form registration is closed");
+ if (dirt == null || grass == null || dirt.getBlock() == grass.getBlock() ||
+ dirt.getBlock().hasTileEntity(dirt) || grass.getBlock().hasTileEntity(grass))
+ throw new IllegalArgumentException("Grass forms need distinct non-tile dirt and grass states");
+ dirt = canonical(dirt); grass = canonical(grass);
+ if (DIRT.containsKey(dirt) || GRASS.containsKey(grass))
+ throw new IllegalArgumentException("Conflicting grass form mapping");
+ DIRT.put(dirt, grass); GRASS.put(grass, dirt);
+ }
+
+ public static boolean isGrassForm(IBlockState state) { return GRASS.containsKey(canonical(state)); }
+ public static IBlockState grassFor(IBlockState dirt) { return DIRT.get(canonical(dirt)); }
+ public static IBlockState dirtFor(IBlockState grass) { return GRASS.get(canonical(grass)); }
+ public static void configurationLoaded() { configurationLoaded = true; }
+ public static void freeze() { frozen = true; }
+
+ public static void tickDirt(World world, BlockPos pos, IBlockState state, Random random) {
+ GrassSpread.tickDirtSlab(world, pos, state, random);
+ }
+
+ public static void tickGrass(World world, BlockPos pos, IBlockState state, Random random) {
+ if (world.isRemote) return;
+ repairSupport(world, pos);
+ if (!GrassSpread.canRemainGrass(world, pos)) {
+ IBlockState dirt = dirtFor(state);
+ if (dirt != null) world.setBlockState(pos, dirt, 3);
+ } else GrassSpread.spreadFrom(world, pos, random, pos.down());
+ }
+
+ public static void repairSupport(World world, BlockPos pos) {
+ if (!world.isRemote && world.isBlockLoaded(pos.down()) &&
+ world.getBlockState(pos.down()).getBlock() == Blocks.GRASS)
+ world.setBlockState(pos.down(), Blocks.DIRT.getDefaultState(), 2);
+ }
+
+ private static IBlockState canonical(IBlockState state) {
+ return state.getBlock().getStateFromMeta(state.getBlock().getMetaFromState(state));
+ }
+}
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
index cb59143..5484ab4 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
@@ -8,6 +8,7 @@
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import zone.moddev.mc.skysgrassslabs.init.ModBlocks;
+import zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi;
public final class GrassSpread {
private static final int SPREAD_ATTEMPTS = 4;
@@ -52,8 +53,8 @@ public static void tickDirtSlab(World world, BlockPos target, IBlockState state,
return;
}
if (isViableSource(world, source)) {
- world.setBlockState(target, ModBlocks.GRASS_SLAB.getDefaultState()
- .withProperty(BlockSlab.HALF, state.getValue(BlockSlab.HALF)), 3);
+ IBlockState grass = GrassSlabsApi.grassFor(state);
+ if (grass != null) world.setBlockState(target, grass, 3);
return;
}
}
@@ -68,10 +69,8 @@ public static boolean growTarget(World world, BlockPos target) {
state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT) {
return world.setBlockState(target, Blocks.GRASS.getDefaultState(), 3);
}
- if (state.getBlock() == ModBlocks.DIRT_SLAB) {
- return world.setBlockState(target, ModBlocks.GRASS_SLAB.getDefaultState()
- .withProperty(BlockSlab.HALF, state.getValue(BlockSlab.HALF)), 3);
- }
+ IBlockState grass = GrassSlabsApi.grassFor(state);
+ if (grass != null) return world.setBlockState(target, grass, 3);
return false;
}
@@ -80,7 +79,7 @@ public static boolean isViableSource(World world, BlockPos pos) {
if (!canRemainGrass(world, pos) || !hasSpreadLight(world, pos)) {
return false;
}
- if (state.getBlock() == Blocks.GRASS || state.getBlock() == ModBlocks.GRASS_SLAB) {
+ if (state.getBlock() == Blocks.GRASS || GrassSlabsApi.isGrassForm(state)) {
return true;
}
return state.getBlock() == ModBlocks.TURF && world.getBlockState(pos.down()).getBlock() == Blocks.DIRT;
@@ -88,7 +87,7 @@ public static boolean isViableSource(World world, BlockPos pos) {
private static boolean targetIsViable(World world, BlockPos target) {
IBlockState state = world.getBlockState(target);
- boolean dirt = state.getBlock() == ModBlocks.DIRT_SLAB ||
+ boolean dirt = GrassSlabsApi.grassFor(state) != null ||
(state.getBlock() == Blocks.DIRT &&
state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT);
if (!dirt) {
@@ -96,7 +95,7 @@ private static boolean targetIsViable(World world, BlockPos target) {
}
BlockPos above = target.up();
IBlockState cover = world.getBlockState(above);
- if (cover.getBlock() == ModBlocks.TURF || cover.getBlock() == ModBlocks.GRASS_SLAB) {
+ if (cover.getBlock() == ModBlocks.TURF || GrassSlabsApi.isGrassForm(cover)) {
return false;
}
return world.getLightFromNeighbors(above) >= 4 &&
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java
index 2785a7f..8663768 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java
@@ -8,6 +8,7 @@
import java.util.Map;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.Loader;
@@ -26,6 +27,8 @@ public final class BuildingBricksCompat {
private static Block grassSlab;
private static Block dirtSlab;
+ private static Block historicalGrassSlab;
+ private static boolean legacyAliasesRegistered;
private static boolean bridgeRecipesRegistered;
private static final Map buildingBricksBlocks =
Collections.synchronizedMap(new IdentityHashMap());
@@ -33,13 +36,14 @@ public final class BuildingBricksCompat {
public static void preInit(File configDirectory) {
boolean installed = Loader.isModLoaded(MOD_ID);
if (!installed) {
+ registerLegacyAliases();
return;
}
SkysGrassSlabs.logger.info("BuildingBricks slab replacement is {}",
SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs()
? "enabled" : "disabled");
if (!BuildingBricksPolicy.shouldArbitrateWorldgen(installed,
- SkysGrassSlabsConfig.generateGrassSlabs())) return;
+ SkysGrassSlabsConfig.isSmoothingActive())) return;
File configFile = new File(configDirectory, "BuildingBricks/general.cfg");
try {
@@ -62,6 +66,8 @@ public static void resolveBlocks() {
? Block.REGISTRY.getObject(GRASS_SLAB_ID) : null;
dirtSlab = Block.REGISTRY.containsKey(DIRT_SLAB_ID)
? Block.REGISTRY.getObject(DIRT_SLAB_ID) : null;
+ historicalGrassSlab = Block.REGISTRY.containsKey(HISTORICAL_GRASS_SLAB_ID)
+ ? Block.REGISTRY.getObject(HISTORICAL_GRASS_SLAB_ID) : null;
buildingBricksBlocks.clear();
for (ResourceLocation id : Block.REGISTRY.getKeys()) {
if (MOD_ID.equals(id.getResourceDomain())) {
@@ -84,6 +90,40 @@ public static boolean isInstalled() {
return Loader.isModLoaded(MOD_ID);
}
+ public static Block historicalGrassSlab() {
+ if (historicalGrassSlab == null) resolveBlocks();
+ return historicalGrassSlab;
+ }
+
+ public static boolean hasLegacyAliases() {
+ return legacyAliasesRegistered;
+ }
+
+ public static Block[] legacyAliases() {
+ return legacyAliasesRegistered ? new Block[] {grassSlab(), dirtSlab(), historicalGrassSlab()}
+ : new Block[0];
+ }
+
+ private static void registerLegacyAliases() {
+ registerLegacyAlias(GRASS_SLAB_ID, true);
+ registerLegacyAlias(DIRT_SLAB_ID, false);
+ registerLegacyAlias(HISTORICAL_GRASS_SLAB_ID, true);
+ legacyAliasesRegistered = true;
+ resolveBlocks();
+ }
+
+ private static void registerLegacyAlias(ResourceLocation id, boolean grass) {
+ LegacySlabAliasBlock block = new LegacySlabAliasBlock(grass);
+ block.setRegistryName(id).setUnlocalizedName(SkysGrassSlabs.MOD_ID + ".legacy_" + id.getResourcePath());
+ ItemBlock item = new ItemBlock(block) {
+ @Override public int getMetadata(int damage) { return damage & 1; }
+ };
+ item.setHasSubtypes(true);
+ item.setRegistryName(id);
+ GameRegistry.register(block);
+ GameRegistry.register(item);
+ }
+
static boolean shouldReplaceSlabs() {
return BuildingBricksPolicy.shouldReplaceSlabs(isInstalled(),
SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs());
@@ -104,8 +144,9 @@ public static Map buildingBricksBlockIdsByNumericId() {
}
public static boolean isGrassSlabItem(ItemStack stack) {
- return stack != null && grassSlab() != null &&
- stack.getItem() == Item.getItemFromBlock(grassSlab());
+ return stack != null && ((grassSlab() != null &&
+ stack.getItem() == Item.getItemFromBlock(grassSlab())) ||
+ (historicalGrassSlab() != null && stack.getItem() == Item.getItemFromBlock(historicalGrassSlab())));
}
static boolean isDirtSlabItem(ItemStack stack) {
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java
index f2bb366..c5c5b3a 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java
@@ -25,6 +25,10 @@
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ClassInheritanceMultiMap;
import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.NibbleArray;
@@ -32,6 +36,7 @@
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
@@ -44,8 +49,9 @@ public final class LegacyMigrationHandler {
private static final String CHUNK_MARKER = "skysgrassslabs";
private static final String CHUNK_MIGRATION_VERSION = "buildingbricks_migration_version";
- private final Map chunkMarkersToSave =
- new HashMap();
+ private final Map chunkMarkersToSave =
+ new HashMap();
+ private final java.util.Queue pendingHandlers = new java.util.concurrent.ConcurrentLinkedQueue();
@SubscribeEvent
public void loadChunk(ChunkDataEvent.Load event) {
@@ -56,7 +62,16 @@ public void loadChunk(ChunkDataEvent.Load event) {
Chunk chunk = event.getChunk();
ChunkKey chunkKey = ChunkKey.of(world, chunk);
if (shouldPreserveChunkMarker(markerVersion)) {
- chunkMarkersToSave.put(chunkKey, markerVersion);
+ chunkMarkersToSave.put(chunkKey, marker.copy());
+ }
+ if (BuildingBricksCompat.hasLegacyAliases()) {
+ boolean changed = migrateBlocks(chunk, event.getData(), null);
+ changed |= migrateChunkInventories(chunk, null);
+ pendingHandlers.add(chunk);
+ if (changed) chunk.setModified(true);
+ return;
+ }
+ if (shouldPreserveChunkMarker(markerVersion)) {
return;
}
if (!shouldMigrateChunk(BuildingBricksCompat.shouldReplaceSlabs(), markerVersion)) return;
@@ -69,49 +84,60 @@ public void loadChunk(ChunkDataEvent.Load event) {
ModWorldState state = ModWorldState.get(world);
migrateBlocks(chunk, event.getData(), state);
migrateChunkInventories(chunk, state);
+ pendingHandlers.add(chunk);
state.recordChunk();
chunk.setModified(true);
- chunkMarkersToSave.put(chunkKey, ModWorldState.MIGRATION_VERSION);
+ NBTTagCompound migratedMarker=marker.copy();
+ migratedMarker.setInteger(CHUNK_MIGRATION_VERSION,ModWorldState.MIGRATION_VERSION);
+ chunkMarkersToSave.put(chunkKey,migratedMarker);
}
@SubscribeEvent
public void saveChunk(ChunkDataEvent.Save event) {
- Integer markerVersion = chunkMarkersToSave.get(
+ NBTTagCompound savedMarker = chunkMarkersToSave.get(
ChunkKey.of(event.getWorld(), event.getChunk()));
- if (markerVersion == null) return;
- NBTTagCompound marker = event.getData().hasKey(CHUNK_MARKER, 10)
- ? event.getData().getCompoundTag(CHUNK_MARKER) : new NBTTagCompound();
- marker.setInteger(CHUNK_MIGRATION_VERSION, markerVersion.intValue());
+ if (savedMarker == null) return;
+ NBTTagCompound marker = savedMarker.copy();
+ if(event.getData().hasKey(CHUNK_MARKER,10))marker.merge(event.getData().getCompoundTag(CHUNK_MARKER));
event.getData().setTag(CHUNK_MARKER, marker);
}
@SubscribeEvent
public void convertPlacedBlock(BlockEvent.PlaceEvent event) {
World world = event.getWorld();
- if (world.isRemote || !BuildingBricksCompat.shouldReplaceSlabs()) {
+ if (world.isRemote || (!BuildingBricksCompat.shouldReplaceSlabs() && !BuildingBricksCompat.hasLegacyAliases())) {
return;
}
IBlockState placed = event.getPlacedBlock();
IBlockState replacement = replacementFor(placed);
if (replacement != null && world.setBlockState(event.getPos(), replacement, 3)) {
- ModWorldState state = ModWorldState.get(world);
+ ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(world);
int metadata = placed.getBlock().getMetaFromState(placed) & 1;
- if (placed.getBlock() == BuildingBricksCompat.grassSlab()) {
+ if (state != null && placed.getBlock() == BuildingBricksCompat.grassSlab()) {
state.recordGrassBlocks(1, metadata);
}
- if (placed.getBlock() == BuildingBricksCompat.dirtSlab()) {
+ if (state != null && placed.getBlock() == BuildingBricksCompat.dirtSlab()) {
state.recordDirtBlocks(1, metadata);
}
}
}
+ @SubscribeEvent
+ public void joinEntity(EntityJoinWorldEvent event) {
+ if (event.getWorld().isRemote || (!BuildingBricksCompat.hasLegacyAliases() &&
+ !BuildingBricksCompat.shouldReplaceSlabs()) || event.getEntity() instanceof EntityPlayer) return;
+ NBTTagCompound serialized = event.getEntity().serializeNBT();
+ ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(event.getWorld());
+ if (migrateStacksInNbt(serialized, state)) event.getEntity().deserializeNBT(serialized);
+ }
+
@SubscribeEvent
public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) {
- if (!(event.player instanceof EntityPlayer) || !BuildingBricksCompat.shouldReplaceSlabs()) {
+ if (!(event.player instanceof EntityPlayer) || (!BuildingBricksCompat.shouldReplaceSlabs() && !BuildingBricksCompat.hasLegacyAliases())) {
return;
}
EntityPlayer player = event.player;
- ModWorldState state = ModWorldState.get(player.getEntityWorld());
+ ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(player.getEntityWorld());
migrateInventory(player.inventory, state);
migrateInventory(player.getInventoryEnderChest(), state);
}
@@ -127,16 +153,11 @@ public void saveWorld(WorldEvent.Save event) {
}
public static void remapMissingMappings(FMLMissingMappingsEvent event) {
- for (FMLMissingMappingsEvent.MissingMapping mapping : event.getAll()) {
- LegacySlabKind kind = legacySlabKind(mapping.resourceLocation);
- if (kind == null) continue;
- boolean grass = kind == LegacySlabKind.GRASS;
- if (mapping.type == GameRegistry.Type.BLOCK) {
- mapping.remap(grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB);
- } else if (mapping.type == GameRegistry.Type.ITEM) {
- mapping.remap(Item.getItemFromBlock(grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB));
- }
- }
+ // Absent-mod holders retain each old registry identity separately.
+ // Remapping several saved IDs onto an already registered Sky object
+ // corrupts Forge 1.10's numeric registry reconciliation in mixed saves.
+ // Installed BuildingBricks owns its own IDs; unknown entries stay visible
+ // in Forge's normal warning rather than being silently substituted.
}
static LegacySlabKind legacySlabKind(ResourceLocation id) {
@@ -155,7 +176,7 @@ static boolean shouldPreserveChunkMarker(int markerVersion) {
return markerVersion >= ModWorldState.MIGRATION_VERSION;
}
- private static void migrateBlocks(Chunk chunk, NBTTagCompound chunkData,
+ private static boolean migrateBlocks(Chunk chunk, NBTTagCompound chunkData,
ModWorldState worldState) {
long grassTop = 0;
long grassBottom = 0;
@@ -168,6 +189,8 @@ private static void migrateBlocks(Chunk chunk, NBTTagCompound chunkData,
Map buildingBricksIds =
BuildingBricksCompat.buildingBricksBlockIdsByNumericId();
int grassId = Block.getIdFromBlock(BuildingBricksCompat.grassSlab());
+ int historicalGrassId = BuildingBricksCompat.historicalGrassSlab() == null ? -1
+ : Block.getIdFromBlock(BuildingBricksCompat.historicalGrassSlab());
int dirtId = Block.getIdFromBlock(BuildingBricksCompat.dirtSlab());
for (int sectionIndex = 0; sectionIndex < serializedSections.tagCount(); ++sectionIndex) {
NBTTagCompound serializedSection = serializedSections.getCompoundTagAt(sectionIndex);
@@ -182,11 +205,11 @@ private static void migrateBlocks(Chunk chunk, NBTTagCompound chunkData,
for (int index = 0; index < blocks.length; ++index) {
int numericId = (blocks[index] & 255) |
(add == null ? 0 : add.getFromIndex(index) << 8);
- if (numericId == grassId || numericId == dirtId) {
+ if (numericId == grassId || numericId == historicalGrassId || numericId == dirtId) {
int slabMetadata = metadata.getFromIndex(index) & 1;
section.set(index & 15, index >> 8 & 15, index >> 4 & 15,
- skyStateFor(numericId == grassId, slabMetadata));
- if (numericId == grassId) {
+ skyStateFor(numericId == grassId || numericId == historicalGrassId, slabMetadata));
+ if (numericId == grassId || numericId == historicalGrassId) {
if (slabMetadata == 0) ++grassTop;
else ++grassBottom;
} else {
@@ -202,23 +225,27 @@ private static void migrateBlocks(Chunk chunk, NBTTagCompound chunkData,
}
}
}
- worldState.recordGrassBlocks(grassTop, 0);
- worldState.recordGrassBlocks(grassBottom, 1);
- worldState.recordDirtBlocks(dirtTop, 0);
- worldState.recordDirtBlocks(dirtBottom, 1);
- for (Map.Entry entry : unsupported.entrySet()) {
- worldState.recordUnsupported("block:" + entry.getKey(), entry.getValue());
+ if (worldState != null) {
+ worldState.recordGrassBlocks(grassTop, 0);
+ worldState.recordGrassBlocks(grassBottom, 1);
+ worldState.recordDirtBlocks(dirtTop, 0);
+ worldState.recordDirtBlocks(dirtBottom, 1);
+ for (Map.Entry entry : unsupported.entrySet()) {
+ worldState.recordUnsupported("block:" + entry.getKey(), entry.getValue());
+ }
}
+ return grassTop + grassBottom + dirtTop + dirtBottom > 0;
}
private static IBlockState replacementFor(IBlockState oldState) {
Block oldBlock = oldState.getBlock();
if (oldBlock != BuildingBricksCompat.grassSlab() &&
+ oldBlock != BuildingBricksCompat.historicalGrassSlab() &&
oldBlock != BuildingBricksCompat.dirtSlab()) {
return null;
}
int metadata = oldBlock.getMetaFromState(oldState) & 1;
- return skyStateFor(oldBlock == BuildingBricksCompat.grassSlab(), metadata);
+ return skyStateFor(oldBlock == BuildingBricksCompat.grassSlab() || oldBlock == BuildingBricksCompat.historicalGrassSlab(), metadata);
}
static IBlockState skyStateFor(boolean grass, int metadata) {
@@ -226,11 +253,13 @@ static IBlockState skyStateFor(boolean grass, int metadata) {
.getStateFromMeta(metadata & 1);
}
- private static void migrateChunkInventories(Chunk chunk, ModWorldState state) {
+ private static boolean migrateChunkInventories(Chunk chunk, ModWorldState state) {
+ boolean changed = false;
for (TileEntity tileEntity : chunk.getTileEntityMap().values()) {
NBTTagCompound serialized = tileEntity.writeToNBT(new NBTTagCompound());
if (migrateStacksInNbt(serialized, state)) {
tileEntity.readFromNBT(serialized);
+ changed = true;
}
}
for (ClassInheritanceMultiMap list : chunk.getEntityLists()) {
@@ -238,6 +267,33 @@ private static void migrateChunkInventories(Chunk chunk, ModWorldState state) {
NBTTagCompound serialized = entity.serializeNBT();
if (migrateStacksInNbt(serialized, state)) {
entity.deserializeNBT(serialized);
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ @SubscribeEvent
+ public void migrateLoadedHandlers(net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent event) {
+ if(event.phase!=net.minecraftforge.fml.common.gameevent.TickEvent.Phase.END)return;
+ for(int count=0;count<8;count++) {
+ Chunk chunk=pendingHandlers.poll();if(chunk==null)break;
+ World world=chunk.getWorld();
+ if(!world.isBlockLoaded(new net.minecraft.util.math.BlockPos(chunk.xPosition<<4,0,chunk.zPosition<<4),false))continue;
+ ModWorldState state=BuildingBricksCompat.hasLegacyAliases()?null:ModWorldState.get(world);
+ for(TileEntity tile:chunk.getTileEntityMap().values()) {
+ // A loot inventory accessor generates loot and can re-enter loading.
+ if(tile.writeToNBT(new NBTTagCompound()).hasKey("LootTable",8))continue;
+ java.util.Set handled=Collections.newSetFromMap(new java.util.IdentityHashMap());
+ for(EnumFacing face:new EnumFacing[]{null,EnumFacing.DOWN,EnumFacing.UP,EnumFacing.NORTH,EnumFacing.SOUTH,EnumFacing.WEST,EnumFacing.EAST}) {
+ if(!tile.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,face))continue;
+ IItemHandler handler=tile.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,face);
+ if(!(handler instanceof IItemHandlerModifiable)||!handled.add(handler))continue;
+ for(int slot=0;slotGrassSlabsApi.registerGrassForm(
+ ModBlocks.DIRT_SLAB.getStateFromMeta(0),ModBlocks.GRASS_SLAB.getStateFromMeta(0)));
+ assertThrows(IllegalArgumentException.class,()->GrassSlabsApi.registerGrassForm(null,ModBlocks.GRASS_SLAB.getDefaultState()));
+ assertThrows(IllegalArgumentException.class,()->GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getDefaultState(),ModBlocks.DIRT_SLAB.getDefaultState()));
+ }
+}
diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java
index 90d5c08..621186c 100644
--- a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java
+++ b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java
@@ -2,6 +2,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.*;
+import net.minecraft.nbt.*;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.state.IBlockState;
@@ -37,4 +39,26 @@ void onlyTheOrientationBitIsAcceptedFromHistoricalMetadata() {
assertEquals(BlockSlab.EnumBlockHalf.BOTTOM,
LegacyMigrationHandler.skyStateFor(false, 3).getValue(BlockSlab.HALF));
}
+
+ @Test void holdersRetainBothOrientationsWithoutPlayableEntries() {
+ for(boolean grass:new boolean[]{false,true}) {
+ LegacySlabAliasBlock holder=new LegacySlabAliasBlock(grass);
+ assertNull(holder.getCreativeTabToDisplayOn());assertFalse(holder.hasTileEntity(holder.getDefaultState()));
+ for(int metadata=0;metadata<16;metadata++)assertEquals(metadata&1,holder.getMetaFromState(holder.getStateFromMeta(metadata)));
+ }
+ }
+
+ @Test void historicalStacksKeepCountsAndUnrelatedNestedData() {
+ if(ModBlocks.GRASS_SLAB.getRegistryName()==null)ModBlocks.GRASS_SLAB.setRegistryName("skysgrassslabs","grass_slab");
+ if(ModBlocks.DIRT_SLAB.getRegistryName()==null)ModBlocks.DIRT_SLAB.setRegistryName("skysgrassslabs","dirt_slab");
+ for(String id:new String[]{"buildingbricks:grass_slab","buildingbricks:dirt_slab","buildingbrickscompatvanilla:grass_slab"}) {
+ NBTTagCompound stack=new NBTTagCompound(),tag=new NBTTagCompound();stack.setString("id",id);stack.setByte("Count",(byte)47);stack.setShort("Damage",(short)13);
+ tag.setString("custom_name","Original");tag.setInteger("RepairCost",9);tag.setString("material","minecraft:dirt");stack.setTag("tag",tag);
+ NBTTagCompound caps=new NBTTagCompound();caps.setString("other_capability","preserved");stack.setTag("ForgeCaps",caps);
+ assertTrue(LegacyMigrationHandler.migrateStacksInNbt(stack,null));
+ assertEquals(id.contains("dirt_slab")?"skysgrassslabs:dirt_slab":"skysgrassslabs:grass_slab",stack.getString("id"));
+ assertEquals(47,stack.getByte("Count"));assertEquals(tag,stack.getCompoundTag("tag"));assertEquals(caps,stack.getCompoundTag("ForgeCaps"));
+ NBTTagCompound converted=stack.copy();assertFalse(LegacyMigrationHandler.migrateStacksInNbt(stack,null));assertEquals(converted,stack);
+ }
+ }
}
From e4b05a5d7e38ea0def2392cfd0e7e7c5714ee23a Mon Sep 17 00:00:00 2001
From: JohnBraham <22159126+SkyBlade1978@users.noreply.github.com>
Date: Tue, 6 Oct 2026 12:47:14 +0100
Subject: [PATCH 2/3] Verify upgrades from the released standalone build
---
.../integrationtest/IntegrationTestMod.java | 5 +-
.../ReleasedUpgradeChecks.java | 60 +++++++++++++++++++
2 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java
diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
index eb6a61e..5261d73 100644
--- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
+++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java
@@ -101,7 +101,10 @@ public void serverStarted(FMLServerStartedEvent event) {
String phase = System.getProperty("skysgrassslabs.integrationPhase", "fresh");
try {
Properties evidence = load(marker);
- if ("fresh".equals(phase)) {
+ if ("upgrade-seed".equals(phase) || "upgrade-reload".equals(phase)) {
+ ReleasedUpgradeChecks.run(world,"upgrade-seed".equals(phase));
+ evidence.setProperty(phase+"_complete","true");
+ } else if ("fresh".equals(phase)) {
int gameplayChecks = verifyGameplay(server, world);
int worldgenChecks = verifyWorldgen(world);
if(BuildingBricksCompat.hasLegacyAliases())AliasRecoveryChecks.fresh(world);
diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java
new file mode 100644
index 0000000..e42b2f2
--- /dev/null
+++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java
@@ -0,0 +1,60 @@
+package zone.moddev.mc.skysgrassslabs.integrationtest;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.item.EntityItem;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntityChest;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.WorldServer;
+import zone.moddev.mc.skysgrassslabs.init.ModBlocks;
+import zone.moddev.mc.skysgrassslabs.world.ModWorldState;
+
+/** Runs against the released jar first, then checks the same save with its successor. */
+final class ReleasedUpgradeChecks {
+ private ReleasedUpgradeChecks() { }
+ static void run(WorldServer world,boolean seed) {
+ world.getGameRules().setOrCreateGameRule("randomTickSpeed","0");
+ Block[] blocks={ModBlocks.DIRT_SLAB,ModBlocks.GRASS_SLAB,ModBlocks.PATH_SLAB,ModBlocks.TURF};
+ int number=0;
+ for(Block block:blocks)for(int metadata=0;metadata<(block==ModBlocks.TURF?1:2);metadata++) {
+ BlockPos pos=new BlockPos(160+number++,100,160);
+ IBlockState expected=block.getStateFromMeta(metadata);
+ if(seed){world.setBlockState(pos.down(),Blocks.DIRT.getDefaultState(),2);world.setBlockToAir(pos.up());world.setBlockState(pos,expected,2);}
+ else require(world.getBlockState(pos).equals(expected),"Released block orientation: "+block.getRegistryName()+" / "+metadata);
+ }
+ BlockPos chestPos=new BlockPos(160,101,164);
+ if(seed)world.setBlockState(chestPos,Blocks.CHEST.getDefaultState(),2);
+ TileEntityChest chest=(TileEntityChest)world.getTileEntity(chestPos);
+ require(chest!=null,"Released container survived");
+ for(int slot=0;slot section:world.getChunkFromBlockCoords(chestPos).getEntityLists())for(Entity entity:section)
+ if(entity instanceof EntityItem) {
+ ItemStack stack=((EntityItem)entity).getEntityItem();
+ if(stack.getTagCompound()!=null&&stack.getTagCompound().hasKey("upgrade_entity")) {
+ require(stack.getItem()==net.minecraft.item.Item.getItemFromBlock(ModBlocks.GRASS_SLAB)&&stack.stackSize==7&&stack.getMetadata()==1&&stack.getTagCompound().getString("upgrade_entity").equals("Retained"),"Released dropped item and NBT");found=true;
+ }
+ }
+ require(found,"Released entity survived");
+ ModWorldState state=ModWorldState.get(world);
+ require(state.migratedChunks()==1&&state.migratedGrassBlocks()==13&&state.migratedGrassBlocksTop()==13&&state.migratedDirtBlocks()==17&&state.migratedDirtBlocksBottom()==17&&state.migratedGrassItems()==19&&state.migratedDirtItems()==23,"Released schema-1 counters survived unchanged");
+ }
+ System.out.println("GRASS_SLABS_RELEASED_UPGRADE_PASS seed="+seed+" states="+number);
+ }
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+}
From 8a66979bb7d047bb2a3e4be525b38451a3a762d7 Mon Sep 17 00:00:00 2001
From: JohnBraham <22159126+SkyBlade1978@users.noreply.github.com>
Date: Thu, 8 Oct 2026 17:27:53 +0100
Subject: [PATCH 3/3] Complete shared grass rules and prepare the 1.10 release
candidate
---
.github/workflows/ci.yml | 5 ++-
CHANGELOG.md | 2 +
docs/GAMEPLAY.md | 1 +
docs/INTEGRATION.md | 13 +++++-
.../mc/skysgrassslabs/api/GrassSlabsApi.java | 42 ++++++++++++++++---
.../skysgrassslabs/block/GrassSlabBlock.java | 4 +-
.../mc/skysgrassslabs/block/GrassSpread.java | 14 ++++---
.../mc/skysgrassslabs/init/ModRecipes.java | 4 ++
8 files changed, 70 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ec6febb..dba2530 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,6 +38,9 @@ jobs:
distribution: microsoft
java-version: '17'
+ - name: Pin production Java
+ run: echo "SGS_JAVA8_HOME=$JAVA_HOME_8_X64" >> "$GITHUB_ENV"
+
- name: Set up Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6
@@ -46,7 +49,7 @@ jobs:
- name: Build, test, and audit release artifacts
run: >-
- ./gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums
+ ./gradlew clean check integrationTest build javadoc verifyReleaseArtifacts writeReleaseChecksums
verifyEclipseProductionClasspath --no-daemon --stacktrace
- name: Upload audited release candidate
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8521f72..9dfb00b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
ownership for Terrain Smoother. Standalone grass smoothing is retained.
- Fixed legacy slab recovery in worlds containing both old and Sky slabs.
- Preserved slab orientation, saved item data and earlier migration history.
+- Added slab recombination recipes and optional native soil rules for terrain
+ add-ons.
## 1.0.1.110021 - Minecraft 1.10.2
diff --git a/docs/GAMEPLAY.md b/docs/GAMEPLAY.md
index 8bd1768..dabee25 100644
--- a/docs/GAMEPLAY.md
+++ b/docs/GAMEPLAY.md
@@ -40,6 +40,7 @@ enabled is removed without dropping an item.
- Three dirt blocks in a horizontal row make six dirt slabs.
- Three grass blocks in a horizontal row make six grass slabs.
+- Two matching dirt or grass slabs side by side make the matching full block.
- A dirt block and a recognised seed make a grass block.
- A dirt slab and a recognised seed make a grass slab.
- A grass block or grass slab together with a compatible shovel makes one
diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md
index 621f750..aa5b619 100644
--- a/docs/INTEGRATION.md
+++ b/docs/INTEGRATION.md
@@ -1,7 +1,7 @@
# Optional content integration
Grass Slabs remains standalone. Terrain Smoother 0.1 uses Grass Slabs 1.1's
-version-one API in `zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi`.
+version 1 API in `zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi`.
A terrain owner calls `claimSmoothing(modid, enabled)` before Grass Slabs
pre-initialization. Installed ownership suppresses the separate grass pass,
@@ -14,3 +14,14 @@ conflicts and closes at load completion. Never register coarse dirt or podzol.
`tickGrass`, `tickDirt` and `repairSupport` retain matching state orientation,
grass lighting rules and covered-target rejection. Integration must be optional
when the other content mod is not installed.
+
+`registerGrassVariant(dirt, grass, spreads)` supports additional grass skins
+sharing a dirt state without replacing its default regrowth. Set `spreads` to
+false for native surfaces that do not spread. `registerGrassSupport` supplies
+native full grass/dirt pairs for support repair.
+
+An add-on can register a source-aware `registerSpreadRule(modid, rule)` to retain
+its native grass family. The callback receives the source grass and target dirt
+states and returns a registered matching grass state, or null to use the default
+pair. It must not load chunks or write to the world. All registration closes at
+load completion. Without an add-on these rules leave vanilla behaviour unchanged.
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
index 4e0425d..0498b17 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java
@@ -15,12 +15,15 @@ public final class GrassSlabsApi {
public static final int VERSION = 1;
private static final Map GRASS = new LinkedHashMap<>();
private static final Map DIRT = new LinkedHashMap<>();
+ private static final java.util.Set NON_SPREADING = new java.util.HashSet<>();
+ private static final Map SUPPORTS = new LinkedHashMap<>();
+ private static final Map> SPREAD_RULES = new LinkedHashMap<>();
private static String smoothingOwner;
private static boolean ownerEnabled, configurationLoaded, frozen;
private GrassSlabsApi() { }
- /** Claim before Grass Slabs pre-initializes; an installed disabled owner still owns the pass. */
+ /** Claim during startup. The owner suppresses our pass even when its own generation is off. */
public static void claimSmoothing(String owner, boolean enabled) {
if (configurationLoaded || owner == null || owner.isEmpty() || smoothingOwner != null)
throw new IllegalStateException("Smoothing ownership must be claimed once before configuration loads");
@@ -45,7 +48,35 @@ public static void registerGrassForm(IBlockState dirt, IBlockState grass) {
}
public static boolean isGrassForm(IBlockState state) { return GRASS.containsKey(canonical(state)); }
+ /** Additional grass skins can share a dirt form without changing its default regrowth. */
+ public static void registerGrassVariant(IBlockState dirt, IBlockState grass, boolean spreads) {
+ if(frozen)throw new IllegalStateException("Grass form registration is closed");
+ if(dirt==null||grass==null||dirt.equals(grass)||dirt.getBlock().hasTileEntity(dirt)||grass.getBlock().hasTileEntity(grass))
+ throw new IllegalArgumentException("Grass variants need distinct non-tile states");
+ dirt=canonical(dirt);grass=canonical(grass);
+ if(GRASS.containsKey(grass))throw new IllegalArgumentException("Conflicting grass variant mapping");
+ GRASS.put(grass,dirt);if(!spreads)NON_SPREADING.add(grass);
+ }
+ public static boolean isSpreadingGrassForm(IBlockState state) { return isGrassForm(state)&&!NON_SPREADING.contains(canonical(state)); }
public static IBlockState grassFor(IBlockState dirt) { return DIRT.get(canonical(dirt)); }
+ /** Choose grass to match the source; return null to use the default dirt/grass pair. */
+ public static IBlockState grassFor(IBlockState dirt,IBlockState source) {
+ for(java.util.function.BiFunction rule:SPREAD_RULES.values()) {
+ IBlockState result=rule.apply(canonical(source),canonical(dirt));if(result!=null)return result;
+ }
+ return grassFor(dirt);
+ }
+ public static void registerSpreadRule(String owner,java.util.function.BiFunction rule) {
+ if(frozen)throw new IllegalStateException("Grass form registration is closed");
+ if(owner==null||owner.isEmpty()||rule==null||SPREAD_RULES.containsKey(owner))throw new IllegalArgumentException("Invalid or conflicting grass spread rule");
+ SPREAD_RULES.put(owner,rule);
+ }
+ public static void registerGrassSupport(IBlockState grass,IBlockState dirt) {
+ if(frozen)throw new IllegalStateException("Grass form registration is closed");
+ if(grass==null||dirt==null||grass.equals(dirt)||grass.getBlock().hasTileEntity(grass)||dirt.getBlock().hasTileEntity(dirt)||SUPPORTS.containsKey(canonical(grass)))
+ throw new IllegalArgumentException("Invalid or conflicting grass support");
+ SUPPORTS.put(canonical(grass),canonical(dirt));
+ }
public static IBlockState dirtFor(IBlockState grass) { return GRASS.get(canonical(grass)); }
public static void configurationLoaded() { configurationLoaded = true; }
public static void freeze() { frozen = true; }
@@ -60,13 +91,14 @@ public static void tickGrass(World world, BlockPos pos, IBlockState state, Rando
if (!GrassSpread.canRemainGrass(world, pos)) {
IBlockState dirt = dirtFor(state);
if (dirt != null) world.setBlockState(pos, dirt, 3);
- } else GrassSpread.spreadFrom(world, pos, random, pos.down());
+ } else if(isSpreadingGrassForm(state)) GrassSpread.spreadFrom(world, pos, random, pos.down());
}
public static void repairSupport(World world, BlockPos pos) {
- if (!world.isRemote && world.isBlockLoaded(pos.down()) &&
- world.getBlockState(pos.down()).getBlock() == Blocks.GRASS)
- world.setBlockState(pos.down(), Blocks.DIRT.getDefaultState(), 2);
+ if(world.isRemote||!world.isBlockLoaded(pos.down()))return;
+ IBlockState lower=world.getBlockState(pos.down());
+ IBlockState dirt=lower.getBlock()==Blocks.GRASS?Blocks.DIRT.getDefaultState():SUPPORTS.get(canonical(lower));
+ if(dirt!=null)world.setBlockState(pos.down(),dirt,2);
}
private static IBlockState canonical(IBlockState state) {
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java
index d49e48c..7fef8ee 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java
@@ -165,8 +165,6 @@ public BlockRenderLayer getBlockLayer() {
}
private static void dirtifyGrassSupport(World world, BlockPos pos) {
- if (!world.isRemote && world.getBlockState(pos.down()).getBlock() == Blocks.GRASS) {
- world.setBlockState(pos.down(), Blocks.DIRT.getDefaultState(), 2);
- }
+ zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi.repairSupport(world,pos);
}
}
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
index 5484ab4..eb6e0a3 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java
@@ -38,7 +38,7 @@ public static void spreadFrom(World world, BlockPos source, Random random, Block
if (target.getY() < 0 || target.getY() >= 256 || !world.isBlockLoaded(target)) {
return;
}
- growTarget(world, target);
+ growTarget(world, target, world.getBlockState(source));
}
}
@@ -53,7 +53,7 @@ public static void tickDirtSlab(World world, BlockPos target, IBlockState state,
return;
}
if (isViableSource(world, source)) {
- IBlockState grass = GrassSlabsApi.grassFor(state);
+ IBlockState grass = GrassSlabsApi.grassFor(state,world.getBlockState(source));
if (grass != null) world.setBlockState(target, grass, 3);
return;
}
@@ -61,15 +61,19 @@ public static void tickDirtSlab(World world, BlockPos target, IBlockState state,
}
public static boolean growTarget(World world, BlockPos target) {
+ return growTarget(world,target,Blocks.GRASS.getDefaultState());
+ }
+ private static boolean growTarget(World world, BlockPos target,IBlockState source) {
if (!targetIsViable(world, target)) {
return false;
}
IBlockState state = world.getBlockState(target);
if (state.getBlock() == Blocks.DIRT &&
state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT) {
- return world.setBlockState(target, Blocks.GRASS.getDefaultState(), 3);
+ IBlockState mapped=GrassSlabsApi.grassFor(state,source);
+ return world.setBlockState(target,mapped==null?Blocks.GRASS.getDefaultState():mapped,3);
}
- IBlockState grass = GrassSlabsApi.grassFor(state);
+ IBlockState grass = GrassSlabsApi.grassFor(state,source);
if (grass != null) return world.setBlockState(target, grass, 3);
return false;
}
@@ -79,7 +83,7 @@ public static boolean isViableSource(World world, BlockPos pos) {
if (!canRemainGrass(world, pos) || !hasSpreadLight(world, pos)) {
return false;
}
- if (state.getBlock() == Blocks.GRASS || GrassSlabsApi.isGrassForm(state)) {
+ if (state.getBlock() == Blocks.GRASS || GrassSlabsApi.isSpreadingGrassForm(state)) {
return true;
}
return state.getBlock() == ModBlocks.TURF && world.getBlockState(pos.down()).getBlock() == Blocks.DIRT;
diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java
index ad8d361..9a0b441 100644
--- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java
+++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java
@@ -20,6 +20,10 @@ public static void register() {
"DDD", 'D', Blocks.DIRT);
GameRegistry.addShapedRecipe(new ItemStack(ModBlocks.GRASS_SLAB, 6),
"GGG", 'G', Blocks.GRASS);
+ GameRegistry.addShapedRecipe(new ItemStack(Blocks.DIRT), "SS", 'S',
+ new ItemStack(ModBlocks.DIRT_SLAB, 1, OreDictionary.WILDCARD_VALUE));
+ GameRegistry.addShapedRecipe(new ItemStack(Blocks.GRASS), "SS", 'S',
+ new ItemStack(ModBlocks.GRASS_SLAB, 1, OreDictionary.WILDCARD_VALUE));
GameRegistry.addRecipe(new ShapelessOreRecipe(new ItemStack(Blocks.GRASS),
new ItemStack(Blocks.DIRT), SEED_ORE));
GameRegistry.addRecipe(new ShapelessOreRecipe(new ItemStack(ModBlocks.GRASS_SLAB),