diff --git a/README.md b/README.md index c151d55..21985c7 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ ### Configuration File: [config.yml](https://github.com/TWME-TW/DebugStickPro/blob/main/src/main/resources/config.yml) +`BlockDataFilter.AllowUnsafeBisectedData` is disabled by default to prevent invalid +double-block structures and duplicate drops. Enable it only when multi-block +`Bisected` properties are intentionally needed. + ### Language File: [lang](https://github.com/TWME-TW/DebugStickPro/tree/main/src/main/resources/lang) ### Permissions: diff --git a/pom.xml b/pom.xml index 57a5f76..6b94a40 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,12 @@ 3.3.3+119e9e1-SNAPSHOT compile + + junit + junit + 4.13.2 + test + diff --git a/src/main/java/dev/twme/debugstickpro/DebugStickPro.java b/src/main/java/dev/twme/debugstickpro/DebugStickPro.java index c102a01..2200e98 100644 --- a/src/main/java/dev/twme/debugstickpro/DebugStickPro.java +++ b/src/main/java/dev/twme/debugstickpro/DebugStickPro.java @@ -63,7 +63,7 @@ public final class DebugStickPro extends JavaPlugin { /** * This is the version of the plugin */ - public static final int CONFIG_VERSION = 7; + public static final int CONFIG_VERSION = 8; /** * This is the version of the language file diff --git a/src/main/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparater.java b/src/main/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparater.java index 5dee3e1..2a06af7 100644 --- a/src/main/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparater.java +++ b/src/main/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparater.java @@ -222,7 +222,7 @@ private static ArrayList separateRaw(BlockData blockData) { blockDataList.add(bigDripleafData); } - if (blockData instanceof Bisected) { + if (isBisectedHalfSafeToModify(blockData)) { SubBlockData bisectedData = new BisectedData(blockData); blockDataList.add(bisectedData); } @@ -881,4 +881,16 @@ private static ArrayList separateRaw(BlockData blockData) { return cloneForBlockData(blockDataList, blockData); } + + static boolean isBisectedHalfSafeToModify(BlockData blockData) { + if (ConfigFile.BlockDataFilter.AllowUnsafeBisectedData) { + return blockData instanceof Bisected; + } + + // Stairs and trapdoors use half as a single-block placement property. Other + // Bisected blocks depend on a matching block above or below; exposing half + // lets one side become a second bottom half and duplicate its drops. + return blockData instanceof Bisected + && (blockData instanceof Stairs || blockData instanceof TrapDoor); + } } diff --git a/src/main/java/dev/twme/debugstickpro/config/ConfigFile.java b/src/main/java/dev/twme/debugstickpro/config/ConfigFile.java index b55571c..d863a57 100644 --- a/src/main/java/dev/twme/debugstickpro/config/ConfigFile.java +++ b/src/main/java/dev/twme/debugstickpro/config/ConfigFile.java @@ -63,6 +63,8 @@ public static class Blacklist { } public static class BlockDataFilter { + public static boolean AllowUnsafeBisectedData; + public static class Whitelist { public static boolean Enabled; public static HashSet Whitelist; diff --git a/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java b/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java index 9192734..d62b4ba 100644 --- a/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java +++ b/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java @@ -107,6 +107,7 @@ private void loadValues() { ConfigFile.BlockDataFilter.Whitelist.Whitelist = new HashSet<>(config.getStringList("BlockDataFilter.Whitelist.Whitelist")); ConfigFile.BlockDataFilter.Blacklist.Enabled = config.getBoolean("BlockDataFilter.Blacklist.Enabled"); ConfigFile.BlockDataFilter.Blacklist.Blacklist = new HashSet<>(config.getStringList("BlockDataFilter.Blacklist.Blacklist")); + ConfigFile.BlockDataFilter.AllowUnsafeBisectedData = config.getBoolean("BlockDataFilter.AllowUnsafeBisectedData"); ConfigFile.ModeSetting.ClassicMode.ClearSelectedDataTypeWhenModeChange = config.getBoolean("ModeSetting.ClassicMode.ClearSelectedDataTypeWhenModeChange"); ConfigFile.ModeSetting.CopyMode.ClearStoredDataWhenModeChange = config.getBoolean("ModeSetting.CopyMode.ClearStoredDataWhenModeChange"); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 892a0a6..e05b848 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ # Don't touch this value -ConfigVersion: 7 +ConfigVersion: 8 Language: # Language files are located in the "lang" folder. @@ -67,6 +67,10 @@ AutoRegionProtection: Enabled: true BlockDataFilter: + # If enabled, exposes the half property of multi-block structures such as doors + # and double plants. This can create invalid block pairs and duplicate drops. + # Default: false + AllowUnsafeBisectedData: false Whitelist: # If enabled, only the specified block data will be displayed. Enabled: false diff --git a/src/test/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparaterTest.java b/src/test/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparaterTest.java new file mode 100644 index 0000000..f587c01 --- /dev/null +++ b/src/test/java/dev/twme/debugstickpro/blockdatautil/BlockDataSeparaterTest.java @@ -0,0 +1,60 @@ +package dev.twme.debugstickpro.blockdatautil; + +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Door; +import org.bukkit.block.data.type.PitcherCrop; +import org.bukkit.block.data.type.SmallDripleaf; +import org.bukkit.block.data.type.Stairs; +import org.bukkit.block.data.type.TrapDoor; +import org.junit.Test; + +import dev.twme.debugstickpro.config.ConfigFile; + +import java.lang.reflect.Proxy; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BlockDataSeparaterTest { + + @Test + public void allowsSingleBlockBisectedProperties() { + assertTrue(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(Stairs.class))); + assertTrue(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(TrapDoor.class))); + } + + @Test + public void rejectsMultiBlockBisectedProperties() { + assertFalse(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(Bisected.class))); + assertFalse(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(Door.class))); + assertFalse(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(SmallDripleaf.class))); + assertFalse(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(PitcherCrop.class))); + } + + @Test + public void allowsUnsafeBisectedPropertiesWhenConfigured() { + ConfigFile.BlockDataFilter.AllowUnsafeBisectedData = true; + try { + assertTrue(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(Bisected.class))); + assertTrue(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(Door.class))); + } finally { + ConfigFile.BlockDataFilter.AllowUnsafeBisectedData = false; + } + } + + @Test + public void rejectsNonBisectedProperties() { + assertFalse(BlockDataSeparater.isBisectedHalfSafeToModify(blockData(BlockData.class))); + } + + private static BlockData blockData(Class type) { + return (BlockData) Proxy.newProxyInstance( + type.getClassLoader(), + new Class[]{type}, + (proxy, method, args) -> { + throw new AssertionError("Unexpected BlockData method call: " + method.getName()); + } + ); + } +}