diff --git a/common/src/main/java/dev/architectury/event/events/client/ClientSystemMessageEvent.java b/common/src/main/java/dev/architectury/event/events/client/ClientSystemMessageEvent.java new file mode 100644 index 00000000..c14b1cde --- /dev/null +++ b/common/src/main/java/dev/architectury/event/events/client/ClientSystemMessageEvent.java @@ -0,0 +1,48 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * 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 dev.architectury.event.events.client; + +import dev.architectury.event.CompoundEventResult; +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.network.chat.Component; + +@Environment(EnvType.CLIENT) +public interface ClientSystemMessageEvent { + /** + * @see Received#process(Component) + */ + Event RECEIVED = EventFactory.createCompoundEventResult(); + + @Environment(EnvType.CLIENT) + interface Received { + /** + * Event to intercept the receiving of a system message. + * Invoked as soon as the client receives the system message packet. + * + * @param message The chat message. + * @return A {@link CompoundEventResult} determining the outcome of the event, + * if an outcome is set, the received message is overridden. + */ + CompoundEventResult process(Component message); + } +} diff --git a/common/src/main/java/dev/architectury/event/events/client/ClientTextureStitchEvent.java b/common/src/main/java/dev/architectury/event/events/client/ClientTextureStitchEvent.java index 1aa8ad23..e3cf69b6 100644 --- a/common/src/main/java/dev/architectury/event/events/client/ClientTextureStitchEvent.java +++ b/common/src/main/java/dev/architectury/event/events/client/ClientTextureStitchEvent.java @@ -25,19 +25,22 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; -import java.util.function.Consumer; +import java.util.function.BiConsumer; @Environment(EnvType.CLIENT) public interface ClientTextureStitchEvent { /** - * @see Pre#stitch(TextureAtlas, Consumer) + * @see Pre#stitch(TextureAtlas, ResourceManager, BiConsumer) */ Event
 PRE = EventFactory.createLoop();
+    
     /**
      * @see Post#stitch(TextureAtlas)
      */
-    Event POST = EventFactory.createLoop();
+//    Event POST = EventFactory.createLoop();
     
     @Environment(EnvType.CLIENT)
     interface Pre {
@@ -45,10 +48,11 @@ public interface ClientTextureStitchEvent {
          * Invoked before the texture atlas is stitched together.
          * Equivalent to Forge's {@code TextureStitchEvent.Pre} event.
          *
-         * @param atlas       The TextureAtlas.
-         * @param spriteAdder A consumer where you can add your own sprites to be stitched.
+         * @param atlas           The TextureAtlas.
+         * @param resourceManager The resource manager.
+         * @param spriteAdder     A consumer where you can add your own sprites to be stitched.
          */
-        void stitch(TextureAtlas atlas, Consumer spriteAdder);
+        void stitch(TextureAtlas atlas, ResourceManager resourceManager, BiConsumer spriteAdder);
     }
     
     @Environment(EnvType.CLIENT)
diff --git a/common/src/main/java/dev/architectury/networking/NetworkManager.java b/common/src/main/java/dev/architectury/networking/NetworkManager.java
index d9f6ed70..58c6c4ed 100644
--- a/common/src/main/java/dev/architectury/networking/NetworkManager.java
+++ b/common/src/main/java/dev/architectury/networking/NetworkManager.java
@@ -29,6 +29,7 @@ import net.fabricmc.api.EnvType;
 import net.fabricmc.api.Environment;
 import net.minecraft.network.FriendlyByteBuf;
 import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.world.entity.Entity;
@@ -107,7 +108,7 @@ public final class NetworkManager {
      * @see Entity#getAddEntityPacket()
      */
     @ExpectPlatform
-    public static Packet createAddEntityPacket(Entity entity) {
+    public static Packet createAddEntityPacket(Entity entity) {
         throw new AssertionError();
     }
     
diff --git a/common/src/main/java/dev/architectury/registry/CreativeTabRegistry.java b/common/src/main/java/dev/architectury/registry/CreativeTabRegistry.java
index 2ce054c3..7a36598c 100644
--- a/common/src/main/java/dev/architectury/registry/CreativeTabRegistry.java
+++ b/common/src/main/java/dev/architectury/registry/CreativeTabRegistry.java
@@ -21,9 +21,11 @@ package dev.architectury.registry;
 
 import dev.architectury.injectables.annotations.ExpectPlatform;
 import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.flag.FeatureFlagSet;
 import net.minecraft.world.item.CreativeModeTab;
 import net.minecraft.world.item.ItemStack;
 
+import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 public final class CreativeTabRegistry {
@@ -32,7 +34,7 @@ public final class CreativeTabRegistry {
     
     // I am sorry, fabric wants a resource location instead of the translation key for whatever reason
     @ExpectPlatform
-    public static CreativeModeTab create(ResourceLocation name, Supplier icon) {
+    public static CreativeModeTab create(ResourceLocation name, Supplier icon, BiConsumer filler) {
         throw new AssertionError();
     }
 }
diff --git a/common/src/main/resources/architectury.accessWidener b/common/src/main/resources/architectury.accessWidener
index 52efcae5..ad1281de 100644
--- a/common/src/main/resources/architectury.accessWidener
+++ b/common/src/main/resources/architectury.accessWidener
@@ -129,6 +129,7 @@ transitive-accessible method net/minecraft/client/renderer/RenderType create (Lj
 transitive-accessible class net/minecraft/client/renderer/RenderType$CompositeState
 transitive-accessible class net/minecraft/client/renderer/RenderType$CompositeRenderType
 transitive-accessible class net/minecraft/client/renderer/RenderType$OutlineProperty
+accessible class net/minecraft/client/resources/model/AtlasSet$AtlasEntry
 accessible field net/minecraft/world/item/SpawnEggItem BY_ID Ljava/util/Map;
 accessible field net/minecraft/world/item/SpawnEggItem defaultType Lnet/minecraft/world/entity/EntityType;
 mutable field net/minecraft/world/item/SpawnEggItem defaultType Lnet/minecraft/world/entity/EntityType;
@@ -136,6 +137,8 @@ accessible field net/minecraft/world/item/RecordItem BY_NAME Ljava/util/Map;
 accessible field net/minecraft/client/particle/ParticleEngine textureAtlas Lnet/minecraft/client/renderer/texture/TextureAtlas;
 accessible class net/minecraft/client/particle/ParticleEngine$MutableSpriteSet
 accessible field net/minecraft/client/particle/ParticleEngine$MutableSpriteSet sprites Ljava/util/List;
+transitive-accessible class net/minecraft/world/item/CreativeModeTab$Output
+transitive-accessible class net/minecraft/world/item/CreativeModeTab$TabVisibility
 
 ##############################
 # This section is generated automatically with Gradle task generateAccessWidener!!!
@@ -161,6 +164,7 @@ transitive-accessible method net/minecraft/world/level/block/BigDripleafBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/BlastFurnaceBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/BushBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
+transitive-accessible method net/minecraft/world/level/block/ButtonBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;IZLnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundEvent;)V
 transitive-accessible method net/minecraft/world/level/block/CactusBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/CakeBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/CandleCakeBlock  (Lnet/minecraft/world/level/block/Block;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
@@ -178,7 +182,7 @@ transitive-accessible method net/minecraft/world/level/block/CrossCollisionBlock
 transitive-accessible method net/minecraft/world/level/block/DeadBushBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/DirtPathBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/DispenserBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
-transitive-accessible method net/minecraft/world/level/block/DoorBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
+transitive-accessible method net/minecraft/world/level/block/DoorBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundEvent;)V
 transitive-accessible method net/minecraft/world/level/block/EnchantmentTableBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/EndGatewayBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/EndPortalBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
@@ -210,7 +214,7 @@ transitive-accessible method net/minecraft/world/level/block/PipeBlock  (F
 transitive-accessible method net/minecraft/world/level/block/PlayerHeadBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/PlayerWallHeadBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/PoweredRailBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
-transitive-accessible method net/minecraft/world/level/block/PressurePlateBlock  (Lnet/minecraft/world/level/block/PressurePlateBlock$Sensitivity;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
+transitive-accessible method net/minecraft/world/level/block/PressurePlateBlock  (Lnet/minecraft/world/level/block/PressurePlateBlock$Sensitivity;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundEvent;)V
 transitive-accessible method net/minecraft/world/level/block/PumpkinBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/RailBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/RedstoneTorchBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
@@ -231,21 +235,19 @@ transitive-accessible method net/minecraft/world/level/block/SpawnerBlock 
 transitive-accessible method net/minecraft/world/level/block/SpongeBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/StairBlock  (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/StemBlock  (Lnet/minecraft/world/level/block/StemGrownBlock;Ljava/util/function/Supplier;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
-transitive-accessible method net/minecraft/world/level/block/StoneButtonBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/StructureBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/StructureVoidBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/SugarCaneBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/TallGrassBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/TorchBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/core/particles/ParticleOptions;)V
-transitive-accessible method net/minecraft/world/level/block/TrapDoorBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
+transitive-accessible method net/minecraft/world/level/block/TrapDoorBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundEvent;)V
 transitive-accessible method net/minecraft/world/level/block/WallSkullBlock  (Lnet/minecraft/world/level/block/SkullBlock$Type;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/WallTorchBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/core/particles/ParticleOptions;)V
 transitive-accessible method net/minecraft/world/level/block/WaterlilyBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
-transitive-accessible method net/minecraft/world/level/block/WeightedPressurePlateBlock  (ILnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
+transitive-accessible method net/minecraft/world/level/block/WeightedPressurePlateBlock  (ILnet/minecraft/world/level/block/state/BlockBehaviour$Properties;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundEvent;)V
 transitive-accessible method net/minecraft/world/level/block/WetSpongeBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/WitherSkullBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/WitherWallSkullBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
-transitive-accessible method net/minecraft/world/level/block/WoodButtonBlock  (Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 transitive-accessible method net/minecraft/world/level/block/WoolCarpetBlock  (Lnet/minecraft/world/item/DyeColor;Lnet/minecraft/world/level/block/state/BlockBehaviour$Properties;)V
 
 # RenderStateShard fields
diff --git a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
index 389d32e3..1c377020 100644
--- a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
+++ b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java
@@ -41,7 +41,6 @@ import net.fabricmc.fabric.api.event.player.UseItemCallback;
 import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
 import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
 import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
-import net.minecraft.network.chat.PlayerChatMessage;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -88,7 +87,7 @@ public class EventHandlerImpl {
             return CompletableFuture.completedFuture(chatComponent.get());
         });
         ServerMessageEvents.ALLOW_CHAT_MESSAGE.register((message, sender, params) -> {
-            return !ChatEvent.RECEIVED.invoker().received(sender, message.serverContent()).isFalse();
+            return !ChatEvent.RECEIVED.invoker().received(sender, message.decoratedContent()).isFalse();
         });
     }
     
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinTextureAtlas.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinAtlasSet.java
similarity index 55%
rename from fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinTextureAtlas.java
rename to fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinAtlasSet.java
index 8105a8df..818737bc 100644
--- a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinTextureAtlas.java
+++ b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinAtlasSet.java
@@ -21,30 +21,36 @@ package dev.architectury.mixin.fabric.client;
 
 import dev.architectury.event.events.client.ClientTextureStitchEvent;
 import net.minecraft.client.renderer.texture.TextureAtlas;
+import net.minecraft.client.resources.model.AtlasSet;
 import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.packs.resources.ResourceManager;
-import net.minecraft.util.profiling.ProfilerFiller;
+import net.minecraft.server.packs.resources.Resource;
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
-import java.util.Set;
-import java.util.stream.Stream;
+import java.util.HashMap;
+import java.util.Map;
 
-@Mixin(TextureAtlas.class)
-public class MixinTextureAtlas {
-    @Inject(method = "prepareToStitch",
-            at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", ordinal = 0,
-                    shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
-    private void preStitch(ResourceManager resourceManager, Stream stream, ProfilerFiller profilerFiller, int i, CallbackInfoReturnable cir, Set set) {
-        ClientTextureStitchEvent.PRE.invoker().stitch((TextureAtlas) (Object) this, set::add);
-    }
+@Mixin(AtlasSet.AtlasEntry.class)
+public class MixinAtlasSet {
+    @Mutable
+    @Shadow
+    @Final
+    AtlasSet.ResourceLister resourceLister;
     
-    @Inject(method = "reload", at = @At("RETURN"))
-    private void postStitch(TextureAtlas.Preparations preparations, CallbackInfo ci) {
-        ClientTextureStitchEvent.POST.invoker().stitch((TextureAtlas) (Object) this);
+    @Inject(method = "", at = @At(value = "RETURN"), locals = LocalCapture.CAPTURE_FAILHARD)
+    private void preStitch(TextureAtlas textureAtlas, AtlasSet.ResourceLister resourceLister, CallbackInfo ci) {
+        AtlasSet.ResourceLister tmp = this.resourceLister;
+        this.resourceLister = resourceManager -> {
+            Map map = new HashMap<>();
+            ClientTextureStitchEvent.PRE.invoker().stitch(textureAtlas, resourceManager, map::put);
+            map.putAll(tmp.apply(resourceManager));
+            return map;
+        };
     }
 }
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener.java
new file mode 100644
index 00000000..1c30077e
--- /dev/null
+++ b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener.java
@@ -0,0 +1,104 @@
+/*
+ * This file is part of architectury.
+ * Copyright (C) 2020, 2021, 2022 architectury
+ *
+ * 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 dev.architectury.mixin.fabric.client;
+
+import com.mojang.authlib.GameProfile;
+import dev.architectury.event.events.client.ClientChatEvent;
+import dev.architectury.event.events.client.ClientSystemMessageEvent;
+import net.minecraft.client.multiplayer.chat.ChatListener;
+import net.minecraft.network.chat.ChatType;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.PlayerChatMessage;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArgs;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
+
+import java.util.Objects;
+
+@Mixin(ChatListener.class)
+public class MixinChatListener {
+    @Unique
+    ChatType.Bound boundChatType;
+    @Unique
+    private ThreadLocal cancelNextChat = new ThreadLocal<>();
+    @Unique
+    private ThreadLocal cancelNextSystem = new ThreadLocal<>();
+    
+    @Inject(method = "handlePlayerChatMessage", at = @At(value = "INVOKE", target = "Ljava/time/Instant;now()Ljava/time/Instant;"))
+    private void handlePlayerChatMessage(PlayerChatMessage playerChatMessage, GameProfile gameProfile, ChatType.Bound bound, CallbackInfo ci) {
+        this.boundChatType = bound;
+    }
+    
+    @ModifyVariable(method = "handlePlayerChatMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/PlayerChatMessage;signature()Lnet/minecraft/network/chat/MessageSignature;"))
+    private Component modifyMessage(Component value) {
+        cancelNextChat.remove();
+        var process = ClientChatEvent.RECEIVED.invoker().process(boundChatType, value);
+        this.boundChatType = null;
+        if (process.isPresent()) {
+            if (process.isFalse()) {
+                cancelNextChat.set(value);
+            } else if (process.object() != null) {
+                return process.object();
+            }
+        }
+        
+        return value;
+    }
+    
+    @Inject(method = "handlePlayerChatMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/chat/ChatListener;handleMessage(Lnet/minecraft/network/chat/MessageSignature;Ljava/util/function/BooleanSupplier;)V"),
+            cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
+    private void handleChatPre(PlayerChatMessage playerChatMessage, GameProfile gameProfile, ChatType.Bound bound, CallbackInfo ci, boolean onlyShowSecureChat, PlayerChatMessage filtered, Component component) {
+        if (Objects.equals(cancelNextChat.get(), component)) {
+            ci.cancel();
+        }
+        
+        cancelNextChat.remove();
+    }
+    
+    @ModifyArgs(method = "handleSystemMessage", at = @At(value = "HEAD"))
+    private void modifySystemMessage(Args args) {
+        cancelNextSystem.remove();
+        Component message = args.get(0);
+        var process = ClientSystemMessageEvent.RECEIVED.invoker().process(message);
+        if (process.isPresent()) {
+            if (process.isFalse()) {
+                cancelNextSystem.set(message);
+            } else if (process.object() != null) {
+                args.set(0, process.object());
+            }
+        }
+    }
+    
+    @Inject(method = "handleSystemMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Options;hideMatchedNames()Lnet/minecraft/client/OptionInstance;"),
+            cancellable = true)
+    private void handleSystemMessage(Component component, boolean bl, CallbackInfo ci) {
+        if (Objects.equals(cancelNextSystem.get(), component)) {
+            ci.cancel();
+        }
+        
+        cancelNextSystem.remove();
+    }
+}
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_1.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_1.java
deleted file mode 100644
index 23344a3e..00000000
--- a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_1.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * This file is part of architectury.
- * Copyright (C) 2020, 2021, 2022 architectury
- *
- * 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 dev.architectury.mixin.fabric.client;
-
-import dev.architectury.event.events.client.ClientChatEvent;
-import net.minecraft.network.chat.ChatType;
-import net.minecraft.network.chat.Component;
-import org.spongepowered.asm.mixin.Final;
-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.ModifyArgs;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
-
-import java.util.Objects;
-
-@Mixin(targets = "net.minecraft.client.multiplayer.chat.ChatListener$1")
-public class MixinChatListener_1 {
-    @Shadow
-    @Final
-    Component val$decoratedMessage;
-    @Unique
-    private ThreadLocal cancelNext = new ThreadLocal<>();
-    
-    @ModifyArgs(method = "accept", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/chat/ChatListener;processPlayerChatMessage(Lnet/minecraft/network/chat/ChatType$Bound;Lnet/minecraft/network/chat/PlayerChatMessage;Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/multiplayer/PlayerInfo;ZLjava/time/Instant;)Z"))
-    private void modifyMessage(Args args) {
-        cancelNext.remove();
-        ChatType.Bound boundChatType = args.get(0);
-        Component message = args.get(2);
-        var process = ClientChatEvent.RECEIVED.invoker().process(boundChatType, message);
-        if (process.isPresent()) {
-            if (process.isFalse()) {
-                cancelNext.set(message);
-            } else if (process.object() != null) {
-                args.set(2, process.object());
-            }
-        }
-    }
-    
-    @Inject(method = "accept", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/chat/ChatListener;processPlayerChatMessage(Lnet/minecraft/network/chat/ChatType$Bound;Lnet/minecraft/network/chat/PlayerChatMessage;Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/multiplayer/PlayerInfo;ZLjava/time/Instant;)Z"),
-            cancellable = true)
-    private void handleChatPre(CallbackInfoReturnable cir) {
-        if (Objects.equals(cancelNext.get(), this.val$decoratedMessage)) {
-            cir.setReturnValue(false);
-        }
-        
-        cancelNext.remove();
-    }
-}
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_2.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_2.java
deleted file mode 100644
index 6050157c..00000000
--- a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinChatListener_2.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * This file is part of architectury.
- * Copyright (C) 2020, 2021, 2022 architectury
- *
- * 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 dev.architectury.mixin.fabric.client;
-
-import dev.architectury.event.events.client.ClientChatEvent;
-import net.minecraft.network.chat.ChatType;
-import net.minecraft.network.chat.Component;
-import org.spongepowered.asm.mixin.Final;
-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.ModifyArgs;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
-
-import java.util.Objects;
-
-@Mixin(targets = "net.minecraft.client.multiplayer.chat.ChatListener$2")
-public class MixinChatListener_2 {
-    @Shadow
-    @Final
-    Component val$decoratedMessage;
-    @Unique
-    private ThreadLocal cancelNext = new ThreadLocal<>();
-    
-    @ModifyArgs(method = "accept", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/chat/ChatListener;processNonPlayerChatMessage(Lnet/minecraft/network/chat/ChatType$Bound;Lnet/minecraft/network/chat/PlayerChatMessage;Lnet/minecraft/network/chat/Component;)Z"))
-    private void modifyMessage(Args args) {
-        cancelNext.remove();
-        ChatType.Bound boundChatType = args.get(0);
-        Component message = args.get(2);
-        var process = ClientChatEvent.RECEIVED.invoker().process(boundChatType, message);
-        if (process.isPresent()) {
-            if (process.isFalse()) {
-                cancelNext.set(message);
-            } else if (process.object() != null) {
-                args.set(2, process.object());
-            }
-        }
-    }
-    
-    @Inject(method = "accept", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/chat/ChatListener;processNonPlayerChatMessage(Lnet/minecraft/network/chat/ChatType$Bound;Lnet/minecraft/network/chat/PlayerChatMessage;Lnet/minecraft/network/chat/Component;)Z"),
-            cancellable = true)
-    private void handleChatPre(CallbackInfoReturnable cir) {
-        if (Objects.equals(cancelNext.get(), this.val$decoratedMessage)) {
-            cir.setReturnValue(false);
-        }
-        
-        cancelNext.remove();
-    }
-}
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinClientPacketListener.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinClientPacketListener.java
index 295b34d1..074cf940 100644
--- a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinClientPacketListener.java
+++ b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinClientPacketListener.java
@@ -19,22 +19,16 @@
 
 package dev.architectury.mixin.fabric.client;
 
+import dev.architectury.event.EventResult;
 import dev.architectury.event.events.client.ClientChatEvent;
 import dev.architectury.event.events.client.ClientPlayerEvent;
 import dev.architectury.event.events.client.ClientRecipeUpdateEvent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.multiplayer.ClientPacketListener;
-import net.minecraft.client.multiplayer.PlayerInfo;
 import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.core.Registry;
-import net.minecraft.network.chat.ChatSender;
-import net.minecraft.network.chat.ChatType;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.PlayerChatMessage;
 import net.minecraft.network.protocol.game.ClientboundLoginPacket;
 import net.minecraft.network.protocol.game.ClientboundRespawnPacket;
-import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
 import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
 import net.minecraft.world.item.crafting.RecipeManager;
 import org.spongepowered.asm.mixin.Final;
@@ -44,7 +38,6 @@ 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;
-import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
 @Mixin(ClientPacketListener.class)
 public class MixinClientPacketListener {
@@ -80,4 +73,12 @@ public class MixinClientPacketListener {
     private void handleUpdateRecipes(ClientboundUpdateRecipesPacket clientboundUpdateRecipesPacket, CallbackInfo ci) {
         ClientRecipeUpdateEvent.EVENT.invoker().update(recipeManager);
     }
+    
+    @Inject(method = "sendChat(Ljava/lang/String;)V", at = @At(value = "HEAD"), cancellable = true)
+    private void chat(String string, CallbackInfo ci) {
+        EventResult process = ClientChatEvent.SEND.invoker().send(string, null);
+        if (process.isFalse()) {
+            ci.cancel();
+        }
+    }
 }
diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinLocalPlayer.java b/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinLocalPlayer.java
deleted file mode 100644
index 6e16ccd4..00000000
--- a/fabric/src/main/java/dev/architectury/mixin/fabric/client/MixinLocalPlayer.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * This file is part of architectury.
- * Copyright (C) 2020, 2021, 2022 architectury
- *
- * 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 dev.architectury.mixin.fabric.client;
-
-import com.mojang.authlib.GameProfile;
-import dev.architectury.event.EventResult;
-import dev.architectury.event.events.client.ClientChatEvent;
-import net.minecraft.client.multiplayer.ClientLevel;
-import net.minecraft.client.player.AbstractClientPlayer;
-import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.network.chat.Component;
-import net.minecraft.world.entity.player.ProfilePublicKey;
-import org.jetbrains.annotations.Nullable;
-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(LocalPlayer.class)
-public abstract class MixinLocalPlayer extends AbstractClientPlayer {
-    public MixinLocalPlayer(ClientLevel clientLevel, GameProfile gameProfile, @Nullable ProfilePublicKey profilePublicKey) {
-        super(clientLevel, gameProfile, profilePublicKey);
-    }
-    
-    @Inject(method = "sendChat(Ljava/lang/String;Lnet/minecraft/network/chat/Component;)V", at = @At(value = "HEAD"), cancellable = true)
-    private void chat(String string, Component component, CallbackInfo ci) {
-        EventResult process = ClientChatEvent.SEND.invoker().send(string, component);
-        if (process.isFalse()) {
-            ci.cancel();
-        }
-    }
-}
diff --git a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
index c7a4483b..f1ece981 100644
--- a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
+++ b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java
@@ -34,6 +34,7 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientPacketListener;
 import net.minecraft.network.FriendlyByteBuf;
 import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.util.thread.BlockableEventLoop;
@@ -153,7 +154,7 @@ public class NetworkManagerImpl {
         return ServerPlayNetworking.canSend(player, id);
     }
     
-    public static Packet createAddEntityPacket(Entity entity) {
+    public static Packet createAddEntityPacket(Entity entity) {
         return SpawnEntityPacket.create(entity);
     }
     
diff --git a/fabric/src/main/java/dev/architectury/networking/fabric/SpawnEntityPacket.java b/fabric/src/main/java/dev/architectury/networking/fabric/SpawnEntityPacket.java
index c6068c83..b4de4c80 100644
--- a/fabric/src/main/java/dev/architectury/networking/fabric/SpawnEntityPacket.java
+++ b/fabric/src/main/java/dev/architectury/networking/fabric/SpawnEntityPacket.java
@@ -28,6 +28,7 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.core.Registry;
 import net.minecraft.network.FriendlyByteBuf;
 import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
 import net.minecraft.resources.ResourceLocation;
 import net.minecraft.world.entity.Entity;
 
@@ -37,7 +38,7 @@ import net.minecraft.world.entity.Entity;
 public class SpawnEntityPacket {
     private static final ResourceLocation PACKET_ID = new ResourceLocation("architectury", "spawn_entity_packet");
     
-    public static Packet create(Entity entity) {
+    public static Packet create(Entity entity) {
         if (entity.level.isClientSide()) {
             throw new IllegalStateException("SpawnPacketUtil.create called on the logical client!");
         }
@@ -59,7 +60,7 @@ public class SpawnEntityPacket {
         if (entity instanceof EntitySpawnExtension ext) {
             ext.saveAdditionalSpawnData(buffer);
         }
-        return NetworkManager.toPacket(NetworkManager.s2c(), PACKET_ID, buffer);
+        return (Packet) NetworkManager.toPacket(NetworkManager.s2c(), PACKET_ID, buffer);
     }
     
     
diff --git a/fabric/src/main/java/dev/architectury/registry/fabric/CreativeTabRegistryImpl.java b/fabric/src/main/java/dev/architectury/registry/fabric/CreativeTabRegistryImpl.java
index 79c16e32..20d24bc5 100644
--- a/fabric/src/main/java/dev/architectury/registry/fabric/CreativeTabRegistryImpl.java
+++ b/fabric/src/main/java/dev/architectury/registry/fabric/CreativeTabRegistryImpl.java
@@ -19,15 +19,26 @@
 
 package dev.architectury.registry.fabric;
 
-import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder;
 import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.flag.FeatureFlagSet;
 import net.minecraft.world.item.CreativeModeTab;
 import net.minecraft.world.item.ItemStack;
 
+import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 public class CreativeTabRegistryImpl {
-    public static CreativeModeTab create(ResourceLocation name, Supplier icon) {
-        return FabricItemGroupBuilder.build(name, icon);
+    public static CreativeModeTab create(ResourceLocation name, Supplier icon, BiConsumer filler) {
+        return new FabricItemGroup(name, icon) {
+            @Override
+            public ItemStack makeIcon() {
+                return icon.get();
+            }
+            
+            @Override
+            protected void generateDisplayItems(FeatureFlagSet flags, CreativeModeTab.Output output) {
+                filler.accept(flags, output);
+            }
+        };
     }
 }
diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json
index 363897b1..75149b7b 100644
--- a/fabric/src/main/resources/architectury.mixins.json
+++ b/fabric/src/main/resources/architectury.mixins.json
@@ -7,8 +7,7 @@
   "client": [
     "client.ClientPlayerAttackInvoker",
     "client.MixinAbstractContainerScreen",
-    "client.MixinChatListener_1",
-    "client.MixinChatListener_2",
+    "client.MixinChatListener",
     "client.MixinClientLevel",
     "client.MixinClientPacketListener",
     "client.MixinDebugScreenOverlay",
@@ -16,12 +15,11 @@
     "client.MixinGameRenderer",
     "client.MixinIntegratedServer",
     "client.MixinKeyboardHandler",
-    "client.MixinLocalPlayer",
     "client.MixinMinecraft",
     "client.MixinMouseHandler",
     "client.MixinMultiPlayerGameMode",
     "client.MixinScreen",
-    "client.MixinTextureAtlas"
+    "client.MixinAtlasSet"
   ],
   "mixins": [
     "BiomeAccessor",
diff --git a/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java b/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java
index 97ff7204..4104a764 100644
--- a/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java
+++ b/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java
@@ -180,7 +180,7 @@ public class NetworkManagerImpl {
         return clientReceivables.get(player).contains(id);
     }
     
-    public static Packet createAddEntityPacket(Entity entity) {
+    public static Packet createAddEntityPacket(Entity entity) {
         return NetworkHooks.getEntitySpawningPacket(entity);
     }
     
diff --git a/gradle.properties b/gradle.properties
index 4c94dd7d..8a0d8457 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,13 +1,13 @@
 org.gradle.jvmargs=-Xmx6G
 org.gradle.daemon=false
 
-platforms=fabric,forge
+platforms=fabric
 
-minecraft_version=1.19.2
-supported_version=1.19.1/2
-required_version=1.19.1
+minecraft_version=22w42a
+supported_version=1.19.3 (22w42a)
+required_version=1.19.3
 
-artifact_type=release
+artifact_type=beta
 
 archives_base_name=architectury
 archives_base_name_snapshot=architectury-snapshot
diff --git a/settings.gradle b/settings.gradle
index 8bfde812..0f4b94fd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,9 +13,9 @@ if (JavaVersion.current().ordinal() + 1 < 17) {
 
 include("common")
 include("fabric")
-include("forge")
+//include("forge")
 include("testmod-common")
 include("testmod-fabric")
-include("testmod-forge")
+//include("testmod-forge")
 
 rootProject.name = "architectury"
diff --git a/testmod-common/src/main/java/dev/architectury/test/entity/TestEntity.java b/testmod-common/src/main/java/dev/architectury/test/entity/TestEntity.java
index fb82101a..b136c0ba 100644
--- a/testmod-common/src/main/java/dev/architectury/test/entity/TestEntity.java
+++ b/testmod-common/src/main/java/dev/architectury/test/entity/TestEntity.java
@@ -22,6 +22,7 @@ package dev.architectury.test.entity;
 import com.google.common.base.Suppliers;
 import dev.architectury.networking.NetworkManager;
 import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.MobCategory;
 import net.minecraft.world.entity.animal.Cow;
@@ -38,7 +39,7 @@ public class TestEntity extends Cow {
     }
     
     @Override
-    public Packet getAddEntityPacket() {
+    public Packet getAddEntityPacket() {
         return NetworkManager.createAddEntityPacket(this);
     }
 }
diff --git a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java
index f78e20dd..279e02a2 100644
--- a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java
+++ b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java
@@ -299,9 +299,9 @@ public class DebugEvents {
         ClientRecipeUpdateEvent.EVENT.register(recipeManager -> {
             TestMod.SINK.accept("Client recipes received");
         });
-        ClientTextureStitchEvent.POST.register(atlas -> {
-            TestMod.SINK.accept("Client texture stitched: " + atlas.location());
-        });
+//        ClientTextureStitchEvent.POST.register(atlas -> {
+//            TestMod.SINK.accept("Client texture stitched: " + atlas.location());
+//        });
         ClientScreenInputEvent.MOUSE_SCROLLED_PRE.register((client, screen, mouseX, mouseY, amount) -> {
             TestMod.SINK.accept("Screen Mouse scrolled: %.2f distance", amount);
             return EventResult.pass();
diff --git a/testmod-common/src/main/java/dev/architectury/test/recipes/TestRecipeSerializer.java b/testmod-common/src/main/java/dev/architectury/test/recipes/TestRecipeSerializer.java
index 5dcede54..819bd254 100644
--- a/testmod-common/src/main/java/dev/architectury/test/recipes/TestRecipeSerializer.java
+++ b/testmod-common/src/main/java/dev/architectury/test/recipes/TestRecipeSerializer.java
@@ -22,22 +22,30 @@ package dev.architectury.test.recipes;
 import com.google.gson.JsonObject;
 import net.minecraft.network.FriendlyByteBuf;
 import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.minecraft.world.item.crafting.CraftingBookCategory;
 import net.minecraft.world.item.crafting.CustomRecipe;
 import net.minecraft.world.item.crafting.FireworkRocketRecipe;
 import net.minecraft.world.item.crafting.RecipeSerializer;
 
+import java.util.Objects;
+
 public class TestRecipeSerializer implements RecipeSerializer {
     @Override
     public CustomRecipe fromJson(ResourceLocation id, JsonObject json) {
-        return new FireworkRocketRecipe(id);
+        CraftingBookCategory category = Objects.requireNonNullElse(
+                CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null)), CraftingBookCategory.MISC);
+        return new FireworkRocketRecipe(id, category);
     }
     
     @Override
     public CustomRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buf) {
-        return new FireworkRocketRecipe(id);
+        CraftingBookCategory category = buf.readEnum(CraftingBookCategory.class);
+        return new FireworkRocketRecipe(id, category);
     }
     
     @Override
     public void toNetwork(FriendlyByteBuf buf, CustomRecipe recipe) {
+        buf.writeEnum(recipe.category());
     }
 }
diff --git a/testmod-common/src/main/java/dev/architectury/test/registry/TestRegistries.java b/testmod-common/src/main/java/dev/architectury/test/registry/TestRegistries.java
index b8e1ff73..b76250ea 100644
--- a/testmod-common/src/main/java/dev/architectury/test/registry/TestRegistries.java
+++ b/testmod-common/src/main/java/dev/architectury/test/registry/TestRegistries.java
@@ -34,7 +34,6 @@ import dev.architectury.test.TestMod;
 import dev.architectury.test.entity.TestEntity;
 import dev.architectury.test.recipes.TestRecipeSerializer;
 import dev.architectury.test.registry.objects.EquippableTickingItem;
-import dev.architectury.test.tab.TestCreativeTabs;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Registry;
 import net.minecraft.resources.ResourceLocation;
@@ -100,27 +99,27 @@ public class TestRegistries {
             });
     
     public static final RegistrySupplier TEST_ITEM = ITEMS.register("test_item", () ->
-            new Item(new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+            new Item(new Item.Properties()));
     public static final RegistrySupplier TEST_EQUIPPABLE = ITEMS.register("test_eqippable", () ->
-            new EquippableTickingItem(new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+            new EquippableTickingItem(new Item.Properties()));
     public static final RegistrySupplier TEST_EDIBLE = ITEMS.register("test_edible", () -> {
         FoodProperties.Builder fpBuilder = new FoodProperties.Builder().nutrition(8).saturationMod(0.8F).meat();
         FoodPropertiesHooks.effect(fpBuilder, () -> new MobEffectInstance(TEST_EFFECT.get(), 100), 1);
-        return new Item(new Item.Properties().tab(TestCreativeTabs.TEST_TAB).food(fpBuilder.build()));
+        return new Item(new Item.Properties().food(fpBuilder.build()));
     });
     public static final RegistrySupplier TEST_SPAWN_EGG = ITEMS.register("test_spawn_egg", () ->
             new ArchitecturySpawnEggItem(TestRegistries.TEST_ENTITY, 0xFF000000, 0xFFFFFFFF,
-                    new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+                    new Item.Properties()));
     public static final RegistrySupplier TEST_SPAWN_EGG_2 = ITEMS.register("test_spawn_egg_2", () ->
             new ArchitecturySpawnEggItem(TestRegistries.TEST_ENTITY_2, 0xFFFFFFFF, 0xFF000000,
-                    new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+                    new Item.Properties()));
     
     public static final RegistrySupplier TEST_FLUID_BUCKET = ITEMS.register("test_fluid_bucket", () -> {
         try {
             // In example mod the forge class isn't being replaced, this is not required in mods depending on architectury
             return (Item) Class.forName(!Platform.isForge() ? "dev.architectury.core.item.ArchitecturyBucketItem" : "dev.architectury.core.item.forge.imitator.ArchitecturyBucketItem")
                     .getDeclaredConstructor(Supplier.class, Item.Properties.class)
-                    .newInstance(TestRegistries.TEST_FLUID, new Item.Properties().tab(TestCreativeTabs.TEST_TAB));
+                    .newInstance(TestRegistries.TEST_FLUID, new Item.Properties());
         } catch (InstantiationException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
                  IllegalAccessException e) {
             throw new RuntimeException(e);
@@ -175,9 +174,9 @@ public class TestRegistries {
     });
     
     public static final RegistrySupplier TEST_BLOCK_ITEM = ITEMS.register("test_block", () ->
-            new BlockItem(TEST_BLOCK.get(), new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+            new BlockItem(TEST_BLOCK.get(), new Item.Properties()));
     public static final RegistrySupplier COLLISION_BLOCK_ITEM = ITEMS.register("collision_block", () ->
-            new BlockItem(COLLISION_BLOCK.get(), new Item.Properties().tab(TestCreativeTabs.TEST_TAB)));
+            new BlockItem(COLLISION_BLOCK.get(), new Item.Properties()));
     
     public static final RegistrySupplier> TEST_ENTITY = ENTITY_TYPES.register("test_entity", TestEntity.TYPE);
     public static final RegistrySupplier> TEST_ENTITY_2 = ENTITY_TYPES.register("test_entity_2", TestEntity.TYPE_2);
diff --git a/testmod-common/src/main/java/dev/architectury/test/tab/TestCreativeTabs.java b/testmod-common/src/main/java/dev/architectury/test/tab/TestCreativeTabs.java
index ec3b6be4..f1ed76ef 100644
--- a/testmod-common/src/main/java/dev/architectury/test/tab/TestCreativeTabs.java
+++ b/testmod-common/src/main/java/dev/architectury/test/tab/TestCreativeTabs.java
@@ -28,5 +28,14 @@ import net.minecraft.world.item.ItemStack;
 
 public class TestCreativeTabs {
     public static final CreativeModeTab TEST_TAB = CreativeTabRegistry.create(new ResourceLocation(TestMod.MOD_ID, "test_tab"),
-            () -> new ItemStack(TestRegistries.TEST_ITEM.get()));
+            () -> new ItemStack(TestRegistries.TEST_ITEM.get()), (featureFlagSet, output) -> {
+                output.accept(TestRegistries.TEST_ITEM.get());
+                output.accept(TestRegistries.TEST_EQUIPPABLE.get());
+                output.accept(TestRegistries.TEST_EDIBLE.get());
+                output.accept(TestRegistries.TEST_SPAWN_EGG.get());
+                output.accept(TestRegistries.TEST_SPAWN_EGG_2.get());
+                output.accept(TestRegistries.TEST_FLUID_BUCKET.get());
+                output.accept(TestRegistries.TEST_BLOCK_ITEM.get());
+                output.accept(TestRegistries.COLLISION_BLOCK_ITEM.get());
+            });
 }