diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..192b055 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*.java] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +max_line_length = 120 +trim_trailing_whitespace = true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0e60737 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +Keep the code easy to read and straightforward to review. + +- Use four spaces, UTF-8 and LF line endings. Aim for 120 columns; do not split a registry ID or other literal just to meet the limit. +- Put each statement on its own line and use braces for conditional and loop bodies, including early returns. +- Leave a blank line between logical steps. Keep related assignments together rather than separating every line. +- Name variables for their role. Use `inventorySlot`, `cellX` or `chunkX` when that context matters, rather than a single letter or a type abbreviation. Keep established names such as `world` and `state` when their meaning is already clear. +- Declare separate local values on separate lines. Explain packed masks and coordinate conventions where the names cannot tell the whole story. +- Write comments in plain English. Explain a decision, save-format constraint or surprising API behaviour instead of repeating what the next line says. +- Document public extension points, including when they can be called and what they preserve. Keep comments current when behaviour changes. + +For Eclipse, import [the Java formatter profile](style/eclipse-java-formatter.xml) under **Java > Code Style > Formatter** and select **Sky Java**. The formatter handles layout; braces and logical grouping still need a quick human review. + +Treat registry IDs, metadata palettes, configuration keys and saved NBT fields as compatibility contracts. A readability change should not alter them. Keep behavioural changes separate and run the relevant tests before submitting a pull request. See [the README](README.md) for this branch's toolchain and checks. diff --git a/src/clientIntegrationTest/java/zone/moddev/mc/skysgrassslabs/clienttest/ClientProbeTestMod.java b/src/clientIntegrationTest/java/zone/moddev/mc/skysgrassslabs/clienttest/ClientProbeTestMod.java index 1a01e66..428b437 100644 --- a/src/clientIntegrationTest/java/zone/moddev/mc/skysgrassslabs/clienttest/ClientProbeTestMod.java +++ b/src/clientIntegrationTest/java/zone/moddev/mc/skysgrassslabs/clienttest/ClientProbeTestMod.java @@ -27,8 +27,11 @@ import zone.moddev.mc.skysgrassslabs.init.ModBlocks; /** Build-only rendered-client probe. This class is never included in release artifacts. */ -@Mod(modid = ClientProbeTestMod.MOD_ID, name = "Sky's Grass Slabs Client Probe", - version = "1", acceptedMinecraftVersions = "[1.12.2]", +@Mod( + modid = ClientProbeTestMod.MOD_ID, + name = "Sky's Grass Slabs Client Probe", + version = "1", + acceptedMinecraftVersions = "[1.12.2]", dependencies = "required-after:skysgrassslabs") public final class ClientProbeTestMod { public static final String MOD_ID = "skysgrassslabsclienttest"; @@ -49,30 +52,37 @@ public void initialize(FMLInitializationEvent event) { @SubscribeEvent public void onWorldRendered(RenderWorldLastEvent event) { - if (state == 1) ++firstWorldFrames; - if (state == 3) ++reloadWorldFrames; + if (state == 1) { + ++firstWorldFrames; + } + if (state == 3) { + ++reloadWorldFrames; + } } @SubscribeEvent public void onClientTick(TickEvent.ClientTickEvent event) { - if (event.phase != TickEvent.Phase.END || - !Boolean.getBoolean("skysgrassslabs.clientProbe")) return; + if (event.phase != TickEvent.Phase.END || !Boolean.getBoolean("skysgrassslabs.clientProbe")) { + return; + } + Minecraft minecraft = Minecraft.getMinecraft(); - if (++stateTicks > 3600) fail(minecraft, "Timed out in client probe state " + state); + + if (++stateTicks > 3600) { + fail(minecraft, "Timed out in client probe state " + state); + } try { switch (state) { case 0: if (minecraft.currentScreen instanceof GuiMainMenu) { - minecraft.launchIntegratedServer(WORLD_DIRECTORY, - "Sky's Grass Slabs Client Smoke", - new WorldSettings(81726354L, GameType.CREATIVE, false, - false, WorldType.DEFAULT)); + minecraft.launchIntegratedServer(WORLD_DIRECTORY, "Sky's Grass Slabs Client Smoke", + new WorldSettings(81726354L, GameType.CREATIVE, false, false, WorldType.DEFAULT)); nextState(1); } break; case 1: - if (minecraft.world != null && minecraft.player != null && - firstWorldFrames >= 8 && stateTicks >= 100) { + if (minecraft.world != null && minecraft.player != null && firstWorldFrames >= 8 + && stateTicks >= 100) { verifyModels(minecraft); verifyRecipeBook(); stopIntegratedServer(minecraft); @@ -81,16 +91,14 @@ public void onClientTick(TickEvent.ClientTickEvent event) { break; case 2: if (minecraft.world == null && stateTicks >= 100) { - minecraft.launchIntegratedServer(WORLD_DIRECTORY, - "Sky's Grass Slabs Client Smoke", - new WorldSettings(81726354L, GameType.CREATIVE, false, - false, WorldType.DEFAULT)); + minecraft.launchIntegratedServer(WORLD_DIRECTORY, "Sky's Grass Slabs Client Smoke", + new WorldSettings(81726354L, GameType.CREATIVE, false, false, WorldType.DEFAULT)); nextState(3); } break; case 3: - if (minecraft.world != null && minecraft.player != null && - reloadWorldFrames >= 8 && stateTicks >= 100) { + if (minecraft.world != null && minecraft.player != null && reloadWorldFrames >= 8 + && stateTicks >= 100) { verifyModels(minecraft); verifyRecipeBook(); stopIntegratedServer(minecraft); @@ -117,40 +125,33 @@ private void verifyModels(Minecraft minecraft) { Block grassSlab = ModBlocks.GRASS_SLAB; Block pathSlab = ModBlocks.PATH_SLAB; Block turf = ModBlocks.TURF; - IBlockState[] states = { - dirtSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), - dirtSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), - grassSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), - grassSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), - pathSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), - pathSlab.getDefaultState().withProperty( - BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), - turf.getDefaultState() - }; - IBakedModel missing = minecraft.getBlockRendererDispatcher().getBlockModelShapes() - .getModelManager().getMissingModel(); + IBlockState[] states = { dirtSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), + dirtSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), + grassSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), + grassSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), + pathSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), + pathSlab.getDefaultState().withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM), + turf.getDefaultState() }; + IBakedModel missing = minecraft.getBlockRendererDispatcher().getBlockModelShapes().getModelManager() + .getMissingModel(); + for (IBlockState blockState : states) { IBakedModel model = minecraft.getBlockRendererDispatcher().getModelForState(blockState); ResourceLocation registryName = blockState.getBlock().getRegistryName(); - if (model == null || model == missing || model.getParticleTexture() == null || - "missingno".equals(model.getParticleTexture().getIconName())) { - throw new IllegalStateException("Missing baked model for " + registryName + - " state " + blockState); + + if (model == null || model == missing || model.getParticleTexture() == null + || "missingno".equals(model.getParticleTexture().getIconName())) { + throw new IllegalStateException("Missing baked model for " + registryName + " state " + blockState); } } + modelsVerified = true; } private void verifyRecipeBook() { - IRecipe turfRecipe = CraftingManager.REGISTRY.getObject( - new ResourceLocation("skysgrassslabs", "turf")); - if (turfRecipe == null || turfRecipe.isDynamic() || - turfRecipe.getIngredients().size() != 2) { + IRecipe turfRecipe = CraftingManager.REGISTRY.getObject(new ResourceLocation("skysgrassslabs", "turf")); + + if (turfRecipe == null || turfRecipe.isDynamic() || turfRecipe.getIngredients().size() != 2) { throw new IllegalStateException("Turf recipe is not recipe-book compatible"); } for (RecipeList recipeList : RecipeBookClient.ALL_RECIPES) { @@ -159,24 +160,29 @@ private void verifyRecipeBook() { return; } } + throw new IllegalStateException("Turf recipe is absent from the client recipe book"); } private static void stopIntegratedServer(Minecraft minecraft) { - if (minecraft.world != null) minecraft.world.sendQuittingDisconnectingPacket(); + if (minecraft.world != null) { + minecraft.world.sendQuittingDisconnectingPacket(); + } + minecraft.loadWorld(null); minecraft.displayGuiScreen(new GuiMainMenu()); } private void writeMarker() throws IOException { Properties values = new Properties(); + values.setProperty("models_verified", Boolean.toString(modelsVerified)); values.setProperty("recipe_book_verified", Boolean.toString(recipeBookVerified)); values.setProperty("first_world_rendered", Boolean.toString(firstWorldFrames >= 8)); values.setProperty("reload_rendered", Boolean.toString(reloadWorldFrames >= 8)); values.setProperty("world_directory", WORLD_DIRECTORY); - try (FileOutputStream output = new FileOutputStream( - new File("client-smoke-pass.properties"))) { + + try (FileOutputStream output = new FileOutputStream(new File("client-smoke-pass.properties"))) { values.store(output, "Sky's Grass Slabs Forge 1.12.2 client gate"); } } @@ -189,13 +195,15 @@ private void nextState(int next) { private static void fail(Minecraft minecraft, String message) { try { Properties values = new Properties(); + values.setProperty("failure", message); - try (FileOutputStream output = new FileOutputStream( - new File("client-smoke-failure.properties"))) { + + try (FileOutputStream output = new FileOutputStream(new File("client-smoke-failure.properties"))) { values.store(output, "Sky's Grass Slabs client probe failure"); } } catch (IOException ignored) { } + minecraft.shutdown(); throw new IllegalStateException(message); } diff --git a/src/fixtureGenerator/java/zone/moddev/mc/skysgrassslabs/fixture/ForwardUpgradeFixtureMod.java b/src/fixtureGenerator/java/zone/moddev/mc/skysgrassslabs/fixture/ForwardUpgradeFixtureMod.java index 2d6b5f8..4491ef0 100644 --- a/src/fixtureGenerator/java/zone/moddev/mc/skysgrassslabs/fixture/ForwardUpgradeFixtureMod.java +++ b/src/fixtureGenerator/java/zone/moddev/mc/skysgrassslabs/fixture/ForwardUpgradeFixtureMod.java @@ -21,8 +21,11 @@ import zone.moddev.mc.skysgrassslabs.world.ModWorldState; /** Creates the immutable Minecraft 1.10.2 input used by forward-upgrade tests. */ -@Mod(modid = ForwardUpgradeFixtureMod.MOD_ID, name = "Sky's Grass Slabs Forward Upgrade Fixture", - version = "1", dependencies = "required-after:skysgrassslabs") +@Mod( + modid = ForwardUpgradeFixtureMod.MOD_ID, + name = "Sky's Grass Slabs Forward Upgrade Fixture", + version = "1", + dependencies = "required-after:skysgrassslabs") public final class ForwardUpgradeFixtureMod { public static final String MOD_ID = "skysgrassslabsforwardfixture"; private static final BlockPos ORIGIN = new BlockPos(8, 65, 8); @@ -31,6 +34,7 @@ public final class ForwardUpgradeFixtureMod { public void serverStarted(FMLServerStartedEvent event) throws Exception { MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); WorldServer world = server.worldServerForDimension(0); + world.getGameRules().setOrCreateGameRule("randomTickSpeed", "0"); world.getWorldInfo().setRaining(false); world.getWorldInfo().setThundering(false); @@ -47,11 +51,14 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { world.setBlockState(ORIGIN.east(6), defaultState(ModBlocks.TURF), 3); BlockPos chestPos = ORIGIN.south(2); + support(world, chestPos); world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 3); + TileEntityChest chest = (TileEntityChest) world.getTileEntity(chestPos); ItemStack grassStack = new ItemStack(ModBlocks.GRASS_SLAB, 3, 0); NBTTagCompound grassData = new NBTTagCompound(); + grassData.setString("fixture", "retained"); grassStack.setTagCompound(grassData); chest.setInventorySlotContents(0, new ItemStack(ModBlocks.DIRT_SLAB, 2, 0)); @@ -60,13 +67,14 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { chest.setInventorySlotContents(3, new ItemStack(ModBlocks.TURF, 5, 0)); chest.markDirty(); - EntityItem dropped = new EntityItem(world, ORIGIN.getX() + 4.5D, - ORIGIN.getY() + 1.0D, ORIGIN.getZ() + 2.5D, + EntityItem dropped = new EntityItem(world, ORIGIN.getX() + 4.5D, ORIGIN.getY() + 1.0D, ORIGIN.getZ() + 2.5D, new ItemStack(ModBlocks.GRASS_SLAB, 6, 0)); + dropped.setNoPickupDelay(); world.spawnEntity(dropped); ModWorldState state = ModWorldState.get(world); + state.recordChunk(); state.recordChunk(); state.recordGrassBlocks(3, 0); @@ -78,25 +86,24 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { state.recordUnsupported("fixture:unsupported_shape", 19); File worldDirectory = world.getSaveHandler().getWorldDirectory(); + Files.write(new File(worldDirectory, "skysgrassslabs-forward-fixture.properties").toPath(), - Arrays.asList( - "source_minecraft=1.10.2", - "source_mod_version=1.0.0.110021", + Arrays.asList("source_minecraft=1.10.2", "source_mod_version=1.0.0.110021", "source_jar_sha256=2030960E217C3F61AE4919C91058696B02F9FAE570BE1CD7B698696EA7BEB861", - "origin=8,65,8", - "expected_blocks=7", - "expected_chest_stacks=4", - "expected_entity_stacks=1", - "schema_version=1"), StandardCharsets.UTF_8); + "origin=8,65,8", "expected_blocks=7", "expected_chest_stacks=4", "expected_entity_stacks=1", + "schema_version=1"), + StandardCharsets.UTF_8); world.saveAllChunks(true, null); server.initiateShutdown(); } - private static void placeSlab(WorldServer world, BlockPos pos, - net.minecraft.block.Block block, BlockSlab.EnumBlockHalf half) { + private static void placeSlab(WorldServer world, BlockPos pos, net.minecraft.block.Block block, + BlockSlab.EnumBlockHalf half) { support(world, pos); + IBlockState state = block.getDefaultState().withProperty(BlockSlab.HALF, half); + world.setBlockState(pos, state, 3); } @@ -109,10 +116,10 @@ private static IBlockState defaultState(net.minecraft.block.Block block) { } private static void clearFixtureArea(WorldServer world) { - for (int y = ORIGIN.getY() - 1; y <= ORIGIN.getY() + 3; ++y) { - for (int z = ORIGIN.getZ() - 2; z <= ORIGIN.getZ() + 4; ++z) { - for (int x = ORIGIN.getX() - 2; x <= ORIGIN.getX() + 8; ++x) { - world.setBlockToAir(new BlockPos(x, y, z)); + for (int blockY = ORIGIN.getY() - 1; blockY <= ORIGIN.getY() + 3; ++blockY) { + for (int blockZ = ORIGIN.getZ() - 2; blockZ <= ORIGIN.getZ() + 4; ++blockZ) { + for (int blockX = ORIGIN.getX() - 2; blockX <= ORIGIN.getX() + 8; ++blockX) { + world.setBlockToAir(new BlockPos(blockX, blockY, blockZ)); } } } diff --git a/src/fixtureGenerator111/java/zone/moddev/mc/skysgrassslabs/fixture/AdjacentUpgradeFixtureMod.java b/src/fixtureGenerator111/java/zone/moddev/mc/skysgrassslabs/fixture/AdjacentUpgradeFixtureMod.java index 5658230..344d740 100644 --- a/src/fixtureGenerator111/java/zone/moddev/mc/skysgrassslabs/fixture/AdjacentUpgradeFixtureMod.java +++ b/src/fixtureGenerator111/java/zone/moddev/mc/skysgrassslabs/fixture/AdjacentUpgradeFixtureMod.java @@ -22,8 +22,10 @@ import zone.moddev.mc.skysgrassslabs.world.ModWorldState; /** Creates the immutable Minecraft 1.11.2 input used by forward upgrade tests. */ -@Mod(modid = AdjacentUpgradeFixtureMod.MOD_ID, - name = "Sky's Grass Slabs Adjacent Upgrade Fixture", version = "1", +@Mod( + modid = AdjacentUpgradeFixtureMod.MOD_ID, + name = "Sky's Grass Slabs Adjacent Upgrade Fixture", + version = "1", dependencies = "required-after:skysgrassslabs") public final class AdjacentUpgradeFixtureMod { public static final String MOD_ID = "skysgrassslabsadjacentfixture"; @@ -33,6 +35,7 @@ public final class AdjacentUpgradeFixtureMod { public void serverStarted(FMLServerStartedEvent event) throws Exception { MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); WorldServer world = server.getWorld(0); + world.getGameRules().setOrCreateGameRule("randomTickSpeed", "0"); world.getWorldInfo().setRaining(false); world.getWorldInfo().setThundering(false); @@ -40,25 +43,23 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { clearFixtureArea(world); placeSlab(world, ORIGIN, ModBlocks.DIRT_SLAB, BlockSlab.EnumBlockHalf.TOP); - placeSlab(world, ORIGIN.east(), ModBlocks.DIRT_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - placeSlab(world, ORIGIN.east(2), ModBlocks.GRASS_SLAB, - BlockSlab.EnumBlockHalf.TOP); - placeSlab(world, ORIGIN.east(3), ModBlocks.GRASS_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - placeSlab(world, ORIGIN.east(4), ModBlocks.PATH_SLAB, - BlockSlab.EnumBlockHalf.TOP); - placeSlab(world, ORIGIN.east(5), ModBlocks.PATH_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); + placeSlab(world, ORIGIN.east(), ModBlocks.DIRT_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + placeSlab(world, ORIGIN.east(2), ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.TOP); + placeSlab(world, ORIGIN.east(3), ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + placeSlab(world, ORIGIN.east(4), ModBlocks.PATH_SLAB, BlockSlab.EnumBlockHalf.TOP); + placeSlab(world, ORIGIN.east(5), ModBlocks.PATH_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); support(world, ORIGIN.east(6)); world.setBlockState(ORIGIN.east(6), defaultState(ModBlocks.TURF), 3); BlockPos chestPos = ORIGIN.south(2); + support(world, chestPos); world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 3); + TileEntityChest chest = (TileEntityChest) world.getTileEntity(chestPos); ItemStack grassStack = new ItemStack(ModBlocks.GRASS_SLAB, 3, 0); NBTTagCompound grassData = new NBTTagCompound(); + grassData.setString("fixture", "retained"); grassStack.setTagCompound(grassData); chest.setInventorySlotContents(0, new ItemStack(ModBlocks.DIRT_SLAB, 2, 0)); @@ -67,13 +68,14 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { chest.setInventorySlotContents(3, new ItemStack(ModBlocks.TURF, 5, 0)); chest.markDirty(); - EntityItem dropped = new EntityItem(world, ORIGIN.getX() + 4.5D, - ORIGIN.getY() + 1.0D, ORIGIN.getZ() + 2.5D, + EntityItem dropped = new EntityItem(world, ORIGIN.getX() + 4.5D, ORIGIN.getY() + 1.0D, ORIGIN.getZ() + 2.5D, new ItemStack(ModBlocks.GRASS_SLAB, 6, 0)); + dropped.setNoPickupDelay(); world.spawnEntity(dropped); ModWorldState state = ModWorldState.get(world); + state.recordChunk(); state.recordChunk(); state.recordGrassBlocks(3, 0); @@ -85,25 +87,23 @@ public void serverStarted(FMLServerStartedEvent event) throws Exception { state.recordUnsupported("fixture:unsupported_shape", 19); File worldDirectory = world.getSaveHandler().getWorldDirectory(); - Files.write(new File(worldDirectory, - "skysgrassslabs-forward-fixture.properties").toPath(), Arrays.asList( - "source_minecraft=1.11.2", - "source_mod_version=1.0.1.111021", + + Files.write(new File(worldDirectory, "skysgrassslabs-forward-fixture.properties").toPath(), + Arrays.asList("source_minecraft=1.11.2", "source_mod_version=1.0.1.111021", "source_jar_sha256=56D8B8C1FA7F2289C9F9A3BCF2BEB2D15F0F880373D0647BB9BDBFA7E1D5FE54", - "origin=8,65,8", - "expected_blocks=7", - "expected_chest_stacks=4", - "expected_entity_stacks=1", - "schema_version=1"), StandardCharsets.UTF_8); + "origin=8,65,8", "expected_blocks=7", "expected_chest_stacks=4", "expected_entity_stacks=1", + "schema_version=1"), + StandardCharsets.UTF_8); world.saveAllChunks(true, null); server.initiateShutdown(); } - private static void placeSlab(WorldServer world, BlockPos pos, Block block, - BlockSlab.EnumBlockHalf half) { + private static void placeSlab(WorldServer world, BlockPos pos, Block block, BlockSlab.EnumBlockHalf half) { support(world, pos); + IBlockState state = block.getDefaultState().withProperty(BlockSlab.HALF, half); + world.setBlockState(pos, state, 3); } @@ -116,10 +116,10 @@ private static IBlockState defaultState(Block block) { } private static void clearFixtureArea(WorldServer world) { - for (int y = ORIGIN.getY() - 1; y <= ORIGIN.getY() + 3; ++y) { - for (int z = ORIGIN.getZ() - 2; z <= ORIGIN.getZ() + 4; ++z) { - for (int x = ORIGIN.getX() - 2; x <= ORIGIN.getX() + 8; ++x) { - world.setBlockToAir(new BlockPos(x, y, z)); + for (int blockY = ORIGIN.getY() - 1; blockY <= ORIGIN.getY() + 3; ++blockY) { + for (int blockZ = ORIGIN.getZ() - 2; blockZ <= ORIGIN.getZ() + 4; ++blockZ) { + for (int blockX = ORIGIN.getX() - 2; blockX <= ORIGIN.getX() + 8; ++blockX) { + world.setBlockToAir(new BlockPos(blockX, blockY, blockZ)); } } } 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 6b9eef9..6ec0f39 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java +++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java @@ -94,8 +94,11 @@ import zone.moddev.mc.skysgrassslabs.world.ModWorldState; /** Build-only runtime probe. This source set is never included in release artifacts. */ -@Mod(modid = IntegrationTestMod.MOD_ID, name = "Sky's Grass Slabs Integration Test", - version = "1", dependencies = "required-after:skysgrassslabs") +@Mod( + modid = IntegrationTestMod.MOD_ID, + name = "Sky's Grass Slabs Integration Test", + version = "1", + dependencies = "required-after:skysgrassslabs") public final class IntegrationTestMod { public static final String MOD_ID = "skysgrassslabsintegrationtest"; private static final String MARKER_NAME = "skysgrassslabs-integration.properties"; @@ -107,31 +110,35 @@ public void serverStarted(FMLServerStartedEvent event) { WorldServer world = server.getWorld(0); File marker = new File(world.getSaveHandler().getWorldDirectory(), MARKER_NAME); String phase = System.getProperty("skysgrassslabs.integrationPhase", "fresh"); + try { Properties evidence = load(marker); + if ("released-seed".equals(phase) || "released-upgrade".equals(phase)) { ReleasedUpgradeChecks.run(world, "released-seed".equals(phase)); evidence.setProperty(phase.replace('-', '_') + "_complete", "true"); - } else if ("upgrade-110-first".equals(phase) || - "upgrade-110-reload".equals(phase)) { + } else if ("upgrade-110-first".equals(phase) || "upgrade-110-reload".equals(phase)) { verifyForwardUpgradeFixture(world, "1.10.2", "1.0.0.110021", "2030960E217C3F61AE4919C91058696B02F9FAE570BE1CD7B698696EA7BEB861"); evidence.setProperty(phase.replace('-', '_') + "_complete", "true"); - } else if ("upgrade-111-first".equals(phase) || - "upgrade-111-reload".equals(phase)) { + } else if ("upgrade-111-first".equals(phase) || "upgrade-111-reload".equals(phase)) { verifyForwardUpgradeFixture(world, "1.11.2", "1.0.1.111021", "56D8B8C1FA7F2289C9F9A3BCF2BEB2D15F0F880373D0647BB9BDBFA7E1D5FE54"); evidence.setProperty(phase.replace('-', '_') + "_complete", "true"); } else if ("fresh".equals(phase)) { int gameplayChecks = verifyGameplay(server, world); int worldgenChecks = verifyWorldgen(world); + evidence.setProperty("gameplay_checks", Integer.toString(gameplayChecks)); evidence.setProperty("worldgen_checks", Integer.toString(worldgenChecks)); + if (Loader.isModLoaded(BuildingBricksCompat.MOD_ID)) { seedLegacyCompatibilityFixture(world); verifyLegacyCompatibilityHooks(server, world, false); verifyLegacyBridgeRecipes(world); + int takeoverSlabs = verifyLegacyWorldgenTakeover(world); + evidence.setProperty("compat_fixture_seeded", "true"); evidence.setProperty("takeover_sky_slabs", Integer.toString(takeoverSlabs)); evidence.setProperty("takeover_complete", "true"); @@ -139,13 +146,14 @@ public void serverStarted(FMLServerStartedEvent event) { seedLegacyAliasFixture(world); evidence.setProperty("legacy_alias_fixture_seeded", "true"); } + evidence.setProperty("fresh_complete", "true"); } else if ("reload".equals(phase)) { require("true".equals(evidence.getProperty("fresh_complete")), "Fresh integration evidence was not retained"); - require(ModWorldState.SCHEMA_VERSION == 1, - "World-state schema changed during reload"); + require(ModWorldState.SCHEMA_VERSION == 1, "World-state schema changed during reload"); require(ModWorldState.get(world) != null, "World state did not reload"); + if ("true".equals(evidence.getProperty("compat_fixture_seeded"))) { require(!SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Default coexistence reload unexpectedly enabled replacement"); @@ -159,6 +167,7 @@ public void serverStarted(FMLServerStartedEvent event) { verifyLegacyAliasFixture(world); evidence.setProperty("legacy_alias_recovery_complete", "true"); } + evidence.setProperty("reload_complete", "true"); } else if ("compat-replacement".equals(phase)) { require("true".equals(evidence.getProperty("compat_retention_complete")), @@ -167,29 +176,34 @@ public void serverStarted(FMLServerStartedEvent event) { "Compatibility replacement phase requires the force option"); verifyLegacyCompatibilityFixture(world, true); verifyLegacyCompatibilityHooks(server, world, true); + ModWorldState state = ModWorldState.get(world); + require(state.migratedGrassBlocks() >= 3 && state.migratedDirtBlocks() >= 3, "Forced replacement did not count loaded and newly placed slabs"); require(state.migratedGrassItems() >= 4 && state.migratedDirtItems() >= 6, "Forced replacement did not count container, entity and player items"); evidence.setProperty("compat_replacement_complete", "true"); } else if ("coexistence".equals(phase)) { - require(Loader.isModLoaded(BuildingBricksCompat.MOD_ID), - "Default coexistence requires BuildingBricks"); + require(Loader.isModLoaded(BuildingBricksCompat.MOD_ID), "Default coexistence requires BuildingBricks"); require(!SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Default coexistence requires replacement to remain disabled"); + LegacyContentCounts counts = auditExistingLegacyContent(server, true); + verifySylvesterContent(counts, "BuildingBricks"); verifyLegacyContentSamples(server, counts, true); + ModWorldState state = ModWorldState.get(world); - require(state.migratedChunks() == 0 && state.migratedGrassBlocks() == 0 && - state.migratedDirtBlocks() == 0 && state.migratedGrassItems() == 0 && - state.migratedDirtItems() == 0, - "Disabled replacement changed migration counters"); - require(!migrationReport(world).exists(), - "Disabled replacement created a migration report"); + + require(state.migratedChunks() == 0 && state.migratedGrassBlocks() == 0 + && state.migratedDirtBlocks() == 0 && state.migratedGrassItems() == 0 + && state.migratedDirtItems() == 0, "Disabled replacement changed migration counters"); + require(!migrationReport(world).exists(), "Disabled replacement created a migration report"); verifyLegacyBridgeRecipes(world); + int takeoverSlabs = verifyLegacyWorldgenTakeover(world); + writeLegacyContentEvidence(evidence, "retained", counts); evidence.setProperty("takeover_sky_slabs", Integer.toString(takeoverSlabs)); evidence.setProperty("takeover_complete", "true"); @@ -197,73 +211,82 @@ public void serverStarted(FMLServerStartedEvent event) { } else if ("missing-mapping".equals(phase)) { require(!Loader.isModLoaded(BuildingBricksCompat.MOD_ID), "Missing mapping recovery requires BuildingBricks to be absent"); + LegacyContentCounts counts = auditExistingLegacyContent(server, false); + verifySylvesterContent(counts, "Sky"); verifyLegacyContentSamples(server, counts, false); + ModWorldState state = ModWorldState.get(world); - require(state.migratedChunks() == 0 && state.migratedGrassBlocks() == 0 && - state.migratedDirtBlocks() == 0 && state.migratedGrassItems() == 0 && - state.migratedDirtItems() == 0, + + require(state.migratedChunks() == 0 && state.migratedGrassBlocks() == 0 + && state.migratedDirtBlocks() == 0 && state.migratedGrassItems() == 0 + && state.migratedDirtItems() == 0, "Missing mapping recovery incorrectly counted forced migration"); - require(!migrationReport(world).exists(), - "Missing mapping recovery created a forced migration report"); + require(!migrationReport(world).exists(), "Missing mapping recovery created a forced migration report"); writeLegacyContentEvidence(evidence, "remapped", counts); evidence.setProperty("missing_mapping_complete", "true"); - } else if ("forward-sylvester".equals(phase) || - "forward-sylvester-reload".equals(phase)) { + } else if ("forward-sylvester".equals(phase) || "forward-sylvester-reload".equals(phase)) { require(!Loader.isModLoaded(BuildingBricksCompat.MOD_ID), "Forward Sylvester audit requires the converted Sky world"); + LegacyContentCounts counts = auditExistingLegacyContent(server, false); boolean reload = "forward-sylvester-reload".equals(phase); + verifyForwardSylvesterContent(counts, reload); verifyLegacyContentSamples(server, counts, false); + ModWorldState state = ModWorldState.get(world); + require(ModWorldState.SCHEMA_VERSION == 1 && state != null, "Sylvester world state did not survive the forward port"); + if ("forward-sylvester".equals(phase)) { writeLegacyContentEvidence(evidence, "forward_sylvester", counts); writeForwardStateEvidence(evidence, state); evidence.setProperty("forward_sylvester_complete", "true"); } else { - require("true".equals(evidence.getProperty( - "forward_sylvester_complete")), + require("true".equals(evidence.getProperty("forward_sylvester_complete")), "First Sylvester forward audit was not retained"); verifyForwardSylvesterReloadEvidence(evidence, counts); verifyForwardStateEvidence(evidence, state); - long unavailableItems = Long.parseLong(evidence.getProperty( - "forward_sylvester_dirt_items")) - counts.dirtItems; - evidence.setProperty("forward_sylvester_reload_dirt_items", - Long.toString(counts.dirtItems)); - evidence.setProperty( - "forward_sylvester_items_unavailable_without_legacy_mod_stack", + + long unavailableItems = Long.parseLong(evidence.getProperty("forward_sylvester_dirt_items")) + - counts.dirtItems; + + evidence.setProperty("forward_sylvester_reload_dirt_items", Long.toString(counts.dirtItems)); + evidence.setProperty("forward_sylvester_items_unavailable_without_legacy_mod_stack", Long.toString(unavailableItems)); evidence.setProperty("forward_sylvester_reload_complete", "true"); } } else if ("migration".equals(phase)) { require(SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Sylvester migration requires compat.forceReplaceBuildingBricksSlabs=true"); + int chunks = migrateExistingChunks(server); ModWorldState state = ModWorldState.get(world); + require(state.migratedGrassBlocks() == 1656276L, "Unexpected Sylvester grass-slab total " + state.migratedGrassBlocks()); require(state.migratedDirtBlocks() == 2968L, "Unexpected Sylvester dirt-slab total " + state.migratedDirtBlocks()); - require(state.migratedGrassBlocks() == state.migratedGrassBlocksTop() + - state.migratedGrassBlocksBottom(), - "Grass-slab orientation totals do not add up"); - require(state.migratedDirtBlocks() == state.migratedDirtBlocksTop() + - state.migratedDirtBlocksBottom(), + require(state.migratedGrassBlocks() == state.migratedGrassBlocksTop() + + state.migratedGrassBlocksBottom(), "Grass-slab orientation totals do not add up"); + require(state.migratedDirtBlocks() == state.migratedDirtBlocksTop() + state.migratedDirtBlocksBottom(), "Dirt-slab orientation totals do not add up"); writeMigrationEvidence(evidence, chunks, state); + if ("true".equals(evidence.getProperty("coexistence_complete"))) { evidence.setProperty("replacement_transition_verified", "true"); } + evidence.setProperty("migration_complete", "true"); } else if ("migration-reload".equals(phase)) { require("true".equals(evidence.getProperty("migration_complete")), "Migration evidence was not retained"); require(SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Sylvester migration reload requires forced replacement to remain enabled"); + ModWorldState before = ModWorldState.get(world); long grass = before.migratedGrassBlocks(); long dirt = before.migratedDirtBlocks(); @@ -272,17 +295,17 @@ public void serverStarted(FMLServerStartedEvent event) { long unsupported = unsupportedTotal(before); int chunks = migrateExistingChunks(server); ModWorldState after = ModWorldState.get(world); - require(grass == after.migratedGrassBlocks() && - dirt == after.migratedDirtBlocks() && - grassItems == after.migratedGrassItems() && - dirtItems == after.migratedDirtItems() && - unsupported == unsupportedTotal(after), + + require(grass == after.migratedGrassBlocks() && dirt == after.migratedDirtBlocks() + && grassItems == after.migratedGrassItems() && dirtItems == after.migratedDirtItems() + && unsupported == unsupportedTotal(after), "Second Sylvester load performed additional conversions"); evidence.setProperty("migration_reload_chunks", Integer.toString(chunks)); evidence.setProperty("migration_reload_complete", "true"); } else { throw new IllegalStateException("Unknown integration phase " + phase); } + store(marker, evidence); } catch (Throwable failure) { SkysGrassSlabs.logger.error("Sky's Grass Slabs integration audit failed", failure); @@ -292,12 +315,12 @@ public void serverStarted(FMLServerStartedEvent event) { } } - private static void verifyForwardUpgradeFixture(WorldServer world, - String expectedMinecraft, String expectedModVersion, - String expectedJarSha256) throws Exception { + private static void verifyForwardUpgradeFixture(WorldServer world, String expectedMinecraft, + String expectedModVersion, String expectedJarSha256) throws Exception { File sourceMarker = new File(world.getSaveHandler().getWorldDirectory(), "skysgrassslabs-forward-fixture.properties"); Properties source = load(sourceMarker); + require(expectedMinecraft.equals(source.getProperty("source_minecraft")), "Forward fixture did not originate from Minecraft " + expectedMinecraft); require(expectedModVersion.equals(source.getProperty("source_mod_version")), @@ -306,46 +329,43 @@ private static void verifyForwardUpgradeFixture(WorldServer world, "Forward fixture used the wrong source jar"); BlockPos origin = new BlockPos(8, 65, 8); + verifyFixtureSlab(world, origin, ModBlocks.DIRT_SLAB, BlockSlab.EnumBlockHalf.TOP); - verifyFixtureSlab(world, origin.east(), ModBlocks.DIRT_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - verifyFixtureSlab(world, origin.east(2), ModBlocks.GRASS_SLAB, - BlockSlab.EnumBlockHalf.TOP); - verifyFixtureSlab(world, origin.east(3), ModBlocks.GRASS_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - verifyFixtureSlab(world, origin.east(4), ModBlocks.PATH_SLAB, - BlockSlab.EnumBlockHalf.TOP); - verifyFixtureSlab(world, origin.east(5), ModBlocks.PATH_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - require(world.getBlockState(origin.east(6)).getBlock() == ModBlocks.TURF, - "Forward fixture turf was lost"); + verifyFixtureSlab(world, origin.east(), ModBlocks.DIRT_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + verifyFixtureSlab(world, origin.east(2), ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.TOP); + verifyFixtureSlab(world, origin.east(3), ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + verifyFixtureSlab(world, origin.east(4), ModBlocks.PATH_SLAB, BlockSlab.EnumBlockHalf.TOP); + verifyFixtureSlab(world, origin.east(5), ModBlocks.PATH_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + require(world.getBlockState(origin.east(6)).getBlock() == ModBlocks.TURF, "Forward fixture turf was lost"); TileEntity chestEntity = world.getTileEntity(origin.south(2)); - require(chestEntity instanceof IInventory, - "Forward fixture chest was lost"); + + require(chestEntity instanceof IInventory, "Forward fixture chest was lost"); + IInventory chest = (IInventory) chestEntity; + verifyFixtureStack(chest.getStackInSlot(0), ModBlocks.DIRT_SLAB, 2); verifyFixtureStack(chest.getStackInSlot(1), ModBlocks.GRASS_SLAB, 3); - require(chest.getStackInSlot(1).hasTagCompound() && "retained".equals( - chest.getStackInSlot(1).getTagCompound().getString("fixture")), + require(chest.getStackInSlot(1).hasTagCompound() + && "retained".equals(chest.getStackInSlot(1).getTagCompound().getString("fixture")), "Forward fixture stack NBT was lost"); verifyFixtureStack(chest.getStackInSlot(2), ModBlocks.PATH_SLAB, 4); verifyFixtureStack(chest.getStackInSlot(3), ModBlocks.TURF, 5); List items = world.getEntitiesWithinAABB(EntityItem.class, new AxisAlignedBB(origin.add(2, 0, 0), origin.add(7, 4, 5))); + require(items.size() == 1, "Forward fixture entity stack count changed: " + items.size()); verifyFixtureStack(items.get(0).getItem(), ModBlocks.GRASS_SLAB, 6); ModWorldState state = ModWorldState.get(world); + require(ModWorldState.SCHEMA_VERSION == 1 && state.migratedChunks() == 2, "Forward fixture world-state schema or chunk count changed"); - require(state.migratedGrassBlocks() == 8 && state.migratedGrassBlocksTop() == 3 && - state.migratedGrassBlocksBottom() == 5, - "Forward fixture grass migration totals changed"); - require(state.migratedDirtBlocks() == 18 && state.migratedDirtBlocksTop() == 7 && - state.migratedDirtBlocksBottom() == 11, - "Forward fixture dirt migration totals changed"); + require(state.migratedGrassBlocks() == 8 && state.migratedGrassBlocksTop() == 3 + && state.migratedGrassBlocksBottom() == 5, "Forward fixture grass migration totals changed"); + require(state.migratedDirtBlocks() == 18 && state.migratedDirtBlocksTop() == 7 + && state.migratedDirtBlocksBottom() == 11, "Forward fixture dirt migration totals changed"); require(state.migratedGrassItems() == 13 && state.migratedDirtItems() == 17, "Forward fixture item migration totals changed"); require(Long.valueOf(19).equals(state.unsupported().get("fixture:unsupported_shape")), @@ -356,15 +376,17 @@ private static void seedLegacyAliasFixture(WorldServer world) { BlockPos origin = legacyAliasOrigin(world); Block grass = Block.REGISTRY.getObject(BuildingBricksCompat.GRASS_SLAB_ID); Block dirt = Block.REGISTRY.getObject(BuildingBricksCompat.DIRT_SLAB_ID); - Block historical = Block.REGISTRY.getObject( - BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID); + Block historical = Block.REGISTRY.getObject(BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID); + require(grass != null && dirt != null && historical != null, "Supported legacy compatibility holders were not registered"); world.setBlockState(origin, grass.getStateFromMeta(0), 2); world.setBlockState(origin.east(), dirt.getStateFromMeta(1), 2); world.setBlockState(origin.east(2), historical.getStateFromMeta(1), 2); world.setBlockState(origin.south(), Blocks.CHEST.getDefaultState(), 2); + IInventory chest = (IInventory) world.getTileEntity(origin.south()); + chest.setInventorySlotContents(0, new ItemStack(grass, 2, 0)); chest.setInventorySlotContents(1, new ItemStack(dirt, 3, 0)); chest.setInventorySlotContents(2, new ItemStack(historical, 4, 0)); @@ -373,335 +395,418 @@ private static void seedLegacyAliasFixture(WorldServer world) { private static void verifyLegacyAliasFixture(WorldServer world) { BlockPos origin = legacyAliasOrigin(world); + verifyFixtureSlab(world, origin, ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.TOP); - verifyFixtureSlab(world, origin.east(), ModBlocks.DIRT_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); - verifyFixtureSlab(world, origin.east(2), ModBlocks.GRASS_SLAB, - BlockSlab.EnumBlockHalf.BOTTOM); + verifyFixtureSlab(world, origin.east(), ModBlocks.DIRT_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + verifyFixtureSlab(world, origin.east(2), ModBlocks.GRASS_SLAB, BlockSlab.EnumBlockHalf.BOTTOM); + IInventory chest = (IInventory) world.getTileEntity(origin.south()); + require(chest != null, "Legacy compatibility holder chest was lost"); verifyFixtureStack(chest.getStackInSlot(0), ModBlocks.GRASS_SLAB, 2); verifyFixtureStack(chest.getStackInSlot(1), ModBlocks.DIRT_SLAB, 3); verifyFixtureStack(chest.getStackInSlot(2), ModBlocks.GRASS_SLAB, 4); + ModWorldState state = ModWorldState.get(world); - require(state.migratedGrassBlocks() == 0 && state.migratedDirtBlocks() == 0 && - state.migratedGrassItems() == 0 && state.migratedDirtItems() == 0, + + require(state.migratedGrassBlocks() == 0 && state.migratedDirtBlocks() == 0 && state.migratedGrassItems() == 0 + && state.migratedDirtItems() == 0, "Automatic missing content recovery changed forced migration counters"); } private static BlockPos legacyAliasOrigin(World world) { BlockPos spawn = world.getSpawnPoint(); - return new BlockPos((spawn.getX() & ~15) + 2, 140, - (spawn.getZ() & ~15) + 2); + + return new BlockPos((spawn.getX() & ~15) + 2, 140, (spawn.getZ() & ~15) + 2); } - private static void verifyFixtureSlab(World world, BlockPos pos, Block expected, - BlockSlab.EnumBlockHalf half) { + private static void verifyFixtureSlab(World world, BlockPos pos, Block expected, BlockSlab.EnumBlockHalf half) { IBlockState state = world.getBlockState(pos); + require(state.getBlock() == expected && state.getValue(BlockSlab.HALF) == half, "Forward fixture slab changed at " + pos + ": " + state); } private static void verifyFixtureStack(ItemStack stack, Block expected, int count) { - require(!stack.isEmpty() && stack.getItem() == Item.getItemFromBlock(expected) && - stack.getCount() == count, + require(!stack.isEmpty() && stack.getItem() == Item.getItemFromBlock(expected) && stack.getCount() == count, "Forward fixture item stack changed for " + expected.getRegistryName()); } private static int migrateExistingChunks(MinecraftServer server) throws Exception { int total = 0; - for (int dimension : new int[] {0, -1, 1}) { + + for (int dimension : new int[] { 0, -1, 1 }) { WorldServer world = server.getWorld(dimension); - if (world == null) continue; - File dimensionDirectory = dimension == 0 - ? world.getSaveHandler().getWorldDirectory() - : new File(world.getSaveHandler().getWorldDirectory(), - world.provider.getSaveFolder()); + + if (world == null) { + continue; + } + + File dimensionDirectory = dimension == 0 ? world.getSaveHandler().getWorldDirectory() + : new File(world.getSaveHandler().getWorldDirectory(), world.provider.getSaveFolder()); List chunks = existingChunks(new File(dimensionDirectory, "region")); + total += chunks.size(); migrateChunks(world, chunks, dimension); } + return total; } - private static LegacyContentCounts auditExistingLegacyContent(MinecraftServer server, - boolean expectBuildingBricks) throws Exception { + private static LegacyContentCounts auditExistingLegacyContent(MinecraftServer server, boolean expectBuildingBricks) + throws Exception { LegacyContentCounts counts = new LegacyContentCounts(); Block grass = expectBuildingBricks ? legacyBlock(true) : ModBlocks.GRASS_SLAB; Block dirt = expectBuildingBricks ? legacyBlock(false) : ModBlocks.DIRT_SLAB; File worldDirectory = server.getWorld(0).getSaveHandler().getWorldDirectory(); - int grassId = savedBlockId(worldDirectory, grass.getRegistryName().toString(), - Block.getIdFromBlock(grass)); - int dirtId = savedBlockId(worldDirectory, dirt.getRegistryName().toString(), - Block.getIdFromBlock(dirt)); + int grassId = savedBlockId(worldDirectory, grass.getRegistryName().toString(), Block.getIdFromBlock(grass)); + int dirtId = savedBlockId(worldDirectory, dirt.getRegistryName().toString(), Block.getIdFromBlock(dirt)); Map blockNames = savedBlockNames(worldDirectory); + counts.blockNames.putAll(blockNames); - SkysGrassSlabs.logger.info("Auditing saved slab ids grass={}, dirt={}", - grassId, dirtId); - for (int dimension : new int[] {0, -1, 1}) { + SkysGrassSlabs.logger.info("Auditing saved slab ids grass={}, dirt={}", grassId, dirtId); + + for (int dimension : new int[] { 0, -1, 1 }) { WorldServer world = server.getWorld(dimension); - if (world == null) continue; - File dimensionDirectory = dimension == 0 - ? world.getSaveHandler().getWorldDirectory() - : new File(world.getSaveHandler().getWorldDirectory(), - world.provider.getSaveFolder()); + + if (world == null) { + continue; + } + + File dimensionDirectory = dimension == 0 ? world.getSaveHandler().getWorldDirectory() + : new File(world.getSaveHandler().getWorldDirectory(), world.provider.getSaveFolder()); List chunks = existingChunks(new File(dimensionDirectory, "region")); + counts.chunks += chunks.size(); - auditSavedChunks(dimensionDirectory, chunks, dimension, - grassId, dirtId, grass.getRegistryName().toString(), + auditSavedChunks(dimensionDirectory, chunks, dimension, grassId, dirtId, grass.getRegistryName().toString(), dirt.getRegistryName().toString(), blockNames, counts); } + return counts; } private static List existingChunks(File regionDirectory) throws Exception { List chunks = new ArrayList(); File[] files = regionDirectory.listFiles(); - if (files == null) return chunks; + + if (files == null) { + return chunks; + } for (File file : files) { String name = file.getName(); - if (!name.startsWith("r.") || !name.endsWith(".mca")) continue; + + if (!name.startsWith("r.") || !name.endsWith(".mca")) { + continue; + } + String[] coordinates = name.substring(2, name.length() - 4).split("\\."); - if (coordinates.length != 2) continue; + + if (coordinates.length != 2) { + continue; + } + int regionX = Integer.parseInt(coordinates[0]); int regionZ = Integer.parseInt(coordinates[1]); RandomAccessFile input = new RandomAccessFile(file, "r"); + try { for (int index = 0; index < 1024; ++index) { if (input.readInt() != 0) { - chunks.add(new int[] {regionX * 32 + (index & 31), - regionZ * 32 + (index >> 5)}); + chunks.add(new int[] { regionX * 32 + (index & 31), regionZ * 32 + (index >> 5) }); } } } finally { input.close(); } } + Collections.sort(chunks, new Comparator() { @Override public int compare(int[] left, int[] right) { - int x = Integer.compare(left[0], right[0]); - return x == 0 ? Integer.compare(left[1], right[1]) : x; + int blockX = Integer.compare(left[0], right[0]); + + return blockX == 0 ? Integer.compare(left[1], right[1]) : blockX; } }); return chunks; } - private static void migrateChunks(WorldServer world, List coordinates, - int dimension) { + private static void migrateChunks(WorldServer world, List coordinates, int dimension) { ChunkProviderServer provider = world.getChunkProvider(); List batch = new ArrayList(MIGRATION_BATCH_SIZE); int processed = 0; + for (int[] coordinate : coordinates) { Chunk chunk = loadChunk(provider, coordinate[0], coordinate[1]); - require(chunk != null, "Could not load existing chunk " + coordinate[0] + "," + - coordinate[1] + " in dimension " + dimension); + + require(chunk != null, "Could not load existing chunk " + coordinate[0] + "," + coordinate[1] + + " in dimension " + dimension); batch.add(chunk); + if (batch.size() == MIGRATION_BATCH_SIZE) { flushAndUnload(provider, batch); } if (++processed % 1000 == 0) { - SkysGrassSlabs.logger.info("Sylvester migration dimension {}: {}/{} chunks", - dimension, processed, coordinates.size()); + SkysGrassSlabs.logger.info("Sylvester migration dimension {}: {}/{} chunks", dimension, processed, + coordinates.size()); } } + flushAndUnload(provider, batch); saveChunks(provider); - SkysGrassSlabs.logger.info("Sylvester migration dimension {} complete: {} chunks", - dimension, coordinates.size()); + SkysGrassSlabs.logger.info("Sylvester migration dimension {} complete: {} chunks", dimension, + coordinates.size()); } - private static void auditSavedChunks(File dimensionDirectory, List coordinates, - int dimension, int grassId, int dirtId, String grassItemId, String dirtItemId, - Map blockNames, LegacyContentCounts counts) throws Exception { + private static void auditSavedChunks(File dimensionDirectory, List coordinates, int dimension, int grassId, + int dirtId, String grassItemId, String dirtItemId, Map blockNames, + LegacyContentCounts counts) throws Exception { int processed = 0; + for (int[] coordinate : coordinates) { - DataInputStream input = RegionFileCache.getChunkInputStream(dimensionDirectory, - coordinate[0], coordinate[1]); - require(input != null, "Could not read existing chunk " + coordinate[0] + "," + - coordinate[1] + " in dimension " + dimension); + DataInputStream input = RegionFileCache.getChunkInputStream(dimensionDirectory, coordinate[0], + coordinate[1]); + + require(input != null, "Could not read existing chunk " + coordinate[0] + "," + coordinate[1] + + " in dimension " + dimension); + NBTTagCompound serialized; + try { serialized = CompressedStreamTools.read(input); } finally { input.close(); } - auditSavedBlocks(serialized, dimension, coordinate[0], coordinate[1], - grassId, dirtId, counts); + + auditSavedBlocks(serialized, dimension, coordinate[0], coordinate[1], grassId, dirtId, counts); + NBTTagCompound level = serialized.getCompoundTag("Level"); - countOwnedStacks(level.getTagList("TileEntities", 10), level, - grassItemId, dirtItemId, counts, - dimension, coordinate[0], coordinate[1]); - countOwnedStacks(level.getTagList("Entities", 10), null, - grassItemId, dirtItemId, counts, - dimension, coordinate[0], coordinate[1]); + + countOwnedStacks(level.getTagList("TileEntities", 10), level, grassItemId, dirtItemId, counts, dimension, + coordinate[0], coordinate[1]); + countOwnedStacks(level.getTagList("Entities", 10), null, grassItemId, dirtItemId, counts, dimension, + coordinate[0], coordinate[1]); + if (++processed % 1000 == 0) { - SkysGrassSlabs.logger.info("Sylvester saved content audit dimension {}: {}/{} chunks", - dimension, processed, coordinates.size()); + SkysGrassSlabs.logger.info("Sylvester saved content audit dimension {}: {}/{} chunks", dimension, + processed, coordinates.size()); } } + RegionFileCache.clearRegionFileReferences(); - SkysGrassSlabs.logger.info("Sylvester saved content audit dimension {} complete: {} chunks", - dimension, coordinates.size()); + SkysGrassSlabs.logger.info("Sylvester saved content audit dimension {} complete: {} chunks", dimension, + coordinates.size()); } - private static void countOwnedStacks(NBTTagList owners, NBTTagCompound chunkLevel, - String grassId, - String dirtId, LegacyContentCounts counts, int dimension, int chunkX, int chunkZ) { + private static void countOwnedStacks(NBTTagList owners, NBTTagCompound chunkLevel, String grassId, String dirtId, + LegacyContentCounts counts, int dimension, int chunkX, int chunkZ) { for (int index = 0; index < owners.tagCount(); ++index) { NBTTagCompound owner = owners.getCompoundTagAt(index); long dirtBefore = counts.dirtItems; + countStacks(owner, grassId, dirtId, counts, dimension, chunkX, chunkZ); + long dirtCount = counts.dirtItems - dirtBefore; + if (dirtCount > 0) { String ownerId = owner.getString("id"); - if (chunkLevel != null && owner.hasKey("x", 99) && owner.hasKey("y", 99) && - owner.hasKey("z", 99)) { - int blockId = savedBlockAt(chunkLevel, owner.getInteger("x"), - owner.getInteger("y"), owner.getInteger("z")); + + if (chunkLevel != null && owner.hasKey("x", 99) && owner.hasKey("y", 99) && owner.hasKey("z", 99)) { + int blockId = savedBlockAt(chunkLevel, owner.getInteger("x"), owner.getInteger("y"), + owner.getInteger("z")); String blockName = counts.blockNames.get(Integer.valueOf(blockId)); + ownerId += "@" + (blockName == null ? Integer.toString(blockId) : blockName); } + Long previous = counts.dirtItemsByOwner.get(ownerId); + counts.dirtItemsByOwner.put(ownerId, Long.valueOf((previous == null ? 0L : previous.longValue()) + dirtCount)); } } } - private static int savedBlockAt(NBTTagCompound level, int x, int y, int z) { + private static int savedBlockAt(NBTTagCompound level, int blockX, int blockY, int blockZ) { NBTTagList sections = level.getTagList("Sections", 10); - int sectionY = y >> 4; - int index = (y & 15) << 8 | (z & 15) << 4 | x & 15; + int sectionY = blockY >> 4; + int index = (blockY & 15) << 8 | (blockZ & 15) << 4 | blockX & 15; + for (int sectionIndex = 0; sectionIndex < sections.tagCount(); ++sectionIndex) { NBTTagCompound section = sections.getCompoundTagAt(sectionIndex); - if ((section.getByte("Y") & 255) != sectionY) continue; + + if ((section.getByte("Y") & 255) != sectionY) { + continue; + } + byte[] blocks = section.getByteArray("Blocks"); - if (blocks.length != 4096) return 0; - NibbleArray add = section.hasKey("Add", 7) - ? new NibbleArray(section.getByteArray("Add")) : null; - return (blocks[index] & 255) | (add == null ? 0 : add.getFromIndex(index) << 8); + + if (blocks.length != 4096) { + return 0; + } + + NibbleArray blockIdHighNibbles = section.hasKey("Add", 7) ? new NibbleArray(section.getByteArray("Add")) + : null; + + return (blocks[index] & 255) + | (blockIdHighNibbles == null ? 0 : blockIdHighNibbles.getFromIndex(index) << 8); } + return 0; } - private static int savedBlockId(File worldDirectory, String name, int fallback) - throws Exception { + private static int savedBlockId(File worldDirectory, String name, int fallback) throws Exception { NBTTagCompound root; - try (FileInputStream input = new FileInputStream( - new File(worldDirectory, "level.dat"))) { + + try (FileInputStream input = new FileInputStream(new File(worldDirectory, "level.dat"))) { root = CompressedStreamTools.readCompressed(input); } + NBTTagCompound data = root.getCompoundTag("Data"); NBTTagCompound fml = data.getCompoundTag("FML"); + if (!fml.hasKey("Registries", 10)) { fml = root.getCompoundTag("FML"); } + NBTTagCompound registries = fml.getCompoundTag("Registries"); - for (String registryName : new String[] {"minecraft:blocks", "fml:blocks"}) { + + for (String registryName : new String[] { "minecraft:blocks", "fml:blocks" }) { NBTTagList ids = registries.getCompoundTag(registryName).getTagList("ids", 10); + for (int index = 0; index < ids.tagCount(); ++index) { NBTTagCompound entry = ids.getCompoundTagAt(index); + if (name.equals(entry.getString("K"))) { return entry.getInteger("V"); } } } + return fallback; } private static Map savedBlockNames(File worldDirectory) throws Exception { NBTTagCompound root; + try (FileInputStream input = new FileInputStream(new File(worldDirectory, "level.dat"))) { root = CompressedStreamTools.readCompressed(input); } + NBTTagCompound data = root.getCompoundTag("Data"); NBTTagCompound fml = data.getCompoundTag("FML"); - if (!fml.hasKey("Registries", 10)) fml = root.getCompoundTag("FML"); + + if (!fml.hasKey("Registries", 10)) { + fml = root.getCompoundTag("FML"); + } + NBTTagCompound registries = fml.getCompoundTag("Registries"); Map names = new LinkedHashMap(); - for (String registryName : new String[] {"minecraft:blocks", "fml:blocks"}) { + + for (String registryName : new String[] { "minecraft:blocks", "fml:blocks" }) { NBTTagList ids = registries.getCompoundTag(registryName).getTagList("ids", 10); + for (int index = 0; index < ids.tagCount(); ++index) { NBTTagCompound entry = ids.getCompoundTagAt(index); + names.put(Integer.valueOf(entry.getInteger("V")), entry.getString("K")); } } + return names; } - private static void auditSavedBlocks(NBTTagCompound serialized, int dimension, int chunkX, - int chunkZ, int grassId, int dirtId, LegacyContentCounts counts) { + private static void auditSavedBlocks(NBTTagCompound serialized, int dimension, int chunkX, int chunkZ, int grassId, + int dirtId, LegacyContentCounts counts) { NBTTagList sections = serialized.getCompoundTag("Level").getTagList("Sections", 10); + for (int sectionIndex = 0; sectionIndex < sections.tagCount(); ++sectionIndex) { NBTTagCompound section = sections.getCompoundTagAt(sectionIndex); int ySection = section.getByte("Y") & 255; byte[] blocks = section.getByteArray("Blocks"); - if (blocks.length != 4096) continue; + + if (blocks.length != 4096) { + continue; + } + NibbleArray metadata = new NibbleArray(section.getByteArray("Data")); - NibbleArray add = section.hasKey("Add", 7) - ? new NibbleArray(section.getByteArray("Add")) : null; + NibbleArray blockIdHighNibbles = section.hasKey("Add", 7) ? new NibbleArray(section.getByteArray("Add")) + : null; + 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) continue; + int numericId = (blocks[index] & 255) + | (blockIdHighNibbles == null ? 0 : blockIdHighNibbles.getFromIndex(index) << 8); + + if (numericId != grassId && numericId != dirtId) { + continue; + } + int slabMetadata = metadata.getFromIndex(index) & 1; + if (numericId == grassId) { - if (slabMetadata == 0) ++counts.grassTop; - else ++counts.grassBottom; + if (slabMetadata == 0) { + ++counts.grassTop; + } else { + ++counts.grassBottom; + } if (counts.grassSample == null) { - counts.grassSample = samplePosition(dimension, chunkX, chunkZ, - ySection, index, slabMetadata); + counts.grassSample = samplePosition(dimension, chunkX, chunkZ, ySection, index, slabMetadata); } } else { - if (slabMetadata == 0) ++counts.dirtTop; - else ++counts.dirtBottom; + if (slabMetadata == 0) { + ++counts.dirtTop; + } else { + ++counts.dirtBottom; + } if (counts.dirtSample == null) { - counts.dirtSample = samplePosition(dimension, chunkX, chunkZ, - ySection, index, slabMetadata); + counts.dirtSample = samplePosition(dimension, chunkX, chunkZ, ySection, index, slabMetadata); } } } } } - private static LegacyBlockSample samplePosition(int dimension, int chunkX, int chunkZ, - int ySection, int index, int metadata) { - int x = (chunkX << 4) + (index & 15); - int y = (ySection << 4) + (index >> 8 & 15); - int z = (chunkZ << 4) + (index >> 4 & 15); - return new LegacyBlockSample(dimension, new BlockPos(x, y, z), metadata); + private static LegacyBlockSample samplePosition(int dimension, int chunkX, int chunkZ, int ySection, int index, + int metadata) { + int blockX = (chunkX << 4) + (index & 15); + int blockY = (ySection << 4) + (index >> 8 & 15); + int blockZ = (chunkZ << 4) + (index >> 4 & 15); + + return new LegacyBlockSample(dimension, new BlockPos(blockX, blockY, blockZ), metadata); } - private static void auditItems(Chunk chunk, String grassId, String dirtId, - LegacyContentCounts counts) { + private static void auditItems(Chunk chunk, String grassId, String dirtId, LegacyContentCounts counts) { for (TileEntity tileEntity : chunk.getTileEntityMap().values()) { countStacks(tileEntity.writeToNBT(new NBTTagCompound()), grassId, dirtId, counts, chunk.getWorld().provider.getDimension(), chunk.x, chunk.z); } for (ClassInheritanceMultiMap list : chunk.getEntityLists()) { for (Entity entity : list) { - countStacks(entity.serializeNBT(), grassId, dirtId, counts, - chunk.getWorld().provider.getDimension(), chunk.x, chunk.z); + countStacks(entity.serializeNBT(), grassId, dirtId, counts, chunk.getWorld().provider.getDimension(), + chunk.x, chunk.z); } } } - private static void countStacks(NBTBase tag, String grassId, - String dirtId, LegacyContentCounts counts, int dimension, int chunkX, int chunkZ) { + private static void countStacks(NBTBase tag, String grassId, String dirtId, LegacyContentCounts counts, + int dimension, int chunkX, int chunkZ) { if (tag instanceof NBTTagCompound) { NBTTagCompound compound = (NBTTagCompound) tag; + if (compound.hasKey("id", 8) && compound.hasKey("Count", 99)) { - String id = compound.getString("id"); + String registryId = compound.getString("id"); int count = compound.getByte("Count") & 255; - if (grassId.equals(id)) { + + if (grassId.equals(registryId)) { counts.grassItems += count; + if (counts.itemSample == null) { counts.itemSample = new LegacyChunkSample(dimension, chunkX, chunkZ); } } - if (dirtId.equals(id)) { + if (dirtId.equals(registryId)) { counts.dirtItems += count; + if (counts.itemSample == null) { counts.itemSample = new LegacyChunkSample(dimension, chunkX, chunkZ); } @@ -709,30 +814,38 @@ private static void countStacks(NBTBase tag, String grassId, } for (String key : new ArrayList(compound.getKeySet())) { NBTBase child = compound.getTag(key); + if (child != null) { countStacks(child, grassId, dirtId, counts, dimension, chunkX, chunkZ); } } } else if (tag instanceof NBTTagList) { NBTTagList list = (NBTTagList) tag; + for (int index = 0; index < list.tagCount(); ++index) { - countStacks(list.get(index), grassId, dirtId, counts, - dimension, chunkX, chunkZ); + countStacks(list.get(index), grassId, dirtId, counts, dimension, chunkX, chunkZ); } } } - private static Chunk loadChunk(ChunkProviderServer provider, int x, int z) { - return provider.loadChunk(x, z); + private static Chunk loadChunk(ChunkProviderServer provider, int blockX, int blockZ) { + return provider.loadChunk(blockX, blockZ); } private static void flushAndUnload(ChunkProviderServer provider, List chunks) { - if (chunks.isEmpty()) return; + if (chunks.isEmpty()) { + return; + } + saveChunks(provider); - for (Chunk chunk : chunks) provider.queueUnload(chunk); + + for (Chunk chunk : chunks) { + provider.queueUnload(chunk); + } for (int remaining = chunks.size(); remaining > 0; remaining -= 100) { tickProvider(provider); } + drainChunkWrites(); chunks.clear(); } @@ -756,73 +869,81 @@ private static void drainChunkWrites() { private static long unsupportedTotal(ModWorldState state) { long total = 0; - for (Long count : state.unsupported().values()) total += count; + + for (Long count : state.unsupported().values()) { + total += count; + } + return total; } private static void verifySylvesterContent(LegacyContentCounts counts, String owner) { require(counts.grassTop == 0 && counts.grassBottom == 1656276L, - owner + " grass slab total or orientation changed: " + - counts.grassTop + " top, " + counts.grassBottom + " bottom"); - require(counts.dirtTop == 12 && counts.dirtBottom == 2956L, - owner + " dirt slab total or orientation changed: " + - counts.dirtTop + " top, " + counts.dirtBottom + " bottom"); - require(counts.grassItems == 0 && counts.dirtItems == 7186L, - owner + " supported item total changed: " + counts.grassItems + - " grass, " + counts.dirtItems + " dirt"); - } - - private static void verifyForwardSylvesterContent(LegacyContentCounts counts, - boolean reload) { - SkysGrassSlabs.logger.info("Sylvester Sky dirt items by owning saved object: {}", - counts.dirtItemsByOwner); + owner + " grass slab total or orientation changed: " + counts.grassTop + " top, " + counts.grassBottom + + " bottom"); + require(counts.dirtTop == 12 && counts.dirtBottom == 2956L, owner + " dirt slab total or orientation changed: " + + counts.dirtTop + " top, " + counts.dirtBottom + " bottom"); + require(counts.grassItems == 0 && counts.dirtItems == 7186L, owner + " supported item total changed: " + + counts.grassItems + " grass, " + counts.dirtItems + " dirt"); + } + + private static void verifyForwardSylvesterContent(LegacyContentCounts counts, boolean reload) { + SkysGrassSlabs.logger.info("Sylvester Sky dirt items by owning saved object: {}", counts.dirtItemsByOwner); require(counts.grassTop == 0 && counts.grassBottom >= 1656276L, - "Sky grass slabs were lost or changed orientation during the forward upgrade: " + - counts.grassTop + " top, " + counts.grassBottom + " bottom"); + "Sky grass slabs were lost or changed orientation during the forward upgrade: " + counts.grassTop + + " top, " + counts.grassBottom + " bottom"); require(counts.dirtTop == 12 && counts.dirtBottom >= 2956L, - "Sky dirt slabs were lost or changed orientation during the forward upgrade: " + - counts.dirtTop + " top, " + counts.dirtBottom + " bottom"); + "Sky dirt slabs were lost or changed orientation during the forward upgrade: " + counts.dirtTop + + " top, " + counts.dirtBottom + " bottom"); + long expectedDirtItems = reload ? 5680L : 7186L; + require(counts.grassItems == 0 && counts.dirtItems == expectedDirtItems, - "Sky item stacks changed during the forward upgrade: " + counts.grassItems + - " grass, " + counts.dirtItems + " dirt"); + "Sky item stacks changed during the forward upgrade: " + counts.grassItems + " grass, " + + counts.dirtItems + " dirt"); } - private static void verifyLegacyContentSamples(MinecraftServer server, - LegacyContentCounts counts, boolean expectBuildingBricks) { - require(counts.grassSample != null && counts.dirtSample != null && - counts.itemSample != null, + private static void verifyLegacyContentSamples(MinecraftServer server, LegacyContentCounts counts, + boolean expectBuildingBricks) { + require(counts.grassSample != null && counts.dirtSample != null && counts.itemSample != null, "Saved legacy content did not provide live verification samples"); + Block grass = expectBuildingBricks ? legacyBlock(true) : ModBlocks.GRASS_SLAB; Block dirt = expectBuildingBricks ? legacyBlock(false) : ModBlocks.DIRT_SLAB; + verifyLegacyBlockSample(server, counts.grassSample, grass, "grass"); verifyLegacyBlockSample(server, counts.dirtSample, dirt, "dirt"); WorldServer world = server.getWorld(counts.itemSample.dimension); + require(world != null, "Legacy item sample dimension was unavailable"); - Chunk chunk = loadChunk(world.getChunkProvider(), counts.itemSample.chunkX, - counts.itemSample.chunkZ); + + Chunk chunk = loadChunk(world.getChunkProvider(), counts.itemSample.chunkX, counts.itemSample.chunkZ); + require(chunk != null, "Legacy item sample chunk could not be loaded"); + LegacyContentCounts live = new LegacyContentCounts(); + auditItems(chunk, grass.getRegistryName().toString(), dirt.getRegistryName().toString(), live); require(live.grassItems + live.dirtItems > 0, "Saved legacy item did not load with the selected registry identity"); } - private static void verifyLegacyBlockSample(MinecraftServer server, LegacyBlockSample sample, - Block expected, String type) { + private static void verifyLegacyBlockSample(MinecraftServer server, LegacyBlockSample sample, Block expected, + String type) { WorldServer world = server.getWorld(sample.dimension); + require(world != null, "Legacy " + type + " sample dimension was unavailable"); + IBlockState state = world.getBlockState(sample.position); - require(state.getBlock() == expected && - (expected.getMetaFromState(state) & 1) == sample.metadata, - "Saved legacy " + type + " sample at " + sample.position + - " did not load with its identity and orientation: expected " + - expected.getRegistryName() + " metadata " + sample.metadata + ", found " + state); + + require(state.getBlock() == expected && (expected.getMetaFromState(state) & 1) == sample.metadata, + "Saved legacy " + type + " sample at " + sample.position + + " did not load with its identity and orientation: expected " + expected.getRegistryName() + + " metadata " + sample.metadata + ", found " + state); } - private static void writeLegacyContentEvidence(Properties evidence, String prefix, - LegacyContentCounts counts) { + private static void writeLegacyContentEvidence(Properties evidence, String prefix, LegacyContentCounts counts) { evidence.setProperty(prefix + "_chunks", Integer.toString(counts.chunks)); evidence.setProperty(prefix + "_grass_blocks_top", Long.toString(counts.grassTop)); evidence.setProperty(prefix + "_grass_blocks_bottom", Long.toString(counts.grassBottom)); @@ -832,23 +953,20 @@ private static void writeLegacyContentEvidence(Properties evidence, String prefi evidence.setProperty(prefix + "_dirt_items", Long.toString(counts.dirtItems)); } - private static void verifyForwardSylvesterReloadEvidence(Properties evidence, - LegacyContentCounts counts) { - require(Long.toString(counts.grassTop).equals( - evidence.getProperty("forward_sylvester_grass_blocks_top")) && - Long.toString(counts.grassBottom).equals( - evidence.getProperty("forward_sylvester_grass_blocks_bottom")) && - Long.toString(counts.dirtTop).equals( - evidence.getProperty("forward_sylvester_dirt_blocks_top")) && - Long.toString(counts.dirtBottom).equals( - evidence.getProperty("forward_sylvester_dirt_blocks_bottom")), + private static void verifyForwardSylvesterReloadEvidence(Properties evidence, LegacyContentCounts counts) { + require(Long.toString(counts.grassTop).equals(evidence.getProperty("forward_sylvester_grass_blocks_top")) + && Long.toString(counts.grassBottom) + .equals(evidence.getProperty("forward_sylvester_grass_blocks_bottom")) + && Long.toString(counts.dirtTop).equals(evidence.getProperty("forward_sylvester_dirt_blocks_top")) + && Long.toString(counts.dirtBottom) + .equals(evidence.getProperty("forward_sylvester_dirt_blocks_bottom")), "Sylvester Sky blocks changed on the 1.12 reload"); - long firstItems = Long.parseLong( - evidence.getProperty("forward_sylvester_dirt_items")); - require(firstItems == 7186L && counts.dirtItems == 5680L && - firstItems - counts.dirtItems == 1506L, - "The known incomplete legacy-mod stack item boundary changed: first=" + - firstItems + ", reload=" + counts.dirtItems); + + long firstItems = Long.parseLong(evidence.getProperty("forward_sylvester_dirt_items")); + + require(firstItems == 7186L && counts.dirtItems == 5680L && firstItems - counts.dirtItems == 1506L, + "The known incomplete legacy-mod stack item boundary changed: first=" + firstItems + ", reload=" + + counts.dirtItems); } private static File migrationReport(WorldServer world) { @@ -856,27 +974,17 @@ private static File migrationReport(WorldServer world) { "skysgrassslabs-migration-report.txt"); } - private static void writeMigrationEvidence(Properties evidence, int chunks, - ModWorldState state) { + private static void writeMigrationEvidence(Properties evidence, int chunks, ModWorldState state) { evidence.setProperty("migration_chunks", Integer.toString(chunks)); - evidence.setProperty("migrated_grass_blocks", - Long.toString(state.migratedGrassBlocks())); - evidence.setProperty("migrated_grass_blocks_top", - Long.toString(state.migratedGrassBlocksTop())); - evidence.setProperty("migrated_grass_blocks_bottom", - Long.toString(state.migratedGrassBlocksBottom())); - evidence.setProperty("migrated_dirt_blocks", - Long.toString(state.migratedDirtBlocks())); - evidence.setProperty("migrated_dirt_blocks_top", - Long.toString(state.migratedDirtBlocksTop())); - evidence.setProperty("migrated_dirt_blocks_bottom", - Long.toString(state.migratedDirtBlocksBottom())); - evidence.setProperty("migrated_grass_items", - Long.toString(state.migratedGrassItems())); - evidence.setProperty("migrated_dirt_items", - Long.toString(state.migratedDirtItems())); - evidence.setProperty("unsupported_entries", - Integer.toString(state.unsupported().size())); + evidence.setProperty("migrated_grass_blocks", Long.toString(state.migratedGrassBlocks())); + evidence.setProperty("migrated_grass_blocks_top", Long.toString(state.migratedGrassBlocksTop())); + evidence.setProperty("migrated_grass_blocks_bottom", Long.toString(state.migratedGrassBlocksBottom())); + evidence.setProperty("migrated_dirt_blocks", Long.toString(state.migratedDirtBlocks())); + evidence.setProperty("migrated_dirt_blocks_top", Long.toString(state.migratedDirtBlocksTop())); + evidence.setProperty("migrated_dirt_blocks_bottom", Long.toString(state.migratedDirtBlocksBottom())); + evidence.setProperty("migrated_grass_items", Long.toString(state.migratedGrassItems())); + evidence.setProperty("migrated_dirt_items", Long.toString(state.migratedDirtItems())); + evidence.setProperty("unsupported_entries", Integer.toString(state.unsupported().size())); evidence.setProperty("unsupported_total", Long.toString(unsupportedTotal(state))); } @@ -890,41 +998,40 @@ private static void writeForwardStateEvidence(Properties evidence, ModWorldState } private static void verifyForwardStateEvidence(Properties evidence, ModWorldState state) { - require(Long.toString(state.migratedChunks()).equals( - evidence.getProperty("forward_state_chunks")) && - Long.toString(state.migratedGrassBlocks()).equals( - evidence.getProperty("forward_state_grass")) && - Long.toString(state.migratedDirtBlocks()).equals( - evidence.getProperty("forward_state_dirt")) && - Long.toString(state.migratedGrassItems()).equals( - evidence.getProperty("forward_state_grass_items")) && - Long.toString(state.migratedDirtItems()).equals( - evidence.getProperty("forward_state_dirt_items")) && - Long.toString(unsupportedTotal(state)).equals( - evidence.getProperty("forward_state_unsupported")), + require(Long.toString(state.migratedChunks()).equals(evidence.getProperty("forward_state_chunks")) + && Long.toString(state.migratedGrassBlocks()).equals(evidence.getProperty("forward_state_grass")) + && Long.toString(state.migratedDirtBlocks()).equals(evidence.getProperty("forward_state_dirt")) + && Long.toString(state.migratedGrassItems()).equals(evidence.getProperty("forward_state_grass_items")) + && Long.toString(state.migratedDirtItems()).equals(evidence.getProperty("forward_state_dirt_items")) + && Long.toString(unsupportedTotal(state)).equals(evidence.getProperty("forward_state_unsupported")), "Sylvester world state changed during its 1.12 reload"); } private static void seedLegacyCompatibilityFixture(WorldServer world) { BlockPos origin = legacyCompatibilityOrigin(world); + setLegacyBlock(world, origin, true, 0); setLegacyBlock(world, origin.east(), true, 1); setLegacyBlock(world, origin.east(2), false, 0); setLegacyBlock(world, origin.east(3), false, 1); BlockPos chestPos = origin.east(5); + world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 3); + IInventory chest = (IInventory) world.getTileEntity(chestPos); ItemStack grassStack = legacyStack(true, 2); NBTTagCompound retained = new NBTTagCompound(); + retained.setString("probe", "retained"); grassStack.setTagCompound(retained); chest.setInventorySlotContents(0, grassStack); chest.setInventorySlotContents(1, legacyStack(false, 3)); chest.markDirty(); - EntityItem dropped = new EntityItem(world, origin.getX() + 6.5D, - origin.getY() + 0.5D, origin.getZ() + 0.5D, legacyStack(true, 2)); + EntityItem dropped = new EntityItem(world, origin.getX() + 6.5D, origin.getY() + 0.5D, origin.getZ() + 0.5D, + legacyStack(true, 2)); + require(world.spawnEntity(dropped), "Could not seed legacy dropped slabs"); } @@ -932,57 +1039,57 @@ private static void verifyLegacyCompatibilityFixture(WorldServer world, boolean BlockPos origin = legacyCompatibilityOrigin(world); Block expectedGrass = replaced ? ModBlocks.GRASS_SLAB : legacyBlock(true); Block expectedDirt = replaced ? ModBlocks.DIRT_SLAB : legacyBlock(false); - require(world.getBlockState(origin).getBlock() == expectedGrass && - expectedGrass.getMetaFromState(world.getBlockState(origin)) == 0, + + require(world.getBlockState(origin).getBlock() == expectedGrass + && expectedGrass.getMetaFromState(world.getBlockState(origin)) == 0, "Top legacy grass slab did not match the selected replacement mode"); - require(world.getBlockState(origin.east()).getBlock() == expectedGrass && - expectedGrass.getMetaFromState(world.getBlockState(origin.east())) == 1, + require(world.getBlockState(origin.east()).getBlock() == expectedGrass + && expectedGrass.getMetaFromState(world.getBlockState(origin.east())) == 1, "Bottom legacy grass slab did not match the selected replacement mode"); - require(world.getBlockState(origin.east(2)).getBlock() == expectedDirt && - expectedDirt.getMetaFromState(world.getBlockState(origin.east(2))) == 0, + require(world.getBlockState(origin.east(2)).getBlock() == expectedDirt + && expectedDirt.getMetaFromState(world.getBlockState(origin.east(2))) == 0, "Top legacy dirt slab did not match the selected replacement mode"); - require(world.getBlockState(origin.east(3)).getBlock() == expectedDirt && - expectedDirt.getMetaFromState(world.getBlockState(origin.east(3))) == 1, + require(world.getBlockState(origin.east(3)).getBlock() == expectedDirt + && expectedDirt.getMetaFromState(world.getBlockState(origin.east(3))) == 1, "Bottom legacy dirt slab did not match the selected replacement mode"); IInventory chest = (IInventory) world.getTileEntity(origin.east(5)); + require(chest != null, "Legacy compatibility chest was not retained"); + Item expectedGrassItem = Item.getItemFromBlock(expectedGrass); Item expectedDirtItem = Item.getItemFromBlock(expectedDirt); - require(!chest.getStackInSlot(0).isEmpty() && - chest.getStackInSlot(0).getItem() == expectedGrassItem && - chest.getStackInSlot(0).getCount() == 2 && - "retained".equals(chest.getStackInSlot(0).getTagCompound().getString("probe")), + + require(!chest.getStackInSlot(0).isEmpty() && chest.getStackInSlot(0).getItem() == expectedGrassItem + && chest.getStackInSlot(0).getCount() == 2 + && "retained".equals(chest.getStackInSlot(0).getTagCompound().getString("probe")), "Legacy chest grass slabs or their NBT did not match the selected mode"); - require(!chest.getStackInSlot(1).isEmpty() && - chest.getStackInSlot(1).getItem() == expectedDirtItem && - chest.getStackInSlot(1).getCount() == 3, - "Legacy chest dirt slabs did not match the selected mode"); + require(!chest.getStackInSlot(1).isEmpty() && chest.getStackInSlot(1).getItem() == expectedDirtItem + && chest.getStackInSlot(1).getCount() == 3, "Legacy chest dirt slabs did not match the selected mode"); List drops = world.getEntitiesWithinAABB(EntityItem.class, new AxisAlignedBB(origin.east(6)).grow(2.0D)); - require(drops.size() == 1 && !drops.get(0).getItem().isEmpty() && - drops.get(0).getItem().getItem() == expectedGrassItem && - drops.get(0).getItem().getCount() == 2, + + require(drops.size() == 1 && !drops.get(0).getItem().isEmpty() + && drops.get(0).getItem().getItem() == expectedGrassItem && drops.get(0).getItem().getCount() == 2, "Dropped legacy slabs did not match the selected mode"); } - private static void verifyLegacyCompatibilityHooks(MinecraftServer server, - WorldServer world, boolean replaced) { + private static void verifyLegacyCompatibilityHooks(MinecraftServer server, WorldServer world, boolean replaced) { Block expectedGrass = replaced ? ModBlocks.GRASS_SLAB : legacyBlock(true); Block expectedDirt = replaced ? ModBlocks.DIRT_SLAB : legacyBlock(false); EntityPlayerMP player = player(server, world); + player.inventory.setInventorySlotContents(0, legacyStack(true, 2)); player.getInventoryEnderChest().setInventorySlotContents(0, legacyStack(false, 3)); FMLCommonHandler.instance().bus().post(new PlayerEvent.PlayerLoggedInEvent(player)); - require(player.inventory.getStackInSlot(0).getItem() == - Item.getItemFromBlock(expectedGrass), + require(player.inventory.getStackInSlot(0).getItem() == Item.getItemFromBlock(expectedGrass), "Player inventory did not match the selected replacement mode"); - require(player.getInventoryEnderChest().getStackInSlot(0).getItem() == - Item.getItemFromBlock(expectedDirt), + require(player.getInventoryEnderChest().getStackInSlot(0).getItem() == Item.getItemFromBlock(expectedDirt), "Ender chest did not match the selected replacement mode"); BlockPos origin = legacyCompatibilityOrigin(world).south(2); + verifyLegacyPlacementEvent(world, player, origin, true, 0, expectedGrass); verifyLegacyPlacementEvent(world, player, origin.east(), false, 1, expectedDirt); } @@ -990,246 +1097,272 @@ private static void verifyLegacyCompatibilityHooks(MinecraftServer server, private static void verifyLegacyBridgeRecipes(World world) { InventoryCrafting seedGrid = craftingGrid(2, 2); ItemStack legacyDirt = legacyStack(false, 1); + seedGrid.setInventorySlotContents(0, legacyDirt); seedGrid.setInventorySlotContents(1, new ItemStack(Items.WHEAT_SEEDS)); + boolean foundSeedBridge = false; int bridgeRecipes = 0; int grassOutputs = 0; + for (IRecipe recipe : CraftingManager.REGISTRY) { if (recipe.getClass().getName().endsWith("BuildingBricksDirtSlabRecipe")) { ++bridgeRecipes; } + ItemStack declaredOutput = recipe.getRecipeOutput(); - if (declaredOutput != null && declaredOutput.getItem() == - Item.getItemFromBlock(ModBlocks.GRASS_SLAB)) { + + if (declaredOutput != null && declaredOutput.getItem() == Item.getItemFromBlock(ModBlocks.GRASS_SLAB)) { ++grassOutputs; } if (recipe.matches(seedGrid, world)) { ItemStack output = recipe.getCraftingResult(seedGrid); - if (output != null && output.getItem() == - Item.getItemFromBlock(ModBlocks.GRASS_SLAB)) { + + if (output != null && output.getItem() == Item.getItemFromBlock(ModBlocks.GRASS_SLAB)) { foundSeedBridge = true; break; } } } + require(foundSeedBridge, - "BuildingBricks dirt slab and seed bridge recipe was not available; found " + - bridgeRecipes + " dynamic bridge recipes and " + grassOutputs + - " recipes declaring a grass slab output"); + "BuildingBricks dirt slab and seed bridge recipe was not available; found " + bridgeRecipes + + " dynamic bridge recipes and " + grassOutputs + " recipes declaring a grass slab output"); IRecipe turfRecipe = new TurfCuttingRecipe(); InventoryCrafting turfGrid = craftingGrid(2, 2); + turfGrid.setInventorySlotContents(0, legacyStack(true, 1)); turfGrid.setInventorySlotContents(1, new ItemStack(Items.IRON_SHOVEL)); - require(turfRecipe.matches(turfGrid, world), - "BuildingBricks grass slab turf bridge recipe was not available"); + require(turfRecipe.matches(turfGrid, world), "BuildingBricks grass slab turf bridge recipe was not available"); + NonNullList remaining = turfRecipe.getRemainingItems(turfGrid); - require(!remaining.get(0).isEmpty() && remaining.get(0).getItem() == - Item.getItemFromBlock(ModBlocks.DIRT_SLAB), + + require(!remaining.get(0).isEmpty() && remaining.get(0).getItem() == Item.getItemFromBlock(ModBlocks.DIRT_SLAB), "BuildingBricks turf recipe did not return a Sky dirt slab"); require(!remaining.get(1).isEmpty() && remaining.get(1).getItem() == Items.IRON_SHOVEL, "BuildingBricks turf recipe did not retain its shovel"); } - private static void verifyLegacyPlacementEvent(WorldServer world, EntityPlayerMP player, - BlockPos pos, boolean grass, int metadata, Block expected) { + private static void verifyLegacyPlacementEvent(WorldServer world, EntityPlayerMP player, BlockPos pos, + boolean grass, int metadata, Block expected) { Block legacy = legacyBlock(grass); + setLegacyBlock(world, pos, grass, metadata); player.setHeldItem(EnumHand.MAIN_HAND, legacyStack(grass, 1)); + BlockSnapshot snapshot = BlockSnapshot.getBlockSnapshot(world, pos); - MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.BlockEvent.PlaceEvent( - snapshot, Blocks.DIRT.getDefaultState(), player, EnumHand.MAIN_HAND)); - require(world.getBlockState(pos).getBlock() == expected && - expected.getMetaFromState(world.getBlockState(pos)) == metadata, + + MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.BlockEvent.PlaceEvent(snapshot, + Blocks.DIRT.getDefaultState(), player, EnumHand.MAIN_HAND)); + require(world.getBlockState(pos).getBlock() == expected + && expected.getMetaFromState(world.getBlockState(pos)) == metadata, "Placed legacy slab did not match the selected replacement mode"); } private static Block legacyBlock(boolean grass) { - Block block = Block.REGISTRY.getObject(grass ? BuildingBricksCompat.GRASS_SLAB_ID - : BuildingBricksCompat.DIRT_SLAB_ID); - require(block != null && block != Blocks.AIR, - "BuildingBricks did not register its supported slabs"); + Block block = Block.REGISTRY + .getObject(grass ? BuildingBricksCompat.GRASS_SLAB_ID : BuildingBricksCompat.DIRT_SLAB_ID); + + require(block != null && block != Blocks.AIR, "BuildingBricks did not register its supported slabs"); return block; } private static ItemStack legacyStack(boolean grass, int count) { ItemStack stack = new ItemStack(legacyBlock(grass), count, 0); + if (!grass) { try { Object material = legacyDirtMaterial(); - stack.getItem().getClass().getMethod("setMaterial", ItemStack.class, - material.getClass()).invoke(stack.getItem(), stack, material); + + stack.getItem().getClass().getMethod("setMaterial", ItemStack.class, material.getClass()) + .invoke(stack.getItem(), stack, material); } catch (ReflectiveOperationException exception) { - throw new IllegalStateException( - "Could not initialize the build-only BuildingBricks dirt item", exception); + throw new IllegalStateException("Could not initialize the build-only BuildingBricks dirt item", + exception); } } + return stack; } - private static void setLegacyBlock(World world, BlockPos pos, boolean grass, - int metadata) { + private static void setLegacyBlock(World world, BlockPos pos, boolean grass, int metadata) { Block legacy = legacyBlock(grass); IBlockState state = legacy.getStateFromMeta(metadata); + if (!grass) { Object material = legacyDirtMaterial(); + try { int materialMetadata = (Integer) material.getClass().getClassLoader() .loadClass("com.hea3ven.buildingbricks.core.materials.MaterialRegistry") .getMethod("getMeta", material.getClass()).invoke(null, material); - state = legacy.getStateForPlacement(world, pos, EnumFacing.UP, 0.5F, - metadata == 0 ? 0.75F : 0.25F, 0.5F, materialMetadata, null) - .withProperty(BlockSlab.HALF, metadata == 0 - ? BlockSlab.EnumBlockHalf.TOP - : BlockSlab.EnumBlockHalf.BOTTOM); + + state = legacy.getStateForPlacement(world, pos, EnumFacing.UP, 0.5F, metadata == 0 ? 0.75F : 0.25F, + 0.5F, materialMetadata, null).withProperty(BlockSlab.HALF, + metadata == 0 ? BlockSlab.EnumBlockHalf.TOP : BlockSlab.EnumBlockHalf.BOTTOM); } catch (ReflectiveOperationException exception) { - throw new IllegalStateException( - "Could not prepare the build-only BuildingBricks dirt state", exception); + throw new IllegalStateException("Could not prepare the build-only BuildingBricks dirt state", + exception); } } + world.setBlockState(pos, state, 3); + if (!grass && world.getTileEntity(pos) != null) { try { - Class materialType = Class.forName( - "com.hea3ven.buildingbricks.core.materials.Material"); + Class materialType = Class.forName("com.hea3ven.buildingbricks.core.materials.Material"); Object material = legacyDirtMaterial(); + world.getTileEntity(pos).getClass().getMethod("setMaterial", materialType) .invoke(world.getTileEntity(pos), material); world.getTileEntity(pos).markDirty(); } catch (ReflectiveOperationException exception) { - throw new IllegalStateException( - "Could not initialize the build-only BuildingBricks dirt fixture", exception); + throw new IllegalStateException("Could not initialize the build-only BuildingBricks dirt fixture", + exception); } } } private static Object legacyDirtMaterial() { try { - Class registry = Class.forName( - "com.hea3ven.buildingbricks.core.materials.MaterialRegistry"); - Object material = registry.getMethod("get", String.class) - .invoke(null, "minecraft:dirt"); + Class registry = Class.forName("com.hea3ven.buildingbricks.core.materials.MaterialRegistry"); + Object material = registry.getMethod("get", String.class).invoke(null, "minecraft:dirt"); + require(material != null, "BuildingBricks dirt material was not registered"); return material; } catch (ReflectiveOperationException exception) { - throw new IllegalStateException( - "Could not resolve the build-only BuildingBricks dirt material", exception); + throw new IllegalStateException("Could not resolve the build-only BuildingBricks dirt material", exception); } } private static BlockPos legacyCompatibilityOrigin(World world) { - return new BlockPos((world.getSpawnPoint().getX() & ~15) + 2, 220, - (world.getSpawnPoint().getZ() & ~15) + 2); + return new BlockPos((world.getSpawnPoint().getX() & ~15) + 2, 220, (world.getSpawnPoint().getZ() & ~15) + 2); } private static int verifyLegacyWorldgenTakeover(WorldServer world) { require(SkysGrassSlabsConfig.generateGrassSlabs(), "Sky world smoothing must be enabled for the takeover check"); + File legacyConfig = new File("config/BuildingBricks/general.cfg"); Configuration configuration = new Configuration(legacyConfig); + configuration.load(); require(!configuration.get("compat.vanilla", "generateGrassSlabs", true).getBoolean(), "BuildingBricks world generation was not disabled before new terrain generation"); - FakePlayer player = FakePlayerFactory.get(world, new GameProfile( - UUID.fromString("11dc17f3-dd5a-4979-a817-13b9215c0d51"), - "GrassSlabWorldgenProbe")); + FakePlayer player = FakePlayerFactory.get(world, + new GameProfile(UUID.fromString("11dc17f3-dd5a-4979-a817-13b9215c0d51"), "GrassSlabWorldgenProbe")); + world.getPlayerChunkMap().setPlayerViewRadius(4); + for (int attempt = 0; attempt < 8; ++attempt) { int regionX = 122 + attempt * 8; int regionZ = 0; File region = new File(new File(world.getSaveHandler().getWorldDirectory(), "region"), "r." + regionX + "." + regionZ + ".mca"); + require(!region.exists(), "Takeover candidate region already exists: " + region.getName()); + int centerChunkX = regionX * 32 + 16; int centerChunkZ = regionZ * 32 + 16; + player.setPosition(centerChunkX * 16 + 8, 80, centerChunkZ * 16 + 8); world.getPlayerChunkMap().addPlayer(player); + try { for (int tick = 0; tick < 200; ++tick) { world.getPlayerChunkMap().tick(); world.getChunkProvider().tick(); } + int[] slabs = countGeneratedSlabs(world, centerChunkX, centerChunkZ, 4); - require(slabs[1] == 0, - "BuildingBricks generated grass slabs after its generator was disabled"); - if (slabs[0] > 0) return slabs[0]; + + require(slabs[1] == 0, "BuildingBricks generated grass slabs after its generator was disabled"); + + if (slabs[0] > 0) { + return slabs[0]; + } } finally { world.getPlayerChunkMap().removePlayer(player); } } - throw new IllegalStateException( - "No eligible Sky grass slope was found in eight virgin player loaded regions"); + + throw new IllegalStateException("No eligible Sky grass slope was found in eight virgin player loaded regions"); } - private static int[] countGeneratedSlabs(WorldServer world, int centerChunkX, - int centerChunkZ, int radius) { + private static int[] countGeneratedSlabs(WorldServer world, int centerChunkX, int centerChunkZ, int radius) { int sky = 0; int legacy = 0; Block legacyGrass = legacyBlock(true); + for (int chunkZ = centerChunkZ - radius; chunkZ <= centerChunkZ + radius; ++chunkZ) { for (int chunkX = centerChunkX - radius; chunkX <= centerChunkX + radius; ++chunkX) { Chunk chunk = world.getChunkProvider().getLoadedChunk(chunkX, chunkZ); - if (chunk == null) continue; + + if (chunk == null) { + continue; + } for (ExtendedBlockStorage section : chunk.getBlockStorageArray()) { - if (section == null) continue; - for (int y = 0; y < 16; ++y) { - for (int z = 0; z < 16; ++z) { - for (int x = 0; x < 16; ++x) { - Block block = section.get(x, y, z).getBlock(); - if (block == ModBlocks.GRASS_SLAB) ++sky; - if (block == legacyGrass) ++legacy; + if (section == null) { + continue; + } + for (int blockY = 0; blockY < 16; ++blockY) { + for (int blockZ = 0; blockZ < 16; ++blockZ) { + for (int blockX = 0; blockX < 16; ++blockX) { + Block block = section.get(blockX, blockY, blockZ).getBlock(); + + if (block == ModBlocks.GRASS_SLAB) { + ++sky; + } + if (block == legacyGrass) { + ++legacy; + } } } } } } } - return new int[] {sky, legacy}; + + return new int[] { sky, legacy }; } private static int verifyGameplay(MinecraftServer server, WorldServer world) { int checks = 0; - require(SkysGrassSlabsConfig.generateGrassSlabs(), - "World smoothing is not enabled by default"); + + require(SkysGrassSlabsConfig.generateGrassSlabs(), "World smoothing is not enabled by default"); require(!SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Installed legacy slab replacement is not disabled by default"); checks += 2; - require("skysgrassslabs:dirt_slab".equals(blockId(ModBlocks.DIRT_SLAB)), - "Dirt slab registry ID changed"); - require("skysgrassslabs:grass_slab".equals(blockId(ModBlocks.GRASS_SLAB)), - "Grass slab registry ID changed"); - require("skysgrassslabs:path_slab".equals(blockId(ModBlocks.PATH_SLAB)), - "Path slab registry ID changed"); - require("skysgrassslabs:turf".equals(blockId(ModBlocks.TURF)), - "Turf registry ID changed"); + require("skysgrassslabs:dirt_slab".equals(blockId(ModBlocks.DIRT_SLAB)), "Dirt slab registry ID changed"); + require("skysgrassslabs:grass_slab".equals(blockId(ModBlocks.GRASS_SLAB)), "Grass slab registry ID changed"); + require("skysgrassslabs:path_slab".equals(blockId(ModBlocks.PATH_SLAB)), "Path slab registry ID changed"); + require("skysgrassslabs:turf".equals(blockId(ModBlocks.TURF)), "Turf registry ID changed"); checks += 4; - require(metadata(ModBlocks.DIRT_SLAB, defaultState(ModBlocks.DIRT_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP)) == 0, + require(metadata(ModBlocks.DIRT_SLAB, + defaultState(ModBlocks.DIRT_SLAB).withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP)) == 0, "Top slab metadata is not zero"); - require(metadata(ModBlocks.DIRT_SLAB, defaultState(ModBlocks.DIRT_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM)) == 1, + require(metadata(ModBlocks.DIRT_SLAB, + defaultState(ModBlocks.DIRT_SLAB).withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM)) == 1, "Bottom slab metadata is not one"); - require(stateFromMetadata(ModBlocks.DIRT_SLAB, 0).getValue(BlockSlab.HALF) - == BlockSlab.EnumBlockHalf.TOP, "Metadata zero did not restore top"); - require(stateFromMetadata(ModBlocks.DIRT_SLAB, 1).getValue(BlockSlab.HALF) - == BlockSlab.EnumBlockHalf.BOTTOM, "Metadata one did not restore bottom"); + require(stateFromMetadata(ModBlocks.DIRT_SLAB, 0).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP, + "Metadata zero did not restore top"); + require(stateFromMetadata(ModBlocks.DIRT_SLAB, 1).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM, + "Metadata one did not restore bottom"); checks += 4; - require(close(PathSlabBlock.BOTTOM_PATH_AABB.maxY, 7.0D / 16.0D), - "Bottom path geometry changed"); - require(close(PathSlabBlock.TOP_PATH_AABB.minY, 8.0D / 16.0D) && - close(PathSlabBlock.TOP_PATH_AABB.maxY, 15.0D / 16.0D), - "Top path geometry changed"); - require(close(TurfBlock.TURF_AABB.maxY, 1.0D / 16.0D), - "Turf geometry changed"); + require(close(PathSlabBlock.BOTTOM_PATH_AABB.maxY, 7.0D / 16.0D), "Bottom path geometry changed"); + require(close(PathSlabBlock.TOP_PATH_AABB.minY, 8.0D / 16.0D) + && close(PathSlabBlock.TOP_PATH_AABB.maxY, 15.0D / 16.0D), "Top path geometry changed"); + require(close(TurfBlock.TURF_AABB.maxY, 1.0D / 16.0D), "Turf geometry changed"); checks += 3; BlockPos origin = new BlockPos((world.getSpawnPoint().getX() & ~15) + 6, 160, (world.getSpawnPoint().getZ() & ~15) + 6); + world.getChunk(origin); verifyTurfDoesNotConnectFences(world, origin.add(0, 0, -4)); checks += 2; @@ -1237,56 +1370,64 @@ private static int verifyGameplay(MinecraftServer server, WorldServer world) { EntityPlayerMP player = player(server, world); Item dirtItem = Item.getItemFromBlock(ModBlocks.DIRT_SLAB); ItemStack dirtStack = new ItemStack(ModBlocks.DIRT_SLAB); + world.setBlockState(origin, defaultState(ModBlocks.DIRT_SLAB), 3); - EnumActionResult combined = useItem(dirtItem, dirtStack, player, world, origin, - EnumHand.MAIN_HAND, EnumFacing.UP, 0.5F, 1.0F, 0.5F); - require(combined == EnumActionResult.SUCCESS && world.getBlockState(origin).getBlock() == Blocks.DIRT && - dirtStack.isEmpty(), "Dirt slabs did not normalize to vanilla dirt"); + + EnumActionResult combined = useItem(dirtItem, dirtStack, player, world, origin, EnumHand.MAIN_HAND, + EnumFacing.UP, 0.5F, 1.0F, 0.5F); + + require(combined == EnumActionResult.SUCCESS && world.getBlockState(origin).getBlock() == Blocks.DIRT + && dirtStack.isEmpty(), "Dirt slabs did not normalize to vanilla dirt"); checks++; Item grassItem = Item.getItemFromBlock(ModBlocks.GRASS_SLAB); ItemStack grassStack = new ItemStack(ModBlocks.GRASS_SLAB); - world.setBlockState(origin, defaultState(ModBlocks.GRASS_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); - combined = useItem(grassItem, grassStack, player, world, origin, EnumHand.MAIN_HAND, - EnumFacing.DOWN, 0.5F, 0.0F, 0.5F); + + world.setBlockState(origin, + defaultState(ModBlocks.GRASS_SLAB).withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); + combined = useItem(grassItem, grassStack, player, world, origin, EnumHand.MAIN_HAND, EnumFacing.DOWN, 0.5F, + 0.0F, 0.5F); require(combined == EnumActionResult.SUCCESS && world.getBlockState(origin).getBlock() == Blocks.GRASS, "Grass slabs did not normalize to vanilla grass"); checks++; Item pathItem = Item.getItemFromBlock(ModBlocks.PATH_SLAB); ItemStack pathStack = new ItemStack(ModBlocks.PATH_SLAB); + world.setBlockState(origin, defaultState(ModBlocks.PATH_SLAB), 3); - combined = useItem(pathItem, pathStack, player, world, origin, EnumHand.MAIN_HAND, - EnumFacing.UP, 0.5F, 1.0F, 0.5F); + combined = useItem(pathItem, pathStack, player, world, origin, EnumHand.MAIN_HAND, EnumFacing.UP, 0.5F, 1.0F, + 0.5F); require(combined == EnumActionResult.SUCCESS && world.getBlockState(origin).getBlock() == Blocks.GRASS_PATH, "Path slabs did not normalize to vanilla grass path"); checks++; Item turfItem = Item.getItemFromBlock(ModBlocks.TURF); + for (BlockSlab.EnumBlockHalf half : BlockSlab.EnumBlockHalf.values()) { ItemStack turfStack = new ItemStack(ModBlocks.TURF); - world.setBlockState(origin, defaultState(ModBlocks.DIRT_SLAB) - .withProperty(BlockSlab.HALF, half), 3); - EnumActionResult result = useItem(turfItem, turfStack, player, world, origin, - EnumHand.MAIN_HAND, EnumFacing.UP, 0.5F, 1.0F, 0.5F); - require(result == EnumActionResult.SUCCESS && turfStack.isEmpty() && - world.getBlockState(origin).getBlock() == ModBlocks.GRASS_SLAB && - world.getBlockState(origin).getValue(BlockSlab.HALF) == half, + + world.setBlockState(origin, defaultState(ModBlocks.DIRT_SLAB).withProperty(BlockSlab.HALF, half), 3); + + EnumActionResult result = useItem(turfItem, turfStack, player, world, origin, EnumHand.MAIN_HAND, + EnumFacing.UP, 0.5F, 1.0F, 0.5F); + + require(result == EnumActionResult.SUCCESS && turfStack.isEmpty() + && world.getBlockState(origin).getBlock() == ModBlocks.GRASS_SLAB + && world.getBlockState(origin).getValue(BlockSlab.HALF) == half, "Turf did not preserve dirt-slab orientation " + half); checks++; } BlockPos support = origin.add(0, 0, 2); + world.setBlockState(support, Blocks.DIRT.getDefaultState(), 3); - require(canPlace(ModBlocks.TURF, world, support.up()), - "Turf rejected a full support block"); + require(canPlace(ModBlocks.TURF, world, support.up()), "Turf rejected a full support block"); world.setBlockState(support, defaultState(ModBlocks.DIRT_SLAB), 3); - require(!canPlace(ModBlocks.TURF, world, support.up()), - "Turf accepted a partial support block"); + require(!canPlace(ModBlocks.TURF, world, support.up()), "Turf accepted a partial support block"); checks += 2; BlockPos turfPos = support.up(); + world.setBlockState(support, Blocks.STONE.getDefaultState(), 3); world.setBlockState(turfPos, defaultState(ModBlocks.TURF), 3); tick(ModBlocks.TURF, world, turfPos, defaultState(ModBlocks.TURF), new Random(1)); @@ -1306,8 +1447,7 @@ private static int verifyGameplay(MinecraftServer server, WorldServer world) { world.setBlockState(turfPos, defaultState(ModBlocks.TURF), 3); world.setBlockState(support, Blocks.GRASS.getDefaultState(), 2); tick(ModBlocks.TURF, world, turfPos, world.getBlockState(turfPos), new Random(3)); - require(world.isAirBlock(turfPos), - "Pre-existing turf above grass did not retain invalid-support removal"); + require(world.isAirBlock(turfPos), "Pre-existing turf above grass did not retain invalid-support removal"); checks++; verifySpreading(world, origin.add(4, 0, 0)); @@ -1322,26 +1462,25 @@ private static int verifyGameplay(MinecraftServer server, WorldServer world) { checks += 8; IPlantable sapling = (IPlantable) Blocks.SAPLING; - IBlockState topGrass = defaultState(ModBlocks.GRASS_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP); + IBlockState topGrass = defaultState(ModBlocks.GRASS_SLAB).withProperty(BlockSlab.HALF, + BlockSlab.EnumBlockHalf.TOP); IBlockState bottomGrass = defaultState(ModBlocks.GRASS_SLAB); - require(((Block) ModBlocks.GRASS_SLAB).canSustainPlant(topGrass, world, origin, - EnumFacing.UP, sapling), "Top grass slab rejected a plant"); - require(!((Block) ModBlocks.GRASS_SLAB).canSustainPlant(bottomGrass, world, origin, - EnumFacing.UP, sapling), "Bottom grass slab accepted a plant"); - require(((IGrowable) ModBlocks.GRASS_SLAB).canGrow(world, origin, topGrass, false) && - !((IGrowable) ModBlocks.GRASS_SLAB).canGrow(world, origin, bottomGrass, false), + + require(((Block) ModBlocks.GRASS_SLAB).canSustainPlant(topGrass, world, origin, EnumFacing.UP, sapling), + "Top grass slab rejected a plant"); + require(!((Block) ModBlocks.GRASS_SLAB).canSustainPlant(bottomGrass, world, origin, EnumFacing.UP, sapling), + "Bottom grass slab accepted a plant"); + require(((IGrowable) ModBlocks.GRASS_SLAB).canGrow(world, origin, topGrass, false) + && !((IGrowable) ModBlocks.GRASS_SLAB).canGrow(world, origin, bottomGrass, false), "Grass slab bonemeal orientation rule changed"); checks += 3; require(canSilkHarvest(ModBlocks.GRASS_SLAB, world, origin, topGrass, player), "Grass slab cannot be Silk Touched"); - require(!canSilkHarvest(ModBlocks.PATH_SLAB, world, origin, - defaultState(ModBlocks.PATH_SLAB), player), + require(!canSilkHarvest(ModBlocks.PATH_SLAB, world, origin, defaultState(ModBlocks.PATH_SLAB), player), "Path slab unexpectedly Silk Touches itself"); - require(isOnlyDrop(drops(ModBlocks.GRASS_SLAB, world, origin, topGrass), ModBlocks.DIRT_SLAB) && - isOnlyDrop(drops(ModBlocks.PATH_SLAB, world, origin, - defaultState(ModBlocks.PATH_SLAB)), ModBlocks.DIRT_SLAB), + require(isOnlyDrop(drops(ModBlocks.GRASS_SLAB, world, origin, topGrass), ModBlocks.DIRT_SLAB) && isOnlyDrop( + drops(ModBlocks.PATH_SLAB, world, origin, defaultState(ModBlocks.PATH_SLAB)), ModBlocks.DIRT_SLAB), "Grass or path slab ordinary drops changed"); checks += 3; return checks; @@ -1349,190 +1488,192 @@ private static int verifyGameplay(MinecraftServer server, WorldServer world) { private static void verifyTurfDoesNotConnectFences(World world, BlockPos fencePos) { clearBox(world, fencePos.add(-1, -1, -1), fencePos.add(2, 2, 1)); + BlockPos turfPos = fencePos.east(); + world.setBlockState(fencePos.down(), Blocks.STONE.getDefaultState(), 3); world.setBlockState(turfPos.down(), Blocks.DIRT.getDefaultState(), 3); world.setBlockState(fencePos, Blocks.OAK_FENCE.getDefaultState(), 3); world.setBlockState(turfPos, defaultState(ModBlocks.TURF), 3); IBlockState turf = world.getBlockState(turfPos); - require(turf.getBlockFaceShape(world, turfPos, EnumFacing.WEST) - == BlockFaceShape.UNDEFINED, "Turf exposes a solid horizontal face"); - IBlockState fence = Blocks.OAK_FENCE.getActualState( - world.getBlockState(fencePos), world, fencePos); + + require(turf.getBlockFaceShape(world, turfPos, EnumFacing.WEST) == BlockFaceShape.UNDEFINED, + "Turf exposes a solid horizontal face"); + + IBlockState fence = Blocks.OAK_FENCE.getActualState(world.getBlockState(fencePos), world, fencePos); + require(!fence.getValue(BlockFence.EAST), "Fence connected to adjacent turf"); } private static void verifySpreading(World world, BlockPos origin) { clearBox(world, origin.add(-2, -2, -2), origin.add(3, 3, 3)); world.setBlockState(origin, defaultState(ModBlocks.GRASS_SLAB), 3); + BlockPos vanillaTarget = origin.east(); + world.setBlockState(vanillaTarget, Blocks.DIRT.getDefaultState(), 3); - tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), - new SequenceRandom(2, 3, 1)); + tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new SequenceRandom(2, 3, 1)); require(world.getBlockState(vanillaTarget).getBlock() == Blocks.GRASS, "Grass slab did not spread to vanilla dirt"); for (BlockSlab.EnumBlockHalf half : BlockSlab.EnumBlockHalf.values()) { - world.setBlockState(vanillaTarget, defaultState(ModBlocks.DIRT_SLAB) - .withProperty(BlockSlab.HALF, half), 3); - tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), - new SequenceRandom(2, 3, 1)); - require(world.getBlockState(vanillaTarget).getBlock() == ModBlocks.GRASS_SLAB && - world.getBlockState(vanillaTarget).getValue(BlockSlab.HALF) == half, + world.setBlockState(vanillaTarget, defaultState(ModBlocks.DIRT_SLAB).withProperty(BlockSlab.HALF, half), 3); + tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new SequenceRandom(2, 3, 1)); + require(world.getBlockState(vanillaTarget).getBlock() == ModBlocks.GRASS_SLAB + && world.getBlockState(vanillaTarget).getValue(BlockSlab.HALF) == half, "Grass slab spread changed orientation " + half); } BlockPos slabTarget = origin.south(); + world.setBlockState(slabTarget, defaultState(ModBlocks.DIRT_SLAB), 3); world.setBlockState(slabTarget.west(), Blocks.GRASS.getDefaultState(), 3); - tick(ModBlocks.DIRT_SLAB, world, slabTarget, world.getBlockState(slabTarget), - new SequenceRandom(0, 1, 1)); + tick(ModBlocks.DIRT_SLAB, world, slabTarget, world.getBlockState(slabTarget), new SequenceRandom(0, 1, 1)); require(world.getBlockState(slabTarget).getBlock() == ModBlocks.GRASS_SLAB, "Vanilla grass did not spread to a dirt slab"); BlockPos turfSource = origin.north(); + world.setBlockState(turfSource.down(), Blocks.DIRT.getDefaultState(), 3); world.setBlockState(turfSource, defaultState(ModBlocks.TURF), 3); + BlockPos turfTarget = turfSource.east(); + world.setBlockState(turfTarget, Blocks.DIRT.getDefaultState(), 3); - tick(ModBlocks.TURF, world, turfSource, world.getBlockState(turfSource), - new SequenceRandom(2, 3, 1)); - require(world.getBlockState(turfTarget).getBlock() == Blocks.GRASS, - "Turf did not spread to vanilla dirt"); + tick(ModBlocks.TURF, world, turfSource, world.getBlockState(turfSource), new SequenceRandom(2, 3, 1)); + require(world.getBlockState(turfTarget).getBlock() == Blocks.GRASS, "Turf did not spread to vanilla dirt"); world.setBlockState(turfSource.down(), Blocks.DIRT.getDefaultState(), 3); - tick(ModBlocks.TURF, world, turfSource, world.getBlockState(turfSource), - new SequenceRandom(1, 2, 1)); - require(world.getBlockState(turfSource.down()).getBlock() == Blocks.DIRT, - "Turf converted its own support"); + tick(ModBlocks.TURF, world, turfSource, world.getBlockState(turfSource), new SequenceRandom(1, 2, 1)); + require(world.getBlockState(turfSource.down()).getBlock() == Blocks.DIRT, "Turf converted its own support"); BlockPos grassSupport = origin.down(); + world.setBlockState(grassSupport, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(origin, defaultState(ModBlocks.GRASS_SLAB), 3); - tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), - new SequenceRandom(1, 2, 1)); - require(world.getBlockState(grassSupport).getBlock() == Blocks.DIRT, - "Grass slab converted its own support"); + tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new SequenceRandom(1, 2, 1)); + require(world.getBlockState(grassSupport).getBlock() == Blocks.DIRT, "Grass slab converted its own support"); BlockPos coveredTarget = origin.add(2, 0, 0); + world.setBlockState(coveredTarget, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(coveredTarget.up(), defaultState(ModBlocks.TURF), 3); - tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), - new SequenceRandom(0, 3, 1)); - require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT, - "Mod grass spread beneath turf"); + tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new SequenceRandom(0, 3, 1)); + require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT, "Mod grass spread beneath turf"); world.setBlockState(coveredTarget, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(coveredTarget.up(), defaultState(ModBlocks.GRASS_SLAB), 3); - tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), - new SequenceRandom(0, 3, 1)); - require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT, - "Mod grass spread beneath a grass slab"); + tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new SequenceRandom(0, 3, 1)); + require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT, "Mod grass spread beneath a grass slab"); BlockPos vanillaSource = coveredTarget.west(); + world.setBlockState(vanillaSource, Blocks.GRASS.getDefaultState(), 3); world.setBlockState(coveredTarget, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(coveredTarget.up(), defaultState(ModBlocks.TURF), 3); - tick(Blocks.GRASS, world, vanillaSource, world.getBlockState(vanillaSource), - new SequenceRandom(2, 3, 1)); - require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT && - world.getBlockState(coveredTarget.up()).getBlock() == ModBlocks.TURF, + tick(Blocks.GRASS, world, vanillaSource, world.getBlockState(vanillaSource), new SequenceRandom(2, 3, 1)); + require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT + && world.getBlockState(coveredTarget.up()).getBlock() == ModBlocks.TURF, "Vanilla grass persisted beneath turf after its neighbour update"); world.setBlockState(coveredTarget, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(coveredTarget.up(), defaultState(ModBlocks.GRASS_SLAB), 3); - tick(Blocks.GRASS, world, vanillaSource, world.getBlockState(vanillaSource), - new SequenceRandom(2, 3, 1)); - require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT && - world.getBlockState(coveredTarget.up()).getBlock() == ModBlocks.GRASS_SLAB, + tick(Blocks.GRASS, world, vanillaSource, world.getBlockState(vanillaSource), new SequenceRandom(2, 3, 1)); + require(world.getBlockState(coveredTarget).getBlock() == Blocks.DIRT + && world.getBlockState(coveredTarget.up()).getBlock() == ModBlocks.GRASS_SLAB, "Vanilla grass persisted beneath a grass slab after its neighbour update"); - world.setBlockState(origin, defaultState(ModBlocks.GRASS_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); + world.setBlockState(origin, + defaultState(ModBlocks.GRASS_SLAB).withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); world.setBlockState(origin.up(), Blocks.STONE.getDefaultState(), 3); tick(ModBlocks.GRASS_SLAB, world, origin, world.getBlockState(origin), new Random(4)); - require(world.getBlockState(origin).getBlock() == ModBlocks.DIRT_SLAB && - world.getBlockState(origin).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP, + require(world.getBlockState(origin).getBlock() == ModBlocks.DIRT_SLAB + && world.getBlockState(origin).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP, "Covered grass slab did not decay while preserving orientation"); - world.setBlockState(origin, defaultState(ModBlocks.PATH_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); + world.setBlockState(origin, + defaultState(ModBlocks.PATH_SLAB).withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP), 3); neighborChanged(ModBlocks.PATH_SLAB, world.getBlockState(origin), world, origin, Blocks.STONE); - require(world.getBlockState(origin).getBlock() == ModBlocks.DIRT_SLAB && - world.getBlockState(origin).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP, + require(world.getBlockState(origin).getBlock() == ModBlocks.DIRT_SLAB + && world.getBlockState(origin).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP, "Covered path slab did not decay while preserving orientation"); } - private static void verifyGrassCoveringAndSnow(World world, EntityPlayerMP player, - BlockPos origin) { + private static void verifyGrassCoveringAndSnow(World world, EntityPlayerMP player, BlockPos origin) { clearBox(world, origin.add(-2, -2, -2), origin.add(10, 4, 4)); + Item grassItem = Item.getItemFromBlock(ModBlocks.GRASS_SLAB); BlockPos bottomSupport = origin; + world.setBlockState(bottomSupport, Blocks.GRASS.getDefaultState(), 3); + ItemStack bottomStack = new ItemStack(ModBlocks.GRASS_SLAB); - EnumActionResult placed = useItem(grassItem, bottomStack, player, world, bottomSupport, - EnumHand.MAIN_HAND, EnumFacing.UP, 0.5F, 1.0F, 0.5F); - require(placed == EnumActionResult.SUCCESS && - world.getBlockState(bottomSupport.up()).getBlock() == ModBlocks.GRASS_SLAB && - world.getBlockState(bottomSupport.up()).getValue(BlockSlab.HALF) == - BlockSlab.EnumBlockHalf.BOTTOM && - world.getBlockState(bottomSupport).getBlock() == Blocks.DIRT, + EnumActionResult placed = useItem(grassItem, bottomStack, player, world, bottomSupport, EnumHand.MAIN_HAND, + EnumFacing.UP, 0.5F, 1.0F, 0.5F); + + require(placed == EnumActionResult.SUCCESS + && world.getBlockState(bottomSupport.up()).getBlock() == ModBlocks.GRASS_SLAB + && world.getBlockState(bottomSupport.up()).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM + && world.getBlockState(bottomSupport).getBlock() == Blocks.DIRT, "Bottom grass slab placement did not dirtify vanilla grass support"); BlockPos topSupport = origin.east(3); BlockPos topTarget = topSupport.up(); + world.setBlockState(topSupport, Blocks.GRASS.getDefaultState(), 3); world.setBlockState(topTarget.west(), Blocks.STONE.getDefaultState(), 3); + ItemStack topStack = new ItemStack(ModBlocks.GRASS_SLAB); - placed = useItem(grassItem, topStack, player, world, topTarget.west(), - EnumHand.MAIN_HAND, EnumFacing.EAST, 1.0F, 0.75F, 0.5F); - require(placed == EnumActionResult.SUCCESS && - world.getBlockState(topTarget).getBlock() == ModBlocks.GRASS_SLAB && - world.getBlockState(topTarget).getValue(BlockSlab.HALF) == - BlockSlab.EnumBlockHalf.TOP && - world.getBlockState(topSupport).getBlock() == Blocks.DIRT, + + placed = useItem(grassItem, topStack, player, world, topTarget.west(), EnumHand.MAIN_HAND, EnumFacing.EAST, + 1.0F, 0.75F, 0.5F); + require(placed == EnumActionResult.SUCCESS && world.getBlockState(topTarget).getBlock() == ModBlocks.GRASS_SLAB + && world.getBlockState(topTarget).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP + && world.getBlockState(topSupport).getBlock() == Blocks.DIRT, "Top grass slab placement did not dirtify vanilla grass support"); BlockPos repair = origin.east(6); + world.setBlockState(repair, Blocks.DIRT.getDefaultState(), 3); world.setBlockState(repair.up(), defaultState(ModBlocks.GRASS_SLAB), 3); world.setBlockState(repair, Blocks.GRASS.getDefaultState(), 2); - tick(ModBlocks.GRASS_SLAB, world, repair.up(), world.getBlockState(repair.up()), - new Random(9)); + tick(ModBlocks.GRASS_SLAB, world, repair.up(), world.getBlockState(repair.up()), new Random(9)); require(world.getBlockState(repair).getBlock() == Blocks.DIRT, "Grass slab random tick did not repair a grass support"); world.setBlockState(repair, Blocks.GRASS.getDefaultState(), 2); - neighborChanged(ModBlocks.GRASS_SLAB, world.getBlockState(repair.up()), world, - repair.up(), Blocks.GRASS); + neighborChanged(ModBlocks.GRASS_SLAB, world.getBlockState(repair.up()), world, repair.up(), Blocks.GRASS); require(world.getBlockState(repair).getBlock() == Blocks.DIRT, "Grass slab neighbour update did not repair a grass support"); - Block[] snowyBlocks = {ModBlocks.GRASS_SLAB, ModBlocks.DIRT_SLAB}; + Block[] snowyBlocks = { ModBlocks.GRASS_SLAB, ModBlocks.DIRT_SLAB }; + for (int index = 0; index < snowyBlocks.length; ++index) { Block block = snowyBlocks[index]; + for (BlockSlab.EnumBlockHalf half : BlockSlab.EnumBlockHalf.values()) { BlockPos pos = origin.add(index * 3, 0, 3 + half.ordinal()); IBlockState state = defaultState(block).withProperty(BlockSlab.HALF, half); + world.setBlockState(pos.down(), Blocks.STONE.getDefaultState(), 3); world.setBlockState(pos, state, 3); + BlockPos snow = half == BlockSlab.EnumBlockHalf.TOP ? pos.east() : pos.up(); + world.setBlockState(snow, Blocks.SNOW.getDefaultState(), 2); + IBlockState actual = actualState(block, state, world, pos); - boolean snowy = block == ModBlocks.GRASS_SLAB - ? actual.getValue(BlockGrass.SNOWY) + boolean snowy = block == ModBlocks.GRASS_SLAB ? actual.getValue(BlockGrass.SNOWY) : actual.getValue(BlockDirt.SNOWY); - require(snowy && metadata(block, actual) == - (half == BlockSlab.EnumBlockHalf.TOP ? 0 : 1), + + require(snowy && metadata(block, actual) == (half == BlockSlab.EnumBlockHalf.TOP ? 0 : 1), "Visual snow state or metadata changed for " + blockId(block) + " " + half); world.setBlockToAir(snow); actual = actualState(block, state, world, pos); - snowy = block == ModBlocks.GRASS_SLAB - ? actual.getValue(BlockGrass.SNOWY) + snowy = block == ModBlocks.GRASS_SLAB ? actual.getValue(BlockGrass.SNOWY) : actual.getValue(BlockDirt.SNOWY); - require(!snowy, "Visual snow state persisted after snow removal for " + - blockId(block) + " " + half); + require(!snowy, "Visual snow state persisted after snow removal for " + blockId(block) + " " + half); } } } @@ -1541,64 +1682,78 @@ private static void verifyTurfEating(WorldServer world, BlockPos turfPos) { clearBox(world, turfPos.add(-2, -2, -2), turfPos.add(2, 3, 2)); world.setBlockState(turfPos.down(), Blocks.DIRT.getDefaultState(), 3); world.setBlockState(turfPos, defaultState(ModBlocks.TURF), 3); + EntitySheep sheep = new EntitySheep(world); + sheep.setPosition(turfPos.getX() + 0.5D, turfPos.getY(), turfPos.getZ() + 0.5D); + CommonEvents events = new CommonEvents(); + events.addTurfEatingTask(new EntityJoinWorldEvent(sheep, world)); events.addTurfEatingTask(new EntityJoinWorldEvent(sheep, world)); - require(countTurfEatingTasks(sheep) == 1, - "Sheep received a duplicate turf-eating task"); + require(countTurfEatingTasks(sheep) == 1, "Sheep received a duplicate turf-eating task"); TurfEatingAI task = turfEatingTask(sheep); + sheep.setGrowingAge(0); sheep.setSheared(true); + int drops = entityItems(world, turfPos); + startAiTask(task); - require(task.getEatingTimer() == 40, - "Turf eating did not begin with the vanilla animation timer"); + require(task.getEatingTimer() == 40, "Turf eating did not begin with the vanilla animation timer"); runEatingTicks(task); require(world.isAirBlock(turfPos), "Sheep did not destroy turf"); require(!sheep.getSheared(), "Adult sheep did not regrow wool after eating turf"); - require(entityItems(world, turfPos) == drops, - "Sheep eating turf created an item drop"); + require(entityItems(world, turfPos) == drops, "Sheep eating turf created an item drop"); boolean mobGriefing = world.getGameRules().getBoolean("mobGriefing"); + try { world.getGameRules().setOrCreateGameRule("mobGriefing", "false"); world.setBlockState(turfPos.down(), Blocks.DIRT.getDefaultState(), 3); world.setBlockState(turfPos, defaultState(ModBlocks.TURF), 3); sheep.setGrowingAge(-1000); + int age = sheep.getGrowingAge(); + startAiTask(task); runEatingTicks(task); - require(world.getBlockState(turfPos).getBlock() == ModBlocks.TURF, - "mobGriefing=false did not retain turf"); - require(sheep.getGrowingAge() > age, - "mobGriefing=false did not retain the vanilla child-growth bonus"); + require(world.getBlockState(turfPos).getBlock() == ModBlocks.TURF, "mobGriefing=false did not retain turf"); + require(sheep.getGrowingAge() > age, "mobGriefing=false did not retain the vanilla child-growth bonus"); } finally { - world.getGameRules().setOrCreateGameRule("mobGriefing", - Boolean.toString(mobGriefing)); + world.getGameRules().setOrCreateGameRule("mobGriefing", Boolean.toString(mobGriefing)); sheep.setGrowingAge(0); } } private static int countTurfEatingTasks(EntitySheep sheep) { int count = 0; + for (EntityAITasks.EntityAITaskEntry entry : sheep.tasks.taskEntries) { - if (entry.action instanceof TurfEatingAI) ++count; + if (entry.action instanceof TurfEatingAI) { + ++count; + } } + return count; } private static TurfEatingAI turfEatingTask(EntitySheep sheep) { for (EntityAITasks.EntityAITaskEntry entry : sheep.tasks.taskEntries) { - if (entry.action instanceof TurfEatingAI) return (TurfEatingAI) entry.action; + if (entry.action instanceof TurfEatingAI) { + return (TurfEatingAI) entry.action; + } } + throw new IllegalStateException("Sheep has no turf-eating task"); } private static void runEatingTicks(TurfEatingAI task) { - for (int tick = 0; tick < 36; ++tick) updateAiTask(task); + for (int tick = 0; tick < 36; ++tick) { + updateAiTask(task); + } + require(task.getEatingTimer() == 4, "Turf eating timer did not reach action tick"); } @@ -1612,24 +1767,32 @@ private static void updateAiTask(EntityAIBase task) { private static int entityItems(World world, BlockPos pos) { AxisAlignedBB area = new AxisAlignedBB(pos.add(-1, -1, -1), pos.add(2, 2, 2)); + return world.getEntitiesWithinAABB(EntityItem.class, area).size(); } private static void verifyFlattening(World world, EntityPlayerMP player, BlockPos pos) { CommonEvents events = new CommonEvents(); + world.setBlockToAir(pos.up()); - IBlockState grassTop = defaultState(ModBlocks.GRASS_SLAB) - .withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.TOP); + + IBlockState grassTop = defaultState(ModBlocks.GRASS_SLAB).withProperty(BlockSlab.HALF, + BlockSlab.EnumBlockHalf.TOP); + world.setBlockState(pos, grassTop, 3); + ItemStack shovel = new ItemStack(Items.IRON_SHOVEL); + shovel.setItemDamage(3); player.setHeldItem(EnumHand.MAIN_HAND, shovel); - PlayerInteractEvent.RightClickBlock event = new PlayerInteractEvent.RightClickBlock(player, - EnumHand.MAIN_HAND, pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + + PlayerInteractEvent.RightClickBlock event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, + pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + events.flattenSlab(event); - require(event.isCanceled() && world.getBlockState(pos).getBlock() == ModBlocks.PATH_SLAB && - world.getBlockState(pos).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP && - shovel.getItemDamage() == 4, "Vanilla shovel flattening contract changed"); + require(event.isCanceled() && world.getBlockState(pos).getBlock() == ModBlocks.PATH_SLAB + && world.getBlockState(pos).getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.TOP + && shovel.getItemDamage() == 4, "Vanilla shovel flattening contract changed"); Item compatibleShovel = new Item() { @Override @@ -1638,67 +1801,74 @@ public Set getToolClasses(ItemStack stack) { } }.setMaxDamage(64); ItemStack compatible = new ItemStack(compatibleShovel); + world.setBlockState(pos, defaultState(ModBlocks.DIRT_SLAB), 3); player.setHeldItem(EnumHand.MAIN_HAND, compatible); - event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, - pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, pos, EnumFacing.UP, + new Vec3d(0.5D, 1.0D, 0.5D)); events.flattenSlab(event); - require(world.getBlockState(pos).getBlock() == ModBlocks.PATH_SLAB && - compatible.getItemDamage() == 1, "Forge-compatible shovel was not accepted"); + require(world.getBlockState(pos).getBlock() == ModBlocks.PATH_SLAB && compatible.getItemDamage() == 1, + "Forge-compatible shovel was not accepted"); world.setBlockState(pos, defaultState(ModBlocks.GRASS_SLAB), 3); + ItemStack downward = new ItemStack(Items.IRON_SHOVEL); + player.setHeldItem(EnumHand.MAIN_HAND, downward); - event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, - pos, EnumFacing.DOWN, new Vec3d(0.5D, 0.0D, 0.5D)); + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, pos, EnumFacing.DOWN, + new Vec3d(0.5D, 0.0D, 0.5D)); events.flattenSlab(event); - require(!event.isCanceled() && world.getBlockState(pos).getBlock() == ModBlocks.GRASS_SLAB && - downward.getItemDamage() == 0, "Downward shovel face was incorrectly flattened"); + require(!event.isCanceled() && world.getBlockState(pos).getBlock() == ModBlocks.GRASS_SLAB + && downward.getItemDamage() == 0, "Downward shovel face was incorrectly flattened"); world.setBlockState(pos.up(), Blocks.STONE.getDefaultState(), 3); - event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, - pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, pos, EnumFacing.UP, + new Vec3d(0.5D, 1.0D, 0.5D)); events.flattenSlab(event); require(!event.isCanceled() && world.getBlockState(pos).getBlock() == ModBlocks.GRASS_SLAB, "Blocked shovel interaction was not refused"); world.setBlockToAir(pos.up()); - require(isOnlyDrop(drops(ModBlocks.PATH_SLAB, world, pos, - defaultState(ModBlocks.PATH_SLAB)), ModBlocks.DIRT_SLAB), - "Path slab did not drop a dirt slab"); + require(isOnlyDrop(drops(ModBlocks.PATH_SLAB, world, pos, defaultState(ModBlocks.PATH_SLAB)), + ModBlocks.DIRT_SLAB), "Path slab did not drop a dirt slab"); } private static void verifyRecipes(World world) { - String[] recipePaths = {"dirt_slab", "grass_slab", "grass_block_from_seeds", - "grass_slab_from_seeds", "turf"}; + String[] recipePaths = { "dirt_slab", "grass_slab", "grass_block_from_seeds", "grass_slab_from_seeds", "turf" }; + for (String path : recipePaths) { - require(CraftingManager.REGISTRY.getObject( - new ResourceLocation(SkysGrassSlabs.MOD_ID, path)) != null, + require(CraftingManager.REGISTRY.getObject(new ResourceLocation(SkysGrassSlabs.MOD_ID, path)) != null, "Registered recipe is missing: " + path); } - IRecipe registeredTurf = CraftingManager.REGISTRY.getObject( - new ResourceLocation(SkysGrassSlabs.MOD_ID, "turf")); - require(registeredTurf instanceof TurfCuttingRecipe && !registeredTurf.isDynamic() && - registeredTurf.canFit(2, 1) && !registeredTurf.canFit(1, 1), + + IRecipe registeredTurf = CraftingManager.REGISTRY + .getObject(new ResourceLocation(SkysGrassSlabs.MOD_ID, "turf")); + + require(registeredTurf instanceof TurfCuttingRecipe && !registeredTurf.isDynamic() + && registeredTurf.canFit(2, 1) && !registeredTurf.canFit(1, 1), "Registered turf recipe is not available to the 1.12 recipe book"); + NonNullList ingredients = registeredTurf.getIngredients(); - require(ingredients.size() == 2 && - ingredients.get(0).apply(new ItemStack(Blocks.GRASS)) && - ingredients.get(0).apply(new ItemStack(ModBlocks.GRASS_SLAB)) && - ingredients.get(1).apply(new ItemStack(Items.WOODEN_SHOVEL)), + + require(ingredients.size() == 2 && ingredients.get(0).apply(new ItemStack(Blocks.GRASS)) + && ingredients.get(0).apply(new ItemStack(ModBlocks.GRASS_SLAB)) + && ingredients.get(1).apply(new ItemStack(Items.WOODEN_SHOVEL)), "Turf recipe book ingredients are incomplete"); + Advancement turfAdvancement = world.getMinecraftServer().getAdvancementManager() - .getAdvancement(new ResourceLocation( - SkysGrassSlabs.MOD_ID, "recipes/turf")); - require(turfAdvancement != null, - "Turf recipe discovery advancement was not loaded"); + .getAdvancement(new ResourceLocation(SkysGrassSlabs.MOD_ID, "recipes/turf")); + + require(turfAdvancement != null, "Turf recipe discovery advancement was not loaded"); IRecipe recipe = new TurfCuttingRecipe(); InventoryCrafting grid = craftingGrid(2, 2); ItemStack shovel = new ItemStack(Items.DIAMOND_SHOVEL); + shovel.setItemDamage(17); shovel.addEnchantment(Enchantments.UNBREAKING, 2); + NBTTagCompound toolData = new NBTTagCompound(); + toolData.setString("probe", "retained"); shovel.setTagCompound(toolData); grid.setInventorySlotContents(0, new ItemStack(ModBlocks.GRASS_SLAB)); @@ -1706,13 +1876,15 @@ private static void verifyRecipes(World world) { require(recipe.matches(grid, world), "Grass slab turf recipe did not match in 2x2 crafting"); require(recipe.getCraftingResult(grid).getItem() == Item.getItemFromBlock(ModBlocks.TURF), "Turf recipe output changed"); + NonNullList remaining = recipe.getRemainingItems(grid); + require(!remaining.get(0).isEmpty() && remaining.get(0).getItem() == Item.getItemFromBlock(ModBlocks.DIRT_SLAB), "Grass slab did not return a dirt slab"); - require(!remaining.get(1).isEmpty() && remaining.get(1).getItem() == shovel.getItem() && - remaining.get(1).getItemDamage() == 17 && remaining.get(1).getCount() == 1 && - remaining.get(1).hasTagCompound() && "retained".equals( - remaining.get(1).getTagCompound().getString("probe")), + require(!remaining.get(1).isEmpty() && remaining.get(1).getItem() == shovel.getItem() + && remaining.get(1).getItemDamage() == 17 && remaining.get(1).getCount() == 1 + && remaining.get(1).hasTagCompound() + && "retained".equals(remaining.get(1).getTagCompound().getString("probe")), "Turf recipe changed the shovel remainder"); grid.clear(); @@ -1735,25 +1907,28 @@ private static int verifyWorldgen(WorldServer world) { int checks = 0; int chunkX = (world.getSpawnPoint().getX() >> 4) + 4; int chunkZ = (world.getSpawnPoint().getZ() >> 4) + 4; + world.getChunk(chunkX, chunkZ); + GrassSlabSmoothingHandler handler = new GrassSlabSmoothingHandler(); BlockPos chunkStart = new BlockPos(chunkX << 4, 0, chunkZ << 4); BlockPos lower = new BlockPos((chunkX << 4) + 5, 180, (chunkZ << 4) + 5); + prepareGrassSurface(world, lower); prepareGrassSurface(world, lower.east().up()); handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(1), chunkStart)); - require(world.getBlockState(lower.up()).getBlock() == ModBlocks.GRASS_SLAB && - metadata(ModBlocks.GRASS_SLAB, world.getBlockState(lower.up())) == 1 && - world.getBlockState(lower).getBlock() == Blocks.DIRT, + require(world.getBlockState(lower.up()).getBlock() == ModBlocks.GRASS_SLAB + && metadata(ModBlocks.GRASS_SLAB, world.getBlockState(lower.up())) == 1 + && world.getBlockState(lower).getBlock() == Blocks.DIRT, "Interior slope did not gain a bottom grass slab over dirt"); checks++; handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(2), chunkStart)); - require(world.getBlockState(lower.up()).getBlock() == ModBlocks.GRASS_SLAB, - "Smoothing was not idempotent"); + require(world.getBlockState(lower.up()).getBlock() == ModBlocks.GRASS_SLAB, "Smoothing was not idempotent"); checks++; BlockPos flat = new BlockPos((chunkX << 4) + 8, 184, (chunkZ << 4) + 8); + prepareGrassSurface(world, flat); prepareGrassSurface(world, flat.east()); handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(3), chunkStart)); @@ -1761,6 +1936,7 @@ private static int verifyWorldgen(WorldServer world) { checks++; BlockPos occupied = new BlockPos((chunkX << 4) + 10, 188, (chunkZ << 4) + 10); + prepareGrassSurface(world, occupied); prepareGrassSurface(world, occupied.east().up()); world.setBlockState(occupied.up(), Blocks.CHEST.getDefaultState(), 3); @@ -1770,6 +1946,7 @@ private static int verifyWorldgen(WorldServer world) { checks++; BlockPos replaceable = new BlockPos((chunkX << 4) + 6, 190, (chunkZ << 4) + 12); + prepareGrassSurface(world, replaceable); prepareGrassSurface(world, replaceable.east().up()); world.setBlockState(replaceable.up(), Blocks.TALLGRASS.getDefaultState(), 3); @@ -1779,17 +1956,20 @@ private static int verifyWorldgen(WorldServer world) { checks++; BlockPos wet = new BlockPos((chunkX << 4) + 12, 192, (chunkZ << 4) + 12); + prepareGrassSurface(world, wet); prepareGrassSurface(world, wet.east().up()); world.setBlockState(wet.up(), Blocks.WATER.getDefaultState(), 3); handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(6), chunkStart)); - require(world.getBlockState(wet.up()).getBlock() == Blocks.WATER, - "Smoothing overwrote a fluid"); + require(world.getBlockState(wet.up()).getBlock() == Blocks.WATER, "Smoothing overwrote a fluid"); checks++; int eastChunkX = chunkX + 1; + world.getChunk(eastChunkX, chunkZ); + BlockPos eastLower = new BlockPos((chunkX << 4) + 15, 196, (chunkZ << 4) + 7); + prepareGrassSurface(world, eastLower); prepareGrassSurface(world, eastLower.east().up()); handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(7), chunkStart)); @@ -1798,12 +1978,13 @@ private static int verifyWorldgen(WorldServer world) { checks++; world.getChunk(chunkX - 1, chunkZ); + BlockPos westLower = new BlockPos(chunkX << 4, 200, (chunkZ << 4) + 11); + prepareGrassSurface(world, westLower); prepareGrassSurface(world, westLower.west().up()); handler.beforeDecoration(new DecorateBiomeEvent.Pre(world, new Random(8), chunkStart)); - require(world.isAirBlock(westLower.up()), - "West border comparison escaped the owning-chunk rule"); + require(world.isAirBlock(westLower.up()), "West border comparison escaped the owning-chunk rule"); checks++; return checks; } @@ -1818,8 +1999,8 @@ private static void prepareGrassSurface(World world, BlockPos grass) { private static EntityPlayerMP player(MinecraftServer server, WorldServer world) { PlayerInteractionManager manager = new PlayerInteractionManager(world); EntityPlayerMP player = new EntityPlayerMP(server, world, - new GameProfile(UUID.fromString("c7c9288e-73d8-4f36-afc4-a377f1cac742"), - "GrassSlabProbe"), manager); + new GameProfile(UUID.fromString("c7c9288e-73d8-4f36-afc4-a377f1cac742"), "GrassSlabProbe"), manager); + player.capabilities.allowEdit = true; return player; } @@ -1834,12 +2015,12 @@ public boolean canInteractWith(EntityPlayer player) { } private static boolean isOnlyDrop(List drops, net.minecraft.block.Block expected) { - return drops.size() == 1 && drops.get(0).getItem() == Item.getItemFromBlock(expected) && - drops.get(0).getCount() == 1; + return drops.size() == 1 && drops.get(0).getItem() == Item.getItemFromBlock(expected) + && drops.get(0).getCount() == 1; } - // Keep inherited vanilla calls owned by vanilla types in bytecode so the - // same probe can be reobfuscated for the packaged-runtime gate. + // Keep inherited vanilla calls owned by vanilla types in bytecode so the same probe can be reobfuscated for the + // packaged-runtime gate. private static String blockId(Block block) { return block.getRegistryName().toString(); } @@ -1856,8 +2037,7 @@ private static IBlockState stateFromMetadata(Block block, int metadata) { return block.getStateFromMeta(metadata); } - private static IBlockState actualState(Block block, IBlockState state, - World world, BlockPos pos) { + private static IBlockState actualState(Block block, IBlockState state, World world, BlockPos pos) { return block.getActualState(state, world, pos); } @@ -1865,38 +2045,34 @@ private static boolean canPlace(Block block, World world, BlockPos pos) { return block.canPlaceBlockAt(world, pos); } - private static void tick(Block block, World world, BlockPos pos, - IBlockState state, Random random) { + private static void tick(Block block, World world, BlockPos pos, IBlockState state, Random random) { block.updateTick(world, pos, state, random); } - private static void neighborChanged(Block block, IBlockState state, World world, - BlockPos pos, Block changedBlock) { + private static void neighborChanged(Block block, IBlockState state, World world, BlockPos pos, Block changedBlock) { block.neighborChanged(state, world, pos, changedBlock, pos.up()); } - private static boolean canSilkHarvest(Block block, World world, BlockPos pos, - IBlockState state, EntityPlayer player) { + private static boolean canSilkHarvest(Block block, World world, BlockPos pos, IBlockState state, + EntityPlayer player) { return block.canSilkHarvest(world, pos, state, player); } - private static List drops(Block block, World world, BlockPos pos, - IBlockState state) { + private static List drops(Block block, World world, BlockPos pos, IBlockState state) { return block.getDrops(world, pos, state, 0); } - private static EnumActionResult useItem(Item item, ItemStack stack, - EntityPlayer player, World world, BlockPos pos, EnumHand hand, - EnumFacing facing, float hitX, float hitY, float hitZ) { + private static EnumActionResult useItem(Item item, ItemStack stack, EntityPlayer player, World world, BlockPos pos, + EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { player.setHeldItem(hand, stack); return item.onItemUse(player, world, pos, hand, facing, hitX, hitY, hitZ); } private static void clearBox(World world, BlockPos from, BlockPos to) { - for (int y = from.getY(); y <= to.getY(); ++y) { - for (int z = from.getZ(); z <= to.getZ(); ++z) { - for (int x = from.getX(); x <= to.getX(); ++x) { - world.setBlockToAir(new BlockPos(x, y, z)); + for (int blockY = from.getY(); blockY <= to.getY(); ++blockY) { + for (int blockZ = from.getZ(); blockZ <= to.getZ(); ++blockZ) { + for (int blockX = from.getX(); blockX <= to.getX(); ++blockX) { + world.setBlockToAir(new BlockPos(blockX, blockY, blockZ)); } } } @@ -1904,19 +2080,23 @@ private static void clearBox(World world, BlockPos from, BlockPos to) { private static Properties load(File marker) throws Exception { Properties values = new Properties(); + if (marker.isFile()) { FileInputStream input = new FileInputStream(marker); + try { values.load(input); } finally { input.close(); } } + return values; } private static void store(File marker, Properties values) throws Exception { FileOutputStream output = new FileOutputStream(marker); + try { values.store(output, "Sky's Grass Slabs build-only integration evidence"); } finally { @@ -1929,7 +2109,9 @@ private static boolean close(double actual, double expected) { } private static void require(boolean condition, String message) { - if (!condition) throw new IllegalStateException(message); + if (!condition) { + throw new IllegalStateException(message); + } } private static final class SequenceRandom extends Random { @@ -1943,6 +2125,7 @@ private SequenceRandom(int... values) { @Override public int nextInt(int bound) { int value = values[index++ % values.length]; + return Math.floorMod(value, bound); } } @@ -1955,10 +2138,8 @@ private static final class LegacyContentCounts { private long dirtBottom; private long grassItems; private long dirtItems; - private final Map dirtItemsByOwner = - new LinkedHashMap(); - private final Map blockNames = - new LinkedHashMap(); + private final Map dirtItemsByOwner = new LinkedHashMap(); + private final Map blockNames = new LinkedHashMap(); private LegacyBlockSample grassSample; private LegacyBlockSample dirtSample; private LegacyChunkSample itemSample; diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java index 966d10f..2ad17bb 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java +++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java @@ -17,53 +17,67 @@ /** Seeds a released-version save and verifies it with the next version. */ final class ReleasedUpgradeChecks { - private 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}; + + Block[] blocks = { ModBlocks.DIRT_SLAB, ModBlocks.GRASS_SLAB, ModBlocks.PATH_SLAB, ModBlocks.TURF }; int states = 0; + for (Block block : blocks) { for (int metadata = 0; metadata < (block == ModBlocks.TURF ? 1 : 2); ++metadata) { BlockPos pos = new BlockPos(160 + states++, 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 identity and orientation: " + block.getRegistryName() - + " / " + metadata); + "Released block identity and orientation: " + block.getRegistryName() + " / " + metadata); } } } BlockPos chestPos = new BlockPos(160, 101, 164); - if (seed) world.setBlockState(chestPos, Blocks.CHEST.getDefaultState(), 2); + + 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 < blocks.length; ++slot) { - ItemStack expected = new ItemStack(blocks[slot], 11 + slot, - blocks[slot] == ModBlocks.TURF ? 0 : 1); + ItemStack expected = new ItemStack(blocks[slot], 11 + slot, blocks[slot] == ModBlocks.TURF ? 0 : 1); NBTTagCompound data = new NBTTagCompound(); + data.setString("upgrade_note", "Saved custom item data"); data.setInteger("fixture_slot", slot); expected.setTagCompound(data); - if (seed) chest.setInventorySlotContents(slot, expected); - else require(ItemStack.areItemStacksEqual(chest.getStackInSlot(slot), expected), - "Released container item identity, count, metadata and NBT: " + slot); + + if (seed) { + chest.setInventorySlotContents(slot, expected); + } else { + require(ItemStack.areItemStacksEqual(chest.getStackInSlot(slot), expected), + "Released container item identity, count, metadata and NBT: " + slot); + } } if (seed) { ItemStack stack = new ItemStack(ModBlocks.GRASS_SLAB, 7, 1); NBTTagCompound data = new NBTTagCompound(); + data.setString("upgrade_entity", "Retained"); stack.setTagCompound(data); - require(world.spawnEntity(new EntityItem(world, 164.5, 101, 164.5, stack)), - "Released dropped item seeded"); + require(world.spawnEntity(new EntityItem(world, 164.5, 101, 164.5, stack)), "Released dropped item seeded"); + ModWorldState state = ModWorldState.get(world); + state.recordChunk(); state.recordGrassBlocks(13, 0); state.recordDirtBlocks(17, 1); @@ -71,35 +85,44 @@ else require(ItemStack.areItemStacksEqual(chest.getStackInSlot(slot), expected), state.recordDirtItems(23); } else { boolean found = false; - for (ClassInheritanceMultiMap section : - world.getChunk(chestPos).getEntityLists()) { + + for (ClassInheritanceMultiMap section : world.getChunk(chestPos).getEntityLists()) { for (Entity entity : section) { - if (!(entity instanceof EntityItem)) continue; + if (!(entity instanceof EntityItem)) { + continue; + } + ItemStack stack = ((EntityItem) entity).getItem(); - if (!stack.hasTagCompound() - || !stack.getTagCompound().hasKey("upgrade_entity")) continue; - require(stack.getItem() == Item.getItemFromBlock(ModBlocks.GRASS_SLAB) - && stack.getCount() == 7 && stack.getMetadata() == 1 - && "Retained".equals(stack.getTagCompound() - .getString("upgrade_entity")), + + if (!stack.hasTagCompound() || !stack.getTagCompound().hasKey("upgrade_entity")) { + continue; + } + + require(stack.getItem() == Item.getItemFromBlock(ModBlocks.GRASS_SLAB) && stack.getCount() == 7 + && stack.getMetadata() == 1 + && "Retained".equals(stack.getTagCompound().getString("upgrade_entity")), "Released dropped item identity, count, metadata and NBT"); found = true; } } + require(found, "Released dropped item survived"); + ModWorldState state = ModWorldState.get(world); + require(ModWorldState.SCHEMA_VERSION == 1 && state.migratedChunks() == 1 - && state.migratedGrassBlocks() == 13 - && state.migratedGrassBlocksTop() == 13 - && state.migratedDirtBlocks() == 17 - && state.migratedDirtBlocksBottom() == 17 - && state.migratedGrassItems() == 19 && state.migratedDirtItems() == 23, + && 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=" + states); } private static void require(boolean value, String message) { - if (!value) throw new AssertionError(message); + if (!value) { + throw new AssertionError(message); + } } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java index 78380a9..c024844 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java @@ -18,8 +18,11 @@ import zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi; import zone.moddev.mc.skysgrassslabs.init.ModBlocks; -@Mod(modid = SkysGrassSlabs.MOD_ID, name = SkysGrassSlabs.NAME, - version = SkysGrassSlabs.VERSION, dependencies = "before:buildingbricks") +@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"; @@ -39,12 +42,16 @@ public void preInit(FMLPreInitializationEvent event) { 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 (!GrassSlabsApi.hasSmoothingOwner() && SkysGrassSlabsConfig.isSmoothingActive()) { MinecraftForge.EVENT_BUS.register(new GrassSlabSmoothingHandler()); } + proxy.preInit(); } @@ -54,6 +61,7 @@ public void init(FMLInitializationEvent event) { GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getStateFromMeta(metadata), ModBlocks.GRASS_SLAB.getStateFromMeta(metadata)); } + proxy.init(); } 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 0498b17..b4c59b5 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java @@ -16,91 +16,184 @@ public final class GrassSlabsApi { 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 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() { } + private GrassSlabsApi() { + } /** 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) + 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(); } + public static boolean hasSmoothingOwner() { + return smoothingOwner != null; + } + + public static boolean ownerAllowsGeneration() { + return smoothingOwner == null || ownerEnabled; + } + + /** Respect the grass setting and legacy-generator arbitration, even when another mod owns smoothing. */ + 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)) + 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)) + } + + 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); + } + + DIRT.put(dirt, grass); + GRASS.put(grass, dirt); + } + + public static boolean isGrassForm(IBlockState state) { + return GRASS.containsKey(canonical(state)); } - 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)) + 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); + } + + 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)); } - 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; + 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); + + /** Register a source-aware rule without allowing two rules to claim the same owner. */ + 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))) + + /** Register the dirt support left beneath a partial grass piece. */ + 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)); + } + + SUPPORTS.put(canonical(grass), canonical(dirt)); + } + + public static IBlockState dirtFor(IBlockState grass) { + return GRASS.get(canonical(grass)); + } + + public static void configurationLoaded() { + configurationLoaded = true; + } + + /** Close registration before worlds start ticking, so grass lookups remain stable. */ + public static void freeze() { + frozen = true; } - 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); } + /** Use shared grass rules while retaining the registered shape when grass decays to dirt. */ public static void tickGrass(World world, BlockPos pos, IBlockState state, Random random) { - if (world.isRemote) return; + 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 if(isSpreadingGrassForm(state)) GrassSpread.spreadFrom(world, pos, random, pos.down()); + + if (dirt != null) { + world.setBlockState(pos, dirt, 3); + } + } 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()))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); + 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); + } } + /** Ignore derived visual properties while retaining the material and saved orientation. */ 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/DirtSlabBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/DirtSlabBlock.java index 749240f..e23c8b3 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/DirtSlabBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/DirtSlabBlock.java @@ -13,15 +13,14 @@ public final class DirtSlabBlock extends LegacySlabBlock { public DirtSlabBlock() { super(Material.GROUND); - setDefaultState(blockState.getBaseState() - .withProperty(HALF, EnumBlockHalf.BOTTOM) - .withProperty(BlockDirt.SNOWY, Boolean.FALSE)); + setDefaultState(blockState.getBaseState().withProperty(HALF, EnumBlockHalf.BOTTOM).withProperty(BlockDirt.SNOWY, + Boolean.FALSE)); setTickRandomly(true); } @Override protected BlockStateContainer createBlockState() { - return new BlockStateContainer(this, new IProperty[] {HALF, BlockDirt.SNOWY}); + return new BlockStateContainer(this, new IProperty[] { HALF, BlockDirt.SNOWY }); } @Override 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 b19f530..a8db739 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSlabBlock.java @@ -30,8 +30,7 @@ public final class GrassSlabBlock extends LegacySlabBlock implements IGrowable { public GrassSlabBlock() { super(Material.GRASS); - setDefaultState(blockState.getBaseState() - .withProperty(HALF, EnumBlockHalf.BOTTOM) + setDefaultState(blockState.getBaseState().withProperty(HALF, EnumBlockHalf.BOTTOM) .withProperty(BlockGrass.SNOWY, Boolean.FALSE)); setSoundType(SoundType.PLANT); setTickRandomly(true); @@ -39,19 +38,17 @@ public GrassSlabBlock() { @Override protected BlockStateContainer createBlockState() { - return new BlockStateContainer(this, new IProperty[] {HALF, BlockGrass.SNOWY}); + return new BlockStateContainer(this, new IProperty[] { HALF, BlockGrass.SNOWY }); } @Override public IBlockState getStateFromMeta(int meta) { - return getDefaultState().withProperty(HALF, - (meta & 1) == 1 ? EnumBlockHalf.BOTTOM : EnumBlockHalf.TOP); + return getDefaultState().withProperty(HALF, (meta & 1) == 1 ? EnumBlockHalf.BOTTOM : EnumBlockHalf.TOP); } @Override public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) { - return state.withProperty(BlockGrass.SNOWY, - SnowySlabAppearance.hasNearbySnow(world, pos)); + return state.withProperty(BlockGrass.SNOWY, SnowySlabAppearance.hasNearbySnow(world, pos)); } @Override @@ -61,8 +58,7 @@ public void onBlockAdded(World world, BlockPos pos, IBlockState state) { } @Override - public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, - BlockPos changedPos) { + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, BlockPos changedPos) { super.neighborChanged(state, world, pos, changedBlock, changedPos); dirtifyGrassSupport(world, pos); } @@ -72,18 +68,21 @@ public void updateTick(World world, BlockPos pos, IBlockState state, Random rand if (world.isRemote) { return; } + dirtifyGrassSupport(world, pos); + if (!GrassSpread.canRemainGrass(world, pos)) { - world.setBlockState(pos, ModBlocks.DIRT_SLAB.getDefaultState() - .withProperty(HALF, state.getValue(HALF)), 3); + world.setBlockState(pos, ModBlocks.DIRT_SLAB.getDefaultState().withProperty(HALF, state.getValue(HALF)), 3); return; } + GrassSpread.spreadFrom(world, pos, random, pos.down()); } @Override public List getDrops(IBlockAccess world, BlockPos pos, IBlockState state, int fortune) { List drops = new ArrayList(); + drops.add(new ItemStack(ModBlocks.DIRT_SLAB)); return drops; } @@ -105,10 +104,10 @@ public Item getItemDropped(IBlockState state, Random random, int fortune) { } @Override - public boolean canSustainPlant(IBlockState state, IBlockAccess world, BlockPos pos, - EnumFacing direction, IPlantable plantable) { - return direction == EnumFacing.UP && state.getValue(HALF) == EnumBlockHalf.TOP && - Blocks.GRASS.canSustainPlant(Blocks.GRASS.getDefaultState(), world, pos, direction, plantable); + public boolean canSustainPlant(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing direction, + IPlantable plantable) { + return direction == EnumFacing.UP && state.getValue(HALF) == EnumBlockHalf.TOP + && Blocks.GRASS.canSustainPlant(Blocks.GRASS.getDefaultState(), world, pos, direction, plantable); } @Override @@ -126,34 +125,42 @@ public void grow(World world, Random random, BlockPos pos, IBlockState state) { if (state.getValue(HALF) != EnumBlockHalf.TOP) { return; } + BlockPos start = pos.up(); + for (int attempt = 0; attempt < 128; ++attempt) { BlockPos target = start; int walk = 0; + while (true) { if (walk >= attempt / 16) { if (world.isAirBlock(target)) { if (random.nextInt(8) == 0) { world.getBiome(target).plantFlower(world, random, target); } else { - IBlockState grass = Blocks.TALLGRASS.getDefaultState() - .withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS); + IBlockState grass = Blocks.TALLGRASS.getDefaultState().withProperty(BlockTallGrass.TYPE, + BlockTallGrass.EnumType.GRASS); + if (Blocks.TALLGRASS.canBlockStay(world, target, grass)) { world.setBlockState(target, grass, 3); } } } + break; } - target = target.add(random.nextInt(3) - 1, - (random.nextInt(3) - 1) * random.nextInt(3) / 2, + + target = target.add(random.nextInt(3) - 1, (random.nextInt(3) - 1) * random.nextInt(3) / 2, random.nextInt(3) - 1); + IBlockState support = world.getBlockState(target.down()); - boolean suitable = support.getBlock() == Blocks.GRASS || - (support.getBlock() == this && support.getValue(HALF) == BlockSlab.EnumBlockHalf.TOP); + boolean suitable = support.getBlock() == Blocks.GRASS + || (support.getBlock() == this && support.getValue(HALF) == BlockSlab.EnumBlockHalf.TOP); + if (!suitable || world.getBlockState(target).isNormalCube()) { break; } + ++walk; } } 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 eb6e0a3..0ac50a2 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java @@ -15,14 +15,14 @@ public final class GrassSpread { public static boolean canRemainGrass(World world, BlockPos pos) { BlockPos above = pos.up(); - return world.getLightFromNeighbors(above) >= 4 || - world.getBlockState(above).getLightOpacity(world, above) <= 2; + + return world.getLightFromNeighbors(above) >= 4 || world.getBlockState(above).getLightOpacity(world, above) <= 2; } public static boolean hasSpreadLight(World world, BlockPos pos) { BlockPos above = pos.up(); - return world.getLightFromNeighbors(above) >= 9 && - world.getBlockState(above).getLightOpacity(world, above) <= 2; + + return world.getLightFromNeighbors(above) >= 9 && world.getBlockState(above).getLightOpacity(world, above) <= 2; } public static void spreadFrom(World world, BlockPos source, Random random, BlockPos excludedTarget) { @@ -30,14 +30,15 @@ public static void spreadFrom(World world, BlockPos source, Random random, Block return; } for (int attempt = 0; attempt < SPREAD_ATTEMPTS; ++attempt) { - BlockPos target = source.add(random.nextInt(3) - 1, - random.nextInt(5) - 3, random.nextInt(3) - 1); + BlockPos target = source.add(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1); + if (target.equals(excludedTarget)) { continue; } if (target.getY() < 0 || target.getY() >= 256 || !world.isBlockLoaded(target)) { return; } + growTarget(world, target, world.getBlockState(source)); } } @@ -47,63 +48,79 @@ public static void tickDirtSlab(World world, BlockPos target, IBlockState state, return; } for (int attempt = 0; attempt < SPREAD_ATTEMPTS; ++attempt) { - BlockPos source = target.add(random.nextInt(3) - 1, - random.nextInt(5) - 1, random.nextInt(3) - 1); + BlockPos source = target.add(random.nextInt(3) - 1, random.nextInt(5) - 1, random.nextInt(3) - 1); + if (source.getY() < 0 || source.getY() >= 256 || !world.isBlockLoaded(source)) { return; } if (isViableSource(world, source)) { - IBlockState grass = GrassSlabsApi.grassFor(state,world.getBlockState(source)); - if (grass != null) world.setBlockState(target, grass, 3); + IBlockState grass = GrassSlabsApi.grassFor(state, world.getBlockState(source)); + + if (grass != null) { + world.setBlockState(target, grass, 3); + } + return; } } } public static boolean growTarget(World world, BlockPos target) { - return growTarget(world,target,Blocks.GRASS.getDefaultState()); + return growTarget(world, target, Blocks.GRASS.getDefaultState()); } - private static boolean growTarget(World world, BlockPos target,IBlockState source) { + + 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) { - IBlockState mapped=GrassSlabsApi.grassFor(state,source); - return world.setBlockState(target,mapped==null?Blocks.GRASS.getDefaultState():mapped,3); + + if (state.getBlock() == Blocks.DIRT && state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT) { + IBlockState mapped = GrassSlabsApi.grassFor(state, source); + + return world.setBlockState(target, mapped == null ? Blocks.GRASS.getDefaultState() : mapped, 3); } - IBlockState grass = GrassSlabsApi.grassFor(state,source); - if (grass != null) return world.setBlockState(target, grass, 3); + + IBlockState grass = GrassSlabsApi.grassFor(state, source); + + if (grass != null) { + return world.setBlockState(target, grass, 3); + } + return false; } public static boolean isViableSource(World world, BlockPos pos) { IBlockState state = world.getBlockState(pos); + if (!canRemainGrass(world, pos) || !hasSpreadLight(world, pos)) { return false; } if (state.getBlock() == Blocks.GRASS || GrassSlabsApi.isSpreadingGrassForm(state)) { return true; } + return state.getBlock() == ModBlocks.TURF && world.getBlockState(pos.down()).getBlock() == Blocks.DIRT; } private static boolean targetIsViable(World world, BlockPos target) { IBlockState state = world.getBlockState(target); - boolean dirt = GrassSlabsApi.grassFor(state) != null || - (state.getBlock() == Blocks.DIRT && - state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT); + boolean dirt = GrassSlabsApi.grassFor(state) != null + || (state.getBlock() == Blocks.DIRT && state.getValue(BlockDirt.VARIANT) == BlockDirt.DirtType.DIRT); + if (!dirt) { return false; } + BlockPos above = target.up(); IBlockState cover = world.getBlockState(above); + if (cover.getBlock() == ModBlocks.TURF || GrassSlabsApi.isGrassForm(cover)) { return false; } - return world.getLightFromNeighbors(above) >= 4 && - cover.getLightOpacity(world, above) <= 2; + + return world.getLightFromNeighbors(above) >= 4 && cover.getLightOpacity(world, above) <= 2; } private GrassSpread() { diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/LegacySlabBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/LegacySlabBlock.java index bc9f8a3..0477f31 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/LegacySlabBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/LegacySlabBlock.java @@ -51,7 +51,6 @@ public int getMetaFromState(IBlockState state) { @Override public IBlockState getStateFromMeta(int meta) { - return getDefaultState().withProperty(HALF, - (meta & 1) == 1 ? EnumBlockHalf.BOTTOM : EnumBlockHalf.TOP); + return getDefaultState().withProperty(HALF, (meta & 1) == 1 ? EnumBlockHalf.BOTTOM : EnumBlockHalf.TOP); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/PathSlabBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/PathSlabBlock.java index d8ab642..11a7827 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/PathSlabBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/PathSlabBlock.java @@ -14,10 +14,9 @@ import zone.moddev.mc.skysgrassslabs.init.ModBlocks; public final class PathSlabBlock extends LegacySlabBlock { - public static final AxisAlignedBB BOTTOM_PATH_AABB = - new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 7.0D / 16.0D, 1.0D); - public static final AxisAlignedBB TOP_PATH_AABB = - new AxisAlignedBB(0.0D, 8.0D / 16.0D, 0.0D, 1.0D, 15.0D / 16.0D, 1.0D); + public static final AxisAlignedBB BOTTOM_PATH_AABB = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 7.0D / 16.0D, 1.0D); + public static final AxisAlignedBB TOP_PATH_AABB = new AxisAlignedBB(0.0D, 8.0D / 16.0D, 0.0D, 1.0D, 15.0D / 16.0D, + 1.0D); public PathSlabBlock() { super(Material.GROUND); @@ -31,18 +30,18 @@ public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, Block } @Override - public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, - BlockPos changedPos) { + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, BlockPos changedPos) { super.neighborChanged(state, world, pos, changedBlock, changedPos); + if (!world.isRemote && world.getBlockState(pos.up()).getMaterial().isSolid()) { - world.setBlockState(pos, ModBlocks.DIRT_SLAB.getDefaultState() - .withProperty(HALF, state.getValue(HALF)), 3); + world.setBlockState(pos, ModBlocks.DIRT_SLAB.getDefaultState().withProperty(HALF, state.getValue(HALF)), 3); } } @Override public List getDrops(IBlockAccess world, BlockPos pos, IBlockState state, int fortune) { List drops = new ArrayList(); + drops.add(new ItemStack(ModBlocks.DIRT_SLAB)); return drops; } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/SnowySlabAppearance.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/SnowySlabAppearance.java index be69750..5ace637 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/SnowySlabAppearance.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/SnowySlabAppearance.java @@ -16,6 +16,7 @@ static boolean hasNearbySnow(IBlockAccess world, BlockPos pos) { return true; } } + return false; } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java index 5c1e35e..3052e8b 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java @@ -17,8 +17,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; public final class TurfBlock extends Block { - public static final AxisAlignedBB TURF_AABB = - new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D / 16.0D, 1.0D); + public static final AxisAlignedBB TURF_AABB = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D / 16.0D, 1.0D); public TurfBlock() { super(Material.CARPET); @@ -44,8 +43,7 @@ public boolean isFullCube(IBlockState state) { } @Override - public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, - EnumFacing face) { + public BlockFaceShape getBlockFaceShape(IBlockAccess world, IBlockState state, BlockPos pos, EnumFacing face) { return face == EnumFacing.DOWN ? BlockFaceShape.SOLID : BlockFaceShape.UNDEFINED; } @@ -55,8 +53,7 @@ public boolean canPlaceBlockAt(World world, BlockPos pos) { } @Override - public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, - BlockPos changedPos) { + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock, BlockPos changedPos) { if (!world.isRemote && world.getBlockState(pos.down()).getBlock() == Blocks.GRASS) { world.setBlockState(pos.down(), Blocks.DIRT.getDefaultState(), 2); return; @@ -72,12 +69,15 @@ public void updateTick(World world, BlockPos pos, IBlockState state, Random rand if (world.isRemote) { return; } + BlockPos support = pos.down(); + if (world.getBlockState(support).getBlock() != Blocks.DIRT) { dropBlockAsItem(world, pos, state, 0); world.setBlockToAir(pos); return; } + GrassSpread.spreadFrom(world, pos, random, support); } 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 0124882..887ad5c 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java @@ -20,37 +20,42 @@ public final class BuildingBricksCompat { public static final String MOD_ID = "buildingbricks"; - public static final ResourceLocation GRASS_SLAB_ID = - new ResourceLocation(MOD_ID, "grass_slab"); - public static final ResourceLocation DIRT_SLAB_ID = - new ResourceLocation(MOD_ID, "dirt_slab"); - public static final ResourceLocation HISTORICAL_GRASS_SLAB_ID = - new ResourceLocation("buildingbrickscompatvanilla", "grass_slab"); + public static final ResourceLocation GRASS_SLAB_ID = new ResourceLocation(MOD_ID, "grass_slab"); + public static final ResourceLocation DIRT_SLAB_ID = new ResourceLocation(MOD_ID, "dirt_slab"); + public static final ResourceLocation HISTORICAL_GRASS_SLAB_ID = new ResourceLocation("buildingbrickscompatvanilla", + "grass_slab"); private static Block grassSlab; private static Block dirtSlab; private static Block historicalGrassSlab; private static boolean legacyAliasesRegistered; - private static final Map buildingBricksBlocks = - Collections.synchronizedMap(new IdentityHashMap()); + private static final Map buildingBricksBlocks = Collections + .synchronizedMap(new IdentityHashMap()); public static void preInit(File configDirectory) { boolean installed = Loader.isModLoaded(MOD_ID); - if (!installed) return; + + if (!installed) { + return; + } + SkysGrassSlabs.logger.info("BuildingBricks slab replacement is {}", - SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs() - ? "enabled" : "disabled"); - if (!BuildingBricksPolicy.shouldArbitrateWorldgen(installed, - SkysGrassSlabsConfig.isSmoothingActive())) return; + SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs() ? "enabled" : "disabled"); + + if (!BuildingBricksPolicy.shouldArbitrateWorldgen(installed, SkysGrassSlabsConfig.isSmoothingActive())) { + return; + } File configFile = new File(configDirectory, "BuildingBricks/general.cfg"); + try { if (!BuildingBricksConfigArbitrator.disable(configFile)) { SkysGrassSlabs.logger.info("BuildingBricks grass-slab generation is already disabled"); return; } - SkysGrassSlabs.logger.info( - "Disabled BuildingBricks grass-slab generation; Sky's Grass Slabs owns smoothing"); + + SkysGrassSlabs.logger + .info("Disabled BuildingBricks grass-slab generation; Sky's Grass Slabs owns smoothing"); } catch (IOException | RuntimeException exception) { SkysGrassSlabsConfig.suppressSmoothingForThisRun(); SkysGrassSlabs.logger.error( @@ -64,25 +69,35 @@ public static void resolveBlocks() { dirtSlab = ForgeRegistries.BLOCKS.getValue(DIRT_SLAB_ID); historicalGrassSlab = ForgeRegistries.BLOCKS.getValue(HISTORICAL_GRASS_SLAB_ID); buildingBricksBlocks.clear(); - for (ResourceLocation id : ForgeRegistries.BLOCKS.getKeys()) { - if (MOD_ID.equals(id.getNamespace())) { - buildingBricksBlocks.put(ForgeRegistries.BLOCKS.getValue(id), id.toString()); + + for (ResourceLocation registryId : ForgeRegistries.BLOCKS.getKeys()) { + if (MOD_ID.equals(registryId.getNamespace())) { + buildingBricksBlocks.put(ForgeRegistries.BLOCKS.getValue(registryId), registryId.toString()); } } } public static Block grassSlab() { - if (grassSlab == null) resolveBlocks(); + if (grassSlab == null) { + resolveBlocks(); + } + return grassSlab; } public static Block dirtSlab() { - if (dirtSlab == null) resolveBlocks(); + if (dirtSlab == null) { + resolveBlocks(); + } + return dirtSlab; } public static Block historicalGrassSlab() { - if (historicalGrassSlab == null) resolveBlocks(); + if (historicalGrassSlab == null) { + resolveBlocks(); + } + return historicalGrassSlab; } @@ -104,25 +119,29 @@ public static String buildingBricksBlockId(Block block) { } public static Map buildingBricksBlockIdsByNumericId() { - Map ids = new HashMap(); + Map registryIds = new HashMap(); + synchronized (buildingBricksBlocks) { for (Map.Entry entry : buildingBricksBlocks.entrySet()) { - ids.put(Block.getIdFromBlock(entry.getKey()), entry.getValue()); + registryIds.put(Block.getIdFromBlock(entry.getKey()), entry.getValue()); } } - return ids; + + return registryIds; } public static boolean isGrassSlabItem(ItemStack stack) { - if (stack == null || stack.isEmpty()) return false; - return (grassSlab() != null && stack.getItem() == Item.getItemFromBlock(grassSlab())) || - (historicalGrassSlab() != null && - stack.getItem() == Item.getItemFromBlock(historicalGrassSlab())); + if (stack == null || stack.isEmpty()) { + return false; + } + + return (grassSlab() != null && stack.getItem() == Item.getItemFromBlock(grassSlab())) + || (historicalGrassSlab() != null && stack.getItem() == Item.getItemFromBlock(historicalGrassSlab())); } static boolean isDirtSlabItem(ItemStack stack) { - return stack != null && !stack.isEmpty() && dirtSlab() != null && - stack.getItem() == Item.getItemFromBlock(dirtSlab()); + return stack != null && !stack.isEmpty() && dirtSlab() != null + && stack.getItem() == Item.getItemFromBlock(dirtSlab()); } public static IRecipe bridgeRecipe() { @@ -130,7 +149,10 @@ public static IRecipe bridgeRecipe() { } public static void registerLegacyAliasBlocks(IForgeRegistry registry) { - if (isInstalled()) return; + if (isInstalled()) { + return; + } + grassSlab = legacyAlias(GRASS_SLAB_ID, true); dirtSlab = legacyAlias(DIRT_SLAB_ID, false); historicalGrassSlab = legacyAlias(HISTORICAL_GRASS_SLAB_ID, true); @@ -139,15 +161,17 @@ public static void registerLegacyAliasBlocks(IForgeRegistry registry) { } public static void registerLegacyAliasItems(IForgeRegistry registry) { - if (!legacyAliasesRegistered) return; - registry.registerAll(aliasItem(grassSlab), aliasItem(dirtSlab), - aliasItem(historicalGrassSlab)); + if (!legacyAliasesRegistered) { + return; + } + + registry.registerAll(aliasItem(grassSlab), aliasItem(dirtSlab), aliasItem(historicalGrassSlab)); } - private static Block legacyAlias(ResourceLocation id, boolean grass) { + private static Block legacyAlias(ResourceLocation registryId, boolean grass) { LegacySlabAliasBlock block = new LegacySlabAliasBlock(grass); - block.setRegistryName(id).setTranslationKey( - SkysGrassSlabs.MOD_ID + ".legacy_" + id.getPath()); + + block.setRegistryName(registryId).setTranslationKey(SkysGrassSlabs.MOD_ID + ".legacy_" + registryId.getPath()); return block; } @@ -158,6 +182,7 @@ public int getMetadata(int damage) { return damage & 1; } }; + item.setHasSubtypes(true); return item.setRegistryName(block.getRegistryName()); } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitrator.java b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitrator.java index f4ff8f7..9063a06 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitrator.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitrator.java @@ -12,14 +12,16 @@ final class BuildingBricksConfigArbitrator { static boolean disable(File configFile) throws IOException { Configuration configuration = new Configuration(configFile); + configuration.load(); + if (!configuration.get(CATEGORY, PROPERTY, true).getBoolean()) { return false; } if (configFile.isFile()) { - File backup = new File(configFile.getParentFile(), - "general.cfg.skysgrassslabs-backup"); + File backup = new File(configFile.getParentFile(), "general.cfg.skysgrassslabs-backup"); + if (!backup.exists()) { Files.copy(configFile.toPath(), backup.toPath()); } @@ -29,10 +31,13 @@ static boolean disable(File configFile) throws IOException { configuration.save(); Configuration verification = new Configuration(configFile); + verification.load(); + if (verification.get(CATEGORY, PROPERTY, true).getBoolean()) { throw new IOException("BuildingBricks configuration did not retain generateGrassSlabs=false"); } + return true; } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java index 7325063..0a804d2 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java @@ -12,16 +12,19 @@ import zone.moddev.mc.skysgrassslabs.init.ModRecipes; /** Matches BuildingBricks material items after its late material registry is available. */ -public final class BuildingBricksDirtSlabRecipe extends IForgeRegistryEntry.Impl - implements IRecipe { +public final class BuildingBricksDirtSlabRecipe extends IForgeRegistryEntry.Impl implements IRecipe { @Override public boolean matches(InventoryCrafting inventory, World world) { boolean dirtSlab = false; boolean seed = false; NonNullList seeds = OreDictionary.getOres(ModRecipes.SEED_ORE); + for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); - if (stack.isEmpty()) continue; + + if (stack.isEmpty()) { + continue; + } if (!dirtSlab && BuildingBricksCompat.isDirtSlabItem(stack)) { dirtSlab = true; } else if (!seed && OreDictionary.containsMatch(false, seeds, stack)) { @@ -30,6 +33,7 @@ public boolean matches(InventoryCrafting inventory, World world) { return false; } } + return dirtSlab && seed; } 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 2c4ed77..eb17d5b 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java @@ -44,74 +44,103 @@ import zone.moddev.mc.skysgrassslabs.init.ModBlocks; import zone.moddev.mc.skysgrassslabs.world.ModWorldState; +/** Recovers legacy slabs without conflating missing-mod recovery with opt-in replacement. */ 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) { World world = event.getWorld(); - if (world.isRemote) return; + + if (world.isRemote) { + return; + } + NBTTagCompound marker = event.getData().getCompoundTag(CHUNK_MARKER); int markerVersion = marker.getInteger(CHUNK_MIGRATION_VERSION); Chunk chunk = event.getChunk(); ChunkKey chunkKey = ChunkKey.of(world, chunk); + if (shouldPreserveChunkMarker(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); + + if (changed) { + chunk.setModified(true); + } + return; } if (shouldPreserveChunkMarker(markerVersion)) { return; } - if (!shouldMigrateChunk(BuildingBricksCompat.shouldReplaceSlabs(), markerVersion)) return; + if (!shouldMigrateChunk(BuildingBricksCompat.shouldReplaceSlabs(), markerVersion)) { + return; + } + BuildingBricksCompat.resolveBlocks(); + if (BuildingBricksCompat.grassSlab() == null || BuildingBricksCompat.dirtSlab() == null) { SkysGrassSlabs.logger.error("BuildingBricks is loaded but its grass or dirt slab is not registered"); return; } ModWorldState state = ModWorldState.get(world); + migrateBlocks(chunk, event.getData(), state); migrateChunkInventories(chunk, state); pendingHandlers.add(chunk); state.recordChunk(); chunk.setModified(true); - NBTTagCompound migratedMarker=marker.copy(); - migratedMarker.setInteger(CHUNK_MIGRATION_VERSION,ModWorldState.MIGRATION_VERSION); - chunkMarkersToSave.put(chunkKey,migratedMarker); + + NBTTagCompound migratedMarker = marker.copy(); + + migratedMarker.setInteger(CHUNK_MIGRATION_VERSION, ModWorldState.MIGRATION_VERSION); + chunkMarkersToSave.put(chunkKey, migratedMarker); } @SubscribeEvent public void saveChunk(ChunkDataEvent.Save event) { - NBTTagCompound savedMarker = chunkMarkersToSave.get( - ChunkKey.of(event.getWorld(), event.getChunk())); - if (savedMarker == null) return; + NBTTagCompound savedMarker = chunkMarkersToSave.get(ChunkKey.of(event.getWorld(), event.getChunk())); + + if (savedMarker == null) { + return; + } + NBTTagCompound marker = savedMarker.copy(); - if(event.getData().hasKey(CHUNK_MARKER,10))marker.merge(event.getData().getCompoundTag(CHUNK_MARKER)); + + 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() && !BuildingBricksCompat.hasLegacyAliases())) { + + 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 = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(world); int metadata = placed.getBlock().getMetaFromState(placed) & 1; + if (state != null && placed.getBlock() == BuildingBricksCompat.grassSlab()) { state.recordGrassBlocks(1, metadata); } @@ -123,20 +152,31 @@ public void convertPlacedBlock(BlockEvent.PlaceEvent event) { @SubscribeEvent public void joinEntity(EntityJoinWorldEvent event) { - if (event.getWorld().isRemote || (!BuildingBricksCompat.hasLegacyAliases() && - !BuildingBricksCompat.shouldReplaceSlabs()) || event.getEntity() instanceof EntityPlayer) return; + 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); + + if (migrateStacksInNbt(serialized, state)) { + event.getEntity().deserializeNBT(serialized); + } } @SubscribeEvent public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) { - if (!(event.player instanceof EntityPlayer) || (!BuildingBricksCompat.shouldReplaceSlabs() && !BuildingBricksCompat.hasLegacyAliases())) { + if (!(event.player instanceof EntityPlayer) + || (!BuildingBricksCompat.shouldReplaceSlabs() && !BuildingBricksCompat.hasLegacyAliases())) { return; } + EntityPlayer player = event.player; - ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(player.getEntityWorld()); + ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null + : ModWorldState.get(player.getEntityWorld()); + migrateInventory(player.inventory, state); migrateInventory(player.getInventoryEnderChest(), state); } @@ -144,39 +184,49 @@ public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) { @SubscribeEvent public void saveWorld(WorldEvent.Save event) { World world = event.getWorld(); - if (world.isRemote || world.provider.getDimension() != 0 || - !BuildingBricksCompat.shouldReplaceSlabs()) { + + if (world.isRemote || world.provider.getDimension() != 0 || !BuildingBricksCompat.shouldReplaceSlabs()) { return; } + writeReport(world, ModWorldState.get(world)); } public static void remapMissingBlocks(RegistryEvent.MissingMappings event) { - // Separate historical holders keep the saved numeric slots distinct. - // Do not remap several old IDs onto an already registered Sky block. - if (!BuildingBricksCompat.hasLegacyAliases()) return; + // Separate historical holders keep the saved numeric slots distinct. Do not remap several old IDs onto an + // already registered Sky block. + if (!BuildingBricksCompat.hasLegacyAliases()) { + return; + } for (RegistryEvent.MissingMappings.Mapping mapping : event.getAllMappings()) { Block holder = net.minecraftforge.fml.common.registry.ForgeRegistries.BLOCKS.getValue(mapping.key); - if (legacySlabKind(mapping.key) != null && holder instanceof LegacySlabAliasBlock) + + if (legacySlabKind(mapping.key) != null && holder instanceof LegacySlabAliasBlock) { mapping.remap(holder); + } } } public static void remapMissingItems(RegistryEvent.MissingMappings event) { - if (!BuildingBricksCompat.hasLegacyAliases()) return; + if (!BuildingBricksCompat.hasLegacyAliases()) { + return; + } for (RegistryEvent.MissingMappings.Mapping mapping : event.getAllMappings()) { Block holder = net.minecraftforge.fml.common.registry.ForgeRegistries.BLOCKS.getValue(mapping.key); - if (legacySlabKind(mapping.key) != null && holder instanceof LegacySlabAliasBlock) + + if (legacySlabKind(mapping.key) != null && holder instanceof LegacySlabAliasBlock) { mapping.remap(Item.getItemFromBlock(holder)); + } } } - static LegacySlabKind legacySlabKind(ResourceLocation id) { - if (id.equals(BuildingBricksCompat.GRASS_SLAB_ID) || - id.equals(BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID)) { + static LegacySlabKind legacySlabKind(ResourceLocation registryId) { + if (registryId.equals(BuildingBricksCompat.GRASS_SLAB_ID) + || registryId.equals(BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID)) { return LegacySlabKind.GRASS; } - return id.equals(BuildingBricksCompat.DIRT_SLAB_ID) ? LegacySlabKind.DIRT : null; + + return registryId.equals(BuildingBricksCompat.DIRT_SLAB_ID) ? LegacySlabKind.DIRT : null; } static boolean shouldMigrateChunk(boolean replacementEnabled, int markerVersion) { @@ -187,51 +237,69 @@ static boolean shouldPreserveChunkMarker(int markerVersion) { return markerVersion >= ModWorldState.MIGRATION_VERSION; } - private static boolean migrateBlocks(Chunk chunk, NBTTagCompound chunkData, - ModWorldState worldState) { + private static boolean migrateBlocks(Chunk chunk, NBTTagCompound chunkData, ModWorldState worldState) { long grassTop = 0; long grassBottom = 0; long dirtTop = 0; long dirtBottom = 0; Map unsupported = new TreeMap(); ExtendedBlockStorage[] storage = chunk.getBlockStorageArray(); - NBTTagList serializedSections = chunkData.getCompoundTag("Level") - .getTagList("Sections", 10); - Map buildingBricksIds = - BuildingBricksCompat.buildingBricksBlockIdsByNumericId(); + NBTTagList serializedSections = chunkData.getCompoundTag("Level").getTagList("Sections", 10); + 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); int ySection = serializedSection.getByte("Y") & 255; - if (ySection >= storage.length || storage[ySection] == null) continue; + + if (ySection >= storage.length || storage[ySection] == null) { + continue; + } + byte[] blocks = serializedSection.getByteArray("Blocks"); - if (blocks.length != 4096) continue; + + if (blocks.length != 4096) { + continue; + } + NibbleArray metadata = new NibbleArray(serializedSection.getByteArray("Data")); - NibbleArray add = serializedSection.hasKey("Add", 7) - ? new NibbleArray(serializedSection.getByteArray("Add")) : null; + NibbleArray blockIdHighNibbles = serializedSection.hasKey("Add", 7) + ? new NibbleArray(serializedSection.getByteArray("Add")) + : null; ExtendedBlockStorage section = storage[ySection]; + for (int index = 0; index < blocks.length; ++index) { - int numericId = (blocks[index] & 255) | - (add == null ? 0 : add.getFromIndex(index) << 8); + int numericId = (blocks[index] & 255) + | (blockIdHighNibbles == null ? 0 : blockIdHighNibbles.getFromIndex(index) << 8); + 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 || numericId == historicalGrassId, slabMetadata)); + if (numericId == grassId || numericId == historicalGrassId) { - if (slabMetadata == 0) ++grassTop; - else ++grassBottom; + if (slabMetadata == 0) { + ++grassTop; + } else { + ++grassBottom; + } } else { - if (slabMetadata == 0) ++dirtTop; - else ++dirtBottom; + if (slabMetadata == 0) { + ++dirtTop; + } else { + ++dirtBottom; + } } } else { - String id = buildingBricksIds.get(numericId); - if (id != null) { - unsupported.put(id, unsupported.containsKey(id) - ? unsupported.get(id) + 1 : 1); + String registryId = buildingBricksIds.get(numericId); + + if (registryId != null) { + unsupported.put(registryId, + unsupported.containsKey(registryId) ? unsupported.get(registryId) + 1 : 1); } } } @@ -241,33 +309,40 @@ private static boolean migrateBlocks(Chunk chunk, NBTTagCompound chunkData, 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()) { + + if (oldBlock != BuildingBricksCompat.grassSlab() && oldBlock != BuildingBricksCompat.historicalGrassSlab() + && oldBlock != BuildingBricksCompat.dirtSlab()) { return null; } + int metadata = oldBlock.getMetaFromState(oldState) & 1; - return skyStateFor(oldBlock == BuildingBricksCompat.grassSlab() || oldBlock == BuildingBricksCompat.historicalGrassSlab(), metadata); + + return skyStateFor( + oldBlock == BuildingBricksCompat.grassSlab() || oldBlock == BuildingBricksCompat.historicalGrassSlab(), + metadata); } static IBlockState skyStateFor(boolean grass, int metadata) { - return (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB) - .getStateFromMeta(metadata & 1); + return (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB).getStateFromMeta(metadata & 1); } 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; @@ -276,34 +351,64 @@ private static boolean migrateChunkInventories(Chunk chunk, ModWorldState state) for (ClassInheritanceMultiMap list : chunk.getEntityLists()) { for (Entity entity : list) { 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.x<<4,0,chunk.z<<4),false))continue; - ModWorldState state=BuildingBricksCompat.hasLegacyAliases()?null:ModWorldState.get(world); - for(TileEntity tile:chunk.getTileEntityMap().values()) { + 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.x << 4, 0, chunk.z << 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;slot 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; slot < handler.getSlots(); slot++) { + ItemStack migrated = migrateStack(handler.getStackInSlot(slot), state); + + if (migrated != null) { + ((IItemHandlerModifiable) handler).setStackInSlot(slot, migrated); + tile.markDirty(); + } } } } @@ -314,46 +419,65 @@ private static void migrateInventory(IInventory inventory, ModWorldState state) for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); ItemStack migrated = migrateStack(stack, state); - if (migrated != null) inventory.setInventorySlotContents(slot, migrated); + + if (migrated != null) { + inventory.setInventorySlotContents(slot, migrated); + } } + inventory.markDirty(); } /** - * Migrates serialized stacks without calling a tile inventory API. In 1.10 a loot chest's - * accessor generates its loot and marks the tile dirty; doing that from ChunkDataEvent.Load - * recursively loads the same chunk. NBT traversal also covers item-handler capabilities and - * nested container formats while leaving unopened loot tables untouched. + * Migrates serialized stacks without calling a tile inventory API. In 1.10 a loot chest's accessor generates its + * loot and marks the tile dirty; doing that from ChunkDataEvent.Load recursively loads the same chunk. NBT + * traversal also covers item-handler capabilities and nested container formats while leaving unopened loot tables + * untouched. */ static boolean migrateStacksInNbt(NBTBase tag, ModWorldState state) { boolean changed = false; + if (tag instanceof NBTTagCompound) { NBTTagCompound compound = (NBTTagCompound) tag; + if (compound.hasKey("id", 8) && compound.hasKey("Count", 99)) { - String id = compound.getString("id"); - boolean grass = BuildingBricksCompat.GRASS_SLAB_ID.toString().equals(id) || - BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID.toString().equals(id); - boolean dirt = BuildingBricksCompat.DIRT_SLAB_ID.toString().equals(id); + String registryId = compound.getString("id"); + boolean grass = BuildingBricksCompat.GRASS_SLAB_ID.toString().equals(registryId) + || BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID.toString().equals(registryId); + boolean dirt = BuildingBricksCompat.DIRT_SLAB_ID.toString().equals(registryId); + if (grass || dirt) { - compound.setString("id", (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB) - .getRegistryName().toString()); + compound.setString("id", + (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB).getRegistryName().toString()); compound.setShort("Damage", (short) 0); + int count = compound.getByte("Count") & 255; - if (state != null && grass) state.recordGrassItems(count); - if (state != null && dirt) state.recordDirtItems(count); + + if (state != null && grass) { + state.recordGrassItems(count); + } + if (state != null && dirt) { + state.recordDirtItems(count); + } + changed = true; } } for (String key : new ArrayList(compound.getKeySet())) { NBTBase child = compound.getTag(key); - if (child != null) changed |= migrateStacksInNbt(child, state); + + if (child != null) { + changed |= migrateStacksInNbt(child, state); + } } } else if (tag instanceof NBTTagList) { NBTTagList list = (NBTTagList) tag; + for (int index = 0; index < list.tagCount(); ++index) { changed |= migrateStacksInNbt(list.get(index), state); } } + return changed; } @@ -361,6 +485,7 @@ private static ItemStack migrateStack(ItemStack stack, ModWorldState state) { if (stack == null || stack.isEmpty()) { return null; } + Item oldItem = stack.getItem(); Item grassItem = BuildingBricksCompat.grassSlab() == null ? null : Item.getItemFromBlock(BuildingBricksCompat.grassSlab()); @@ -368,17 +493,27 @@ private static ItemStack migrateStack(ItemStack stack, ModWorldState state) { : Item.getItemFromBlock(BuildingBricksCompat.dirtSlab()); Item historicalGrassItem = BuildingBricksCompat.historicalGrassSlab() == null ? null : Item.getItemFromBlock(BuildingBricksCompat.historicalGrassSlab()); + if (oldItem == grassItem || oldItem == historicalGrassItem || oldItem == dirtItem) { boolean grass = oldItem == grassItem || oldItem == historicalGrassItem; NBTTagCompound serialized = stack.writeToNBT(new NBTTagCompound()); - serialized.setString("id", (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB) - .getRegistryName().toString()); + + serialized.setString("id", + (grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB).getRegistryName().toString()); serialized.setShort("Damage", (short) 0); + ItemStack migrated = new ItemStack(serialized); - if (state != null && grass) state.recordGrassItems(stack.getCount()); - if (state != null && !grass) state.recordDirtItems(stack.getCount()); + + if (state != null && grass) { + state.recordGrassItems(stack.getCount()); + } + if (state != null && !grass) { + state.recordDirtItems(stack.getCount()); + } + return migrated; } + return null; } @@ -387,9 +522,11 @@ private static void writeReport(World world, ModWorldState state) { File report = new File(directory, "skysgrassslabs-migration-report.txt"); File temporary = new File(directory, "skysgrassslabs-migration-report.txt.tmp"); List lines = migrationReportLines(state); + try { Files.createDirectories(directory.toPath()); Files.write(temporary.toPath(), lines, StandardCharsets.UTF_8); + try { Files.move(temporary.toPath(), report.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); @@ -403,8 +540,8 @@ private static void writeReport(World world, ModWorldState state) { static List migrationReportLines(ModWorldState state) { List lines = new ArrayList(); - lines.add("Sky's Grass Slabs " + SkysGrassSlabs.VERSION - + " BuildingBricks Migration Report"); + + lines.add("Sky's Grass Slabs " + SkysGrassSlabs.VERSION + " BuildingBricks Migration Report"); lines.add("schema_version=1"); lines.add("migration_version=1"); lines.add("force_replacement_enabled=true"); @@ -418,15 +555,16 @@ static List migrationReportLines(ModWorldState state) { lines.add("migrated_grass_items=" + state.migratedGrassItems()); lines.add("migrated_dirt_items=" + state.migratedDirtItems()); lines.add("unsupported_entries=" + state.unsupported().size()); + for (Map.Entry entry : state.unsupported().entrySet()) { lines.add("unsupported." + entry.getKey() + "=" + entry.getValue()); } + return lines; } enum LegacySlabKind { - GRASS, - DIRT + GRASS, DIRT } /** Stable identity retained between Forge's separate chunk load and save events. */ @@ -435,10 +573,10 @@ private static final class ChunkKey { private final int x; private final int z; - private ChunkKey(int dimension, int x, int z) { + private ChunkKey(int dimension, int blockX, int blockZ) { this.dimension = dimension; - this.x = x; - this.z = z; + this.x = blockX; + this.z = blockZ; } private static ChunkKey of(World world, Chunk chunk) { @@ -448,15 +586,22 @@ private static ChunkKey of(World world, Chunk chunk) { @Override public int hashCode() { int result = dimension; + result = 31 * result + x; return 31 * result + z; } @Override public boolean equals(Object value) { - if (this == value) return true; - if (!(value instanceof ChunkKey)) return false; + if (this == value) { + return true; + } + if (!(value instanceof ChunkKey)) { + return false; + } + ChunkKey other = (ChunkKey) value; + return dimension == other.dimension && x == other.x && z == other.z; } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacySlabAliasBlock.java b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacySlabAliasBlock.java index a017ad7..ea68f2c 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacySlabAliasBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacySlabAliasBlock.java @@ -4,8 +4,8 @@ import zone.moddev.mc.skysgrassslabs.block.LegacySlabBlock; /** - * Temporary registry holder for a supported slab from a world whose original mod is absent. - * Loaded aliases are replaced with the corresponding Sky slab before the chunk is used. + * Temporary registry holder for a supported slab from a world whose original mod is absent. Loaded aliases are replaced + * with the corresponding Sky slab before the chunk is used. */ final class LegacySlabAliasBlock extends LegacySlabBlock { LegacySlabAliasBlock(boolean grass) { 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 cd6606b..216879d 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java @@ -6,8 +6,7 @@ public final class SkysGrassSlabsConfig { public static final String COMPAT_CATEGORY = "compat"; - public static final String FORCE_REPLACE_BUILDINGBRICKS_SLABS = - "forceReplaceBuildingBricksSlabs"; + public static final String FORCE_REPLACE_BUILDINGBRICKS_SLABS = "forceReplaceBuildingBricksSlabs"; public static final String WORLDGEN_CATEGORY = "worldgen"; public static final String GENERATE_GRASS_SLABS = "generateGrassSlabs"; @@ -17,17 +16,20 @@ public final class SkysGrassSlabsConfig { public static void load(File file) { compatibilitySuppressed = false; + Configuration config = new Configuration(file); + config.load(); - forceReplaceBuildingBricksSlabs = config.getBoolean( - FORCE_REPLACE_BUILDINGBRICKS_SLABS, COMPAT_CATEGORY, false, - "Replace supported BuildingBricks grass and dirt slabs and items with Sky's " + - "versions while BuildingBricks is installed. Existing conversions cannot be undone."); + forceReplaceBuildingBricksSlabs = config.getBoolean(FORCE_REPLACE_BUILDINGBRICKS_SLABS, COMPAT_CATEGORY, false, + "Replace supported BuildingBricks grass and dirt slabs and items with Sky's " + + "versions while BuildingBricks is installed. Existing conversions cannot be undone."); generateGrassSlabs = config.getBoolean(GENERATE_GRASS_SLABS, WORLDGEN_CATEGORY, true, "Place grass slabs on suitable one-block slopes in newly generated Overworld chunks."); + if (config.hasChanged()) { config.save(); } + GrassSlabsApi.configurationLoaded(); } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/entity/ai/TurfEatingAI.java b/src/main/java/zone/moddev/mc/skysgrassslabs/entity/ai/TurfEatingAI.java index b862454..ad06dc6 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/entity/ai/TurfEatingAI.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/entity/ai/TurfEatingAI.java @@ -22,6 +22,7 @@ public boolean shouldExecute() { if (sheep.getRNG().nextInt(sheep.isChild() ? 50 : 1000) != 0) { return false; } + return world.getBlockState(position()).getBlock() == ModBlocks.TURF; } @@ -49,17 +50,20 @@ public int getEatingTimer() { @Override public void updateTask() { eatingTimer = Math.max(0, eatingTimer - 1); + if (eatingTimer != 4) { return; } BlockPos pos = position(); + if (world.getBlockState(pos).getBlock() != ModBlocks.TURF) { return; } if (world.getGameRules().getBoolean("mobGriefing")) { world.destroyBlock(pos, false); } + sheep.eatGrassBonus(); } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/event/CommonEvents.java b/src/main/java/zone/moddev/mc/skysgrassslabs/event/CommonEvents.java index 7bbcd89..66f3afc 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/event/CommonEvents.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/event/CommonEvents.java @@ -24,42 +24,52 @@ public void addTurfEatingTask(EntityJoinWorldEvent event) { if (event.getWorld().isRemote || !(event.getEntity() instanceof EntitySheep)) { return; } + EntitySheep sheep = (EntitySheep) event.getEntity(); + for (EntityAITasks.EntityAITaskEntry entry : sheep.tasks.taskEntries) { if (entry.action instanceof TurfEatingAI) { return; } } + sheep.tasks.addTask(5, new TurfEatingAI(sheep)); } @SubscribeEvent public void flattenSlab(PlayerInteractEvent.RightClickBlock event) { ItemStack stack = event.getItemStack(); + if (stack.isEmpty() || event.getFace() == EnumFacing.DOWN || !isShovel(stack)) { return; } + World world = event.getWorld(); BlockPos pos = event.getPos(); IBlockState state = world.getBlockState(pos); + if (state.getBlock() != ModBlocks.DIRT_SLAB && state.getBlock() != ModBlocks.GRASS_SLAB) { return; } + EntityPlayer player = event.getEntityPlayer(); + if (!world.isAirBlock(pos.up()) || !player.canPlayerEdit(pos, event.getFace(), stack)) { return; } event.setCanceled(true); + if (world.isRemote) { return; } - IBlockState path = ModBlocks.PATH_SLAB.getDefaultState() - .withProperty(BlockSlab.HALF, state.getValue(BlockSlab.HALF)); + IBlockState path = ModBlocks.PATH_SLAB.getDefaultState().withProperty(BlockSlab.HALF, + state.getValue(BlockSlab.HALF)); + if (world.setBlockState(pos, path, 11)) { - world.playSound(null, pos, SoundEvents.ITEM_SHOVEL_FLATTEN, - SoundCategory.BLOCKS, 1.0F, 1.0F); + world.playSound(null, pos, SoundEvents.ITEM_SHOVEL_FLATTEN, SoundCategory.BLOCKS, 1.0F, 1.0F); + if (!player.capabilities.isCreativeMode) { stack.damageItem(1, player); } @@ -68,6 +78,7 @@ public void flattenSlab(PlayerInteractEvent.RightClickBlock event) { private static boolean isShovel(ItemStack stack) { Set toolClasses = stack.getItem().getToolClasses(stack); + return toolClasses != null && toolClasses.contains("shovel"); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java index f10e15e..d7e8c13 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java @@ -7,7 +7,9 @@ import zone.moddev.mc.skysgrassslabs.SkysGrassSlabs; import zone.moddev.mc.skysgrassslabs.proxy.ClientProxy; -@Mod.EventBusSubscriber(modid = SkysGrassSlabs.MOD_ID, value = Side.CLIENT) +@Mod.EventBusSubscriber( + modid = SkysGrassSlabs.MOD_ID, + value = Side.CLIENT) public final class ClientRegistryEvents { @SubscribeEvent public static void registerModels(ModelRegistryEvent event) { diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java index f3ae55c..2f9dd26 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java @@ -25,16 +25,16 @@ public static void registerBlocks(IForgeRegistry registry) { } public static void registerItems(IForgeRegistry registry) { - registry.registerAll( - item(new NormalizingSlabItem(DIRT_SLAB, net.minecraft.init.Blocks.DIRT), DIRT_SLAB), + registry.registerAll(item(new NormalizingSlabItem(DIRT_SLAB, net.minecraft.init.Blocks.DIRT), DIRT_SLAB), item(new NormalizingSlabItem(GRASS_SLAB, net.minecraft.init.Blocks.GRASS), GRASS_SLAB), item(new NormalizingSlabItem(PATH_SLAB, net.minecraft.init.Blocks.GRASS_PATH), PATH_SLAB), item(new TurfBlockItem(TURF), TURF)); } private static T configure(T block, String name) { - ResourceLocation id = new ResourceLocation(SkysGrassSlabs.MOD_ID, name); - block.setRegistryName(id).setTranslationKey(SkysGrassSlabs.MOD_ID + "." + name) + ResourceLocation registryId = new ResourceLocation(SkysGrassSlabs.MOD_ID, name); + + block.setRegistryName(registryId).setTranslationKey(SkysGrassSlabs.MOD_ID + "." + name) .setCreativeTab(CreativeTabs.BUILDING_BLOCKS); return block; } 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 3be0ba0..0ff02ac 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java @@ -20,33 +20,28 @@ public final class ModRecipes { public static void register(IForgeRegistry registry) { registerVanillaSeeds(); - RecipeSorter.register("skysgrassslabs:turf_cutting", TurfCuttingRecipe.class, - RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless"); - RecipeSorter.register("skysgrassslabs:buildingbricks_grass_slab_from_seeds", - BuildingBricksDirtSlabRecipe.class, RecipeSorter.Category.SHAPELESS, + RecipeSorter.register("skysgrassslabs:turf_cutting", TurfCuttingRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless"); + RecipeSorter.register("skysgrassslabs:buildingbricks_grass_slab_from_seeds", BuildingBricksDirtSlabRecipe.class, + RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless"); registry.registerAll( - named(new ShapedOreRecipe(null, new ItemStack(ModBlocks.DIRT_SLAB, 6), - "DDD", 'D', Blocks.DIRT), "dirt_slab"), - named(new ShapedOreRecipe(null, new ItemStack(ModBlocks.GRASS_SLAB, 6), - "GGG", 'G', Blocks.GRASS), "grass_slab"), + named(new ShapedOreRecipe(null, new ItemStack(ModBlocks.DIRT_SLAB, 6), "DDD", 'D', Blocks.DIRT), + "dirt_slab"), + named(new ShapedOreRecipe(null, new ItemStack(ModBlocks.GRASS_SLAB, 6), "GGG", 'G', Blocks.GRASS), + "grass_slab"), named(new ShapedOreRecipe(null, new ItemStack(Blocks.DIRT), "SS", 'S', - new ItemStack(ModBlocks.DIRT_SLAB, 1, OreDictionary.WILDCARD_VALUE)), - "dirt_from_slabs"), + new ItemStack(ModBlocks.DIRT_SLAB, 1, OreDictionary.WILDCARD_VALUE)), "dirt_from_slabs"), named(new ShapedOreRecipe(null, new ItemStack(Blocks.GRASS), "SS", 'S', - new ItemStack(ModBlocks.GRASS_SLAB, 1, OreDictionary.WILDCARD_VALUE)), - "grass_from_slabs"), - named(new ShapelessOreRecipe(null, new ItemStack(Blocks.GRASS), - new ItemStack(Blocks.DIRT), SEED_ORE), "grass_block_from_seeds"), + new ItemStack(ModBlocks.GRASS_SLAB, 1, OreDictionary.WILDCARD_VALUE)), "grass_from_slabs"), + named(new ShapelessOreRecipe(null, new ItemStack(Blocks.GRASS), new ItemStack(Blocks.DIRT), SEED_ORE), + "grass_block_from_seeds"), named(new ShapelessOreRecipe(null, new ItemStack(ModBlocks.GRASS_SLAB), - new ItemStack(ModBlocks.DIRT_SLAB), SEED_ORE), - "grass_slab_from_seeds"), + new ItemStack(ModBlocks.DIRT_SLAB), SEED_ORE), "grass_slab_from_seeds"), named(new TurfCuttingRecipe(), "turf")); if (BuildingBricksCompat.isInstalled()) { - registry.register(named(BuildingBricksCompat.bridgeRecipe(), - "buildingbricks_grass_slab_from_seeds")); + registry.register(named(BuildingBricksCompat.bridgeRecipe(), "buildingbricks_grass_slab_from_seeds")); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java index f5ec543..4ee0555 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java @@ -10,7 +10,8 @@ import zone.moddev.mc.skysgrassslabs.compat.BuildingBricksCompat; import zone.moddev.mc.skysgrassslabs.compat.LegacyMigrationHandler; -@Mod.EventBusSubscriber(modid = SkysGrassSlabs.MOD_ID) +@Mod.EventBusSubscriber( + modid = SkysGrassSlabs.MOD_ID) public final class ModRegistryEvents { @SubscribeEvent public static void registerBlocks(RegistryEvent.Register event) { diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java b/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java index bd4cccb..61e5c04 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java @@ -32,62 +32,74 @@ public int getMetadata(int damage) { } @Override - public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, - EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, + float hitX, float hitY, float hitZ) { ItemStack stack = player.getHeldItem(hand); - if (stack.isEmpty() || - !player.canPlayerEdit(pos.offset(facing), facing, stack)) { + + if (stack.isEmpty() || !player.canPlayerEdit(pos.offset(facing), facing, stack)) { return EnumActionResult.FAIL; } IBlockState state = world.getBlockState(pos); + if (state.getBlock() == slab) { BlockSlab.EnumBlockHalf half = state.getValue(BlockSlab.HALF); - if ((facing == EnumFacing.UP && half == BlockSlab.EnumBlockHalf.BOTTOM) || - (facing == EnumFacing.DOWN && half == BlockSlab.EnumBlockHalf.TOP)) { + + if ((facing == EnumFacing.UP && half == BlockSlab.EnumBlockHalf.BOTTOM) + || (facing == EnumFacing.DOWN && half == BlockSlab.EnumBlockHalf.TOP)) { return combine(stack, player, world, pos); } } BlockPos adjacent = pos.offset(facing); + if (world.getBlockState(adjacent).getBlock() == slab) { return combine(stack, player, world, adjacent); } + return super.onItemUse(player, world, pos, hand, facing, hitX, hitY, hitZ); } @Override - public boolean canPlaceBlockOnSide(World world, BlockPos pos, EnumFacing side, - EntityPlayer player, ItemStack stack) { + public boolean canPlaceBlockOnSide(World world, BlockPos pos, EnumFacing side, EntityPlayer player, + ItemStack stack) { IBlockState state = world.getBlockState(pos); + if (state.getBlock() == slab) { BlockSlab.EnumBlockHalf half = state.getValue(BlockSlab.HALF); - if ((side == EnumFacing.UP && half == BlockSlab.EnumBlockHalf.BOTTOM) || - (side == EnumFacing.DOWN && half == BlockSlab.EnumBlockHalf.TOP)) { + + if ((side == EnumFacing.UP && half == BlockSlab.EnumBlockHalf.BOTTOM) + || (side == EnumFacing.DOWN && half == BlockSlab.EnumBlockHalf.TOP)) { return true; } } if (world.getBlockState(pos.offset(side)).getBlock() == slab) { return true; } + return super.canPlaceBlockOnSide(world, pos, side, player, stack); } private EnumActionResult combine(ItemStack stack, EntityPlayer player, World world, BlockPos pos) { IBlockState combined = combinedBlock.getDefaultState(); - AxisAlignedBB box = combined.getCollisionBoundingBox(world, pos); - if (box == Block.NULL_AABB || !world.checkNoEntityCollision(box.offset(pos))) { + AxisAlignedBB boundingBox = combined.getCollisionBoundingBox(world, pos); + + if (boundingBox == Block.NULL_AABB || !world.checkNoEntityCollision(boundingBox.offset(pos))) { return EnumActionResult.FAIL; } if (!world.setBlockState(pos, combined, 11)) { return EnumActionResult.FAIL; } + SoundType sound = combinedBlock.getSoundType(combined, world, pos, player); - world.playSound(player, pos, sound.getPlaceSound(), SoundCategory.BLOCKS, - (sound.getVolume() + 1.0F) / 2.0F, sound.getPitch() * 0.8F); + + world.playSound(player, pos, sound.getPlaceSound(), SoundCategory.BLOCKS, (sound.getVolume() + 1.0F) / 2.0F, + sound.getPitch() * 0.8F); + if (!player.capabilities.isCreativeMode) { stack.shrink(1); } + return EnumActionResult.SUCCESS; } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java b/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java index a6bd1a8..bfebeb3 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java @@ -19,24 +19,30 @@ public TurfBlockItem(Block block) { } @Override - public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, - EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, + float hitX, float hitY, float hitZ) { ItemStack stack = player.getHeldItem(hand); IBlockState state = world.getBlockState(pos); + if (facing == EnumFacing.UP && state.getBlock() == ModBlocks.DIRT_SLAB) { if (!player.canPlayerEdit(pos, facing, stack)) { return EnumActionResult.FAIL; } - IBlockState grass = ModBlocks.GRASS_SLAB.getDefaultState() - .withProperty(BlockSlab.HALF, state.getValue(BlockSlab.HALF)); + + IBlockState grass = ModBlocks.GRASS_SLAB.getDefaultState().withProperty(BlockSlab.HALF, + state.getValue(BlockSlab.HALF)); + if (world.setBlockState(pos, grass, 3)) { if (!player.capabilities.isCreativeMode) { stack.shrink(1); } + return EnumActionResult.SUCCESS; } + return EnumActionResult.FAIL; } + return super.onItemUse(player, world, pos, hand, facing, hitX, hitY, hitZ); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java b/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java index 24b3521..1802237 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java @@ -28,11 +28,11 @@ public static void registerModels() { registerModel(ModBlocks.GRASS_SLAB, "grass_slab"); registerModel(ModBlocks.PATH_SLAB, "path_slab"); registerModel(ModBlocks.TURF, "turf"); + if (BuildingBricksCompat.hasLegacyAliases()) { registerLegacyAliasModel(BuildingBricksCompat.grassSlab(), "grass_slab"); registerLegacyAliasModel(BuildingBricksCompat.dirtSlab(), "dirt_slab"); - registerLegacyAliasModel(BuildingBricksCompat.historicalGrassSlab(), - "grass_slab"); + registerLegacyAliasModel(BuildingBricksCompat.historicalGrassSlab(), "grass_slab"); } } @@ -40,20 +40,19 @@ public static void registerModels() { public void init() { BlockColors blockColors = Minecraft.getMinecraft().getBlockColors(); ItemColors itemColors = Minecraft.getMinecraft().getItemColors(); - IBlockColor grassBlockColor = (state, world, pos, tintIndex) -> - world == null || pos == null - ? ColorizerGrass.getGrassColor(0.5D, 1.0D) - : BiomeColorHelper.getGrassColorAtPos(world, pos); + IBlockColor grassBlockColor = (state, world, pos, tintIndex) -> world == null || pos == null + ? ColorizerGrass.getGrassColor(0.5D, 1.0D) + : BiomeColorHelper.getGrassColorAtPos(world, pos); IItemColor grassItemColor = (stack, tintIndex) -> ColorizerGrass.getGrassColor(0.5D, 1.0D); + blockColors.registerBlockColorHandler(grassBlockColor, ModBlocks.GRASS_SLAB, ModBlocks.TURF); - itemColors.registerItemColorHandler(grassItemColor, - Item.getItemFromBlock(ModBlocks.GRASS_SLAB), Item.getItemFromBlock(ModBlocks.TURF)); + itemColors.registerItemColorHandler(grassItemColor, Item.getItemFromBlock(ModBlocks.GRASS_SLAB), + Item.getItemFromBlock(ModBlocks.TURF)); + if (BuildingBricksCompat.hasLegacyAliases()) { - blockColors.registerBlockColorHandler(grassBlockColor, - BuildingBricksCompat.grassSlab(), + blockColors.registerBlockColorHandler(grassBlockColor, BuildingBricksCompat.grassSlab(), BuildingBricksCompat.historicalGrassSlab()); - itemColors.registerItemColorHandler(grassItemColor, - Item.getItemFromBlock(BuildingBricksCompat.grassSlab()), + itemColors.registerItemColorHandler(grassItemColor, Item.getItemFromBlock(BuildingBricksCompat.grassSlab()), Item.getItemFromBlock(BuildingBricksCompat.historicalGrassSlab())); } } @@ -65,11 +64,12 @@ private static void registerModel(Block block, String path) { private static void registerLegacyAliasModel(Block block, String path) { final ResourceLocation model = new ResourceLocation(SkysGrassSlabs.MOD_ID, path); + ModelLoader.setCustomStateMapper(block, new StateMapperBase() { @Override protected ModelResourceLocation getModelResourceLocation(IBlockState state) { - String half = state.getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM - ? "bottom" : "top"; + String half = state.getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM ? "bottom" : "top"; + return new ModelResourceLocation(model, "half=" + half + ",snowy=false"); } }); diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java b/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java index 4f81729..d64e740 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java @@ -17,16 +17,17 @@ import zone.moddev.mc.skysgrassslabs.compat.BuildingBricksCompat; import zone.moddev.mc.skysgrassslabs.init.ModBlocks; -public final class TurfCuttingRecipe extends IForgeRegistryEntry.Impl - implements IRecipe { +public final class TurfCuttingRecipe extends IForgeRegistryEntry.Impl implements IRecipe { private final NonNullList ingredients = createIngredients(); @Override public boolean matches(InventoryCrafting inventory, World world) { int grassInputs = 0; int shovels = 0; + for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); + if (stack.isEmpty()) { continue; } @@ -38,6 +39,7 @@ public boolean matches(InventoryCrafting inventory, World world) { return false; } } + return grassInputs == 1 && shovels == 1; } @@ -68,53 +70,65 @@ public boolean isDynamic() { @Override public NonNullList getRemainingItems(InventoryCrafting inventory) { - NonNullList remaining = NonNullList.withSize( - inventory.getSizeInventory(), ItemStack.EMPTY); + NonNullList remaining = NonNullList.withSize(inventory.getSizeInventory(), ItemStack.EMPTY); + for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); + if (stack.isEmpty()) { continue; } + ItemStack soil = soilRemainder(stack); + if (soil != null) { remaining.set(slot, soil); } else if (isShovel(stack)) { ItemStack shovel = stack.copy(); + shovel.setCount(1); remaining.set(slot, shovel); } } + return remaining; } private static boolean isShovel(ItemStack stack) { Set toolClasses = stack.getItem().getToolClasses(stack); + return toolClasses != null && toolClasses.contains("shovel"); } private static NonNullList createIngredients() { NonNullList result = NonNullList.create(); - result.add(Ingredient.fromStacks(new ItemStack(Blocks.GRASS), - new ItemStack(ModBlocks.GRASS_SLAB))); + + result.add(Ingredient.fromStacks(new ItemStack(Blocks.GRASS), new ItemStack(ModBlocks.GRASS_SLAB))); List shovels = new ArrayList(); + for (Item item : ForgeRegistries.ITEMS.getValuesCollection()) { ItemStack candidate = new ItemStack(item); - if (isShovel(candidate)) shovels.add(candidate); + + if (isShovel(candidate)) { + shovels.add(candidate); + } } + result.add(new ShovelIngredient(shovels.toArray(new ItemStack[shovels.size()]))); return result; } private static ItemStack soilRemainder(ItemStack stack) { Item item = stack.getItem(); + if (item == Item.getItemFromBlock(Blocks.GRASS)) { return new ItemStack(Blocks.DIRT); } - if (item == Item.getItemFromBlock(ModBlocks.GRASS_SLAB) || - BuildingBricksCompat.isGrassSlabItem(stack)) { + if (item == Item.getItemFromBlock(ModBlocks.GRASS_SLAB) || BuildingBricksCompat.isGrassSlabItem(stack)) { return new ItemStack(ModBlocks.DIRT_SLAB); } + return null; } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java b/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java index e2054fe..374c307 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java @@ -15,54 +15,61 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import zone.moddev.mc.skysgrassslabs.init.ModBlocks; +/** Plans a chunk's grass caps before writing any, without loading neighbouring chunks. */ public final class GrassSlabSmoothingHandler { public static final String FEATURE_ID = "skysgrassslabs:grass_slab_smoothing"; - private static final ThreadLocal DECISIONS = - new ThreadLocal() { - @Override - protected boolean[] initialValue() { - return new boolean[256]; - } - }; + private static final ThreadLocal DECISIONS = new ThreadLocal() { + @Override + protected boolean[] initialValue() { + return new boolean[256]; + } + }; - @SubscribeEvent(priority = EventPriority.LOWEST) + @SubscribeEvent( + priority = EventPriority.LOWEST) public void beforeDecoration(DecorateBiomeEvent.Pre event) { World world = event.getWorld(); + if (world.isRemote || world.provider.getDimension() != 0) { return; } + ChunkPos chunkPos = new ChunkPos(event.getPos()); Chunk owner = loadedChunk(world, chunkPos.x, chunkPos.z); + if (owner == null) { return; } boolean[] decisions = DECISIONS.get(); + for (int index = 0; index < decisions.length; ++index) { decisions[index] = false; } int startX = chunkPos.getXStart(); int startZ = chunkPos.getZStart(); + for (int localZ = 0; localZ < 16; ++localZ) { for (int localX = 0; localX < 16; ++localX) { int worldX = startX + localX; int worldZ = startZ + localZ; int surfaceY = owner.getHeightValue(localX, localZ) - 1; + if (surfaceY < 0 || surfaceY >= 255) { continue; } + BlockPos surface = new BlockPos(worldX, surfaceY, worldZ); BlockPos target = surface.up(); IBlockState lower = owner.getBlockState(surface); IBlockState targetState = owner.getBlockState(target); boolean lowerGrass = lower.getBlock() == Blocks.GRASS; boolean clear = targetState.getBlock().isAir(targetState, world, target); - boolean dry = targetState.getMaterial() != Material.WATER && - targetState.getMaterial() != Material.LAVA; + boolean dry = targetState.getMaterial() != Material.WATER && targetState.getMaterial() != Material.LAVA; boolean supported = lower.isSideSolid(world, surface, EnumFacing.UP); - boolean hasBlockEntity = owner.getTileEntity(target, - Chunk.EnumCreateEntityType.CHECK) != null; + boolean hasBlockEntity = owner.getTileEntity(target, Chunk.EnumCreateEntityType.CHECK) != null; + if (!lowerGrass || !clear || !dry || !supported || hasBlockEntity) { continue; } @@ -74,24 +81,30 @@ public void beforeDecoration(DecorateBiomeEvent.Pre event) { } boolean changed = false; + for (int index = 0; index < decisions.length; ++index) { if (!decisions[index]) { continue; } + int localX = index & 15; int localZ = index >>> 4; int surfaceY = owner.getHeightValue(localX, localZ) - 1; BlockPos target = new BlockPos(startX + localX, surfaceY + 1, startZ + localZ); + setGenerationState(owner, target, ModBlocks.GRASS_SLAB.getDefaultState()); + BlockPos support = target.down(); + if (owner.getBlockState(support).getBlock() == Blocks.GRASS) { setGenerationState(owner, support, Blocks.DIRT.getDefaultState()); } + changed = true; } if (changed) { - // Chunk#setBlockState performs cross-chunk lighting work in 1.12. - // Rebuild the owning chunk once after the two-pass write instead. + // Chunk#setBlockState performs cross-chunk lighting work in 1.12. Rebuild the owning chunk once after the + // two-pass write instead. owner.generateSkylightMap(); owner.markDirty(); } @@ -101,60 +114,58 @@ private static void setGenerationState(Chunk chunk, BlockPos pos, IBlockState st ExtendedBlockStorage[] storage = chunk.getBlockStorageArray(); int sectionIndex = pos.getY() >> 4; ExtendedBlockStorage section = storage[sectionIndex]; + if (section == Chunk.NULL_BLOCK_STORAGE) { - section = new ExtendedBlockStorage(sectionIndex << 4, - chunk.getWorld().provider.hasSkyLight()); + section = new ExtendedBlockStorage(sectionIndex << 4, chunk.getWorld().provider.hasSkyLight()); storage[sectionIndex] = section; } + section.set(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15, state); } - private static boolean hasHigherGrassNeighbour(World world, ChunkPos owner, - int localX, int localZ, int lowerSurfaceY) { - if (localX > 0 && isHigherGrass(world, owner.x, owner.z, - localX - 1, localZ, lowerSurfaceY)) { + private static boolean hasHigherGrassNeighbour(World world, ChunkPos owner, int localX, int localZ, + int lowerSurfaceY) { + if (localX > 0 && isHigherGrass(world, owner.x, owner.z, localX - 1, localZ, lowerSurfaceY)) { return true; } - if (localZ > 0 && isHigherGrass(world, owner.x, owner.z, - localX, localZ - 1, lowerSurfaceY)) { + if (localZ > 0 && isHigherGrass(world, owner.x, owner.z, localX, localZ - 1, lowerSurfaceY)) { return true; } if (localX < 15) { - if (isHigherGrass(world, owner.x, owner.z, - localX + 1, localZ, lowerSurfaceY)) { + if (isHigherGrass(world, owner.x, owner.z, localX + 1, localZ, lowerSurfaceY)) { return true; } - } else if (isHigherGrass(world, owner.x + 1, owner.z, - 0, localZ, lowerSurfaceY)) { + } else if (isHigherGrass(world, owner.x + 1, owner.z, 0, localZ, lowerSurfaceY)) { return true; } if (localZ < 15) { - return isHigherGrass(world, owner.x, owner.z, - localX, localZ + 1, lowerSurfaceY); + return isHigherGrass(world, owner.x, owner.z, localX, localZ + 1, lowerSurfaceY); } - return isHigherGrass(world, owner.x, owner.z + 1, - localX, 0, lowerSurfaceY); + + return isHigherGrass(world, owner.x, owner.z + 1, localX, 0, lowerSurfaceY); } - private static boolean isHigherGrass(World world, int chunkX, int chunkZ, - int localX, int localZ, int lowerSurfaceY) { + private static boolean isHigherGrass(World world, int chunkX, int chunkZ, int localX, int localZ, + int lowerSurfaceY) { Chunk chunk = loadedChunk(world, chunkX, chunkZ); + if (chunk == null) { return false; } + int neighbourSurfaceY = chunk.getHeightValue(localX, localZ) - 1; - BlockPos neighbour = new BlockPos((chunkX << 4) + localX, - neighbourSurfaceY, (chunkZ << 4) + localZ); - boolean neighbourGrass = neighbourSurfaceY >= 0 && - chunk.getBlockState(neighbour).getBlock() == Blocks.GRASS; - return SmoothingDecision.shouldPlace(lowerSurfaceY, neighbourSurfaceY, - true, neighbourGrass, true, true, true, false); + BlockPos neighbour = new BlockPos((chunkX << 4) + localX, neighbourSurfaceY, (chunkZ << 4) + localZ); + boolean neighbourGrass = neighbourSurfaceY >= 0 && chunk.getBlockState(neighbour).getBlock() == Blocks.GRASS; + + return SmoothingDecision.shouldPlace(lowerSurfaceY, neighbourSurfaceY, true, neighbourGrass, true, true, true, + false); } private static Chunk loadedChunk(World world, int chunkX, int chunkZ) { if (!(world.getChunkProvider() instanceof ChunkProviderServer)) { return null; } + return ((ChunkProviderServer) world.getChunkProvider()).getLoadedChunk(chunkX, chunkZ); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/world/ModWorldState.java b/src/main/java/zone/moddev/mc/skysgrassslabs/world/ModWorldState.java index 56bb8bb..992e579 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/world/ModWorldState.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/world/ModWorldState.java @@ -30,18 +30,23 @@ public ModWorldState(String name) { } public static ModWorldState get(World world) { - if (!world.isRemote && world.provider.getDimension() != 0 && - world.getMinecraftServer() != null) { + if (!world.isRemote && world.provider.getDimension() != 0 && world.getMinecraftServer() != null) { World overworld = world.getMinecraftServer().getWorld(0); - if (overworld != null) world = overworld; + + if (overworld != null) { + world = overworld; + } } + MapStorage storage = world.getMapStorage(); ModWorldState state = (ModWorldState) storage.getOrLoadData(ModWorldState.class, DATA_NAME); + if (state == null) { state = new ModWorldState(DATA_NAME); storage.setData(DATA_NAME, state); state.markDirty(); } + return state; } @@ -56,10 +61,17 @@ public void recordGrassBlocks(long count) { } public void recordGrassBlocks(long count, int metadata) { - if (count <= 0) return; + if (count <= 0) { + return; + } + recordGrassBlocks(count); - if ((metadata & 1) == 0) migratedGrassBlocksTop += count; - else migratedGrassBlocksBottom += count; + + if ((metadata & 1) == 0) { + migratedGrassBlocksTop += count; + } else { + migratedGrassBlocksBottom += count; + } } public void recordDirtBlocks(long count) { @@ -68,10 +80,17 @@ public void recordDirtBlocks(long count) { } public void recordDirtBlocks(long count, int metadata) { - if (count <= 0) return; + if (count <= 0) { + return; + } + recordDirtBlocks(count); - if ((metadata & 1) == 0) migratedDirtBlocksTop += count; - else migratedDirtBlocksBottom += count; + + if ((metadata & 1) == 0) { + migratedDirtBlocksTop += count; + } else { + migratedDirtBlocksBottom += count; + } } public void recordGrassItems(long count) { @@ -84,9 +103,12 @@ public void recordDirtItems(long count) { markDirty(); } - public void recordUnsupported(String id, long count) { - if (count <= 0) return; - unsupported.put(id, unsupported.containsKey(id) ? unsupported.get(id) + count : count); + public void recordUnsupported(String registryId, long count) { + if (count <= 0) { + return; + } + + unsupported.put(registryId, unsupported.containsKey(registryId) ? unsupported.get(registryId) + count : count); markDirty(); } @@ -142,9 +164,12 @@ public void readFromNBT(NBTTagCompound nbt) { migratedGrassItems = nbt.getLong("migrated_grass_items"); migratedDirtItems = nbt.getLong("migrated_dirt_items"); unsupported.clear(); + NBTTagList list = nbt.getTagList("unsupported", 10); + for (int index = 0; index < list.tagCount(); ++index) { NBTTagCompound entry = list.getCompoundTagAt(index); + unsupported.put(entry.getString("id"), entry.getLong("count")); } } @@ -162,13 +187,17 @@ public NBTTagCompound writeToNBT(NBTTagCompound nbt) { nbt.setLong("migrated_dirt_blocks_bottom", migratedDirtBlocksBottom); nbt.setLong("migrated_grass_items", migratedGrassItems); nbt.setLong("migrated_dirt_items", migratedDirtItems); + NBTTagList list = new NBTTagList(); + for (Map.Entry value : unsupported.entrySet()) { NBTTagCompound entry = new NBTTagCompound(); + entry.setString("id", value.getKey()); entry.setLong("count", value.getValue()); list.appendTag(entry); } + nbt.setTag("unsupported", list); return nbt; } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecision.java b/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecision.java index 4d3e910..c4aab0b 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecision.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecision.java @@ -1,13 +1,11 @@ package zone.moddev.mc.skysgrassslabs.world; public final class SmoothingDecision { - public static boolean shouldPlace(int lowerSurfaceY, int neighbourSurfaceY, - boolean lowerIsNaturalGrass, boolean neighbourIsNaturalGrass, - boolean targetClear, boolean targetDry, boolean supported, + public static boolean shouldPlace(int lowerSurfaceY, int neighbourSurfaceY, boolean lowerIsNaturalGrass, + boolean neighbourIsNaturalGrass, boolean targetClear, boolean targetDry, boolean supported, boolean targetHasBlockEntity) { - return neighbourSurfaceY == lowerSurfaceY + 1 && lowerIsNaturalGrass && - neighbourIsNaturalGrass && targetClear && targetDry && supported && - !targetHasBlockEntity; + return neighbourSurfaceY == lowerSurfaceY + 1 && lowerIsNaturalGrass && neighbourIsNaturalGrass && targetClear + && targetDry && supported && !targetHasBlockEntity; } private SmoothingDecision() { diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/ForwardUpgradeFixtureContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/ForwardUpgradeFixtureContractTest.java index b625275..dbb3fc4 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/ForwardUpgradeFixtureContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/ForwardUpgradeFixtureContractTest.java @@ -19,19 +19,19 @@ import org.junit.jupiter.api.Test; class ForwardUpgradeFixtureContractTest { - private static final File FIXTURE = new File( - "src/test/resources/fixtures/skysgrassslabs-1.10.2-forward-world.zip"); + private static final File FIXTURE = new File("src/test/resources/fixtures/skysgrassslabs-1.10.2-forward-world.zip"); private static final File MANIFEST = new File( "src/test/resources/fixtures/skysgrassslabs-1.10.2-forward-world.manifest"); @Test void fixtureIsTheLockedGenuineOneTenWorld() throws Exception { Properties manifest = new Properties(); + try (InputStream input = Files.newInputStream(MANIFEST.toPath())) { manifest.load(input); } - assertEquals("D6923BFFE062C1F0C454190AB11F031825949DF8080D8000133A723DEC2770BF", - sha256(FIXTURE)); + + assertEquals("D6923BFFE062C1F0C454190AB11F031825949DF8080D8000133A723DEC2770BF", sha256(FIXTURE)); assertEquals(sha256(FIXTURE), manifest.getProperty("fixture_sha256")); assertEquals("1.10.2", manifest.getProperty("source_minecraft")); assertEquals("1.0.0.110021", manifest.getProperty("source_mod_version")); @@ -42,26 +42,26 @@ void fixtureIsTheLockedGenuineOneTenWorld() throws Exception { @Test void fixtureContainsOnlyTheRequiredWorldFiles() throws Exception { - Set expected = new TreeSet(Arrays.asList( - "data/capabilities.dat", - "data/skysgrassslabs_world_state.dat", - "level.dat", - "level.dat_old", - "region/r.0.0.mca", - "skysgrassslabs-forward-fixture.properties")); + Set expected = new TreeSet( + Arrays.asList("data/capabilities.dat", "data/skysgrassslabs_world_state.dat", "level.dat", + "level.dat_old", "region/r.0.0.mca", "skysgrassslabs-forward-fixture.properties")); + try (ZipFile zip = new ZipFile(FIXTURE)) { Set actual = new TreeSet(); + zip.stream().filter(entry -> !entry.isDirectory()) .forEach(entry -> actual.add(entry.getName().replace('\\', '/'))); assertEquals(expected, actual); assertFalse(actual.stream().anyMatch(name -> { String lower = name.toLowerCase(Locale.ROOT); - return lower.contains("playerdata") || lower.contains("agent") || - lower.contains(".codex") || lower.contains(".claude"); + + return lower.contains("playerdata") || lower.contains("agent") || lower.contains(".codex") + || lower.contains(".claude"); })); ZipEntry marker = zip.getEntry("skysgrassslabs-forward-fixture.properties"); String contents = new String(readAll(zip.getInputStream(marker)), StandardCharsets.UTF_8); + assertTrue(contents.contains("source_minecraft=1.10.2")); assertTrue(contents.contains("source_mod_version=1.0.0.110021")); assertTrue(contents.contains("expected_blocks=7")); @@ -72,26 +72,36 @@ void fixtureContainsOnlyTheRequiredWorldFiles() throws Exception { private static String sha256(File file) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (InputStream input = Files.newInputStream(file.toPath())) { byte[] buffer = new byte[8192]; + for (int read = input.read(buffer); read >= 0; read = input.read(buffer)) { - if (read > 0) digest.update(buffer, 0, read); + if (read > 0) { + digest.update(buffer, 0, read); + } } } + StringBuilder result = new StringBuilder(); + for (byte value : digest.digest()) { result.append(String.format("%02X", value & 0xff)); } + return result.toString(); } private static byte[] readAll(InputStream input) throws Exception { - try (InputStream source = input; java.io.ByteArrayOutputStream output = - new java.io.ByteArrayOutputStream()) { + try (InputStream source = input; java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; + for (int read = source.read(buffer); read >= 0; read = source.read(buffer)) { - if (read > 0) output.write(buffer, 0, read); + if (read > 0) { + output.write(buffer, 0, read); + } } + return output.toByteArray(); } } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/LocalizationContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/LocalizationContractTest.java index ec177b8..c56d4d7 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/LocalizationContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/LocalizationContractTest.java @@ -23,29 +23,30 @@ import org.junit.jupiter.api.Test; class LocalizationContractTest { - private static final File LANG_DIR = - new File("src/main/resources/assets/skysgrassslabs/lang"); - private static final List KEYS = Arrays.asList( - "tile.skysgrassslabs.dirt_slab.name", - "tile.skysgrassslabs.grass_slab.name", - "tile.skysgrassslabs.path_slab.name", + private static final File LANG_DIR = new File("src/main/resources/assets/skysgrassslabs/lang"); + private static final List KEYS = Arrays.asList("tile.skysgrassslabs.dirt_slab.name", + "tile.skysgrassslabs.grass_slab.name", "tile.skysgrassslabs.path_slab.name", "tile.skysgrassslabs.turf.name"); - private static final List LOCALES = Arrays.asList( - "de_at", "de_au", "de_de", - "en_ca", "en_en", "en_gb", "en_pt", "en_us", - "es_es", "es_mx", "fr_ca", "fr_fr", "ja_jp", "ko_kr", - "pt_br", "pt_pt", "ru_ru", "zh_cn"); + private static final List LOCALES = Arrays.asList("de_at", "de_au", "de_de", "en_ca", "en_en", "en_gb", + "en_pt", "en_us", "es_es", "es_mx", "fr_ca", "fr_fr", "ja_jp", "ko_kr", "pt_br", "pt_pt", "ru_ru", "zh_cn"); @Test void allEighteenLocalesHaveExactOrderedKeyParity() throws Exception { - File[] files = LANG_DIR.listFiles((dir, name) -> name.endsWith(".lang")); + File[] files = LANG_DIR.listFiles((directory, name) -> name.endsWith(".lang")); + assertNotNull(files); + Set actual = new LinkedHashSet(); - for (File file : files) actual.add(stripExtension(file.getName())); + + for (File file : files) { + actual.add(stripExtension(file.getName())); + } + assertEquals(new LinkedHashSet(LOCALES), actual); for (String locale : LOCALES) { Map translations = read(locale); + assertEquals(KEYS, new ArrayList(translations.keySet()), locale); assertEquals(4, translations.size(), locale); } @@ -55,18 +56,20 @@ void allEighteenLocalesHaveExactOrderedKeyParity() throws Exception { void localeFilesAreCleanUtf8() throws Exception { for (String locale : LOCALES) { byte[] bytes = Files.readAllBytes(file(locale).toPath()); + assertFalse(hasUtf8Bom(bytes), locale + " must not contain a UTF-8 BOM"); + String content = decodeUtf8(bytes, locale); + assertTrue(content.endsWith("\n"), locale + " must end with a newline"); assertFalse(content.contains("\r"), locale + " must use LF line endings"); assertFalse(content.contains("\ufffd"), locale + " contains a replacement character"); + for (String line : content.split("\n", -1)) { - assertEquals(line.replaceFirst("[ \\t]+$", ""), line, - locale + " has trailing whitespace"); + assertEquals(line.replaceFirst("[ \\t]+$", ""), line, locale + " has trailing whitespace"); } for (Map.Entry entry : read(locale).entrySet()) { - assertFalse(entry.getValue().trim().isEmpty(), - locale + " has a blank value for " + entry.getKey()); + assertFalse(entry.getValue().trim().isEmpty(), locale + " has a blank value for " + entry.getKey()); } } } @@ -75,30 +78,25 @@ void localeFilesAreCleanUtf8() throws Exception { void translationsAndRegionalDifferencesAreLocked() throws Exception { assertValues("de_at", "Eanstufn", "Grosstufn", "Steigstufn", "Grassodn"); assertValues("de_de", "Erdstufe", "Grasblockstufe", "Trampelpfadstufe", "Grassode"); - assertValues("es_es", "Losa de tierra", "Losa de césped", - "Losa de camino de hierba", "Tepe de césped"); - assertValues("es_mx", "Losa de tierra", "Losa de pasto", - "Losa de sendero de pasto", "Tapete de pasto"); - assertValues("fr_ca", "Dalle de terre", "Dalle de gazon", - "Dalle de sentier de gazon", "Plaque de gazon"); - assertValues("fr_fr", "Dalle de terre", "Dalle d'herbe", - "Dalle de chemin d'herbe", "Plaque de gazon"); - assertValues("ja_jp", "土のハーフブロック", "草ブロックのハーフブロック", - "草の道のハーフブロック", "芝生"); + assertValues("es_es", "Losa de tierra", "Losa de césped", "Losa de camino de hierba", "Tepe de césped"); + assertValues("es_mx", "Losa de tierra", "Losa de pasto", "Losa de sendero de pasto", "Tapete de pasto"); + assertValues("fr_ca", "Dalle de terre", "Dalle de gazon", "Dalle de sentier de gazon", "Plaque de gazon"); + assertValues("fr_fr", "Dalle de terre", "Dalle d'herbe", "Dalle de chemin d'herbe", "Plaque de gazon"); + assertValues("ja_jp", "土のハーフブロック", "草ブロックのハーフブロック", "草の道のハーフブロック", "芝生"); assertValues("ko_kr", "흙 반 블록", "잔디 블록 반 블록", "잔디 길 반 블록", "잔디"); - assertValues("pt_br", "Laje de Terra", "Laje de Bloco de Grama", - "Laje de Caminho de Grama", "Placa de Grama"); - assertValues("pt_pt", "Degrau de Terra", "Degrau de Bloco de Relva", - "Degrau de Caminho de Relva", "Placa de Relva"); + assertValues("pt_br", "Laje de Terra", "Laje de Bloco de Grama", "Laje de Caminho de Grama", "Placa de Grama"); + assertValues("pt_pt", "Degrau de Terra", "Degrau de Bloco de Relva", "Degrau de Caminho de Relva", + "Placa de Relva"); assertValues("ru_ru", "Земляная плита", "Дёрновая плита", "Плита тропы", "Дёрн"); assertValues("zh_cn", "泥土台阶", "草方块台阶", "草径台阶", "草皮"); byte[] english = Files.readAllBytes(file("en_us").toPath()); + for (String locale : Arrays.asList("en_ca", "en_en", "en_gb", "en_pt")) { assertArrayEquals(english, Files.readAllBytes(file(locale).toPath()), locale); } - assertArrayEquals(Files.readAllBytes(file("de_de").toPath()), - Files.readAllBytes(file("de_au").toPath())); + + assertArrayEquals(Files.readAllBytes(file("de_de").toPath()), Files.readAllBytes(file("de_au").toPath())); assertNotEquals(read("es_es"), read("es_mx")); assertNotEquals(read("fr_ca"), read("fr_fr")); assertNotEquals(read("pt_br"), read("pt_pt")); @@ -110,31 +108,36 @@ private static void assertValues(String locale, String... values) throws Excepti private static Map read(String locale) throws Exception { Map values = new LinkedHashMap(); + for (String line : Files.readAllLines(file(locale).toPath(), StandardCharsets.UTF_8)) { - if (line.trim().isEmpty() || line.trim().startsWith("#")) continue; + if (line.trim().isEmpty() || line.trim().startsWith("#")) { + continue; + } + int separator = line.indexOf('='); + assertTrue(separator > 0, locale + " has a malformed line: " + line); + String key = line.substring(0, separator); + assertFalse(values.containsKey(key), locale + " has duplicate key " + key); values.put(key, line.substring(separator + 1)); } + return values; } private static String decodeUtf8(byte[] bytes, String locale) { try { - return StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(bytes)).toString(); + return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT).decode(ByteBuffer.wrap(bytes)).toString(); } catch (CharacterCodingException exception) { throw new AssertionError(locale + " is not valid UTF-8", exception); } } private static boolean hasUtf8Bom(byte[] bytes) { - return bytes.length >= 3 && bytes[0] == (byte) 0xef - && bytes[1] == (byte) 0xbb && bytes[2] == (byte) 0xbf; + return bytes.length >= 3 && bytes[0] == (byte) 0xef && bytes[1] == (byte) 0xbb && bytes[2] == (byte) 0xbf; } private static File file(String locale) { diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java index 1907fab..8daccec 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java @@ -17,6 +17,7 @@ class ProjectContractTest { @Test void releaseIdentityAndToolchainArePinned() throws Exception { String properties = read("gradle.properties"); + assertTrue(properties.contains("minecraft_version=1.12.2")); assertTrue(properties.contains("forge_version=14.23.5.2859")); assertTrue(properties.contains("mapping_version=39-1.12")); @@ -33,18 +34,21 @@ void saveFacingIdsAndLegacyRecipeKeyRemainStable() throws Exception { String config = read("src/main/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfig.java"); String worldState = read("src/main/java/zone/moddev/mc/skysgrassslabs/world/ModWorldState.java"); String recipes = read("src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java"); - String registryEvents = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java"); - for (String id : new String[] {"dirt_slab", "grass_slab", "path_slab", "turf"}) { - assertTrue(blocks.contains("\"" + id + "\"")); + String registryEvents = read("src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRegistryEvents.java"); + + for (String registryId : new String[] { "dirt_slab", "grass_slab", "path_slab", "turf" }) { + assertTrue(blocks.contains("\"" + registryId + "\"")); } + assertTrue(worldState.contains("skysgrassslabs_world_state")); assertTrue(worldState.contains("SCHEMA_VERSION = 1")); assertTrue(recipes.contains("skysgrassslabs:turf_cutting")); - for (String id : new String[] {"dirt_slab", "grass_slab", - "grass_block_from_seeds", "grass_slab_from_seeds", "turf"}) { - assertTrue(recipes.contains("\"" + id + "\"")); + + for (String registryId : new String[] { "dirt_slab", "grass_slab", "grass_block_from_seeds", + "grass_slab_from_seeds", "turf" }) { + assertTrue(recipes.contains("\"" + registryId + "\"")); } + assertTrue(registryEvents.contains("RegistryEvent.Register")); assertTrue(registryEvents.contains("RegistryEvent.Register")); assertTrue(registryEvents.contains("RegistryEvent.Register")); @@ -55,10 +59,9 @@ void saveFacingIdsAndLegacyRecipeKeyRemainStable() throws Exception { @Test void turfRecipeIsDiscoverableThroughTheRecipeBook() throws Exception { - String recipe = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java"); - String advancement = read( - "src/main/resources/assets/skysgrassslabs/advancements/recipes/turf.json"); + String recipe = read("src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java"); + String advancement = read("src/main/resources/assets/skysgrassslabs/advancements/recipes/turf.json"); + assertTrue(recipe.contains("public boolean isDynamic()")); assertTrue(recipe.contains("return false;")); assertTrue(recipe.contains("public NonNullList getIngredients()")); @@ -72,19 +75,15 @@ void turfRecipeIsDiscoverableThroughTheRecipeBook() throws Exception { @Test void oneTwelveResourcesAndLegacyRecoveryUseTheTargetNativeContracts() throws Exception { String pack = read("src/main/resources/pack.mcmeta"); - String compatibility = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java"); - String migration = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java"); - String client = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java"); - String clientRegistry = read( - "src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java"); + String compatibility = read("src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java"); + String migration = read("src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java"); + String client = read("src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java"); + String clientRegistry = read("src/main/java/zone/moddev/mc/skysgrassslabs/init/ClientRegistryEvents.java"); + assertTrue(pack.contains("\"pack_format\": 3")); assertTrue(compatibility.contains("legacyAlias(GRASS_SLAB_ID, true)")); assertTrue(compatibility.contains("legacyAlias(DIRT_SLAB_ID, false)")); - assertTrue(compatibility.contains( - "legacyAlias(HISTORICAL_GRASS_SLAB_ID, true)")); + assertTrue(compatibility.contains("legacyAlias(HISTORICAL_GRASS_SLAB_ID, true)")); assertTrue(migration.contains("RegistryEvent.MissingMappings")); assertTrue(migration.contains("RegistryEvent.MissingMappings")); assertTrue(migration.contains("event.getAllMappings()")); @@ -99,12 +98,15 @@ void oneTwelveResourcesAndLegacyRecoveryUseTheTargetNativeContracts() throws Exc @Test void localContextIsIgnoredAndModernDataResourcesAreAbsent() throws Exception { String ignore = read(".gitignore"); + assertTrue(ignore.contains("AGENTS.md")); assertTrue(ignore.contains("agent-notes/")); assertTrue(ignore.contains(".codex/")); assertTrue(ignore.contains(".claude/")); assertFalse(new File("src/main/resources/META-INF/mods.toml").exists()); + File modernData = new File("src/main/resources/data"); + if (modernData.isDirectory()) { try (Stream paths = Files.walk(modernData.toPath())) { assertFalse(paths.anyMatch(Files::isRegularFile)); @@ -115,6 +117,7 @@ void localContextIsIgnoredAndModernDataResourcesAreAbsent() throws Exception { @Test void releaseTagWorkflowPointsAtTheManualDispatcherInputs() throws Exception { String workflow = read(".github/workflows/release-on-tag.yml"); + assertTrue(workflow.contains("release_version:")); assertTrue(workflow.contains("curseforge_release_level:")); assertTrue(workflow.contains("confirm_live_publication:")); @@ -127,34 +130,41 @@ void releaseTagWorkflowPointsAtTheManualDispatcherInputs() throws Exception { @Test void codeQlCannotReuseCachedCompilationOutput() throws Exception { String workflow = read(".github/workflows/codeql-analysis.yml"); + assertTrue(workflow.contains("clean classes --rerun-tasks --no-daemon")); } @Test void releaseArchivesNormalizeLicenceLineEndings() throws Exception { String build = read("build.gradle"); + assertTrue(build.contains("'**/LICENSE', '**/LICENSE.spdx', '**/NOTICE'")); } @Test void localContextIsNotTracked() throws Exception { - Process process = new ProcessBuilder("git", "ls-files", "-z") - .redirectErrorStream(true).start(); + Process process = new ProcessBuilder("git", "ls-files", "-z").redirectErrorStream(true).start(); ByteArrayOutputStream output = new ByteArrayOutputStream(); InputStream input = process.getInputStream(); byte[] buffer = new byte[4096]; + for (int read = input.read(buffer); read >= 0; read = input.read(buffer)) { - if (read > 0) output.write(buffer, 0, read); + if (read > 0) { + output.write(buffer, 0, read); + } } + assertTrue(process.waitFor() == 0, output.toString(StandardCharsets.UTF_8.name())); String[] tracked = new String(output.toByteArray(), StandardCharsets.UTF_8).split("\u0000"); + for (String path : tracked) { String lower = ("/" + path.replace('\\', '/')).toLowerCase(Locale.ROOT); - assertFalse(lower.endsWith("/agent.md") || lower.endsWith("/agents.md") || - lower.endsWith("/claude.md") || lower.contains("/agent-notes/") || - lower.contains("/agent_notes/") || lower.contains("/.codex/") || - lower.contains("/.claude/") || lower.contains("/.cursor/"), + + assertFalse( + lower.endsWith("/agent.md") || lower.endsWith("/agents.md") || lower.endsWith("/claude.md") + || lower.contains("/agent-notes/") || lower.contains("/agent_notes/") + || lower.contains("/.codex/") || lower.contains("/.claude/") || lower.contains("/.cursor/"), "Local agent context is tracked: " + path); } } @@ -162,11 +172,14 @@ void localContextIsNotTracked() throws Exception { @Test void blockModelsUseLegacySlabParents() throws Exception { Path models = new File("src/main/resources/assets/skysgrassslabs/models/block").toPath(); + try (Stream paths = Files.walk(models)) { for (Path path : (Iterable) paths.filter(Files::isRegularFile)::iterator) { String model = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); - assertFalse(model.contains("\"parent\": \"block/slab\"") || - model.contains("\"parent\": \"block/slab_top\""), + + assertFalse( + model.contains("\"parent\": \"block/slab\"") + || model.contains("\"parent\": \"block/slab_top\""), "Modern slab model parent in " + path); } } @@ -176,19 +189,22 @@ void blockModelsUseLegacySlabParents() throws Exception { void snowCapsUseUntintedSnowTopsAndSnowEdgedDirtSides() throws Exception { String dirtState = read("src/main/resources/assets/skysgrassslabs/blockstates/dirt_slab.json"); String grassState = read("src/main/resources/assets/skysgrassslabs/blockstates/grass_slab.json"); + assertTrue(dirtState.contains("half=bottom,snowy=true")); assertTrue(dirtState.contains("half=top,snowy=true")); assertTrue(grassState.contains("half=bottom,snowy=true")); assertTrue(grassState.contains("half=top,snowy=true")); - for (String name : new String[] {"dirt_slab_snow.json", "dirt_slab_top_snow.json"}) { + for (String name : new String[] { "dirt_slab_snow.json", "dirt_slab_top_snow.json" }) { String model = read("src/main/resources/assets/skysgrassslabs/models/block/" + name); + assertTrue(model.contains("\"top\": \"blocks/snow\"")); assertTrue(model.contains("\"side\": \"blocks/grass_side_snowed\"")); assertFalse(upFace(model).contains("tintindex")); } - for (String name : new String[] {"grass_slab_snow.json", "grass_slab_top_snow.json"}) { + for (String name : new String[] { "grass_slab_snow.json", "grass_slab_top_snow.json" }) { String model = read("src/main/resources/assets/skysgrassslabs/models/block/" + name); + assertTrue(model.contains("\"top\": \"blocks/snow\"")); assertTrue(model.contains("\"side\": \"blocks/grass_side_snowed\"")); assertFalse(upFace(model).contains("tintindex")); @@ -197,11 +213,10 @@ void snowCapsUseUntintedSnowTopsAndSnowEdgedDirtSides() throws Exception { @Test void pathSlabSidesKeepTheVanillaPathSurfaceLip() throws Exception { - String bottom = read( - "src/main/resources/assets/skysgrassslabs/models/block/path_slab.json"); - String top = read( - "src/main/resources/assets/skysgrassslabs/models/block/path_slab_top.json"); - for (String face : new String[] {"north", "south", "west", "east"}) { + String bottom = read("src/main/resources/assets/skysgrassslabs/models/block/path_slab.json"); + String top = read("src/main/resources/assets/skysgrassslabs/models/block/path_slab_top.json"); + + for (String face : new String[] { "north", "south", "west", "east" }) { assertTrue(bottom.contains("\"" + face + "\": { \"uv\": [0, 1, 16, 8]")); assertTrue(top.contains("\"" + face + "\": { \"uv\": [0, 1, 16, 8]")); } @@ -211,6 +226,7 @@ void pathSlabSidesKeepTheVanillaPathSurfaceLip() throws Exception { void turfEatingIsAnAdditiveDuplicateSafeVanillaStyleTask() throws Exception { String events = read("src/main/java/zone/moddev/mc/skysgrassslabs/event/CommonEvents.java"); String task = read("src/main/java/zone/moddev/mc/skysgrassslabs/entity/ai/TurfEatingAI.java"); + assertTrue(events.contains("instanceof TurfEatingAI")); assertTrue(events.contains("addTask(5, new TurfEatingAI(sheep))")); assertTrue(task.contains("setMutexBits(7)")); @@ -224,6 +240,7 @@ void turfEatingIsAnAdditiveDuplicateSafeVanillaStyleTask() throws Exception { private static String upFace(String model) { int start = model.indexOf("\"up\""); int end = model.indexOf('\n', start); + return model.substring(start, end < 0 ? model.length() : end); } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/RepositoryHygieneTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/RepositoryHygieneTest.java index 7e40b76..e2d261c 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/RepositoryHygieneTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/RepositoryHygieneTest.java @@ -21,49 +21,43 @@ import org.junit.jupiter.api.Test; public class RepositoryHygieneTest { - private static final Pattern WINDOWS_ABSOLUTE_PATH = Pattern.compile( - "(?i)(? TEXT_EXTENSIONS = setOf( - "bat", "gradle", "java", "json", "md", "mcmeta", "properties", - "ps1", "sh", "toml", "txt", "xml", "yaml", "yml"); - private static final Set TEXT_NAMES = setOf( - ".gitattributes", ".gitignore", "gradlew", "license", "notice"); - private static final Set FORBIDDEN_DIRECTORY_SEGMENTS = setOf( - ".codex", ".claude", ".continue", ".cursor", ".eclipse", ".gradle", - ".gradle-verify-cache", ".idea", ".kilocode", ".metadata", ".roo", - ".settings", ".vscode", ".windsurf", "agent-notes", "agent_notes", "bin", - "build", "crash-reports", "dist", "eclipse", "evidence", "logs", - "mcmodsrepo", "out", "release", "run", "run-data", "test-output", - "test-reports", "test-results"); - private static final Set FORBIDDEN_FILE_NAMES = setOf( - ".classpath", ".project", "agent.md", "agents.md", "claude.md", - "gemini.md", "secret.json"); + private static final Pattern WINDOWS_ABSOLUTE_PATH = Pattern.compile("(?i)(? TEXT_EXTENSIONS = setOf("bat", "gradle", "java", "json", "md", "mcmeta", + "properties", "ps1", "sh", "toml", "txt", "xml", "yaml", "yml"); + private static final Set TEXT_NAMES = setOf(".gitattributes", ".gitignore", "gradlew", "license", "notice"); + private static final Set FORBIDDEN_DIRECTORY_SEGMENTS = setOf(".codex", ".claude", ".continue", ".cursor", + ".eclipse", ".gradle", ".gradle-verify-cache", ".idea", ".kilocode", ".metadata", ".roo", ".settings", + ".vscode", ".windsurf", "agent-notes", "agent_notes", "bin", "build", "crash-reports", "dist", "eclipse", + "evidence", "logs", "mcmodsrepo", "out", "release", "run", "run-data", "test-output", "test-reports", + "test-results"); + private static final Set FORBIDDEN_FILE_NAMES = setOf(".classpath", ".project", "agent.md", "agents.md", + "claude.md", "gemini.md", "secret.json"); @Test public void trackedTreeContainsNoLocalOnlyFilesOrMachineSpecificText() throws Exception { assumeGitCheckout(); CommandResult tracked = git("ls-files", "-z"); + assertEquals(0, tracked.exitCode, tracked.output); for (String file : tracked.output.split("\\u0000")) { if (file.isEmpty()) { continue; } + assertFalse(isLocalOnlyPath(file), "Local-only file is tracked: " + file); + if (isTextFile(file)) { - String text = new String(Files.readAllBytes(Paths.get(file)), - StandardCharsets.UTF_8); + String text = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8); + assertFalse(WINDOWS_ABSOLUTE_PATH.matcher(text).find(), "Windows absolute path in tracked file: " + file); - assertFalse(USER_HOME_PATH.matcher(text).find(), - "User-home path in tracked file: " + file); - assertFalse(PERSONAL_EMAIL.matcher(text).find(), - "Personal email address in tracked file: " + file); + assertFalse(USER_HOME_PATH.matcher(text).find(), "User-home path in tracked file: " + file); + assertFalse(PERSONAL_EMAIL.matcher(text).find(), "Personal email address in tracked file: " + file); } } } @@ -72,54 +66,30 @@ public void trackedTreeContainsNoLocalOnlyFilesOrMachineSpecificText() throws Ex public void localContextPatternsAreIgnoredAtRootAndNestedDepths() throws Exception { assumeGitCheckout(); - List ignoredCandidates = Arrays.asList( - "AGENTS.md", - "nested/AGENTS.md", - "agent-notes/README.md", - "nested/agent-notes/README.md", - ".codex/state.json", - "nested/.codex/state.json", - ".claude/settings.json", - "nested/.cursor/rules/project.md", - ".vscode/settings.json", - "nested/.continue/config.json", - ".windsurf/rules/project.md", - "nested/.roo/rules.md", - ".kilocode/settings.json", - ".aider.conf.yml", - "project.code-workspace", - ".github/copilot-instructions.md", - ".github/instructions/project.instructions.md", - "runClient.launch", - "run-server-smoke/output.log", - "evidence/report.txt", - ".settings/org.eclipse.buildship.core.prefs", - ".classpath", - ".project"); + List ignoredCandidates = Arrays.asList("AGENTS.md", "nested/AGENTS.md", "agent-notes/README.md", + "nested/agent-notes/README.md", ".codex/state.json", "nested/.codex/state.json", + ".claude/settings.json", "nested/.cursor/rules/project.md", ".vscode/settings.json", + "nested/.continue/config.json", ".windsurf/rules/project.md", "nested/.roo/rules.md", + ".kilocode/settings.json", ".aider.conf.yml", "project.code-workspace", + ".github/copilot-instructions.md", ".github/instructions/project.instructions.md", "runClient.launch", + "run-server-smoke/output.log", "evidence/report.txt", ".settings/org.eclipse.buildship.core.prefs", + ".classpath", ".project"); for (String candidate : ignoredCandidates) { CommandResult result = git("check-ignore", "--quiet", "--no-index", "--", candidate); - assertEquals(0, result.exitCode, - "Expected ignored path: " + candidate + "\n" + result.output); + + assertEquals(0, result.exitCode, "Expected ignored path: " + candidate + "\n" + result.output); } } @Test public void localOnlyClassifierCoversGeneratedAndLocalContext() { - List localOnlyCandidates = Arrays.asList( - "AGENTS.md", - "nested/agent-notes/README.md", - ".github/copilot-instructions.md", - ".github/instructions/project.instructions.md", - "nested/.windsurf/rules/project.md", - "run-server-smoke/output.log", - "evidence/report.txt", - ".metadata/.plugins/state.dat", - "project.iml", - "forge-1.10.2-changelog.txt", - "README.txt", - "workspace.code-workspace", - ".env.local"); + List localOnlyCandidates = Arrays.asList("AGENTS.md", "nested/agent-notes/README.md", + ".github/copilot-instructions.md", ".github/instructions/project.instructions.md", + "nested/.windsurf/rules/project.md", "run-server-smoke/output.log", "evidence/report.txt", + ".metadata/.plugins/state.dat", "project.iml", "forge-1.10.2-changelog.txt", "README.txt", + "workspace.code-workspace", ".env.local"); + for (String candidate : localOnlyCandidates) { assertTrue(isLocalOnlyPath(candidate), "Expected local-only path: " + candidate); } @@ -132,71 +102,60 @@ public void localOnlyClassifierCoversGeneratedAndLocalContext() { private static void assumeGitCheckout() throws Exception { CommandResult result = git("rev-parse", "--is-inside-work-tree"); + assumeTrue(result.exitCode == 0 && result.output.trim().equals("true"), "Repository hygiene checks require a Git checkout"); } private static boolean isLocalOnlyPath(String file) { String normalized = file.replace('\\', '/').toLowerCase(Locale.ROOT); - if (normalized.equals(".github/copilot-instructions.md") - || normalized.startsWith(".github/instructions/")) { + + if (normalized.equals(".github/copilot-instructions.md") || normalized.startsWith(".github/instructions/")) { return true; } String[] segments = normalized.split("/"); + for (String segment : segments) { - if (FORBIDDEN_DIRECTORY_SEGMENTS.contains(segment) - || segment.startsWith("run-")) { + if (FORBIDDEN_DIRECTORY_SEGMENTS.contains(segment) || segment.startsWith("run-")) { return true; } } String name = segments[segments.length - 1]; - return FORBIDDEN_FILE_NAMES.contains(name) - || name.endsWith(".launch") - || name.endsWith(".log") - || name.contains(".log.") - || name.endsWith(".tmp") - || name.endsWith(".temp") - || name.endsWith(".bak") - || name.endsWith(".orig") - || name.endsWith(".rej") - || name.endsWith(".jfr") - || name.endsWith(".hprof") - || name.endsWith(".dmp") - || name.endsWith(".ipr") - || name.endsWith(".iws") - || name.endsWith(".iml") - || name.startsWith(".aider") - || (name.startsWith(".env") && !name.equals(".env.example")) - || name.startsWith("hs_err_pid") - || name.startsWith("replay_pid") - || (name.startsWith("forge") && name.endsWith("changelog.txt")) - || normalized.equals("readme.txt") - || name.endsWith(".code-workspace") - || name.contains("agent-handover"); + + return FORBIDDEN_FILE_NAMES.contains(name) || name.endsWith(".launch") || name.endsWith(".log") + || name.contains(".log.") || name.endsWith(".tmp") || name.endsWith(".temp") || name.endsWith(".bak") + || name.endsWith(".orig") || name.endsWith(".rej") || name.endsWith(".jfr") || name.endsWith(".hprof") + || name.endsWith(".dmp") || name.endsWith(".ipr") || name.endsWith(".iws") || name.endsWith(".iml") + || name.startsWith(".aider") || (name.startsWith(".env") && !name.equals(".env.example")) + || name.startsWith("hs_err_pid") || name.startsWith("replay_pid") + || (name.startsWith("forge") && name.endsWith("changelog.txt")) || normalized.equals("readme.txt") + || name.endsWith(".code-workspace") || name.contains("agent-handover"); } private static boolean isTextFile(String file) { String name = Paths.get(file).getFileName().toString().toLowerCase(Locale.ROOT); + if (TEXT_NAMES.contains(name)) { return true; } + int dot = name.lastIndexOf('.'); + return dot >= 0 && TEXT_EXTENSIONS.contains(name.substring(dot + 1)); } private static CommandResult git(String... arguments) throws IOException, InterruptedException { String[] command = new String[arguments.length + 1]; + command[0] = "git"; System.arraycopy(arguments, 0, command, 1, arguments.length); - Process process = new ProcessBuilder(Arrays.asList(command)) - .directory(Paths.get("").toAbsolutePath().toFile()) - .redirectErrorStream(true) - .start(); - String output = new String(readAllBytes(process.getInputStream()), - StandardCharsets.UTF_8); + Process process = new ProcessBuilder(Arrays.asList(command)).directory(Paths.get("").toAbsolutePath().toFile()) + .redirectErrorStream(true).start(); + String output = new String(readAllBytes(process.getInputStream()), StandardCharsets.UTF_8); + return new CommandResult(process.waitFor(), output); } @@ -204,9 +163,11 @@ private static byte[] readAllBytes(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int read; + while ((read = input.read(buffer)) != -1) { output.write(buffer, 0, read); } + return output.toByteArray(); } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java index e26a6e6..1eeb4fb 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java @@ -11,34 +11,38 @@ import static org.junit.jupiter.api.Assertions.*; class GrassFormsContractTest { - @BeforeAll static void register() { + @BeforeAll + static void register() { MinecraftTestBootstrap.registerVanilla(); + for (int metadata = 0; metadata < 2; metadata++) { GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getStateFromMeta(metadata), ModBlocks.GRASS_SLAB.getStateFromMeta(metadata)); } } - @Test void bothOrientationsRoundTripAndSnowDoesNotChangeIdentity() { + @Test + void bothOrientationsRoundTripAndSnowDoesNotChangeIdentity() { for (int metadata = 0; metadata < 2; metadata++) { IBlockState dirt = ModBlocks.DIRT_SLAB.getStateFromMeta(metadata); IBlockState grass = ModBlocks.GRASS_SLAB.getStateFromMeta(metadata); + assertEquals(grass, GrassSlabsApi.grassFor(dirt)); assertEquals(dirt, GrassSlabsApi.dirtFor(grass)); assertEquals(dirt, GrassSlabsApi.dirtFor(grass.withProperty(BlockGrass.SNOWY, true))); - assertEquals(grass.getValue(BlockSlab.HALF), - GrassSlabsApi.grassFor(dirt).getValue(BlockSlab.HALF)); + assertEquals(grass.getValue(BlockSlab.HALF), GrassSlabsApi.grassFor(dirt).getValue(BlockSlab.HALF)); assertTrue(GrassSlabsApi.isGrassForm(grass)); assertFalse(GrassSlabsApi.isGrassForm(dirt)); } } - @Test void duplicateOrInvalidMappingsAreRejected() { - assertThrows(IllegalArgumentException.class, () -> GrassSlabsApi.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())); + @Test + void duplicateOrInvalidMappingsAreRejected() { + assertThrows(IllegalArgumentException.class, () -> GrassSlabsApi + .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/block/SlabContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/block/SlabContractTest.java index 6c1f769..c273ea2 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/block/SlabContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/block/SlabContractTest.java @@ -34,49 +34,43 @@ void metadataMatchesBuildingBricksTopAndBottomContract() { void visualSnowStateIsNotPersistedInSlabMetadata() { for (BlockSlab.EnumBlockHalf half : BlockSlab.EnumBlockHalf.values()) { int expected = half == BlockSlab.EnumBlockHalf.TOP ? 0 : 1; - IBlockState dirt = ModBlocks.DIRT_SLAB.getDefaultState() - .withProperty(BlockSlab.HALF, half) + IBlockState dirt = ModBlocks.DIRT_SLAB.getDefaultState().withProperty(BlockSlab.HALF, half) .withProperty(BlockDirt.SNOWY, Boolean.TRUE); - IBlockState grass = ModBlocks.GRASS_SLAB.getDefaultState() - .withProperty(BlockSlab.HALF, half) + IBlockState grass = ModBlocks.GRASS_SLAB.getDefaultState().withProperty(BlockSlab.HALF, half) .withProperty(BlockGrass.SNOWY, Boolean.TRUE); assertEquals(expected, ModBlocks.DIRT_SLAB.getMetaFromState(dirt)); assertEquals(expected, ModBlocks.GRASS_SLAB.getMetaFromState(grass)); - assertFalse(ModBlocks.DIRT_SLAB.getStateFromMeta(expected) - .getValue(BlockDirt.SNOWY)); - assertFalse(ModBlocks.GRASS_SLAB.getStateFromMeta(expected) - .getValue(BlockGrass.SNOWY)); + assertFalse(ModBlocks.DIRT_SLAB.getStateFromMeta(expected).getValue(BlockDirt.SNOWY)); + assertFalse(ModBlocks.GRASS_SLAB.getStateFromMeta(expected).getValue(BlockGrass.SNOWY)); } } @Test void pathProfilesAreExactlySevenSixteenthsHigh() { IBlockState bottom = ModBlocks.PATH_SLAB.getStateFromMeta(1); - IBlockState top = ModBlocks.PATH_SLAB.getStateFromMeta(0); + IBlockState surfaceState = ModBlocks.PATH_SLAB.getStateFromMeta(0); assertEquals(0.0D, ModBlocks.PATH_SLAB.getBoundingBox(bottom, null, BlockPos.ORIGIN).minY); - assertEquals(7.0D / 16.0D, - ModBlocks.PATH_SLAB.getBoundingBox(bottom, null, BlockPos.ORIGIN).maxY); - assertEquals(8.0D / 16.0D, - ModBlocks.PATH_SLAB.getBoundingBox(top, null, BlockPos.ORIGIN).minY); - assertEquals(15.0D / 16.0D, - ModBlocks.PATH_SLAB.getBoundingBox(top, null, BlockPos.ORIGIN).maxY); + assertEquals(7.0D / 16.0D, ModBlocks.PATH_SLAB.getBoundingBox(bottom, null, BlockPos.ORIGIN).maxY); + assertEquals(8.0D / 16.0D, ModBlocks.PATH_SLAB.getBoundingBox(surfaceState, null, BlockPos.ORIGIN).minY); + assertEquals(15.0D / 16.0D, ModBlocks.PATH_SLAB.getBoundingBox(surfaceState, null, BlockPos.ORIGIN).maxY); } @Test void turfIsOnePixelHighAndNotAFullCube() { IBlockState turf = ModBlocks.TURF.getDefaultState(); - assertEquals(1.0D / 16.0D, - ModBlocks.TURF.getBoundingBox(turf, null, BlockPos.ORIGIN).maxY); + + assertEquals(1.0D / 16.0D, ModBlocks.TURF.getBoundingBox(turf, null, BlockPos.ORIGIN).maxY); assertFalse(ModBlocks.TURF.isFullCube(turf)); assertFalse(ModBlocks.TURF.isOpaqueCube(turf)); - assertEquals(BlockFaceShape.SOLID, ModBlocks.TURF.getBlockFaceShape( - null, turf, BlockPos.ORIGIN, EnumFacing.DOWN)); + assertEquals(BlockFaceShape.SOLID, + ModBlocks.TURF.getBlockFaceShape(null, turf, BlockPos.ORIGIN, EnumFacing.DOWN)); + for (EnumFacing face : EnumFacing.values()) { if (face != EnumFacing.DOWN) { - assertEquals(BlockFaceShape.UNDEFINED, ModBlocks.TURF.getBlockFaceShape( - null, turf, BlockPos.ORIGIN, face)); + assertEquals(BlockFaceShape.UNDEFINED, + ModBlocks.TURF.getBlockFaceShape(null, turf, BlockPos.ORIGIN, face)); } } } @@ -88,11 +82,12 @@ void grassAndTurfUseTheCutoutMippedLayer() { } private static void assertMetadata(LegacySlabBlock slab) { - IBlockState top = slab.getStateFromMeta(0); + IBlockState surfaceState = slab.getStateFromMeta(0); IBlockState bottom = slab.getStateFromMeta(1); - assertEquals(BlockSlab.EnumBlockHalf.TOP, top.getValue(BlockSlab.HALF)); + + assertEquals(BlockSlab.EnumBlockHalf.TOP, surfaceState.getValue(BlockSlab.HALF)); assertEquals(BlockSlab.EnumBlockHalf.BOTTOM, bottom.getValue(BlockSlab.HALF)); - assertEquals(0, slab.getMetaFromState(top)); + assertEquals(0, slab.getMetaFromState(surfaceState)); assertEquals(1, slab.getMetaFromState(bottom)); assertFalse(slab.isDouble()); assertTrue(slab.getDefaultState().getValue(BlockSlab.HALF) == BlockSlab.EnumBlockHalf.BOTTOM); diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitratorTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitratorTest.java index 02d430a..c0153a4 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitratorTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksConfigArbitratorTest.java @@ -20,6 +20,7 @@ class BuildingBricksConfigArbitratorTest { @BeforeEach void provideForgeMinecraftHome() throws Exception { java.lang.reflect.Field field = FMLInjectionData.class.getDeclaredField("minecraftHome"); + field.setAccessible(true); field.set(null, temporaryDirectory.toFile()); } @@ -27,12 +28,15 @@ void provideForgeMinecraftHome() throws Exception { @Test void enabledWorldgenIsBackedUpThenDisabled() throws Exception { File file = temporaryDirectory.resolve("BuildingBricks/general.cfg").toFile(); + write(file, true); + byte[] original = Files.readAllBytes(file.toPath()); assertTrue(BuildingBricksConfigArbitrator.disable(file)); File backup = new File(file.getParentFile(), "general.cfg.skysgrassslabs-backup"); + assertTrue(backup.isFile()); assertArrayEquals(original, Files.readAllBytes(backup.toPath())); assertFalse(read(file)); @@ -41,22 +45,26 @@ void enabledWorldgenIsBackedUpThenDisabled() throws Exception { @Test void disabledWorldgenIsLeftByteForByteUntouched() throws Exception { File file = temporaryDirectory.resolve("BuildingBricks/general.cfg").toFile(); + write(file, false); + byte[] original = Files.readAllBytes(file.toPath()); assertFalse(BuildingBricksConfigArbitrator.disable(file)); assertArrayEquals(original, Files.readAllBytes(file.toPath())); - assertFalse(new File(file.getParentFile(), - "general.cfg.skysgrassslabs-backup").exists()); + assertFalse(new File(file.getParentFile(), "general.cfg.skysgrassslabs-backup").exists()); } @Test void existingOneTimeBackupIsNeverOverwritten() throws Exception { File file = temporaryDirectory.resolve("BuildingBricks/general.cfg").toFile(); + write(file, true); + File backup = new File(file.getParentFile(), "general.cfg.skysgrassslabs-backup"); byte[] sentinel = "first backup".getBytes("UTF-8"); + Files.write(backup.toPath(), sentinel); assertTrue(BuildingBricksConfigArbitrator.disable(file)); @@ -65,16 +73,18 @@ void existingOneTimeBackupIsNeverOverwritten() throws Exception { private static void write(File file, boolean enabled) { Configuration configuration = new Configuration(file); + configuration.load(); - configuration.get(BuildingBricksConfigArbitrator.CATEGORY, - BuildingBricksConfigArbitrator.PROPERTY, true).set(enabled); + configuration.get(BuildingBricksConfigArbitrator.CATEGORY, BuildingBricksConfigArbitrator.PROPERTY, true) + .set(enabled); configuration.save(); } private static boolean read(File file) { Configuration configuration = new Configuration(file); + configuration.load(); - return configuration.get(BuildingBricksConfigArbitrator.CATEGORY, - BuildingBricksConfigArbitrator.PROPERTY, true).getBoolean(); + return configuration.get(BuildingBricksConfigArbitrator.CATEGORY, BuildingBricksConfigArbitrator.PROPERTY, true) + .getBoolean(); } } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksPolicyTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksPolicyTest.java index 0dfcb24..72269f8 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksPolicyTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksPolicyTest.java @@ -41,20 +41,20 @@ void currentAndFutureChunkMarkersSurviveLaterSaves() { @Test void onlySupportedLegacyIdsAreClassifiedForMissingMappingRecovery() { - assertTrue(LegacyMigrationHandler.legacySlabKind(BuildingBricksCompat.GRASS_SLAB_ID) - == LegacyMigrationHandler.LegacySlabKind.GRASS); + assertTrue(LegacyMigrationHandler + .legacySlabKind(BuildingBricksCompat.GRASS_SLAB_ID) == LegacyMigrationHandler.LegacySlabKind.GRASS); assertTrue(LegacyMigrationHandler.legacySlabKind( - BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID) - == LegacyMigrationHandler.LegacySlabKind.GRASS); - assertTrue(LegacyMigrationHandler.legacySlabKind(BuildingBricksCompat.DIRT_SLAB_ID) - == LegacyMigrationHandler.LegacySlabKind.DIRT); - assertTrue(LegacyMigrationHandler.legacySlabKind( - new ResourceLocation("buildingbricks", "dirt_stairs")) == null); + BuildingBricksCompat.HISTORICAL_GRASS_SLAB_ID) == LegacyMigrationHandler.LegacySlabKind.GRASS); + assertTrue(LegacyMigrationHandler + .legacySlabKind(BuildingBricksCompat.DIRT_SLAB_ID) == LegacyMigrationHandler.LegacySlabKind.DIRT); + assertTrue( + LegacyMigrationHandler.legacySlabKind(new ResourceLocation("buildingbricks", "dirt_stairs")) == null); } @Test void forcedMigrationReportIdentifiesItsModeAndRetainsSchema() { ModWorldState state = new ModWorldState(ModWorldState.DATA_NAME); + state.recordChunk(); state.recordGrassBlocks(2, 1); state.recordDirtItems(3); 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..3a4f58e 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java @@ -18,13 +18,12 @@ static void bootstrapMinecraft() { @Test void buildingBricksMetadataIsPreservedForBothSupportedBlocks() { - for (boolean grass : new boolean[] {false, true}) { + for (boolean grass : new boolean[] { false, true }) { for (int metadata = 0; metadata <= 1; ++metadata) { IBlockState migrated = LegacyMigrationHandler.skyStateFor(grass, metadata); - assertSame(grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB, - migrated.getBlock()); - assertEquals(metadata == 0 ? BlockSlab.EnumBlockHalf.TOP - : BlockSlab.EnumBlockHalf.BOTTOM, + + assertSame(grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB, migrated.getBlock()); + assertEquals(metadata == 0 ? BlockSlab.EnumBlockHalf.TOP : BlockSlab.EnumBlockHalf.BOTTOM, migrated.getValue(BlockSlab.HALF)); } } @@ -32,8 +31,7 @@ void buildingBricksMetadataIsPreservedForBothSupportedBlocks() { @Test void onlyTheOrientationBitIsAcceptedFromHistoricalMetadata() { - assertEquals(BlockSlab.EnumBlockHalf.TOP, - LegacyMigrationHandler.skyStateFor(true, 2).getValue(BlockSlab.HALF)); + assertEquals(BlockSlab.EnumBlockHalf.TOP, LegacyMigrationHandler.skyStateFor(true, 2).getValue(BlockSlab.HALF)); assertEquals(BlockSlab.EnumBlockHalf.BOTTOM, LegacyMigrationHandler.skyStateFor(false, 3).getValue(BlockSlab.HALF)); } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfigTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfigTest.java index 722e710..43e3b66 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfigTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/config/SkysGrassSlabsConfigTest.java @@ -19,6 +19,7 @@ class SkysGrassSlabsConfigTest { @BeforeEach void provideForgeMinecraftHome() throws Exception { java.lang.reflect.Field field = FMLInjectionData.class.getDeclaredField("minecraftHome"); + field.setAccessible(true); field.set(null, temporaryDirectory.toFile()); } @@ -32,7 +33,9 @@ void defaultsToSmoothingEnabledAndReplacementDisabled() { assertTrue(SkysGrassSlabsConfig.generateGrassSlabs()); assertTrue(SkysGrassSlabsConfig.isSmoothingActive()); assertFalse(SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs()); + Configuration written = new Configuration(file); + written.load(); assertFalse(written.get(SkysGrassSlabsConfig.COMPAT_CATEGORY, SkysGrassSlabsConfig.FORCE_REPLACE_BUILDINGBRICKS_SLABS, false).getBoolean()); @@ -40,19 +43,20 @@ void defaultsToSmoothingEnabledAndReplacementDisabled() { @Test void worldgenAndReplacementSettingsAreIndependent() { - boolean[] values = new boolean[] {false, true}; + boolean[] values = new boolean[] { false, true }; int index = 0; + for (boolean worldgen : values) { for (boolean replacement : values) { File file = temporaryDirectory.resolve("matrix-" + index++ + ".cfg").toFile(); + write(file, worldgen, replacement); SkysGrassSlabsConfig.load(file); assertEquals(worldgen, SkysGrassSlabsConfig.generateGrassSlabs()); assertEquals(worldgen, SkysGrassSlabsConfig.isSmoothingActive()); - assertEquals(replacement, - SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs()); + assertEquals(replacement, SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs()); } } } @@ -60,6 +64,7 @@ void worldgenAndReplacementSettingsAreIndependent() { @Test void loadingConfigurationClearsOnlyTheRunSpecificSmoothingSuppression() { File file = temporaryDirectory.resolve("reload.cfg").toFile(); + write(file, true, true); SkysGrassSlabsConfig.load(file); SkysGrassSlabsConfig.suppressSmoothingForThisRun(); @@ -73,12 +78,12 @@ void loadingConfigurationClearsOnlyTheRunSpecificSmoothingSuppression() { private static void write(File file, boolean worldgen, boolean replacement) { Configuration configuration = new Configuration(file); + configuration.load(); - configuration.get(SkysGrassSlabsConfig.WORLDGEN_CATEGORY, - SkysGrassSlabsConfig.GENERATE_GRASS_SLABS, true).set(worldgen); - configuration.get(SkysGrassSlabsConfig.COMPAT_CATEGORY, - SkysGrassSlabsConfig.FORCE_REPLACE_BUILDINGBRICKS_SLABS, false) - .set(replacement); + configuration.get(SkysGrassSlabsConfig.WORLDGEN_CATEGORY, SkysGrassSlabsConfig.GENERATE_GRASS_SLABS, true) + .set(worldgen); + configuration.get(SkysGrassSlabsConfig.COMPAT_CATEGORY, SkysGrassSlabsConfig.FORCE_REPLACE_BUILDINGBRICKS_SLABS, + false).set(replacement); configuration.save(); } } diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/world/ModWorldStateTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/world/ModWorldStateTest.java index dcbc927..8643598 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/world/ModWorldStateTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/world/ModWorldStateTest.java @@ -9,6 +9,7 @@ class ModWorldStateTest { @Test void schemaAndMigrationCountersRoundTrip() { ModWorldState original = new ModWorldState(ModWorldState.DATA_NAME); + original.recordChunk(); original.recordGrassBlocks(3, 0); original.recordGrassBlocks(1, 1); @@ -19,6 +20,7 @@ void schemaAndMigrationCountersRoundTrip() { NBTTagCompound nbt = original.writeToNBT(new NBTTagCompound()); ModWorldState restored = new ModWorldState(ModWorldState.DATA_NAME); + restored.readFromNBT(nbt); assertEquals(1, nbt.getInteger("schema_version")); diff --git a/src/test/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecisionTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecisionTest.java index 27e1c60..55a6677 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecisionTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/world/SmoothingDecisionTest.java @@ -10,28 +10,19 @@ class SmoothingDecisionTest { @Test void acceptsOnlyAOneBlockNaturalGrassTransition() { - assertTrue(SmoothingDecision.shouldPlace(63, 64, - true, true, true, true, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 63, - true, true, true, true, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 65, - true, true, true, true, true, false)); + assertTrue(SmoothingDecision.shouldPlace(63, 64, true, true, true, true, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 63, true, true, true, true, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 65, true, true, true, true, true, false)); } @Test void rejectsEveryProtectedTargetCondition() { - assertFalse(SmoothingDecision.shouldPlace(63, 64, - false, true, true, true, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 64, - true, false, true, true, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 64, - true, true, false, true, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 64, - true, true, true, false, true, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 64, - true, true, true, true, false, false)); - assertFalse(SmoothingDecision.shouldPlace(63, 64, - true, true, true, true, true, true)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, false, true, true, true, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, true, false, true, true, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, true, true, false, true, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, true, true, true, false, true, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, true, true, true, true, false, false)); + assertFalse(SmoothingDecision.shouldPlace(63, 64, true, true, true, true, true, true)); } @Test @@ -41,14 +32,17 @@ void fixedSeedAreaIsStableAcrossSeveralChunkOrders() { int[] reverse = chunkOrder(true); int[] shuffled = forward.clone(); Random random = new Random(0x112021L); + for (int index = shuffled.length - 1; index > 0; --index) { int swap = random.nextInt(index + 1); int value = shuffled[index]; + shuffled[index] = shuffled[swap]; shuffled[swap] = value; } boolean[] expected = smooth(area, forward); + assertArrayEquals(expected, smooth(area, reverse)); assertArrayEquals(expected, smooth(area, shuffled)); } @@ -59,31 +53,37 @@ void benchmarksTheTwoHundredAndFiftySixColumnDecisionPass() { int[] order = chunkOrder(false); int checksum = 0; long started = System.nanoTime(); + for (int pass = 0; pass < 20000; ++pass) { checksum += smoothChunk(area, order[pass % order.length], null); } + long elapsed = System.nanoTime() - started; long nanosPerPass = elapsed / 20000L; + System.out.println("256-column smoothing decision pass: " + nanosPerPass + " ns"); assertTrue(checksum > 0, "Synthetic benchmark did not exercise an eligible slope"); - assertTrue(nanosPerPass < 5_000_000L, - "Smoothing decision pass exceeded the 5 ms regression ceiling"); + assertTrue(nanosPerPass < 5_000_000L, "Smoothing decision pass exceeded the 5 ms regression ceiling"); } private static int[] chunkOrder(boolean reverse) { int[] order = new int[81]; + for (int index = 0; index < order.length; ++index) { order[index] = reverse ? order.length - index - 1 : index; } + return order; } private static boolean[] smooth(SyntheticArea area, int[] order) { boolean[] decisions = new boolean[SyntheticArea.COLUMNS * SyntheticArea.COLUMNS]; + for (int chunk : order) { smoothChunk(area, chunk, decisions); } + return decisions; } @@ -91,31 +91,41 @@ private static int smoothChunk(SyntheticArea area, int chunk, boolean[] decision int chunkX = chunk % SyntheticArea.CHUNKS; int chunkZ = chunk / SyntheticArea.CHUNKS; int accepted = 0; + for (int localZ = 0; localZ < 16; ++localZ) { for (int localX = 0; localX < 16; ++localX) { - int x = (chunkX << 4) + localX; - int z = (chunkZ << 4) + localZ; - int index = z * SyntheticArea.COLUMNS + x; - boolean place = area.eligible(index) && hasHigherNeighbour( - area, x, z, localX, localZ); + int blockX = (chunkX << 4) + localX; + int blockZ = (chunkZ << 4) + localZ; + int index = blockZ * SyntheticArea.COLUMNS + blockX; + boolean place = area.eligible(index) && hasHigherNeighbour(area, blockX, blockZ, localX, localZ); + if (place) { ++accepted; - if (decisions != null) decisions[index] = true; + + if (decisions != null) { + decisions[index] = true; + } } } } + return accepted; } - private static boolean hasHigherNeighbour(SyntheticArea area, int x, int z, - int localX, int localZ) { - int surface = area.height(x, z); - if (localX > 0 && area.higherGrass(x - 1, z, surface)) return true; - if (localZ > 0 && area.higherGrass(x, z - 1, surface)) return true; - if (x + 1 < SyntheticArea.COLUMNS && area.higherGrass(x + 1, z, surface)) { + private static boolean hasHigherNeighbour(SyntheticArea area, int blockX, int blockZ, int localX, int localZ) { + int surface = area.height(blockX, blockZ); + + if (localX > 0 && area.higherGrass(blockX - 1, blockZ, surface)) { + return true; + } + if (localZ > 0 && area.higherGrass(blockX, blockZ - 1, surface)) { return true; } - return z + 1 < SyntheticArea.COLUMNS && area.higherGrass(x, z + 1, surface); + if (blockX + 1 < SyntheticArea.COLUMNS && area.higherGrass(blockX + 1, blockZ, surface)) { + return true; + } + + return blockZ + 1 < SyntheticArea.COLUMNS && area.higherGrass(blockX, blockZ + 1, surface); } private static final class SyntheticArea { @@ -131,6 +141,7 @@ private static final class SyntheticArea { private SyntheticArea(long seed) { Random random = new Random(seed); + for (int index = 0; index < heights.length; ++index) { heights[index] = 62 + random.nextInt(4); grass[index] = random.nextInt(10) != 0; @@ -141,19 +152,19 @@ private SyntheticArea(long seed) { } } - private int height(int x, int z) { - return heights[z * COLUMNS + x]; + private int height(int blockX, int blockZ) { + return heights[blockZ * COLUMNS + blockX]; } - private boolean higherGrass(int x, int z, int lowerHeight) { - int index = z * COLUMNS + x; - return SmoothingDecision.shouldPlace(lowerHeight, heights[index], - true, grass[index], true, true, true, false); + private boolean higherGrass(int blockX, int blockZ, int lowerHeight) { + int index = blockZ * COLUMNS + blockX; + + return SmoothingDecision.shouldPlace(lowerHeight, heights[index], true, grass[index], true, true, true, + false); } private boolean eligible(int index) { - return grass[index] && clear[index] && dry[index] && supported[index] && - !blockEntity[index]; + return grass[index] && clear[index] && dry[index] && supported[index] && !blockEntity[index]; } } } diff --git a/style/eclipse-java-formatter.xml b/style/eclipse-java-formatter.xml new file mode 100644 index 0000000..a0229cc --- /dev/null +++ b/style/eclipse-java-formatter.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +