diff --git a/patchwork-events-world/build.gradle b/patchwork-events-world/build.gradle index b8809b37..913a6bf0 100644 --- a/patchwork-events-world/build.gradle +++ b/patchwork-events-world/build.gradle @@ -3,4 +3,5 @@ version = getSubprojectVersion(project, "0.3.0") dependencies { implementation project(path: ':patchwork-api-base', configuration: 'dev') + implementation project(path: ':patchwork-extensions-block', configuration: 'dev') } diff --git a/patchwork-events-world/src/main/java/net/minecraftforge/event/world/BlockEvent.java b/patchwork-events-world/src/main/java/net/minecraftforge/event/world/BlockEvent.java index 57308dd5..07123fb3 100644 --- a/patchwork-events-world/src/main/java/net/minecraftforge/event/world/BlockEvent.java +++ b/patchwork-events-world/src/main/java/net/minecraftforge/event/world/BlockEvent.java @@ -21,16 +21,21 @@ import java.util.List; +import net.minecraftforge.common.extensions.IForgeBlockState; import net.minecraftforge.eventbus.api.Event; import net.minecraft.item.ItemStack; import net.minecraft.util.DefaultedList; import net.minecraft.block.BlockState; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IWorld; import net.minecraft.world.World; +import net.patchworkmc.impl.extensions.block.BlockHarvestManager; + public class BlockEvent extends Event { private final IWorld world; private final BlockPos pos; @@ -73,17 +78,14 @@ public BreakEvent(World world, BlockPos pos, BlockState state, PlayerEntity play this.player = player; this.exp = 0; - // TODO: BlockState#getExpDrop - - /* // Handle empty block or player unable to break block scenario - if (state == null || !ForgeHooks.canHarvestBlock(state, player, world, pos)) { + if (state == null || !BlockHarvestManager.canHarvestBlock(state, player, world, pos)) { this.exp = 0; } else { - int bonusLevel = EnchantmentHelper.getLevel(Enchantments.FORTUNE, player.getHeldItemMainhand()); - int silklevel = EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, player.getHeldItemMainhand()); - this.exp = state.getExpDrop(world, pos, bonusLevel, silklevel); - }*/ + int bonusLevel = EnchantmentHelper.getLevel(Enchantments.FORTUNE, player.getMainHandStack()); + int silklevel = EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, player.getMainHandStack()); + this.exp = ((IForgeBlockState) state).getExpDrop(world, pos, bonusLevel, silklevel); + } } public PlayerEntity getPlayer() { diff --git a/patchwork-events-world/src/main/java/net/patchworkmc/impl/event/world/WorldEvents.java b/patchwork-events-world/src/main/java/net/patchworkmc/impl/event/world/WorldEvents.java index 4b33e3a3..cae0e965 100644 --- a/patchwork-events-world/src/main/java/net/patchworkmc/impl/event/world/WorldEvents.java +++ b/patchwork-events-world/src/main/java/net/patchworkmc/impl/event/world/WorldEvents.java @@ -28,14 +28,19 @@ import net.minecraftforge.event.world.WorldEvent; import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.DefaultedList; import net.minecraft.world.World; import net.minecraft.entity.EntityCategory; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.EmptyBlockView; +import net.minecraft.world.GameMode; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.IWorld; import net.minecraft.world.biome.Biome; @@ -72,6 +77,60 @@ public static void onWorldSave(IWorld world) { MinecraftForge.EVENT_BUS.post(new WorldEvent.Save(world)); } + /** + * Called by Mixin and ForgeHooks. + * @return experience dropped, -1 = block breaking is cancelled. + */ + public static int onBlockBreakEvent(World world, GameMode gameMode, ServerPlayerEntity player, BlockPos pos) { + // Logic from tryHarvestBlock for pre-canceling the event + boolean preCancelEvent = false; + + ItemStack itemstack = player.getMainHandStack(); + + if (!itemstack.isEmpty() && !itemstack.getItem().canMine(world.getBlockState(pos), world, pos, player)) { + preCancelEvent = true; + } + + // method_21701 => canMine + // Isn't the function really canNotMine? + + if (player.method_21701(world, pos, gameMode)) { + preCancelEvent = true; + } + + // Tell client the block is gone immediately then process events + if (world.getBlockEntity(pos) == null) { + player.networkHandler.sendPacket(new BlockUpdateS2CPacket(EmptyBlockView.INSTANCE, pos)); + } + + // Post the block break event + BlockState state = world.getBlockState(pos); + BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player); + event.setCanceled(preCancelEvent); + MinecraftForge.EVENT_BUS.post(event); + + // Handle if the event is canceled + if (event.isCanceled()) { + // Let the client know the block still exists + player.networkHandler.sendPacket(new BlockUpdateS2CPacket(world, pos)); + + // Update any block entity data for this block + BlockEntity entity = world.getBlockEntity(pos); + + if (entity != null) { + BlockEntityUpdateS2CPacket packet = entity.toUpdatePacket(); + + if (packet != null) { + player.networkHandler.sendPacket(packet); + } + } + + return -1; // Cancelled + } else { + return event.getExpToDrop(); + } + } + // TODO: Leaving this unfired is intentional. See: https://github.com/MinecraftForge/MinecraftForge/issues/5828 public static float fireBlockHarvesting(DefaultedList drops, World world, BlockPos pos, BlockState state, int fortune, float dropChance, boolean silkTouch, PlayerEntity player) { BlockEvent.HarvestDropsEvent event = new BlockEvent.HarvestDropsEvent(world, pos, state, fortune, dropChance, drops, player, silkTouch); diff --git a/patchwork-events-world/src/main/java/net/patchworkmc/mixin/event/world/MixinServerPlayerInteractionManager.java b/patchwork-events-world/src/main/java/net/patchworkmc/mixin/event/world/MixinServerPlayerInteractionManager.java index 7b62fa81..713a88b2 100644 --- a/patchwork-events-world/src/main/java/net/patchworkmc/mixin/event/world/MixinServerPlayerInteractionManager.java +++ b/patchwork-events-world/src/main/java/net/patchworkmc/mixin/event/world/MixinServerPlayerInteractionManager.java @@ -19,28 +19,23 @@ package net.patchworkmc.mixin.event.world; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.world.BlockEvent; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import net.minecraft.block.BlockState; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.item.ItemStack; -import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; -import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerInteractionManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.EmptyBlockView; import net.minecraft.world.GameMode; +import net.patchworkmc.impl.event.world.WorldEvents; +import net.patchworkmc.impl.extensions.block.BlockHarvestManager; + @Mixin(ServerPlayerInteractionManager.class) -public class MixinServerPlayerInteractionManager { +public abstract class MixinServerPlayerInteractionManager { @Shadow public ServerWorld world; @Shadow @@ -50,52 +45,17 @@ public class MixinServerPlayerInteractionManager { @Inject(method = "tryBreakBlock", at = @At("HEAD"), cancellable = true) private void hookBreakBlock(BlockPos pos, CallbackInfoReturnable callback) { - boolean preCancelEvent = false; - - ItemStack itemstack = player.getMainHandStack(); - - if (!itemstack.isEmpty() && !itemstack.getItem().canMine(world.getBlockState(pos), world, pos, player)) { - preCancelEvent = true; - } - - // method_21701 => canMine - // Isn't the function really canNotMine? - - if (player.method_21701(world, pos, gameMode)) { - preCancelEvent = true; - } - - // Tell client the block is gone immediately then process events - if (world.getBlockEntity(pos) == null) { - player.networkHandler.sendPacket(new BlockUpdateS2CPacket(EmptyBlockView.INSTANCE, pos)); - } - - // Post the block break event - BlockState state = world.getBlockState(pos); - BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player); - event.setCanceled(preCancelEvent); - MinecraftForge.EVENT_BUS.post(event); - - // Handle if the event is canceled - if (event.isCanceled()) { - // Let the client know the block still exists - player.networkHandler.sendPacket(new BlockUpdateS2CPacket(world, pos)); - - // Update any block entity data for this block - BlockEntity entity = world.getBlockEntity(pos); - - if (entity != null) { - BlockEntityUpdateS2CPacket packet = entity.toUpdatePacket(); - - if (packet != null) { - player.networkHandler.sendPacket(packet); - } - } + int exp = WorldEvents.onBlockBreakEvent(world, gameMode, player, pos); + if (exp < 0) { callback.setReturnValue(false); - } else if (event.getExpToDrop() != 0) { - // TODO: Drop experience - throw new UnsupportedOperationException("Cannot drop exp from a BreakEvent yet"); + } else { + BlockHarvestManager.pushExpDropStack(exp); } } + + @Inject(method = "tryBreakBlock", at = @At("RETURN"), cancellable = true) + private void tryBreakBlock_return(BlockPos pos, CallbackInfoReturnable callback) { + BlockHarvestManager.popExpDropStack(); + } } diff --git a/patchwork-events-world/src/main/resources/fabric.mod.json b/patchwork-events-world/src/main/resources/fabric.mod.json index 509b8176..f74ea781 100644 --- a/patchwork-events-world/src/main/resources/fabric.mod.json +++ b/patchwork-events-world/src/main/resources/fabric.mod.json @@ -13,7 +13,8 @@ "PatchworkMC" ], "depends": { - "patchwork-api-base": "*" + "patchwork-api-base": "*", + "patchwork-extensions-block": "*" }, "mixins": [ "patchwork-events-world.mixins.json" diff --git a/patchwork-extensions-block/build.gradle b/patchwork-extensions-block/build.gradle index 6bdfc305..94c92392 100644 --- a/patchwork-extensions-block/build.gradle +++ b/patchwork-extensions-block/build.gradle @@ -4,6 +4,7 @@ version = getSubprojectVersion(project, "0.3.0") dependencies { implementation project(path: ':patchwork-api-base', configuration: 'dev') implementation project(path: ':patchwork-enum-hacks', configuration: 'dev') + implementation project(path: ':patchwork-extensions-item', configuration: 'dev') implementation project(path: ':patchwork-tooltype', configuration: 'dev') } diff --git a/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java b/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java index 4c540345..2a860bff 100644 --- a/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java +++ b/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlock.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import net.minecraftforge.common.IPlantable; +import net.minecraftforge.common.ToolType; import net.minecraft.block.BedBlock; import net.minecraft.block.Block; @@ -82,10 +83,12 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.patchworkmc.impl.extensions.block.BlockHarvestManager; +import net.patchworkmc.impl.extensions.block.PatchworkBlock; import net.patchworkmc.mixin.extensions.block.FireBlockAccessor; import net.patchworkmc.mixin.extensions.block.PlantBlockAccessor; -public interface IForgeBlock { +public interface IForgeBlock extends PatchworkBlock { default Block getBlock() { return (Block) this; } @@ -204,7 +207,7 @@ default BlockEntity createTileEntity(BlockState state, BlockView world) { return null; } - /* TODO IForgeBlock#canHarvestBlock indirectly requires ToolType (via ForgeHooks#canHarvestBlock) + /* TODO IForgeBlock#canHarvestBlock indirectly requires ToolType (via ForgeHooks#canHarvestBlock) */ /** * Determines if the player can harvest this block, obtaining it's drops when the block is destroyed. * @@ -212,10 +215,10 @@ default BlockEntity createTileEntity(BlockState state, BlockView world) { * @param pos The block's current position * @param player The player damaging the block * @return True to spawn the drops - * + */ default boolean canHarvestBlock(BlockState state, BlockView world, BlockPos pos, PlayerEntity player) { - return ForgeHooks.canHarvestBlock(state, player, world, pos); - }*/ + return BlockHarvestManager.canHarvestBlock(state, player, world, pos); + } // TODO Call locations: Patches: ServerPlayerInteractionManager* /** @@ -240,7 +243,7 @@ default boolean canHarvestBlock(BlockState state, BlockView world, BlockPos pos, */ default boolean removedByPlayer(BlockState state, World world, BlockPos pos, PlayerEntity player, boolean willHarvest, FluidState fluid) { getBlock().onBreak(world, pos, state, player); - return world.removeBlock(pos, false); + return world.setBlockState(pos, fluid.getBlockState(), world.isClient ? 11 : 3); } // TODO Call locations: Patches: LivingEntity*, PlayerEntity*, Forge classes: ForgeEventFactory (called from LivingEntity patch) @@ -645,6 +648,7 @@ default boolean isBeaconBase(BlockState state, CollisionView world, BlockPos pos // TODO Call locations: Forge classes: BreakEvent* /** * Gathers how much experience this block drops when broken. + * TODO: there's no equivalent callback in Fabric API, so for now Fabric mods should always return 0 here. * * @param state The current state * @param world The world @@ -778,12 +782,12 @@ default boolean getWeakChanges(BlockState state, CollisionView world, BlockPos p return false; } - /* TODO IForgeBlock#getHarvestTool needs ToolType + /* TODO IForgeBlock#getHarvestTool needs ToolType */ /** * Queries the class of tool required to harvest this block, if null is returned * we assume that anything can harvest this block. - * - ToolType getHarvestTool(BlockState state);*/ + */ + ToolType getHarvestTool(BlockState state); // TODO Call locations: Patches: PickaxeItem*, Forge classes: ForgeHooks* /** @@ -793,18 +797,18 @@ default boolean getWeakChanges(BlockState state, CollisionView world, BlockPos p */ int getHarvestLevel(BlockState state); - /* TODO IForgeBlock#isToolEffective needs ToolType + /* TODO IForgeBlock#isToolEffective needs ToolType */ /** * Checks if the specified tool type is efficient on this block, * meaning that it digs at full speed. - * + */ default boolean isToolEffective(BlockState state, ToolType tool) { if (tool == ToolType.PICKAXE && (this.getBlock() == Blocks.REDSTONE_ORE || this.getBlock() == Blocks.REDSTONE_LAMP || this.getBlock() == Blocks.OBSIDIAN)) { return false; } return tool == getHarvestTool(state); - }*/ + } // TODO Call locations: Forge classes: ForgeHooksClient /** diff --git a/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java b/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java index 55e94ee2..0f0c4182 100644 --- a/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java +++ b/patchwork-extensions-block/src/main/java/net/minecraftforge/common/extensions/IForgeBlockState.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import net.minecraftforge.common.IPlantable; +import net.minecraftforge.common.ToolType; import net.minecraft.block.Block; import net.minecraft.block.BlockEntityProvider; @@ -149,7 +150,7 @@ default BlockEntity createTileEntity(BlockView world) { return patchwork$getForgeBlock().createTileEntity(getBlockState(), world); } - /* TODO IForgeBlockState#canHarvestBlock indirectly requires ToolType + /* TODO IForgeBlockState#canHarvestBlock indirectly requires ToolType */ /** * Determines if the player can harvest this block, obtaining it's drops when the block is destroyed. * @@ -157,10 +158,10 @@ default BlockEntity createTileEntity(BlockView world) { * @param pos The block's current position * @param player The player damaging the block * @return True to spawn the drops - * + */ default boolean canHarvestBlock(BlockView world, BlockPos pos, PlayerEntity player) { return patchwork$getForgeBlock().canHarvestBlock(getBlockState(), world, pos, player); - }*/ + } /** * Called when a player removes a block. This is responsible for @@ -582,14 +583,14 @@ default boolean getWeakChanges(CollisionView world, BlockPos pos) { return patchwork$getForgeBlock().getWeakChanges(getBlockState(), world, pos); } - /* TODO IForgeBlockState#getHarvestTool needs ToolType + /* TODO IForgeBlockState#getHarvestTool needs ToolType */ /** * Queries the class of tool required to harvest this block, if null is returned * we assume that anything can harvest this block. - * + */ default ToolType getHarvestTool() { return patchwork$getForgeBlock().getHarvestTool(getBlockState()); - }*/ + } default int getHarvestLevel() { return patchwork$getForgeBlock().getHarvestLevel(getBlockState()); @@ -605,6 +606,7 @@ default boolean isToolEffective(ToolType tool) { }*/ /** + * TODO: do not bother implementing hooks, deprecated since 1.13 * Can return IExtendedBlockState. */ default BlockState getExtendedState(BlockView world, BlockPos pos) { @@ -612,6 +614,7 @@ default BlockState getExtendedState(BlockView world, BlockPos pos) { } /** + * TODO: do not bother implementing hooks, deprecated since 1.15 * Queries if this block should render in a given layer. * A custom {@link net.minecraft.client.render.model.BakedModel} can use {@link net.minecraftforge.client.MinecraftForgeClient#getRenderLayer()} to alter the model based on layer. */ diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockContext.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockContext.java index 5f06ae28..be0317d9 100644 --- a/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockContext.java +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockContext.java @@ -40,30 +40,30 @@ public static ThreadLocal createContext() { return ThreadLocal.withInitial(() -> BlockContext.CLEAN_MARKER); } - public static void setContext(ThreadLocal stack, Object value) { - Object oldValue = stack.get(); + public static void setContext(ThreadLocal context, Object value) { + Object oldValue = context.get(); if (oldValue != CLEAN_MARKER) { throw new IllegalStateException("The context is not clean."); } - stack.set(value); + context.set(value); } @SuppressWarnings("unchecked") - public static T getContext(ThreadLocal stack) { - Object oldValue = stack.get(); + public static T getContext(ThreadLocal context) { + Object oldValue = context.get(); if (oldValue == CLEAN_MARKER) { throw new IllegalStateException("The context is not set."); } - return (T) stack.get(); + return (T) context.get(); } @SuppressWarnings("unchecked") - public static T getContextOr(ThreadLocal stack, T defaultValue) { - Object value = stack.get(); + public static T getContextOr(ThreadLocal context, T defaultValue) { + Object value = context.get(); if (value == CLEAN_MARKER) { return defaultValue; @@ -73,14 +73,14 @@ public static T getContextOr(ThreadLocal stack, T defaultValue) { } @SuppressWarnings("unchecked") - public static T releaseContext(ThreadLocal stack) { - Object oldValue = stack.get(); + public static T releaseContext(ThreadLocal context) { + Object oldValue = context.get(); if (oldValue == CLEAN_MARKER) { throw new IllegalStateException("The context is not set."); } - stack.set(CLEAN_MARKER); + context.remove(); return (T) oldValue; } diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockHarvestManager.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockHarvestManager.java new file mode 100644 index 00000000..da60ddf3 --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/BlockHarvestManager.java @@ -0,0 +1,101 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.impl.extensions.block; + +import java.util.Stack; + +import javax.annotation.Nonnull; + +import net.minecraftforge.common.ToolType; +import net.minecraftforge.common.extensions.IForgeBlockState; +import net.minecraftforge.common.extensions.IForgeItem; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.OreBlock; +import net.minecraft.block.RedstoneOreBlock; +import net.minecraft.block.SpawnerBlock; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; + +public class BlockHarvestManager { + private static final ThreadLocal> expDrops = ThreadLocal.withInitial(Stack::new); + + private static void checkExpDropStack() { + if (expDrops.get().isEmpty()) { + throw new IllegalStateException("Patchwork's experience drop stack is not balanced!"); + } + } + + public static void pushExpDropStack(int exp) { + expDrops.get().push(exp); + } + + public static int getLastExpDrop() { + checkExpDropStack(); + return expDrops.get().lastElement(); + } + + public static int popExpDropStack() { + checkExpDropStack(); + return expDrops.get().pop(); + } + + /** + * Called by Mixins and ForgeHooks.canHarvestBlock, + * Requires harvest levels. + */ + @SuppressWarnings("unused") + public static boolean canHarvestBlock(@Nonnull BlockState state, @Nonnull PlayerEntity player, @Nonnull BlockView world, @Nonnull BlockPos pos) { + // state = state.getActualState(world, pos); + if (state.getMaterial().canBreakByHand()) { + return true; + } + + ItemStack stack = player.getMainHandStack(); + ToolType tool = null; // TODO: Unimplemented: ((IForgeBlockState) state).getHarvestTool(); + + if (stack.isEmpty() || tool == null) { + return player.isUsingEffectiveTool(state); + } + + int toolLevel = ((IForgeItem) stack.getItem()).getHarvestLevel(stack, tool, player, state); + + if (toolLevel < 0) { + return player.isUsingEffectiveTool(state); + } + + return toolLevel >= ((IForgeBlockState) state).getHarvestLevel(); + } + + public static boolean isVanillaBlock(Block block) { + if (block instanceof OreBlock || block instanceof RedstoneOreBlock) { + return true; + } + + if (block instanceof SpawnerBlock) { + return true; + } + + return false; + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/PatchworkBlock.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/PatchworkBlock.java new file mode 100644 index 00000000..68bd014e --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/impl/extensions/block/PatchworkBlock.java @@ -0,0 +1,26 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.impl.extensions.block; + +import java.util.Random; + +public interface PatchworkBlock { + Random getRandom(); +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/MixinBlock.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/MixinBlock.java index a93ee87d..7db399eb 100644 --- a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/MixinBlock.java +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/MixinBlock.java @@ -21,12 +21,14 @@ import java.util.HashSet; import java.util.Map; +import java.util.Random; import java.util.Set; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; +import net.minecraftforge.common.ToolType; import net.minecraftforge.common.extensions.IForgeBlock; import net.minecraft.block.Block; @@ -40,6 +42,8 @@ @Mixin(Block.class) public class MixinBlock implements IForgeBlock { + protected Random RANDOM = new Random(); + @Shadow @Final private float slipperiness; @@ -54,6 +58,11 @@ public float getSlipperiness(BlockState state, CollisionView world, BlockPos pos return slipperiness; } + @Override + public ToolType getHarvestTool(BlockState state) { + throw new UnsupportedOperationException("Harvest levels not yet implemented"); // TODO implement getHarvestLevel + } + @Override public int getHarvestLevel(BlockState state) { throw new UnsupportedOperationException("Harvest levels not yet implemented"); // TODO implement getHarvestLevel, really sucks for vanilla blocks so i'm putting it off @@ -75,4 +84,9 @@ public Set getTags() { return this.cachedTags; } + + @Override + public Random getRandom() { + return RANDOM; + } } diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/flammable/MixinWorld.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/flammable/MixinWorld.java new file mode 100644 index 00000000..5399b00a --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/flammable/MixinWorld.java @@ -0,0 +1,49 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.flammable; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import net.minecraftforge.common.extensions.IForgeBlockState; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import net.patchworkmc.impl.extensions.block.Signatures; + +@Mixin(World.class) +public abstract class MixinWorld { + //////////////////////////////////////////////////////// + /// doesAreaContainFireSource - IForgeBlock.isBurning + /// In 1.16.1, this patch is moved to Entity. + //////////////////////////////////////////////////////// + // This really should be included in the Fabric API! + // Block block = this.getBlockState(pooledMutable.set(o, p, q)).getBlock(); + // if (block == Blocks.FIRE || block == Blocks.LAVA) { + @Redirect(method = "doesAreaContainFireSource", at = @At(value = "INVOKE", target = Signatures.World_getBlockState, ordinal = 0)) + private BlockState patchwork_doesAreaContainFireSource_getBlockState(World world, BlockPos blockPos) { + BlockState blockState = world.getBlockState(blockPos); + boolean isBurning = ((IForgeBlockState) blockState).isBurning(world, blockPos); + return isBurning ? Blocks.FIRE.getDefaultState() : Blocks.WATER.getDefaultState(); + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinClientPlayerInteractionManager.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinClientPlayerInteractionManager.java new file mode 100644 index 00000000..612f9069 --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinClientPlayerInteractionManager.java @@ -0,0 +1,56 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.harvest; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import net.minecraftforge.common.extensions.IForgeBlockState; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import net.patchworkmc.impl.extensions.block.Signatures; + +@Mixin(ClientPlayerInteractionManager.class) +public abstract class MixinClientPlayerInteractionManager { + @Shadow + @Final + private MinecraftClient client; + + @Redirect(method = "breakBlock", at = @At(value = "INVOKE", target = Signatures.Block_onBreak, ordinal = 0)) + private void patchwork$breakBlock_onBreak(Block block, World world, BlockPos pos, BlockState state, PlayerEntity player) { + // Suppress this call: block.onBreak(world, pos, blockState, this.client.player); + } + + @Redirect(method = "breakBlock", at = @At(value = "INVOKE", target = Signatures.World_setBlockState, ordinal = 0)) + private boolean patchwork_breakBlock_setBlockState(World world, BlockPos pos, BlockState state, int flags) { + FluidState ifluidstate = world.getFluidState(pos); + return ((IForgeBlockState) state).removedByPlayer(world, pos, client.player, false, ifluidstate); + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinOreBlock.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinOreBlock.java new file mode 100644 index 00000000..92640771 --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinOreBlock.java @@ -0,0 +1,42 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.harvest; + +import java.util.Random; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import net.minecraftforge.common.extensions.IForgeBlock; + +import net.minecraft.block.BlockState; +import net.minecraft.block.OreBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.CollisionView; + +@Mixin(OreBlock.class) +public abstract class MixinOreBlock implements IForgeBlock { + @Shadow + protected abstract int getExperienceWhenMined(Random random); + + @Override + public int getExpDrop(BlockState state, CollisionView world, BlockPos pos, int fortune, int silktouch) { + return silktouch == 0 ? getExperienceWhenMined(getRandom()) : 0; + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinRedstoneOreBlock.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinRedstoneOreBlock.java new file mode 100644 index 00000000..fcaaab76 --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinRedstoneOreBlock.java @@ -0,0 +1,36 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.harvest; + +import org.spongepowered.asm.mixin.Mixin; +import net.minecraftforge.common.extensions.IForgeBlock; + +import net.minecraft.block.BlockState; +import net.minecraft.block.RedstoneOreBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.CollisionView; + +@Mixin(RedstoneOreBlock.class) +public abstract class MixinRedstoneOreBlock implements IForgeBlock { + @Override + public int getExpDrop(BlockState state, CollisionView world, BlockPos pos, int fortune, int silktouch) { + return silktouch == 0 ? 1 + getRandom().nextInt(5) : 0; + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinServerPlayerInteractionManager.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinServerPlayerInteractionManager.java new file mode 100644 index 00000000..cb929eaa --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinServerPlayerInteractionManager.java @@ -0,0 +1,119 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.harvest; + +import javax.annotation.Nullable; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import net.minecraftforge.common.extensions.IForgeBlockState; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IWorld; +import net.minecraft.world.World; + +import net.patchworkmc.impl.extensions.block.BlockContext; +import net.patchworkmc.impl.extensions.block.BlockHarvestManager; +import net.patchworkmc.impl.extensions.block.Signatures; + +@Mixin(ServerPlayerInteractionManager.class) +public abstract class MixinServerPlayerInteractionManager { + // removedByPlayer, canHarvestBlock + + @Unique + private boolean patchwork$removeBlock(BlockPos pos, boolean canHarvest) { + ServerPlayerInteractionManager me = (ServerPlayerInteractionManager) (Object) this; + BlockState state = me.world.getBlockState(pos); + boolean removed = ((IForgeBlockState) state).removedByPlayer(me.world, pos, me.player, canHarvest, me.world.getFluidState(pos)); + + if (removed) { + state.getBlock().onBroken(me.world, pos, state); + } + + return removed; + } + + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.Block_onBreak, ordinal = 0)) + private void patchwork$tryBreakBlock_onBreak(Block block, World world, BlockPos pos, BlockState state, PlayerEntity player) { + // Suppress this call + } + + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.ServerWorld_removeBlock, ordinal = 0)) + private boolean patchwork$tryBreakBlock_removeBlock(ServerWorld world, BlockPos pos, boolean bool) { + return true; // bypass if (bl && bl2) { + } + + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.Block_onBroken, ordinal = 0)) + private void patchwork$tryBreakBlock_onBroken(Block block, IWorld world, BlockPos pos, BlockState state) { + // Suppress this call + } + + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.ServerPlayerInteractionManager_isCreative, ordinal = 0)) + private boolean patchwork$tryBreakBlock_isCreative(ServerPlayerInteractionManager me, BlockPos pos) { + boolean isCreative = me.isCreative(); + + if (isCreative) { + patchwork$removeBlock(pos, false); + } + + return isCreative; + } + + @Unique + private static final ThreadLocal tryBreakBlock_canHarvest = BlockContext.createContext(); // flag1 + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.ServerPlayerEntity_isUsingEffectiveTool, ordinal = 0)) + private boolean patchwork$tryBreakBlock_isUsingEffectiveTool(ServerPlayerEntity player, BlockState blockState, BlockPos pos) { + ServerPlayerInteractionManager me = (ServerPlayerInteractionManager) (Object) this; + boolean canHarvest = ((IForgeBlockState) blockState).canHarvestBlock(me.world, pos, player); + BlockContext.setContext(tryBreakBlock_canHarvest, canHarvest); + return true; // bypass if (bl && bl2) { + } + + @Redirect(method = "tryBreakBlock", at = @At(value = "INVOKE", target = Signatures.Block_afterBreak, ordinal = 0)) + private void patchwork$tryBreakBlock_afterBreak(Block block, World world, PlayerEntity player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack) { + boolean canHarvest = BlockContext.releaseContext(tryBreakBlock_canHarvest); + boolean removed = patchwork$removeBlock(pos, canHarvest); + + if (removed && canHarvest) { + block.afterBreak(world, player, pos, state, blockEntity, stack); + } + + int exp = BlockHarvestManager.getLastExpDrop(); + + // isVanillaBlock exp Action + // Vanilla true xxx Let the vanilla method do the orb drop + // Fabric mod false == 0 Let the vanilla method do the orb drop + // Forge mod false >0 Do orb drop here + // Forge mod false <=0 Do nothing + if (removed && exp > 0 && !BlockHarvestManager.isVanillaBlock(block)) { + state.getBlock().dropExperience(world, pos, exp); + } + } +} diff --git a/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinSpawnerBlock.java b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinSpawnerBlock.java new file mode 100644 index 00000000..40100d06 --- /dev/null +++ b/patchwork-extensions-block/src/main/java/net/patchworkmc/mixin/extensions/block/harvest/MixinSpawnerBlock.java @@ -0,0 +1,36 @@ +/* + * Minecraft Forge, Patchwork Project + * Copyright (c) 2016-2020, 2019-2020 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.patchworkmc.mixin.extensions.block.harvest; + +import org.spongepowered.asm.mixin.Mixin; +import net.minecraftforge.common.extensions.IForgeBlock; + +import net.minecraft.block.BlockState; +import net.minecraft.block.SpawnerBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.CollisionView; + +@Mixin(SpawnerBlock.class) +public abstract class MixinSpawnerBlock implements IForgeBlock { + @Override + public int getExpDrop(BlockState state, CollisionView world, BlockPos pos, int fortune, int silktouch) { + return 15 + getRandom().nextInt(15) + getRandom().nextInt(15); + } +} diff --git a/patchwork-extensions-block/src/main/resources/fabric.mod.json b/patchwork-extensions-block/src/main/resources/fabric.mod.json index 7ccd29e1..f1f22b18 100644 --- a/patchwork-extensions-block/src/main/resources/fabric.mod.json +++ b/patchwork-extensions-block/src/main/resources/fabric.mod.json @@ -13,11 +13,14 @@ "depends": { "patchwork-api-base": "*", "patchwork-enum-hacks": "*", + "patchwork-extensions-item": "*", "patchwork-tooltype": "*" }, "mixins": [ "patchwork-extensions-block.mixins.json", - "patchwork-extensions-block-blockentity.mixins.json" + "patchwork-extensions-block-blockentity.mixins.json", + "patchwork-extensions-block-flammable.mixins.json", + "patchwork-extensions-block-harvest.mixins.json" ], "accessWidener": "patchwork-extensions-block.accesswidener", "custom": { diff --git a/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-flammable.mixins.json b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-flammable.mixins.json new file mode 100644 index 00000000..973ae16c --- /dev/null +++ b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-flammable.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "net.patchworkmc.mixin.extensions.block.flammable", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MixinWorld" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-harvest.mixins.json b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-harvest.mixins.json new file mode 100644 index 00000000..2650febb --- /dev/null +++ b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block-harvest.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "package": "net.patchworkmc.mixin.extensions.block.harvest", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MixinOreBlock", + "MixinRedstoneOreBlock", + "MixinServerPlayerInteractionManager", + "MixinSpawnerBlock" + ], + "client": [ + "MixinClientPlayerInteractionManager" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/patchwork-extensions-block/src/main/resources/patchwork-extensions-block.accesswidener b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block.accesswidener index 2188fab7..4b314a92 100644 --- a/patchwork-extensions-block/src/main/resources/patchwork-extensions-block.accesswidener +++ b/patchwork-extensions-block/src/main/resources/patchwork-extensions-block.accesswidener @@ -9,3 +9,5 @@ accessible field net/minecraft/item/AxeItem STRIPPED_BLOCKS Ljava/util/Map; # accessible method net/minecraft/block/PressurePlateBlock (Lnet/minecraft/block/PressurePlateBlock$ActivationRule;Lnet/minecraft/Block$Settings;)V accessible method net/minecraft/class_2440 (Lnet/minecraft/class_2440$class_2441;Lnet/minecraft/class_2248$class_2251;)V + +accessible method net/minecraft/block/Block dropExperience (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;I)V