Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit 83fd279

Browse files
authored
Impl IForgeBlockState.hasTileEntity and createTileEntity (#105)
* Impl IForgeBlockState.hasTileEntity and createTileEntity * Rename methods * Update README.MD * Move blockentity mixins to a separate package * Add missing @unique * Readme.md
1 parent 41ccb63 commit 83fd279

21 files changed

+1139
-1
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# `IForgeBlockState.hasTileEntity` and `IForgeBlockState.createTileEntity`
2+
We patched all vanilla calls to those two methods so that Forge's blockstate-sensitive handlers can receive blockstate as the parameter.
3+
There are around 20 patches in this group:
4+
```
5+
* OK net.minecraft.command.arguments.BlockArgumentParser
6+
* OK net.minecraft.block.entity.HopperBlockEntity
7+
* OK net.minecraft.block.entity.ChestBlockEntity
8+
* OK net.minecraft.block.AbstractRedstoneGateBlock (net.minecraft.block.RedstoneDiodeBlock)
9+
* OK net.minecraft.block.Block
10+
* OK net.minecraft.block.SpongeBlock
11+
* OK net.minecraft.block.PistonBlock
12+
* OK net.minecraft.client.render.chunk.ChunkRenderer (net.minecraft.client.renderer.chunk.ChunkRenderer)
13+
* OK net.minecraft.client.render.WorldRenderer
14+
* OK net.minecraft.client.MinecraftClient
15+
* OK net.minecraft.entity.FallingBlockEntity (net.minecraft.entity.item.FallingBlockEntity)
16+
* OK net.minecraft.server.world.ChunkHolder (net.minecraft.world.server.ChunkHolder)
17+
* OK net.minecraft.world.explosion.Explosion (net.minecraft.world.Explosion)
18+
* OK net.minecraft.world.chunk.WorldChunk (net.minecraft.world.chunk.Chunk)
19+
* OK net.minecraft.world.ChunkRegion (net.minecraft.world.gen.WorldGenRegion)
20+
* OK net.minecraft.world.World
21+
```
22+
Vanilla uses both `block.hasBlockEntity()` and `block instanceof BlockEntityProvider`, which is considered to be a bad practice. All of these checks are redirected to `BlockContext`.
23+
`BlockContext.hasBlockEntity(BlockState)` calls the patched `Block.hasBlockEntity()`, which then calls the `IForgeBlock.hasBlockEntity(BlockState)`.
24+
`Block.hasBlockEntity()` will attempt to get the blockstate from a ThreadLocal managed by `BlockContext.hasBlockEntity(BlockState)` as the parameter, if the ThreadLocal is not set,
25+
the default blockstate of that block will be passed to `IForgeBlock.hasBlockEntity(BlockState)`.
26+
__The reason why we did not deprecate the vanilla `block.hasBlockEntity()` is that Fabric mods may call them.__
27+
28+
29+
Common patch types are:
30+
1. For `blockstate.getBlock().hasBlockEntity()` \
31+
`getBlock()` is redirected to `call BlockContext.hasBlockEntity`, if the test returns true, the mixin method will return a vanilla block which is known to always have BlockEntity,
32+
otherwise it will return `Blocks.AIR`.
33+
1. For `block.hasBlockEntity()` \
34+
Similar to the previous case, but the blockstate is not directly available here.
35+
Either get it from the method args or collect it from the context. \
36+
The latter requires ThreadLocal variables (called context here),
37+
they are collected using a @Injection which captures all local variables visible to the scope.
38+
The @Injection mixin set the context, the mixin which does the check consumes and then releases the context.
39+
__To support potential recursive calling to the mixin target function, the lifespan of the context has to be minimized.__
40+
1. `block instanceof BlockEntityProvider` \
41+
These are patched with @ModifyConstant. Some cases also require a ThreadLocal for temporary storing the blockstate. The lifespan rule from the previous case also applies here.
42+
43+
# `IForgeBlockState.isBurning`
44+
```
45+
* net.minecraft.world.World
46+
```
47+
48+
# `IForgeBlockState.removedByPlayer and canHarvestBlock`
49+
```
50+
* net.minecraft.server.network.ServerPlayerInteractionManager
51+
* net.minecraft.client.network.ClientPlayerInteractionManager
52+
```
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.impl.extensions.block;
21+
22+
import net.minecraftforge.common.extensions.IForgeBlock;
23+
24+
import net.minecraft.block.Block;
25+
import net.minecraft.block.BlockState;
26+
import net.minecraft.block.Blocks;
27+
28+
public class BlockContext {
29+
private static final ThreadLocal<Object> hasBlockEntity_blockState = createContext();
30+
31+
//////////////////////////////////////////////////////////////
32+
/// Context helper
33+
/// Pass parameters without using method args
34+
/// Thread safe, but DOES NOT support recursive calls
35+
/// It is the caller's responsibility to maintain the context
36+
//////////////////////////////////////////////////////////////
37+
private static final Object CLEAN_MARKER = new Object();
38+
39+
public static ThreadLocal<Object> createContext() {
40+
return ThreadLocal.withInitial(() -> BlockContext.CLEAN_MARKER);
41+
}
42+
43+
public static void setContext(ThreadLocal<Object> stack, Object value) {
44+
Object oldValue = stack.get();
45+
46+
if (oldValue != CLEAN_MARKER) {
47+
throw new IllegalStateException("The context is not clean.");
48+
}
49+
50+
stack.set(value);
51+
}
52+
53+
@SuppressWarnings("unchecked")
54+
public static <T> T getContext(ThreadLocal<Object> stack) {
55+
Object oldValue = stack.get();
56+
57+
if (oldValue == CLEAN_MARKER) {
58+
throw new IllegalStateException("The context is not set.");
59+
}
60+
61+
return (T) stack.get();
62+
}
63+
64+
@SuppressWarnings("unchecked")
65+
public static <T> T getContextOr(ThreadLocal<Object> stack, T defaultValue) {
66+
Object value = stack.get();
67+
68+
if (value == CLEAN_MARKER) {
69+
return defaultValue;
70+
} else {
71+
return (T) value;
72+
}
73+
}
74+
75+
@SuppressWarnings("unchecked")
76+
public static <T> T releaseContext(ThreadLocal<Object> stack) {
77+
Object oldValue = stack.get();
78+
79+
if (oldValue == CLEAN_MARKER) {
80+
throw new IllegalStateException("The context is not set.");
81+
}
82+
83+
stack.set(CLEAN_MARKER);
84+
85+
return (T) oldValue;
86+
}
87+
88+
///////////////////////////////////////////////////////
89+
/// Block.hasBlockEntity()
90+
///////////////////////////////////////////////////////
91+
/**
92+
* Called by mixin methods.
93+
* @param blockState
94+
* @return
95+
*/
96+
public static boolean hasBlockEntity(BlockState blockState) {
97+
setContext(hasBlockEntity_blockState, blockState);
98+
Block block = blockState.getBlock();
99+
boolean ret = block.hasBlockEntity();
100+
releaseContext(hasBlockEntity_blockState);
101+
return ret;
102+
}
103+
104+
/**
105+
* Called by vanilla Block Class, as a wrapper which redirects the call to Forge's BlockState sensitive version.
106+
* @param forgeBlock
107+
* @return
108+
*/
109+
public static boolean block_hasBlockEntity(IForgeBlock forgeBlock) {
110+
BlockState blockState = getContextOr(hasBlockEntity_blockState, forgeBlock.getBlock().getDefaultState());
111+
112+
return forgeBlock.hasTileEntity(blockState);
113+
}
114+
115+
/**
116+
* Called by mixin methods. The return value is used to pass the vanilla Block.hasBlockEntity or block instanceof BlockEntityProvider.
117+
* @param hasBlockEntity
118+
* @return Blocks.CHEST (Always have a BlockEntity) if hasBlockEntity is true, otherwise Blocks.AIR (impossible to host BlockEntity)
119+
*/
120+
public static Block hasBlockEntityBlockMarker(boolean hasBlockEntity) {
121+
return hasBlockEntity ? Blocks.CHEST : Blocks.AIR;
122+
}
123+
124+
public static Block hasBlockEntityBlockMarker(BlockState blockState) {
125+
return hasBlockEntityBlockMarker(hasBlockEntity(blockState));
126+
}
127+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.impl.extensions.block;
21+
22+
public class Signatures {
23+
public static final String PATCHWORK_YARN_CLS_BLOCKENTITYPROVIDER = "classValue=net/minecraft/block/BlockEntityProvider";
24+
public static final String PATCHWORK_REOBF_CLS_BLOCKENTITYPROVIDER = "classValue=net/minecraft/class_2343";
25+
26+
// Method Signatures
27+
public static final String Block_hasBlockEntity = "net/minecraft/block/Block.hasBlockEntity()Z";
28+
public static final String BlockState_getBlock = "net/minecraft/block/BlockState.getBlock()Lnet/minecraft/block/Block;";
29+
public static final String BlockEntityProvider_createBlockEntity = "net/minecraft/block/BlockEntityProvider.createBlockEntity(Lnet/minecraft/world/BlockView;)Lnet/minecraft/block/entity/BlockEntity;";
30+
public static final String Blocks_FIRE = "net/minecraft/block/Blocks.FIRE:Lnet/minecraft/block/Block;";
31+
public static final String World_getBlockState = "net/minecraft/world/World.getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
32+
public static final String ServerPlayerInteractionManager_isCreative = "net/minecraft/server/network/ServerPlayerInteractionManager.isCreative()Z";
33+
public static final String PlayerEntity_isUsingEffectiveTool = "net/minecraft/entity/player/PlayerEntity.isUsingEffectiveTool(Lnet/minecraft/block/BlockState;)Z";
34+
public static final String ServerPlayerEntity_isUsingEffectiveTool = "net/minecraft/server/network/ServerPlayerEntity.isUsingEffectiveTool(Lnet/minecraft/block/BlockState;)Z";
35+
public static final String Block_onBreak = "net/minecraft/block/Block.onBreak(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/player/PlayerEntity;)V";
36+
public static final String ServerWorld_removeBlock = "net/minecraft/server/world/ServerWorld.removeBlock(Lnet/minecraft/util/math/BlockPos;Z)Z";
37+
public static final String Block_onBroken = "net/minecraft/block/Block.onBroken(Lnet/minecraft/world/IWorld;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V";
38+
public static final String ItemStack_postMine = "net/minecraft/item/ItemStack.postMine(Lnet/minecraft/world/World;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/player/PlayerEntity;)V";
39+
public static final String Block_afterBreak = "net/minecraft/block/Block.afterBreak(Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/item/ItemStack;)V";
40+
public static final String World_setBlockState = "net/minecraft/world/World.setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;I)Z";
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.mixin.extensions.block.blockentity;
21+
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Redirect;
25+
26+
import net.minecraft.block.AbstractRedstoneGateBlock;
27+
import net.minecraft.block.Block;
28+
import net.minecraft.block.BlockState;
29+
import net.minecraft.util.math.BlockPos;
30+
import net.minecraft.world.World;
31+
32+
import net.patchworkmc.impl.extensions.block.BlockContext;
33+
34+
@Mixin(AbstractRedstoneGateBlock.class)
35+
public abstract class MixinAbstractRedstoneGateBlock {
36+
@Redirect(method = "neighborUpdate", at = @At(value = "INVOKE", target = "net/minecraft/block/AbstractRedstoneGateBlock.hasBlockEntity()Z", ordinal = 0))
37+
private boolean patchwork_neighborUpdate_hasBlockEntity(AbstractRedstoneGateBlock dummy, BlockState state, World world, BlockPos pos, Block block, BlockPos neighborPos, boolean moved) {
38+
return BlockContext.hasBlockEntity(state);
39+
}
40+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.mixin.extensions.block.blockentity;
21+
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Inject;
25+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
26+
import net.minecraftforge.common.extensions.IForgeBlock;
27+
28+
import net.minecraft.block.Block;
29+
30+
import net.patchworkmc.impl.extensions.block.BlockContext;
31+
32+
@Mixin(Block.class)
33+
public abstract class MixinBlock {
34+
@Inject(method = "hasBlockEntity", at = @At("RETURN"), cancellable = true)
35+
public void patchwork_hasBlockEntity(CallbackInfoReturnable<Boolean> info) {
36+
info.setReturnValue(BlockContext.block_hasBlockEntity((IForgeBlock) this));
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.mixin.extensions.block.blockentity;
21+
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Redirect;
25+
26+
import net.minecraft.block.Block;
27+
import net.minecraft.block.BlockState;
28+
import net.minecraft.command.arguments.BlockArgumentParser;
29+
30+
import net.patchworkmc.impl.extensions.block.BlockContext;
31+
import net.patchworkmc.impl.extensions.block.Signatures;
32+
33+
@Mixin(BlockArgumentParser.class)
34+
public abstract class MixinBlockArgumentParser {
35+
// First occurrence: return this.blockState.getBlock().hasBlockEntity();
36+
@Redirect(method = "suggestSnbtOrBlockProperties", at = @At(value = "INVOKE", target = Signatures.Block_hasBlockEntity, ordinal = 0))
37+
private boolean patchwork_suggestSnbtOrBlockProperties_hasBlockEntity(Block dummy) {
38+
BlockArgumentParser me = (BlockArgumentParser) (Object) this;
39+
BlockState blockState = me.getBlockState();
40+
return BlockContext.hasBlockEntity(blockState);
41+
}
42+
43+
// First occurrence: return this.blockState.getBlock().hasBlockEntity();
44+
@Redirect(method = "hasBlockEntity()Z", at = @At(value = "INVOKE", target = Signatures.Block_hasBlockEntity, ordinal = 0))
45+
private boolean patchwork_hasBlockEntity_hasBlockEntity(Block dummy) {
46+
BlockArgumentParser me = (BlockArgumentParser) (Object) this;
47+
BlockState blockState = me.getBlockState();
48+
return BlockContext.hasBlockEntity(blockState);
49+
}
50+
51+
// Second occurrence in BlockArgumentParser.hasBlockEntity()Z
52+
// while(var2.hasNext()) {
53+
// Block block = (Block)var2.next();
54+
// if (block.hasBlockEntity()) return true;
55+
// }
56+
//
57+
// Forge's patch:
58+
// - if (block.getDefaultState().hasTileEntity()) {
59+
// + if (block.hasBlockEntity()) {
60+
//
61+
// We leave it unpatched and the behavior is identical
62+
63+
// First occurrence in private CompletableFuture<Suggestions> suggestSnbtOrTagProperties(SuggestionsBuilder suggestionsBuilder)
64+
// We leave it unpatched and the behavior is identical
65+
}

0 commit comments

Comments
 (0)