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/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java index f6035c1..3ec526f 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java +++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/AliasRecoveryChecks.java @@ -26,69 +26,178 @@ /** Mixed permanent/historical identities, including an already migrated chunk. */ public final class AliasRecoveryChecks { - private static BlockPos pos(int x){return new BlockPos(4096+x,80,4096);} + private static BlockPos pos(int blockX) { + return new BlockPos(4096 + blockX, 80, 4096); + } + public static void fresh(WorldServer world) { - Block[] aliases=BuildingBricksCompat.legacyAliases();require(aliases.length==3,"three distinct holders"); - NBTTagCompound before=state(world); - for(int i=0;i<3;i++)for(int orientation=0;orientation<2;orientation++) { - Block alias=aliases[i];require(alias!=ModBlocks.GRASS_SLAB&&alias!=ModBlocks.DIRT_SLAB,"holder identity must not collide with permanent IDs"); - world.setBlockState(pos(i*2+orientation),alias.getStateFromMeta(orientation),2); + Block[] aliases = BuildingBricksCompat.legacyAliases(); + + require(aliases.length == 3, "three distinct holders"); + + NBTTagCompound before = state(world); + + for (int entryIndex = 0; entryIndex < 3; entryIndex++) { + for (int orientation = 0; orientation < 2; orientation++) { + Block alias = aliases[entryIndex]; + + require(alias != ModBlocks.GRASS_SLAB && alias != ModBlocks.DIRT_SLAB, + "holder identity must not collide with permanent IDs"); + world.setBlockState(pos(entryIndex * 2 + orientation), alias.getStateFromMeta(orientation), 2); + } } - world.setBlockState(pos(6),sky(true).getStateFromMeta(0),2); - world.setBlockState(pos(7),sky(false).getStateFromMeta(1),2); - world.setBlockState(pos(8),Blocks.CHEST.getDefaultState(),2); - TileEntityChest chest=(TileEntityChest)world.getTileEntity(pos(8)); - for(int i=0;i<3;i++)chest.setInventorySlotContents(i,stack(aliases[i],i+7)); + + world.setBlockState(pos(6), sky(true).getStateFromMeta(0), 2); + world.setBlockState(pos(7), sky(false).getStateFromMeta(1), 2); + world.setBlockState(pos(8), Blocks.CHEST.getDefaultState(), 2); + + TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos(8)); + + for (int entryIndex = 0; entryIndex < 3; entryIndex++) { + chest.setInventorySlotContents(entryIndex, stack(aliases[entryIndex], entryIndex + 7)); + } + // A real entity join exercises the early live-item path without a counter reset. - EntityItem dropped=new EntityItem(world,4096,83,4096,stack(aliases[2],11)); - new LegacyMigrationHandler().joinEntity(new EntityJoinWorldEvent(dropped,world)); - require(dropped.getEntityItem().getItem()==Item.getItemFromBlock(ModBlocks.GRASS_SLAB)&&dropped.getEntityItem().stackSize==11&&dropped.getEntityItem().getTagCompound().getString("custom").equals("preserved"),"dropped stack recovery"); - LegacyMigrationHandler events=new LegacyMigrationHandler(); - EntityItemFrame frame=new EntityItemFrame(world,pos(9),EnumFacing.NORTH); - frame.setDisplayedItem(stack(aliases[1],1)); - events.joinEntity(new EntityJoinWorldEvent(frame,world)); - checkStack(frame.getDisplayedItem(),ModBlocks.DIRT_SLAB,1,"frame"); - FakePlayer player=FakePlayerFactory.get(world,new GameProfile( - UUID.fromString("11111111-1111-4111-8111-111111111111"),"SlabRecoveryTest")); - player.inventory.setInventorySlotContents(0,stack(aliases[0],13)); - player.getInventoryEnderChest().setInventorySlotContents(0,stack(aliases[1],17)); + EntityItem dropped = new EntityItem(world, 4096, 83, 4096, stack(aliases[2], 11)); + + new LegacyMigrationHandler().joinEntity(new EntityJoinWorldEvent(dropped, world)); + require(dropped.getEntityItem().getItem() == Item.getItemFromBlock(ModBlocks.GRASS_SLAB) + && dropped.getEntityItem().stackSize == 11 + && dropped.getEntityItem().getTagCompound().getString("custom").equals("preserved"), + "dropped stack recovery"); + + LegacyMigrationHandler events = new LegacyMigrationHandler(); + EntityItemFrame frame = new EntityItemFrame(world, pos(9), EnumFacing.NORTH); + + frame.setDisplayedItem(stack(aliases[1], 1)); + events.joinEntity(new EntityJoinWorldEvent(frame, world)); + checkStack(frame.getDisplayedItem(), ModBlocks.DIRT_SLAB, 1, "frame"); + + FakePlayer player = FakePlayerFactory.get(world, + new GameProfile(UUID.fromString("11111111-1111-4111-8111-111111111111"), "SlabRecoveryTest")); + + player.inventory.setInventorySlotContents(0, stack(aliases[0], 13)); + player.getInventoryEnderChest().setInventorySlotContents(0, stack(aliases[1], 17)); events.playerLogin(new PlayerEvent.PlayerLoggedInEvent(player)); - checkStack(player.inventory.getStackInSlot(0),ModBlocks.GRASS_SLAB,13,"player"); - checkStack(player.getInventoryEnderChest().getStackInSlot(0),ModBlocks.DIRT_SLAB,17,"ender chest"); + checkStack(player.inventory.getStackInSlot(0), ModBlocks.GRASS_SLAB, 13, "player"); + checkStack(player.getInventoryEnderChest().getStackInSlot(0), ModBlocks.DIRT_SLAB, 17, "ender chest"); // Queue a loaded chest, then put a historical stack through its live handler. - world.setBlockState(pos(10),Blocks.CHEST.getDefaultState(),2); - events.loadChunk(new ChunkDataEvent.Load(world.getChunkFromBlockCoords(pos(10)),new NBTTagCompound())); - TileEntityChest handlerChest=(TileEntityChest)world.getTileEntity(pos(10)); - handlerChest.setInventorySlotContents(0,stack(aliases[2],19)); + world.setBlockState(pos(10), Blocks.CHEST.getDefaultState(), 2); + events.loadChunk(new ChunkDataEvent.Load(world.getChunkFromBlockCoords(pos(10)), new NBTTagCompound())); + + TileEntityChest handlerChest = (TileEntityChest) world.getTileEntity(pos(10)); + + handlerChest.setInventorySlotContents(0, stack(aliases[2], 19)); events.migrateLoadedHandlers(new TickEvent.ServerTickEvent(TickEvent.Phase.END)); - checkStack(handlerChest.getStackInSlot(0),ModBlocks.GRASS_SLAB,19,"live item handler"); + checkStack(handlerChest.getStackInSlot(0), ModBlocks.GRASS_SLAB, 19, "live item handler"); events.playerLogin(new PlayerEvent.PlayerLoggedInEvent(player)); - checkStack(player.inventory.getStackInSlot(0),ModBlocks.GRASS_SLAB,13,"second login"); - require(before.equals(state(world)),"absent recovery preserves historical counters"); - System.out.println("SKY_ALIAS_FIXTURE_SEEDED historical_and_permanent_ids=true player_ender_frame_handler=true"); + checkStack(player.inventory.getStackInSlot(0), ModBlocks.GRASS_SLAB, 13, "second login"); + require(before.equals(state(world)), "absent recovery preserves historical counters"); + System.out + .println("SKY_ALIAS_FIXTURE_SEEDED historical_and_permanent_ids=true player_ender_frame_handler=true"); } + public static void reload(WorldServer world) { - NBTTagCompound before=state(world); - for(int i=0;i<3;i++)for(int orientation=0;orientation<2;orientation++) { - Block expected=i==1?ModBlocks.DIRT_SLAB:ModBlocks.GRASS_SLAB; - require(world.getBlockState(pos(i*2+orientation)).equals(expected.getStateFromMeta(orientation)),"alias block orientation "+i+" / "+orientation); + NBTTagCompound before = state(world); + + for (int entryIndex = 0; entryIndex < 3; entryIndex++) { + for (int orientation = 0; orientation < 2; orientation++) { + Block expected = entryIndex == 1 ? ModBlocks.DIRT_SLAB : ModBlocks.GRASS_SLAB; + + require(world.getBlockState(pos(entryIndex * 2 + orientation)) + .equals(expected.getStateFromMeta(orientation)), + "alias block orientation " + entryIndex + " / " + orientation); + } + } + + require(world.getBlockState(pos(6)).equals(sky(true).getStateFromMeta(0)) + && world.getBlockState(pos(7)).equals(sky(false).getStateFromMeta(1)), + "permanent mixed content unchanged"); + + TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos(8)); + + for (int entryIndex = 0; entryIndex < 3; entryIndex++) { + ItemStack itemStack = chest.getStackInSlot(entryIndex); + + require(itemStack != null && itemStack.stackSize == entryIndex + 7 + && itemStack.getItem() == Item + .getItemFromBlock(entryIndex == 1 ? ModBlocks.DIRT_SLAB : ModBlocks.GRASS_SLAB) + && itemStack.getTagCompound().getString("custom").equals("preserved"), "chest count and NBT"); } - require(world.getBlockState(pos(6)).equals(sky(true).getStateFromMeta(0))&&world.getBlockState(pos(7)).equals(sky(false).getStateFromMeta(1)),"permanent mixed content unchanged"); - TileEntityChest chest=(TileEntityChest)world.getTileEntity(pos(8)); - for(int i=0;i<3;i++){ItemStack s=chest.getStackInSlot(i);require(s!=null&&s.stackSize==i+7&&s.getItem()==Item.getItemFromBlock(i==1?ModBlocks.DIRT_SLAB:ModBlocks.GRASS_SLAB)&&s.getTagCompound().getString("custom").equals("preserved"),"chest count and NBT");} + // Revisit a marker that predates the remaining historical content. - Chunk chunk=world.getChunkFromBlockCoords(pos(0));Block alias=BuildingBricksCompat.grassSlab();world.setBlockState(pos(0),alias.getStateFromMeta(0),2); - NBTTagCompound root=new NBTTagCompound(),level=new NBTTagCompound(),section=new NBTTagCompound(),marker=new NBTTagCompound();root.setTag("Level",level);marker.setInteger("buildingbricks_migration_version",1);marker.setString("future_field","preserved");root.setTag("skysgrassslabs",marker); - byte[] low=new byte[4096],high=new byte[2048],data=new byte[2048];int id=Block.getIdFromBlock(alias);low[0]=(byte)id;high[0]=(byte)(id>>8);section.setByte("Y",(byte)5);section.setByteArray("Blocks",low);section.setByteArray("Add",high);section.setByteArray("Data",data);NBTTagList sections=new NBTTagList();sections.appendTag(section);level.setTag("Sections",sections); - LegacyMigrationHandler events=new LegacyMigrationHandler();events.loadChunk(new ChunkDataEvent.Load(chunk,root)); - require(world.getBlockState(pos(0)).equals(sky(true).getStateFromMeta(0)),"old marker cannot skip remaining recovery"); - NBTTagCompound saved=new NBTTagCompound();events.saveChunk(new ChunkDataEvent.Save(chunk,saved));require(saved.getCompoundTag("skysgrassslabs").equals(marker),"complete old marker preserved"); - require(before.equals(state(world)),"reload preserves counters"); + Chunk chunk = world.getChunkFromBlockCoords(pos(0)); + Block alias = BuildingBricksCompat.grassSlab(); + + world.setBlockState(pos(0), alias.getStateFromMeta(0), 2); + + NBTTagCompound root = new NBTTagCompound(); + NBTTagCompound level = new NBTTagCompound(); + NBTTagCompound section = new NBTTagCompound(); + NBTTagCompound marker = new NBTTagCompound(); + + root.setTag("Level", level); + marker.setInteger("buildingbricks_migration_version", 1); + marker.setString("future_field", "preserved"); + root.setTag("skysgrassslabs", marker); + + byte[] blockIdLowBytes = new byte[4096]; + byte[] high = new byte[2048]; + byte[] data = new byte[2048]; + + int blockId = Block.getIdFromBlock(alias); + + blockIdLowBytes[0] = (byte) blockId; + high[0] = (byte) (blockId >> 8); + section.setByte("Y", (byte) 5); + section.setByteArray("Blocks", blockIdLowBytes); + section.setByteArray("Add", high); + section.setByteArray("Data", data); + + NBTTagList sections = new NBTTagList(); + + sections.appendTag(section); + level.setTag("Sections", sections); + + LegacyMigrationHandler events = new LegacyMigrationHandler(); + + events.loadChunk(new ChunkDataEvent.Load(chunk, root)); + require(world.getBlockState(pos(0)).equals(sky(true).getStateFromMeta(0)), + "old marker cannot skip remaining recovery"); + + NBTTagCompound saved = new NBTTagCompound(); + + events.saveChunk(new ChunkDataEvent.Save(chunk, saved)); + require(saved.getCompoundTag("skysgrassslabs").equals(marker), "complete old marker preserved"); + require(before.equals(state(world)), "reload preserves counters"); System.out.println("SKY_ALIAS_RECOVERY_PASS orientations=2 aliases=3 counts_and_nbt=true old_marker=true"); } - private static ItemStack stack(Block alias,int count){ItemStack s=new ItemStack(alias,count,0);NBTTagCompound tag=new NBTTagCompound();tag.setString("custom","preserved");s.setTagCompound(tag);return s;} - private static void checkStack(ItemStack stack,Block block,int count,String context){require(stack!=null&&stack.getItem()==Item.getItemFromBlock(block)&&stack.stackSize==count&&stack.getTagCompound().getString("custom").equals("preserved"),context+" identity/count/NBT");} - private static NBTTagCompound state(WorldServer world){return ((net.minecraft.world.WorldSavedData)ModWorldState.get(world)).writeToNBT(new NBTTagCompound());} - private static Block sky(boolean grass){return grass?ModBlocks.GRASS_SLAB:ModBlocks.DIRT_SLAB;} - private static void require(boolean value,String message){if(!value)throw new AssertionError(message);} + + private static ItemStack stack(Block alias, int count) { + ItemStack itemStack = new ItemStack(alias, count, 0); + NBTTagCompound tag = new NBTTagCompound(); + + tag.setString("custom", "preserved"); + itemStack.setTagCompound(tag); + return itemStack; + } + + private static void checkStack(ItemStack stack, Block block, int count, String context) { + require(stack != null && stack.getItem() == Item.getItemFromBlock(block) && stack.stackSize == count + && stack.getTagCompound().getString("custom").equals("preserved"), context + " identity/count/NBT"); + } + + private static NBTTagCompound state(WorldServer world) { + return ((net.minecraft.world.WorldSavedData) ModWorldState.get(world)).writeToNBT(new NBTTagCompound()); + } + + private static Block sky(boolean grass) { + return grass ? ModBlocks.GRASS_SLAB : ModBlocks.DIRT_SLAB; + } + + private static void require(boolean value, String message) { + if (!value) { + throw new AssertionError(message); + } + } } diff --git a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java index 5261d73..915f120 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java +++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/IntegrationTestMod.java @@ -86,8 +86,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"; @@ -99,34 +102,46 @@ public void serverStarted(FMLServerStartedEvent event) { WorldServer world = server.worldServerForDimension(0); File marker = new File(world.getSaveHandler().getWorldDirectory(), MARKER_NAME); String phase = System.getProperty("skysgrassslabs.integrationPhase", "fresh"); + try { Properties evidence = load(marker); + if ("upgrade-seed".equals(phase) || "upgrade-reload".equals(phase)) { - ReleasedUpgradeChecks.run(world,"upgrade-seed".equals(phase)); - evidence.setProperty(phase+"_complete","true"); + ReleasedUpgradeChecks.run(world, "upgrade-seed".equals(phase)); + evidence.setProperty(phase + "_complete", "true"); } else if ("fresh".equals(phase)) { int gameplayChecks = verifyGameplay(server, world); int worldgenChecks = verifyWorldgen(world); - if(BuildingBricksCompat.hasLegacyAliases())AliasRecoveryChecks.fresh(world); + + if (BuildingBricksCompat.hasLegacyAliases()) { + AliasRecoveryChecks.fresh(world); + } + evidence.setProperty("gameplay_checks", Integer.toString(gameplayChecks)); evidence.setProperty("worldgen_checks", Integer.toString(worldgenChecks)); + if (Loader.isModLoaded(BuildingBricksCompat.MOD_ID)) { 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"); } + 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(BuildingBricksCompat.hasLegacyAliases())AliasRecoveryChecks.reload(world); + + if (BuildingBricksCompat.hasLegacyAliases()) { + AliasRecoveryChecks.reload(world); + } if ("true".equals(evidence.getProperty("compat_fixture_seeded"))) { require(!SkysGrassSlabsConfig.forceReplaceBuildingBricksSlabs(), "Default coexistence reload unexpectedly enabled replacement"); @@ -136,6 +151,7 @@ public void serverStarted(FMLServerStartedEvent event) { "Disabled replacement marked or counted a chunk"); evidence.setProperty("compat_retention_complete", "true"); } + evidence.setProperty("reload_complete", "true"); } else if ("compat-replacement".equals(phase)) { require("true".equals(evidence.getProperty("compat_retention_complete")), @@ -144,29 +160,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"); @@ -174,43 +195,49 @@ 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 ("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(); @@ -219,17 +246,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); @@ -241,205 +268,254 @@ public void serverStarted(FMLServerStartedEvent event) { 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.worldServerForDimension(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; - for (int dimension : new int[] {0, -1, 1}) { + + for (int dimension : new int[] { 0, -1, 1 }) { WorldServer world = server.worldServerForDimension(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, grass, dirt, 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, Block grass, Block dirt, LegacyContentCounts counts) throws Exception { + private static void auditSavedChunks(File dimensionDirectory, List coordinates, int dimension, Block grass, + Block dirt, LegacyContentCounts counts) throws Exception { int grassId = Block.getIdFromBlock(grass); int dirtId = Block.getIdFromBlock(dirt); 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"); - countStacks(level.getTagList("TileEntities", 10), - BuildingBricksCompat.GRASS_SLAB_ID.toString(), - BuildingBricksCompat.DIRT_SLAB_ID.toString(), counts, - dimension, coordinate[0], coordinate[1]); - countStacks(level.getTagList("Entities", 10), - BuildingBricksCompat.GRASS_SLAB_ID.toString(), - BuildingBricksCompat.DIRT_SLAB_ID.toString(), counts, - dimension, coordinate[0], coordinate[1]); + + countStacks(level.getTagList("TileEntities", 10), BuildingBricksCompat.GRASS_SLAB_ID.toString(), + BuildingBricksCompat.DIRT_SLAB_ID.toString(), counts, dimension, coordinate[0], coordinate[1]); + countStacks(level.getTagList("Entities", 10), BuildingBricksCompat.GRASS_SLAB_ID.toString(), + BuildingBricksCompat.DIRT_SLAB_ID.toString(), 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 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.xPosition, chunk.zPosition); } for (ClassInheritanceMultiMap list : chunk.getEntityLists()) { for (Entity entity : list) { - countStacks(entity.serializeNBT(), grassId, dirtId, counts, - chunk.getWorld().provider.getDimension(), chunk.xPosition, chunk.zPosition); + countStacks(entity.serializeNBT(), grassId, dirtId, counts, chunk.getWorld().provider.getDimension(), + chunk.xPosition, chunk.zPosition); } } } - 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); } @@ -447,30 +523,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.unload(chunk); + + for (Chunk chunk : chunks) { + provider.unload(chunk); + } for (int remaining = chunks.size(); remaining > 0; remaining -= 100) { tickProvider(provider); } + drainChunkWrites(); chunks.clear(); } @@ -494,55 +578,63 @@ 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 verifyLegacyContentSamples(MinecraftServer server, - LegacyContentCounts counts, boolean expectBuildingBricks) { - require(counts.grassSample != null && counts.dirtSample != null && - counts.itemSample != null, + 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 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.worldServerForDimension(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.worldServerForDimension(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, + + require(state.getBlock() == expected && (expected.getMetaFromState(state) & 1) == sample.metadata, "Saved legacy " + type + " sample did not load with its identity and orientation"); } - 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)); @@ -557,50 +649,45 @@ 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))); } 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"); } @@ -608,57 +695,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) != null && - chest.getStackInSlot(0).getItem() == expectedGrassItem && - chest.getStackInSlot(0).stackSize == 2 && - "retained".equals(chest.getStackInSlot(0).getTagCompound().getString("probe")), + + require(chest.getStackInSlot(0) != null && chest.getStackInSlot(0).getItem() == expectedGrassItem + && chest.getStackInSlot(0).stackSize == 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) != null && - chest.getStackInSlot(1).getItem() == expectedDirtItem && - chest.getStackInSlot(1).stackSize == 3, - "Legacy chest dirt slabs did not match the selected mode"); + require(chest.getStackInSlot(1) != null && chest.getStackInSlot(1).getItem() == expectedDirtItem + && chest.getStackInSlot(1).stackSize == 3, "Legacy chest dirt slabs did not match the selected mode"); List drops = world.getEntitiesWithinAABB(EntityItem.class, new AxisAlignedBB(origin.east(6)).expandXyz(2.0D)); - require(drops.size() == 1 && drops.get(0).getEntityItem() != null && - drops.get(0).getEntityItem().getItem() == expectedGrassItem && - drops.get(0).getEntityItem().stackSize == 2, - "Dropped legacy slabs did not match the selected mode"); + + require(drops.size() == 1 && drops.get(0).getEntityItem() != null + && drops.get(0).getEntityItem().getItem() == expectedGrassItem + && drops.get(0).getEntityItem().stackSize == 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); } @@ -666,300 +753,335 @@ 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.getInstance().getRecipeList()) { 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"); + ItemStack[] remaining = turfRecipe.getRemainingItems(turfGrid); - require(remaining[0] != null && remaining[0].getItem() == - Item.getItemFromBlock(ModBlocks.DIRT_SLAB), + + require(remaining[0] != null && remaining[0].getItem() == Item.getItemFromBlock(ModBlocks.DIRT_SLAB), "BuildingBricks turf recipe did not return a Sky dirt slab"); require(remaining[1] != null && remaining[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.getChunkFromBlockCoords(origin); + 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.stackSize == 0, "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.stackSize == 0, "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.stackSize == 0 && - 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.stackSize == 0 + && 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)); @@ -979,8 +1101,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)); @@ -995,26 +1116,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; @@ -1023,173 +1143,171 @@ private static int verifyGameplay(MinecraftServer server, WorldServer world) { 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); } } } @@ -1198,64 +1316,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"); } @@ -1269,23 +1401,31 @@ 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); - PlayerInteractEvent.RightClickBlock event = new PlayerInteractEvent.RightClickBlock(player, - EnumHand.MAIN_HAND, shovel, pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + + PlayerInteractEvent.RightClickBlock event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, + shovel, 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 @@ -1294,41 +1434,46 @@ public Set getToolClasses(ItemStack stack) { } }.setMaxDamage(64); ItemStack compatible = new ItemStack(compatibleShovel); + world.setBlockState(pos, defaultState(ModBlocks.DIRT_SLAB), 3); - event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, compatible, - pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, compatible, 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); - event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, downward, - pos, EnumFacing.DOWN, new Vec3d(0.5D, 0.0D, 0.5D)); + + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, downward, 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, downward, - pos, EnumFacing.UP, new Vec3d(0.5D, 1.0D, 0.5D)); + event = new PlayerInteractEvent.RightClickBlock(player, EnumHand.MAIN_HAND, downward, 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) { 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)); @@ -1336,13 +1481,14 @@ 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"); + ItemStack[] remaining = recipe.getRemainingItems(grid); + require(remaining[0] != null && remaining[0].getItem() == Item.getItemFromBlock(ModBlocks.DIRT_SLAB), "Grass slab did not return a dirt slab"); - require(remaining[1] != null && remaining[1].getItem() == shovel.getItem() && - remaining[1].getItemDamage() == 17 && remaining[1].stackSize == 1 && - remaining[1].hasTagCompound() && "retained".equals( - remaining[1].getTagCompound().getString("probe")), + require(remaining[1] != null && remaining[1].getItem() == shovel.getItem() && remaining[1].getItemDamage() == 17 + && remaining[1].stackSize == 1 && remaining[1].hasTagCompound() + && "retained".equals(remaining[1].getTagCompound().getString("probe")), "Turf recipe changed the shovel remainder"); grid.clear(); @@ -1365,25 +1511,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.getChunkFromChunkCoords(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)); @@ -1391,6 +1540,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); @@ -1400,6 +1550,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); @@ -1409,17 +1560,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.getChunkFromChunkCoords(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)); @@ -1428,12 +1582,13 @@ private static int verifyWorldgen(WorldServer world) { checks++; world.getChunkFromChunkCoords(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; } @@ -1448,8 +1603,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; } @@ -1464,12 +1619,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).stackSize == 1; + return drops.size() == 1 && drops.get(0).getItem() == Item.getItemFromBlock(expected) + && drops.get(0).stackSize == 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(); } @@ -1486,8 +1641,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); } @@ -1495,37 +1649,33 @@ 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); } - 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) { return item.onItemUse(stack, 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)); } } } @@ -1533,19 +1683,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 { @@ -1558,7 +1712,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 { @@ -1572,6 +1728,7 @@ private SequenceRandom(int... values) { @Override public int nextInt(int bound) { int value = values[index++ % values.length]; + return Math.floorMod(value, bound); } } 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 e42b2f2..016ad00 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java +++ b/src/integrationTest/java/zone/moddev/mc/skysgrassslabs/integrationtest/ReleasedUpgradeChecks.java @@ -15,46 +15,106 @@ /** Runs against the released jar first, then checks the same save with its successor. */ final class ReleasedUpgradeChecks { - private ReleasedUpgradeChecks() { } - static void run(WorldServer world,boolean seed) { - world.getGameRules().setOrCreateGameRule("randomTickSpeed","0"); - Block[] blocks={ModBlocks.DIRT_SLAB,ModBlocks.GRASS_SLAB,ModBlocks.PATH_SLAB,ModBlocks.TURF}; - int number=0; - for(Block block:blocks)for(int metadata=0;metadata<(block==ModBlocks.TURF?1:2);metadata++) { - BlockPos pos=new BlockPos(160+number++,100,160); - IBlockState expected=block.getStateFromMeta(metadata); - if(seed){world.setBlockState(pos.down(),Blocks.DIRT.getDefaultState(),2);world.setBlockToAir(pos.up());world.setBlockState(pos,expected,2);} - else require(world.getBlockState(pos).equals(expected),"Released block orientation: "+block.getRegistryName()+" / "+metadata); + private ReleasedUpgradeChecks() { + } + + static void run(WorldServer world, boolean seed) { + world.getGameRules().setOrCreateGameRule("randomTickSpeed", "0"); + + Block[] blocks = { ModBlocks.DIRT_SLAB, ModBlocks.GRASS_SLAB, ModBlocks.PATH_SLAB, ModBlocks.TURF }; + int number = 0; + + for (Block block : blocks) { + for (int metadata = 0; metadata < (block == ModBlocks.TURF ? 1 : 2); metadata++) { + BlockPos pos = new BlockPos(160 + number++, 100, 160); + IBlockState expected = block.getStateFromMeta(metadata); + + if (seed) { + world.setBlockState(pos.down(), Blocks.DIRT.getDefaultState(), 2); + world.setBlockToAir(pos.up()); + world.setBlockState(pos, expected, 2); + } else { + require(world.getBlockState(pos).equals(expected), + "Released block orientation: " + block.getRegistryName() + " / " + metadata); + } + } } - BlockPos chestPos=new BlockPos(160,101,164); - if(seed)world.setBlockState(chestPos,Blocks.CHEST.getDefaultState(),2); - TileEntityChest chest=(TileEntityChest)world.getTileEntity(chestPos); - require(chest!=null,"Released container survived"); - for(int slot=0;slot section:world.getChunkFromBlockCoords(chestPos).getEntityLists())for(Entity entity:section) - if(entity instanceof EntityItem) { - ItemStack stack=((EntityItem)entity).getEntityItem(); - if(stack.getTagCompound()!=null&&stack.getTagCompound().hasKey("upgrade_entity")) { - require(stack.getItem()==net.minecraft.item.Item.getItemFromBlock(ModBlocks.GRASS_SLAB)&&stack.stackSize==7&&stack.getMetadata()==1&&stack.getTagCompound().getString("upgrade_entity").equals("Retained"),"Released dropped item and NBT");found=true; + boolean found = false; + + for (net.minecraft.util.ClassInheritanceMultiMap section : world.getChunkFromBlockCoords(chestPos) + .getEntityLists()) { + for (Entity entity : section) { + if (entity instanceof EntityItem) { + ItemStack stack = ((EntityItem) entity).getEntityItem(); + + if (stack.getTagCompound() != null && stack.getTagCompound().hasKey("upgrade_entity")) { + require(stack.getItem() == net.minecraft.item.Item.getItemFromBlock(ModBlocks.GRASS_SLAB) + && stack.stackSize == 7 && stack.getMetadata() == 1 + && stack.getTagCompound().getString("upgrade_entity").equals("Retained"), + "Released dropped item and NBT"); + found = true; + } } } - require(found,"Released entity survived"); - ModWorldState state=ModWorldState.get(world); - require(state.migratedChunks()==1&&state.migratedGrassBlocks()==13&&state.migratedGrassBlocksTop()==13&&state.migratedDirtBlocks()==17&&state.migratedDirtBlocksBottom()==17&&state.migratedGrassItems()==19&&state.migratedDirtItems()==23,"Released schema-1 counters survived unchanged"); + } + + require(found, "Released entity survived"); + + ModWorldState state = ModWorldState.get(world); + + require(state.migratedChunks() == 1 && state.migratedGrassBlocks() == 13 + && state.migratedGrassBlocksTop() == 13 && state.migratedDirtBlocks() == 17 + && state.migratedDirtBlocksBottom() == 17 && state.migratedGrassItems() == 19 + && state.migratedDirtItems() == 23, "Released schema-1 counters survived unchanged"); + } + + System.out.println("GRASS_SLABS_RELEASED_UPGRADE_PASS seed=" + seed + " states=" + number); + } + + private static void require(boolean value, String message) { + if (!value) { + throw new AssertionError(message); } - System.out.println("GRASS_SLABS_RELEASED_UPGRADE_PASS seed="+seed+" states="+number); } - private static void require(boolean value,String message){if(!value)throw new AssertionError(message);} } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java index da52173..1ed9aba 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/SkysGrassSlabs.java @@ -20,8 +20,11 @@ import zone.moddev.mc.skysgrassslabs.world.GrassSlabSmoothingHandler; import zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi; -@Mod(modid = SkysGrassSlabs.MOD_ID, name = SkysGrassSlabs.NAME, - version = SkysGrassSlabs.VERSION, dependencies = "before:buildingbricks") +@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,18 +42,25 @@ public void preInit(FMLPreInitializationEvent event) { logger = event.getModLog(); SkysGrassSlabsConfig.load(event.getSuggestedConfigurationFile()); ModBlocks.register(); - for (int meta = 0; meta < 2; ++meta) + + for (int meta = 0; meta < 2; ++meta) { GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getStateFromMeta(meta), ModBlocks.GRASS_SLAB.getStateFromMeta(meta)); + } + BuildingBricksCompat.preInit(event.getModConfigurationDirectory()); MinecraftForge.EVENT_BUS.register(new CommonEvents()); + LegacyMigrationHandler migration = new LegacyMigrationHandler(); + MinecraftForge.EVENT_BUS.register(migration); FMLCommonHandler.instance().bus().register(migration); + if (!GrassSlabsApi.hasSmoothingOwner() && SkysGrassSlabsConfig.isSmoothingActive()) { MinecraftForge.EVENT_BUS.register(new GrassSlabSmoothingHandler()); } + proxy.preInit(); } @@ -66,7 +76,9 @@ public void postInit(FMLPostInitializationEvent event) { } @Mod.EventHandler - public void loadComplete(FMLLoadCompleteEvent event) { GrassSlabsApi.freeze(); } + public void loadComplete(FMLLoadCompleteEvent event) { + GrassSlabsApi.freeze(); + } @Mod.EventHandler public void missingMappings(FMLMissingMappingsEvent event) { diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java b/src/main/java/zone/moddev/mc/skysgrassslabs/api/GrassSlabsApi.java 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 7fef8ee..958eaf0 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 @@ -71,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; } @@ -104,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 @@ -125,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; } } @@ -165,6 +173,6 @@ public BlockRenderLayer getBlockLayer() { } private static void dirtifyGrassSupport(World world, BlockPos pos) { - zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi.repairSupport(world,pos); + zone.moddev.mc.skysgrassslabs.api.GrassSlabsApi.repairSupport(world, pos); } } diff --git a/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java b/src/main/java/zone/moddev/mc/skysgrassslabs/block/GrassSpread.java index 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 7bbbbaa..aeaf962 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 a46a5dd..3b69224 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); @@ -33,15 +32,16 @@ public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess world, Block @Override public void neighborChanged(IBlockState state, World world, BlockPos pos, Block changedBlock) { super.neighborChanged(state, world, pos, changedBlock); + 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 6cb63a6..0d3aef2 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/block/TurfBlock.java @@ -16,8 +16,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); @@ -64,12 +63,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 8663768..8106766 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java @@ -18,41 +18,44 @@ 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 boolean bridgeRecipesRegistered; - 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) { registerLegacyAliases(); 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( @@ -62,27 +65,33 @@ public static void preInit(File configDirectory) { } public static void resolveBlocks() { - grassSlab = Block.REGISTRY.containsKey(GRASS_SLAB_ID) - ? Block.REGISTRY.getObject(GRASS_SLAB_ID) : null; - dirtSlab = Block.REGISTRY.containsKey(DIRT_SLAB_ID) - ? Block.REGISTRY.getObject(DIRT_SLAB_ID) : null; + grassSlab = Block.REGISTRY.containsKey(GRASS_SLAB_ID) ? Block.REGISTRY.getObject(GRASS_SLAB_ID) : null; + dirtSlab = Block.REGISTRY.containsKey(DIRT_SLAB_ID) ? Block.REGISTRY.getObject(DIRT_SLAB_ID) : null; historicalGrassSlab = Block.REGISTRY.containsKey(HISTORICAL_GRASS_SLAB_ID) - ? Block.REGISTRY.getObject(HISTORICAL_GRASS_SLAB_ID) : null; + ? Block.REGISTRY.getObject(HISTORICAL_GRASS_SLAB_ID) + : null; buildingBricksBlocks.clear(); - for (ResourceLocation id : Block.REGISTRY.getKeys()) { - if (MOD_ID.equals(id.getResourceDomain())) { - buildingBricksBlocks.put(Block.REGISTRY.getObject(id), id.toString()); + + for (ResourceLocation registryId : Block.REGISTRY.getKeys()) { + if (MOD_ID.equals(registryId.getResourceDomain())) { + buildingBricksBlocks.put(Block.REGISTRY.getObject(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; } @@ -91,7 +100,10 @@ public static boolean isInstalled() { } public static Block historicalGrassSlab() { - if (historicalGrassSlab == null) resolveBlocks(); + if (historicalGrassSlab == null) { + resolveBlocks(); + } + return historicalGrassSlab; } @@ -100,8 +112,7 @@ public static boolean hasLegacyAliases() { } public static Block[] legacyAliases() { - return legacyAliasesRegistered ? new Block[] {grassSlab(), dirtSlab(), historicalGrassSlab()} - : new Block[0]; + return legacyAliasesRegistered ? new Block[] { grassSlab(), dirtSlab(), historicalGrassSlab() } : new Block[0]; } private static void registerLegacyAliases() { @@ -112,14 +123,21 @@ private static void registerLegacyAliases() { resolveBlocks(); } - private static void registerLegacyAlias(ResourceLocation id, boolean grass) { + private static void registerLegacyAlias(ResourceLocation registryId, boolean grass) { LegacySlabAliasBlock block = new LegacySlabAliasBlock(grass); - block.setRegistryName(id).setUnlocalizedName(SkysGrassSlabs.MOD_ID + ".legacy_" + id.getResourcePath()); + + block.setRegistryName(registryId) + .setUnlocalizedName(SkysGrassSlabs.MOD_ID + ".legacy_" + registryId.getResourcePath()); + ItemBlock item = new ItemBlock(block) { - @Override public int getMetadata(int damage) { return damage & 1; } + @Override + public int getMetadata(int damage) { + return damage & 1; + } }; + item.setHasSubtypes(true); - item.setRegistryName(id); + item.setRegistryName(registryId); GameRegistry.register(block); GameRegistry.register(item); } @@ -134,30 +152,31 @@ 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) { - return stack != null && ((grassSlab() != null && - stack.getItem() == Item.getItemFromBlock(grassSlab())) || - (historicalGrassSlab() != null && stack.getItem() == Item.getItemFromBlock(historicalGrassSlab()))); + return stack != null && ((grassSlab() != null && stack.getItem() == Item.getItemFromBlock(grassSlab())) + || (historicalGrassSlab() != null && stack.getItem() == Item.getItemFromBlock(historicalGrassSlab()))); } static boolean isDirtSlabItem(ItemStack stack) { - return stack != null && dirtSlab() != null && - stack.getItem() == Item.getItemFromBlock(dirtSlab()); + return stack != null && dirtSlab() != null && stack.getItem() == Item.getItemFromBlock(dirtSlab()); } public static void registerBridgeRecipes() { if (!isInstalled() || bridgeRecipesRegistered) { return; } + GameRegistry.addRecipe(new BuildingBricksDirtSlabRecipe()); bridgeRecipesRegistered = true; } 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 91a94ad..46acbb3 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksDirtSlabRecipe.java @@ -17,9 +17,13 @@ public boolean matches(InventoryCrafting inventory, World world) { boolean dirtSlab = false; boolean seed = false; List seeds = OreDictionary.getOres(ModRecipes.SEED_ORE); + for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); - if (stack == null) continue; + + if (stack == null) { + continue; + } if (!dirtSlab && BuildingBricksCompat.isDirtSlabItem(stack)) { dirtSlab = true; } else if (!seed && OreDictionary.containsMatch(false, seeds, stack)) { @@ -28,6 +32,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 c5c5b3a..c7df876 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java @@ -45,74 +45,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); } @@ -124,20 +153,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); } @@ -145,27 +185,28 @@ 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 remapMissingMappings(FMLMissingMappingsEvent event) { - // Absent-mod holders retain each old registry identity separately. - // Remapping several saved IDs onto an already registered Sky object - // corrupts Forge 1.10's numeric registry reconciliation in mixed saves. - // Installed BuildingBricks owns its own IDs; unknown entries stay visible - // in Forge's normal warning rather than being silently substituted. + // Absent-mod holders retain each old registry identity separately. Remapping several saved IDs onto an already + // registered Sky object corrupts Forge 1.10's numeric registry reconciliation in mixed saves. Installed + // BuildingBricks owns its own IDs; unknown entries stay visible in Forge's normal warning rather than being + // silently substituted. } - static LegacySlabKind legacySlabKind(ResourceLocation id) { - 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) { @@ -176,51 +217,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); } } } @@ -230,33 +289,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; @@ -265,34 +331,65 @@ 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.xPosition<<4,0,chunk.zPosition<<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.xPosition << 4, 0, chunk.zPosition << 4), false)) { + continue; + } + + ModWorldState state = BuildingBricksCompat.hasLegacyAliases() ? null : ModWorldState.get(world); + + for (TileEntity tile : chunk.getTileEntityMap().values()) { // A loot inventory accessor generates loot and can re-enter loading. - if(tile.writeToNBT(new NBTTagCompound()).hasKey("LootTable",8))continue; - java.util.Set handled=Collections.newSetFromMap(new java.util.IdentityHashMap()); - for(EnumFacing face:new EnumFacing[]{null,EnumFacing.DOWN,EnumFacing.UP,EnumFacing.NORTH,EnumFacing.SOUTH,EnumFacing.WEST,EnumFacing.EAST}) { - if(!tile.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,face))continue; - IItemHandler handler=tile.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,face); - if(!(handler instanceof IItemHandlerModifiable)||!handled.add(handler))continue; - for(int slot=0;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(); + } } } } @@ -303,46 +400,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; } @@ -350,6 +466,7 @@ private static ItemStack migrateStack(ItemStack stack, ModWorldState state) { if (stack == null) { return null; } + Item oldItem = stack.getItem(); Item grassItem = BuildingBricksCompat.grassSlab() == null ? null : Item.getItemFromBlock(BuildingBricksCompat.grassSlab()); @@ -357,17 +474,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 = ItemStack.loadItemStackFromNBT(serialized); - if (state != null && grass) state.recordGrassItems(stack.stackSize); - if (state != null && !grass) state.recordDirtItems(stack.stackSize); + + if (state != null && grass) { + state.recordGrassItems(stack.stackSize); + } + if (state != null && !grass) { + state.recordDirtItems(stack.stackSize); + } + return migrated; } + return null; } @@ -376,9 +503,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); @@ -392,8 +521,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"); @@ -407,15 +536,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. */ @@ -424,10 +554,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) { @@ -437,15 +567,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/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 c39a6ac..3f392c3 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 e802036..e27d5dc 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 == null || 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/ModBlocks.java b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java index e3feecd..aef87a4 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModBlocks.java @@ -31,10 +31,11 @@ private static void registerSlab(Block block, String name, Block combinedBlock) } private static void registerBlock(Block block, String name, ItemBlock item) { - ResourceLocation id = new ResourceLocation(SkysGrassSlabs.MOD_ID, name); - block.setRegistryName(id).setUnlocalizedName(SkysGrassSlabs.MOD_ID + "." + name) + ResourceLocation registryId = new ResourceLocation(SkysGrassSlabs.MOD_ID, name); + + block.setRegistryName(registryId).setUnlocalizedName(SkysGrassSlabs.MOD_ID + "." + name) .setCreativeTab(CreativeTabs.BUILDING_BLOCKS); - item.setRegistryName(id); + item.setRegistryName(registryId); GameRegistry.register(block); GameRegistry.register(item); } 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 9a0b441..59966c1 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/init/ModRecipes.java @@ -16,21 +16,19 @@ public final class ModRecipes { public static void register() { registerVanillaSeeds(); - GameRegistry.addShapedRecipe(new ItemStack(ModBlocks.DIRT_SLAB, 6), - "DDD", 'D', Blocks.DIRT); - GameRegistry.addShapedRecipe(new ItemStack(ModBlocks.GRASS_SLAB, 6), - "GGG", 'G', Blocks.GRASS); + GameRegistry.addShapedRecipe(new ItemStack(ModBlocks.DIRT_SLAB, 6), "DDD", 'D', Blocks.DIRT); + GameRegistry.addShapedRecipe(new ItemStack(ModBlocks.GRASS_SLAB, 6), "GGG", 'G', Blocks.GRASS); GameRegistry.addShapedRecipe(new ItemStack(Blocks.DIRT), "SS", 'S', new ItemStack(ModBlocks.DIRT_SLAB, 1, OreDictionary.WILDCARD_VALUE)); GameRegistry.addShapedRecipe(new ItemStack(Blocks.GRASS), "SS", 'S', new ItemStack(ModBlocks.GRASS_SLAB, 1, OreDictionary.WILDCARD_VALUE)); - GameRegistry.addRecipe(new ShapelessOreRecipe(new ItemStack(Blocks.GRASS), - new ItemStack(Blocks.DIRT), SEED_ORE)); + GameRegistry + .addRecipe(new ShapelessOreRecipe(new ItemStack(Blocks.GRASS), new ItemStack(Blocks.DIRT), SEED_ORE)); GameRegistry.addRecipe(new ShapelessOreRecipe(new ItemStack(ModBlocks.GRASS_SLAB), new ItemStack(ModBlocks.DIRT_SLAB), SEED_ORE)); BuildingBricksCompat.registerBridgeRecipes(); - RecipeSorter.register("skysgrassslabs:turf_cutting", TurfCuttingRecipe.class, - RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless"); + RecipeSorter.register("skysgrassslabs:turf_cutting", TurfCuttingRecipe.class, RecipeSorter.Category.SHAPELESS, + "after:minecraft:shapeless"); CraftingManager.getInstance().getRecipeList().add(new TurfCuttingRecipe()); } 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 7f01241..83e44e9 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/item/NormalizingSlabItem.java @@ -32,61 +32,72 @@ public int getMetadata(int damage) { } @Override - public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, - EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { - if (stack == null || stack.stackSize == 0 || - !player.canPlayerEdit(pos.offset(facing), facing, stack)) { + public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { + if (stack == null || stack.stackSize == 0 || !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(stack, 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.stackSize; } + 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 d6fa158..309c7e7 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/item/TurfBlockItem.java @@ -19,23 +19,29 @@ public TurfBlockItem(Block block) { } @Override - public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, - EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + public EnumActionResult onItemUse(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumHand hand, + EnumFacing facing, float hitX, float hitY, float hitZ) { 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.stackSize; } + return EnumActionResult.SUCCESS; } + return EnumActionResult.FAIL; } + return super.onItemUse(stack, 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 64609a8..25ad937 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java @@ -35,8 +35,10 @@ public void preInit() { private static void registerLegacyModels() { for (Block alias : BuildingBricksCompat.legacyAliases()) { final String path = alias == BuildingBricksCompat.dirtSlab() ? "dirt_slab" : "grass_slab"; + ModelLoader.setCustomStateMapper(alias, new StateMapperBase() { - @Override protected ModelResourceLocation getModelResourceLocation(IBlockState state) { + @Override + protected ModelResourceLocation getModelResourceLocation(IBlockState state) { return new ModelResourceLocation(new ResourceLocation(SkysGrassSlabs.MOD_ID, path), "half=" + state.getValue(BlockSlab.HALF).getName() + ",snowy=false"); } @@ -51,14 +53,15 @@ private static void registerLegacyModels() { 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(), BuildingBricksCompat.historicalGrassSlab()); 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 d5184c4..9e9ffcb 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/recipe/TurfCuttingRecipe.java @@ -16,8 +16,10 @@ public final class TurfCuttingRecipe implements IRecipe { 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 == null) { continue; } @@ -29,6 +31,7 @@ public boolean matches(InventoryCrafting inventory, World world) { return false; } } + return grassInputs == 1 && shovels == 1; } @@ -51,12 +54,16 @@ public ItemStack getRecipeOutput() { @Override public ItemStack[] getRemainingItems(InventoryCrafting inventory) { ItemStack[] remaining = new ItemStack[inventory.getSizeInventory()]; + for (int slot = 0; slot < inventory.getSizeInventory(); ++slot) { ItemStack stack = inventory.getStackInSlot(slot); + if (stack == null) { continue; } + ItemStack soil = soilRemainder(stack); + if (soil != null) { remaining[slot] = soil; } else if (isShovel(stack)) { @@ -64,23 +71,26 @@ public ItemStack[] getRemainingItems(InventoryCrafting inventory) { remaining[slot].stackSize = 1; } } + return remaining; } private static boolean isShovel(ItemStack stack) { Set toolClasses = stack.getItem().getToolClasses(stack); + return toolClasses != null && toolClasses.contains("shovel"); } 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 90f942d..0660a7f 100644 --- a/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java +++ b/src/main/java/zone/moddev/mc/skysgrassslabs/world/GrassSlabSmoothingHandler.java @@ -14,54 +14,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.chunkXPos, chunkPos.chunkZPos); + 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) { 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; } @@ -76,12 +83,15 @@ public void beforeDecoration(DecorateBiomeEvent.Pre event) { 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); + if (owner.setBlockState(target, ModBlocks.GRASS_SLAB.getDefaultState()) != null) { BlockPos support = target.down(); + if (owner.getBlockState(support).getBlock() == Blocks.GRASS) { owner.setBlockState(support, Blocks.DIRT.getDefaultState()); } @@ -89,52 +99,49 @@ public void beforeDecoration(DecorateBiomeEvent.Pre event) { } } - private static boolean hasHigherGrassNeighbour(World world, ChunkPos owner, - int localX, int localZ, int lowerSurfaceY) { - if (localX > 0 && isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, - localX - 1, localZ, lowerSurfaceY)) { + private static boolean hasHigherGrassNeighbour(World world, ChunkPos owner, int localX, int localZ, + int lowerSurfaceY) { + if (localX > 0 && isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, localX - 1, localZ, lowerSurfaceY)) { return true; } - if (localZ > 0 && isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, - localX, localZ - 1, lowerSurfaceY)) { + if (localZ > 0 && isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, localX, localZ - 1, lowerSurfaceY)) { return true; } if (localX < 15) { - if (isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, - localX + 1, localZ, lowerSurfaceY)) { + if (isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, localX + 1, localZ, lowerSurfaceY)) { return true; } - } else if (isHigherGrass(world, owner.chunkXPos + 1, owner.chunkZPos, - 0, localZ, lowerSurfaceY)) { + } else if (isHigherGrass(world, owner.chunkXPos + 1, owner.chunkZPos, 0, localZ, lowerSurfaceY)) { return true; } if (localZ < 15) { - return isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, - localX, localZ + 1, lowerSurfaceY); + return isHigherGrass(world, owner.chunkXPos, owner.chunkZPos, localX, localZ + 1, lowerSurfaceY); } - return isHigherGrass(world, owner.chunkXPos, owner.chunkZPos + 1, - localX, 0, lowerSurfaceY); + + return isHigherGrass(world, owner.chunkXPos, owner.chunkZPos + 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 b73c6db..6986651 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().worldServerForDimension(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/LocalizationContractTest.java b/src/test/java/zone/moddev/mc/skysgrassslabs/LocalizationContractTest.java index 86d8083..5202e8f 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 5eb5e15..38cd9f2 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/ProjectContractTest.java @@ -17,21 +17,27 @@ class ProjectContractTest { @Test void absentRecoveryKeepsHistoricalIdentitiesSeparate() throws Exception { String compat = read("src/main/java/zone/moddev/mc/skysgrassslabs/compat/BuildingBricksCompat.java"); - for (String registration : new String[] {"GRASS_SLAB_ID, true", "DIRT_SLAB_ID, false", - "HISTORICAL_GRASS_SLAB_ID, true"}) { + + for (String registration : new String[] { "GRASS_SLAB_ID, true", "DIRT_SLAB_ID, false", + "HISTORICAL_GRASS_SLAB_ID, true" }) { assertTrue(compat.contains("registerLegacyAlias(" + registration + ")")); } + String migration = read("src/main/java/zone/moddev/mc/skysgrassslabs/compat/LegacyMigrationHandler.java"); + assertTrue(migration.contains("BuildingBricksCompat.hasLegacyAliases()")); - assertTrue(migration.indexOf("if (BuildingBricksCompat.hasLegacyAliases())") < - migration.indexOf("if (!shouldMigrateChunk(")); + assertTrue(migration.indexOf("if (BuildingBricksCompat.hasLegacyAliases())") < migration + .indexOf("if (!shouldMigrateChunk(")); + String client = read("src/main/java/zone/moddev/mc/skysgrassslabs/proxy/ClientProxy.java"); + assertTrue(client.contains("registerLegacyModels")); } @Test void releaseIdentityAndToolchainArePinned() throws Exception { String properties = read("gradle.properties"); + assertTrue(properties.contains("minecraft_version=1.10.2")); assertTrue(properties.contains("forge_version=12.18.3.2511")); assertTrue(properties.contains("mapping_version=29-1.10.2")); @@ -48,9 +54,11 @@ 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"); - for (String id : new String[] {"dirt_slab", "grass_slab", "path_slab", "turf"}) { - assertTrue(blocks.contains("\"" + id + "\"")); + + 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")); @@ -62,12 +70,15 @@ void saveFacingIdsAndLegacyRecipeKeyRemainStable() throws Exception { @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)); @@ -78,6 +89,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:")); @@ -90,34 +102,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); } } @@ -125,11 +144,14 @@ void localContextIsNotTracked() throws Exception { @Test void blockModelsUseMinecraftOneTenParents() 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); } } @@ -139,19 +161,22 @@ void blockModelsUseMinecraftOneTenParents() 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")); @@ -160,11 +185,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]")); } @@ -174,6 +198,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)")); @@ -187,6 +212,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 143ec50..3fc345b 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/block/GrassFormsContractTest.java @@ -10,24 +10,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)); + + for (int metadata = 0; metadata < 2; metadata++) { + GrassSlabsApi.registerGrassForm(ModBlocks.DIRT_SLAB.getStateFromMeta(metadata), + ModBlocks.GRASS_SLAB.getStateFromMeta(metadata)); + } } - @Test void bothOrientationsRoundTripAndSnowDoesNotChangeIdentity() { - for(int metadata=0;metadata<2;metadata++) { - IBlockState dirt=ModBlocks.DIRT_SLAB.getStateFromMeta(metadata),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)); - assertTrue(GrassSlabsApi.isGrassForm(grass));assertFalse(GrassSlabsApi.isGrassForm(dirt)); + + @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)); + 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 b5458a2..807f5c5 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/block/SlabContractTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/block/SlabContractTest.java @@ -32,41 +32,33 @@ 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() { assertEquals(1.0D / 16.0D, - ModBlocks.TURF.getBoundingBox(ModBlocks.TURF.getDefaultState(), null, - BlockPos.ORIGIN).maxY); + ModBlocks.TURF.getBoundingBox(ModBlocks.TURF.getDefaultState(), null, BlockPos.ORIGIN).maxY); assertFalse(ModBlocks.TURF.isFullCube(ModBlocks.TURF.getDefaultState())); assertFalse(ModBlocks.TURF.isOpaqueCube(ModBlocks.TURF.getDefaultState())); } @@ -78,11 +70,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 621186c..0be6a52 100644 --- a/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java +++ b/src/test/java/zone/moddev/mc/skysgrassslabs/compat/MigrationMappingTest.java @@ -20,13 +20,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)); } } @@ -34,31 +33,61 @@ 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)); } - @Test void holdersRetainBothOrientationsWithoutPlayableEntries() { - for(boolean grass:new boolean[]{false,true}) { - LegacySlabAliasBlock holder=new LegacySlabAliasBlock(grass); - assertNull(holder.getCreativeTabToDisplayOn());assertFalse(holder.hasTileEntity(holder.getDefaultState())); - for(int metadata=0;metadata<16;metadata++)assertEquals(metadata&1,holder.getMetaFromState(holder.getStateFromMeta(metadata))); + @Test + void holdersRetainBothOrientationsWithoutPlayableEntries() { + for (boolean grass : new boolean[] { false, true }) { + LegacySlabAliasBlock holder = new LegacySlabAliasBlock(grass); + + assertNull(holder.getCreativeTabToDisplayOn()); + assertFalse(holder.hasTileEntity(holder.getDefaultState())); + + for (int metadata = 0; metadata < 16; metadata++) { + assertEquals(metadata & 1, holder.getMetaFromState(holder.getStateFromMeta(metadata))); + } } } - @Test void historicalStacksKeepCountsAndUnrelatedNestedData() { - if(ModBlocks.GRASS_SLAB.getRegistryName()==null)ModBlocks.GRASS_SLAB.setRegistryName("skysgrassslabs","grass_slab"); - if(ModBlocks.DIRT_SLAB.getRegistryName()==null)ModBlocks.DIRT_SLAB.setRegistryName("skysgrassslabs","dirt_slab"); - for(String id:new String[]{"buildingbricks:grass_slab","buildingbricks:dirt_slab","buildingbrickscompatvanilla:grass_slab"}) { - NBTTagCompound stack=new NBTTagCompound(),tag=new NBTTagCompound();stack.setString("id",id);stack.setByte("Count",(byte)47);stack.setShort("Damage",(short)13); - tag.setString("custom_name","Original");tag.setInteger("RepairCost",9);tag.setString("material","minecraft:dirt");stack.setTag("tag",tag); - NBTTagCompound caps=new NBTTagCompound();caps.setString("other_capability","preserved");stack.setTag("ForgeCaps",caps); - assertTrue(LegacyMigrationHandler.migrateStacksInNbt(stack,null)); - assertEquals(id.contains("dirt_slab")?"skysgrassslabs:dirt_slab":"skysgrassslabs:grass_slab",stack.getString("id")); - assertEquals(47,stack.getByte("Count"));assertEquals(tag,stack.getCompoundTag("tag"));assertEquals(caps,stack.getCompoundTag("ForgeCaps")); - NBTTagCompound converted=stack.copy();assertFalse(LegacyMigrationHandler.migrateStacksInNbt(stack,null));assertEquals(converted,stack); + @Test + void historicalStacksKeepCountsAndUnrelatedNestedData() { + if (ModBlocks.GRASS_SLAB.getRegistryName() == null) { + ModBlocks.GRASS_SLAB.setRegistryName("skysgrassslabs", "grass_slab"); + } + if (ModBlocks.DIRT_SLAB.getRegistryName() == null) { + ModBlocks.DIRT_SLAB.setRegistryName("skysgrassslabs", "dirt_slab"); + } + for (String registryId : new String[] { "buildingbricks:grass_slab", "buildingbricks:dirt_slab", + "buildingbrickscompatvanilla:grass_slab" }) { + NBTTagCompound stack = new NBTTagCompound(); + NBTTagCompound tag = new NBTTagCompound(); + + stack.setString("id", registryId); + stack.setByte("Count", (byte) 47); + stack.setShort("Damage", (short) 13); + tag.setString("custom_name", "Original"); + tag.setInteger("RepairCost", 9); + tag.setString("material", "minecraft:dirt"); + stack.setTag("tag", tag); + + NBTTagCompound caps = new NBTTagCompound(); + + caps.setString("other_capability", "preserved"); + stack.setTag("ForgeCaps", caps); + assertTrue(LegacyMigrationHandler.migrateStacksInNbt(stack, null)); + assertEquals(registryId.contains("dirt_slab") ? "skysgrassslabs:dirt_slab" : "skysgrassslabs:grass_slab", + stack.getString("id")); + assertEquals(47, stack.getByte("Count")); + assertEquals(tag, stack.getCompoundTag("tag")); + assertEquals(caps, stack.getCompoundTag("ForgeCaps")); + + NBTTagCompound converted = stack.copy(); + + assertFalse(LegacyMigrationHandler.migrateStacksInNbt(stack, null)); + assertEquals(converted, stack); } } } 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 7f9d6c5..4f7df7a 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(0x110021L); + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +