From 57d6f31adb5cf97028c4e3c3b3fda82d0580ecbf Mon Sep 17 00:00:00 2001 From: Dennis C Date: Sun, 13 Mar 2022 22:59:59 +0100 Subject: [PATCH] [1.18.x] Allow blocks to hide faces on a neighboring block (#8300) * Allow blocks to hide faces on a neighboring block * Allow blocks to opt-out of external face hiding --- .../world/level/BlockGetter.java.patch | 9 ++ .../world/level/block/Block.java.patch | 9 ++ .../client/ForgeHooksClient.java | 5 + .../common/extensions/IForgeBlock.java | 41 +++++++ .../common/extensions/IForgeBlockGetter.java | 49 +++++++++ .../common/extensions/IForgeBlockState.java | 27 +++++ .../debug/block/HideNeighborFaceTest.java | 100 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + .../blockstates/glass_slab.json | 13 +++ .../models/block/glass_slab.json | 8 ++ .../models/block/glass_slab_top.json | 8 ++ 11 files changed, 271 insertions(+) create mode 100644 src/main/java/net/minecraftforge/common/extensions/IForgeBlockGetter.java create mode 100644 src/test/java/net/minecraftforge/debug/block/HideNeighborFaceTest.java create mode 100644 src/test/resources/assets/hide_neighbor_face_test/blockstates/glass_slab.json create mode 100644 src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab.json create mode 100644 src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab_top.json diff --git a/patches/minecraft/net/minecraft/world/level/BlockGetter.java.patch b/patches/minecraft/net/minecraft/world/level/BlockGetter.java.patch index d6f63afde9..ddb49a9981 100644 --- a/patches/minecraft/net/minecraft/world/level/BlockGetter.java.patch +++ b/patches/minecraft/net/minecraft/world/level/BlockGetter.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/world/level/BlockGetter.java +++ b/net/minecraft/world/level/BlockGetter.java +@@ -18,7 +_,7 @@ + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.VoxelShape; + +-public interface BlockGetter extends LevelHeightAccessor { ++public interface BlockGetter extends LevelHeightAccessor, net.minecraftforge.common.extensions.IForgeBlockGetter { + @Nullable + BlockEntity m_7702_(BlockPos p_45570_); + @@ -32,7 +_,7 @@ FluidState m_6425_(BlockPos p_45569_); diff --git a/patches/minecraft/net/minecraft/world/level/block/Block.java.patch b/patches/minecraft/net/minecraft/world/level/block/Block.java.patch index abd7eb903a..1805ab8f7e 100644 --- a/patches/minecraft/net/minecraft/world/level/block/Block.java.patch +++ b/patches/minecraft/net/minecraft/world/level/block/Block.java.patch @@ -23,6 +23,15 @@ } public static boolean m_152463_(BlockState p_152464_) { +@@ -193,6 +_,8 @@ + BlockState blockstate = p_152446_.m_8055_(p_152449_); + if (p_152445_.m_60719_(blockstate, p_152448_)) { + return false; ++ } else if (p_152445_.supportsExternalFaceHiding() && blockstate.hidesNeighborFace(p_152446_, p_152449_, p_152445_, p_152448_.m_122424_())) { ++ return false; + } else if (blockstate.m_60815_()) { + Block.BlockStatePairKey block$blockstatepairkey = new Block.BlockStatePairKey(p_152445_, blockstate, p_152448_); + Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap = f_49789_.get(); @@ -324,7 +_,7 @@ } diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 3b6f84ea66..e430fda139 100755 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -1074,4 +1074,9 @@ public class ForgeHooksClient MinecraftForge.EVENT_BUS.post(event); return event.getResult(); } + + public static boolean isBlockInSolidLayer(BlockState state) + { + return ItemBlockRenderTypes.canRenderInLayer(state, RenderType.solid()); + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java index d22634b841..0dd95ac4b2 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java @@ -32,6 +32,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.server.level.ServerLevel; +import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.IPlantable; @@ -44,6 +45,7 @@ import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.ToolAction; import net.minecraftforge.common.ToolActions; +import net.minecraftforge.fml.loading.FMLEnvironment; @SuppressWarnings("deprecation") public interface IForgeBlock @@ -749,4 +751,43 @@ public interface IForgeBlock return state.isSignalSource() && direction != null; } } + + /** + * Whether this block hides the neighbors face pointed towards by the given direction. + *

+ * This method should only be used for blocks you don't control, for your own blocks override + * {@link Block#skipRendering(BlockState, BlockState, Direction)} on the respective block instead + *

+ * WARNING: This method is likely to be called from a worker thread! If you want to retrieve a + * {@link net.minecraft.world.level.block.entity.BlockEntity} from the given level, make sure to use + * {@link net.minecraftforge.common.extensions.IForgeBlockGetter#getExistingBlockEntity(BlockPos)} to not + * accidentally create a new or delete an old {@link net.minecraft.world.level.block.entity.BlockEntity} + * off of the main thread as this would cause a write operation to the given {@link BlockGetter} and cause + * a CME in the process. Any other direct or indirect write operation to the {@link BlockGetter} will have + * the same outcome. + * + * @param level The world + * @param pos The blocks position in the world + * @param state The blocks {@link BlockState} + * @param neighborState The neighboring blocks {@link BlockState} + * @param dir The direction towards the neighboring block + */ + default boolean hidesNeighborFace(BlockGetter level, BlockPos pos, BlockState state, BlockState neighborState, Direction dir) + { + return false; + } + + /** + * Whether this block allows a neighboring block to hide the face of this block it touches. + * If this returns true, {@link IForgeBlockState#hidesNeighborFace(BlockGetter, BlockPos, BlockState, Direction)} + * will be called on the neighboring block. + */ + default boolean supportsExternalFaceHiding(BlockState state) + { + if (FMLEnvironment.dist.isClient()) + { + return !ForgeHooksClient.isBlockInSolidLayer(state); + } + return true; + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockGetter.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockGetter.java new file mode 100644 index 0000000000..cf4bc993b1 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockGetter.java @@ -0,0 +1,49 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.extensions; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.*; +import org.jetbrains.annotations.Nullable; + +public interface IForgeBlockGetter +{ + private BlockGetter self() { return (BlockGetter) this; } + + /** + * Get the {@link BlockEntity} at the given position if it exists. + *

+ * {@link Level#getBlockEntity(BlockPos)} would create a new {@link BlockEntity} if the + * {@link net.minecraft.world.level.block.Block} has one, but it has not been placed in the world yet + * (This can happen on world load). + * @return The BlockEntity at the given position or null if it doesn't exist + */ + @Nullable + default BlockEntity getExistingBlockEntity(BlockPos pos) + { + if (this instanceof Level level) + { + if (!level.hasChunk(pos.getX(), pos.getZ())) + { + return null; + } + + return level.getChunk(pos).getExistingBlockEntity(pos); + } + else if (this instanceof LevelChunk chunk) + { + return chunk.getBlockEntities().get(pos); + } + else if (this instanceof ImposterProtoChunk chunk) + { + return chunk.getWrapped().getExistingBlockEntity(pos); + } + return self().getBlockEntity(pos); + } +} diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java index a5342f6a47..30b6627a82 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java @@ -646,4 +646,31 @@ public interface IForgeBlockState { return self().getBlock().canConnectRedstone(self(), level, pos, direction); } + + /** + * Whether this block hides the neighbors face pointed towards by the given direction. + *

+ * This method should only be used for blocks you don't control, for your own blocks override + * {@link net.minecraft.world.level.block.Block#skipRendering(BlockState, BlockState, Direction)} + * on the respective block instead + * + * @param level The world + * @param pos The blocks position in the world + * @param neighborState The neighboring blocks {@link BlockState} + * @param dir The direction towards the neighboring block + */ + default boolean hidesNeighborFace(BlockGetter level, BlockPos pos, BlockState neighborState, Direction dir) + { + return self().getBlock().hidesNeighborFace(level, pos, self(), neighborState, dir); + } + + /** + * Whether this block allows a neighboring block to hide the face of this block it touches. + * If this returns true, {@link IForgeBlockState#hidesNeighborFace(BlockGetter, BlockPos, BlockState, Direction)} + * will be called on the neighboring block. + */ + default boolean supportsExternalFaceHiding() + { + return self().getBlock().supportsExternalFaceHiding(self()); + } } diff --git a/src/test/java/net/minecraftforge/debug/block/HideNeighborFaceTest.java b/src/test/java/net/minecraftforge/debug/block/HideNeighborFaceTest.java new file mode 100644 index 0000000000..61a272e3f5 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/block/HideNeighborFaceTest.java @@ -0,0 +1,100 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.block; + +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +@Mod(HideNeighborFaceTest.MOD_ID) +public class HideNeighborFaceTest +{ + public static final String MOD_ID = "hide_neighbor_face_test"; + + private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MOD_ID); + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + + private static final RegistryObject GLASS_SLAB = BLOCKS.register("glass_slab", GlassSlab::new); + private static final RegistryObject GLASS_SLAB_ITEM = ITEMS.register("glass_slab", () -> new BlockItem(GLASS_SLAB.get(), new Item.Properties())); + + public HideNeighborFaceTest() + { + IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); + + BLOCKS.register(bus); + ITEMS.register(bus); + } + + private static class GlassSlab extends SlabBlock + { + public GlassSlab() { super(Properties.copy(Blocks.GLASS)); } + + @Override + public boolean skipRendering(BlockState state, BlockState neighborState, Direction face) + { + SlabType type = state.getValue(TYPE); + + if (neighborState.is(Blocks.GLASS)) + { + return (type == SlabType.BOTTOM && face == Direction.DOWN) || + (type == SlabType.TOP && face == Direction.UP) || + type == SlabType.DOUBLE; + } + else if (neighborState.is(this)) + { + SlabType neighborType = neighborState.getValue(TYPE); + return (type != SlabType.BOTTOM && neighborType != SlabType.TOP && face == Direction.UP) || + (type != SlabType.TOP && neighborType != SlabType.BOTTOM && face == Direction.DOWN) || + (type == neighborType && face.getAxis() != Direction.Axis.Y); + } + + return false; + } + + @Override + public boolean hidesNeighborFace(BlockGetter level, BlockPos pos, BlockState state, BlockState neighborState, Direction dir) + { + SlabType type = state.getValue(TYPE); + + if (neighborState.is(Blocks.GLASS)) + { + return (type == SlabType.BOTTOM && dir == Direction.DOWN) || + (type == SlabType.TOP && dir == Direction.UP) || + type == SlabType.DOUBLE; + } + + return false; + } + } + + @Mod.EventBusSubscriber(value = Dist.CLIENT, modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) + public static class ClientEvents + { + @SubscribeEvent + public static void onClientSetup(final FMLClientSetupEvent event) + { + ItemBlockRenderTypes.setRenderLayer(GLASS_SLAB.get(), RenderType.cutout()); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 5422baebc1..f8402c0d02 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -200,5 +200,7 @@ license="LGPL v2.1" modId="gametest_test" [[mods]] modId="potion_size_event_test" +[[mods]] + modId="hide_neighbor_face_test" # ADD ABOVE THIS LINE diff --git a/src/test/resources/assets/hide_neighbor_face_test/blockstates/glass_slab.json b/src/test/resources/assets/hide_neighbor_face_test/blockstates/glass_slab.json new file mode 100644 index 0000000000..dac9d8866b --- /dev/null +++ b/src/test/resources/assets/hide_neighbor_face_test/blockstates/glass_slab.json @@ -0,0 +1,13 @@ +{ + "variants": { + "type=bottom": { + "model": "hide_neighbor_face_test:block/glass_slab" + }, + "type=double": { + "model": "minecraft:block/glass" + }, + "type=top": { + "model": "hide_neighbor_face_test:block/glass_slab_top" + } + } +} \ No newline at end of file diff --git a/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab.json b/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab.json new file mode 100644 index 0000000000..5754a4adde --- /dev/null +++ b/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab.json @@ -0,0 +1,8 @@ +{ + "parent": "minecraft:block/slab", + "textures": { + "bottom": "minecraft:block/glass", + "top": "minecraft:block/glass", + "side": "minecraft:block/glass" + } +} \ No newline at end of file diff --git a/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab_top.json b/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab_top.json new file mode 100644 index 0000000000..520290e898 --- /dev/null +++ b/src/test/resources/assets/hide_neighbor_face_test/models/block/glass_slab_top.json @@ -0,0 +1,8 @@ +{ + "parent": "minecraft:block/slab_top", + "textures": { + "bottom": "minecraft:block/glass", + "top": "minecraft:block/glass", + "side": "minecraft:block/glass" + } +} \ No newline at end of file