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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -56,9 +59,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

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# 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.
- Added slab recombination recipes and optional native soil rules for terrain
add-ons.

## 1.0.1.110021 - Minecraft 1.10.2

- Corrected the side texture alignment on top and bottom path slabs so it
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand All @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/GAMEPLAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions docs/INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Optional content integration

Grass Slabs remains standalone. Terrain Smoother 0.1 uses Grass Slabs 1.1's
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,
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.

`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.
11 changes: 11 additions & 0 deletions docs/RELEASE-1.0.2.110021.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions docs/RELEASE-1.1.0.110021.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions docs/WORLD-UPGRADES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -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);}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,13 @@ 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);
evidence.setProperty("gameplay_checks", Integer.toString(gameplayChecks));
evidence.setProperty("worldgen_checks", Integer.toString(worldgenChecks));
if (Loader.isModLoaded(BuildingBricksCompat.MOD_ID)) {
Expand All @@ -122,6 +126,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");
Expand Down
Loading
Loading