Browse Source

Merge remote-tracking branch 'architectury/1.16' into feature/gui-input

shedaniel 4 years ago
parent
commit
21f4243b2a
20 changed files with 511 additions and 29 deletions
  1. 7 2
      build.gradle
  2. 9 4
      common/src/main/java/me/shedaniel/architectury/Architectury.java
  3. 1 1
      common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java
  4. 1 1
      common/src/main/java/me/shedaniel/architectury/registry/CreativeTabs.java
  5. 39 0
      common/src/main/java/me/shedaniel/architectury/registry/CriteriaTriggersRegistry.java
  6. 38 15
      common/src/main/java/me/shedaniel/architectury/registry/DeferredRegister.java
  7. 6 0
      common/src/main/java/me/shedaniel/architectury/registry/Registries.java
  8. 13 0
      common/src/test/java/me/shedaniel/architectury/test/ConsoleMessageSink.java
  9. 9 0
      common/src/test/java/me/shedaniel/architectury/test/MessageSink.java
  10. 211 0
      common/src/test/java/me/shedaniel/architectury/test/TestMod.java
  11. 80 0
      common/src/test/java/me/shedaniel/architectury/test/client/ClientOverlayMessageSink.java
  12. 17 0
      common/src/test/resources/fabric.mod.json
  13. 2 0
      fabric/build.gradle
  14. 10 1
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinItemEntity.java
  15. 1 1
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayer.java
  16. 3 1
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java
  17. 29 0
      fabric/src/main/java/me/shedaniel/architectury/registry/fabric/CriteriaTriggersRegistryImpl.java
  18. 2 1
      fabric/src/main/resources/architectury.accessWidener
  19. 4 2
      forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java
  20. 29 0
      forge/src/main/java/me/shedaniel/architectury/registry/forge/CriteriaTriggersRegistryImpl.java

+ 7 - 2
build.gradle

