diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 7e94222..40ceb82 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "1.9-SNAPSHOT" + id("fabric-loom") version "1.10.1" id("spectatorplus.platform") } diff --git a/fabric/gradle.properties b/fabric/gradle.properties index eec19dc..79c3d77 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,13 +1,13 @@ -minecraft_version=1.21.4 -yarn_mappings=1.21.4+build.8 -loader_version=0.16.9 +minecraft_version=1.21.5 +yarn_mappings=1.21.5+build.1 +loader_version=0.16.14 # Fabric API -fabric_version=0.114.1+1.21.4 +fabric_version=0.125.0+1.21.5 -parchment_minecraft_version=1.21.4 -parchment_version=2025.01.05 +parchment_minecraft_version=1.21.5 +parchment_version=2025.04.19 fabric_permissions_api_version=0.3.3 -cloth_config_version=17.0.144 -modmenu_version=13.0.0 +cloth_config_version=18.0.145 +modmenu_version=14.0.0-rc.2 diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java index 4a8a866..116c30f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java @@ -13,6 +13,7 @@ import net.minecraft.network.chat.MutableComponent; import java.io.IOException; +import java.net.URI; public class ClothConfigIntegration { private ClothConfigIntegration() { @@ -131,7 +132,7 @@ private static void setupServerConfig(ConfigBuilder builder) { .build()); category.addEntry(entryBuilder.startBooleanToggle(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.name") - .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://bugs.mojang.com/browse/MC-148993"))), config.autoUpdatePosition) + .withStyle(style -> style.withClickEvent(new ClickEvent.OpenUrl(URI.create("https://bugs.mojang.com/browse/MC-148993")))), config.autoUpdatePosition) .setTooltip(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.tooltip", Component.literal("MC-148993").withStyle(ChatFormatting.BLUE, ChatFormatting.UNDERLINE))) .setSaveConsumer(val -> config.autoUpdatePosition = val) .setDefaultValue(defaults.autoUpdatePosition) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java index f0f3256..5a9f92c 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.client.gui.screens; +import com.hpfxd.spectatorplus.fabric.client.mixin.InventoryAccessor; import com.hpfxd.spectatorplus.fabric.client.mixin.screen.AbstractRecipeBookScreenAccessor; import com.hpfxd.spectatorplus.fabric.client.mixin.screen.ImageButtonAccessor; import net.minecraft.client.gui.components.ImageButton; @@ -9,6 +10,7 @@ import net.minecraft.client.gui.narration.NarratableEntry; import net.minecraft.client.gui.screens.inventory.InventoryScreen; import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent; +import net.minecraft.world.entity.EntityEquipment; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; @@ -36,11 +38,11 @@ private void syncOtherItems() { final Inventory playerInventory = menu.getOwner().getInventory(); final Inventory fakeInventory = menu.getInventory(); - for (int slot = 0; slot < playerInventory.armor.size(); slot++) { - fakeInventory.armor.set(slot, playerInventory.armor.get(slot)); - } + final EntityEquipment playerEquipment = ((InventoryAccessor) (menu.getOwner().getInventory())).getEquipment(); + final EntityEquipment fakeEquipment = ((InventoryAccessor) (menu.getInventory())).getEquipment(); - fakeInventory.offhand.set(0, playerInventory.offhand.get(0)); + fakeEquipment.setAll(playerEquipment); + fakeInventory.setItem(Inventory.SLOT_OFFHAND, playerInventory.getItem(Inventory.SLOT_OFFHAND)); } @Override diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java index 456d7ac..5b560bc 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java @@ -46,7 +46,7 @@ public abstract class GameRendererMixin { @Unique private float xBobO; @Unique private float yBobO; - @Inject(method = "renderItemInHand(Lnet/minecraft/client/Camera;FLorg/joml/Matrix4f;)V", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;")) + @Inject(method = "renderItemInHand(Lnet/minecraft/client/Camera;FLorg/joml/Matrix4f;)V", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) public void spectatorplus$renderItemInHand(Camera camera, float partialTicks, Matrix4f matrix4f, CallbackInfo ci, @Local PoseStack poseStackIn) { if (SpectatorClientMod.config.renderArms && this.minecraft.player != null && this.minecraft.options.getCameraType().isFirstPerson() && !this.minecraft.options.hideGui) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java index 9005700..349637f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java @@ -16,7 +16,6 @@ import net.minecraft.client.multiplayer.MultiPlayerGameMode; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.core.NonNullList; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; @@ -163,12 +162,12 @@ public abstract class GuiMixin { return (ClientSyncController.syncData != null && ClientSyncController.syncData.foodData != null) || SpecUtil.getCameraPlayer(this.minecraft) == null; } - @Redirect(method = "renderItemHotbar(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/player/Inventory;selected:I", opcode = Opcodes.GETFIELD)) + @Redirect(method = "renderItemHotbar(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;getSelectedSlot()I", opcode = Opcodes.GETFIELD)) private int spectatorplus$showSyncedSelectedSlot(Inventory inventory) { if (ClientSyncController.syncData != null && ClientSyncController.syncData.selectedHotbarSlot != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { return ClientSyncController.syncData.selectedHotbarSlot; } - return inventory.selected; + return inventory.getSelectedSlot(); } @Redirect(method = "renderFood(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/entity/player/Player;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;getFoodData()Lnet/minecraft/world/food/FoodData;")) @@ -179,12 +178,12 @@ public abstract class GuiMixin { return instance.getFoodData(); } - @ModifyReceiver(method = "renderItemHotbar(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/NonNullList;get(I)Ljava/lang/Object;", ordinal = 0)) - private NonNullList spectatorplus$showSyncedItems(NonNullList instance, int i) { + @Redirect(method = "renderItemHotbar(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;getItem(I)Lnet/minecraft/world/item/ItemStack;")) + private ItemStack spectatorplus$showSyncedItems(Inventory instance, int slot) { if (ClientSyncController.syncData != null && ClientSyncController.syncData.selectedHotbarSlot != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { - return ClientSyncController.syncData.hotbarItems; + return ClientSyncController.syncData.hotbarItems.get(slot); } - return instance; + return instance.getItem(slot); } @Redirect(method = "renderExperienceBar(Lnet/minecraft/client/gui/GuiGraphics;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getXpNeededForNextLevel()I")) @@ -211,7 +210,7 @@ public abstract class GuiMixin { return instance.experienceLevel; } - @ModifyReceiver(method = "tick()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;getSelected()Lnet/minecraft/world/item/ItemStack;")) + @ModifyReceiver(method = "tick()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;getSelectedItem()Lnet/minecraft/world/item/ItemStack;")) private Inventory spectatorplus$modifyTooltipTick(Inventory instance) { if (this.minecraft.getCameraEntity() instanceof Player player) { return player.getInventory(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java new file mode 100644 index 0000000..9a1f46d --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java @@ -0,0 +1,12 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import net.minecraft.world.entity.EntityEquipment; +import net.minecraft.world.entity.player.Inventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Inventory.class) +public interface InventoryAccessor { + @Accessor + EntityEquipment getEquipment(); +} diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java index 1815087..a43e841 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java @@ -1,6 +1,7 @@ package com.hpfxd.spectatorplus.fabric.client.sync.screen; import com.hpfxd.spectatorplus.fabric.client.gui.screens.SyncedInventoryScreen; +import com.hpfxd.spectatorplus.fabric.client.mixin.InventoryAccessor; import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundInventorySyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundScreenCursorSyncPacket; @@ -14,6 +15,7 @@ import net.minecraft.client.gui.screens.inventory.MenuAccess; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.EntityEquipment; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -120,10 +122,11 @@ public static boolean createInventory(Player spectated) { return false; } - syncedInventory = new Inventory(spectated); + EntityEquipment equipment = ((InventoryAccessor)spectated.getInventory()).getEquipment(); + syncedInventory = new Inventory(spectated, equipment); for (int i = 0; i < syncData.screen.inventoryItems.size(); i++) { - syncedInventory.items.set(i, syncData.screen.inventoryItems.get(i)); + syncedInventory.setItem(i, syncData.screen.inventoryItems.get(i)); } return true; } diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index 39a0b36..cb31ffd 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -9,6 +9,7 @@ "GameRendererAccessor", "GameRendererMixin", "GuiMixin", + "InventoryAccessor", "ItemInHandRendererAccessor", "ItemInHandRendererMixin", "LevelRendererAccessor", diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java index 76e89a0..15d8e13 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java @@ -16,7 +16,7 @@ public abstract class ServerGamePacketListenerImplMixin { @Shadow public ServerPlayer player; - @Inject(method = "handleSetCarriedItem(Lnet/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/player/Inventory;selected:I", opcode = Opcodes.PUTFIELD)) + @Inject(method = "handleSetCarriedItem(Lnet/minecraft/network/protocol/game/ServerboundSetCarriedItemPacket;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Inventory;setSelectedSlot(I)V")) private void spectatorplus$syncSelectedSlot(ServerboundSetCarriedItemPacket packet, CallbackInfo ci) { ServerSyncController.broadcastPacketToSpectators(this.player, new ClientboundSelectedSlotSyncPacket(this.player.getUUID(), packet.getSlot())); } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java index 804b156..0f7f71c 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java @@ -68,7 +68,7 @@ public ServerPlayerMixin(Level level, BlockPos pos, float yRot, GameProfile game ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); // Send initial map data patch packet if the target has a map in inventory - for (final ItemStack stack : target.getInventory().items) { + for (final ItemStack stack : target.getInventory().getNonEquipmentItems()) { if (stack.is(Items.FILLED_MAP)) { final MapId mapId = stack.get(DataComponents.MAP_ID); final MapItemSavedData mapItemSavedData = MapItem.getSavedData(mapId, this.level()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java index a6b19b8..f0d56c3 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java @@ -1,8 +1,16 @@ package com.hpfxd.spectatorplus.fabric.sync; +import io.netty.handler.codec.EncoderException; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.item.ItemStack; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + public final class CustomPacketCodecs { private CustomPacketCodecs() { } @@ -35,10 +43,42 @@ public static void writeItems(RegistryFriendlyByteBuf buf, ItemStack[] items) { } public static ItemStack readItem(RegistryFriendlyByteBuf buf) { - return ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); + final int len = buf.readInt(); + if (len == 0) { + return ItemStack.EMPTY; + } + + try { + final byte[] in = new byte[len]; + buf.readBytes(in); + + final CompoundTag tag = NbtIo.readCompressed(new ByteArrayInputStream(in), NbtAccounter.unlimitedHeap()); + return ItemStack.parse(buf.registryAccess(), tag).orElse(ItemStack.EMPTY); + } catch (IOException e) { + throw new EncoderException(e); + } } public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) { - ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item); + if (item.isEmpty()) { + buf.writeInt(0); + return; + } + + final byte[] bytes; + try { + final CompoundTag tag = new CompoundTag(); + item.save(buf.registryAccess(), tag); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + NbtIo.writeCompressed(tag, out); + + bytes = out.toByteArray(); + } catch (IOException e) { + throw new EncoderException(e); + } + + buf.writeInt(bytes.length); + buf.writeBytes(bytes); } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java index de8e57e..756a9fa 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java @@ -21,7 +21,7 @@ public record ClientboundSelectedSlotSyncPacket( public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:selected_slot_sync")); public static ClientboundSelectedSlotSyncPacket initializing(ServerPlayer target) { - return new ClientboundSelectedSlotSyncPacket(target.getUUID(), target.getInventory().selected); + return new ClientboundSelectedSlotSyncPacket(target.getUUID(), target.getInventory().getSelectedSlot()); } public ClientboundSelectedSlotSyncPacket(FriendlyByteBuf buf) { diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 0709447..f217a85 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -51,13 +51,13 @@ ], "depends": { "fabricloader": ">=0.15.0", - "minecraft": "~1.21.3", + "minecraft": "~1.21.5", "java": ">=21", "fabric-api": "*", "fabric-permissions-api-v0": "*" }, "suggests": { - "cloth-config": "^17.0.0", - "modmenu": "^13.0.0" + "cloth-config": "^18.0.145", + "modmenu": "^14.0.0-rc.2" } }