diff --git a/build.gradle b/build.gradle index e1f98a21e..4a813bed7 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ minecraft { dependencies { minecraft "com.mojang:minecraft:18w48b" - mappings "net.fabricmc:pomf:18w48b.7" + mappings "net.fabricmc:pomf:18w48b.16" modCompile "net.fabricmc:fabric-loader:0.1.0.49" } diff --git a/src/main/java/net/fabricmc/fabric/FabricAPI.java b/src/main/java/net/fabricmc/fabric/FabricAPI.java new file mode 100644 index 000000000..99caab7f0 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/FabricAPI.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.block.BreakInteractable; +import net.fabricmc.fabric.events.PlayerInteractionEvent; +import net.minecraft.block.BlockState; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Facing; +import net.minecraft.world.World; + +public class FabricAPI implements ModInitializer { + @Override + public void onInitialize() { + PlayerInteractionEvent.BREAK_BLOCK.register((player, world, hand, pos, facing) -> { + BlockState state = world.getBlockState(pos); + if (state instanceof BreakInteractable) { + if (((BreakInteractable) state).onBreakInteract(state, world, pos, player, hand, facing)) { + return ActionResult.FAILURE; + } + } else if (state.getBlock() instanceof BreakInteractable) { + if (((BreakInteractable) state.getBlock()).onBreakInteract(state, world, pos, player, hand, facing)) { + return ActionResult.FAILURE; + } + } + + return ActionResult.PASS; + }); + } +} diff --git a/src/main/java/net/fabricmc/fabric/block/BreakInteractable.java b/src/main/java/net/fabricmc/fabric/block/BreakInteractable.java new file mode 100644 index 000000000..b498589f8 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/block/BreakInteractable.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.block; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Facing; +import net.minecraft.world.World; + +/** + * Convienence interface for blocks which listen to "break interactions" (left-click). + */ +public interface BreakInteractable { + /** + * @return True if the block accepted the interaction and it should no longer be processed. + */ + boolean onBreakInteract(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, Facing facing); +} diff --git a/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java b/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java new file mode 100644 index 000000000..689fcba0e --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.events; + +import net.fabricmc.fabric.util.HandlerList; +import net.fabricmc.fabric.util.HandlerRegistry; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Facing; +import net.minecraft.world.World; + +/** + * This is a class for INTERACTION EVENTS (think left-clicking/right-clicking). For block placement/break + * events, look elsewhere - this just handles the interaction! + * + * CURRENT LIMITATIONS: + * + * - INTERACT_BLOCK/INTERACT_ITEM do not expect the ItemStack instance in the player's held hand to change! + * If you must do that, consider returning an ActionResult.SUCCESS and re-emitting the event in some manner! + */ +public final class PlayerInteractionEvent { + @FunctionalInterface + public interface Block { + ActionResult interact(PlayerEntity player, World world, Hand hand, BlockPos pos, Facing facing); + } + + @FunctionalInterface + public interface BlockPositioned { + ActionResult interact(PlayerEntity player, World world, Hand hand, BlockPos pos, Facing facing, float hitX, float hitY, float hitZ); + } + + @FunctionalInterface + public interface Item { + ActionResult interact(PlayerEntity player, World world, Hand hand); + } + + public static HandlerRegistry<Block> BREAK_BLOCK = new HandlerList<>(); + public static HandlerRegistry<BlockPositioned> INTERACT_BLOCK = new HandlerList<>(); + public static HandlerRegistry<Item> INTERACT_ITEM = new HandlerList<>(); + + private PlayerInteractionEvent() { + + } +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java new file mode 100644 index 000000000..76c7602cd --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.events; + +import net.fabricmc.fabric.events.PlayerInteractionEvent; +import net.fabricmc.fabric.util.HandlerList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.client.network.packet.BlockUpdateClientPacket; +import net.minecraft.client.world.ClientWorld; +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.network.packet.PlayerInteractBlockServerPacket; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Facing; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameMode; +import net.minecraft.world.World; +import org.lwjgl.system.CallbackI; +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.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public class MixinClientPlayerInteractionManager { + @Shadow + private MinecraftClient client; + @Shadow + private ClientPlayNetworkHandler networkHandler; + @Shadow + private GameMode gameMode; + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/GameMode;isCreative()Z", ordinal = 0), method = "attackBlock", cancellable = true) + public void attackBlock(BlockPos pos, Facing facing, CallbackInfoReturnable<Boolean> info) { + for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) { + PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler; + ActionResult result = event.interact(client.player, client.world, Hand.MAIN, pos, facing); + if (result != ActionResult.PASS) { + info.setReturnValue(result == ActionResult.SUCCESS); + info.cancel(); + return; + } + } + } + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/GameMode;isCreative()Z", ordinal = 0), method = "method_2902", cancellable = true) + public void method_2902(BlockPos pos, Facing facing, CallbackInfoReturnable<Boolean> info) { + if (!gameMode.isCreative()) { + return; + } + + for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) { + PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler; + ActionResult result = event.interact(client.player, client.world, Hand.MAIN, pos, facing); + if (result != ActionResult.PASS) { + info.setReturnValue(result == ActionResult.SUCCESS); + info.cancel(); + return; + } + } + } + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getStackInHand(Lnet/minecraft/util/Hand;)Lnet/minecraft/item/ItemStack;", ordinal = 0), method = "interactBlock", cancellable = true) + public void interactBlock(ClientPlayerEntity player, ClientWorld world, BlockPos pos, Facing facing, Vec3d vec, Hand hand, CallbackInfoReturnable<ActionResult> info) { + Object[] backingArray = ((HandlerList<PlayerInteractionEvent.BlockPositioned>) PlayerInteractionEvent.INTERACT_BLOCK).getBackingArray(); + if (backingArray.length > 0) { + float hitX = (float) (vec.x - pos.getX()); + float hitY = (float) (vec.y - pos.getY()); + float hitZ = (float) (vec.z - pos.getZ()); + + for (Object handler : backingArray) { + PlayerInteractionEvent.BlockPositioned event = (PlayerInteractionEvent.BlockPositioned) handler; + ActionResult result = event.interact(player, world, hand, pos, facing, hitX, hitY, hitZ); + if (result != ActionResult.PASS) { + if (result == ActionResult.SUCCESS) { + this.networkHandler.sendPacket(new PlayerInteractBlockServerPacket(pos, facing, hand, hitX, hitY, hitZ)); + } + info.setReturnValue(result); + info.cancel(); + return; + } + } + } + } + + @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getStackInHand(Lnet/minecraft/util/Hand;)Lnet/minecraft/item/ItemStack;", ordinal = 0), method = "interactItem", cancellable = true) + public void interactItem(PlayerEntity player, World world, Hand hand, CallbackInfoReturnable<ActionResult> info) { + for (Object handler : ((HandlerList<PlayerInteractionEvent.Item>) PlayerInteractionEvent.INTERACT_ITEM).getBackingArray()) { + PlayerInteractionEvent.Item event = (PlayerInteractionEvent.Item) handler; + ActionResult result = event.interact(player, world, hand); + if (result != ActionResult.PASS) { + info.setReturnValue(result); + info.cancel(); + return; + } + } + } +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java new file mode 100644 index 000000000..dd73a6e02 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.events; + +import net.fabricmc.fabric.events.PlayerInteractionEvent; +import net.fabricmc.fabric.util.HandlerList; +import net.minecraft.client.network.packet.BlockUpdateClientPacket; +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.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Facing; +import net.minecraft.world.World; +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.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ServerPlayerInteractionManager.class) +public class MixinServerPlayerInteractionManager { + @Shadow + public World world; + @Shadow + public ServerPlayerEntity player; + + @Inject(at = @At("HEAD"), method = "method_14263", cancellable = true) + public void startBlockBreak(BlockPos pos, Facing facing, CallbackInfo info) { + for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) { + PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler; + ActionResult result = event.interact(player, world, Hand.MAIN, pos, facing); + if (result != ActionResult.PASS) { + // The client might have broken the block on its side, so make sure to let it know. + this.player.networkHandler.sendPacket(new BlockUpdateClientPacket(world, pos)); + info.cancel(); + return; + } + } + } + + @Inject(at = @At("HEAD"), method = "interactBlock", cancellable = true) + public void interactBlock(PlayerEntity player, World world, ItemStack stack, Hand hand, BlockPos pos, Facing facing, float hitX, float hitY, float hitZ, CallbackInfoReturnable<ActionResult> info) { + for (Object handler : ((HandlerList<PlayerInteractionEvent.BlockPositioned>) PlayerInteractionEvent.INTERACT_BLOCK).getBackingArray()) { + PlayerInteractionEvent.BlockPositioned event = (PlayerInteractionEvent.BlockPositioned) handler; + ActionResult result = event.interact(player, world, hand, pos, facing, hitX, hitY, hitZ); + if (result != ActionResult.PASS) { + info.setReturnValue(result); + info.cancel(); + return; + } + } + } + + @Inject(at = @At("HEAD"), method = "interactItem", cancellable = true) + public void interactItem(PlayerEntity player, World world, ItemStack stack, Hand hand, CallbackInfoReturnable<ActionResult> info) { + for (Object handler : ((HandlerList<PlayerInteractionEvent.Item>) PlayerInteractionEvent.INTERACT_ITEM).getBackingArray()) { + PlayerInteractionEvent.Item event = (PlayerInteractionEvent.Item) handler; + ActionResult result = event.interact(player, world, hand); + if (result != ActionResult.PASS) { + info.setReturnValue(result); + info.cancel(); + return; + } + } + } +} diff --git a/src/main/resources/mod.json b/src/main/resources/mod.json index fcb1876b0..95eab1d75 100644 --- a/src/main/resources/mod.json +++ b/src/main/resources/mod.json @@ -4,6 +4,7 @@ "version": "0.0.2", "side": "universal", "initializers": [ + "net.fabricmc.fabric.FabricAPI" ], "mixins": { "client": "net.fabricmc.fabric.mixins.client.json", diff --git a/src/main/resources/net.fabricmc.fabric.mixins.client.json b/src/main/resources/net.fabricmc.fabric.mixins.client.json index 6b9855aab..2cee76b4a 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.client.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.client.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ + "events.MixinClientPlayerInteractionManager", "networking.MixinClientPlayNetworkHandler", "registry.client.MixinBlockColorMap", "registry.client.MixinItemColorMap", diff --git a/src/main/resources/net.fabricmc.fabric.mixins.common.json b/src/main/resources/net.fabricmc.fabric.mixins.common.json index e640dfe14..192a0c70f 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.common.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.common.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_8", "mixins": [ "commands.MixinServerCommandManager", + "events.MixinServerPlayerInteractionManager", "helpers.MixinBlock", "helpers.MixinBlockBuilder", "helpers.MixinItem",