@@ -1,5 +1,6 @@
 plugins {
 plugins {
-    id "architect-plugin" version "1.2.33"
+    id "architectury-plugin" version "1.3.41"
+    id "forgified-fabric-loom" version "0.5.22" apply false
     id "org.cadixdev.licenser" version "0.5.0"
     id "org.cadixdev.licenser" version "0.5.0"
     id "com.jfrog.bintray" version "1.8.4"
     id "com.jfrog.bintray" version "1.8.4"
     id "com.matthewprenger.cursegradle" version "1.4.0" apply false
     id "com.matthewprenger.cursegradle" version "1.4.0" apply false
@@ -13,11 +14,15 @@ architectury {
 
 
 subprojects {
 subprojects {
     apply plugin: "forgified-fabric-loom"
     apply plugin: "forgified-fabric-loom"
+
+    dependencies {
+        testCompile sourceSets.main.output
+    }
 }
 }
 
 
 allprojects {
 allprojects {
     apply plugin: "java"
     apply plugin: "java"
-    apply plugin: "architect-plugin"
+    apply plugin: "architectury-plugin"
     apply plugin: "org.cadixdev.licenser"
     apply plugin: "org.cadixdev.licenser"
 
 
     archivesBaseName = rootProject.archives_base_name
     archivesBaseName = rootProject.archives_base_name

+ 9 - 4
common/src/main/java/me/shedaniel/architectury/Architectury.java

@@ -20,8 +20,11 @@
 package me.shedaniel.architectury;
 package me.shedaniel.architectury;
 
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap;
+import org.apache.logging.log4j.LogManager;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.ApiStatus;
 
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 @ApiStatus.Internal
 @ApiStatus.Internal
@@ -37,16 +40,18 @@ public class Architectury {
     }
     }
     
     
     static {
     static {
-        String loader = null;
+        List<String> loader = new ArrayList<>();
         for (Map.Entry<String, String> entry : MOD_LOADERS.entrySet()) {
         for (Map.Entry<String, String> entry : MOD_LOADERS.entrySet()) {
             try {
             try {
                 Class.forName(entry.getKey(), false, Architectury.class.getClassLoader());
                 Class.forName(entry.getKey(), false, Architectury.class.getClassLoader());
-                loader = entry.getValue();
+                loader.add(entry.getValue());
                 break;
                 break;
             } catch (ClassNotFoundException ignored) {}
             } catch (ClassNotFoundException ignored) {}
         }
         }
-        if (loader == null)
+        if (loader.isEmpty())
             throw new IllegalStateException("No detected mod loader!");
             throw new IllegalStateException("No detected mod loader!");
-        MOD_LOADER = loader;
+        if (loader.size() >= 2)
+            LogManager.getLogger().error("Detected multiple mod loaders! Something is wrong on the classpath! " + String.join(", ", loader));
+        MOD_LOADER = loader.get(0);
     }
     }
 }
 }

+ 1 - 1
common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java

@@ -71,7 +71,7 @@ public interface PlayerEvent {
     }
     }
     
     
     interface CraftItem {
     interface CraftItem {
-        void craft(Player player, ItemStack smelted, Container inventory);
+        void craft(Player player, ItemStack constructed, Container inventory);
     }
     }
     
     
     interface SmeltItem {
     interface SmeltItem {

+ 1 - 1
common/src/main/java/me/shedaniel/architectury/registry/CreativeTabs.java

@@ -27,7 +27,7 @@ import net.minecraft.world.item.ItemStack;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 public final class CreativeTabs {
 public final class CreativeTabs {
-    public CreativeTabs() {}
+    private CreativeTabs() {}
     
     
     // I am sorry, fabric wants a resource location instead of the translation key for whatever reason
     // I am sorry, fabric wants a resource location instead of the translation key for whatever reason
     @ExpectPlatform
     @ExpectPlatform

+ 39 - 0
common/src/main/java/me/shedaniel/architectury/registry/CriteriaTriggersRegistry.java

@@ -0,0 +1,39 @@
+/*
+ * 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.registry;
+
+import me.shedaniel.architectury.ExpectPlatform;
+import net.minecraft.advancements.CriterionTrigger;
+
+public final class CriteriaTriggersRegistry {
+    private CriteriaTriggersRegistry() {}
+    
+    /**
+     * Invokes {@link net.minecraft.advancements.CriteriaTriggers#register(CriterionTrigger)}.
+     *
+     * @param trigger The trigger to register
+     * @param <T>     The type of trigger
+     * @return The trigger registered
+     */
+    @ExpectPlatform
+    public static <T extends CriterionTrigger<?>> T register(T trigger) {
+        throw new AssertionError();
+    }
+}

+ 38 - 15
common/src/main/java/me/shedaniel/architectury/registry/DeferredRegister.java

@@ -19,42 +19,65 @@
 
 
 package me.shedaniel.architectury.registry;
 package me.shedaniel.architectury.registry;
 
 
-import com.google.common.base.Objects;
 import net.minecraft.resources.ResourceKey;
 import net.minecraft.resources.ResourceKey;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.util.LazyLoadedValue;
 import net.minecraft.util.LazyLoadedValue;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 public class DeferredRegister<T> {
 public class DeferredRegister<T> {
+    @NotNull
     private final Supplier<Registries> registriesSupplier;
     private final Supplier<Registries> registriesSupplier;
+    @NotNull
     private final ResourceKey<net.minecraft.core.Registry<T>> key;
     private final ResourceKey<net.minecraft.core.Registry<T>> key;
     private final List<Entry<T>> entries = new ArrayList<>();
     private final List<Entry<T>> entries = new ArrayList<>();
     private boolean registered = false;
     private boolean registered = false;
+    @Nullable
+    private String modId;
+    
+    private DeferredRegister(@NotNull Supplier<Registries> registriesSupplier, @NotNull ResourceKey<net.minecraft.core.Registry<T>> key, @Nullable String modId) {
+        this.registriesSupplier = Objects.requireNonNull(registriesSupplier);
+        this.key = Objects.requireNonNull(key);
+        this.modId = modId;
+    }
     
     
-    private DeferredRegister(Supplier<Registries> registriesSupplier, ResourceKey<net.minecraft.core.Registry<T>> key) {
-        this.registriesSupplier = registriesSupplier;
-        this.key = key;
+    @NotNull
+    public static <T> DeferredRegister<T> create(@NotNull String modId, @NotNull ResourceKey<net.minecraft.core.Registry<T>> key) {
+        LazyLoadedValue<Registries> value = new LazyLoadedValue<>(() -> Registries.get(modId));
+        return new DeferredRegister<>(value::get, key, Objects.requireNonNull(modId));
     }
     }
     
     
     @NotNull
     @NotNull
-    public static <T> DeferredRegister<T> create(Registries registries, ResourceKey<net.minecraft.core.Registry<T>> key) {
-        return new DeferredRegister<>(() -> registries, key);
+    @Deprecated
+    public static <T> DeferredRegister<T> create(@NotNull Registries registries, @NotNull ResourceKey<net.minecraft.core.Registry<T>> key) {
+        return new DeferredRegister<>(() -> registries, key, null);
     }
     }
     
     
     @NotNull
     @NotNull
-    public static <T> DeferredRegister<T> create(Supplier<Registries> registries, ResourceKey<net.minecraft.core.Registry<T>> key) {
-        return new DeferredRegister<>(registries, key);
+    @Deprecated
+    public static <T> DeferredRegister<T> create(@NotNull Supplier<Registries> registries, @NotNull ResourceKey<net.minecraft.core.Registry<T>> key) {
+        return new DeferredRegister<>(registries, key, null);
     }
     }
     
     
     @NotNull
     @NotNull
-    public static <T> DeferredRegister<T> create(LazyLoadedValue<Registries> registries, ResourceKey<net.minecraft.core.Registry<T>> key) {
+    @Deprecated
+    public static <T> DeferredRegister<T> create(@NotNull LazyLoadedValue<Registries> registries, @NotNull ResourceKey<net.minecraft.core.Registry<T>> key) {
         return create(registries::get, key);
         return create(registries::get, key);
     }
     }
     
     
+    public RegistrySupplier<T> register(String id, Supplier<T> supplier) {
+        if (modId == null) {
+            throw new NullPointerException("You must create the deferred register with a mod id to register entries without the namespace!");
+        }
+        
+        return register(new ResourceLocation(modId, id), supplier);
+    }
+    
     public RegistrySupplier<T> register(ResourceLocation id, Supplier<T> supplier) {
     public RegistrySupplier<T> register(ResourceLocation id, Supplier<T> supplier) {
         Entry<T> entry = new Entry<>(id, supplier);
         Entry<T> entry = new Entry<>(id, supplier);
         this.entries.add(entry);
         this.entries.add(entry);
@@ -76,12 +99,12 @@ public class DeferredRegister<T> {
         }
         }
     }
     }
     
     
-    private class Entry<T> implements RegistrySupplier<T> {
+    private class Entry<R> implements RegistrySupplier<R> {
         private final ResourceLocation id;
         private final ResourceLocation id;
-        private final Supplier<T> supplier;
-        private RegistrySupplier<T> value;
+        private final Supplier<R> supplier;
+        private RegistrySupplier<R> value;
     
     
-        public Entry(ResourceLocation id, Supplier<T> supplier) {
+        public Entry(ResourceLocation id, Supplier<R> supplier) {
             this.id = id;
             this.id = id;
             this.supplier = supplier;
             this.supplier = supplier;
         }
         }
@@ -102,7 +125,7 @@ public class DeferredRegister<T> {
         }
         }
     
     
         @Override
         @Override
-        public T get() {
+        public R get() {
             if (isPresent()) {
             if (isPresent()) {
                 return value.get();
                 return value.get();
             }
             }
@@ -111,7 +134,7 @@ public class DeferredRegister<T> {
     
     
         @Override
         @Override
         public int hashCode() {
         public int hashCode() {
-            return Objects.hashCode(getRegistryId(), getId());
+            return com.google.common.base.Objects.hashCode(getRegistryId(), getId());
         }
         }
     
     
         @Override
         @Override

+ 6 - 0
common/src/main/java/me/shedaniel/architectury/registry/Registries.java

@@ -34,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap;
 public final class Registries {
 public final class Registries {
     private static final Map<String, Registries> REGISTRIES = new ConcurrentHashMap<>();
     private static final Map<String, Registries> REGISTRIES = new ConcurrentHashMap<>();
     private final RegistryProvider provider;
     private final RegistryProvider provider;
+    private final String modId;
     
     
     public static Registries get(String modId) {
     public static Registries get(String modId) {
         return REGISTRIES.computeIfAbsent(modId, Registries::new);
         return REGISTRIES.computeIfAbsent(modId, Registries::new);
@@ -41,6 +42,7 @@ public final class Registries {
     
     
     private Registries(String modId) {
     private Registries(String modId) {
         this.provider = _get(modId);
         this.provider = _get(modId);
+        this.modId = modId;
     }
     }
     
     
     public <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key) {
     public <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key) {
@@ -88,6 +90,10 @@ public final class Registries {
         throw new AssertionError();
         throw new AssertionError();
     }
     }
     
     
+    public String getModId() {
+        return modId;
+    }
+    
     @ApiStatus.Internal
     @ApiStatus.Internal
     public interface RegistryProvider {
     public interface RegistryProvider {
         <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key);
         <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key);

+ 13 - 0
common/src/test/java/me/shedaniel/architectury/test/ConsoleMessageSink.java

@@ -0,0 +1,13 @@
+package me.shedaniel.architectury.test;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class ConsoleMessageSink implements MessageSink {
+    protected final Logger logger = LogManager.getLogger("Architectury Test");
+    
+    @Override
+    public void accept(String message) {
+        logger.info(message);
+    }
+}

+ 9 - 0
common/src/test/java/me/shedaniel/architectury/test/MessageSink.java

@@ -0,0 +1,9 @@
+package me.shedaniel.architectury.test;
+
+public interface MessageSink {
+    void accept(String message);
+    
+    default void accept(String message, Object... args) {
+        accept(String.format(message, args));
+    }
+}

+ 211 - 0
common/src/test/java/me/shedaniel/architectury/test/TestMod.java

@@ -0,0 +1,211 @@
+package me.shedaniel.architectury.test;
+
+import me.shedaniel.architectury.event.events.*;
+import me.shedaniel.architectury.event.events.client.ClientChatEvent;
+import me.shedaniel.architectury.event.events.client.ClientLifecycleEvent;
+import me.shedaniel.architectury.event.events.client.ClientPlayerEvent;
+import me.shedaniel.architectury.hooks.ExplosionHooks;
+import me.shedaniel.architectury.platform.Platform;
+import me.shedaniel.architectury.test.client.ClientOverlayMessageSink;
+import me.shedaniel.architectury.utils.Env;
+import me.shedaniel.architectury.utils.EnvExecutor;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.core.Position;
+import net.minecraft.core.Vec3i;
+import net.minecraft.network.chat.TranslatableComponent;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.InteractionResultHolder;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+
+import java.util.Optional;
+
+public class TestMod {
+    public static final MessageSink SINK = EnvExecutor.getEnvSpecific(() -> ClientOverlayMessageSink::new, () -> ConsoleMessageSink::new);
+    
+    public static void initialize() {
+        debugEvents();
+        if (Platform.getEnvironment() == Env.CLIENT)
+            debugEventsClient();
+    }
+    
+    public static void debugEvents() {
+        ChatEvent.SERVER.register((player, message, component) -> {
+            SINK.accept("Server chat received: " + message);
+            return InteractionResultHolder.pass(component);
+        });
+        CommandPerformEvent.EVENT.register(event -> {
+            SINK.accept("Server command performed: " + event.getResults().getReader().getString());
+            return InteractionResult.PASS;
+        });
+        CommandRegistrationEvent.EVENT.register((dispatcher, selection) -> {
+            SINK.accept("Server commands registers");
+        });
+        EntityEvent.LIVING_DEATH.register((entity, source) -> {
+            if (entity instanceof Player) {
+                SINK.accept(entity.getScoreboardName() + " died to " + source.getMsgId() + logSide(entity.level));
+            }
+            return InteractionResult.PASS;
+        });
+        EntityEvent.LIVING_ATTACK.register((entity, source, amount) -> {
+            if (source.getDirectEntity() instanceof Player) {
+                SINK.accept(source.getDirectEntity().getScoreboardName() + " deals %.2f damage" + logSide(entity.level), amount);
+            }
+            return InteractionResult.PASS;
+        });
+        EntityEvent.ADD.register((entity, level) -> {
+            if (entity instanceof Player) {
+                SINK.accept(entity.getScoreboardName() + " was added to " + level.dimension().location().toString() + logSide(level));
+            }
+            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));
+        });
+        InteractionEvent.LEFT_CLICK_BLOCK.register((player, hand, pos, face) -> {
+            SINK.accept(player.getScoreboardName() + " left clicks " + toShortString(pos) + logSide(player.level));
+            return InteractionResult.PASS;
+        });
+        InteractionEvent.RIGHT_CLICK_BLOCK.register((player, hand, pos, face) -> {
+            SINK.accept(player.getScoreboardName() + " right clicks " + toShortString(pos) + logSide(player.level));
+            return InteractionResult.PASS;
+        });
+        InteractionEvent.RIGHT_CLICK_ITEM.register((player, hand) -> {
+            SINK.accept(player.getScoreboardName() + " uses " + (hand == InteractionHand.MAIN_HAND ? "main hand" : "off hand") + logSide(player.level));
+            return InteractionResultHolder.pass(player.getItemInHand(hand));
+        });
+        InteractionEvent.INTERACT_ENTITY.register((player, entity, hand) -> {
+            SINK.accept(player.getScoreboardName() + " interacts with " + entity.getScoreboardName() + " using " + (hand == InteractionHand.MAIN_HAND ? "main hand" : "off hand") + logSide(player.level));
+            return InteractionResult.PASS;
+        });
+        LifecycleEvent.SERVER_BEFORE_START.register(instance -> {
+            SINK.accept("Server ready to start");
+        });
+        LifecycleEvent.SERVER_STARTING.register(instance -> {
+            SINK.accept("Server starting");
+        });
+        LifecycleEvent.SERVER_STARTED.register(instance -> {
+            SINK.accept("Server started");
+        });
+        LifecycleEvent.SERVER_STOPPING.register(instance -> {
+            SINK.accept("Server stopping");
+        });
+        LifecycleEvent.SERVER_STOPPED.register(instance -> {
+            SINK.accept("Server stopped");
+        });
+        LifecycleEvent.SERVER_WORLD_LOAD.register(instance -> {
+            SINK.accept("Server world loaded: " + instance.dimension().location());
+        });
+        LifecycleEvent.SERVER_WORLD_UNLOAD.register(instance -> {
+            SINK.accept("Server world unloaded: " + instance.dimension().location());
+        });
+        LifecycleEvent.SERVER_WORLD_SAVE.register(instance -> {
+            SINK.accept("Server world saved: " + instance.dimension().location());
+        });
+        PlayerEvent.PLAYER_JOIN.register(player -> {
+            SINK.accept(player.getScoreboardName() + " joined" + logSide(player.level));
+        });
+        PlayerEvent.PLAYER_QUIT.register(player -> {
+            SINK.accept(player.getScoreboardName() + " quit" + logSide(player.level));
+        });
+        PlayerEvent.PLAYER_RESPAWN.register((player, conqueredEnd) -> {
+            if (!conqueredEnd) {
+                SINK.accept(player.getScoreboardName() + " respawns " + logSide(player.level));
+            }
+        });
+        PlayerEvent.PLAYER_CLONE.register((oldPlayer, newPlayer, wonGame) -> {
+            SINK.accept("Player cloned: " + newPlayer.getScoreboardName() + logSide(newPlayer.level));
+        });
+        PlayerEvent.PLAYER_ADVANCEMENT.register((player, advancement) -> {
+            SINK.accept(player.getScoreboardName() + " was awarded with %s" + logSide(player.level), advancement.getChatComponent().getString());
+        });
+        PlayerEvent.CRAFT_ITEM.register((player, constructed, inventory) -> {
+            SINK.accept(player.getScoreboardName() + " crafts " + new TranslatableComponent(constructed.getDescriptionId()).getString() + logSide(player.level));
+        });
+        PlayerEvent.SMELT_ITEM.register((player, smelted) -> {
+            SINK.accept(player.getScoreboardName() + " smelts " + new TranslatableComponent(smelted.getDescriptionId()).getString() + logSide(player.level));
+        });
+        PlayerEvent.PICKUP_ITEM_POST.register((player, entity, stack) -> {
+            SINK.accept(player.getScoreboardName() + " picks up " + new TranslatableComponent(stack.getDescriptionId()).getString() + logSide(player.level));
+        });
+        PlayerEvent.DROP_ITEM.register((player, entity) -> {
+            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));
+        });
+        PlayerEvent.CLOSE_MENU.register((player, menu) -> {
+            SINK.accept(player.getScoreboardName() + " closes " + toSimpleName(menu) + logSide(player.level));
+        });
+    }
+    
+    public static String toShortString(Vec3i pos) {
+        return pos.getX() + ", " + pos.getY() + ", " + pos.getZ();
+    }
+    
+    public static String toShortString(Position pos) {
+        return pos.x() + ", " + pos.y() + ", " + pos.z();
+    }
+    
+    public static String logSide(Level level) {
+        if (level.isClientSide())
+            return " (client)";
+        return " (server)";
+    }
+    
+    @Environment(EnvType.CLIENT)
+    public static void debugEventsClient() {
+        ClientChatEvent.CLIENT.register(message -> {
+            SINK.accept("Client chat sent: " + message);
+            return InteractionResultHolder.pass(message);
+        });
+        ClientChatEvent.CLIENT_RECEIVED.register((type, message, sender) -> {
+            SINK.accept("Client chat received: " + message.getString());
+            return InteractionResultHolder.pass(message);
+        });
+        ClientLifecycleEvent.CLIENT_WORLD_LOAD.register(world -> {
+            SINK.accept("Client world loaded: " + world.dimension().location().toString());
+        });
+        ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(player -> {
+            SINK.accept(player.getScoreboardName() + " joined (client)");
+        });
+        ClientPlayerEvent.CLIENT_PLAYER_QUIT.register(player -> {
+            SINK.accept(player.getScoreboardName() + " quit (client)");
+        });
+        ClientPlayerEvent.CLIENT_PLAYER_RESPAWN.register((oldPlayer, newPlayer) -> {
+            SINK.accept(newPlayer.getScoreboardName() + " respawned (client)");
+        });
+        GuiEvent.INIT_PRE.register((screen, widgets, children) -> {
+            SINK.accept(toSimpleName(screen) + " initializes");
+            return InteractionResult.PASS;
+        });
+        InteractionEvent.CLIENT_LEFT_CLICK_AIR.register((player, hand) -> {
+            SINK.accept(player.getScoreboardName() + " left clicks air" + logSide(player.level));
+        });
+        InteractionEvent.CLIENT_RIGHT_CLICK_AIR.register((player, hand) -> {
+            SINK.accept(player.getScoreboardName() + " right clicks air" + logSide(player.level));
+        });
+        RecipeUpdateEvent.EVENT.register(recipeManager -> {
+            SINK.accept("Client recipes received");
+        });
+        TextureStitchEvent.POST.register(atlas -> {
+            SINK.accept("Client texture stitched: " + atlas.location());
+        });
+    }
+    
+    private static String toSimpleName(Object o) {
+        return o.getClass().getSimpleName() + "@" + Integer.toHexString(o.hashCode());
+    }
+}

+ 80 - 0
common/src/test/java/me/shedaniel/architectury/test/client/ClientOverlayMessageSink.java

@@ -0,0 +1,80 @@
+package me.shedaniel.architectury.test.client;
+
+import com.google.common.collect.Lists;
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.PoseStack;
+import me.shedaniel.architectury.event.events.GuiEvent;
+import me.shedaniel.architectury.test.ConsoleMessageSink;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.Util;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiComponent;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.util.Mth;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+@Environment(EnvType.CLIENT)
+public class ClientOverlayMessageSink extends ConsoleMessageSink {
+    private final List<Message> messages = Collections.synchronizedList(Lists.newArrayList());
+    
+    public ClientOverlayMessageSink() {
+        GuiEvent.RENDER_POST.register((screen, matrices, mouseX, mouseY, delta) -> render(matrices, delta));
+        GuiEvent.RENDER_HUD.register((matrices, tickDelta) -> {
+            if (Minecraft.getInstance().screen == null && !Minecraft.getInstance().options.renderDebug) {
+                render(matrices, tickDelta);
+            }
+        });
+    }
+    
+    @Override
+    public void accept(String message) {
+        super.accept(message);
+        messages.add(0, new Message(new TextComponent(message), Util.getMillis()));
+    }
+    
+    public void render(PoseStack matrices, float delta) {
+        Minecraft minecraft = Minecraft.getInstance();
+        long currentMills = Util.getMillis();
+        int lineHeight = minecraft.font.lineHeight;
+        
+        synchronized (messages) {
+            Iterator<Message> messageIterator = messages.iterator();
+            int y = 1;
+    
+            RenderSystem.enableBlend();
+    
+            while (messageIterator.hasNext()) {
+                Message message = messageIterator.next();
+                int timeExisted = (int) (currentMills - message.created);
+        
+                if (timeExisted >= 5000) {
+                    messageIterator.remove();
+                } else {
+                    int textWidth = minecraft.font.width(message.text);
+                    int alpha = (int) Mth.clamp((5000 - timeExisted) / 5000f * 400f + 8, 0, 255);
+                    GuiComponent.fill(matrices, 0, y - 1, 2 + textWidth + 1, y + lineHeight - 1, 0x505050 + ((alpha * 144 / 255) << 24));
+                    minecraft.font.draw(matrices, message.text, 1, y, 0xE0E0E0 + (alpha << 24));
+                    y += lineHeight;
+                }
+            }
+        }
+        
+        RenderSystem.disableAlphaTest();
+        RenderSystem.disableBlend();
+    }
+    
+    private static class Message {
+        private final Component text;
+        private final long created;
+        
+        public Message(Component text, long created) {
+            this.text = text;
+            this.created = created;
+        }
+    }
+}

+ 17 - 0
common/src/test/resources/fabric.mod.json

@@ -0,0 +1,17 @@
+{
+  "schemaVersion": 1,
+  "id": "architectury-test",
+  "version": "${version}",
+  "name": "Architectury Test",
+  "description": "A intermediary api aimed to ease developing multiplatform mods.",
+  "authors": [
+    "shedaniel"
+  ],
+  "license": "LGPL-3",
+  "environment": "*",
+  "entrypoints": {
+    "main": [
+      "me.shedaniel.architectury.test.TestMod::initialize"
+    ]
+  }
+}

+ 2 - 0
fabric/build.gradle

@@ -40,6 +40,8 @@ dependencies {
     shadow(project(path: ":common", configuration: "transformed")) {
     shadow(project(path: ":common", configuration: "transformed")) {
         transitive = false
         transitive = false
     }
     }
+
+    testCompile project(":common").sourceSets.test.output
 }
 }
 
 
 processResources {
 processResources {

+ 10 - 1
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinItemEntity.java

@@ -26,6 +26,7 @@ import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -35,9 +36,13 @@ public abstract class MixinItemEntity {
     @Shadow
     @Shadow
     public abstract ItemStack getItem();
     public abstract ItemStack getItem();
     
     
+    @Unique
+    private ItemStack cache;
+    
     @Inject(method = "playerTouch",
     @Inject(method = "playerTouch",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getCount()I"), cancellable = true)
             at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getCount()I"), cancellable = true)
     private void prePickup(Player player, CallbackInfo ci) {
     private void prePickup(Player player, CallbackInfo ci) {
+        cache = getItem().copy();
         InteractionResult canPickUp = PlayerEvent.PICKUP_ITEM_PRE.invoker().canPickup(player, (ItemEntity) (Object) this, getItem());
         InteractionResult canPickUp = PlayerEvent.PICKUP_ITEM_PRE.invoker().canPickup(player, (ItemEntity) (Object) this, getItem());
         if (canPickUp == InteractionResult.FAIL) {
         if (canPickUp == InteractionResult.FAIL) {
             ci.cancel();
             ci.cancel();
@@ -47,6 +52,10 @@ public abstract class MixinItemEntity {
     @Inject(method = "playerTouch",
     @Inject(method = "playerTouch",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;take(Lnet/minecraft/world/entity/Entity;I)V"))
             at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;take(Lnet/minecraft/world/entity/Entity;I)V"))
     private void pickup(Player player, CallbackInfo ci) {
     private void pickup(Player player, CallbackInfo ci) {
-        PlayerEvent.PICKUP_ITEM_POST.invoker().pickup(player, (ItemEntity) (Object) this, getItem());
+        if (cache != null) {
+            PlayerEvent.PICKUP_ITEM_POST.invoker().pickup(player, (ItemEntity) (Object) this, cache);
+        }
+        
+        this.cache = null;
     }
     }
 }
 }

+ 1 - 1
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayer.java

@@ -48,7 +48,7 @@ public class MixinPlayer {
     
     
     @Inject(method = "drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At("RETURN"), cancellable = true)
     @Inject(method = "drop(Lnet/minecraft/world/item/ItemStack;ZZ)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At("RETURN"), cancellable = true)
     private void drop(ItemStack itemStack, boolean bl, boolean bl2, CallbackInfoReturnable<ItemEntity> cir) {
     private void drop(ItemStack itemStack, boolean bl, boolean bl2, CallbackInfoReturnable<ItemEntity> cir) {
-        if (PlayerEvent.DROP_ITEM.invoker().drop((Player) (Object) this, cir.getReturnValue()) == InteractionResult.FAIL) {
+        if (cir.getReturnValue() != null && PlayerEvent.DROP_ITEM.invoker().drop((Player) (Object) this, cir.getReturnValue()) == InteractionResult.FAIL) {
             cir.setReturnValue(null);
             cir.setReturnValue(null);
         }
         }
     }
     }

+ 3 - 1
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java

@@ -44,7 +44,9 @@ public class MixinMinecraft {
     @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V",
     @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/chat/NarratorChatListener;clear()V"))
             at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/chat/NarratorChatListener;clear()V"))
     private void handleLogin(Screen screen, CallbackInfo ci) {
     private void handleLogin(Screen screen, CallbackInfo ci) {
-        ClientPlayerEvent.CLIENT_PLAYER_QUIT.invoker().quit(player);
+        if (player != null) {
+            ClientPlayerEvent.CLIENT_PLAYER_QUIT.invoker().quit(player);
+        }
     }
     }
     
     
     @Inject(method = "startUseItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;isEmpty()Z", ordinal = 1),
     @Inject(method = "startUseItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;isEmpty()Z", ordinal = 1),

+ 29 - 0
fabric/src/main/java/me/shedaniel/architectury/registry/fabric/CriteriaTriggersRegistryImpl.java

@@ -0,0 +1,29 @@
+/*
+ * 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.registry.fabric;
+
+import net.minecraft.advancements.CriteriaTriggers;
+import net.minecraft.advancements.CriterionTrigger;
+
+public class CriteriaTriggersRegistryImpl {
+    public static <T extends CriterionTrigger<?>> T register(T trigger) {
+        return CriteriaTriggers.register(trigger);
+    }
+}

+ 2 - 1
fabric/src/main/resources/architectury.accessWidener

@@ -51,4 +51,5 @@ accessible method net/minecraft/world/entity/Entity getEncodeId ()Ljava/lang/Str
 accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
 accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
 mutable field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
 mutable field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
 accessible field net/minecraft/world/item/DyeColor textureDiffuseColor I
 accessible field net/minecraft/world/item/DyeColor textureDiffuseColor I
-accessible method net/minecraft/world/entity/player/Player closeContainer ()V
+accessible method net/minecraft/world/entity/player/Player closeContainer ()V
+accessible method net/minecraft/advancements/CriteriaTriggers register (Lnet/minecraft/advancements/CriterionTrigger;)Lnet/minecraft/advancements/CriterionTrigger;

+ 4 - 2
forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplClient.java

@@ -91,8 +91,10 @@ public class EventHandlerImplClient {
     
     
     @SubscribeEvent
     @SubscribeEvent
     public static void event(RenderGameOverlayEvent.Text event) {
     public static void event(RenderGameOverlayEvent.Text event) {
-        GuiEvent.DEBUG_TEXT_LEFT.invoker().gatherText(event.getLeft());
-        GuiEvent.DEBUG_TEXT_RIGHT.invoker().gatherText(event.getRight());
+        if (Minecraft.getInstance().options.renderDebug) {
+            GuiEvent.DEBUG_TEXT_LEFT.invoker().gatherText(event.getLeft());
+            GuiEvent.DEBUG_TEXT_RIGHT.invoker().gatherText(event.getRight());
+        }
     }
     }
     
     
     @SubscribeEvent
     @SubscribeEvent

+ 29 - 0
forge/src/main/java/me/shedaniel/architectury/registry/forge/CriteriaTriggersRegistryImpl.java

@@ -0,0 +1,29 @@
+/*
+ * 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.registry.forge;
+
+import net.minecraft.advancements.CriteriaTriggers;
+import net.minecraft.advancements.CriterionTrigger;
+
+public class CriteriaTriggersRegistryImpl {
+    public static <T extends CriterionTrigger<?>> T register(T trigger) {
+        return CriteriaTriggers.register(trigger);
+    }
+}