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",