diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caa3223..2354f9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,9 +111,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/SkysGrassSlabs-1.1.2.2603002.jar - build/libs/SkysGrassSlabs-1.1.2.2603002-sources.jar - build/libs/SkysGrassSlabs-1.1.2.2603002-javadoc.jar + build/libs/SkysGrassSlabs-1.1.3.2603002.jar + build/libs/SkysGrassSlabs-1.1.3.2603002-sources.jar + build/libs/SkysGrassSlabs-1.1.3.2603002-javadoc.jar build/release/SHA256SUMS CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 156d3cd..d642568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ # Changelog +## 1.1.3.2603002 - Minecraft 26.3 (NeoForge) + +- Fixed crashes when starting the mod or loading a world with newer NeoForge + 26.3 builds. Earlier 26.3 builds still work. +- You can keep your existing worlds and `skysgrassslabs-common.toml`. Blocks, + recipes and terrain smoothing are unchanged. + ## 1.1.2.2603002 - Minecraft 26.3 (NeoForge) -- Ported the accepted NeoForge release to Minecraft 26.3. +- Added the Minecraft 26.3 NeoForge build. - Preserved blocks, recipes, configuration, world data and legacy upgrades. ## 1.1.2.2602002 - Minecraft 26.2 (NeoForge) @@ -13,7 +20,7 @@ ## 1.1.1.2602002 - Minecraft 26.2 (NeoForge) - Added the NeoForge 26.2 build with the same blocks, recipes, world - smoothing and upgrade support as the accepted 26.1.2 build. + smoothing and upgrade support as the 26.1.2 build. - Updated grass light checks, carpet references and recipe-book displays for Minecraft 26.2. - Retained translated names for all four items in 18 language variants. @@ -81,7 +88,7 @@ ## 1.0.1.118021 - Minecraft 1.18.2 -- Brought the accepted dirt, grass and dirt path slabs, turf and terrain +- Brought dirt, grass and dirt path slabs, turf and terrain smoothing forward to Minecraft 1.18.2. - Kept both slab orientations, waterlogging, snowy caps, path alignment, grass spreading, turf recipes and sheep interaction. diff --git a/README.md b/README.md index a7bc2b3..e203b9c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ # Sky's Grass Slabs -Sky's Grass Slabs adds dirt, grass and lowered dirt 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. +Sky's Grass Slabs adds dirt, grass and lowered dirt path slabs, plus a thin +layer of turf. Grass spreads between blocks, slabs and turf, and grass slabs +soften one block steps in newly generated Overworld terrain. -This branch is for Minecraft 26.3. Version `1.1.2.2603002` requires NeoForge -26.3.0.1-beta and Java 25. +This branch is for Minecraft 26.3. Version `1.1.3.2603002` requires NeoForge +26.3.0.1-beta or a compatible newer 26.3 build, and Java 25. ## Installation @@ -14,27 +13,35 @@ Place the mod jar in the `mods` folder. It must be installed on both the client and server for multiplayer. Back up an existing world before adding, removing or updating world changing mods. -## Highlights - -- Place dirt, grass and dirt path slabs in either half of a block. -- Combine two matching slabs into the corresponding vanilla block. -- Let grass spread between full blocks, slabs and turf. -- Use a shovel on dirt or grass slabs to create lowered dirt path slabs. -- Cut turf from grass without consuming the shovel. -- Let sheep eat turf and regrow their wool. -- Add grass slabs to suitable slopes in newly generated Overworld chunks. -- Display biome colours and snowy edges that match the surrounding terrain. -- Use standard Minecraft waterlogging behaviour on slabs. -- Use translated names in 18 supported language variants. -- Recover selected grass slabs and grass carpet when replacing a compatible - older mod. +## Using the mod + +- Place slabs in either half of a block. Two matching slabs combine into the + corresponding vanilla block. +- Use a shovel on dirt or grass slabs to make a dirt path slab. +- Craft grass and a shovel into turf. The shovel is returned unchanged. +- Let sheep eat turf to regrow their wool. + +Grass follows the surrounding biome colour, and slabs show snow when it is +nearby. Slabs can also be waterlogged. Block names are translated into 18 +language variants. + +World smoothing affects new chunks only and can be turned off in the config. +The mod can also recover supported slabs and grass carpet from older mods; +read the upgrade guide before removing one from an existing world. + +## This update + +Version `1.1.3.2603002` fixes crashes when starting the mod or loading a world +with newer NeoForge 26.3 builds. Earlier 26.3 builds still work. You can keep +your existing worlds and `skysgrassslabs-common.toml`; no settings need to be +changed. ## Guides - [Gameplay and recipes](docs/GAMEPLAY.md) - [Configuration](docs/CONFIGURATION.md) - [Upgrading an older world](docs/WORLD-UPGRADES.md) -- [Release notes](docs/RELEASE-1.1.2.2603002.md) +- [Release notes](docs/RELEASE-1.1.3.2603002.md) ## License diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 479f27b..e0f2fae 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -5,6 +5,10 @@ first time. Stop the game or server before editing it, then restart to apply changes. On a dedicated server, the server's configuration controls world generation and world upgrades. +Newer NeoForge 26.3 builds call this configuration scope `LOCAL` rather than +`COMMON`. The file remains `skysgrassslabs-common.toml`; existing settings do +not need to be moved or recreated. + When upgrading from an earlier release, the mod copies valid settings from `skysgrassslabs.cfg` if the TOML file does not already exist. The old file is kept. An existing TOML file always takes priority. @@ -46,9 +50,10 @@ the other legacy replacement option. ## Related game rules -Turf and grass behaviour use Minecraft random ticks. Setting `randomTickSpeed` -to zero pauses spreading, grass decay and delayed turf support checks. +Turf and grass behaviour use Minecraft random ticks. Setting +`minecraft:random_tick_speed` to zero pauses spreading, grass decay and delayed +turf support checks. -Sheep respect the `mobGriefing` game rule. With mob griefing enabled they eat +Sheep respect the `minecraft:mob_griefing` game rule. With mob griefing enabled they eat and remove turf without a drop. With it disabled they can still perform their normal eating behaviour and regrow wool, but the turf remains. diff --git a/docs/GAMEPLAY.md b/docs/GAMEPLAY.md index 1b4b240..d2f3cee 100644 --- a/docs/GAMEPLAY.md +++ b/docs/GAMEPLAY.md @@ -37,7 +37,7 @@ Turf spreads grass to nearby dirt and dirt slabs. Placing turf directly on a dirt slab consumes the turf and changes the slab into a grass slab without changing whether it is in the top or bottom half. -Sheep can eat turf and regrow their wool. Turf eaten while `mobGriefing` is +Sheep can eat turf and regrow their wool. Turf eaten while `minecraft:mob_griefing` is enabled is removed without dropping an item. ## Crafting diff --git a/docs/RELEASE-1.1.3.2603002.md b/docs/RELEASE-1.1.3.2603002.md new file mode 100644 index 0000000..c0d9986 --- /dev/null +++ b/docs/RELEASE-1.1.3.2603002.md @@ -0,0 +1,11 @@ +# Sky's Grass Slabs 1.1.3 for Minecraft 26.3 + +This update fixes crashes when starting the mod or loading a world with +newer NeoForge 26.3 builds. Earlier 26.3 builds still work. + +You can keep your existing worlds and `skysgrassslabs-common.toml`. No +settings need to be changed, and blocks, recipes and terrain smoothing work +as before. + +Requires Minecraft 26.3, NeoForge 26.3.0.1-beta or a compatible newer 26.3 +build, and Java 25. diff --git a/docs/WORLD-UPGRADES.md b/docs/WORLD-UPGRADES.md index 374e559..e464ab8 100644 --- a/docs/WORLD-UPGRADES.md +++ b/docs/WORLD-UPGRADES.md @@ -6,7 +6,7 @@ world has been checked in game. ## Sky's Grass Slabs content -Install `SkysGrassSlabs-1.1.2.2603002.jar` before opening a world that used an +Install `SkysGrassSlabs-1.1.3.2603002.jar` before opening a world that used an earlier release. The permanent dirt slab, grass slab, path slab and turf IDs are unchanged. diff --git a/gradle.properties b/gradle.properties index 476b8f5..83e162d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -43,7 +43,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.1.2.2603002 +mod_version=1.1.3.2603002 # 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 diff --git a/src/gameTest/java/zone/moddev/mc/skysgrassslabs/gametest/WorldgenGameTests.java b/src/gameTest/java/zone/moddev/mc/skysgrassslabs/gametest/WorldgenGameTests.java index 46e97ea..9f6b422 100644 --- a/src/gameTest/java/zone/moddev/mc/skysgrassslabs/gametest/WorldgenGameTests.java +++ b/src/gameTest/java/zone/moddev/mc/skysgrassslabs/gametest/WorldgenGameTests.java @@ -12,6 +12,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.common.world.BiomeModifier.Phase; +import net.neoforged.neoforge.common.world.ModifiableBiomeInfo.BiomeInfo; import zone.moddev.mc.skysgrassslabs.SkysGrassSlabs; import zone.moddev.mc.skysgrassslabs.init.ModBlocks; import zone.moddev.mc.skysgrassslabs.world.GrassSlabSmoothingFeature; @@ -40,6 +42,46 @@ public static void smoothingIsFirstVegetationFeature(GameTestHelper helper) { helper.succeed(); } + public static void bothModifierSignaturesPreservePhaseOrderAndDeduplication(GameTestHelper helper) { + var biome = helper.getLevel().getBiome(helper.absolutePos(BlockPos.ZERO)); + var value = biome.value(); + var original = new BiomeInfo(value.getModifiedClimateSettings(), value.getAttributes(), + value.getModifiedSpecialEffects(), value.getGenerationSettings()); + var key = biome.unwrapKey().orElseThrow(); + var legacy = BiomeInfo.Builder.copyOf(key, original); + var current = BiomeInfo.Builder.copyOf(key, original); + var step = GenerationStep.Decoration.VEGETAL_DECORATION; + for (var builder : java.util.List.of(legacy, current)) { + builder.getGenerationSettings().getFeatures(step).removeIf(feature -> + feature.value().feature().value() instanceof GrassSlabSmoothingFeature); + } + var before = java.util.List.copyOf(current.getGenerationSettings().getFeatures(step)); + var modifier = zone.moddev.mc.skysgrassslabs.world.SmoothingBiomeModifier.INSTANCE; + for (Phase phase : Phase.values()) { + if (phase == Phase.AFTER_EVERYTHING) { + continue; + } + modifier.modify(biome, phase, legacy); + modifier.modify(helper.getLevel().registryAccess(), biome, phase, current); + require(helper, legacy.getGenerationSettings().getFeatures(step).equals(before) + && current.getGenerationSettings().getFeatures(step).equals(before), + "a nonfinal phase changed vegetation features"); + } + modifier.modify(biome, Phase.AFTER_EVERYTHING, legacy); + modifier.modify(helper.getLevel().registryAccess(), biome, Phase.AFTER_EVERYTHING, current); + var expected = java.util.List.copyOf(legacy.getGenerationSettings().getFeatures(step)); + require(helper, current.getGenerationSettings().getFeatures(step).equals(expected), + "the two loader entry points changed feature order"); + require(helper, expected.size() == before.size() + 1 + && expected.getFirst().value().feature().value() instanceof GrassSlabSmoothingFeature, + "smoothing was not inserted exactly once at index zero"); + modifier.modify(helper.getLevel().registryAccess(), biome, Phase.AFTER_EVERYTHING, current); + modifier.modify(biome, Phase.AFTER_EVERYTHING, current); + require(helper, current.getGenerationSettings().getFeatures(step).equals(expected), + "repeat calls duplicated the smoothing feature"); + helper.succeed(); + } + public static void smoothingIsBorderSafeAndIdempotent(GameTestHelper helper) { ChunkPos owner = ChunkPos.containing(helper.absolutePos(new BlockPos(1, 2, 1))); int y = 120; diff --git a/src/gameTest/resources/data/skysgrassslabs/test_instance/worldgen/both_modifier_signatures_preserve_phase_order_and_deduplication.json b/src/gameTest/resources/data/skysgrassslabs/test_instance/worldgen/both_modifier_signatures_preserve_phase_order_and_deduplication.json new file mode 100644 index 0000000..173c3dc --- /dev/null +++ b/src/gameTest/resources/data/skysgrassslabs/test_instance/worldgen/both_modifier_signatures_preserve_phase_order_and_deduplication.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "skysgrassslabs:worldgen/both_modifier_signatures_preserve_phase_order_and_deduplication", + "max_ticks": 100, + "structure": "minecraft:empty" +} diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java index 7fa1558..0491ef8 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java @@ -27,7 +27,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.1.2.2603002"; + public static final String VERSION = "1.1.3.2603002"; public static final Logger LOGGER = LogManager.getLogger(); /** Registers content, configuration, world generation, and persistent state. */ diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigType.java b/src/main/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigType.java new file mode 100644 index 0000000..410bd5e --- /dev/null +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigType.java @@ -0,0 +1,18 @@ +package zone.moddev.mc.skysgrassslabs.config; + +import net.neoforged.fml.config.ModConfig; + +/** Keeps the same local configuration scope across the NeoForge 26.3 rename. */ +final class NeoForgeConfigType { + private NeoForgeConfigType() { + } + + static ModConfig.Type local() { + // Resolve names at runtime so one jar supports both loader versions. + try { + return ModConfig.Type.valueOf("LOCAL"); + } catch (IllegalArgumentException missingLocalName) { + return ModConfig.Type.valueOf("COMMON"); + } + } +} diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java b/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java index 5525e29..93801c4 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java @@ -7,7 +7,6 @@ import java.util.List; import org.apache.commons.lang3.tuple.Pair; import net.neoforged.neoforge.common.ModConfigSpec; -import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.loading.FMLPaths; import net.neoforged.fml.ModContainer; import zone.moddev.mc.skysgrassslabs.SkysGrassSlabs; @@ -32,7 +31,7 @@ public final class SkysGrassSlabsConfig { } public static void register(ModContainer container) { - container.registerConfig(ModConfig.Type.COMMON, SPEC, FILE_NAME); + container.registerConfig(NeoForgeConfigType.local(), SPEC, FILE_NAME); } public static void migrateLegacyConfig() { diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingBiomeModifier.java b/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingBiomeModifier.java index 1b99a67..df1c54e 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingBiomeModifier.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingBiomeModifier.java @@ -3,6 +3,7 @@ import com.mojang.serialization.MapCodec; import java.util.List; import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; import net.minecraft.tags.BiomeTags; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.levelgen.GenerationStep; @@ -34,6 +35,12 @@ public void modify(Holder biome, Phase phase, BiomeInfo.Builder builder) } } + /** Newer NeoForge builds provide registry access alongside the biome. */ + public void modify(RegistryAccess registries, Holder biome, + Phase phase, BiomeInfo.Builder builder) { + modify(biome, phase, builder); + } + @Override public MapCodec codec() { return CODEC; diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java index 14c56fe..6ecb066 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java @@ -35,14 +35,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.1.2.2603002.md"))); + assertTrue(Files.isRegularFile(Path.of("docs/RELEASE-1.1.3.2603002.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.1.2.2603002")); + assertTrue(properties.contains("mod_version=1.1.3.2603002")); 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) @@ -117,7 +117,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.1.2.2603002\"")); + assertTrue(main.contains("VERSION = \"1.1.3.2603002\"")); assertTrue(state.contains("skysgrassslabs_world_state")); assertTrue(state.contains("SCHEMA_VERSION = 1")); assertTrue(state.contains("schema_version")); @@ -302,9 +302,9 @@ public void continuousIntegrationTargetsTheStableBranchAndArtifacts() throws Exc } String ci = Files.readString(Path.of(".github/workflows/ci.yml"), StandardCharsets.UTF_8); - assertTrue(ci.contains("SkysGrassSlabs-1.1.2.2603002.jar")); - assertTrue(ci.contains("SkysGrassSlabs-1.1.2.2603002-sources.jar")); - assertTrue(ci.contains("SkysGrassSlabs-1.1.2.2603002-javadoc.jar")); + assertTrue(ci.contains("SkysGrassSlabs-1.1.3.2603002.jar")); + assertTrue(ci.contains("SkysGrassSlabs-1.1.3.2603002-sources.jar")); + assertTrue(ci.contains("SkysGrassSlabs-1.1.3.2603002-javadoc.jar")); assertTrue(ci.contains("if-no-files-found: error")); assertTrue(ci.contains("java-version: '25.0.3+9.0.LTS'")); assertTrue(ci.contains("--offline --no-daemon")); diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigTypeTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigTypeTest.java new file mode 100644 index 0000000..24e1894 --- /dev/null +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/config/NeoForgeConfigTypeTest.java @@ -0,0 +1,95 @@ +package zone.moddev.mc.skysgrassslabs.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.tools.ToolProvider; +import net.neoforged.fml.config.ModConfig; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class NeoForgeConfigTypeTest { + private static final String HELPER = + "zone.moddev.mc.skysgrassslabs.config.NeoForgeConfigType"; + + @Rule + public final TemporaryFolder temporary = new TemporaryFolder(); + + @Test + public void actualLoaderUsesItsUnsyncedConfigType() throws Exception { + String expected = java.util.Arrays.stream(ModConfig.Type.values()) + .anyMatch(type -> type.name().equals("LOCAL")) ? "LOCAL" : "COMMON"; + Method local = Class.forName(HELPER).getDeclaredMethod("local"); + local.setAccessible(true); + assertEquals(ModConfig.Type.valueOf(expected), local.invoke(null)); + } + + @Test + public void olderLoaderKeepsCommonScope() throws Exception { + assertEquals("COMMON", resolveWith("COMMON, CLIENT, SERVER, STARTUP")); + } + + @Test + public void newerLoaderUsesLocalScope() throws Exception { + assertEquals("LOCAL", resolveWith("LOCAL, CLIENT, SYNCED, STARTUP")); + } + + @Test + public void localWinsWhenBothNamesExist() throws Exception { + assertEquals("LOCAL", resolveWith("COMMON, LOCAL, CLIENT, SYNCED, STARTUP")); + } + + @Test + public void missingUnsyncedScopeFailsInsteadOfUsingClientOrSynced() throws Exception { + try { + resolveWith("CLIENT, SYNCED, STARTUP"); + fail("A loader without either local scope must fail clearly"); + } catch (InvocationTargetException exception) { + assertTrue(exception.getCause() instanceof IllegalArgumentException); + assertTrue(exception.getCause().getMessage().contains("COMMON")); + } + } + + @Test + public void registrationRetainsTheExistingConfigFile() throws Exception { + String source = Files.readString(Path.of( + "src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java")); + assertEquals("skysgrassslabs-common.toml", SkysGrassSlabsConfig.FILE_NAME); + assertTrue(source.contains("registerConfig(NeoForgeConfigType.local(), SPEC, FILE_NAME)")); + } + + private String resolveWith(String constants) throws Exception { + Path directory = temporary.newFolder().toPath(); + Path source = directory.resolve("net/neoforged/fml/config/ModConfig.java"); + Files.createDirectories(source.getParent()); + Files.writeString(source, "package net.neoforged.fml.config; public final class ModConfig {" + + " public enum Type { " + constants + " } }"); + var compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull("The enum compatibility test requires a JDK", compiler); + assertEquals(0, compiler.run(null, null, null, "-proc:none", "-d", + directory.toString(), source.toString())); + Path helper = directory.resolve(HELPER.replace('.', '/') + ".class"); + Files.createDirectories(helper.getParent()); + try (InputStream bytes = getClass().getClassLoader().getResourceAsStream( + HELPER.replace('.', '/') + ".class")) { + assertNotNull("The shipped config helper must be present", bytes); + Files.copy(bytes, helper); + } + try (URLClassLoader isolated = new URLClassLoader( + new URL[] {directory.toUri().toURL()}, ClassLoader.getPlatformClassLoader())) { + Method local = isolated.loadClass(HELPER).getDeclaredMethod("local"); + local.setAccessible(true); + return ((Enum) local.invoke(null)).name(); + } + } +} diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/world/BiomeModifierCompatibilityTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/world/BiomeModifierCompatibilityTest.java new file mode 100644 index 0000000..ef906f4 --- /dev/null +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/world/BiomeModifierCompatibilityTest.java @@ -0,0 +1,32 @@ +package zone.moddev.mc.skysgrassslabs.world; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.neoforged.neoforge.common.world.BiomeModifier.Phase; +import net.neoforged.neoforge.common.world.ModifiableBiomeInfo.BiomeInfo; +import org.junit.Test; + +public class BiomeModifierCompatibilityTest { + @Test + public void bothLoaderEntryPointsRemainAvailable() throws Exception { + Class modifier = Class.forName(SmoothingBiomeModifier.class.getName(), false, + getClass().getClassLoader()); + assertNotNull(modifier.getMethod("modify", Holder.class, Phase.class, + BiomeInfo.Builder.class)); + assertNotNull(modifier.getMethod("modify", RegistryAccess.class, Holder.class, + Phase.class, BiomeInfo.Builder.class)); + } + + @Test + public void newerEntryPointDelegatesWithoutChangingTheSmoothingRules() throws Exception { + String source = java.nio.file.Files.readString(java.nio.file.Path.of( + "src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingBiomeModifier.java")); + assertTrue(source.contains("modify(biome, phase, builder);")); + assertTrue(source.contains("phase != Phase.AFTER_EVERYTHING")); + assertTrue(source.contains("features.add(0, smoothing)")); + assertTrue(source.contains("features.stream().noneMatch")); + } +}