From 01d2582c44c269dca24d168ea2b85895c1e18a8a Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 May 2025 16:32:21 -0400 Subject: [PATCH] Sync tag ingredients directly if ModernFix is installed on both sides --- .../main/resources/modernfix.accesswidener | 1 + .../ConnectionMixin.java | 33 ++++++++++ .../IngredientMixin.java | 63 +++++++++++++++++++ .../PlayerListMixin.java | 50 +++++++++++++++ .../modernfix/forge/packet/PacketHandler.java | 22 ++++--- 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java diff --git a/common/src/main/resources/modernfix.accesswidener b/common/src/main/resources/modernfix.accesswidener index f1291622..54f5368c 100644 --- a/common/src/main/resources/modernfix.accesswidener +++ b/common/src/main/resources/modernfix.accesswidener @@ -66,5 +66,6 @@ accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$Sta accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State reloadNanos Ljava/util/concurrent/atomic/AtomicLong; accessible class net/minecraft/world/item/crafting/Ingredient$Value +accessible field net/minecraft/world/item/crafting/Ingredient$TagValue tag Lnet/minecraft/tags/TagKey; accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue accessible class net/minecraft/client/searchtree/SearchRegistry$TreeEntry \ No newline at end of file diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java new file mode 100644 index 00000000..e9cbeeac --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java @@ -0,0 +1,33 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Connection.class) +public class ConnectionMixin { + /** + * @author embeddedt + * @reason Provide context to the ingredient serializer about whether the enhanced sync protocol is supported. + */ + @WrapMethod(method = "doSendPacket") + private void modernfix$checkClientPresence(Packet packet, @Nullable PacketSendListener sendListener, ConnectionProtocol newProtocol, ConnectionProtocol currentProtocol, Operation original) { + if (packet instanceof ClientboundUpdateRecipesPacket && PacketHandler.INGREDIENT_SYNC.isRemotePresent((Connection)(Object)this)) { + PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(true); + try { + original.call(packet, sendListener, newProtocol, currentProtocol); + } finally { + PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(false); + } + } else { + original.call(packet, sendListener, newProtocol, currentProtocol); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java new file mode 100644 index 00000000..771dbf0a --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java @@ -0,0 +1,63 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.crafting.Ingredient; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +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.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Ingredient.class) +public abstract class IngredientMixin { + @Shadow public abstract boolean isVanilla(); + + @Shadow @Final private Ingredient.Value[] values; + + @Unique + private static final ResourceLocation MODERNFIX_TAG_VALUE = new ResourceLocation("modernfix", "tag_value"); + + @Inject(method = "toNetwork", + at = @At("HEAD"), + cancellable = true) + private void checkForVanillaTagIngredient(FriendlyByteBuf buf, CallbackInfo ci) { + if (!PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.get() || !this.isVanilla()) { + return; + } + Ingredient.Value[] values = this.values; + if (values.length == 1 && values[0] instanceof Ingredient.TagValue tagValue) { + var optionalHolderSet = BuiltInRegistries.ITEM.getTag(tagValue.tag); + if (optionalHolderSet.isEmpty()) { + // Use default serialization logic for tags that do not exist + return; + } + + // Encode this as our tag ingredient type instead of using vanilla's flattening logic. + ci.cancel(); + buf.writeVarInt(-1); + buf.writeResourceLocation(MODERNFIX_TAG_VALUE); + buf.writeResourceLocation(tagValue.tag.location()); + } + } + + @Inject(method = "fromNetwork", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/FriendlyByteBuf;readResourceLocation()Lnet/minecraft/resources/ResourceLocation;", ordinal = 0), cancellable = true) + private static void deserializeModernFixTagValue(FriendlyByteBuf buffer, CallbackInfoReturnable cir) { + int readerIndex = buffer.readerIndex(); + var type = buffer.readResourceLocation(); + if (!type.equals(MODERNFIX_TAG_VALUE)) { + // Allow Forge to read the original packet + buffer.readerIndex(readerIndex); + return; + } + var tag = buffer.readResourceLocation(); + cir.setReturnValue(Ingredient.of(TagKey.create(Registries.ITEM, tag))); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java new file mode 100644 index 00000000..80e64379 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java @@ -0,0 +1,50 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; +import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + /** + * @author embeddedt + * @reason Ensure the tag packet is always sent before the recipe packet (like it is after /reload). + */ + @Redirect(method = "placeNewPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"), + slice = @Slice( + from = @At(value = "NEW", target = "(Lnet/minecraft/server/players/PlayerList;Lnet/minecraft/server/level/ServerPlayer;)Lnet/minecraftforge/event/OnDatapackSyncEvent;"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;sendPlayerPermissionLevel(Lnet/minecraft/server/level/ServerPlayer;)V") + ), expect = 2, allow = 2, require = 2) + private void modernfix$switchTagAndRecipeOrder(ServerGamePacketListenerImpl instance, Packet packet, @Local(ordinal = 0, argsOnly = true) Connection netManager, @Local(ordinal = 0, argsOnly = true) ServerPlayer player, @Share("deferred") LocalRef> pktRef) { + if (!(packet instanceof ClientboundUpdateRecipesPacket) && !(packet instanceof ClientboundUpdateTagsPacket)) { + throw new AssertionError("Mixin injected in wrong place"); + } + if (!PacketHandler.INGREDIENT_SYNC.isRemotePresent(netManager)) { + instance.send(packet); + return; + } + + if (packet instanceof ClientboundUpdateRecipesPacket) { + pktRef.set(packet); + } else { + ModernFix.LOGGER.info("Using enhanced recipe sync for player {}", player.getName().getString()); + // send tags + instance.send(packet); + // send recipes + instance.send(pktRef.get()); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java b/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java index e556618a..de7ad5a3 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java @@ -13,17 +13,21 @@ import org.embeddedt.modernfix.packet.EntityIDSyncPacket; import java.util.function.Supplier; public class PacketHandler { - private static final String PROTOCOL_VERSION = "1"; - public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( - new ResourceLocation(ModernFix.MODID, "main"), - () -> PROTOCOL_VERSION, - NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION), - NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION) - ); + public static final SimpleChannel INSTANCE = buildChannel("main", "1"); + public static final SimpleChannel INGREDIENT_SYNC = buildChannel("ingredient_sync", "1"); + public static final ThreadLocal CLIENT_HAS_SMART_INGREDIENT_SYNC = ThreadLocal.withInitial(() -> false); + + private static SimpleChannel buildChannel(String name, String version) { + return NetworkRegistry.newSimpleChannel( + new ResourceLocation(ModernFix.MODID, name), + () -> version, + NetworkRegistry.acceptMissingOr(version), + NetworkRegistry.acceptMissingOr(version) + ); + } public static void register() { - int id = 1; - INSTANCE.registerMessage(id++, EntityIDSyncPacket.class, EntityIDSyncPacket::serialize, EntityIDSyncPacket::deserialize, PacketHandler::handleSyncPacket); + INSTANCE.registerMessage(1, EntityIDSyncPacket.class, EntityIDSyncPacket::serialize, EntityIDSyncPacket::deserialize, PacketHandler::handleSyncPacket); } private static void handleSyncPacket(EntityIDSyncPacket packet, Supplier contextSupplier) {