diff --git a/README.md b/README.md index 9d536685..0ce54cff 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,26 @@ -# Architectury +# Architectury API Talk to us on [Discord](https://discord.gg/C2RdJDpRBP)! An intermediary api aimed to ease developing multiplatform mods. -### What is Architectury -Architectury is an api to abstract calls to fabric api and forge api as both loader has different implementations of what can be perceived as the same thing. +### What is Architectury API +Architectury API is an api to abstract calls to fabric api and forge api as both loader has different implementations of what can be perceived as the same thing. -Architectury updates regularly, with new hooks and features. Currently contains over **60** events hooks, networking abstraction, loader calls abstraction, game registry abstraction and an easy to use @ExpectPlatform annotation (Only works on static methods). +Architectury API updates regularly, with new hooks and features. Currently contains over **90** events hooks, networking abstraction, loader calls abstraction, game registry abstraction and an easy to use @ExpectPlatform annotation (Only works on static methods). + +### Do I really need this API? +Architectury API is only one part of the architectury ecosystem, **Architectury Plugin** is the gradle plugin enabling all this multiplatform actions. + +Architectury API is optional for projects built on architectury, you may create your architectury project with just Architectury Plugin. ### Advantages of Architectury - Open sourced - Less boilerplate for your multiplatform mod ### Getting started with making multiplatform mods -Gradle Plugin: https://github.com/architectury/architect-plugin +Gradle Plugin: https://github.com/architectury/architectury-plugin -Example Mod: https://github.com/architectury/architect-example-mod +Example Mod: https://github.com/architectury/architectury-example-mod ### Credits This library bundles typetools, which you can find its license [here](https://github.com/jhalterman/typetools/blob/master/LICENSE.txt "") diff --git a/build.gradle b/build.gradle index 4fcac77a..b62d4c3d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { - id "architectury-plugin" version "2.0.65" - id "forgified-fabric-loom" version "0.6.54" apply false + id "architectury-plugin" version "3.0.97" + id "forgified-fabric-loom" version "0.6.78" apply false id "org.cadixdev.licenser" version "0.5.0" id "com.matthewprenger.cursegradle" version "1.4.0" apply false id "maven-publish" diff --git a/common/build.gradle b/common/build.gradle index ec12c342..2cb7627e 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,26 +20,16 @@ task sourcesJar(type: Jar, dependsOn: classes) { from sourceSets.main.allSource } -task javadocs(type: Javadoc) { - source = sourceSets.main.allJava -} - -task javadocsJar(type: Jar, dependsOn: javadocs) { - archiveClassifier.set("javadocs") - javadocs.failOnError false - from javadocs.destinationDir -} - publishing { publications { mavenCommon(MavenPublication) { + artifactId = rootProject.archivesBaseName artifact(file("${project.buildDir}/libs/${project.archivesBaseName}-${project.version}.jar")) { builtBy build } artifact(sourcesJar) { builtBy remapSourcesJar } - artifact javadocsJar } } diff --git a/common/src/main/java/me/shedaniel/architectury/ExpectPlatform.java b/common/src/main/java/me/shedaniel/architectury/ExpectPlatform.java index 5e19c317..0cb81338 100644 --- a/common/src/main/java/me/shedaniel/architectury/ExpectPlatform.java +++ b/common/src/main/java/me/shedaniel/architectury/ExpectPlatform.java @@ -19,6 +19,8 @@ package me.shedaniel.architectury; +import org.jetbrains.annotations.ApiStatus; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,5 +32,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Deprecated +@ApiStatus.ScheduledForRemoval(inVersion = "2.0") public @interface ExpectPlatform { -} \ No newline at end of file +} diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java b/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java index e054b55c..fca87718 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java +++ b/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java @@ -89,7 +89,7 @@ public final class EventFactory { @Override protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { for (T listener : listeners) { - InteractionResult result = (InteractionResult) method.invoke(listener, args); + InteractionResult result = (InteractionResult) Objects.requireNonNull(method.invoke(listener, args)); if (result != InteractionResult.PASS) { return result; } diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java b/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java index 8c53ace2..339f005a 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java +++ b/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java @@ -20,6 +20,9 @@ package me.shedaniel.architectury.event; import me.shedaniel.architectury.annotations.ExpectPlatform; +import me.shedaniel.architectury.event.events.BlockEvent; +import me.shedaniel.architectury.event.events.EntityEvent; +import me.shedaniel.architectury.event.events.PlayerEvent; import me.shedaniel.architectury.platform.Platform; import me.shedaniel.architectury.utils.Env; import net.fabricmc.api.EnvType; @@ -38,6 +41,8 @@ public final class EventHandler { registerCommon(); if (Platform.getEnvironment() == Env.SERVER) registerServer(); + + registerDelegates(); } @ExpectPlatform @@ -56,4 +61,10 @@ public final class EventHandler { private static void registerServer() { throw new AssertionError(); } + + @SuppressWarnings("deprecation") + private static void registerDelegates() { + BlockEvent.PLACE.register((EntityEvent.PLACE_BLOCK.invoker()::placeBlock)); + BlockEvent.BREAK.register((PlayerEvent.BREAK_BLOCK.invoker()::breakBlock)); + } } diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/BlockEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/BlockEvent.java new file mode 100644 index 00000000..4c783f1b --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/events/BlockEvent.java @@ -0,0 +1,63 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 shedaniel + * + * This program 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; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.event.events; + +import me.shedaniel.architectury.event.Event; +import me.shedaniel.architectury.event.EventFactory; +import me.shedaniel.architectury.utils.IntValue; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +public interface BlockEvent { + + // Block interaction events + /** + * Called when a player breaks a block. + */ + Event BREAK = EventFactory.createInteractionResult(); + /** + * Called when a block is placed in the world by an entity. + */ + Event PLACE = EventFactory.createInteractionResult(); + + /** + * Called when a falling block (sand, anvil, etc.) is about to land. + * Use fallState#getBlock to get the type of block for more granular control. + */ + Event FALLING_LAND = EventFactory.createLoop(); + + interface Break { + InteractionResult breakBlock(Level world, BlockPos pos, BlockState state, ServerPlayer player, @Nullable IntValue xp); + } + + interface Place { + InteractionResult placeBlock(Level world, BlockPos pos, BlockState state, @Nullable Entity placer); + } + + interface FallingLand { + void onLand(Level level, BlockPos pos, BlockState fallState, BlockState landOn, FallingBlockEntity entity); + } +} diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java index 82b5c629..9bafe120 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java +++ b/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java @@ -28,6 +28,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; public interface EntityEvent { @@ -43,6 +44,12 @@ public interface EntityEvent { * Invoked before entity is added to a world, equivalent to forge's {@code EntityJoinWorldEvent}. */ Event ADD = EventFactory.createInteractionResult(); + + /** + * @deprecated use {@link BlockEvent#PLACE} + */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.0") Event PLACE_BLOCK = EventFactory.createInteractionResult(); interface LivingDeath { diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/ExplosionEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/ExplosionEvent.java index 38bc9e9c..9a2699bc 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/events/ExplosionEvent.java +++ b/common/src/main/java/me/shedaniel/architectury/event/events/ExplosionEvent.java @@ -30,7 +30,7 @@ import java.util.List; public interface ExplosionEvent { Event
 PRE = EventFactory.createInteractionResult();
