diff --git a/common/src/main/java/me/shedaniel/architectury/ArchitecturyPopulator.java b/common/src/main/java/me/shedaniel/architectury/ArchitecturyPopulator.java index e8096da3..d0508a23 100644 --- a/common/src/main/java/me/shedaniel/architectury/ArchitecturyPopulator.java +++ b/common/src/main/java/me/shedaniel/architectury/ArchitecturyPopulator.java @@ -33,7 +33,7 @@ public final class ArchitecturyPopulator { if (Modifier.isStatic(field.getModifiers())) { FieldUtils.removeFinalModifier(field); field.setAccessible(true); - String type = field.getType().toString().replace("$", ""); + String type = field.getType().getName().replace("$", ""); Class newClass = Class.forName(type.substring(0, type.lastIndexOf('.')) + "." + Architectury.getModLoader() + "." + type.substring(type.lastIndexOf('.') + 1)); field.set(null, newClass.getConstructor().newInstance()); } @@ -44,7 +44,7 @@ public final class ArchitecturyPopulator { if (!Modifier.isStatic(field.getModifiers())) { FieldUtils.removeFinalModifier(field); field.setAccessible(true); - String type = field.getType().toString().replace("$", ""); + String type = field.getType().getName().replace("$", ""); Class newClass = Class.forName(type.substring(0, type.lastIndexOf('.')) + "." + Architectury.getModLoader() + "." + type.substring(type.lastIndexOf('.') + 1)); field.set(o, newClass.getConstructor().newInstance()); } 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 85f302a2..741a6c9b 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java +++ b/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java @@ -17,11 +17,6 @@ package me.shedaniel.architectury.event; import com.google.common.reflect.AbstractInvocationHandler; -import me.shedaniel.architectury.ArchitecturyPopulator; -import me.shedaniel.architectury.Populatable; -import me.shedaniel.architectury.platform.Platform; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.jodah.typetools.TypeResolver; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; @@ -38,9 +33,6 @@ import java.util.function.Function; public final class EventFactory { private EventFactory() {} - @Populatable - private static final Impl IMPL = null; - public static Event create(Function function) { Class[] arguments = TypeResolver.resolveRawArguments(Function.class, function.getClass()); return new EventImpl<>(arguments[1], function); @@ -177,23 +169,4 @@ public final class EventFactory { } } } - - public interface Impl { - @Environment(EnvType.CLIENT) - void registerClient(); - - void registerCommon(); - - @Environment(EnvType.SERVER) - void registerServer(); - } - - static { - ArchitecturyPopulator.populate(EventFactory.class); - if (Platform.getEnv() == EnvType.CLIENT) - IMPL.registerClient(); - IMPL.registerCommon(); - if (Platform.getEnv() == EnvType.SERVER) - IMPL.registerServer(); - } } diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java b/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java new file mode 100644 index 00000000..b7a82be3 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/EventHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 shedaniel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.shedaniel.architectury.event; + +import me.shedaniel.architectury.ArchitecturyPopulator; +import me.shedaniel.architectury.Populatable; +import me.shedaniel.architectury.platform.Platform; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public final class EventHandler { + private EventHandler() {} + + @Populatable + private static final Impl IMPL = null; + private static boolean initialized = false; + + public static void init() { + if (initialized) return; + initialized = true; + if (Platform.getEnv() == EnvType.CLIENT) + IMPL.registerClient(); + IMPL.registerCommon(); + if (Platform.getEnv() == EnvType.SERVER) + IMPL.registerServer(); + } + + public interface Impl { + @Environment(EnvType.CLIENT) + void registerClient(); + + void registerCommon(); + + @Environment(EnvType.SERVER) + void registerServer(); + } + + static { + ArchitecturyPopulator.populate(EventHandler.class); + } +} 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 new file mode 100644 index 00000000..d4ff5c7f --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/events/ExplosionEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 shedaniel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.shedaniel.architectury.event.events; + +import me.shedaniel.architectury.event.Event; +import me.shedaniel.architectury.event.EventFactory; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; + +import java.util.List; + +public interface ExplosionEvent { + Event
 PRE = EventFactory.createInteractionResult(Pre.class);
+    Event DETONATE = EventFactory.createInteractionResult(Detonate.class);
+    
+    interface Pre {
+        InteractionResult explode(Level world, Explosion explosion);
+    }
+    
+    interface Detonate {
+        void explode(Level world, Explosion explosion, List affectedEntities);
+    }
+}
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 707724fa..79769b49 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
@@ -44,6 +44,8 @@ public interface GuiEvent {
      * Invoked after Screen#init, equivalent to forge's {@code GuiScreenEvent.InitGuiEvent.Post}.
      */
     Event INIT_POST = EventFactory.createLoop(ScreenInitPost.class);
+    Event RENDER_PRE = EventFactory.createInteractionResult(ScreenRenderPre.class);
+    Event RENDER_POST = EventFactory.createInteractionResult(ScreenRenderPost.class);
     
     @Environment(EnvType.CLIENT)
     interface RenderHud {
@@ -64,4 +66,14 @@ public interface GuiEvent {
     interface ScreenInitPost {
         void init(Screen screen, List widgets, List children);
     }
+    
+    @Environment(EnvType.CLIENT)
+    interface ScreenRenderPre {
+        InteractionResult render(Screen screen, PoseStack matrices, int mouseX, int mouseY, float delta);
+    }
+    
+    @Environment(EnvType.CLIENT)
+    interface ScreenRenderPost {
+        void render(Screen screen, PoseStack matrices, int mouseX, int mouseY, float delta);
+    }
 }
diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/LifecycleEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/LifecycleEvent.java
index cc16106f..38b01967 100644
--- a/common/src/main/java/me/shedaniel/architectury/event/events/LifecycleEvent.java
+++ b/common/src/main/java/me/shedaniel/architectury/event/events/LifecycleEvent.java
@@ -53,9 +53,13 @@ public interface LifecycleEvent {
      */
     Event SERVER_STOPPED = EventFactory.createLoop(ServerState.class);
     /**
-     * Invoked after a world is loaded only on server, equivalent to forge's {@code WorldEvent.Load}.
+     * Invoked after a world is loaded only on server, equivalent to forge's {@code WorldEvent.Load} and fabric's {@code ServerWorldEvents#LOAD}.
      */
     Event SERVER_WORLD_LOAD = EventFactory.createLoop(ServerWorldState.class);
+    /**
+     * Invoked after a world is unloaded, equivalent to forge's {@code WorldEvent.Unload} and fabric's {@code ServerWorldEvents#UNLOAD}.
+     */
+    Event SERVER_WORLD_UNLOAD = EventFactory.createLoop(ServerWorldState.class);
     /**
      * Invoked during a world is saved, equivalent to forge's {@code WorldEvent.Save}.
      */
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 d3f3294e..c9f5f5a3 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
@@ -20,6 +20,7 @@ import me.shedaniel.architectury.event.Event;
 import me.shedaniel.architectury.event.EventFactory;
 import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
+import net.minecraft.advancements.Advancement;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.server.level.ServerPlayer;
 
@@ -30,6 +31,8 @@ public interface PlayerEvent {
     @Environment(EnvType.CLIENT) Event CLIENT_PLAYER_JOIN = EventFactory.createLoop(ClientPlayerJoin.class);
     @Environment(EnvType.CLIENT) Event CLIENT_PLAYER_QUIT = EventFactory.createLoop(ClientPlayerQuit.class);
     @Environment(EnvType.CLIENT) Event CLIENT_PLAYER_RESPAWN = EventFactory.createLoop(ClientPlayerRespawn.class);
+    Event PLAYER_ADVANCEMENT = EventFactory.createLoop(PlayerAdvancement.class);
+    Event PLAYER_CLONE = EventFactory.createLoop(PlayerClone.class);
     
     interface PlayerJoin {
         void join(ServerPlayer player);
@@ -43,6 +46,14 @@ public interface PlayerEvent {
         void respawn(ServerPlayer newPlayer, boolean conqueredEnd);
     }
     
+    interface PlayerClone {
+        void clone(ServerPlayer oldPlayer, ServerPlayer newPlayer, boolean wonGame);
+    }
+    
+    interface PlayerAdvancement {
+        void award(ServerPlayer player, Advancement advancement);
+    }
+    
     @Environment(EnvType.CLIENT)
     interface ClientPlayerJoin {
         void join(LocalPlayer player);
diff --git a/common/src/main/java/me/shedaniel/architectury/registry/Registry.java b/common/src/main/java/me/shedaniel/architectury/registry/Registry.java
index 6b99347a..f0e1dd5b 100644
--- a/common/src/main/java/me/shedaniel/architectury/registry/Registry.java
+++ b/common/src/main/java/me/shedaniel/architectury/registry/Registry.java
@@ -16,12 +16,16 @@
 
 package me.shedaniel.architectury.registry;
 
+import net.minecraft.resources.ResourceKey;
 import net.minecraft.resources.ResourceLocation;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Supplier;
 
-public interface Registry {
+public interface Registry extends Iterable {
     Supplier delegate(ResourceLocation id);
     
     Supplier register(ResourceLocation id, Supplier supplier);
@@ -29,6 +33,18 @@ public interface Registry {
     @Nullable
     ResourceLocation getId(T obj);
     
+    Optional> getKey(T obj);
+    
     @Nullable
     T get(ResourceLocation id);
+    
+    boolean contains(ResourceLocation id);
+    
+    boolean containsValue(T obj);
+    
+    Set getIds();
+    
+    Set, T>> entrySet();
+    
+    ResourceKey> key();
 }
diff --git a/fabric/build.gradle b/fabric/build.gradle
index 87376019..404faa89 100644
--- a/fabric/build.gradle
+++ b/fabric/build.gradle
@@ -17,6 +17,8 @@ dependencies {
     modCompile("net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}")
     modCompile("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}")
     modCompileOnly("io.github.prospector:modmenu:${rootProject.mod_menu_version}")
+    implementation "net.jodah:typetools:0.6.2"
+    shadow "net.jodah:typetools:0.6.2"
 
     compile(project(":common")) {
         transitive = false
@@ -34,6 +36,7 @@ processResources {
 }
 
 shadowJar {
+    relocate "net.jodah.typetools", "me.shedaniel.architectury.shadowed.impl.net.jodah.typetools"
     configurations = [project.configurations.shadow]
     classifier "shadow"
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java b/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventHandlerImpl.java
similarity index 88%
rename from fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java
rename to fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventHandlerImpl.java
index f60e8d7f..bc70f081 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventHandlerImpl.java
@@ -16,7 +16,7 @@
 
 package me.shedaniel.architectury.event.fabric;
 
-import me.shedaniel.architectury.event.EventFactory;
+import me.shedaniel.architectury.event.EventHandler;
 import me.shedaniel.architectury.event.events.*;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
@@ -25,9 +25,10 @@ import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
 import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
 import net.minecraft.commands.Commands;
 
-public class EventFactoryImpl implements EventFactory.Impl {
+public class EventHandlerImpl implements EventHandler.Impl {
     @Override
     public void registerClient() {
         ClientLifecycleEvents.CLIENT_STARTED.register(LifecycleEvent.CLIENT_STARTED.invoker()::stateChanged);
@@ -54,6 +55,9 @@ public class EventFactoryImpl implements EventFactory.Impl {
         ServerTickEvents.START_WORLD_TICK.register(TickEvent.SERVER_WORLD_PRE.invoker()::tick);
         ServerTickEvents.END_WORLD_TICK.register(TickEvent.SERVER_WORLD_POST.invoker()::tick);
         
+        ServerWorldEvents.LOAD.register((server, world) -> LifecycleEvent.SERVER_WORLD_LOAD.invoker().act(world));
+        ServerWorldEvents.UNLOAD.register((server, world) -> LifecycleEvent.SERVER_WORLD_UNLOAD.invoker().act(world));
+        
         CommandRegistrationCallback.EVENT.register((commandDispatcher, b) -> CommandRegistrationEvent.EVENT.invoker().register(commandDispatcher, b ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED));
     }
     
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/ExplosionPreInvoker.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/ExplosionPreInvoker.java
new file mode 100644
index 00000000..1ba3a9ab
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/ExplosionPreInvoker.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.mixin.fabric;
+
+import me.shedaniel.architectury.event.events.ExplosionEvent;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.Explosion;
+import net.minecraft.world.level.ExplosionDamageCalculator;
+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.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+@Mixin(value = {Level.class, ServerLevel.class})
+public class ExplosionPreInvoker {
+    @Inject(method = "explode(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Lnet/minecraft/world/level/ExplosionDamageCalculator;DDDFZLnet/minecraft/world/level/Explosion$BlockInteraction;)Lnet/minecraft/world/level/Explosion;", 
+            at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Explosion;explode()V"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
+    private void explodePre(Entity entity, DamageSource damageSource, ExplosionDamageCalculator explosionDamageCalculator, double d, double e, double f, float g, boolean bl, Explosion.BlockInteraction blockInteraction, CallbackInfoReturnable cir, Explosion explosion) {
+        if (ExplosionEvent.PRE.invoker().explode((Level)(Object) this, explosion) == InteractionResult.FAIL) {
+            cir.setReturnValue(explosion);
+        }
+    }
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCommands.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCommands.java
index 662214ac..441c9b00 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCommands.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCommands.java
@@ -32,7 +32,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
 @Mixin(Commands.class)
 public class MixinCommands {
     @Redirect(method = "performCommand",
-              at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/StringReader;Ljava/lang/Object;)I"))
+              at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/CommandDispatcher;execute(Lcom/mojang/brigadier/StringReader;Ljava/lang/Object;)I", remap = false))
     private int performCommand(CommandDispatcher dispatcher, StringReader input, Object source) throws CommandSyntaxException {
         CommandSourceStack stack = (CommandSourceStack) source;
         ParseResults parse = dispatcher.parse(input, stack);
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinMinecraftServer.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinExplosion.java
similarity index 55%
rename from fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinMinecraftServer.java
rename to fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinExplosion.java
index 13effb8b..2c7d3453 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinMinecraftServer.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinExplosion.java
@@ -16,11 +16,10 @@
 
 package me.shedaniel.architectury.mixin.fabric;
 
-import me.shedaniel.architectury.event.events.LifecycleEvent;
-import net.minecraft.resources.ResourceKey;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.level.progress.ChunkProgressListener;
+import me.shedaniel.architectury.event.events.ExplosionEvent;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.Explosion;
 import net.minecraft.world.level.Level;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
@@ -28,17 +27,17 @@ import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
-import java.util.Map;
+import java.util.List;
+import java.util.Set;
 
-@Mixin(MinecraftServer.class)
-public class MixinMinecraftServer {
-    @Shadow @Final private Map, ServerLevel> levels;
+@Mixin(Explosion.class)
+public class MixinExplosion {
+    @Shadow @Final private Level level;
     
-    @Inject(method = "createLevels", at = @At("RETURN"))
-    private void createLevels(ChunkProgressListener chunkProgressListener, CallbackInfo ci) {
-        for (ServerLevel level : levels.values()) {
-            LifecycleEvent.SERVER_WORLD_LOAD.invoker().act(level);
-        }
+    @Inject(method = "explode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;(DDD)V", ordinal = 0), locals = LocalCapture.CAPTURE_FAILHARD)
+    private void explodePost(CallbackInfo ci, Set set, float q, int r, int s, int t, int u, int v, int w, List list) {
+        ExplosionEvent.DETONATE.invoker().explode(level, (Explosion) (Object) this, list);
     }
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayerAdvancements.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayerAdvancements.java
new file mode 100644
index 00000000..9de8a309
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayerAdvancements.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.mixin.fabric;
+
+import me.shedaniel.architectury.event.events.PlayerEvent;
+import net.minecraft.advancements.Advancement;
+import net.minecraft.server.PlayerAdvancements;
+import net.minecraft.server.level.ServerPlayer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(PlayerAdvancements.class)
+public class MixinPlayerAdvancements {
+    @Shadow private ServerPlayer player;
+    
+    @Inject(method = "award",
+            at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V",
+                     shift = At.Shift.AFTER))
+    private void award(Advancement advancement, String string, CallbackInfoReturnable cir) {
+        PlayerEvent.PLAYER_ADVANCEMENT.invoker().award(player, advancement);
+    }
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayer.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayer.java
new file mode 100644
index 00000000..ade7e4fb
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinServerPlayer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.mixin.fabric;
+
+import me.shedaniel.architectury.event.events.PlayerEvent;
+import net.minecraft.server.level.ServerPlayer;
+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;
+
+@Mixin(ServerPlayer.class)
+public class MixinServerPlayer {
+    @Inject(method = "restoreFrom", at = @At("RETURN"))
+    private void restoreFrom(ServerPlayer serverPlayer, boolean bl, CallbackInfo ci) {
+        PlayerEvent.PLAYER_CLONE.invoker().clone((ServerPlayer) (Object) this, serverPlayer, bl);
+    }
+}
diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinGameRenderer.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinGameRenderer.java
new file mode 100644
index 00000000..71e8561a
--- /dev/null
+++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinGameRenderer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.mixin.fabric.client;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.shedaniel.architectury.event.events.GuiEvent;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GameRenderer;
+import net.minecraft.world.InteractionResult;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+@Mixin(GameRenderer.class)
+public abstract class MixinGameRenderer {
+    @Shadow @Final private Minecraft minecraft;
+    
+    @Inject(method = "render(FJZ)V",
+            at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;render(Lcom/mojang/blaze3d/vertex/PoseStack;IIF)V",
+                     ordinal = 0), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
+    public void renderScreenPre(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, PoseStack matrices) {
+        if (GuiEvent.RENDER_PRE.invoker().render(minecraft.screen, matrices, mouseX, mouseY, minecraft.getDeltaFrameTime()) == InteractionResult.FAIL) {
+            ci.cancel();
+        }
+    }
+    
+    @Inject(method = "render(FJZ)V",
+            at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;render(Lcom/mojang/blaze3d/vertex/PoseStack;IIF)V",
+                     shift = At.Shift.AFTER, ordinal = 0), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+    public void renderScreenPost(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, PoseStack matrices) {
+        GuiEvent.RENDER_POST.invoker().render(minecraft.screen, matrices, mouseX, mouseY, minecraft.getDeltaFrameTime());
+    }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/me/shedaniel/architectury/registry/fabric/RegistriesImpl.java b/fabric/src/main/java/me/shedaniel/architectury/registry/fabric/RegistriesImpl.java
index 0632a337..63025ad2 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/registry/fabric/RegistriesImpl.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/registry/fabric/RegistriesImpl.java
@@ -21,8 +21,13 @@ import me.shedaniel.architectury.registry.Registry;
 import net.minecraft.resources.ResourceKey;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.util.LazyLoadedValue;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Supplier;
 
 public class RegistriesImpl implements Registries.Impl {
@@ -78,10 +83,46 @@ public class RegistriesImpl implements Registries.Impl {
         public @Nullable ResourceLocation getId(T obj) {
             return delegate.getKey(obj);
         }
-        
+    
+        @Override
+        public Optional> getKey(T obj) {
+            return delegate.getResourceKey(obj);
+        }
+    
         @Override
         public @Nullable T get(ResourceLocation id) {
             return delegate.get(id);
         }
+    
+        @Override
+        public boolean contains(ResourceLocation id) {
+            return delegate.containsKey(id);
+        }
+    
+        @Override
+        public boolean containsValue(T obj) {
+            return delegate.getResourceKey(obj).isPresent();
+        }
+    
+        @Override
+        public Set getIds() {
+            return delegate.keySet();
+        }
+    
+        @Override
+        public Set, T>> entrySet() {
+            return delegate.entrySet();
+        }
+    
+        @Override
+        public ResourceKey> key() {
+            return delegate.key();
+        }
+    
+        @NotNull
+        @Override
+        public Iterator iterator() {
+            return delegate.iterator();
+        }
     }
 }
diff --git a/fabric/src/main/java/me/shedaniel/architectury/utils/fabric/GameInstanceImpl.java b/fabric/src/main/java/me/shedaniel/architectury/utils/fabric/GameInstanceImpl.java
index 6fff6d1f..8f0955e4 100644
--- a/fabric/src/main/java/me/shedaniel/architectury/utils/fabric/GameInstanceImpl.java
+++ b/fabric/src/main/java/me/shedaniel/architectury/utils/fabric/GameInstanceImpl.java
@@ -16,6 +16,7 @@
 
 package me.shedaniel.architectury.utils.fabric;
 
+import me.shedaniel.architectury.event.EventHandler;
 import me.shedaniel.architectury.event.events.LifecycleEvent;
 import me.shedaniel.architectury.platform.Platform;
 import me.shedaniel.architectury.utils.GameInstance;
@@ -38,6 +39,7 @@ public class GameInstanceImpl implements GameInstance.Impl {
     }
     
     public static void init() {
+        EventHandler.init();
         LifecycleEvent.SERVER_STARTING.register(server -> GameInstanceImpl.server = server);
         LifecycleEvent.SERVER_STOPPED.register(server -> GameInstanceImpl.server = null);
     }
diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json
index 2e50b051..1baef9e7 100644
--- a/fabric/src/main/resources/architectury.mixins.json
+++ b/fabric/src/main/resources/architectury.mixins.json
@@ -7,11 +7,13 @@
     "client.MixinClientLevel",
     "client.MixinClientPacketListener",
     "client.MixinDebugScreenOverlay",
+    "client.MixinGameRenderer",
     "client.MixinMinecraft",
     "client.MixinScreen"
   ],
   "mixins": [
-    "LivingDeathInvoker", "MixinCommands", "MixinMinecraftServer", "MixinPlayer", "MixinPlayerList", "MixinServerGamePacketListenerImpl", "MixinServerLevel"
+    "ExplosionPreInvoker", "LivingDeathInvoker", "MixinCommands", "MixinExplosion", "MixinPlayer", "MixinPlayerAdvancements", "MixinPlayerList",
+    "MixinServerGamePacketListenerImpl", "MixinServerLevel", "MixinServerPlayer"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImpl.java
similarity index 81%
rename from forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java
rename to forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImpl.java
index 266d046f..d14bbbfb 100644
--- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImpl.java
@@ -16,7 +16,7 @@
 
 package me.shedaniel.architectury.event.forge;
 
-import me.shedaniel.architectury.event.EventFactory;
+import me.shedaniel.architectury.event.EventHandler;
 import me.shedaniel.architectury.event.events.*;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.IGuiEventListener;
@@ -35,10 +35,14 @@ import net.minecraftforge.event.RegisterCommandsEvent;
 import net.minecraftforge.event.ServerChatEvent;
 import net.minecraftforge.event.TickEvent.*;
 import net.minecraftforge.event.entity.living.LivingDeathEvent;
+import net.minecraftforge.event.entity.player.AdvancementEvent;
 import net.minecraftforge.event.entity.player.ItemTooltipEvent;
+import net.minecraftforge.event.entity.player.PlayerEvent.Clone;
 import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
 import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
 import net.minecraftforge.event.entity.player.PlayerEvent.PlayerRespawnEvent;
+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.SubscribeEvent;
 import net.minecraftforge.fml.LogicalSide;
@@ -50,7 +54,7 @@ import net.minecraftforge.fml.server.ServerLifecycleHooks;
 
 import java.util.List;
 
-public class EventFactoryImpl implements EventFactory.Impl {
+public class EventHandlerImpl implements EventHandler.Impl {
     @Override
     public void registerClient() {
         MinecraftForge.EVENT_BUS.register(Client.class);
@@ -145,6 +149,18 @@ public class EventFactoryImpl implements EventFactory.Impl {
                 LifecycleEvent.CLIENT_WORLD_LOAD.invoker().act(world);
             }
         }
+        
+        @SubscribeEvent
+        public static void event(GuiScreenEvent.DrawScreenEvent.Pre event) {
+            if (GuiEvent.RENDER_PRE.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()) == ActionResultType.FAIL) {
+                event.setCanceled(true);
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(GuiScreenEvent.DrawScreenEvent.Post event) {
+            GuiEvent.RENDER_POST.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks());
+        }
     }
     
     public static class Common {
@@ -242,6 +258,14 @@ public class EventFactoryImpl implements EventFactory.Impl {
             }
         }
         
+        @SubscribeEvent
+        public static void event(WorldEvent.Unload event) {
+            if (event.getWorld() instanceof ServerWorld) {
+                ServerWorld world = (ServerWorld) event.getWorld();
+                LifecycleEvent.SERVER_WORLD_UNLOAD.invoker().act(world);
+            }
+        }
+        
         @SubscribeEvent
         public static void event(WorldEvent.Save event) {
             if (event.getWorld() instanceof ServerWorld) {
@@ -256,6 +280,32 @@ public class EventFactoryImpl implements EventFactory.Impl {
                 event.setCanceled(true);
             }
         }
+        
+        @SubscribeEvent
+        public static void event(AdvancementEvent event) {
+            if (event.getPlayer() instanceof ServerPlayerEntity) {
+                PlayerEvent.PLAYER_ADVANCEMENT.invoker().award((ServerPlayerEntity) event.getPlayer(), event.getAdvancement());
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(Clone event) {
+            if (event.getOriginal() instanceof ServerPlayerEntity && event.getPlayer() instanceof ServerPlayerEntity) {
+                PlayerEvent.PLAYER_CLONE.invoker().clone((ServerPlayerEntity) event.getOriginal(), (ServerPlayerEntity) event.getPlayer(), !event.isWasDeath());
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(Start event) {
+            if (ExplosionEvent.PRE.invoker().explode(event.getWorld(), event.getExplosion()) == ActionResultType.FAIL) {
+                event.setCanceled(true);
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(Detonate event) {
+            ExplosionEvent.DETONATE.invoker().explode(event.getWorld(), event.getExplosion(), event.getAffectedEntities());
+        }
     }
     
     @OnlyIn(Dist.DEDICATED_SERVER)
diff --git a/forge/src/main/java/me/shedaniel/architectury/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java
similarity index 95%
rename from forge/src/main/java/me/shedaniel/architectury/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java
rename to forge/src/main/java/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java
index 3c4a5de6..01165684 100644
--- a/forge/src/main/java/me/shedaniel/architectury/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/hooks/forge/ScreenHooksImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package me.shedaniel.architectury.me.shedaniel.architectury.hooks.forge;
+package me.shedaniel.architectury.hooks.forge;
 
 import me.shedaniel.architectury.hooks.ScreenHooks;
 import net.minecraft.client.gui.IGuiEventListener;
diff --git a/forge/src/main/java/me/shedaniel/architectury/registry/forge/RegistriesImpl.java b/forge/src/main/java/me/shedaniel/architectury/registry/forge/RegistriesImpl.java
index e42cab19..d35e2187 100644
--- a/forge/src/main/java/me/shedaniel/architectury/registry/forge/RegistriesImpl.java
+++ b/forge/src/main/java/me/shedaniel/architectury/registry/forge/RegistriesImpl.java
@@ -34,7 +34,10 @@ import net.minecraftforge.registries.RegistryManager;
 
 import javax.annotation.Nullable;
 import java.lang.reflect.Type;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Supplier;
 
 public class RegistriesImpl implements Registries.Impl {
@@ -119,12 +122,47 @@ public class RegistriesImpl implements Registries.Impl {
         public ResourceLocation getId(T obj) {
             return delegate.getKey(obj);
         }
-        
+    
+        @Override
+        public Optional> getKey(T t) {
+            return delegate.getResourceKey(t);
+        }
+    
         @Override
         @Nullable
         public T get(ResourceLocation id) {
             return delegate.get(id);
         }
+    
+        @Override
+        public boolean contains(ResourceLocation resourceLocation) {
+            return delegate.containsKey(resourceLocation);
+        }
+    
+        @Override
+        public boolean containsValue(T t) {
+            return delegate.getResourceKey(t).isPresent();
+        }
+    
+        @Override
+        public Set getIds() {
+            return delegate.keySet();
+        }
+    
+        @Override
+        public Set, T>> entrySet() {
+            return delegate.entrySet();
+        }
+    
+        @Override
+        public RegistryKey> key() {
+            return delegate.key();
+        }
+    
+        @Override
+        public Iterator iterator() {
+            return delegate.iterator();
+        }
     }
     
     public static class ForgeBackedRegistryImpl> implements Registry {
@@ -154,11 +192,46 @@ public class RegistriesImpl implements Registries.Impl {
         public ResourceLocation getId(T obj) {
             return delegate.getKey(obj);
         }
-        
+    
+        @Override
+        public Optional> getKey(T t) {
+            return Optional.ofNullable(getId(t)).map(id -> RegistryKey.create(key(), id));
+        }
+    
         @Override
         @Nullable
         public T get(ResourceLocation id) {
             return delegate.getValue(id);
         }
+    
+        @Override
+        public boolean contains(ResourceLocation resourceLocation) {
+            return delegate.containsKey(resourceLocation);
+        }
+    
+        @Override
+        public boolean containsValue(T t) {
+            return delegate.containsValue(t);
+        }
+    
+        @Override
+        public Set getIds() {
+            return delegate.getKeys();
+        }
+    
+        @Override
+        public Set, T>> entrySet() {
+            return delegate.getEntries();
+        }
+    
+        @Override
+        public RegistryKey> key() {
+            return RegistryKey.createRegistryKey(delegate.getRegistryName());
+        }
+    
+        @Override
+        public Iterator iterator() {
+            return delegate.iterator();
+        }
     }
 }