Ver código fonte

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

shedaniel 4 anos atrás
pai
commit
21f4243b2a
20 arquivos alterados com 511 adições e 29 exclusões
  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 {
-    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 "com.jfrog.bintray" version "1.8.4"
     id "com.matthewprenger.cursegradle" version "1.4.0" apply false
@@ -13,11 +14,15 @@ architectury {
 
 subprojects {
     apply plugin: "forgified-fabric-loom"
+
+    dependencies {
+        testCompile sourceSets.main.output
+    }
 }
 
 allprojects {
     apply plugin: "java"
-    apply plugin: "architect-plugin"
+    apply plugin: "architectury-plugin"
     apply plugin: "org.cadixdev.licenser"
 
     archivesBaseName = rootProject.archives_base_name

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

@@ -20,8 +20,11 @@
 package me.shedaniel.architectury;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.logging.log4j.LogManager;
 import org.jetbrains.annotations.ApiStatus;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 @ApiStatus.Internal
@@ -37,16 +40,18 @@ public class Architectury {
     }
     
     static {
-        String loader = null;
+        List<String> loader = new ArrayList<>();
         for (Map.Entry<String, String> entry : MOD_LOADERS.entrySet()) {
             try {
                 Class.forName(entry.getKey(), false, Architectury.class.getClassLoader());
-                loader = entry.getValue();
+                loader.add(entry.getValue());
                 break;
             } catch (ClassNotFoundException ignored) {}
         }
-        if (loader == null)
+        if (loader.isEmpty())
             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 {
-        void craft(Player player, ItemStack smelted, Container inventory);
+        void craft(Player player, ItemStack constructed, Container inventory);
     }
     
     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;
 
 public final class CreativeTabs {
-    public CreativeTabs() {}
+    private CreativeTabs() {}
     
     // I am sorry, fabric wants a resource location instead of the translation key for whatever reason
     @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;
 
-import com.google.common.base.Objects;
 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.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Supplier;
 
 public class DeferredRegister<T> {
+    @NotNull
     private final Supplier<Registries> registriesSupplier;
+    @NotNull
     private final ResourceKey<net.minecraft.core.Registry<T>> key;
     private final List<Entry<T>> entries = new ArrayList<>();
     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
-    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
-    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
-    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);
     }
     
+    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) {
         Entry<T> entry = new Entry<>(id, supplier);
         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 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.supplier = supplier;
         }
@@ -102,7 +125,7 @@ public class DeferredRegister<T> {
         }
     
         @Override
-        public T get() {
+        public R get() {
             if (isPresent()) {
                 return value.get();
             }
@@ -111,7 +134,7 @@ public class DeferredRegister<T> {
     
         @Override
         public int hashCode() {
-            return Objects.hashCode(getRegistryId(), getId());
+            return com.google.common.base.Objects.hashCode(getRegistryId(), getId());
         }
     
         @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 {
     private static final Map<String, Registries> REGISTRIES = new ConcurrentHashMap<>();
     private final RegistryProvider provider;
+    private final String modId;
     
     public static Registries get(String modId) {
         return REGISTRIES.computeIfAbsent(modId, Registries::new);
@@ -41,6 +42,7 @@ public final class Registries {
     
     private Registries(String modId) {
         this.provider = _get(modId);
+        this.modId = modId;
     }
     
     public <T> Registry<T> get(ResourceKey<net.minecraft.core.Registry<T>> key) {
@@ -88,6 +90,10 @@ public final class Registries {
         throw new AssertionError();
     }
     
+    public String getModId() {
+        return modId;
+    }
+    
     @ApiStatus.Internal
     public interface RegistryProvider {
         <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")) {
         transitive = false
     }
+
+    testCompile project(":common").sourceSets.test.output
 }
 
 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 org.spongepowered.asm.mixin.Mixin;
 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.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -35,9 +36,13 @@ public abstract class MixinItemEntity {
     @Shadow
     public abstract ItemStack getItem();
     
+    @Unique
+    private ItemStack cache;
+    
     @Inject(method = "playerTouch",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getCount()I"), cancellable = true)
     private void prePickup(Player player, CallbackInfo ci) {
+        cache = getItem().copy();
         InteractionResult canPickUp = PlayerEvent.PICKUP_ITEM_PRE.invoker().canPickup(player, (ItemEntity) (Object) this, getItem());
         if (canPickUp == InteractionResult.FAIL) {
             ci.cancel();
@@ -47,6 +52,10 @@ public abstract class MixinItemEntity {
     @Inject(method = "playerTouch",
             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) {
-        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)
     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);
         }
     }

+ 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",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/chat/NarratorChatListener;clear()V"))
     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),

+ 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;
 mutable field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
 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
     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

+ 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);
+    }
+}