-    Event DETONATE = EventFactory.createInteractionResult();
+    Event DETONATE = EventFactory.createLoop();
     
     interface Pre {
         InteractionResult explode(Level world, Explosion explosion);
diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/GuiEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/GuiEvent.java
index e8a61a92..9a7863f5 100644
--- a/common/src/main/java/me/shedaniel/architectury/event/events/GuiEvent.java
+++ b/common/src/main/java/me/shedaniel/architectury/event/events/GuiEvent.java
@@ -49,7 +49,7 @@ public interface GuiEvent {
      */
     Event INIT_POST = EventFactory.createLoop();
     Event RENDER_PRE = EventFactory.createInteractionResult();
-    Event RENDER_POST = EventFactory.createInteractionResult();
+    Event RENDER_POST = EventFactory.createLoop();
     
     /**
      * Invoked during Minecraft#setScreen, equivalent to forge's {@code GuiOpenEvent}.
diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/LightningEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/LightningEvent.java
new file mode 100644
index 00000000..f1fac890
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/event/events/LightningEvent.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.event.events;
+
+import me.shedaniel.architectury.event.Event;
+import me.shedaniel.architectury.event.EventFactory;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+
+import java.util.List;
+
+public interface LightningEvent {
+    
+    // TODO Pre - Called before a lightning bolt entity is added to the world. (cancellable)
+    /**
+     * Invoked after the lightning has gathered a list of entities to strike.
+     * Remove entities from the list to stop them from being hit.
+     */
+    Event STRIKE = EventFactory.createLoop();
+    // TODO Post - Called before a lightning bolt entity is removed from the world.
+    
+    interface Strike {
+        void onStrike(LightningBolt bolt, Level level, Vec3 pos, List toStrike);
+    }
+    
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java
index 451bf92d..b86aae90 100644
--- a/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java
+++ b/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java
@@ -34,6 +34,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.state.BlockState;
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Nullable;
 
 public interface PlayerEvent {
@@ -50,6 +51,12 @@ public interface PlayerEvent {
     Event DROP_ITEM = EventFactory.createLoop();
     Event OPEN_MENU = EventFactory.createLoop();
     Event CLOSE_MENU = EventFactory.createLoop();
+    
+    /**
+     * @deprecated use {@link BlockEvent#BREAK}
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     Event BREAK_BLOCK = EventFactory.createInteractionResult();
     
     interface PlayerJoin {
diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/TooltipEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/TooltipEvent.java
index 673994e0..8a492439 100644
--- a/common/src/main/java/me/shedaniel/architectury/event/events/TooltipEvent.java
+++ b/common/src/main/java/me/shedaniel/architectury/event/events/TooltipEvent.java
@@ -44,8 +44,8 @@ public interface TooltipEvent {
      * Render forge events are only invoked on the forge side.
      */
     Event RENDER_FORGE_PRE = EventFactory.createInteractionResult();
-    Event RENDER_MODIFY_POSITION = EventFactory.createInteractionResult();
-    Event RENDER_MODIFY_COLOR = EventFactory.createInteractionResult();
+    Event RENDER_MODIFY_POSITION = EventFactory.createLoop();
+    Event RENDER_MODIFY_COLOR = EventFactory.createLoop();
     
     @Environment(EnvType.CLIENT)
     interface Item {
diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/EntityHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/EntityHooks.java
index b28c74cd..96dcff38 100644
--- a/common/src/main/java/me/shedaniel/architectury/hooks/EntityHooks.java
+++ b/common/src/main/java/me/shedaniel/architectury/hooks/EntityHooks.java
@@ -21,6 +21,8 @@ package me.shedaniel.architectury.hooks;
 
 import me.shedaniel.architectury.annotations.ExpectPlatform;
 import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import org.jetbrains.annotations.Nullable;
 
 public final class EntityHooks {
     private EntityHooks() {}
@@ -29,4 +31,10 @@ public final class EntityHooks {
     public static String getEncodeId(Entity entity) {
         throw new AssertionError();
     }
+    
+    @Nullable
+    @ExpectPlatform
+    public static Entity fromCollision(CollisionContext ctx) {
+        throw new AssertionError();
+    }
 }
diff --git a/common/src/main/java/me/shedaniel/architectury/hooks/TagHooks.java b/common/src/main/java/me/shedaniel/architectury/hooks/TagHooks.java
new file mode 100644
index 00000000..c596fcf0
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/hooks/TagHooks.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.hooks;
+
+import me.shedaniel.architectury.annotations.ExpectPlatform;
+import me.shedaniel.architectury.mixin.FluidTagsAccessor;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.*;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.material.Fluid;
+
+import java.util.function.Supplier;
+
+public final class TagHooks {
+    private TagHooks() {}
+    
+    @ExpectPlatform
+    public static  Tag.Named getOptional(ResourceLocation id, Supplier> collection) {
+        throw new AssertionError();
+    }
+    
+    public static Tag.Named getItemOptional(ResourceLocation id) {
+        return getOptional(id, ItemTags::getAllTags);
+    }
+    
+    public static Tag.Named getBlockOptional(ResourceLocation id) {
+        return getOptional(id, BlockTags::getAllTags);
+    }
+    
+    public static Tag.Named getFluidOptional(ResourceLocation id) {
+        return getOptional(id, FluidTagsAccessor.getHelper()::getAllTags);
+    }
+    
+    public static Tag.Named> getEntityTypeOptional(ResourceLocation id) {
+        return getOptional(id, EntityTypeTags::getAllTags);
+    }
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/mixin/BlockLandingInvoker.java b/common/src/main/java/me/shedaniel/architectury/mixin/BlockLandingInvoker.java
new file mode 100644
index 00000000..36996dce
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/mixin/BlockLandingInvoker.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.mixin;
+
+import me.shedaniel.architectury.event.events.BlockEvent;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.item.FallingBlockEntity;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.AnvilBlock;
+import net.minecraft.world.level.block.ConcretePowderBlock;
+import net.minecraft.world.level.block.FallingBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import org.spongepowered.asm.mixin.Mixin;
+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.LocalCapture;
+
+@Mixin({FallingBlock.class, AnvilBlock.class, ConcretePowderBlock.class})
+public abstract class BlockLandingInvoker {
+    @Inject(method = "onLand", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD)
+    public void handleLand(Level level, BlockPos pos, BlockState fallState, BlockState landOn, FallingBlockEntity entity, CallbackInfo ci) {
+        BlockEvent.FALLING_LAND.invoker().onLand(level, pos, fallState, landOn, entity);
+    }
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/mixin/FluidTagsAccessor.java b/common/src/main/java/me/shedaniel/architectury/mixin/FluidTagsAccessor.java
new file mode 100644
index 00000000..f095c06e
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/mixin/FluidTagsAccessor.java
@@ -0,0 +1,34 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.mixin;
+
+import net.minecraft.tags.FluidTags;
+import net.minecraft.tags.StaticTagHelper;
+import net.minecraft.world.level.material.Fluid;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(FluidTags.class)
+public interface FluidTagsAccessor {
+    @Accessor("HELPER")
+    static StaticTagHelper getHelper() {
+        throw new AssertionError();
+    }
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/mixin/MixinLightningBolt.java b/common/src/main/java/me/shedaniel/architectury/mixin/MixinLightningBolt.java
new file mode 100644
index 00000000..eb9ed20d
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/mixin/MixinLightningBolt.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.mixin;
+
+import me.shedaniel.architectury.event.events.LightningEvent;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LightningBolt;
+import net.minecraft.world.level.Level;
+import org.spongepowered.asm.mixin.Mixin;
+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.LocalCapture;
+
+import java.util.List;
+
+@Mixin(LightningBolt.class)
+public abstract class MixinLightningBolt extends Entity {
+    
+    public MixinLightningBolt(EntityType type, Level level) {
+        super(type, level);
+        throw new IllegalStateException();
+    }
+    
+    @Inject(method = "tick", at = @At(
+            value = "INVOKE_ASSIGN",
+            target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;",
+            ordinal = 0,
+            shift = At.Shift.BY,
+            by = 1
+    ), locals = LocalCapture.CAPTURE_FAILHARD)
+    public void handleLightning(CallbackInfo ci, double d0, List list) {
+        if (this.removed || this.level.isClientSide) {
+            return;
+        }
+        
+        LightningEvent.STRIKE.invoker().onStrike((LightningBolt) (Object) this, this.level, this.position(), list);
+    }
+    
+}
diff --git a/common/src/main/java/me/shedaniel/architectury/networking/NetworkChannel.java b/common/src/main/java/me/shedaniel/architectury/networking/NetworkChannel.java
index 75d8c282..b2c203a3 100644
--- a/common/src/main/java/me/shedaniel/architectury/networking/NetworkChannel.java
+++ b/common/src/main/java/me/shedaniel/architectury/networking/NetworkChannel.java
@@ -20,7 +20,6 @@
 package me.shedaniel.architectury.networking;
 
 import com.google.common.collect.Maps;
-import com.mojang.datafixers.util.Pair;
 import io.netty.buffer.Unpooled;
 import me.shedaniel.architectury.networking.NetworkManager.PacketContext;
 import me.shedaniel.architectury.platform.Platform;
@@ -32,11 +31,13 @@ import net.minecraft.network.FriendlyByteBuf;
 import net.minecraft.network.protocol.Packet;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.server.level.ServerPlayer;
-import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.ApiStatus;
 
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.UUID;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -57,26 +58,28 @@ public final class NetworkChannel {
     }
     
     @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     public  void register(NetworkManager.Side side, Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
         register(type, encoder, decoder, messageConsumer);
     }
     
     @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     public  void register(Optional side, Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
         register(type, encoder, decoder, messageConsumer);
     }
     
     public  void register(Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
-        String s = StringUtils.leftPad(String.valueOf(hashCodeString(type.toString())), 10, '0');
-        if (s.length() > 10) s = s.substring(0, 10);
-        MessageInfo info = new MessageInfo<>(new ResourceLocation(id + "_" + s), encoder, decoder, messageConsumer);
+        // TODO: this is pretty wasteful; add a way to specify custom or numeric ids
+        String s = UUID.nameUUIDFromBytes(type.getName().getBytes(StandardCharsets.UTF_8)).toString().replace("-", "");
+        MessageInfo info = new MessageInfo<>(new ResourceLocation(id + "/" + s), encoder, decoder, messageConsumer);
         encoders.put(type, info);
         NetworkManager.NetworkReceiver receiver = (buf, context) -> {
             info.messageConsumer.accept(info.decoder.apply(buf), () -> context);
         };
-        NetworkManager.registerReceiver(NetworkManager.clientToServer(), info.packetId, receiver);
+        NetworkManager.registerReceiver(NetworkManager.c2s(), info.packetId, receiver);
         if (Platform.getEnvironment() == Env.CLIENT) {
-            NetworkManager.registerReceiver(NetworkManager.serverToClient(), info.packetId, receiver);
+            NetworkManager.registerReceiver(NetworkManager.s2c(), info.packetId, receiver);
         }
     }
     
@@ -90,46 +93,48 @@ public final class NetworkChannel {
     }
     
     @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     public  void register(int id, Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
         register(type, encoder, decoder, messageConsumer);
     }
     
     @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     public  void register(NetworkManager.Side side, int id, Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
         register(type, encoder, decoder, messageConsumer);
     }
     
     @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.0")
     public  void register(Optional side, int id, Class type, BiConsumer encoder, Function decoder, BiConsumer> messageConsumer) {
         register(type, encoder, decoder, messageConsumer);
     }
     
-    private  Pair, FriendlyByteBuf> encode(T message) {
-        MessageInfo messageInfo = (MessageInfo) Objects.requireNonNull(encoders.get(message.getClass()));
+    public  Packet toPacket(NetworkManager.Side side, T message) {
+        MessageInfo messageInfo = (MessageInfo) Objects.requireNonNull(encoders.get(message.getClass()), "Unknown message type! " + message);
         FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
         messageInfo.encoder.accept(message, buf);
-        return new Pair<>(messageInfo, buf);
-    }
-    
-    public  Packet toPacket(NetworkManager.Side side, T message) {
-        Pair, FriendlyByteBuf> encoded = encode(message);
-        return NetworkManager.toPacket(side, encoded.getFirst().packetId, encoded.getSecond());
+        return NetworkManager.toPacket(side, messageInfo.packetId, buf);
     }
     
     public  void sendToPlayer(ServerPlayer player, T message) {
-        player.connection.send(toPacket(NetworkManager.s2c(), message));
+        Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(toPacket(NetworkManager.s2c(), message));
     }
     
     public  void sendToPlayers(Iterable players, T message) {
         Packet packet = toPacket(NetworkManager.s2c(), message);
         for (ServerPlayer player : players) {
-            player.connection.send(packet);
+            Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet);
         }
     }
     
     @Environment(EnvType.CLIENT)
     public  void sendToServer(T message) {
-        Minecraft.getInstance().getConnection().send(toPacket(NetworkManager.c2s(), message));
+        if (Minecraft.getInstance().getConnection() != null) {
+            Minecraft.getInstance().getConnection().send(toPacket(NetworkManager.c2s(), message));
+        } else {
+            throw new IllegalStateException("Unable to send packet to the server while not in game!");
+        }
     }
     
     @Environment(EnvType.CLIENT)
diff --git a/common/src/main/java/me/shedaniel/architectury/networking/NetworkManager.java b/common/src/main/java/me/shedaniel/architectury/networking/NetworkManager.java
index 135b49e0..51a74cd1 100644
--- a/common/src/main/java/me/shedaniel/architectury/networking/NetworkManager.java
+++ b/common/src/main/java/me/shedaniel/architectury/networking/NetworkManager.java
@@ -30,6 +30,8 @@ import net.minecraft.resources.ResourceLocation;
 import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.world.entity.player.Player;
 
+import java.util.Objects;
+
 public final class NetworkManager {
     @ExpectPlatform
     public static void registerReceiver(Side side, ResourceLocation id, NetworkReceiver receiver) {
@@ -42,19 +44,23 @@ public final class NetworkManager {
     }
     
     public static void sendToPlayer(ServerPlayer player, ResourceLocation id, FriendlyByteBuf buf) {
-        player.connection.send(toPacket(serverToClient(), id, buf));
+        Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(toPacket(serverToClient(), id, buf));
     }
     
     public static void sendToPlayers(Iterable players, ResourceLocation id, FriendlyByteBuf buf) {
         Packet packet = toPacket(serverToClient(), id, buf);
         for (ServerPlayer player : players) {
-            player.connection.send(packet);
+            Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet);
         }
     }
     
     @Environment(EnvType.CLIENT)
     public static void sendToServer(ResourceLocation id, FriendlyByteBuf buf) {
-        Minecraft.getInstance().getConnection().send(toPacket(clientToServer(), id, buf));
+        if (Minecraft.getInstance().getConnection() != null) {
+            Minecraft.getInstance().getConnection().send(toPacket(clientToServer(), id, buf));
+        } else {
+            throw new IllegalStateException("Unable to send packet to the server while not in game!");
+        }
     }
     
     @Environment(EnvType.CLIENT)
@@ -77,7 +83,7 @@ public final class NetworkManager {
         Player getPlayer();
         
         void queue(Runnable runnable);
-    
+        
         Env getEnvironment();
         
         default EnvType getEnv() {
diff --git a/common/src/main/java/me/shedaniel/architectury/registry/entity/EntityRenderers.java b/common/src/main/java/me/shedaniel/architectury/registry/entity/EntityRenderers.java
new file mode 100644
index 00000000..336d3dc4
--- /dev/null
+++ b/common/src/main/java/me/shedaniel/architectury/registry/entity/EntityRenderers.java
@@ -0,0 +1,21 @@
+package me.shedaniel.architectury.registry.entity;
+
+import me.shedaniel.architectury.annotations.ExpectPlatform;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+
+import java.util.function.Function;
+
+@Environment(EnvType.CLIENT)
+public final class EntityRenderers {
+    private EntityRenderers() {}
+    
+    @ExpectPlatform
+    public static  void register(EntityType type, Function> factory) {
+        throw new AssertionError();
+    }
+}
diff --git a/common/src/main/resources/architectury-common.mixins.json b/common/src/main/resources/architectury-common.mixins.json
new file mode 100644
index 00000000..e8e55db3
--- /dev/null
+++ b/common/src/main/resources/architectury-common.mixins.json
@@ -0,0 +1,13 @@
+{
+  "required": true,
+  "package": "me.shedaniel.architectury.mixin",
+  "compatibilityLevel": "JAVA_8",
+  "minVersion": "0.7.11",
+  "client": [
+  ],
+  "mixins": ["BlockLandingInvoker", "FluidTagsAccessor", "MixinLightningBolt"],
+  "injectors": {
+    "maxShiftBy": 5,
+    "defaultRequire": 1
+  }
+}
diff --git a/fabric/build.gradle b/fabric/build.gradle
index bbad71ae..a5e299ba 100644
--- a/fabric/build.gradle
+++ b/fabric/build.gradle
@@ -1,9 +1,3 @@
-import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
-import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
-import shadow.org.apache.tools.zip.ZipEntry
-import shadow.org.apache.tools.zip.ZipOutputStream
-import shadow.org.codehaus.plexus.util.IOUtil
-
 plugins {
     id "com.github.johnrengelman.shadow" version "5.0.0"
     id "com.matthewprenger.cursegradle"
@@ -14,7 +8,7 @@ loom {
 }
 
 configurations {
-    shadow
+    shadowCommon
     dev
 }
 
@@ -24,6 +18,7 @@ artifacts {
 
 architectury {
     platformSetupLoomIde()
+    fabric()
 }
 
 repositories {
@@ -37,15 +32,15 @@ dependencies {
     modImplementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
     modCompileOnly "com.terraformersmc:modmenu:${rootProject.mod_menu_version}"
     implementation "net.jodah:typetools:0.6.2"
-    shadow "net.jodah:typetools:0.6.2"
+    shadowCommon "net.jodah:typetools:0.6.2"
 
-    compileOnly(project(path: ":common")) {
+    implementation(project(path: ":common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":common", configuration: "transformDevelopmentFabric")) {
+    developmentFabric(project(path: ":common")) {
         transitive = false
     }
-    shadow(project(path: ":common", configuration: "transformProductionFabric")) {
+    shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) {
         transitive = false
     }
 }
@@ -59,7 +54,7 @@ processResources {
 
 shadowJar {
     relocate "net.jodah.typetools", "me.shedaniel.architectury.shadowed.impl.net.jodah.typetools"
-    configurations = [project.configurations.shadow]
+    configurations = [project.configurations.shadowCommon]
     classifier "shadow"
 }
 
@@ -72,9 +67,10 @@ remapJar {
 publishing {
     publications {
         mavenFabric(MavenPublication) {
+            artifactId = rootProject.archivesBaseName + "-fabric"
             artifact(remapJar.archivePath) {
                 builtBy build
-                classifier "fabric"
+                classifier null
             }
         }
     }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/EntityHooksImpl.java b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/EntityHooksImpl.java
index dffe86af..39739848 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/EntityHooksImpl.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/EntityHooksImpl.java
@@ -20,9 +20,23 @@
 package me.shedaniel.architectury.hooks.fabric;
 
 import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import org.jetbrains.annotations.Nullable;
 
 public class EntityHooksImpl {
     public static String getEncodeId(Entity entity) {
         return entity.getEncodeId();
     }
+    
+    @Nullable
+    public static Entity fromCollision(CollisionContext ctx) {
+        return ((CollisionContextExtension) ctx).getEntity();
+    }
+    
+    public interface CollisionContextExtension {
+        @Nullable
+        default Entity getEntity() {
+            return null;
+        }
+    }
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/TagHooksImpl.java b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/TagHooksImpl.java
new file mode 100644
index 00000000..378405c8
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/hooks/fabric/TagHooksImpl.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.hooks.fabric;
+
+import net.fabricmc.fabric.api.tag.TagRegistry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.Tag;
+import net.minecraft.tags.TagCollection;
+
+import java.util.function.Supplier;
+
+public class TagHooksImpl {
+    public static  Tag.Named getOptional(ResourceLocation id, Supplier> collection) {
+        return TagRegistry.create(id, collection);
+    }
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java
index 967c0f49..b1963f47 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockEntityExtension.java
@@ -21,23 +21,12 @@ package me.shedaniel.architectury.mixin.fabric;
 
 import me.shedaniel.architectury.extensions.BlockEntityExtension;
 import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable;
-import net.minecraft.core.BlockPos;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.world.level.block.entity.BlockEntity;
-import net.minecraft.world.level.block.state.BlockState;
-import org.jetbrains.annotations.NotNull;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
 
 @Mixin(BlockEntityExtension.class)
 public interface MixinBlockEntityExtension extends BlockEntityClientSerializable {
-    @Shadow(remap = false)
-    @NotNull
-    CompoundTag saveClientData(@NotNull CompoundTag tag);
-    
-    @Shadow(remap = false)
-    void loadClientData(@NotNull BlockState pos, @NotNull CompoundTag tag);
-    
     @Override
     default void fromClientTag(CompoundTag tag) {
         BlockEntity entity = (BlockEntity) this;
@@ -48,6 +37,6 @@ public interface MixinBlockEntityExtension extends BlockEntityClientSerializable
     
     @Override
     default CompoundTag toClientTag(CompoundTag tag) {
-        return saveClientData(tag);
+        return ((BlockEntityExtension) this).saveClientData(tag);
     }
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockItem.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockItem.java
index 7634ef14..73fb715f 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockItem.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBlockItem.java
@@ -19,7 +19,7 @@
 
 package me.shedaniel.architectury.mixin.fabric;
 
-import me.shedaniel.architectury.event.events.EntityEvent;
+import me.shedaniel.architectury.event.events.BlockEvent;
 import net.minecraft.world.InteractionResult;
 import net.minecraft.world.item.BlockItem;
 import net.minecraft.world.item.context.BlockPlaceContext;
@@ -35,9 +35,9 @@ public abstract class MixinBlockItem {
                      target = "Lnet/minecraft/world/item/context/BlockPlaceContext;getClickedPos()Lnet/minecraft/core/BlockPos;"),
             cancellable = true)
     private void place(BlockPlaceContext context, CallbackInfoReturnable cir) {
-        InteractionResult result = EntityEvent.PLACE_BLOCK.invoker().placeBlock(context.getLevel(), context.getClickedPos(), context.getLevel().getBlockState(context.getClickedPos()), context.getPlayer());
+        InteractionResult result = BlockEvent.PLACE.invoker().placeBlock(context.getLevel(), context.getClickedPos(), context.getLevel().getBlockState(context.getClickedPos()), context.getPlayer());
         if (result != InteractionResult.PASS) {
             cir.setReturnValue(result);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCollisionContext.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCollisionContext.java
new file mode 100644
index 00000000..cb79fb48
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCollisionContext.java
@@ -0,0 +1,9 @@
+package me.shedaniel.architectury.mixin.fabric;
+
+import me.shedaniel.architectury.hooks.fabric.EntityHooksImpl;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import org.spongepowered.asm.mixin.Mixin;
+
+@Mixin(CollisionContext.class)
+public interface MixinCollisionContext extends EntityHooksImpl.CollisionContextExtension {
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinEntityCollisionContext.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinEntityCollisionContext.java
new file mode 100644
index 00000000..ed51c31c
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinEntityCollisionContext.java
@@ -0,0 +1,32 @@
+package me.shedaniel.architectury.mixin.fabric;
+
+import me.shedaniel.architectury.hooks.fabric.EntityHooksImpl;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.EntityCollisionContext;
+import org.jetbrains.annotations.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.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(EntityCollisionContext.class)
+public abstract class MixinEntityCollisionContext implements CollisionContext, EntityHooksImpl.CollisionContextExtension {
+    
+    @Unique
+    private Entity entity = null;
+    
+    @Inject(method = "(Lnet/minecraft/world/entity/Entity;)V",
+            at = @At("RETURN"))
+    public void saveEntity(Entity entity, CallbackInfo ci) {
+        this.entity = entity;
+    }
+    
+    @Nullable
+    @Override
+    public Entity getEntity() {
+        return entity;
+    }
+    
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayerGameMode.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayerGameMode.java
index c85b7a3f..57a0f97a 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayerGameMode.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayerGameMode.java
@@ -19,7 +19,7 @@
 
 package me.shedaniel.architectury.mixin.fabric;
 
-import me.shedaniel.architectury.event.events.PlayerEvent;
+import me.shedaniel.architectury.event.events.BlockEvent;
 import net.minecraft.core.BlockPos;
 import net.minecraft.server.level.ServerLevel;
 import net.minecraft.server.level.ServerPlayer;
@@ -44,7 +44,7 @@ public class MixinServerPlayerGameMode {
                                               ordinal = 0),
             locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
     private void onBreak(BlockPos blockPos, CallbackInfoReturnable cir, BlockState state) {
-        if (PlayerEvent.BREAK_BLOCK.invoker().breakBlock(this.level, blockPos, state, this.player, null) == InteractionResult.FAIL) {
+        if (BlockEvent.BREAK.invoker().breakBlock(this.level, blockPos, state, this.player, null) == InteractionResult.FAIL) {
             cir.setReturnValue(false);
         }
     }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java
index b1c5dd28..a83c8f4c 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java
@@ -23,40 +23,35 @@ import me.shedaniel.architectury.event.events.GuiEvent;
 import me.shedaniel.architectury.event.events.InteractionEvent;
 import me.shedaniel.architectury.event.events.client.ClientPlayerEvent;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.screens.ConnectScreen;
 import net.minecraft.client.gui.screens.Screen;
-import net.minecraft.client.gui.screens.TitleScreen;
-import net.minecraft.client.main.GameConfig;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.world.InteractionHand;
 import net.minecraft.world.InteractionResultHolder;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.phys.HitResult;
 import org.jetbrains.annotations.Nullable;
+import org.objectweb.asm.Opcodes;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.*;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
-import java.io.File;
-
 @Unique
 @Mixin(Minecraft.class)
 public abstract class MixinMinecraft {
-    // @formatter:off
     @Shadow @Nullable public LocalPlayer player;
-
+    
     @Shadow @Nullable public HitResult hitResult;
-
-    @Shadow public abstract void setScreen(@Nullable Screen screen);
-
-    private boolean setScreenCancelled;
-
-    private String hostname;
-    private int port;
-    // @formatter:on
+    
+    @Shadow
+    public abstract void setScreen(@Nullable Screen screen);
+    
+    @Unique
+    private ThreadLocal setScreenCancelled = new ThreadLocal<>();
     
     @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/chat/NarratorChatListener;clear()V"))
@@ -79,10 +74,11 @@ public abstract class MixinMinecraft {
     
     @ModifyVariable(
             method = "setScreen",
-            at = @At(value = "INVOKE",
-                    target = "Lnet/minecraft/client/player/LocalPlayer;respawn()V",
-                    shift = At.Shift.BY,
-                    by = 2),
+            at = @At(value = "FIELD",
+                     opcode = Opcodes.PUTFIELD,
+                     target = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;",
+                     shift = At.Shift.BY,
+                     by = -1),
             argsOnly = true
     )
     public Screen modifyScreen(Screen screen) {
@@ -90,7 +86,7 @@ public abstract class MixinMinecraft {
         InteractionResultHolder event = GuiEvent.SET_SCREEN.invoker().modifyScreen(screen);
         switch (event.getResult()) {
             case FAIL:
-                setScreenCancelled = true;
+                setScreenCancelled.set(true);
                 return old;
             case SUCCESS:
                 screen = event.getObject();
@@ -98,56 +94,24 @@ public abstract class MixinMinecraft {
                     old.removed();
                 }
             default:
-                setScreenCancelled = false;
+                setScreenCancelled.set(false);
                 return screen;
         }
     }
     
     @Inject(
             method = "setScreen",
-            at = @At(value = "INVOKE",
-                    target = "Lnet/minecraft/client/player/LocalPlayer;respawn()V",
-                    shift = At.Shift.BY,
-                    by = 3),
+            at = @At(value = "FIELD",
+                     opcode = Opcodes.PUTFIELD,
+                     target = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;",
+                     shift = At.Shift.BY,
+                     by = -1),
             cancellable = true
     )
     public void cancelSetScreen(@Nullable Screen screen, CallbackInfo ci) {
-        if (setScreenCancelled) {
+        if (setScreenCancelled.get()) {
             ci.cancel();
-        }
-    }
-    
-    @Redirect(
-            method = "",
-            at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V"),
-            slice = @Slice(
-                    from = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;resizeDisplay()V"),
-                    to = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/LoadingOverlay;registerTextures(Lnet/minecraft/client/Minecraft;)V")
-            )
-    )
-    public void minecraftWhy(Minecraft mc, Screen screen) {
-    }
-    
-    @Inject(
-            method = "",
-            at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;resizeDisplay()V"),
-            locals = LocalCapture.CAPTURE_FAILHARD
-    )
-    public void saveLocals(GameConfig gc, CallbackInfo ci, File f, String string2, int j) {
-        hostname = string2;
-        port = j;
-    }
-    
-    @SuppressWarnings({"UnresolvedMixinReference", "ConstantConditions"})
-    @Inject(
-            method = {"method_29338", "lambda$null$1"}, // .lambda$null$1
-            at = @At("RETURN")
-    )
-    public void registerMainScreens(CallbackInfo ci) {
-        if (hostname != null) {
-            setScreen(new ConnectScreen(new TitleScreen(), (Minecraft) ((Object) this), hostname, port));
-        } else {
-            setScreen(new TitleScreen(true));
+            setScreenCancelled.set(false);
         }
     }
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/plugin/fabric/ArchitecturyMixinPlugin.java b/fabric/src/main/java/me/shedaniel/architectury/plugin/fabric/ArchitecturyMixinPlugin.java
index a97f2c28..bde86bc3 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/plugin/fabric/ArchitecturyMixinPlugin.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/plugin/fabric/ArchitecturyMixinPlugin.java
@@ -1,3 +1,22 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
 package me.shedaniel.architectury.plugin.fabric;
 
 import net.fabricmc.loader.api.FabricLoader;
diff --git a/fabric/src/main/java/me/shedaniel/architectury/registry/entity/fabric/EntityRenderersImpl.java b/fabric/src/main/java/me/shedaniel/architectury/registry/entity/fabric/EntityRenderersImpl.java
new file mode 100644
index 00000000..96830f6c
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/registry/entity/fabric/EntityRenderersImpl.java
@@ -0,0 +1,15 @@
+package me.shedaniel.architectury.registry.entity.fabric;
+
+import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry;
+import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+
+import java.util.function.Function;
+
+public class EntityRenderersImpl {
+    public static  void register(EntityType type, Function> factory) {
+        EntityRendererRegistry.INSTANCE.register(type, (manager, context) -> factory.apply(manager));
+    }
+}
diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json
index 0283e76b..d74e1893 100644
--- a/fabric/src/main/resources/architectury.mixins.json
+++ b/fabric/src/main/resources/architectury.mixins.json
@@ -1,35 +1,37 @@
 {
-  "required": true,
-  "package": "me.shedaniel.architectury.mixin.fabric",
-  "plugin": "me.shedaniel.architectury.plugin.fabric.ArchitecturyMixinPlugin",
-  "compatibilityLevel": "JAVA_8",
-  "minVersion": "0.7.11",
-  "client": [
-    "client.MixinClientLevel",
-    "client.MixinClientPacketListener",
-    "client.MixinDebugScreenOverlay",
-    "client.MixinEffectInstance",
-    "client.MixinGameRenderer",
-    "client.MixinIntegratedServer",
-    "client.MixinKeyboardHandler",
-    "client.MixinMinecraft",
-    "client.MixinMouseHandler",
-    "client.MixinMultiPlayerGameMode",
-    "client.MixinScreen",
-    "client.MixinTextureAtlas"
-  ],
-  "mixins": [
-    "ExplosionPreInvoker",
-    "LivingDeathInvoker",
-    "MixinBlockEntityExtension",
-    "MixinBlockItem",
-    "MixinCommands",
-    "MixinDedicatedServer",
-    "MixinExplosion",
-    "MixinFurnaceResultSlot",
-    "MixinItemEntity",
-    "MixinLivingEntity",
-    "MixinPersistentEntitySectionManager", "MixinPlayer",
+    "required": true,
+    "package": "me.shedaniel.architectury.mixin.fabric",
+    "plugin": "me.shedaniel.architectury.plugin.fabric.ArchitecturyMixinPlugin",
+    "compatibilityLevel": "JAVA_8",
+    "minVersion": "0.7.11",
+    "client": [
+        "client.MixinClientLevel",
+        "client.MixinClientPacketListener",
+        "client.MixinDebugScreenOverlay",
+        "client.MixinEffectInstance",
+        "client.MixinGameRenderer",
+        "client.MixinIntegratedServer",
+        "client.MixinKeyboardHandler",
+        "client.MixinMinecraft",
+        "client.MixinMouseHandler",
+        "client.MixinMultiPlayerGameMode",
+        "client.MixinScreen",
+        "client.MixinTextureAtlas"
+    ],
+    "mixins": [
+        "ExplosionPreInvoker",
+        "LivingDeathInvoker",
+        "MixinBlockEntityExtension",
+        "MixinBlockItem",
+        "MixinCollisionContext",
+        "MixinCommands",
+        "MixinDedicatedServer",
+        "MixinEntityCollisionContext",
+        "MixinExplosion",
+        "MixinFurnaceResultSlot",
+        "MixinItemEntity",
+        "MixinLivingEntity",
+        "MixinPersistentEntitySectionManager", "MixinPlayer",
     "MixinPlayerAdvancements",
     "MixinPlayerList",
     "MixinResultSlot",
diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json
index e4476b10..13ff4d36 100644
--- a/fabric/src/main/resources/fabric.mod.json
+++ b/fabric/src/main/resources/fabric.mod.json
@@ -10,6 +10,7 @@
   "license": "LGPL-3",
   "environment": "*",
   "mixins": [
+    "architectury-common.mixins.json",
     "architectury.mixins.json"
   ],
   "entrypoints": {
@@ -30,4 +31,4 @@
   "custom": {
     "modmenu:api": true
   }
-}
\ No newline at end of file
+}
diff --git a/forge/build.gradle b/forge/build.gradle
index 556ef3a6..d90f04b5 100644
--- a/forge/build.gradle
+++ b/forge/build.gradle
@@ -4,11 +4,11 @@ plugins {
 }
 
 loom {
-    mixinConfig = "architectury.mixins.json"
+    mixinConfigs = ["architectury.mixins.json", "architectury-common.mixins.json"]
 }
 
 configurations {
-    shadow
+    shadowCommon
     dev
 }
 
@@ -18,6 +18,7 @@ artifacts {
 
 architectury {
     platformSetupLoomIde()
+    forge()
 }
 
 dependencies {
@@ -25,15 +26,15 @@ dependencies {
     mappings loom.officialMojangMappings()
     forge "net.minecraftforge:forge:${rootProject.architectury.minecraft}-${rootProject.forge_version}"
     implementation "net.jodah:typetools:0.6.2"
-    shadow "net.jodah:typetools:0.6.2"
+    shadowCommon "net.jodah:typetools:0.6.2"
 
-    compileOnly(project(path: ":common")) {
+    implementation(project(path: ":common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":common", configuration: "transformDevelopmentForge")) {
+    developmentForge(project(path: ":common")) {
         transitive = false
     }
-    shadow(project(path: ":common", configuration: "transformProductionForge")) {
+    shadowCommon(project(path: ":common", configuration: "transformProductionForge")) {
         transitive = false
     }
 }
@@ -50,7 +51,7 @@ shadowJar {
     exclude "fabric.mod.json"
     exclude "architectury-common.accessWidener"
 
-    configurations = [project.configurations.shadow]
+    configurations = [project.configurations.shadowCommon]
     classifier "shadow"
 }
 
@@ -63,9 +64,10 @@ remapJar {
 publishing {
     publications {
         mavenForge(MavenPublication) {
+            artifactId = rootProject.archivesBaseName + "-forge"
             artifact(remapJar.archivePath) {
                 builtBy build
-                classifier "forge"
+                classifier null
             }
         }
     }
diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java
index ac3eefc1..76627c45 100644
--- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java
+++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java
@@ -38,6 +38,7 @@ import net.minecraftforge.client.event.*;
 import net.minecraftforge.event.entity.player.ItemTooltipEvent;
 import net.minecraftforge.event.entity.player.PlayerInteractEvent;
 import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.eventbus.api.EventPriority;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
 
@@ -45,12 +46,12 @@ import java.util.List;
 
 @OnlyIn(Dist.CLIENT)
 public class EventHandlerImplClient {
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ItemTooltipEvent event) {
         TooltipEvent.ITEM.invoker().append(event.getItemStack(), event.getToolTip(), event.getFlags());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(net.minecraftforge.event.TickEvent.ClientTickEvent event) {
         if (event.phase == net.minecraftforge.event.TickEvent.Phase.START)
             ClientTickEvent.CLIENT_PRE.invoker().tick(Minecraft.getInstance());
@@ -58,40 +59,40 @@ public class EventHandlerImplClient {
             ClientTickEvent.CLIENT_POST.invoker().tick(Minecraft.getInstance());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RenderGameOverlayEvent.Post event) {
         if (event.getType() == RenderGameOverlayEvent.ElementType.ALL)
             GuiEvent.RENDER_HUD.invoker().renderHud(event.getMatrixStack(), event.getPartialTicks());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ClientPlayerNetworkEvent.LoggedInEvent event) {
         ClientPlayerEvent.CLIENT_PLAYER_JOIN.invoker().join(event.getPlayer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ClientPlayerNetworkEvent.LoggedOutEvent event) {
         ClientPlayerEvent.CLIENT_PLAYER_QUIT.invoker().quit(event.getPlayer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ClientPlayerNetworkEvent.RespawnEvent event) {
         ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.invoker().respawn(event.getOldPlayer(), event.getNewPlayer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.InitGuiEvent.Pre event) {
-        if (GuiEvent.INIT_PRE.invoker().init(event.getGui(), event.getWidgetList(), (List) event.getGui().children()) == InteractionResult.FAIL) {
+        if (GuiEvent.INIT_PRE.invoker().init(event.getGui(), event.getWidgetList(), (List) event.getGui().children()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.InitGuiEvent.Post event) {
         GuiEvent.INIT_POST.invoker().init(event.getGui(), event.getWidgetList(), (List) event.getGui().children());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RenderGameOverlayEvent.Text event) {
         if (Minecraft.getInstance().options.renderDebug) {
             GuiEvent.DEBUG_TEXT_LEFT.invoker().gatherText(event.getLeft());
@@ -99,7 +100,7 @@ public class EventHandlerImplClient {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(net.minecraftforge.client.event.ClientChatEvent event) {
         InteractionResultHolder process = ClientChatEvent.CLIENT.invoker().process(event.getMessage());
         if (process.getObject() != null)
@@ -108,7 +109,7 @@ public class EventHandlerImplClient {
             event.setCanceled(true);
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ClientChatReceivedEvent event) {
         InteractionResultHolder process = ClientChatEvent.CLIENT_RECEIVED.invoker().process(event.getType(), event.getMessage(), event.getSenderUUID());
         if (process.getObject() != null)
@@ -117,15 +118,15 @@ public class EventHandlerImplClient {
             event.setCanceled(true);
     }
     
-    @SubscribeEvent
-    public static void event(WorldEvent.Save event) {
-        if (event.getWorld() instanceof ClientLevel) {
+    @SubscribeEvent(priority = EventPriority.HIGH)
+    public static void event(WorldEvent.Load event) {
+        if (event.getWorld().isClientSide()) {
             ClientLevel world = (ClientLevel) event.getWorld();
             ClientLifecycleEvent.CLIENT_WORLD_LOAD.invoker().act(world);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiOpenEvent event) {
         InteractionResultHolder result = GuiEvent.SET_SCREEN.invoker().modifyScreen(event.getGui());
         switch (result.getResult()) {
@@ -137,29 +138,29 @@ public class EventHandlerImplClient {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.DrawScreenEvent.Pre event) {
-        if (GuiEvent.RENDER_PRE.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()) == InteractionResult.FAIL) {
+        if (GuiEvent.RENDER_PRE.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.DrawScreenEvent.Post event) {
         GuiEvent.RENDER_POST.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.RightClickEmpty event) {
         InteractionEvent.CLIENT_RIGHT_CLICK_AIR.invoker().click(event.getPlayer(), event.getHand());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.LeftClickEmpty event) {
         InteractionEvent.CLIENT_LEFT_CLICK_AIR.invoker().click(event.getPlayer(), event.getHand());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RecipesUpdatedEvent event) {
         RecipeUpdateEvent.EVENT.invoker().update(event.getRecipeManager());
     }
@@ -167,7 +168,7 @@ public class EventHandlerImplClient {
     private static final ThreadLocal tooltipColorContext = ThreadLocal.withInitial(TooltipEventColorContextImpl::new);
     private static final ThreadLocal tooltipPositionContext = ThreadLocal.withInitial(TooltipEventPositionContextImpl::new);
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RenderTooltipEvent.Pre event) {
         if (TooltipEvent.RENDER_FORGE_PRE.invoker().renderTooltip(event.getMatrixStack(), event.getLines(), event.getX(), event.getY()) == InteractionResult.FAIL) {
             event.setCanceled(true);
@@ -181,7 +182,7 @@ public class EventHandlerImplClient {
         event.setY(positionContext.getTooltipY());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RenderTooltipEvent.Color event) {
         TooltipEventColorContextImpl colorContext = tooltipColorContext.get();
         colorContext.reset();
@@ -194,127 +195,127 @@ public class EventHandlerImplClient {
         event.setBorderStart(colorContext.getOutlineGradientTopColor());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseScrollEvent.Pre event) {
-        if (ClientScreenInputEvent.MOUSE_SCROLLED_PRE.invoker().mouseScrolled(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getScrollDelta()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.MOUSE_SCROLLED_PRE.invoker().mouseScrolled(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getScrollDelta()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseScrollEvent.Post event) {
         ClientScreenInputEvent.MOUSE_SCROLLED_POST.invoker().mouseScrolled(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getScrollDelta());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseClickedEvent.Pre event) {
-        if (ClientScreenInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseClickedEvent.Post event) {
         ClientScreenInputEvent.MOUSE_CLICKED_POST.invoker().mouseClicked(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseDragEvent.Pre event) {
-        if (ClientScreenInputEvent.MOUSE_DRAGGED_PRE.invoker().mouseDragged(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getMouseButton(), event.getDragX(), event.getDragY()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.MOUSE_DRAGGED_PRE.invoker().mouseDragged(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getMouseButton(), event.getDragX(), event.getDragY()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseDragEvent.Post event) {
         ClientScreenInputEvent.MOUSE_DRAGGED_POST.invoker().mouseDragged(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getMouseButton(), event.getDragX(), event.getDragY());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseReleasedEvent.Pre event) {
-        if (ClientScreenInputEvent.MOUSE_RELEASED_PRE.invoker().mouseReleased(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.MOUSE_RELEASED_PRE.invoker().mouseReleased(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.MouseReleasedEvent.Post event) {
         ClientScreenInputEvent.MOUSE_RELEASED_PRE.invoker().mouseReleased(Minecraft.getInstance(), event.getGui(), event.getMouseX(), event.getMouseY(), event.getButton());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardCharTypedEvent.Pre event) {
-        if (ClientScreenInputEvent.CHAR_TYPED_PRE.invoker().charTyped(Minecraft.getInstance(), event.getGui(), event.getCodePoint(), event.getModifiers()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.CHAR_TYPED_PRE.invoker().charTyped(Minecraft.getInstance(), event.getGui(), event.getCodePoint(), event.getModifiers()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardCharTypedEvent.Post event) {
         ClientScreenInputEvent.CHAR_TYPED_POST.invoker().charTyped(Minecraft.getInstance(), event.getGui(), event.getCodePoint(), event.getModifiers());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardKeyPressedEvent.Pre event) {
-        if (ClientScreenInputEvent.KEY_PRESSED_PRE.invoker().keyPressed(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.KEY_PRESSED_PRE.invoker().keyPressed(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardKeyPressedEvent.Post event) {
         ClientScreenInputEvent.KEY_PRESSED_POST.invoker().keyPressed(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardKeyReleasedEvent.Pre event) {
-        if (ClientScreenInputEvent.KEY_RELEASED_PRE.invoker().keyReleased(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers()) == InteractionResult.FAIL) {
+        if (ClientScreenInputEvent.KEY_RELEASED_PRE.invoker().keyReleased(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(GuiScreenEvent.KeyboardKeyReleasedEvent.Post event) {
         ClientScreenInputEvent.KEY_RELEASED_POST.invoker().keyReleased(Minecraft.getInstance(), event.getGui(), event.getKeyCode(), event.getScanCode(), event.getModifiers());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(InputEvent.MouseScrollEvent event) {
-        if (ClientRawInputEvent.MOUSE_SCROLLED.invoker().mouseScrolled(Minecraft.getInstance(), event.getScrollDelta()) == InteractionResult.FAIL) {
+        if (ClientRawInputEvent.MOUSE_SCROLLED.invoker().mouseScrolled(Minecraft.getInstance(), event.getScrollDelta()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(InputEvent.RawMouseEvent event) {
-        if (ClientRawInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getButton(), event.getAction(), event.getMods()) == InteractionResult.FAIL) {
+        if (ClientRawInputEvent.MOUSE_CLICKED_PRE.invoker().mouseClicked(Minecraft.getInstance(), event.getButton(), event.getAction(), event.getMods()) != InteractionResult.PASS) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(InputEvent.MouseInputEvent event) {
         ClientRawInputEvent.MOUSE_CLICKED_POST.invoker().mouseClicked(Minecraft.getInstance(), event.getButton(), event.getAction(), event.getMods());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(InputEvent.KeyInputEvent event) {
         ClientRawInputEvent.KEY_PRESSED.invoker().keyPressed(Minecraft.getInstance(), event.getKey(), event.getScanCode(), event.getAction(), event.getModifiers());
     }
     
     @OnlyIn(Dist.CLIENT)
     public static class ModBasedEventHandler {
-        @SubscribeEvent
+        @SubscribeEvent(priority = EventPriority.HIGH)
         public static void event(net.minecraftforge.client.event.TextureStitchEvent.Pre event) {
             TextureStitchEvent.PRE.invoker().stitch(event.getMap(), event::addSprite);
         }
         
-        @SubscribeEvent
+        @SubscribeEvent(priority = EventPriority.HIGH)
         public static void event(net.minecraftforge.client.event.TextureStitchEvent.Post event) {
             TextureStitchEvent.POST.invoker().stitch(event.getMap());
         }
         
-        @SubscribeEvent
+        @SubscribeEvent(priority = EventPriority.HIGH)
         public static void event(FMLClientSetupEvent event) {
             ClientLifecycleEvent.CLIENT_SETUP.invoker().stateChanged(event.getMinecraftSupplier().get());
         }
diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java
index 72309483..e3c98f7f 100644
--- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java
+++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java
@@ -44,18 +44,20 @@ import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
 import net.minecraftforge.event.entity.player.PlayerContainerEvent;
 import net.minecraftforge.event.entity.player.PlayerEvent.*;
 import net.minecraftforge.event.entity.player.PlayerInteractEvent;
-import net.minecraftforge.event.world.BlockEvent;
+import net.minecraftforge.event.world.BlockEvent.BreakEvent;
+import net.minecraftforge.event.world.BlockEvent.EntityPlaceEvent;
 import net.minecraftforge.event.world.ExplosionEvent.Detonate;
 import net.minecraftforge.event.world.ExplosionEvent.Start;
 import net.minecraftforge.event.world.WorldEvent;
 import net.minecraftforge.eventbus.api.Event;
+import net.minecraftforge.eventbus.api.EventPriority;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.LogicalSide;
 import net.minecraftforge.fml.event.server.*;
 import net.minecraftforge.fml.server.ServerLifecycleHooks;
 
 public class EventHandlerImplCommon {
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ServerTickEvent event) {
         if (event.phase == Phase.START)
             TickEvent.SERVER_PRE.invoker().tick(ServerLifecycleHooks.getCurrentServer());
@@ -63,7 +65,7 @@ public class EventHandlerImplCommon {
             TickEvent.SERVER_POST.invoker().tick(ServerLifecycleHooks.getCurrentServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(WorldTickEvent event) {
         if (event.side == LogicalSide.SERVER) {
             if (event.phase == Phase.START)
@@ -73,47 +75,47 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(FMLServerStartingEvent event) {
         LifecycleEvent.SERVER_STARTING.invoker().stateChanged(event.getServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(FMLServerStartedEvent event) {
         LifecycleEvent.SERVER_STARTED.invoker().stateChanged(event.getServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(FMLServerStoppingEvent event) {
         LifecycleEvent.SERVER_STOPPING.invoker().stateChanged(event.getServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(FMLServerStoppedEvent event) {
         LifecycleEvent.SERVER_STOPPED.invoker().stateChanged(event.getServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(RegisterCommandsEvent event) {
         CommandRegistrationEvent.EVENT.invoker().register(event.getDispatcher(), event.getEnvironment());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerLoggedInEvent event) {
         PlayerEvent.PLAYER_JOIN.invoker().join((ServerPlayer) event.getPlayer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerLoggedOutEvent event) {
         PlayerEvent.PLAYER_QUIT.invoker().quit((ServerPlayer) event.getPlayer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerRespawnEvent event) {
         PlayerEvent.PLAYER_RESPAWN.invoker().respawn((ServerPlayer) event.getPlayer(), event.isEndConquered());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(CommandEvent event) {
         CommandPerformEvent performEvent = new CommandPerformEvent(event.getParseResults(), event.getException());
         if (CommandPerformEvent.EVENT.invoker().act(performEvent) == InteractionResult.FAIL) {
@@ -123,7 +125,7 @@ public class EventHandlerImplCommon {
         event.setException(performEvent.getThrowable());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerTickEvent event) {
         if (event.phase == Phase.START) {
             TickEvent.PLAYER_PRE.invoker().tick(event.player);
@@ -132,7 +134,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ServerChatEvent event) {
         InteractionResultHolder process = ChatEvent.SERVER.invoker().process(event.getPlayer(), event.getMessage(), event.getComponent());
         if (process.getObject() != null)
@@ -141,7 +143,7 @@ public class EventHandlerImplCommon {
             event.setCanceled(true);
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(WorldEvent.Load event) {
         if (event.getWorld() instanceof ServerLevel) {
             ServerLevel world = (ServerLevel) event.getWorld();
@@ -149,7 +151,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(WorldEvent.Unload event) {
         if (event.getWorld() instanceof ServerLevel) {
             ServerLevel world = (ServerLevel) event.getWorld();
@@ -157,7 +159,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(WorldEvent.Save event) {
         if (event.getWorld() instanceof ServerLevel) {
             ServerLevel world = (ServerLevel) event.getWorld();
@@ -165,89 +167,89 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(LivingDeathEvent event) {
         if (EntityEvent.LIVING_DEATH.invoker().die(event.getEntityLiving(), event.getSource()) == InteractionResult.FAIL) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(AdvancementEvent event) {
         if (event.getPlayer() instanceof ServerPlayer) {
             PlayerEvent.PLAYER_ADVANCEMENT.invoker().award((ServerPlayer) event.getPlayer(), event.getAdvancement());
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(Clone event) {
         if (event.getOriginal() instanceof ServerPlayer && event.getPlayer() instanceof ServerPlayer) {
             PlayerEvent.PLAYER_CLONE.invoker().clone((ServerPlayer) event.getOriginal(), (ServerPlayer) event.getPlayer(), !event.isWasDeath());
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(Start event) {
         if (ExplosionEvent.PRE.invoker().explode(event.getWorld(), event.getExplosion()) == InteractionResult.FAIL) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(Detonate event) {
         ExplosionEvent.DETONATE.invoker().explode(event.getWorld(), event.getExplosion(), event.getAffectedEntities());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(LivingAttackEvent event) {
         if (EntityEvent.LIVING_ATTACK.invoker().attack(event.getEntityLiving(), event.getSource(), event.getAmount()) == InteractionResult.FAIL) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(EntityJoinWorldEvent event) {
         if (EntityEvent.ADD.invoker().add(event.getEntity(), event.getWorld()) == InteractionResult.FAIL) {
             event.setCanceled(true);
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ItemCraftedEvent event) {
         PlayerEvent.CRAFT_ITEM.invoker().craft(event.getPlayer(), event.getCrafting(), event.getInventory());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ItemSmeltedEvent event) {
         PlayerEvent.SMELT_ITEM.invoker().smelt(event.getPlayer(), event.getSmelting());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(EntityItemPickupEvent event) {
         PlayerEvent.PICKUP_ITEM_PRE.invoker().canPickup(event.getPlayer(), event.getItem(), event.getItem().getItem());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ItemPickupEvent event) {
         PlayerEvent.PICKUP_ITEM_POST.invoker().pickup(event.getPlayer(), event.getOriginalEntity(), event.getStack());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(ItemTossEvent event) {
         PlayerEvent.DROP_ITEM.invoker().drop(event.getPlayer(), event.getEntityItem());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerContainerEvent.Open event) {
         PlayerEvent.OPEN_MENU.invoker().open(event.getPlayer(), event.getContainer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerContainerEvent.Close event) {
         PlayerEvent.CLOSE_MENU.invoker().close(event.getPlayer(), event.getContainer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.RightClickItem event) {
         InteractionResultHolder result = InteractionEvent.RIGHT_CLICK_ITEM.invoker().click(event.getPlayer(), event.getHand());
         if (result.getResult() != InteractionResult.PASS) {
@@ -256,7 +258,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.RightClickBlock event) {
         InteractionResult result = InteractionEvent.RIGHT_CLICK_BLOCK.invoker().click(event.getPlayer(), event.getHand(), event.getPos(), event.getFace());
         if (result != InteractionResult.PASS) {
@@ -267,7 +269,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.EntityInteract event) {
         InteractionResult result = InteractionEvent.INTERACT_ENTITY.invoker().interact(event.getPlayer(), event.getTarget(), event.getHand());
         if (result != InteractionResult.PASS) {
@@ -276,7 +278,7 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerInteractEvent.LeftClickBlock event) {
         InteractionResult result = InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(event.getPlayer(), event.getHand(), event.getPos(), event.getFace());
         if (result != InteractionResult.PASS) {
@@ -287,10 +289,10 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
-    public static void event(BlockEvent.BreakEvent event) {
+    @SubscribeEvent(priority = EventPriority.HIGH)
+    public static void event(BreakEvent event) {
         if (event.getPlayer() instanceof ServerPlayer && event.getWorld() instanceof Level) {
-            InteractionResult result = PlayerEvent.BREAK_BLOCK.invoker().breakBlock((Level) event.getWorld(), event.getPos(), event.getState(), (ServerPlayer) event.getPlayer(), new IntValue() {
+            InteractionResult result = BlockEvent.BREAK.invoker().breakBlock((Level) event.getWorld(), event.getPos(), event.getState(), (ServerPlayer) event.getPlayer(), new IntValue() {
                 @Override
                 public int getAsInt() {
                     return event.getExpToDrop();
@@ -307,22 +309,22 @@ public class EventHandlerImplCommon {
         }
     }
     
-    @SubscribeEvent
-    public static void event(BlockEvent.EntityPlaceEvent event) {
+    @SubscribeEvent(priority = EventPriority.HIGH)
+    public static void event(EntityPlaceEvent event) {
         if (event.getWorld() instanceof Level) {
-            InteractionResult result = EntityEvent.PLACE_BLOCK.invoker().placeBlock((Level) event.getWorld(), event.getPos(), event.getState(), event.getEntity());
+            InteractionResult result = BlockEvent.PLACE.invoker().placeBlock((Level) event.getWorld(), event.getPos(), event.getState(), event.getEntity());
             if (result != InteractionResult.PASS) {
                 event.setCanceled(true);
             }
         }
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(FMLServerAboutToStartEvent event) {
         LifecycleEvent.SERVER_BEFORE_START.invoker().stateChanged(event.getServer());
     }
     
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.HIGH)
     public static void event(PlayerChangedDimensionEvent event) {
         if (event.getPlayer() instanceof ServerPlayer) {
             PlayerEvent.CHANGE_DIMENSION.invoker().change((ServerPlayer) event.getPlayer(), event.getFrom(), event.getTo());
diff --git a/forge/src/main/java/me/shedaniel/architectury/hooks/forge/EntityHooksImpl.java b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/EntityHooksImpl.java
index 469894a6..def8695c 100644
--- a/forge/src/main/java/me/shedaniel/architectury/hooks/forge/EntityHooksImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/EntityHooksImpl.java
@@ -20,9 +20,16 @@
 package me.shedaniel.architectury.hooks.forge;
 
 import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import org.jetbrains.annotations.Nullable;
 
 public class EntityHooksImpl {
     public static String getEncodeId(Entity entity) {
         return entity.getEncodeId();
     }
+    
+    @Nullable
+    public static Entity fromCollision(CollisionContext ctx) {
+        return ctx.getEntity();
+    }
 }
diff --git a/forge/src/main/java/me/shedaniel/architectury/hooks/forge/TagHooksImpl.java b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/TagHooksImpl.java
new file mode 100644
index 00000000..079ec9d2
--- /dev/null
+++ b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/TagHooksImpl.java
@@ -0,0 +1,79 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.hooks.forge;
+
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.tags.Tag;
+import net.minecraft.tags.TagCollection;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class TagHooksImpl {
+    public static  Tag.Named getOptional(ResourceLocation id, Supplier> collection) {
+        return new Tag.Named() {
+            private volatile Tag backend;
+            private volatile WeakReference> backendCollection;
+            
+            @Override
+            public ResourceLocation getName() {
+                return id;
+            }
+            
+            @Override
+            public boolean contains(T object) {
+                return getBackend().contains(object);
+            }
+            
+            @Override
+            public List getValues() {
+                return getBackend().getValues();
+            }
+            
+            private Tag getBackend() {
+                TagCollection currentCollection = collection.get();
+                
+                if (backend == null || backendCollection == null || backendCollection.get() != currentCollection) { // If not initialized or was tag changed.
+                    backendCollection = new WeakReference<>(currentCollection);
+                    return backend = currentCollection.getTagOrEmpty(id);
+                } else {
+                    return backend;
+                }
+            }
+            
+            @Override
+            public String toString() {
+                return "OptionalNamedTag[" + getName().toString() + ']';
+            }
+            
+            @Override
+            public boolean equals(Object o) {
+                return o == this || o instanceof Named && Objects.equals(getName(), ((Named) o).getName());
+            }
+            
+            @Override
+            public int hashCode() {
+                return getName().hashCode();
+            }
+        };
+    }
+}
diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/GameRulesAccessor.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/GameRulesAccessor.java
new file mode 100644
index 00000000..03713b65
--- /dev/null
+++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/GameRulesAccessor.java
@@ -0,0 +1,65 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.mixin.forge;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.GameRules;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import java.util.function.BiConsumer;
+
+@Mixin(GameRules.class)
+public interface GameRulesAccessor {
+    /**
+     * Spliting simple classes because mixin can't handle refmap using the same name
+     */
+    @Mixin(GameRules.BooleanValue.class)
+    interface BooleanValue {
+        @Invoker("create")
+        static GameRules.Type invokeCreateArchitectury(boolean value, BiConsumer biConsumer) {
+            throw new AssertionError();
+        }
+    }
+    
+    @Mixin(GameRules.BooleanValue.class)
+    interface BooleanValueSimple {
+        @Invoker("create")
+        static GameRules.Type invokeCreateArchitectury(boolean value) {
+            throw new AssertionError();
+        }
+    }
+    
+    @Mixin(GameRules.IntegerValue.class)
+    interface IntegerValue {
+        @Invoker("create")
+        static GameRules.Type invokeCreateArchitectury(int value, BiConsumer biConsumer) {
+            throw new AssertionError();
+        }
+    }
+    
+    @Mixin(GameRules.IntegerValue.class)
+    interface IntegerValueSimple {
+        @Invoker("create")
+        static GameRules.Type invokeCreateArchitectury(int value) {
+            throw new AssertionError();
+        }
+    }
+}
diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java
index fea8725e..02dd3e41 100644
--- a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java
+++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinBlockEntityExtension.java
@@ -21,19 +21,22 @@ package me.shedaniel.architectury.mixin.forge;
 
 import me.shedaniel.architectury.extensions.BlockEntityExtension;
 import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.Connection;
+import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
+import net.minecraft.world.level.block.entity.BlockEntity;
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraftforge.common.extensions.IForgeTileEntity;
-import org.jetbrains.annotations.NotNull;
 import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
 
 @Mixin(BlockEntityExtension.class)
 public interface MixinBlockEntityExtension extends IForgeTileEntity {
-    @Shadow(remap = false)
-    void loadClientData(@NotNull BlockState pos, @NotNull CompoundTag tag);
-    
     @Override
     default void handleUpdateTag(BlockState state, CompoundTag tag) {
-        loadClientData(state, tag);
+        ((BlockEntityExtension) this).loadClientData(state, tag);
+    }
+    
+    @Override
+    default void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket packet) {
+        ((BlockEntityExtension) this).loadClientData(((BlockEntity) this).getBlockState(), packet.getTag());
     }
 }
diff --git a/forge/src/main/java/me/shedaniel/architectury/networking/forge/ClientNetworkingManager.java b/forge/src/main/java/me/shedaniel/architectury/networking/forge/ClientNetworkingManager.java
index a167beb5..e7ce2d31 100644
--- a/forge/src/main/java/me/shedaniel/architectury/networking/forge/ClientNetworkingManager.java
+++ b/forge/src/main/java/me/shedaniel/architectury/networking/forge/ClientNetworkingManager.java
@@ -27,6 +27,7 @@ import net.minecraftforge.api.distmarker.Dist;
 import net.minecraftforge.api.distmarker.OnlyIn;
 import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
 import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.network.NetworkEvent;
 
 import java.util.Set;
@@ -38,7 +39,7 @@ import static me.shedaniel.architectury.networking.forge.NetworkManagerImpl.SYNC
 public class ClientNetworkingManager {
     public static void initClient() {
         NetworkManagerImpl.CHANNEL.addListener(NetworkManagerImpl.createPacketHandler(NetworkEvent.ServerCustomPayloadEvent.class, NetworkManagerImpl.S2C));
-        MinecraftForge.EVENT_BUS.addListener(event -> NetworkManagerImpl.serverReceivables.clear());
+        MinecraftForge.EVENT_BUS.register(ClientNetworkingManager.class);
         
         NetworkManagerImpl.registerS2CReceiver(SYNC_IDS, (buffer, context) -> {
             Set receivables = NetworkManagerImpl.serverReceivables;
@@ -54,4 +55,9 @@ public class ClientNetworkingManager {
     public static Player getClientPlayer() {
         return Minecraft.getInstance().player;
     }
-}
\ No newline at end of file
+    
+    @SubscribeEvent
+    public static void loggedOut(ClientPlayerNetworkEvent.LoggedOutEvent event) {
+        NetworkManagerImpl.serverReceivables.clear();
+    }
+}
diff --git a/forge/src/main/java/me/shedaniel/architectury/networking/forge/NetworkManagerImpl.java b/forge/src/main/java/me/shedaniel/architectury/networking/forge/NetworkManagerImpl.java
index b04c85db..54fe4ea8 100644
--- a/forge/src/main/java/me/shedaniel/architectury/networking/forge/NetworkManagerImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/networking/forge/NetworkManagerImpl.java
@@ -22,6 +22,7 @@ package me.shedaniel.architectury.networking.forge;
 
 import com.google.common.collect.*;
 import io.netty.buffer.Unpooled;
+import me.shedaniel.architectury.forge.ArchitecturyForge;
 import me.shedaniel.architectury.networking.NetworkManager;
 import me.shedaniel.architectury.networking.NetworkManager.NetworkReceiver;
 import me.shedaniel.architectury.utils.Env;
@@ -34,8 +35,10 @@ import net.minecraftforge.api.distmarker.Dist;
 import net.minecraftforge.api.distmarker.OnlyIn;
 import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.event.entity.player.PlayerEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.DistExecutor;
 import net.minecraftforge.fml.LogicalSide;
+import net.minecraftforge.fml.common.Mod;
 import net.minecraftforge.fml.network.NetworkDirection;
 import net.minecraftforge.fml.network.NetworkEvent;
 import net.minecraftforge.fml.network.NetworkRegistry;
@@ -49,6 +52,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
+@Mod.EventBusSubscriber(modid = ArchitecturyForge.MOD_ID)
 public class NetworkManagerImpl {
     public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, NetworkReceiver receiver) {
         if (side == NetworkManager.Side.C2S) {
@@ -79,9 +83,6 @@ public class NetworkManagerImpl {
         
         DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientNetworkingManager::initClient);
         
-        MinecraftForge.EVENT_BUS.addListener(event -> NetworkManager.sendToPlayer((ServerPlayer) event.getPlayer(), SYNC_IDS, sendSyncPacket(C2S)));
-        MinecraftForge.EVENT_BUS.addListener(event -> clientReceivables.removeAll(event.getPlayer()));
-        
         registerC2SReceiver(SYNC_IDS, (buffer, context) -> {
             Set receivables = (Set) clientReceivables.get(context.getPlayer());
             int size = buffer.readInt();
@@ -157,4 +158,14 @@ public class NetworkManagerImpl {
         }
         return packetBuffer;
     }
+    
+    @SubscribeEvent
+    public static void loggedIn(PlayerEvent.PlayerLoggedInEvent event) {
+        NetworkManager.sendToPlayer((ServerPlayer) event.getPlayer(), SYNC_IDS, sendSyncPacket(C2S));
+    }
+    
+    @SubscribeEvent
+    public static void loggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
+        clientReceivables.removeAll(event.getPlayer());
+    }
 }
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/entity/forge/EntityRenderersImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/entity/forge/EntityRenderersImpl.java
new file mode 100644
index 00000000..8be995d3
--- /dev/null
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/entity/forge/EntityRenderersImpl.java
@@ -0,0 +1,15 @@
+package me.shedaniel.architectury.registry.entity.forge;
+
+import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraftforge.fml.client.registry.RenderingRegistry;
+
+import java.util.function.Function;
+
+public class EntityRenderersImpl {
+    public static  void register(EntityType type, Function> factory) {
+        RenderingRegistry.registerEntityRenderingHandler(type, factory::apply);
+    }
+}
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/forge/BiomeModificationsImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/forge/BiomeModificationsImpl.java
index a5891b66..95c68bba 100644
--- a/forge/src/main/java/me/shedaniel/architectury/registry/forge/BiomeModificationsImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/forge/BiomeModificationsImpl.java
@@ -20,6 +20,7 @@
 package me.shedaniel.architectury.registry.forge;
 
 import com.google.common.collect.Lists;
+import me.shedaniel.architectury.forge.ArchitecturyForge;
 import me.shedaniel.architectury.hooks.biome.*;
 import me.shedaniel.architectury.mixin.forge.BiomeGenerationSettingsBuilderAccessor;
 import me.shedaniel.architectury.mixin.forge.MobSpawnSettingsBuilderAccessor;
@@ -34,10 +35,11 @@ import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
 import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
 import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
 import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder;
-import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.common.world.BiomeGenerationSettingsBuilder;
 import net.minecraftforge.common.world.MobSpawnInfoBuilder;
 import net.minecraftforge.event.world.BiomeLoadingEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.fml.common.Mod;
 import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.NotNull;
 
@@ -49,6 +51,7 @@ import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
+@Mod.EventBusSubscriber(modid = ArchitecturyForge.MOD_ID)
 public class BiomeModificationsImpl {
     private static final List, BiConsumer>> MODIFICATIONS = Lists.newArrayList();
     
@@ -68,25 +71,6 @@ public class BiomeModificationsImpl {
         MODIFICATIONS.add(Pair.of(predicate, modifier));
     }
     
-    static {
-        MinecraftForge.EVENT_BUS.addListener(event -> {
-            BiomeContext biomeContext = wrapSelectionContext(event);
-            BiomeProperties.Mutable mutableBiome = new MutableBiomeWrapped(event);
-            for (Pair, BiConsumer> pair : MODIFICATIONS) {
-                if (pair.getLeft().test(biomeContext)) {
-                    pair.getRight().accept(biomeContext, mutableBiome);
-                }
-            }
-            MutableClimatePropertiesWrapped climateProperties = (MutableClimatePropertiesWrapped) mutableBiome.getClimateProperties();
-            if (climateProperties.dirty) {
-                event.setClimate(new Biome.ClimateSettings(climateProperties.precipitation,
-                        climateProperties.temperature,
-                        climateProperties.temperatureModifier,
-                        climateProperties.downfall));
-            }
-        });
-    }
-    
     private static BiomeContext wrapSelectionContext(BiomeLoadingEvent event) {
         return new BiomeContext() {
             BiomeProperties properties = new BiomeWrapped(event);
@@ -446,4 +430,22 @@ public class BiomeModificationsImpl {
             return this;
         }
     }
+    
+    @SubscribeEvent
+    public static void onBiomeLoading(BiomeLoadingEvent event) {
+        BiomeContext biomeContext = wrapSelectionContext(event);
+        BiomeProperties.Mutable mutableBiome = new MutableBiomeWrapped(event);
+        for (Pair, BiConsumer> pair : MODIFICATIONS) {
+            if (pair.getLeft().test(biomeContext)) {
+                pair.getRight().accept(biomeContext, mutableBiome);
+            }
+        }
+        MutableClimatePropertiesWrapped climateProperties = (MutableClimatePropertiesWrapped) mutableBiome.getClimateProperties();
+        if (climateProperties.dirty) {
+            event.setClimate(new Biome.ClimateSettings(climateProperties.precipitation,
+                    climateProperties.temperature,
+                    climateProperties.temperatureModifier,
+                    climateProperties.downfall));
+        }
+    }
 }
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/forge/ColorHandlersImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/forge/ColorHandlersImpl.java
index 44dfa3d2..2e234347 100644
--- a/forge/src/main/java/me/shedaniel/architectury/registry/forge/ColorHandlersImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/forge/ColorHandlersImpl.java
@@ -28,6 +28,7 @@ import net.minecraft.client.color.item.ItemColor;
 import net.minecraft.world.level.ItemLike;
 import net.minecraft.world.level.block.Block;
 import net.minecraftforge.client.event.ColorHandlerEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
 import org.apache.commons.lang3.tuple.Pair;
 
 import java.util.List;
@@ -40,19 +41,24 @@ public class ColorHandlersImpl {
     
     static {
         EventBuses.onRegistered(ArchitecturyForge.MOD_ID, bus -> {
-            bus.addListener(event -> {
-                for (Pair[]> pair : ITEM_COLORS) {
-                    event.getItemColors().register(pair.getLeft(), unpackItems(pair.getRight()));
-                }
-            });
-            bus.addListener(event -> {
-                for (Pair[]> pair : BLOCK_COLORS) {
-                    event.getBlockColors().register(pair.getLeft(), unpackBlocks(pair.getRight()));
-                }
-            });
+            bus.register(ColorHandlersImpl.class);
         });
     }
     
+    @SubscribeEvent
+    public static void onItemColorEvent(ColorHandlerEvent.Item event) {
+        for (Pair[]> pair : ITEM_COLORS) {
+            event.getItemColors().register(pair.getLeft(), unpackItems(pair.getRight()));
+        }
+    }
+    
+    @SubscribeEvent
+    public static void onBlockColorEvent(ColorHandlerEvent.Block event) {
+        for (Pair[]> pair : BLOCK_COLORS) {
+            event.getBlockColors().register(pair.getLeft(), unpackBlocks(pair.getRight()));
+        }
+    }
+    
     @SafeVarargs
     public static void registerItemColors(ItemColor itemColor, Supplier... items) {
         Objects.requireNonNull(itemColor, "color is null!");
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/forge/GameRuleFactoryImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/forge/GameRuleFactoryImpl.java
index c925bf5f..bec7c989 100644
--- a/forge/src/main/java/me/shedaniel/architectury/registry/forge/GameRuleFactoryImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/forge/GameRuleFactoryImpl.java
@@ -19,6 +19,7 @@
 
 package me.shedaniel.architectury.registry.forge;
 
+import me.shedaniel.architectury.mixin.forge.GameRulesAccessor;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.world.level.GameRules;
 
@@ -26,20 +27,20 @@ import java.util.function.BiConsumer;
 
 public class GameRuleFactoryImpl {
     private GameRuleFactoryImpl() {}
-
+    
     public static GameRules.Type createBooleanRule(boolean defaultValue) {
-        return GameRules.BooleanValue.create(defaultValue);
+        return GameRulesAccessor.BooleanValueSimple.invokeCreateArchitectury(defaultValue);
     }
-
+    
     public static GameRules.Type createBooleanRule(boolean defaultValue, BiConsumer changedCallback) {
-        return GameRules.BooleanValue.create(defaultValue, changedCallback);
+        return GameRulesAccessor.BooleanValue.invokeCreateArchitectury(defaultValue, changedCallback);
     }
-
+    
     public static GameRules.Type createIntRule(int defaultValue) {
-        return GameRules.IntegerValue.create(defaultValue);
+        return GameRulesAccessor.IntegerValueSimple.invokeCreateArchitectury(defaultValue);
     }
-
+    
     public static GameRules.Type createIntRule(int defaultValue, BiConsumer changedCallback) {
-        return GameRules.IntegerValue.create(defaultValue, changedCallback);
+        return GameRulesAccessor.IntegerValue.invokeCreateArchitectury(defaultValue, changedCallback);
     }
 }
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/forge/ReloadListenersImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/forge/ReloadListenersImpl.java
index 3a0a76f0..b1bc82b1 100644
--- a/forge/src/main/java/me/shedaniel/architectury/registry/forge/ReloadListenersImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/forge/ReloadListenersImpl.java
@@ -20,38 +20,40 @@
 package me.shedaniel.architectury.registry.forge;
 
 import com.google.common.collect.Lists;
+import me.shedaniel.architectury.forge.ArchitecturyForge;
 import net.minecraft.client.Minecraft;
 import net.minecraft.server.packs.PackType;
 import net.minecraft.server.packs.resources.PreparableReloadListener;
 import net.minecraft.server.packs.resources.ReloadableResourceManager;
 import net.minecraftforge.api.distmarker.Dist;
 import net.minecraftforge.api.distmarker.OnlyIn;
-import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.event.AddReloadListenerEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.fml.common.Mod;
 
 import java.util.List;
 
+@Mod.EventBusSubscriber(modid = ArchitecturyForge.MOD_ID)
 public class ReloadListenersImpl {
     private static List serverDataReloadListeners = Lists.newArrayList();
     
-    static {
-        MinecraftForge.EVENT_BUS.addListener(event -> {
-            for (PreparableReloadListener listener : serverDataReloadListeners) {
-                event.addListener(listener);
-            }
-        });
-    }
-    
     public static void registerReloadListener(PackType type, PreparableReloadListener listener) {
         if (type == PackType.SERVER_DATA) {
             serverDataReloadListeners.add(listener);
         } else if (type == PackType.CLIENT_RESOURCES) {
-            reloadClientReloadListener(listener);
+            registerClientReloadListener(listener);
         }
     }
     
     @OnlyIn(Dist.CLIENT)
-    private static void reloadClientReloadListener(PreparableReloadListener listener) {
+    private static void registerClientReloadListener(PreparableReloadListener listener) {
         ((ReloadableResourceManager) Minecraft.getInstance().getResourceManager()).registerReloadListener(listener);
     }
+    
+    @SubscribeEvent
+    public static void addReloadListeners(AddReloadListenerEvent event) {
+        for (PreparableReloadListener listener : serverDataReloadListeners) {
+            event.addListener(listener);
+        }
+    }
 }
diff --git a/forge/src/main/resources/META-INF/accesstransformer.cfg b/forge/src/main/resources/META-INF/accesstransformer.cfg
index 523978ec..b4afaea4 100644
--- a/forge/src/main/resources/META-INF/accesstransformer.cfg
+++ b/forge/src/main/resources/META-INF/accesstransformer.cfg
@@ -33,7 +33,3 @@ public-f net.minecraft.world.biome.BiomeAmbience field_242524_f # foliageColor
 public-f net.minecraft.world.biome.BiomeAmbience field_242525_g # grassColor
 public-f net.minecraft.world.biome.BiomeAmbience field_242526_h # grassColorModifier
 public net.minecraft.world.storage.FolderName (Ljava/lang/String;)V
-public net.minecraft.world.GameRules$BooleanValue func_223567_b(ZLjava/util/function/BiConsumer;)Lnet/minecraft/world/GameRules$RuleType; # create
-public net.minecraft.world.GameRules$BooleanValue func_223568_b(Z)Lnet/minecraft/world/GameRules$RuleType; # create
-public net.minecraft.world.GameRules$IntegerValue func_223564_a(ILjava/util/function/BiConsumer;)Lnet/minecraft/world/GameRules$RuleType; # create
-public net.minecraft.world.GameRules$IntegerValue func_223559_b(I)Lnet/minecraft/world/GameRules$RuleType; # create
diff --git a/forge/src/main/resources/architectury.mixins.json b/forge/src/main/resources/architectury.mixins.json
index af3e3799..383b59e3 100644
--- a/forge/src/main/resources/architectury.mixins.json
+++ b/forge/src/main/resources/architectury.mixins.json
@@ -8,7 +8,7 @@
   ],
   "mixins": [
     "BiomeGenerationSettingsBuilderAccessor", "MixinRegistryEntry", "MixinBlockEntity", "MixinBlockEntityExtension",
-    "MobSpawnSettingsBuilderAccessor"
+    "MobSpawnSettingsBuilderAccessor", "GameRulesAccessor", "GameRulesAccessor$BooleanValue", "GameRulesAccessor$BooleanValueSimple", "GameRulesAccessor$IntegerValue", "GameRulesAccessor$IntegerValueSimple"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/gradle.properties b/gradle.properties
index 1f9d3a88..47ff23d4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,8 +1,8 @@
 org.gradle.jvmargs=-Xmx3G
 org.gradle.daemon=false
 
-minecraft_version=21w06a
-supported_version=21w06a
+minecraft_version=21w14a
+supported_version=21w14a
 
 cf_type=beta
 
@@ -11,8 +11,9 @@ archives_base_name_snapshot=architectury-snapshot
 base_version=2.0
 maven_group=me.shedaniel
 
-fabric_loader_version=0.11.1
-fabric_api_version=0.30.2+1.17
-mod_menu_version=2.0.0-beta.2
+fabric_loader_version=0.11.3
+fabric_api_version=0.32.7+1.17
+mod_menu_version=2.0.0-beta.3
 
-#forge_version=35.1.36
+#forge_version=36
+.0.42
diff --git a/settings.gradle b/settings.gradle
index 24df22f6..6395da1e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,8 +1,7 @@
 pluginManagement {
     repositories {
-        jcenter()
         maven { url "https://maven.fabricmc.net/" }
-        maven { url "https://dl.bintray.com/shedaniel/cloth" }
+        maven { url "https://maven.shedaniel.me/" }
         maven { url "https://files.minecraftforge.net/maven/" }
         gradlePluginPortal()
     }
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
index 7a53cd3c..2d7a2905 100644
--- a/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/TestMod.java
@@ -21,12 +21,13 @@ package me.shedaniel.architectury.test;
 
 import me.shedaniel.architectury.platform.Platform;
 import me.shedaniel.architectury.test.debug.ConsoleMessageSink;
-import me.shedaniel.architectury.test.events.DebugEvents;
 import me.shedaniel.architectury.test.debug.MessageSink;
 import me.shedaniel.architectury.test.debug.client.ClientOverlayMessageSink;
+import me.shedaniel.architectury.test.events.DebugEvents;
 import me.shedaniel.architectury.test.gamerule.TestGameRules;
 import me.shedaniel.architectury.test.registry.TestRegistries;
 import me.shedaniel.architectury.test.registry.client.TestKeybinds;
+import me.shedaniel.architectury.test.tags.TestTags;
 import me.shedaniel.architectury.utils.Env;
 import me.shedaniel.architectury.utils.EnvExecutor;
 
@@ -38,6 +39,7 @@ public class TestMod {
         DebugEvents.initialize();
         TestRegistries.initialize();
         TestGameRules.init();
+        TestTags.initialize();
         if (Platform.getEnvironment() == Env.CLIENT)
             TestKeybinds.initialize();
     }
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java
index 31bd02a9..8626510c 100644
--- a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java
@@ -27,6 +27,7 @@ import me.shedaniel.architectury.platform.Platform;
 import me.shedaniel.architectury.utils.Env;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
+import net.minecraft.client.gui.screens.ChatScreen;
 import net.minecraft.core.Position;
 import net.minecraft.core.Vec3i;
 import net.minecraft.network.chat.TranslatableComponent;
@@ -49,6 +50,14 @@ public class DebugEvents {
     }
     
     public static void debugEvents() {
+        BlockEvent.BREAK.register((world, pos, state, player, xp) -> {
+            SINK.accept(player.getScoreboardName() + " breaks " + toShortString(pos) + logSide(player.level));
+            return InteractionResult.PASS;
+        });
+        BlockEvent.PLACE.register((world, pos, state, placer) -> {
+            SINK.accept(Optional.ofNullable(placer).map(Entity::getScoreboardName).orElse("null") + " places block at " + toShortString(pos) + logSide(world));
+            return InteractionResult.PASS;
+        });
         ChatEvent.SERVER.register((player, message, component) -> {
             SINK.accept("Server chat received: " + message);
             return InteractionResultHolder.pass(component);
@@ -78,10 +87,6 @@ public class DebugEvents {
             }
             return InteractionResult.PASS;
         });
-        EntityEvent.PLACE_BLOCK.register((world, pos, state, placer) -> {
-            SINK.accept(Optional.ofNullable(placer).map(Entity::getScoreboardName).orElse("null") + " places block at " + toShortString(pos) + logSide(world));
-            return InteractionResult.PASS;
-        });
         ExplosionEvent.DETONATE.register((world, explosion, affectedEntities) -> {
             SINK.accept(world.dimension().location() + " explodes at " + toShortString(ExplosionHooks.getPosition(explosion)) + logSide(world));
         });
@@ -155,10 +160,6 @@ public class DebugEvents {
             SINK.accept(player.getScoreboardName() + " drops " + new TranslatableComponent(entity.getItem().getDescriptionId()).getString() + logSide(player.level));
             return InteractionResult.PASS;
         });
-        PlayerEvent.BREAK_BLOCK.register((world, pos, state, player, xp) -> {
-            SINK.accept(player.getScoreboardName() + " breaks " + toShortString(pos) + logSide(player.level));
-            return InteractionResult.PASS;
-        });
         PlayerEvent.OPEN_MENU.register((player, menu) -> {
             SINK.accept(player.getScoreboardName() + " opens " + toSimpleName(menu) + logSide(player.level));
         });
@@ -168,6 +169,9 @@ public class DebugEvents {
         PlayerEvent.CHANGE_DIMENSION.register((player, oldLevel, newLevel) -> {
             SINK.accept(player.getScoreboardName() + " switched from " + oldLevel.location() + " to " + newLevel.location() + logSide(player.level));
         });
+        LightningEvent.STRIKE.register((bolt, level, pos, toStrike) -> {
+            SINK.accept(bolt.getScoreboardName() + " struck at " + toShortString(pos) + logSide(level));
+        });
     }
     
     public static String toShortString(Vec3i pos) {
@@ -208,10 +212,6 @@ public class DebugEvents {
         ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.register((oldPlayer, newPlayer) -> {
             SINK.accept(newPlayer.getScoreboardName() + " respawned (client)");
         });
-        GuiEvent.SET_SCREEN.register((screen -> {
-            SINK.accept("Screen has been changed to " + toSimpleName(screen));
-            return InteractionResultHolder.pass(screen);
-        }));
         GuiEvent.INIT_PRE.register((screen, widgets, children) -> {
             SINK.accept(toSimpleName(screen) + " initializes");
             return InteractionResult.PASS;
@@ -268,6 +268,14 @@ public class DebugEvents {
             SINK.accept("Raw Key pressed: " + InputConstants.getKey(keyCode, scanCode).getDisplayName().getString());
             return InteractionResult.PASS;
         });
+        GuiEvent.SET_SCREEN.register(screen -> {
+            if (screen instanceof ChatScreen) {
+                return InteractionResultHolder.fail(screen);
+            }
+            
+            SINK.accept("Screen has been changed to " + toSimpleName(screen));
+            return InteractionResultHolder.pass(screen);
+        });
     }
     
     private static String toSimpleName(Object o) {
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/registry/TestRegistries.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/registry/TestRegistries.java
index 57a817d7..a0d789c6 100644
--- a/testmod-common/src/main/java/me/shedaniel/architectury/test/registry/TestRegistries.java
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/registry/TestRegistries.java
@@ -19,16 +19,24 @@
 
 package me.shedaniel.architectury.test.registry;
 
+import me.shedaniel.architectury.hooks.EntityHooks;
 import me.shedaniel.architectury.registry.BlockProperties;
 import me.shedaniel.architectury.registry.DeferredRegister;
 import me.shedaniel.architectury.registry.RegistrySupplier;
 import me.shedaniel.architectury.test.TestMod;
 import me.shedaniel.architectury.test.tab.TestCreativeTabs;
+import net.minecraft.core.BlockPos;
 import net.minecraft.core.Registry;
 import net.minecraft.world.item.BlockItem;
 import net.minecraft.world.item.Item;
+import net.minecraft.world.level.BlockGetter;
 import net.minecraft.world.level.block.Block;
 import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.VoxelShape;
+
+import static me.shedaniel.architectury.test.TestMod.SINK;
 
 public class TestRegistries {
     public static final DeferredRegister ITEMS = DeferredRegister.create(TestMod.MOD_ID, Registry.ITEM_REGISTRY);
@@ -39,8 +47,19 @@ public class TestRegistries {
     
     public static final RegistrySupplier TEST_BLOCK = BLOCKS.register("test_block", () ->
             new Block(BlockProperties.copy(Blocks.STONE)));
+    public static final RegistrySupplier COLLISION_BLOCK = BLOCKS.register("collision_block", () ->
+            new Block(BlockProperties.copy(Blocks.STONE)) {
+                @Override
+                public VoxelShape getCollisionShape(BlockState state, BlockGetter bg, BlockPos pos, CollisionContext ctx) {
+                    SINK.accept(EntityHooks.fromCollision(ctx) + " is colliding with " + state);
+                    return super.getCollisionShape(state, bg, pos, ctx);
+                }
+            });
+    
     public static final RegistrySupplier TEST_BLOCK_ITEM = ITEMS.register("test_block", () ->
             new BlockItem(TEST_BLOCK.get(), new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+    public static final RegistrySupplier COLLISION_BLOCK_ITEM = ITEMS.register("collision_block", () ->
+            new BlockItem(COLLISION_BLOCK.get(), new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
     
     public static void initialize() {
         BLOCKS.register();
diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/tags/TestTags.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/tags/TestTags.java
new file mode 100644
index 00000000..ec25be88
--- /dev/null
+++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/tags/TestTags.java
@@ -0,0 +1,47 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021 shedaniel
+ *
+ * This program 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; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package me.shedaniel.architectury.test.tags;
+
+import me.shedaniel.architectury.event.events.BlockEvent;
+import me.shedaniel.architectury.hooks.TagHooks;
+import me.shedaniel.architectury.test.TestMod;
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.tags.Tag;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.level.block.Block;
+
+public class TestTags {
+    public static void initialize() {
+        // This will not be present, but it should return an empty tag
+        Tag.Named heartParticles = TagHooks.getBlockOptional(new ResourceLocation(TestMod.MOD_ID, "heart_particles"));
+        // This will act like a normal tag, we have emerald block here
+        Tag.Named heartParticles2 = TagHooks.getBlockOptional(new ResourceLocation(TestMod.MOD_ID, "heart_particles2"));
+        
+        BlockEvent.BREAK.register((world, pos, state, player, xp) -> {
+            if (player != null && !world.isClientSide() && (state.is(heartParticles) || state.is(heartParticles2))) {
+                ((ServerLevel) world).sendParticles(player, ParticleTypes.HEART, false, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 10, 0.0, 0.0, 0.0, 0.0);
+            }
+            
+            return InteractionResult.PASS;
+        });
+    }
+}
diff --git a/testmod-common/src/main/resources/data/architectury-test/tags/blocks/heart_particles2.json b/testmod-common/src/main/resources/data/architectury-test/tags/blocks/heart_particles2.json
new file mode 100644
index 00000000..434965a2
--- /dev/null
+++ b/testmod-common/src/main/resources/data/architectury-test/tags/blocks/heart_particles2.json
@@ -0,0 +1,6 @@
+{
+    "replace": false,
+    "values": [
+        "minecraft:emerald_block"
+    ]
+}
diff --git a/testmod-fabric/build.gradle b/testmod-fabric/build.gradle
index 8d25c3ad..7892b52a 100644
--- a/testmod-fabric/build.gradle
+++ b/testmod-fabric/build.gradle
@@ -5,6 +5,7 @@ plugins {
 
 architectury {
     platformSetupLoomIde()
+    fabric()
 }
 
 dependencies {
@@ -14,16 +15,16 @@ dependencies {
     modCompile "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
 
     implementation project(path: ":fabric", configuration: "dev")
-    compileOnly(project(path: ":common")) {
+    implementation(project(path: ":common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":common", configuration: "transformDevelopmentFabric")) {
+    developmentFabric(project(path: ":common")) {
         transitive = false
     }
-    compileOnly(project(path: ":testmod-common")) {
+    implementation(project(path: ":testmod-common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":testmod-common", configuration: "transformDevelopmentFabric")) {
+    developmentFabric(project(path: ":testmod-common")) {
         transitive = false
     }
 }
diff --git a/testmod-forge/build.gradle b/testmod-forge/build.gradle
index 7008ff10..b8eb8568 100644
--- a/testmod-forge/build.gradle
+++ b/testmod-forge/build.gradle
@@ -4,7 +4,7 @@ plugins {
 }
 
 loom {
-    mixinConfig = "architectury.mixins.json"
+    mixinConfig "architectury.mixins.json"
     
     localMods {
         it.add(project(":forge").sourceSets.main)
@@ -13,6 +13,7 @@ loom {
 
 architectury {
     platformSetupLoomIde()
+    forge()
 }
 
 dependencies {
@@ -21,16 +22,16 @@ dependencies {
     forge "net.minecraftforge:forge:${gradle.rootProject.architectury.minecraft}-${rootProject.forge_version}"
 
     implementation project(path: ":forge", configuration: "dev")
-    compileOnly(project(path: ":common")) {
+    implementation(project(path: ":common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":common", configuration: "transformDevelopmentForge")) {
+    developmentForge(project(path: ":common")) {
         transitive = false
     }
-    compileOnly(project(path: ":testmod-common")) {
+    implementation(project(path: ":testmod-common")) {
         transitive = false
     }
-    runtimeOnly(project(path: ":testmod-common", configuration: "transformDevelopmentForge")) {
+    developmentForge(project(path: ":testmod-common")) {
         transitive = false
     }
 }