From aa082c9cbfbb51c442d8593e1d58d323cede5448 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Thu, 29 May 2025 16:51:20 -0400 Subject: [PATCH 01/57] Init 1.21.5 update --- fabric/build.gradle.kts | 2 +- fabric/gradle.properties | 16 ++++++++-------- .../client/config/ClothConfigIntegration.java | 16 ++++++++++------ .../gui/screens/SyncedInventoryScreen.java | 10 ++++++---- .../fabric/client/mixin/GuiMixin.java | 15 +++++++-------- .../fabric/client/mixin/InventoryAccessor.java | 12 ++++++++++++ .../client/sync/screen/ScreenSyncController.java | 7 +++++-- .../resources/spectatorplus.client.mixins.json | 3 ++- .../mixin/ServerGamePacketListenerImplMixin.java | 2 +- .../fabric/mixin/ServerPlayerMixin.java | 2 +- .../ClientboundSelectedSlotSyncPacket.java | 2 +- 11 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java 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..810ded6 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 @@ -10,9 +10,11 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MutableComponent; import java.io.IOException; +import java.net.URI; public class ClothConfigIntegration { private ClothConfigIntegration() { @@ -130,11 +132,13 @@ private static void setupServerConfig(ConfigBuilder builder) { .setDefaultValue(defaults.allowTransferBetweenLevels) .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) - .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) - .build()); +// category.addEntry(entryBuilder.startBooleanToggle(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.name") +// .withStyle(style -> style.withClickEvent(new ClickEvent.OpenUrl(URI.create("https://bugs.mojang.com/browse/MC-148993"))) +// .withHoverEvent(new HoverEvent.ShowText(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.tooltip", Component.literal("MC-148993").withStyle(ChatFormatting.BLUE, ChatFormatting.UNDERLINE)))) +// +//// .setSaveConsumer(val -> config.autoUpdatePosition = val) +// // TODO: what +// .setDefaultValue(defaults.autoUpdatePosition) +// .build()); } } 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/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..360e842 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -26,7 +26,8 @@ "screen.ImageButtonAccessor", "screen.InventoryMenuMixin", "screen.InventoryScreenMixin", - "screen.MenuScreensMixin" + "screen.MenuScreensMixin", + "InventoryAccessor" ], "injectors": { "defaultRequire": 1 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/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) { From a7c7222fc0bb0df330472295dd54219b7a0bf977 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Thu, 29 May 2025 17:17:32 -0400 Subject: [PATCH 02/57] Fix cloth config impl --- .../client/config/ClothConfigIntegration.java | 15 ++++++--------- .../fabric/client/mixin/GameRendererMixin.java | 2 +- .../resources/spectatorplus.client.mixins.json | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) 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 810ded6..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 @@ -10,7 +10,6 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MutableComponent; import java.io.IOException; @@ -132,13 +131,11 @@ private static void setupServerConfig(ConfigBuilder builder) { .setDefaultValue(defaults.allowTransferBetweenLevels) .build()); -// category.addEntry(entryBuilder.startBooleanToggle(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.name") -// .withStyle(style -> style.withClickEvent(new ClickEvent.OpenUrl(URI.create("https://bugs.mojang.com/browse/MC-148993"))) -// .withHoverEvent(new HoverEvent.ShowText(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.tooltip", Component.literal("MC-148993").withStyle(ChatFormatting.BLUE, ChatFormatting.UNDERLINE)))) -// -//// .setSaveConsumer(val -> config.autoUpdatePosition = val) -// // TODO: what -// .setDefaultValue(defaults.autoUpdatePosition) -// .build()); + category.addEntry(entryBuilder.startBooleanToggle(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.name") + .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) + .build()); } } 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/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index 360e842..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", @@ -26,8 +27,7 @@ "screen.ImageButtonAccessor", "screen.InventoryMenuMixin", "screen.InventoryScreenMixin", - "screen.MenuScreensMixin", - "InventoryAccessor" + "screen.MenuScreensMixin" ], "injectors": { "defaultRequire": 1 From 8573262a9879d435416c16226f0a7bd6d3fcb593 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Thu, 29 May 2025 20:58:44 -0400 Subject: [PATCH 03/57] Revert "Use ItemStack stream codec for sync packets" This reverts commit 77453595b1aa68cb8b8ebd47954853da712dbe40. For some reason the ItemStack codecs arent working in the new 1.21.5 update? --- .../fabric/sync/CustomPacketCodecs.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) 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..d963987 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,9 +1,21 @@ 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.network.codec.StreamCodec; import net.minecraft.world.item.ItemStack; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + public final class CustomPacketCodecs { + public static final StreamCodec ITEM_CODEC = StreamCodec.of(CustomPacketCodecs::writeItem, CustomPacketCodecs::readItem); + public static final StreamCodec ITEM_ARRAY_CODEC = StreamCodec.of(CustomPacketCodecs::writeItems, CustomPacketCodecs::readItems); + private CustomPacketCodecs() { } @@ -35,10 +47,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); } } From e7180c81cd3a204515d7c7ca5ebd0028897eee5a Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Thu, 29 May 2025 23:08:18 -0400 Subject: [PATCH 04/57] Update Minecraft version in mod.json --- .../hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java | 4 ---- fabric/src/main/resources/fabric.mod.json | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) 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 d963987..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 @@ -5,7 +5,6 @@ import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.item.ItemStack; import java.io.ByteArrayInputStream; @@ -13,9 +12,6 @@ import java.io.IOException; public final class CustomPacketCodecs { - public static final StreamCodec ITEM_CODEC = StreamCodec.of(CustomPacketCodecs::writeItem, CustomPacketCodecs::readItem); - public static final StreamCodec ITEM_ARRAY_CODEC = StreamCodec.of(CustomPacketCodecs::writeItems, CustomPacketCodecs::readItems); - private CustomPacketCodecs() { } 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" } } From 4595e7c8c8ae92a9a4d603e1c6c0665b6c0e771a Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Fri, 30 May 2025 15:58:38 +0100 Subject: [PATCH 05/57] Update paper settings to 1.21.5 --- paper/gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paper/gradle.properties b/paper/gradle.properties index ff60283..f737760 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ -paper_version=1.21-R0.1-SNAPSHOT -reflection_remapper_version=0.1.1 +paper_version=1.21.4-R0.1-SNAPSHOT +reflection_remapper_version=0.1.2 \ No newline at end of file From e7409aa4e3c8fe2266c11e8a18771cca47baa516 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Fri, 30 May 2025 15:59:01 +0100 Subject: [PATCH 06/57] Add crafter to container sync --- .../paper/sync/handler/screen/ScreenSyncHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java index 8c0ffcf..6ab8ef0 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java @@ -82,6 +82,7 @@ private void openSyncedContainer(Player spectator, InventoryView targetView) { case BLAST_FURNACE: case LECTERN: case SMOKER: + case CRAFTER: this.openSyncedDirectContainer(spectator, targetView); break; case WORKBENCH: From 3e96681a071b1bb755f161fd15e124ab5bfd4fb9 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 28 Jun 2025 21:08:25 +0100 Subject: [PATCH 07/57] update paper to 1.21.6 --- paper/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/gradle.properties b/paper/gradle.properties index f737760..5502716 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ -paper_version=1.21.4-R0.1-SNAPSHOT +paper_version=1.21.6-R0.1-SNAPSHOT reflection_remapper_version=0.1.2 \ No newline at end of file From 7062cc82dc51e14b949695dcbb6b6a7ae64d3683 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 28 Jun 2025 21:09:20 +0100 Subject: [PATCH 08/57] update fabric to 1.21.6 - SNAPSHOT --- fabric/gradle.properties | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 79c3d77..a1c83d1 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,13 +1,13 @@ -minecraft_version=1.21.5 -yarn_mappings=1.21.5+build.1 +minecraft_version=1.21.6 +yarn_mappings=1.21.6+build.1 loader_version=0.16.14 # Fabric API -fabric_version=0.125.0+1.21.5 +fabric_version=0.128.1+1.21.6 -parchment_minecraft_version=1.21.5 -parchment_version=2025.04.19 +parchment_minecraft_version=1.21.6 +parchment_version=BLEEDING-20250627.192053-2 -fabric_permissions_api_version=0.3.3 -cloth_config_version=18.0.145 -modmenu_version=14.0.0-rc.2 +fabric_permissions_api_version=0.4.0 +cloth_config_version=19.0.147 +modmenu_version=15.0.0-beta.3 From 5fc974541dd33c0c4c0201af57c0278a19dc4f95 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 28 Jun 2025 21:10:17 +0100 Subject: [PATCH 09/57] fix player contstructor with new mappings --- .../spectatorplus/fabric/mixin/ServerPlayerMixin.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 0f7f71c..80b2e8c 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 @@ -43,8 +43,11 @@ public abstract class ServerPlayerMixin extends Player { @Shadow public abstract boolean teleportTo(ServerLevel newLevel, double x, double y, double z, Set relative, float yaw, float pitch, boolean resetCamera); @Shadow public abstract void setCamera(@Nullable Entity entityToSpectate); - public ServerPlayerMixin(Level level, BlockPos pos, float yRot, GameProfile gameProfile) { - super(level, pos, yRot, gameProfile); + public ServerPlayerMixin(Level level, GameProfile gameProfile, BlockPos pos, float yRot) { + super(level, gameProfile); + // Set initial position and rotation after construction + this.setPos(pos.getX(), pos.getY(), pos.getZ()); + this.setYRot(yRot); } @Inject(method = "doTick()V", at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerPlayer;lastFoodSaturationZero:Z", opcode = Opcodes.PUTFIELD)) From abae45308b54a37f622773ae1985eda3f9894e8d Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 28 Jun 2025 21:16:12 +0100 Subject: [PATCH 10/57] update mod.json --- fabric/src/main/resources/fabric.mod.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index f217a85..f2e043b 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.5", + "minecraft": "~1.21.6", "java": ">=21", "fabric-api": "*", "fabric-permissions-api-v0": "*" }, "suggests": { - "cloth-config": "^18.0.145", - "modmenu": "^14.0.0-rc.2" + "cloth-config": "^19.0.147", + "modmenu": "^15.0.0-beta.3" } } From 091b1c9b6faf1914d81d1d25d4986b27e9dcd9c6 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Sat, 28 Jun 2025 17:30:10 -0400 Subject: [PATCH 11/57] Update for Minecraft 1.21.6 --- .../mixin/ExperienceBarRendererMixin.java | 34 +++++++++++++++++++ .../client/mixin/GameRendererMixin.java | 4 +-- .../fabric/client/mixin/GuiMixin.java | 20 ++--------- .../mixin/ScreenEffectRendererMixin.java | 34 ++++++++----------- .../screen/AbstractContainerScreenMixin.java | 12 +++---- .../spectatorplus.client.mixins.json | 1 + .../fabric/mixin/ServerPlayerMixin.java | 5 +-- .../fabric/sync/CustomPacketCodecs.java | 8 +++-- 8 files changed, 65 insertions(+), 53 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java new file mode 100644 index 0000000..94478b0 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java @@ -0,0 +1,34 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; +import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.contextualbar.ExperienceBarRenderer; +import net.minecraft.client.player.LocalPlayer; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ExperienceBarRenderer.class) +public class ExperienceBarRendererMixin { + @Shadow @Final private Minecraft minecraft; + + @Redirect(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getXpNeededForNextLevel()I")) + private int spectatorplus$showSyncedExperienceBar(LocalPlayer instance) { + if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { + return ClientSyncController.syncData.experienceNeededForNextLevel; + } + return instance.getXpNeededForNextLevel(); + } + + @Redirect(method = "renderBackground", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;experienceProgress:F", opcode = Opcodes.GETFIELD)) + private float spectatorplus$showSyncedExperienceProgress(LocalPlayer instance) { + if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { + return ClientSyncController.syncData.experienceProgress; + } + return instance.experienceProgress; + } +} 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 5b560bc..fe9e30e 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,8 +46,8 @@ 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;", remap = false)) - public void spectatorplus$renderItemInHand(Camera camera, float partialTicks, Matrix4f matrix4f, CallbackInfo ci, @Local PoseStack poseStackIn) { + @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) + public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, 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); if (spectated != null && !spectated.isSpectator()) { 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 349637f..9b6b6e9 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 @@ -96,7 +96,7 @@ public abstract class GuiMixin { return spectated != null && !spectated.isCreative() && !spectated.isSpectator() && this.spectatorplus$isStatusEnabled(); } - @Redirect(method = "isExperienceBarVisible()Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/MultiPlayerGameMode;hasExperience()Z")) + @Redirect(method = "renderHotbarAndDecorations", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/MultiPlayerGameMode;hasExperience()Z")) private boolean spectatorplus$renderExperience(MultiPlayerGameMode instance) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); if (spectated != null) { @@ -186,23 +186,7 @@ public abstract class GuiMixin { 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")) - private int spectatorplus$showSyncedExperienceBar(LocalPlayer instance) { - if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { - return ClientSyncController.syncData.experienceNeededForNextLevel; - } - return instance.getXpNeededForNextLevel(); - } - - @Redirect(method = "renderExperienceBar(Lnet/minecraft/client/gui/GuiGraphics;I)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;experienceProgress:F", opcode = Opcodes.GETFIELD)) - private float spectatorplus$showSyncedExperienceProgress(LocalPlayer instance) { - if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { - return ClientSyncController.syncData.experienceProgress; - } - return instance.experienceProgress; - } - - @Redirect(method = "renderExperienceLevel(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;experienceLevel:I", opcode = Opcodes.GETFIELD)) + @Redirect(method = "renderHotbarAndDecorations", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/LocalPlayer;experienceLevel:I", opcode = Opcodes.GETFIELD)) private int spectatorplus$showSyncedExperienceLevel(LocalPlayer instance) { if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { return ClientSyncController.syncData.experienceLevel; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java index 4deae0f..17c4f84 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java @@ -1,40 +1,34 @@ package com.hpfxd.spectatorplus.fabric.client.mixin; -import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.ScreenEffectRenderer; import net.minecraft.tags.TagKey; import net.minecraft.world.level.material.Fluid; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +// Redirect calls from mc.player to the current camera entity + @Mixin(ScreenEffectRenderer.class) public class ScreenEffectRendererMixin { - // Redirect calls from mc.player to the current camera entity + @Shadow @Final private Minecraft minecraft; - @Redirect( - method = "renderScreenEffect(Lnet/minecraft/client/Minecraft;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSpectator()Z") - ) - private static boolean spectatorplus$modifyIsSpectator(LocalPlayer instance, @Local(argsOnly = true) Minecraft mc) { - return mc.getCameraEntity().isSpectator(); + @Redirect(method = "renderScreenEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSpectator()Z")) + private boolean spectatorplus$modifyIsSpectator(LocalPlayer instance) { + return this.minecraft.getCameraEntity().isSpectator(); } - @Redirect( - method = "renderScreenEffect(Lnet/minecraft/client/Minecraft;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isEyeInFluid(Lnet/minecraft/tags/TagKey;)Z") - ) - private static boolean spectatorplus$modifyIsEyeInFluid(LocalPlayer instance, TagKey tagKey, @Local(argsOnly = true) Minecraft mc) { - return mc.getCameraEntity().isEyeInFluid(tagKey); + @Redirect(method = "renderScreenEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isEyeInFluid(Lnet/minecraft/tags/TagKey;)Z")) + private boolean spectatorplus$modifyIsEyeInFluid(LocalPlayer instance, TagKey tagKey) { + return this.minecraft.getCameraEntity().isEyeInFluid(tagKey); } - @Redirect( - method = "renderScreenEffect(Lnet/minecraft/client/Minecraft;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isOnFire()Z") - ) - private static boolean spectatorplus$modifyIsOnFire(LocalPlayer instance, @Local(argsOnly = true) Minecraft mc) { - return mc.getCameraEntity().isOnFire(); + @Redirect(method = "renderScreenEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isOnFire()Z")) + private boolean spectatorplus$modifyIsOnFire(LocalPlayer instance) { + return this.minecraft.getCameraEntity().isOnFire(); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java index da20de4..72700ec 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java @@ -7,7 +7,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.inventory.AbstractContainerMenu; @@ -66,12 +66,10 @@ public abstract class AbstractContainerScreenMixin { } @Inject( - method = "render(Lnet/minecraft/client/gui/GuiGraphics;IIF)V", + method = "renderContents", at = @At( value = "INVOKE", - target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;renderLabels(Lnet/minecraft/client/gui/GuiGraphics;II)V", - ordinal = 0, - shift = At.Shift.AFTER + target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;renderLabels(Lnet/minecraft/client/gui/GuiGraphics;II)V" ) ) private void spectatorplus$renderSyncedCursorItem(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick, CallbackInfo ci) { @@ -125,11 +123,11 @@ public abstract class AbstractContainerScreenMixin { private void spectatorplus$renderCursorItem(GuiGraphics guiGraphics, ItemStack stack, int cursorX, int cursorY) { final Slot hoverSlot = this.getHoveredSlot(cursorX + this.leftPos + 8, cursorY + this.topPos + 8); if (hoverSlot != null && hoverSlot.isHighlightable()) { - guiGraphics.blitSprite(RenderType::guiTextured, SLOT_HIGHLIGHT_BACK_SPRITE, hoverSlot.x - 4, hoverSlot.y - 4, 24, 24); + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_HIGHLIGHT_BACK_SPRITE, hoverSlot.x - 4, hoverSlot.y - 4, 24, 24); } this.renderFloatingItem(guiGraphics, stack, cursorX, cursorY, null); if (hoverSlot != null && hoverSlot.isHighlightable()) { - guiGraphics.blitSprite(RenderType::guiTexturedOverlay, SLOT_HIGHLIGHT_FRONT_SPRITE, hoverSlot.x - 4, hoverSlot.y - 4, 24, 24); + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_HIGHLIGHT_FRONT_SPRITE, hoverSlot.x - 4, hoverSlot.y - 4, 24, 24); } } diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index cb31ffd..b49ca9c 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -6,6 +6,7 @@ "ClientLevelAccessor", "ClientLevelMixin", "EntityMixin", + "ExperienceBarRendererMixin", "GameRendererAccessor", "GameRendererMixin", "GuiMixin", 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 80b2e8c..d03126d 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 @@ -43,11 +43,8 @@ public abstract class ServerPlayerMixin extends Player { @Shadow public abstract boolean teleportTo(ServerLevel newLevel, double x, double y, double z, Set relative, float yaw, float pitch, boolean resetCamera); @Shadow public abstract void setCamera(@Nullable Entity entityToSpectate); - public ServerPlayerMixin(Level level, GameProfile gameProfile, BlockPos pos, float yRot) { + public ServerPlayerMixin(Level level, GameProfile gameProfile) { super(level, gameProfile); - // Set initial position and rotation after construction - this.setPos(pos.getX(), pos.getY(), pos.getZ()); - this.setYRot(yRot); } @Inject(method = "doTick()V", at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerPlayer;lastFoodSaturationZero:Z", opcode = Opcodes.PUTFIELD)) 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 f0d56c3..141c452 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 @@ -4,6 +4,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.item.ItemStack; @@ -53,7 +54,9 @@ public static ItemStack readItem(RegistryFriendlyByteBuf buf) { buf.readBytes(in); final CompoundTag tag = NbtIo.readCompressed(new ByteArrayInputStream(in), NbtAccounter.unlimitedHeap()); - return ItemStack.parse(buf.registryAccess(), tag).orElse(ItemStack.EMPTY); + + var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); + return ItemStack.CODEC.parse(registryOps, tag).resultOrPartial().orElse(ItemStack.EMPTY); } catch (IOException e) { throw new EncoderException(e); } @@ -68,7 +71,8 @@ public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) { final byte[] bytes; try { final CompoundTag tag = new CompoundTag(); - item.save(buf.registryAccess(), tag); + var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); + ItemStack.CODEC.encode(item, registryOps, tag).getOrThrow(); final ByteArrayOutputStream out = new ByteArrayOutputStream(); NbtIo.writeCompressed(tag, out); From 6e677b73f0bcf9f5d226cb5fba2e17ae30de3681 Mon Sep 17 00:00:00 2001 From: Connor Slade Date: Sat, 28 Jun 2025 17:40:11 -0400 Subject: [PATCH 12/57] Add run-paper plugin and update .gitignore --- .gitignore | 1 + paper/build.gradle.kts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 5f9d8a4..4e1068b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +run ### IntelliJ IDEA ### .idea/ diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index adde18f..f93da29 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("spectatorplus.platform") id("io.github.goooler.shadow") version "8.1.7" + id("xyz.jpenilla.run-paper") version "2.3.1" } description = "Paper server-side companion for the SpectatorPlus mod" @@ -45,6 +46,10 @@ tasks { enabled = false // only output shadow jar } + runServer { + minecraftVersion("1.21.6") + } + named("build") { dependsOn(shadowJar) } From dcdd9ac37b5632e995a403c67011576221ec04f1 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sun, 29 Jun 2025 12:20:29 +0100 Subject: [PATCH 13/57] update reflection remapper --- paper/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper/gradle.properties b/paper/gradle.properties index 5502716..171d35d 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ paper_version=1.21.6-R0.1-SNAPSHOT -reflection_remapper_version=0.1.2 \ No newline at end of file +reflection_remapper_version=0.1.3 \ No newline at end of file From 3c92f515ca8b156aa7be1f2de1cdabb460a1077a Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Mon, 30 Jun 2025 21:07:58 +0100 Subject: [PATCH 14/57] fix reflection util --- .../com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java index 74a492d..fed081d 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java @@ -75,10 +75,10 @@ private static Object getConnection(Object serverPlayer) throws ReflectiveOperat private static void internalTeleport(Object connection, Location location) throws ReflectiveOperationException { if (ServerGamePacketListenerImpl$internalTeleport == null) { final Class clazz = connection.getClass(); - final Class[] parameterTypes = new Class[]{double.class, double.class, double.class, float.class, float.class, Set.class}; - ServerGamePacketListenerImpl$internalTeleport = clazz.getMethod(REMAPPER.remapMethodName(clazz, "internalTeleport", parameterTypes), parameterTypes); + final Class[] parameterTypes = new Class[]{double.class, double.class, double.class, float.class, float.class}; + ServerGamePacketListenerImpl$internalTeleport = clazz.getMethod(REMAPPER.remapMethodName(clazz, "teleport", parameterTypes), parameterTypes); } - ServerGamePacketListenerImpl$internalTeleport.invoke(connection, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch(), Set.of()); + ServerGamePacketListenerImpl$internalTeleport.invoke(connection, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); } private static Object constructCameraPacket(Object minecraftEntity) throws ReflectiveOperationException { From 26366cbb2ee86f23c4e4cec02d5f989d0c6042f3 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Mon, 30 Jun 2025 21:08:21 +0100 Subject: [PATCH 15/57] update parchment --- fabric/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index a1c83d1..53b4c51 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -6,7 +6,7 @@ loader_version=0.16.14 fabric_version=0.128.1+1.21.6 parchment_minecraft_version=1.21.6 -parchment_version=BLEEDING-20250627.192053-2 +parchment_version=2025.06.29 fabric_permissions_api_version=0.4.0 cloth_config_version=19.0.147 From 5ba7d6f4eb8f4f6885f33d2b2ec898fe4536f0eb Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Tue, 1 Jul 2025 01:10:07 +0100 Subject: [PATCH 16/57] fix xp bar --- .../mixin/ClientWaypointManagerMixin.java | 57 +++++++++++++++++++ .../fabric/client/mixin/GuiMixin.java | 15 +++++ .../client/sync/ClientSyncController.java | 6 ++ .../fabric/client/util/SpecUtil.java | 7 ++- .../spectatorplus.client.mixins.json | 1 + 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java new file mode 100644 index 0000000..3b08a4d --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java @@ -0,0 +1,57 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; +import com.mojang.datafixers.util.Either; +import net.minecraft.client.Minecraft; +import net.minecraft.client.waypoints.ClientWaypointManager; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.waypoints.TrackedWaypoint; +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; + +import java.util.Comparator; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +@Mixin(ClientWaypointManager.class) +public class ClientWaypointManagerMixin { + @Shadow + @Final + private Map, TrackedWaypoint> waypoints; + + @Unique + private boolean shouldIgnore(TrackedWaypoint waypoint) { + var client = Minecraft.getInstance(); + return waypoint.distanceSquared(client.player) < 1.0; + } + + @Inject(method = "hasWaypoints", at = @At("HEAD"), cancellable = true) + private void spectatorplus$hasWaypoints(CallbackInfoReturnable cir) { + if (ClientSyncController.syncData != null) { + for (var waypoint : this.waypoints.values()) { + if (!shouldIgnore(waypoint)) { + cir.setReturnValue(true); + return; + } + } + cir.setReturnValue(false); + } + } + + @Inject(method = "forEachWaypoint", at = @At("HEAD"), cancellable = true) + private void spectatorplus$forEachWaypoint(Entity entity, Consumer action, CallbackInfo ci) { + if (ClientSyncController.syncData != null) { + this.waypoints.values().stream().filter(waypoint -> !shouldIgnore(waypoint)) + .sorted(Comparator.comparingDouble((waypoint) -> ((TrackedWaypoint) waypoint).distanceSquared(entity)) + .reversed()).forEachOrdered(action); + ci.cancel(); + } + } +} \ No newline at end of file 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 9b6b6e9..08fc50b 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 @@ -21,6 +21,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodData; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; import net.minecraft.world.phys.HitResult; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; @@ -35,6 +36,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.Objects; + @Mixin(Gui.class) public abstract class GuiMixin { @Shadow @Final private Minecraft minecraft; @@ -42,6 +45,8 @@ public abstract class GuiMixin { @Shadow protected abstract void renderItemHotbar(GuiGraphics guiGraphics, DeltaTracker deltaTracker); @Shadow protected abstract void renderSelectedItemName(GuiGraphics guiGraphics); + @Shadow @Final private SpectatorGui spectatorGui; + @Redirect(method = "renderCameraOverlays(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isScoping()Z")) private boolean spectatorplus$renderScoping(LocalPlayer instance) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); @@ -106,6 +111,16 @@ public abstract class GuiMixin { return instance.hasExperience(); } + @Redirect(method = "nextContextualInfoState", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/MultiPlayerGameMode;hasExperience()Z")) + private boolean spectatorplus$hasExperience(MultiPlayerGameMode instance) { + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); + if (spectated != null) { + return !spectated.isCreative() && !spectated.isSpectator() && ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && this.spectatorplus$isStatusEnabled(); + } + + return instance.hasExperience(); + } + @Unique private boolean spectatorplus$isStatusEnabled() { if (!SpectatorClientMod.config.renderStatus) { diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index e9d4eb0..84270f1 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -8,6 +8,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.Minecraft; import net.minecraft.world.food.FoodData; import net.minecraft.world.item.ItemStack; @@ -15,6 +16,7 @@ public class ClientSyncController { public static ClientSyncData syncData; + private static Minecraft minecraft = Minecraft.getInstance(); public static void init() { ClientPlayNetworking.registerGlobalReceiver(ClientboundExperienceSyncPacket.TYPE, ClientSyncController::handle); @@ -31,6 +33,10 @@ public static void init() { private static void handle(ClientboundExperienceSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); + var player = minecraft.player; + if (player != null && (packet.progress() != player.experienceProgress || packet.level() != player.experienceLevel)) + player.experienceDisplayStartTick = player.tickCount; + syncData.experienceProgress = packet.progress(); syncData.experienceLevel = packet.level(); syncData.experienceNeededForNextLevel = packet.neededForNextLevel(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java index 65753f5..2c1ae0f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java @@ -1,8 +1,11 @@ package com.hpfxd.spectatorplus.fabric.client.util; +import com.hpfxd.spectatorplus.fabric.client.SpectatorClientMod; +import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.world.level.GameType; +import org.spongepowered.asm.mixin.Unique; public final class SpecUtil { private SpecUtil() { @@ -10,8 +13,8 @@ private SpecUtil() { public static AbstractClientPlayer getCameraPlayer(Minecraft minecraft) { if (minecraft.gameMode != null && minecraft.gameMode.getPlayerMode() == GameType.SPECTATOR - && minecraft.getCameraEntity() instanceof final AbstractClientPlayer spectated - && spectated != minecraft.player) { + && minecraft.getCameraEntity() instanceof final AbstractClientPlayer spectated + && spectated != minecraft.player) { return spectated; } return null; diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index b49ca9c..956bcc1 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -5,6 +5,7 @@ "client": [ "ClientLevelAccessor", "ClientLevelMixin", + "ClientWaypointManagerMixin", "EntityMixin", "ExperienceBarRendererMixin", "GameRendererAccessor", From 8d3b2ff61c6a30a0f4b7f7577570e4e096d53c47 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Tue, 1 Jul 2025 19:24:51 +0100 Subject: [PATCH 17/57] Never show waypoints --- .../client/mixin/ClientWaypointManagerMixin.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java index 3b08a4d..bc7eead 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java @@ -15,7 +15,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.Comparator; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; @@ -35,12 +34,6 @@ private boolean shouldIgnore(TrackedWaypoint waypoint) { @Inject(method = "hasWaypoints", at = @At("HEAD"), cancellable = true) private void spectatorplus$hasWaypoints(CallbackInfoReturnable cir) { if (ClientSyncController.syncData != null) { - for (var waypoint : this.waypoints.values()) { - if (!shouldIgnore(waypoint)) { - cir.setReturnValue(true); - return; - } - } cir.setReturnValue(false); } } @@ -48,9 +41,6 @@ private boolean shouldIgnore(TrackedWaypoint waypoint) { @Inject(method = "forEachWaypoint", at = @At("HEAD"), cancellable = true) private void spectatorplus$forEachWaypoint(Entity entity, Consumer action, CallbackInfo ci) { if (ClientSyncController.syncData != null) { - this.waypoints.values().stream().filter(waypoint -> !shouldIgnore(waypoint)) - .sorted(Comparator.comparingDouble((waypoint) -> ((TrackedWaypoint) waypoint).distanceSquared(entity)) - .reversed()).forEachOrdered(action); ci.cancel(); } } From 149f2d2c7f661b20ea4047fd50c9f63f2e0de77b Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Tue, 1 Jul 2025 19:25:05 +0100 Subject: [PATCH 18/57] show xp bar even if 0 xp --- .../fabric/client/mixin/ExperienceBarRendererMixin.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java index 94478b0..4f771d9 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java @@ -19,7 +19,9 @@ public class ExperienceBarRendererMixin { @Redirect(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getXpNeededForNextLevel()I")) private int spectatorplus$showSyncedExperienceBar(LocalPlayer instance) { if (ClientSyncController.syncData != null && ClientSyncController.syncData.experienceLevel != -1 && SpecUtil.getCameraPlayer(this.minecraft) != null) { - return ClientSyncController.syncData.experienceNeededForNextLevel; + // If experienceNeededForNextLevel is 0, return 1 to ensure the bar renders + int needed = ClientSyncController.syncData.experienceNeededForNextLevel; + return needed > 0 ? needed : 1; } return instance.getXpNeededForNextLevel(); } From 74d501d18c9e676434721b35a2849bfd7dce8909 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Tue, 1 Jul 2025 18:58:05 +0100 Subject: [PATCH 19/57] roll up --- fabric/gradle.properties | 6 +++--- paper/gradle.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 53b4c51..c47fc5a 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,9 +1,9 @@ -minecraft_version=1.21.6 -yarn_mappings=1.21.6+build.1 +minecraft_version=1.21.7 +yarn_mappings=1.21.7+build.1 loader_version=0.16.14 # Fabric API -fabric_version=0.128.1+1.21.6 +fabric_version=0.128.1+1.21.7 parchment_minecraft_version=1.21.6 parchment_version=2025.06.29 diff --git a/paper/gradle.properties b/paper/gradle.properties index 171d35d..9ada3d7 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ -paper_version=1.21.6-R0.1-SNAPSHOT +paper_version=1.21.7-R0.1-SNAPSHOT reflection_remapper_version=0.1.3 \ No newline at end of file From c3264f060fcfd5d1b456feab79fa8c9ac516c98b Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 12 Jul 2025 14:18:56 +0100 Subject: [PATCH 20/57] Add armour and effects to server custom packets --- .../sync/screen/ScreenSyncController.java | 2 + .../paper/effect/EffectType.java | 36 +++++++ .../paper/effect/SyncedEffect.java | 26 +++++ .../paper/sync/ServerSyncController.java | 10 ++ .../spectatorplus/paper/sync/SyncPackets.java | 2 + .../sync/handler/EffectsSyncHandler.java | 68 ++++++++++++ .../sync/handler/InventorySyncHandler.java | 100 ++++++++++++++++-- .../screen/ReplicaSyncedContainer.java | 17 +-- .../packet/ClientboundEffectsSyncPacket.java | 31 ++++++ .../ClientboundInventorySyncPacket.java | 8 +- .../paper/util/SerializationUtil.java | 26 +++++ paper/src/main/resources/paper-plugin.yml | 3 + 12 files changed, 306 insertions(+), 23 deletions(-) create mode 100644 paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java create mode 100644 paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java create mode 100644 paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java create mode 100644 paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java 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 a43e841..cc8c638 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 @@ -61,8 +61,10 @@ private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetw } final ItemStack[] items = packet.items(); + System.out.println("[SpectatorPlus] Received inventory sync packet for player: " + packet.playerId()); for (int slot = 0; slot < items.length; slot++) { final ItemStack item = items[slot]; + System.out.println(" Slot " + slot + ": " + (item == null ? "null" : item.toString())); if (item != null) { syncData.screen.inventoryItems.set(slot, item); diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java new file mode 100644 index 0000000..a8730f9 --- /dev/null +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java @@ -0,0 +1,36 @@ +package com.hpfxd.spectatorplus.paper.effect; + +public enum EffectType { + ABSORPTION, + BAD_OMEN, + BLINDNESS, + CONDUIT_POWER, + DARKNESS, + FIRE_RESISTANCE, + GLOWING, + HASTE, + HEALTH_BOOST, + HERO_OF_THE_VILLAGE, + HUNGER, + INSTANT_DAMAGE, + INSTANT_HEALTH, + INVISIBILITY, + JUMP_BOOST, + LEVITATION, + LUCK, + MINING_FATIGUE, + NAUSEA, + NIGHT_VISION, + POISON, + REGENERATION, + RESISTANCE, + SATURATION, + SLOW_FALLING, + SLOWNESS, + SPEED, + STRENGTH, + UNLUCK, + WATER_BREATHING, + WEAKNESS, + WITHER +} diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java new file mode 100644 index 0000000..d22962a --- /dev/null +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java @@ -0,0 +1,26 @@ +package com.hpfxd.spectatorplus.paper.effect; + +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteArrayDataInput; +import com.hpfxd.spectatorplus.paper.util.SerializationUtil; +import org.bukkit.NamespacedKey; +import org.bukkit.potion.PotionEffectType; + +public record SyncedEffect(String effectKey, int amplifier, int duration) { + public void write(ByteArrayDataOutput buf) { + SerializationUtil.writeString(buf, effectKey); + SerializationUtil.writeVarInt(buf, amplifier); + SerializationUtil.writeVarInt(buf, duration); + } + + public static SyncedEffect read(ByteArrayDataInput buf) { + String effectKey = SerializationUtil.readString(buf); + int amplifier = SerializationUtil.readVarInt(buf); + int duration = SerializationUtil.readVarInt(buf); + return new SyncedEffect(effectKey, amplifier, duration); + } + + public PotionEffectType getType() { + return PotionEffectType.getByKey(NamespacedKey.fromString(effectKey)); + } +} diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java index 816af4e..7b55a33 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java @@ -5,6 +5,7 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.hpfxd.spectatorplus.paper.SpectatorPlugin; +import com.hpfxd.spectatorplus.paper.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.paper.sync.handler.ExperienceSyncHandler; import com.hpfxd.spectatorplus.paper.sync.handler.FoodSyncHandler; import com.hpfxd.spectatorplus.paper.sync.handler.InventorySyncHandler; @@ -27,6 +28,7 @@ public class ServerSyncController implements PluginMessageListener { private final SpectatorPlugin plugin; private final ScreenSyncHandler screenSyncHandler; private final InventorySyncHandler inventorySyncHandler; + private final EffectsSyncHandler effectsSyncHandler; public ServerSyncController(SpectatorPlugin plugin) { this.plugin = plugin; @@ -44,9 +46,13 @@ public ServerSyncController(SpectatorPlugin plugin) { this.screenSyncHandler = new ScreenSyncHandler(plugin); new ExperienceSyncHandler(plugin); new FoodSyncHandler(plugin); + this.inventorySyncHandler = new InventorySyncHandler(plugin); new SelectedSlotSyncHandler(plugin); new MapSyncHandler(plugin); + + this.effectsSyncHandler = new EffectsSyncHandler(plugin); + } public void sendPacket(Player receiver, ClientboundSyncPacket packet) { @@ -86,6 +92,10 @@ public InventorySyncHandler getInventorySyncHandler() { return this.inventorySyncHandler; } + public EffectsSyncHandler getEffectsSyncHandler() { + return this.effectsSyncHandler; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message) { final NamespacedKey key = NamespacedKey.fromString(channel); diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java index 65b14dd..fc78e8a 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteArrayDataInput; +import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundEffectsSyncPacket; import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundHotbarSyncPacket; @@ -25,6 +26,7 @@ public final class SyncPackets { .put(ClientboundScreenCursorSyncPacket.ID, ClientboundScreenCursorSyncPacket.class) .put(ClientboundScreenSyncPacket.ID, ClientboundScreenSyncPacket.class) .put(ClientboundSelectedSlotSyncPacket.ID, ClientboundSelectedSlotSyncPacket.class) + .put(ClientboundEffectsSyncPacket.ID, ClientboundEffectsSyncPacket.class) .build(); public static final Map> SERVERBOUND = ImmutableMap.>builder() diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java new file mode 100644 index 0000000..3040138 --- /dev/null +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java @@ -0,0 +1,68 @@ +package com.hpfxd.spectatorplus.paper.sync.handler; + +import com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent; +import com.hpfxd.spectatorplus.paper.SpectatorPlugin; +import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundEffectsSyncPacket; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPotionEffectEvent; +import org.bukkit.potion.PotionEffect; + +import java.util.List; +import java.util.stream.Collectors; +import com.hpfxd.spectatorplus.paper.effect.EffectType; + +public class EffectsSyncHandler implements Listener { + private static final String PERMISSION = "spectatorplus.sync.effects"; + + private final SpectatorPlugin plugin; + + public EffectsSyncHandler(SpectatorPlugin plugin) { + this.plugin = plugin; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + private java.util.List getSyncedEffects(Player player) { + java.util.List effects = java.util.List.copyOf(player.getActivePotionEffects()); + if (effects.isEmpty()) { + this.plugin.getSLF4JLogger().info("[SpectatorPlus] No active effects for {}", player.getName()); + } else { + for (PotionEffect effect : effects) { + this.plugin.getSLF4JLogger().info("[SpectatorPlus] Effect for {}: type={}, key={}, amplifier={}, duration={}", player.getName(), effect.getType().getName(), effect.getType().getKey(), effect.getAmplifier(), effect.getDuration()); + } + } + return effects.stream() + .map(pe -> new com.hpfxd.spectatorplus.paper.effect.SyncedEffect( + pe.getType().getKey().toString(), + pe.getAmplifier(), + pe.getDuration() + )) + .collect(java.util.stream.Collectors.toList()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onStartSpectatingEntity(PlayerStartSpectatingEntityEvent event) { + final Player spectator = event.getPlayer(); + if (event.getNewSpectatorTarget() instanceof final Player target && spectator.hasPermission(PERMISSION)) { + java.util.List effects = getSyncedEffects(target); + this.plugin.getSLF4JLogger().info("[SpectatorPlus] Sending effects sync packet to {}: {}", spectator.getName(), effects); + this.plugin.getSyncController().sendPacket(spectator, + new ClientboundEffectsSyncPacket(target.getUniqueId(), effects)); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPotionEffectChange(EntityPotionEffectEvent event) { + if (event.getEntity() instanceof Player player) { + Bukkit.getScheduler().runTask(this.plugin, () -> { + java.util.List effects = getSyncedEffects(player); + this.plugin.getSLF4JLogger().info("[SpectatorPlus] Broadcasting effects sync packet for {}: {}", player.getName(), effects); + this.plugin.getSyncController().broadcastPacketToSpectators(player, PERMISSION, + new ClientboundEffectsSyncPacket(player.getUniqueId(), effects)); + }); + } + } +} diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java index a20a960..0c68719 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java @@ -13,6 +13,9 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -46,7 +49,8 @@ public void tick() { boolean updatedHotbar = false; boolean updatedInventory = false; - for (int i = 0; i < ClientboundInventorySyncPacket.ITEMS_LENGTH; i++) { + // Main inventory (0-35) + for (int i = 0; i < 36; i++) { ItemStack item = player.getInventory().getItem(i); if (item == null) { item = ItemStack.empty(); @@ -54,16 +58,37 @@ public void tick() { if (!item.equals(slots[i])) { slots[i] = item.clone(); - inventorySendSlots[i] = item; updatedInventory = true; - if (i < ClientboundHotbarSyncPacket.ITEMS_LENGTH) { hotbarSendSlots[i] = item; updatedHotbar = true; } } } + // Armor (36-39) + for (int i = 0; i < 4; i++) { + ItemStack item = player.getInventory().getArmorContents()[i]; + if (item == null) { + item = ItemStack.empty(); + } + int idx = 36 + i; + if (!item.equals(slots[idx])) { + slots[idx] = item.clone(); + inventorySendSlots[idx] = item; + updatedInventory = true; + } + } + // Offhand (slot 40) + ItemStack offhand = player.getInventory().getItemInOffHand(); + if (offhand == null) { + offhand = ItemStack.empty(); + } + if (!offhand.equals(slots[40])) { + slots[40] = offhand.clone(); + inventorySendSlots[40] = offhand; + updatedInventory = true; + } if (updatedInventory) { this.plugin.getSyncController().getScreenSyncHandler().updatePlayerInventory(player, inventorySendSlots); @@ -77,15 +102,44 @@ public void tick() { public void sendInventory(Player spectator, PlayerInventory inventory) { final ItemStack[] slots = new ItemStack[ClientboundInventorySyncPacket.ITEMS_LENGTH]; - for (int slot = 0; slot < slots.length; slot++) { - ItemStack item = inventory.getItem(slot); + // Main inventory (0-35) + for (int i = 0; i < 36; i++) { + ItemStack item = inventory.getItem(i); if (item == null) { item = ItemStack.empty(); } - - slots[slot] = item; + slots[i] = item; } + // Armor (36-39) + ItemStack[] armor = inventory.getArmorContents(); + for (int i = 0; i < 4; i++) { + ItemStack item = armor[i]; + if (item == null) { + item = ItemStack.empty(); + } + slots[36 + i] = item; + } + // Offhand (slot 40) + ItemStack offhand = inventory.getItemInOffHand(); + if (offhand == null) { + offhand = ItemStack.empty(); + } + slots[40] = offhand; + this.plugin.getSyncController().sendPacket(spectator, new ClientboundInventorySyncPacket(inventory.getHolder().getUniqueId(), slots)); + } + public void sendArmour(Player spectator, PlayerInventory inventory) { + final ItemStack[] slots = new ItemStack[ClientboundInventorySyncPacket.ITEMS_LENGTH]; + Arrays.fill(slots, ItemStack.empty()); + ItemStack[] armor = inventory.getArmorContents(); + for (int i = 0; i < 4; i++) { + ItemStack item = armor[i]; + if (item == null) { + item = ItemStack.empty(); + } + slots[36 + i] = item; + } + // Send the custom slots array directly as a packet this.plugin.getSyncController().sendPacket(spectator, new ClientboundInventorySyncPacket(inventory.getHolder().getUniqueId(), slots)); } @@ -106,6 +160,38 @@ public void onStartSpectatingEntity(PlayerStartSpectatingEntityEvent event) { this.plugin.getSyncController().sendPacket(spectator, new ClientboundHotbarSyncPacket(target.getUniqueId(), slots)); } + if (event.getNewSpectatorTarget() instanceof final Player target && spectator.hasPermission(INVENTORY_PERMISSION)) { + // Send only the armor slots to the spectator + this.sendArmour(spectator, target.getInventory()); + } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerArmorChange(PlayerArmorChangeEvent event) { + // Send updated armor to all spectators with permission + Player player = event.getPlayer(); + for (Player spectator : this.plugin.getSyncController().getSpectators(player, INVENTORY_PERMISSION)) { + this.sendArmour(spectator, player.getInventory()); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerItemDamage(PlayerItemDamageEvent event) { + // If the damaged item is armor, send updated armor to all spectators with permission + Player player = event.getPlayer(); + ItemStack damaged = event.getItem(); + // Check if the damaged item is currently equipped as armor + boolean isArmor = false; + for (ItemStack armor : player.getInventory().getArmorContents()) { + if (armor != null && armor.equals(damaged)) { + isArmor = true; + break; + } + } + if (isArmor) { + for (Player spectator : this.plugin.getSyncController().getSpectators(player, INVENTORY_PERMISSION)) { + this.sendArmour(spectator, player.getInventory()); + } + } } @EventHandler diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java index d05fa4e..6395d42 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java @@ -34,21 +34,8 @@ public void update() { spectatorInventory.setItem(slot, targetInventory.getItem(slot)); } - // Note: The Bukkit InventoryView.Property API is flawed. Each property can only apply to one InventoryType, - // but they should apply to multiple. Example: The COOK_TIME property should be able to be set on - // inventories with the types: FURNACE, BLAST_FURNACE, and SMOKER, but it only works on FURNACE. - // TODO could use NMS to set the properties instead of Bukkit's API, but it doesn't really matter since I think - // the only type this really effects is FURNACE, which is handled as a DirectSyncedContainer anyway. - - if (!this.getPropertiesFailed) { - try { - // Sync all data slots (called a "Property" in Bukkit) - ReflectionUtil.getContainerProperties(this.targetView).forEach(this.spectatorView::setProperty); - } catch (ReflectiveOperationException e) { - this.getPropertiesFailed = true; - JavaPlugin.getPlugin(SpectatorPlugin.class).getSLF4JLogger().warn("Failed to retrieve container properties for a replica container", e); - } - } + // Note: The Bukkit InventoryView.Property API is deprecated and removed in 1.21+. There is no way to get all properties for a container. This is a limitation in Bukkit. + // If you need to sync properties, you must use NMS or another workaround. } @Override diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java new file mode 100644 index 0000000..af957ae --- /dev/null +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java @@ -0,0 +1,31 @@ +package com.hpfxd.spectatorplus.paper.sync.packet; + +import com.google.common.io.ByteArrayDataOutput; +import com.hpfxd.spectatorplus.paper.sync.ClientboundSyncPacket; +import com.hpfxd.spectatorplus.paper.util.SerializationUtil; +import org.bukkit.NamespacedKey; + +import java.util.List; +import java.util.UUID; +import com.hpfxd.spectatorplus.paper.effect.SyncedEffect; + +public record ClientboundEffectsSyncPacket( + UUID playerId, + List effects // List of effect data +) implements ClientboundSyncPacket { + public static final NamespacedKey ID = new NamespacedKey("spectatorplus", "effects_sync"); + + @Override + public void write(ByteArrayDataOutput buf) { + SerializationUtil.writeUuid(buf, this.playerId); + SerializationUtil.writeVarInt(buf, effects.size()); + for (SyncedEffect effect : effects) { + effect.write(buf); + } + } + + @Override + public NamespacedKey channel() { + return ID; + } +} diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java index cac28e0..0818cd4 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java @@ -8,11 +8,17 @@ import java.util.UUID; +/** + * inventoryItems layout: + * 0-35: main inventory (including hotbar) + * 36-39: armor (helmet, chestplate, leggings, boots) + */ public record ClientboundInventorySyncPacket( UUID playerId, ItemStack[] inventoryItems ) implements ClientboundSyncPacket { - public static final int ITEMS_LENGTH = 4 * 9; + // 0-35: main inventory, 36-39: armor, 40: offhand + public static final int ITEMS_LENGTH = 4 * 9 + 4 + 1; public static final NamespacedKey ID = new NamespacedKey("spectatorplus", "inventory_sync"); @Override diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java index 5fd9066..970ca8a 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java @@ -49,4 +49,30 @@ public static void writeItem(ByteArrayDataOutput buf, ItemStack item) { buf.write(itemData); } } + public static void writeString(ByteArrayDataOutput out, String value) { + byte[] bytes = value.getBytes(java.nio.charset.StandardCharsets.UTF_8); + writeVarInt(out, bytes.length); + out.write(bytes); + } + + public static String readString(ByteArrayDataInput in) { + int length = readVarInt(in); + byte[] bytes = new byte[length]; + in.readFully(bytes); + return new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + } + + public static int readVarInt(ByteArrayDataInput in) { + int value = 0; + int position = 0; + byte currentByte; + while (true) { + currentByte = in.readByte(); + value |= (currentByte & 0x7F) << position; + if ((currentByte & 0x80) == 0) break; + position += 7; + if (position > 35) throw new RuntimeException("VarInt too big"); + } + return value; + } } diff --git a/paper/src/main/resources/paper-plugin.yml b/paper/src/main/resources/paper-plugin.yml index 413cecb..34d9e6e 100644 --- a/paper/src/main/resources/paper-plugin.yml +++ b/paper/src/main/resources/paper-plugin.yml @@ -24,3 +24,6 @@ permissions: spectatorplus.sync.screen: description: "Allows the player to receive the open screen of the spectated target (e.g. a chest)" default: true + spectatorplus.sync.effects: + description: "Allows the player to receive the active potion effects of the spectated target" + default: true \ No newline at end of file From 8bb65de55c95f51d51ff994d7f26040dba61a94d Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 12 Jul 2025 14:25:16 +0100 Subject: [PATCH 21/57] added amour and effects to the giu --- .../gui/screens/SyncedInventoryScreen.java | 15 +-- .../client/mixin/GameRendererMixin.java | 18 +++ .../fabric/client/mixin/GuiMixin.java | 108 ++++++++++++++++-- .../client/mixin/LivingEntityMixin.java | 51 +++++++++ .../client/sync/ClientSyncController.java | 42 ++++++- .../fabric/client/sync/ClientSyncData.java | 4 + .../sync/screen/ScreenSyncController.java | 17 ++- .../client/sync/screen/ScreenSyncData.java | 5 + .../fabric/client/util/SpecUtil.java | 17 +++ .../fabric/sync/SyncPackets.java | 2 + .../fabric/sync/SyncedEffect.java | 36 ++++++ .../packet/ClientboundEffectsSyncPacket.java | 48 ++++++++ .../ClientboundInventorySyncPacket.java | 7 +- 13 files changed, 348 insertions(+), 22 deletions(-) create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java 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 5a9f92c..0ca8ea0 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 @@ -34,15 +34,16 @@ public void containerTick() { private void syncOtherItems() { final SyncedInventoryMenu menu = (SyncedInventoryMenu) this.menu; - - final Inventory playerInventory = menu.getOwner().getInventory(); final Inventory fakeInventory = menu.getInventory(); - final EntityEquipment playerEquipment = ((InventoryAccessor) (menu.getOwner().getInventory())).getEquipment(); - final EntityEquipment fakeEquipment = ((InventoryAccessor) (menu.getInventory())).getEquipment(); - - fakeEquipment.setAll(playerEquipment); - fakeInventory.setItem(Inventory.SLOT_OFFHAND, playerInventory.getItem(Inventory.SLOT_OFFHAND)); + // Use synced inventory data for all slots (main, armor, offhand) + var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; + if (syncData != null && syncData.screen != null && syncData.screen.inventoryItems != null) { + var items = syncData.screen.inventoryItems; + for (int i = 0; i < items.size(); i++) { + fakeInventory.setItem(i, items.get(i)); + } + } } @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 fe9e30e..1643d94 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 @@ -31,6 +31,7 @@ @Mixin(GameRenderer.class) public abstract class GameRendererMixin { + @Shadow @Final private Minecraft minecraft; @Shadow @Final private LightTexture lightTexture; @Shadow @Final private RenderBuffers renderBuffers; @@ -121,6 +122,22 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender private void spectatorplus$tick(CallbackInfo ci) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); if (spectated != null) { + // effect handling + // Force-add all effects from syncData.effects + // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; + // if (syncData != null && syncData.effects != null && this.minecraft.player != null) { + // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : syncData.effects) { + // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); + // if (optHolder.isPresent()) { + // net.minecraft.world.effect.MobEffect effect = optHolder.get().value(); + // net.minecraft.core.Holder holder = net.minecraft.core.Holder.direct(effect); + // net.minecraft.world.effect.MobEffectInstance instance = new net.minecraft.world.effect.MobEffectInstance(holder, synced.duration, synced.amplifier); + // this.minecraft.player.forceAddEffect(instance, this.minecraft.player); + // } + // } + // } + + // View bobbing // get horizontal distance between current and last pos @@ -188,4 +205,5 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender return original; } + } 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 08fc50b..1099890 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 @@ -3,11 +3,13 @@ import com.hpfxd.spectatorplus.fabric.client.SpectatorClientMod; import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.ModifyReceiver; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; + import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; @@ -16,13 +18,13 @@ import net.minecraft.client.multiplayer.MultiPlayerGameMode; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodData; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.GameType; import net.minecraft.world.phys.HitResult; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.world.entity.EquipmentSlot; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -35,18 +37,38 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.Objects; +import net.minecraft.resources.ResourceLocation; @Mixin(Gui.class) public abstract class GuiMixin { + // Local copy of vanilla overlay resource location @Shadow @Final private Minecraft minecraft; @Shadow public abstract SpectatorGui getSpectatorGui(); @Shadow protected abstract void renderItemHotbar(GuiGraphics guiGraphics, DeltaTracker deltaTracker); - @Shadow protected abstract void renderSelectedItemName(GuiGraphics guiGraphics); @Shadow @Final private SpectatorGui spectatorGui; + // Use correct ResourceLocations for vanilla empty armor slot icons from the GUI atlas + private static final ResourceLocation EMPTY_ARMOR_SLOT_HELMET = ResourceLocation.withDefaultNamespace("container/slot/helmet"); + private static final ResourceLocation EMPTY_ARMOR_SLOT_CHESTPLATE = ResourceLocation.withDefaultNamespace("container/slot/chestplate"); + private static final ResourceLocation EMPTY_ARMOR_SLOT_LEGGINGS = ResourceLocation.withDefaultNamespace("container/slot/leggings"); + private static final ResourceLocation EMPTY_ARMOR_SLOT_BOOTS = ResourceLocation.withDefaultNamespace("container/slot/boots"); + private static final ResourceLocation EFFECT_BACKGROUND_AMBIENT_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background_ambient"); + private static final ResourceLocation EFFECT_BACKGROUND_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background"); + + private static final ResourceLocation[] TEXTURE_EMPTY_SLOTS = new ResourceLocation[]{ + EMPTY_ARMOR_SLOT_BOOTS, EMPTY_ARMOR_SLOT_LEGGINGS, EMPTY_ARMOR_SLOT_CHESTPLATE, EMPTY_ARMOR_SLOT_HELMET + }; + + @Inject( + method = "renderEffects(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", + at = @At("HEAD"), + cancellable = true + ) + private void spectatorplus$cancelRenderEffects(GuiGraphics guiGraphics, DeltaTracker deltaTracker, CallbackInfo ci) { + ci.cancel(); + } + @Redirect(method = "renderCameraOverlays(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isScoping()Z")) private boolean spectatorplus$renderScoping(LocalPlayer instance) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); @@ -67,12 +89,20 @@ public abstract class GuiMixin { @Redirect(method = "renderCameraOverlays(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getTicksFrozen()I")) private int spectatorplus$renderFreezeOverlay(LocalPlayer instance) { - return this.minecraft.getCameraEntity().getTicksFrozen(); + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); + if (spectated != null) { + return spectated.getTicksFrozen(); + } + return instance.getTicksFrozen(); } @Redirect(method = "renderCameraOverlays(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getPercentFrozen()F")) private float spectatorplus$renderFreezeOverlayPercent(LocalPlayer instance) { - return this.minecraft.getCameraEntity().getPercentFrozen(); + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); + if (spectated != null) { + return spectated.getPercentFrozen(); + } + return instance.getPercentFrozen(); } @Inject(method = "renderHotbarAndDecorations(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/spectator/SpectatorGui;renderHotbar(Lnet/minecraft/client/gui/GuiGraphics;)V")) @@ -86,7 +116,57 @@ public abstract class GuiMixin { this.renderItemHotbar(guiGraphics, deltaTracker); } - this.renderSelectedItemName(guiGraphics); + // Render all spectatee's armor in the top right: helmet, chestplate, leggings, boots + if (ClientSyncController.syncData != null && ClientSyncController.syncData.armorItems != null) { + int spacing = 1; // vertical spacing between items + int itemWidth = 16; // standard item icon width + int itemHeight = 16; // standard item icon height + int baseY = 2; + int baseX = this.minecraft.getWindow().getGuiScaledWidth() - itemWidth - 2; + // Armor order: helmet, chestplate, leggings, boots (reverse to boots, leggings, chestplate, helmet) + EquipmentSlot[] slots = {EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + for (int i = 0; i < slots.length; i++) { + int idx = ClientSyncController.syncData.armorItems.size() - 1 - i; + ItemStack armorStack = idx >= 0 && idx < ClientSyncController.syncData.armorItems.size() ? ClientSyncController.syncData.armorItems.get(idx) : ItemStack.EMPTY; + int y = baseY + i * (itemHeight + spacing); + boolean isAir = armorStack == null || armorStack.isEmpty() || armorStack.getItem() == net.minecraft.world.item.Items.AIR; + if (isAir) { + // Show empty slot icon if no item + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE_EMPTY_SLOTS[idx], baseX, y, itemWidth, itemHeight); + } else { + // Show item icon if present + guiGraphics.renderItem(armorStack, baseX, y); + // Draw durability % if item is damageable + if (armorStack.isDamageableItem() && armorStack.getMaxDamage() > 0) { + int durability = armorStack.getMaxDamage() - armorStack.getDamageValue(); + int percent = (int) ((durability * 100.0) / armorStack.getMaxDamage()); + String text = percent + "%"; + // Right-align the text to the left of the icon + int textWidth = this.minecraft.font.width(text); + int textX = baseX - spacing - textWidth; // right-aligned to the left of the item + int textY = y + 4; // vertically centered + guiGraphics.drawString(this.minecraft.font, text, textX, textY, 0xFFFFFFFF, true); + } + } + } + + int effectBaseY = baseY + slots.length * (itemHeight + spacing) + 4; // start below armor + + // Render all active effect icons down the right side below armor + if (ClientSyncController.syncData.effects != null && !ClientSyncController.syncData.effects.isEmpty()) { + + for (int i = 0; i < ClientSyncController.syncData.effects.size(); i++) { + SyncedEffect effect = ClientSyncController.syncData.effects.get(i); + int y = effectBaseY + i * (itemWidth + spacing); + + // Draw vanilla effect background + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, EFFECT_BACKGROUND_SPRITE, baseX, y, itemWidth, itemHeight); + + ResourceLocation effectIcon = GuiMixin.getEffectIcon(effect.effectKey); + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX+2, y+2, itemWidth-4, itemHeight-4); + } + } + } } } } @@ -216,4 +296,16 @@ public abstract class GuiMixin { } return instance; } + // Map EffectType to vanilla effect icon ResourceLocation + private static ResourceLocation getEffectIcon(String effectKey) { + // If effectKey contains a namespace (e.g., minecraft:nausea), strip it + String key = effectKey; + int colonIdx = key.indexOf(":"); + if (colonIdx != -1) { + key = key.substring(colonIdx + 1); + } + // Vanilla effect icons are in the GUI atlas as effect/ + // The effectKey should be lowercase, matching the registry name + return ResourceLocation.withDefaultNamespace("mob_effect/" + key.toLowerCase()); + } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index a54e9a3..9f316bb 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -3,18 +3,27 @@ import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.core.Holder; import net.minecraft.world.InteractionHand; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.phys.HitResult; + +import java.util.Collection; + +import org.jetbrains.annotations.Nullable; 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.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends Entity { @@ -32,6 +41,48 @@ public LivingEntityMixin(EntityType entityType, Level level) { } } + // @Inject(method = "hasEffect(Lnet/minecraft/core/Holder;)Z", at = @At("HEAD"), cancellable = true) + // private void overrideNauseaEffect(Holder effect, CallbackInfoReturnable cir) { + // System.out.println("[SpectatorPlus] hasEffect called. Entity type: " + this.getClass().getName() + ", effect: " + + // effect.value().getDescriptionId() + " player: " + this.getName().getString()); + // if ((Entity) this instanceof Player) { + // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; + // if (syncData != null && syncData.effects != null) { + // boolean found = syncData.effects.stream().anyMatch(synced -> { + // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); + // return optHolder.isPresent() && optHolder.get().value().equals(effect.value()); + // }); + // if (found) { + // System.out.println("[SpectatorPlus] hasEffect: Synced effect found for player: " + this.getName().getString()); + // cir.setReturnValue(true); + // } + // } + // } + // } + + // @Inject(method = "getActiveEffects", at = @At("HEAD"), cancellable = true) + // private void overrideActiveEffects(CallbackInfoReturnable> cir) { + // System.out.println("[SpectatorPlus] getActiveEffects called. Entity type: " + this.getClass().getName()); + // if ((Entity) this instanceof Player) { + // System.out.println("[SpectatorPlus] Injecting effects from syncdata for player: " + this.getName().getString()); + // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; + // if (syncData != null && syncData.effects != null) { + // java.util.List instances = new java.util.ArrayList<>(); + // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : syncData.effects) { + // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); + // if (optHolder.isPresent()) { + // MobEffect effect = optHolder.get().value(); + // net.minecraft.core.Holder holder = net.minecraft.core.Holder.direct(effect); + // MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); + // instances.add(instance); + // } + // } + // System.out.println("[SpectatorPlus] Injecting synced effects for player: " + this.getName().getString() + " -> " + instances); + // cir.setReturnValue(instances); + // } + // } + // } + @Unique private boolean isBreakingBlock() { return ((LevelRendererAccessor) Minecraft.getInstance().levelRenderer).getDestroyingBlocks().containsKey(this.getId()); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index 84270f1..de56cb7 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -4,7 +4,16 @@ import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundInventorySyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundSelectedSlotSyncPacket; +import java.util.List; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; @@ -12,6 +21,7 @@ import net.minecraft.world.food.FoodData; import net.minecraft.world.item.ItemStack; +import java.util.ArrayList; import java.util.UUID; public class ClientSyncController { @@ -23,13 +33,43 @@ public static void init() { ClientPlayNetworking.registerGlobalReceiver(ClientboundFoodSyncPacket.TYPE, ClientSyncController::handle); ClientPlayNetworking.registerGlobalReceiver(ClientboundHotbarSyncPacket.TYPE, ClientSyncController::handle); ClientPlayNetworking.registerGlobalReceiver(ClientboundSelectedSlotSyncPacket.TYPE, ClientSyncController::handle); - + ClientPlayNetworking.registerGlobalReceiver(ClientboundEffectsSyncPacket.TYPE, ClientSyncController::handle); ClientLoginConnectionEvents.INIT.register((handler, client) -> setSyncData(null)); ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> setSyncData(null)); ScreenSyncController.init(); } + private static void handle(ClientboundEffectsSyncPacket packet, ClientPlayNetworking.Context context) { + setSyncData(packet.playerId()); + syncData.effects = packet.effects(); // Now List + System.out.println("[SpectatorPlus] Synced effects: " + syncData.effects); + + // var client = Minecraft.getInstance(); + // if (client.player != null) { + // // Remove all current effects from the client player + // List> toRemove = new ArrayList<>(client.player.getActiveEffectsMap().keySet()); + // for (Holder effect : toRemove) { + // System.out.println("[SpectatorPlus] Removing effect: " + BuiltInRegistries.MOB_EFFECT.getKey(effect.value())); + // client.player.removeEffect(effect); + // } + // // Add all synced effects to the client player + // for (SyncedEffect synced : syncData.effects) { + // System.out.println("[SpectatorPlus] Syncing effect: " + synced.effectKey + " duration=" + synced.duration + " amplifier=" + synced.amplifier); + // java.util.Optional> optHolder = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.tryParse(synced.effectKey)); + // if (optHolder.isPresent()) { + // MobEffect effect = optHolder.get().value(); + // Holder holder = Holder.direct(effect); + // MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); + // client.player.forceAddEffect(instance, client.player); + // System.out.println("[SpectatorPlus] Added effect: " + synced.effectKey); + // } else { + // System.out.println("[SpectatorPlus] Effect not found in registry: " + synced.effectKey); + // } + // } + // } + } + private static void handle(ClientboundExperienceSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java index 6c4d394..ec15e27 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java @@ -6,11 +6,13 @@ import net.minecraft.world.item.ItemStack; import java.util.UUID; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; public class ClientSyncData { public final UUID playerId; public final NonNullList hotbarItems = NonNullList.withSize(9, ItemStack.EMPTY); + public final NonNullList armorItems = NonNullList.withSize(4, ItemStack.EMPTY); public int selectedHotbarSlot = -1; public FoodData foodData; @@ -20,6 +22,8 @@ public class ClientSyncData { public ScreenSyncData screen; + public java.util.List effects = java.util.Collections.emptyList(); + public ClientSyncData(UUID playerId) { this.playerId = playerId; } 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 cc8c638..19fc69d 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 @@ -56,24 +56,29 @@ private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetw setSyncData(packet.playerId()); syncData.setScreen(); - if (syncData.screen.inventoryItems == null) { + if (syncData.screen.inventoryItems == null || syncData.screen.inventoryItems.size() != ClientboundInventorySyncPacket.ITEMS_LENGTH) { syncData.screen.inventoryItems = NonNullList.withSize(ClientboundInventorySyncPacket.ITEMS_LENGTH, ItemStack.EMPTY); } final ItemStack[] items = packet.items(); - System.out.println("[SpectatorPlus] Received inventory sync packet for player: " + packet.playerId()); for (int slot = 0; slot < items.length; slot++) { final ItemStack item = items[slot]; - System.out.println(" Slot " + slot + ": " + (item == null ? "null" : item.toString())); - if (item != null) { syncData.screen.inventoryItems.set(slot, item); - if (syncedInventory != null) { syncedInventory.setItem(slot, item); } } } + // Also update global armorItems for convenience + if (syncData.armorItems != null && items.length >= 40) { + for (int slot = 36; slot < 40; slot++) { + final ItemStack item = items[slot]; + if (item != null) { + syncData.armorItems.set(slot - 36, item); + } + } + } } private static void handle(ClientboundScreenCursorSyncPacket packet, ClientPlayNetworking.Context context) { @@ -127,9 +132,11 @@ public static boolean createInventory(Player spectated) { EntityEquipment equipment = ((InventoryAccessor)spectated.getInventory()).getEquipment(); syncedInventory = new Inventory(spectated, equipment); + // Set all inventory slots (0-39) for (int i = 0; i < syncData.screen.inventoryItems.size(); i++) { syncedInventory.setItem(i, syncData.screen.inventoryItems.get(i)); } + // No direct access to armor list; setting slots 36-39 is sufficient for GUI return true; } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java index cff3354..8e69bac 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java @@ -4,6 +4,11 @@ import net.minecraft.world.item.ItemStack; public class ScreenSyncData { + /** + * inventoryItems layout: + * 0-35: main inventory (including hotbar) + * 36-39: armor (helmet, chestplate, leggings, boots) + */ public NonNullList inventoryItems; public boolean isSurvivalInventory; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java index 2c1ae0f..bf3766f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java @@ -4,8 +4,11 @@ import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.GameType; import org.spongepowered.asm.mixin.Unique; +import net.minecraft.core.Holder; +import net.minecraft.world.effect.MobEffect; public final class SpecUtil { private SpecUtil() { @@ -19,4 +22,18 @@ public static AbstractClientPlayer getCameraPlayer(Minecraft minecraft) { } return null; } + + /** + * Returns the synced MobEffectInstance for the given entity and effect, or null if not found. + */ + public static net.minecraft.world.effect.MobEffectInstance getSyncedEffect(LivingEntity entity, Holder effect) { + if (ClientSyncController.syncData == null || ClientSyncController.syncData.effects == null) return null; + String queryKey = effect.unwrapKey().get().location().toString(); + for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : ClientSyncController.syncData.effects) { + if (queryKey.equals(synced.effectKey)) { + return new net.minecraft.world.effect.MobEffectInstance(effect, synced.duration, synced.amplifier); + } + } + return null; + } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java index 226e3b8..eee14b5 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -23,6 +24,7 @@ public static void registerAll() { PayloadTypeRegistry.playS2C().register(ClientboundScreenCursorSyncPacket.TYPE, ClientboundScreenCursorSyncPacket.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ClientboundScreenSyncPacket.TYPE, ClientboundScreenSyncPacket.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ClientboundSelectedSlotSyncPacket.TYPE, ClientboundSelectedSlotSyncPacket.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundEffectsSyncPacket.TYPE, ClientboundEffectsSyncPacket.STREAM_CODEC); } private SyncPackets() { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java new file mode 100644 index 0000000..17b2234 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java @@ -0,0 +1,36 @@ +package com.hpfxd.spectatorplus.fabric.sync; + +import net.minecraft.core.Holder; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.effect.MobEffect; + +import java.util.ArrayList; +import java.util.List; + +public class SyncedEffect { + public final String effectKey; + public final int amplifier; + public final int duration; + + public SyncedEffect(String effectKey, int amplifier, int duration) { + this.effectKey = effectKey; + this.amplifier = amplifier; + this.duration = duration; + } + + public static SyncedEffect read(FriendlyByteBuf buf) { + String effectKey = buf.readUtf(); + int amplifier = buf.readVarInt(); + int duration = buf.readVarInt(); + return new SyncedEffect(effectKey, amplifier, duration); + } + + public static List readList(FriendlyByteBuf buf) { + int size = buf.readVarInt(); + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(SyncedEffect.read(buf)); + } + return list; + } +} diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java new file mode 100644 index 0000000..cc9e68c --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java @@ -0,0 +1,48 @@ + +package com.hpfxd.spectatorplus.fabric.sync.packet; + +import com.hpfxd.spectatorplus.fabric.sync.ClientboundSyncPacket; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; + +public record ClientboundEffectsSyncPacket( + UUID playerId, + List effects +) implements ClientboundSyncPacket { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundEffectsSyncPacket::write, ClientboundEffectsSyncPacket::new); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:effects_sync")); + private static final String PERMISSION = "spectatorplus.sync.effects"; + + public ClientboundEffectsSyncPacket(FriendlyByteBuf buf) { + this(buf.readUUID(), SyncedEffect.readList(buf)); + } + + public void write(FriendlyByteBuf buf) { + buf.writeUUID(this.playerId); + buf.writeVarInt(this.effects.size()); + for (SyncedEffect effect : this.effects) { + buf.writeUtf(effect.effectKey); + buf.writeVarInt(effect.amplifier); + buf.writeVarInt(effect.duration); + } + } + + @Override + public @NotNull Type type() { + return TYPE; + } + + @Override + public boolean canSend(ServerPlayer receiver) { + return Permissions.check(receiver, PERMISSION, true); + } +} diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java index 6f89618..9741792 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java @@ -13,13 +13,18 @@ import java.util.UUID; +/** + * items layout: + * 0-35: main inventory (including hotbar) + * 36-39: armor (helmet, chestplate, leggings, boots) + */ public record ClientboundInventorySyncPacket( UUID playerId, ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundInventorySyncPacket::write, ClientboundInventorySyncPacket::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:inventory_sync")); - public static final int ITEMS_LENGTH = 4 * 9; + public static final int ITEMS_LENGTH = 4 * 9 + 4 + 1; // 36 main + 4 armor + 1 offhand = 41 private static final String PERMISSION = "spectatorplus.sync.inventory"; public ClientboundInventorySyncPacket(RegistryFriendlyByteBuf buf) { From 4bb4fa75714b5e21db62bf3cd8a0cd9005669e70 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Fri, 3 Oct 2025 19:45:22 +0100 Subject: [PATCH 22/57] fix --- .../client/mixin/GameRendererMixin.java | 16 ---- .../fabric/client/mixin/GuiMixin.java | 96 ++++++++++++++++--- .../client/mixin/LivingEntityMixin.java | 42 -------- .../resources/spectatorplus.accesswidener | 2 + .../sync/handler/EffectsSyncHandler.java | 90 +++++++++++------ 5 files changed, 144 insertions(+), 102 deletions(-) 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 1643d94..bf25eaa 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 @@ -122,22 +122,6 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender private void spectatorplus$tick(CallbackInfo ci) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); if (spectated != null) { - // effect handling - // Force-add all effects from syncData.effects - // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; - // if (syncData != null && syncData.effects != null && this.minecraft.player != null) { - // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : syncData.effects) { - // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); - // if (optHolder.isPresent()) { - // net.minecraft.world.effect.MobEffect effect = optHolder.get().value(); - // net.minecraft.core.Holder holder = net.minecraft.core.Holder.direct(effect); - // net.minecraft.world.effect.MobEffectInstance instance = new net.minecraft.world.effect.MobEffectInstance(holder, synced.duration, synced.amplifier); - // this.minecraft.player.forceAddEffect(instance, this.minecraft.player); - // } - // } - // } - - // View bobbing // get horizontal distance between current and last pos 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 1099890..cb37db1 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 @@ -25,6 +25,7 @@ import net.minecraft.world.phys.HitResult; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -37,7 +38,10 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context.Local; + import net.minecraft.resources.ResourceLocation; +import net.minecraft.core.Holder; @Mixin(Gui.class) public abstract class GuiMixin { @@ -45,9 +49,11 @@ public abstract class GuiMixin { @Shadow @Final private Minecraft minecraft; @Shadow public abstract SpectatorGui getSpectatorGui(); @Shadow protected abstract void renderItemHotbar(GuiGraphics guiGraphics, DeltaTracker deltaTracker); + @Shadow protected abstract void renderPortalOverlay(GuiGraphics guiGraphics, float intensity); @Shadow @Final private SpectatorGui spectatorGui; + // Use correct ResourceLocations for vanilla empty armor slot icons from the GUI atlas private static final ResourceLocation EMPTY_ARMOR_SLOT_HELMET = ResourceLocation.withDefaultNamespace("container/slot/helmet"); private static final ResourceLocation EMPTY_ARMOR_SLOT_CHESTPLATE = ResourceLocation.withDefaultNamespace("container/slot/chestplate"); @@ -66,7 +72,10 @@ public abstract class GuiMixin { cancellable = true ) private void spectatorplus$cancelRenderEffects(GuiGraphics guiGraphics, DeltaTracker deltaTracker, CallbackInfo ci) { - ci.cancel(); + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); + if (spectated != null) { + ci.cancel(); + } } @Redirect(method = "renderCameraOverlays(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isScoping()Z")) @@ -122,7 +131,7 @@ public abstract class GuiMixin { int itemWidth = 16; // standard item icon width int itemHeight = 16; // standard item icon height int baseY = 2; - int baseX = this.minecraft.getWindow().getGuiScaledWidth() - itemWidth - 2; + int baseX = this.minecraft.getWindow().getGuiScaledWidth() - itemWidth - 4; // Armor order: helmet, chestplate, leggings, boots (reverse to boots, leggings, chestplate, helmet) EquipmentSlot[] slots = {EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; for (int i = 0; i < slots.length; i++) { @@ -140,30 +149,79 @@ public abstract class GuiMixin { if (armorStack.isDamageableItem() && armorStack.getMaxDamage() > 0) { int durability = armorStack.getMaxDamage() - armorStack.getDamageValue(); int percent = (int) ((durability * 100.0) / armorStack.getMaxDamage()); - String text = percent + "%"; + String numText = String.valueOf(percent); + String percentChar = "%"; + int numColor; + if (percent == 100) { + numColor = 0xFF00FF00; // lime + } else if (percent < 10) { + numColor = 0xFFFF0000; // red + } else if (percent < 25) { + numColor = 0xFFFFA500; // orange + } else { + numColor = 0xFFFFFFFF; // white + } // Right-align the text to the left of the icon - int textWidth = this.minecraft.font.width(text); + int numTextWidth = this.minecraft.font.width(numText); + int percentTextWidth = this.minecraft.font.width(percentChar); + int textWidth = numTextWidth + percentTextWidth; int textX = baseX - spacing - textWidth; // right-aligned to the left of the item int textY = y + 4; // vertically centered - guiGraphics.drawString(this.minecraft.font, text, textX, textY, 0xFFFFFFFF, true); + // Draw numeric part + guiGraphics.drawString(this.minecraft.font, numText, textX, textY, numColor, true); + // Draw '%' in white + guiGraphics.drawString(this.minecraft.font, percentChar, textX + numTextWidth, textY, 0xFFFFFFFF, true); } } } - int effectBaseY = baseY + slots.length * (itemHeight + spacing) + 4; // start below armor + int effectBaseY = baseY + slots.length * (itemHeight + spacing) + spacing; // start below armor // Render all active effect icons down the right side below armor - if (ClientSyncController.syncData.effects != null && !ClientSyncController.syncData.effects.isEmpty()) { - - for (int i = 0; i < ClientSyncController.syncData.effects.size(); i++) { - SyncedEffect effect = ClientSyncController.syncData.effects.get(i); - int y = effectBaseY + i * (itemWidth + spacing); + LocalPlayer player = this.minecraft.player; + if (player != null && player.getActiveEffects() != null && !player.getActiveEffects().isEmpty()) { + int effectIndex = 0; + for (var effectInstance : player.getActiveEffects()) { + int y = effectBaseY + effectIndex * (itemWidth + spacing); // Draw vanilla effect background guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, EFFECT_BACKGROUND_SPRITE, baseX, y, itemWidth, itemHeight); - ResourceLocation effectIcon = GuiMixin.getEffectIcon(effect.effectKey); - guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX+2, y+2, itemWidth-4, itemHeight-4); + ResourceLocation effectIcon = Gui.getMobEffectSprite(effectInstance.getEffect()); + guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX + 2, y + 2, itemWidth - 4, itemHeight - 4); + + // Draw effect level as a small white number on the top right of the icon + int level = effectInstance.getAmplifier() + 1; + String levelText = String.valueOf(level); + int levelTextWidth = this.minecraft.font.width(levelText); + int levelTextX = baseX + itemWidth - (int)(levelTextWidth * 0.4F) - 3; // right-align inside top-right corner + int levelTextY = y + 2; + guiGraphics.pose().pushMatrix(); + guiGraphics.pose().scale(0.5F, 0.5F); + guiGraphics.drawString(this.minecraft.font, levelText, (int)(levelTextX / 0.5F), (int)(levelTextY / 0.5F), 0xFFFFFFFF, true); + guiGraphics.pose().popMatrix(); + + // Draw duration bar (1px wide) to the left of the effect icon, color changes with percent + int duration = effectInstance.getDuration(); + int maxDuration = 3600; // 3 minutes, adjust as needed + float percent = maxDuration > 0 ? (duration / (float)maxDuration) : 1.0F; + int maxBarHeight = itemHeight - 2; + int barHeight = Math.min(maxBarHeight, (int)(maxBarHeight * percent)); + int barX = baseX + 1; // 1px left of icon + int barY = y + itemHeight - 1 - barHeight; // 1px up from bottom + int barColor; + if (percent > 0.20F) { + barColor = 0xFF00FF00; // green + } else if (percent > 0.05F) { + barColor = 0xFFFFA500; // orange + } else { + barColor = 0xFFFF0000; // red + } + if (barHeight > 0) { + guiGraphics.fill(barX, barY, barX + 2, barY + barHeight, barColor); + } + + effectIndex++; } } } @@ -273,6 +331,17 @@ public abstract class GuiMixin { return instance.getFoodData(); } + @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;hasEffect(Lnet/minecraft/core/Holder;)Z")) + private boolean spectatorplus$showSyncedFoodSprite(Player instance, net.minecraft.core.Holder effect) { + final LocalPlayer player = this.minecraft.player; + if (player != null && player.hasEffect(effect)) { + return true; + } + return instance.hasEffect(effect); + } + @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) { @@ -308,4 +377,5 @@ private static ResourceLocation getEffectIcon(String effectKey) { // The effectKey should be lowercase, matching the registry name return ResourceLocation.withDefaultNamespace("mob_effect/" + key.toLowerCase()); } + } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index 9f316bb..2da419c 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -41,48 +41,6 @@ public LivingEntityMixin(EntityType entityType, Level level) { } } - // @Inject(method = "hasEffect(Lnet/minecraft/core/Holder;)Z", at = @At("HEAD"), cancellable = true) - // private void overrideNauseaEffect(Holder effect, CallbackInfoReturnable cir) { - // System.out.println("[SpectatorPlus] hasEffect called. Entity type: " + this.getClass().getName() + ", effect: " + - // effect.value().getDescriptionId() + " player: " + this.getName().getString()); - // if ((Entity) this instanceof Player) { - // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; - // if (syncData != null && syncData.effects != null) { - // boolean found = syncData.effects.stream().anyMatch(synced -> { - // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); - // return optHolder.isPresent() && optHolder.get().value().equals(effect.value()); - // }); - // if (found) { - // System.out.println("[SpectatorPlus] hasEffect: Synced effect found for player: " + this.getName().getString()); - // cir.setReturnValue(true); - // } - // } - // } - // } - - // @Inject(method = "getActiveEffects", at = @At("HEAD"), cancellable = true) - // private void overrideActiveEffects(CallbackInfoReturnable> cir) { - // System.out.println("[SpectatorPlus] getActiveEffects called. Entity type: " + this.getClass().getName()); - // if ((Entity) this instanceof Player) { - // System.out.println("[SpectatorPlus] Injecting effects from syncdata for player: " + this.getName().getString()); - // var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; - // if (syncData != null && syncData.effects != null) { - // java.util.List instances = new java.util.ArrayList<>(); - // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : syncData.effects) { - // java.util.Optional> optHolder = net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT.get(net.minecraft.resources.ResourceLocation.tryParse(synced.effectKey)); - // if (optHolder.isPresent()) { - // MobEffect effect = optHolder.get().value(); - // net.minecraft.core.Holder holder = net.minecraft.core.Holder.direct(effect); - // MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); - // instances.add(instance); - // } - // } - // System.out.println("[SpectatorPlus] Injecting synced effects for player: " + this.getName().getString() + " -> " + instances); - // cir.setReturnValue(instances); - // } - // } - // } - @Unique private boolean isBreakingBlock() { return ((LevelRendererAccessor) Minecraft.getInstance().levelRenderer).getDestroyingBlocks().containsKey(this.getId()); diff --git a/fabric/src/main/resources/spectatorplus.accesswidener b/fabric/src/main/resources/spectatorplus.accesswidener index 939d77e..edae0eb 100644 --- a/fabric/src/main/resources/spectatorplus.accesswidener +++ b/fabric/src/main/resources/spectatorplus.accesswidener @@ -5,3 +5,5 @@ accessible class net/minecraft/server/level/ChunkMap$TrackedEntity accessible class net/minecraft/client/renderer/ItemInHandRenderer$HandRenderSelection accessible field net/minecraft/client/renderer/ItemInHandRenderer$HandRenderSelection renderMainHand Z accessible field net/minecraft/client/renderer/ItemInHandRenderer$HandRenderSelection renderOffHand Z + +accessible method net/minecraft/client/gui/Gui renderFood (Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/entity/player/Player;II)V \ No newline at end of file diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java index 3040138..65f167e 100644 --- a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java +++ b/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java @@ -2,7 +2,6 @@ import com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent; import com.hpfxd.spectatorplus.paper.SpectatorPlugin; -import com.hpfxd.spectatorplus.paper.sync.packet.ClientboundEffectsSyncPacket; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -10,12 +9,61 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPotionEffectEvent; import org.bukkit.potion.PotionEffect; - -import java.util.List; -import java.util.stream.Collectors; -import com.hpfxd.spectatorplus.paper.effect.EffectType; +import org.bukkit.potion.PotionEffectType; public class EffectsSyncHandler implements Listener { + /** + * Sets the target player's potion effects to match the source player's effects. + * Removes all current effects and applies all effects from source. + */ + private void syncPlayerEffects(Player target, Player source) { + java.util.Map sourceEffects = new java.util.HashMap<>(); + //this.plugin.getSLF4JLogger().info("[SpectatorPlus] Syncing effects: setting {} effects to match {}: {}", target.getName(), source.getName(), sourceEffects); + + for (PotionEffect effect : source.getActivePotionEffects()) { + sourceEffects.put(effect.getType(), effect); + } + + java.util.Map targetEffects = new java.util.HashMap<>(); + for (PotionEffect effect : target.getActivePotionEffects()) { + targetEffects.put(effect.getType(), effect); + } + + // Remove effects that are not present in source or have changed + for (PotionEffectType type : targetEffects.keySet()) { + PotionEffect sourceEffect = sourceEffects.get(type); + PotionEffect targetEffect = targetEffects.get(type); + if (sourceEffect == null || + sourceEffect.getDuration() != targetEffect.getDuration() || + sourceEffect.getAmplifier() != targetEffect.getAmplifier() || + sourceEffect.isAmbient() != targetEffect.isAmbient() || + sourceEffect.hasParticles() != targetEffect.hasParticles() || + sourceEffect.hasIcon() != targetEffect.hasIcon()) { + target.removePotionEffect(type); + } + } + + // Add effects that are new or changed + for (PotionEffectType type : sourceEffects.keySet()) { + PotionEffect sourceEffect = sourceEffects.get(type); + PotionEffect targetEffect = targetEffects.get(type); + if (targetEffect == null || + sourceEffect.getDuration() != targetEffect.getDuration() || + sourceEffect.getAmplifier() != targetEffect.getAmplifier() || + sourceEffect.isAmbient() != targetEffect.isAmbient() || + sourceEffect.hasParticles() != targetEffect.hasParticles() || + sourceEffect.hasIcon() != targetEffect.hasIcon()) { + target.addPotionEffect(new PotionEffect( + sourceEffect.getType(), + sourceEffect.getDuration(), + sourceEffect.getAmplifier(), + sourceEffect.isAmbient(), + sourceEffect.hasParticles(), + sourceEffect.hasIcon() + )); + } + } + } private static final String PERMISSION = "spectatorplus.sync.effects"; private final SpectatorPlugin plugin; @@ -25,32 +73,11 @@ public EffectsSyncHandler(SpectatorPlugin plugin) { Bukkit.getPluginManager().registerEvents(this, plugin); } - private java.util.List getSyncedEffects(Player player) { - java.util.List effects = java.util.List.copyOf(player.getActivePotionEffects()); - if (effects.isEmpty()) { - this.plugin.getSLF4JLogger().info("[SpectatorPlus] No active effects for {}", player.getName()); - } else { - for (PotionEffect effect : effects) { - this.plugin.getSLF4JLogger().info("[SpectatorPlus] Effect for {}: type={}, key={}, amplifier={}, duration={}", player.getName(), effect.getType().getName(), effect.getType().getKey(), effect.getAmplifier(), effect.getDuration()); - } - } - return effects.stream() - .map(pe -> new com.hpfxd.spectatorplus.paper.effect.SyncedEffect( - pe.getType().getKey().toString(), - pe.getAmplifier(), - pe.getDuration() - )) - .collect(java.util.stream.Collectors.toList()); - } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onStartSpectatingEntity(PlayerStartSpectatingEntityEvent event) { final Player spectator = event.getPlayer(); if (event.getNewSpectatorTarget() instanceof final Player target && spectator.hasPermission(PERMISSION)) { - java.util.List effects = getSyncedEffects(target); - this.plugin.getSLF4JLogger().info("[SpectatorPlus] Sending effects sync packet to {}: {}", spectator.getName(), effects); - this.plugin.getSyncController().sendPacket(spectator, - new ClientboundEffectsSyncPacket(target.getUniqueId(), effects)); + syncPlayerEffects(spectator, target); } } @@ -58,10 +85,11 @@ public void onStartSpectatingEntity(PlayerStartSpectatingEntityEvent event) { public void onPotionEffectChange(EntityPotionEffectEvent event) { if (event.getEntity() instanceof Player player) { Bukkit.getScheduler().runTask(this.plugin, () -> { - java.util.List effects = getSyncedEffects(player); - this.plugin.getSLF4JLogger().info("[SpectatorPlus] Broadcasting effects sync packet for {}: {}", player.getName(), effects); - this.plugin.getSyncController().broadcastPacketToSpectators(player, PERMISSION, - new ClientboundEffectsSyncPacket(player.getUniqueId(), effects)); + for (Player spectator : Bukkit.getOnlinePlayers()) { + if (spectator != player && spectator.hasPermission(PERMISSION) && spectator.getSpectatorTarget() == player) { + syncPlayerEffects(spectator, player); + } + } }); } } From c8ae55540dc8fe3537d0535528ddd2a8e3f57b29 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 15 Nov 2025 16:58:12 +0000 Subject: [PATCH 23/57] upgrade gradle and fabric versions --- fabric/build.gradle.kts | 2 +- fabric/gradle.properties | 12 ++++++------ gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 40ceb82..e1e4187 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "1.10.1" + id("fabric-loom") version "1.11.7" id("spectatorplus.platform") } diff --git a/fabric/gradle.properties b/fabric/gradle.properties index c47fc5a..8a4fce2 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,12 +1,12 @@ -minecraft_version=1.21.7 -yarn_mappings=1.21.7+build.1 -loader_version=0.16.14 +minecraft_version=1.21.10 +yarn_mappings=1.21.10+build.1 +loader_version=0.17.2 # Fabric API -fabric_version=0.128.1+1.21.7 +fabric_version=0.138.3+1.21.10 -parchment_minecraft_version=1.21.6 -parchment_version=2025.06.29 +parchment_minecraft_version=1.21.10 +parchment_version=2025.10.12 fabric_permissions_api_version=0.4.0 cloth_config_version=19.0.147 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f079b14f95d807caca09b0405e4182ab319aa8a8 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 15 Nov 2025 17:46:42 +0000 Subject: [PATCH 24/57] fix camera entity and profile id --- .../spectatorplus/fabric/client/mixin/ClientLevelMixin.java | 4 ++-- .../fabric/client/mixin/ItemInHandRendererMixin.java | 6 +++--- .../fabric/client/mixin/PlayerMenuItemMixin.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java index 1ab72f0..e3dff22 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java @@ -20,7 +20,7 @@ public class ClientLevelMixin { @Redirect(method = "getMarkerParticleTarget()Lnet/minecraft/world/level/block/Block;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/MultiPlayerGameMode;getPlayerMode()Lnet/minecraft/world/level/GameType;")) private GameType spectatorplus$useCameraGameModeForMarkerParticleCheck(MultiPlayerGameMode instance) { - if (this.minecraft.cameraEntity instanceof Player player && player.isCreative()) { + if (this.minecraft.getCameraEntity() instanceof Player player && player.isCreative()) { return GameType.CREATIVE; } @@ -29,7 +29,7 @@ public class ClientLevelMixin { @Redirect(method = "getMarkerParticleTarget()Lnet/minecraft/world/level/block/Block;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getMainHandItem()Lnet/minecraft/world/item/ItemStack;")) private ItemStack spectatorplus$useCameraForMarkerParticleCheck(LocalPlayer instance) { - if (this.minecraft.cameraEntity instanceof LivingEntity livingEntity) { + if (this.minecraft.getCameraEntity() instanceof LivingEntity livingEntity) { return livingEntity.getMainHandItem(); } return ItemStack.EMPTY; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java index 3555123..df81ab2 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java @@ -97,7 +97,7 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;getRenderer(Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/client/renderer/entity/EntityRenderer;") ) private EntityRenderer spectatorplus$mapHandUseCameraEntityRenderer(EntityRenderDispatcher instance, T entity) { - return instance.getRenderer(this.minecraft.cameraEntity); + return instance.getRenderer(this.minecraft.getCameraEntity()); } @Redirect( @@ -105,7 +105,7 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isModelPartShown(Lnet/minecraft/world/entity/player/PlayerModelPart;)Z") ) private boolean spectatorplus$mapHandFixPartVisibility(LocalPlayer instance, PlayerModelPart playerModelPart) { - return ((Player) this.minecraft.cameraEntity).isModelPartShown(playerModelPart); + return ((Player) this.minecraft.getCameraEntity()).isModelPartShown(playerModelPart); } @Redirect(method = { @@ -113,6 +113,6 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { "renderTwoHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFFF)V", }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isInvisible()Z")) private boolean spectatorplus$spectatedInvisibility(LocalPlayer instance) { - return this.minecraft.cameraEntity.isInvisible(); + return this.minecraft.getCameraEntity().isInvisible(); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java index 390f697..a0a5103 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java @@ -20,7 +20,7 @@ public class PlayerMenuItemMixin { @Inject(method = "selectItem(Lnet/minecraft/client/gui/spectator/SpectatorMenu;)V", at = @At("HEAD"), cancellable = true) private void spectatorplus$handleSelect(SpectatorMenu menu, CallbackInfo ci) { if (SpectatorClientMod.config.teleportAutoSpectate) { - ClientTargetController.requestTargetFromServer(Minecraft.getInstance(), this.profile.getId()); + ClientTargetController.requestTargetFromServer(Minecraft.getInstance(), this.profile.id()); ci.cancel(); } } From f711e495383e7acd21994a6ce69397c408f71794 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Sat, 15 Nov 2025 17:51:23 +0000 Subject: [PATCH 25/57] fix keybinds --- .../fabric/client/SpectatorKeybinds.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java index 6bd45f6..b8eed96 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java @@ -28,7 +28,7 @@ import java.util.UUID; public class SpectatorKeybinds { - private static final Comparator PROFILE_ORDER = Comparator.comparing(playerInfo -> playerInfo.getProfile().getId()); + private static final Comparator PROFILE_ORDER = Comparator.comparing(playerInfo -> playerInfo.getProfile().id()); private static KeyMapping CLOSEST_PLAYER; private static KeyMapping NEXT_PLAYER; @@ -40,19 +40,19 @@ public static void init() { CLOSEST_PLAYER = KeyBindingHelper.registerKeyBinding(new KeyMapping( "key.spectatorplus.closestPlayer", GLFW.GLFW_KEY_UP, - "key.categories.spectatorplus" + KeyMapping.Category.MISC )); NEXT_PLAYER = KeyBindingHelper.registerKeyBinding(new KeyMapping( "key.spectatorplus.nextPlayer", GLFW.GLFW_KEY_RIGHT, - "key.categories.spectatorplus" + KeyMapping.Category.MISC )); PREVIOUS_PLAYER = KeyBindingHelper.registerKeyBinding(new KeyMapping( "key.spectatorplus.previousPlayer", GLFW.GLFW_KEY_LEFT, - "key.categories.spectatorplus" + KeyMapping.Category.MISC )); ClientTickEvents.END_CLIENT_TICK.register(SpectatorKeybinds::tick); @@ -61,7 +61,7 @@ public static void init() { private static void tick(Minecraft mc) { if (mc.player != null && mc.gameMode.getPlayerMode() == GameType.SPECTATOR) { while (CLOSEST_PLAYER.consumeClick()) { - final Entity nearest = mc.level.getNearestPlayer(mc.player.getX(), mc.player.getY(), mc.player.getZ(), 256, EntitySelector.NO_SPECTATORS.and(entity -> mc.cameraEntity != entity)); + final Entity nearest = mc.level.getNearestPlayer(mc.player.getX(), mc.player.getY(), mc.player.getZ(), 256, EntitySelector.NO_SPECTATORS.and(entity -> mc.getCameraEntity() != entity)); if (nearest != null) { setTarget(mc, nearest.getUUID()); @@ -85,8 +85,8 @@ private static void tick(Minecraft mc) { private static void targetNext(Minecraft mc, int shift) { final PlayerInfo target = shiftPlayerCursor(mc, shift); - if (target != null && !target.getProfile().getId().equals(mc.cameraEntity.getUUID())) { - setTarget(mc, target.getProfile().getId()); + if (target != null && !target.getProfile().id().equals(mc.getCameraEntity().getUUID())) { + setTarget(mc, target.getProfile().id()); mc.player.displayClientMessage(Component.translatable("spectatorplus.target.now-spectating", Component.empty().append(mc.gui.getTabList().getNameForDisplay(target)) .withStyle(ChatFormatting.WHITE)).withStyle(ChatFormatting.GRAY), true); } else { @@ -107,7 +107,7 @@ private static PlayerInfo shiftPlayerCursor(Minecraft mc, int shift) { int index = -1; if (targetCursorId != null) { - index = Iterables.indexOf(players, player -> targetCursorId.equals(player.getProfile().getId())); + index = Iterables.indexOf(players, player -> targetCursorId.equals(player.getProfile().id())); } if (index == -1 && shift < 0) { @@ -134,7 +134,7 @@ private static void setTarget(Minecraft mc, UUID uuid) { private static void selectInMenu(Minecraft mc, UUID uuid) { final TeleportToPlayerMenuCategory category = new TeleportToPlayerMenuCategory(); - final SpectatorMenuItem menuItem = Iterables.find(category.getItems(), item -> item instanceof PlayerMenuItem && uuid.equals(((PlayerMenuItemAccessor) item).getProfile().getId())); + final SpectatorMenuItem menuItem = Iterables.find(category.getItems(), item -> item instanceof PlayerMenuItem && uuid.equals(((PlayerMenuItemAccessor) item).getProfile().id())); if (menuItem == null) { return; @@ -143,7 +143,7 @@ private static void selectInMenu(Minecraft mc, UUID uuid) { final SpectatorGui gui = mc.gui.getSpectatorGui(); if (!gui.isMenuActive()) { // Activate the menu - gui.onMouseMiddleClick(); + // gui.onMouseMiddleClick(); } final SpectatorMenu menu = ((SpectatorGuiAccessor) gui).getMenu(); From b197b34c86105b7e63b10f00316b363974f7a9d4 Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Mon, 17 Nov 2025 14:53:07 +0000 Subject: [PATCH 26/57] more fixes to remapped classes --- .../fabric/client/SpectatorKeybinds.java | 2 +- .../client/mixin/EntityRendererMixin.java | 27 ++++++++++++ .../client/mixin/GameRendererMixin.java | 12 +++--- .../mixin/ItemInHandRendererAccessor.java | 8 ++-- .../client/mixin/ItemInHandRendererMixin.java | 43 ++++++++++++------- .../client/mixin/LevelRendererMixin.java | 20 ++++----- .../client/mixin/PlayerMenuItemAccessor.java | 4 +- .../client/mixin/PlayerMenuItemMixin.java | 6 ++- .../spectatorplus.client.mixins.json | 1 + 9 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java index b8eed96..fc3c452 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java @@ -134,7 +134,7 @@ private static void setTarget(Minecraft mc, UUID uuid) { private static void selectInMenu(Minecraft mc, UUID uuid) { final TeleportToPlayerMenuCategory category = new TeleportToPlayerMenuCategory(); - final SpectatorMenuItem menuItem = Iterables.find(category.getItems(), item -> item instanceof PlayerMenuItem && uuid.equals(((PlayerMenuItemAccessor) item).getProfile().id())); + final SpectatorMenuItem menuItem = Iterables.find(category.getItems(), item -> item instanceof PlayerMenuItem && uuid.equals(((PlayerMenuItemAccessor) item).getPlayerInfo().getProfile().id())); if (menuItem == null) { return; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java new file mode 100644 index 0000000..a3dd984 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java @@ -0,0 +1,27 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import com.hpfxd.spectatorplus.fabric.client.SpectatorClientMod; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.RemotePlayer; +import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.world.entity.Entity; +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.CallbackInfoReturnable; + +@Mixin(EntityRenderer.class) +public abstract class EntityRendererMixin { + @Inject( + method = "shouldRender(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z", + at = @At("HEAD"), + cancellable = true + ) + private void spectatorplus$maybeHideSpectator(Entity entity, Frustum frustum, double x, double y, double z, CallbackInfoReturnable cir) { + final Minecraft mc = Minecraft.getInstance(); + if (!SpectatorClientMod.config.showSpectators && mc.player != null && mc.player.isSpectator() && entity instanceof final RemotePlayer player && player.isSpectator()) { + cir.setReturnValue(false); + } + } +} 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 bf25eaa..f07be8e 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 @@ -7,11 +7,11 @@ import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; -import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.ItemInHandRenderer; +import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.RenderBuffers; import net.minecraft.core.Direction; @@ -48,7 +48,7 @@ public abstract class GameRendererMixin { @Unique private float yBobO; @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) - public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, CallbackInfo ci, @Local PoseStack poseStackIn) { + public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, CallbackInfo ci, @Local PoseStack poseStackIn, @Local SubmitNodeCollector submitNodeCollector) { if (SpectatorClientMod.config.renderArms && this.minecraft.player != null && this.minecraft.options.getCameraType().isFirstPerson() && !this.minecraft.options.hideGui) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); if (spectated != null && !spectated.isSpectator()) { @@ -70,18 +70,18 @@ public abstract class GameRendererMixin { final float swingProgress = interactionHand == InteractionHand.MAIN_HAND ? attackAnim : 0.0F; final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOMainHandHeight(), accessor.getMainHandHeight()); - accessor.invokeRenderArmWithItem(spectated, partialTicks, + accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.MAIN_HAND, swingProgress, accessor.getMainHandItem(), equippedProgress, - poseStackIn, this.renderBuffers.bufferSource(), packedLightCoords); + poseStackIn, submitNodeCollector, packedLightCoords); } if (handRenderSelection.renderOffHand) { final float swingProgress = interactionHand == InteractionHand.OFF_HAND ? attackAnim : 0.0F; final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOOffHandHeight(), accessor.getOffHandHeight()); - accessor.invokeRenderArmWithItem(spectated, partialTicks, + accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.OFF_HAND, swingProgress, accessor.getOffHandItem(), equippedProgress, - poseStackIn, this.renderBuffers.bufferSource(), packedLightCoords); + poseStackIn, submitNodeCollector, packedLightCoords); } this.lightTexture.turnOffLightLayer(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java index d9a352a..3872649 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java @@ -3,7 +3,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; @@ -13,9 +13,9 @@ @Mixin(ItemInHandRenderer.class) public interface ItemInHandRendererAccessor { @Invoker("renderArmWithItem") - void invokeRenderArmWithItem(AbstractClientPlayer player, float partialTicks, float pitch, InteractionHand hand, - float swingProgress, ItemStack stack, float equippedProgress, PoseStack poseStack, - MultiBufferSource buffer, int combinedLight); + void invokeRenderArmWithItem(AbstractClientPlayer player, float partialTick, float pitch, InteractionHand hand, + float swingProgress, ItemStack item, float equippedProgress, PoseStack poseStack, + SubmitNodeCollector nodeCollector, int packedLight); @Accessor float getMainHandHeight(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java index df81ab2..48724e0 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java @@ -5,8 +5,7 @@ import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; -import net.minecraft.client.renderer.entity.EntityRenderDispatcher; -import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.player.AvatarRenderer; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; @@ -80,7 +79,7 @@ public abstract class ItemInHandRendererMixin { } } - @ModifyVariable(method = "renderPlayerArm(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFFLnet/minecraft/world/entity/HumanoidArm;)V", at = @At("STORE")) + @ModifyVariable(method = "renderPlayerArm(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFFLnet/minecraft/world/entity/HumanoidArm;)V", at = @At("STORE")) private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { // render the arm as the camera entity, instead of always as the client player @@ -93,26 +92,38 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { } @Redirect( - method = "renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/entity/HumanoidArm;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;getRenderer(Lnet/minecraft/world/entity/Entity;)Lnet/minecraft/client/renderer/entity/EntityRenderer;") + method = "renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;getPlayerRenderer(Lnet/minecraft/client/player/AbstractClientPlayer;)Lnet/minecraft/client/renderer/entity/player/AvatarRenderer;") ) - private EntityRenderer spectatorplus$mapHandUseCameraEntityRenderer(EntityRenderDispatcher instance, T entity) { - return instance.getRenderer(this.minecraft.getCameraEntity()); + private AvatarRenderer spectatorplus$mapHandUseCameraAvatarRenderer(net.minecraft.client.renderer.entity.EntityRenderDispatcher dispatcher, AbstractClientPlayer ignoredPlayer) { + Entity cameraEntity = this.minecraft.getCameraEntity(); + if (cameraEntity instanceof AbstractClientPlayer cameraPlayer) { + return dispatcher.getPlayerRenderer(cameraPlayer); + } + return dispatcher.getPlayerRenderer(this.minecraft.player); } @Redirect( - method = "renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/entity/HumanoidArm;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isModelPartShown(Lnet/minecraft/world/entity/player/PlayerModelPart;)Z") + method = "renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isModelPartShown(Lnet/minecraft/world/entity/player/PlayerModelPart;)Z") ) - private boolean spectatorplus$mapHandFixPartVisibility(LocalPlayer instance, PlayerModelPart playerModelPart) { - return ((Player) this.minecraft.getCameraEntity()).isModelPartShown(playerModelPart); + private boolean spectatorplus$mapHandFixPartVisibility(AbstractClientPlayer instance, PlayerModelPart playerModelPart) { + Entity cameraEntity = this.minecraft.getCameraEntity(); + if (cameraEntity instanceof Player cameraPlayer) { + return cameraPlayer.isModelPartShown(playerModelPart); + } + return instance.isModelPartShown(playerModelPart); } @Redirect(method = { - "renderOneHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFLnet/minecraft/world/entity/HumanoidArm;FLnet/minecraft/world/item/ItemStack;)V", - "renderTwoHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IFFF)V", - }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isInvisible()Z")) - private boolean spectatorplus$spectatedInvisibility(LocalPlayer instance) { - return this.minecraft.getCameraEntity().isInvisible(); + "renderOneHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFLnet/minecraft/world/entity/HumanoidArm;FLnet/minecraft/world/item/ItemStack;)V", + "renderTwoHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFFF)V", + }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isInvisible()Z")) + private boolean spectatorplus$spectatedInvisibility(AbstractClientPlayer instance) { + Entity cameraEntity = this.minecraft.getCameraEntity(); + if (cameraEntity instanceof net.minecraft.world.entity.Entity entity) { + return entity.isInvisible(); + } + return instance.isInvisible(); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java index 35e7787..1b54dcc 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java @@ -33,14 +33,14 @@ public abstract class LevelRendererMixin { } } - @Inject( - method = "renderEntity(Lnet/minecraft/world/entity/Entity;DDDFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)V", - at = @At("HEAD"), - cancellable = true - ) - private void spectatorplus$hideOtherSpectators(Entity entity, double camX, double camY, double camZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, CallbackInfo ci) { - if (!SpectatorClientMod.config.showSpectators && this.minecraft.player.isSpectator() && entity instanceof final RemotePlayer player && player.isSpectator()) { - ci.cancel(); - } - } + // @Inject( + // method = "renderEntity(Lnet/minecraft/world/entity/Entity;DDDFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)V", + // at = @At("HEAD"), + // cancellable = true + // ) + // private void spectatorplus$hideOtherSpectators(Entity entity, double camX, double camY, double camZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, CallbackInfo ci) { + // if (!SpectatorClientMod.config.showSpectators && this.minecraft.player.isSpectator() && entity instanceof final RemotePlayer player && player.isSpectator()) { + // ci.cancel(); + // } + // } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java index 1c6d604..22ce04f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java @@ -2,11 +2,13 @@ import com.mojang.authlib.GameProfile; import net.minecraft.client.gui.spectator.PlayerMenuItem; +import net.minecraft.client.multiplayer.PlayerInfo; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(PlayerMenuItem.class) public interface PlayerMenuItemAccessor { @Accessor - GameProfile getProfile(); + PlayerInfo getPlayerInfo(); } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java index a0a5103..2eda9b2 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java @@ -6,6 +6,8 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.spectator.PlayerMenuItem; import net.minecraft.client.gui.spectator.SpectatorMenu; +import net.minecraft.client.multiplayer.PlayerInfo; + import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -15,12 +17,12 @@ @Mixin(PlayerMenuItem.class) public class PlayerMenuItemMixin { - @Shadow @Final private GameProfile profile; + @Shadow @Final private PlayerInfo playerInfo; @Inject(method = "selectItem(Lnet/minecraft/client/gui/spectator/SpectatorMenu;)V", at = @At("HEAD"), cancellable = true) private void spectatorplus$handleSelect(SpectatorMenu menu, CallbackInfo ci) { if (SpectatorClientMod.config.teleportAutoSpectate) { - ClientTargetController.requestTargetFromServer(Minecraft.getInstance(), this.profile.id()); + ClientTargetController.requestTargetFromServer(Minecraft.getInstance(), this.playerInfo.getProfile().id()); ci.cancel(); } } diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index 956bcc1..59c3082 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -7,6 +7,7 @@ "ClientLevelMixin", "ClientWaypointManagerMixin", "EntityMixin", + "EntityRendererMixin", "ExperienceBarRendererMixin", "GameRendererAccessor", "GameRendererMixin", From b893017339acce6445ef2f4f13219f3bfa546cfc Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Wed, 19 Nov 2025 11:29:07 +0000 Subject: [PATCH 27/57] apply connors patch --- fabric/gradle.properties | 2 +- .../client/mixin/GameRendererMixin.java | 36 ++++++++----------- .../client/mixin/ItemInHandRendererMixin.java | 14 ++++---- .../fabric/client/util/SpecUtil.java | 2 -- fabric/src/main/resources/fabric.mod.json | 4 +-- paper/build.gradle.kts | 2 +- 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 8a4fce2..29facc9 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -10,4 +10,4 @@ parchment_version=2025.10.12 fabric_permissions_api_version=0.4.0 cloth_config_version=19.0.147 -modmenu_version=15.0.0-beta.3 +modmenu_version=16.0.0-rc.1 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 f07be8e..de281b2 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 @@ -8,12 +8,9 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.ClientAvatarState; import net.minecraft.client.player.AbstractClientPlayer; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.ItemInHandRenderer; -import net.minecraft.client.renderer.SubmitNodeCollector; -import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.RenderBuffers; +import net.minecraft.client.renderer.*; import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; @@ -27,6 +24,7 @@ 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GameRenderer.class) @@ -48,7 +46,7 @@ public abstract class GameRendererMixin { @Unique private float yBobO; @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) - public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, CallbackInfo ci, @Local PoseStack poseStackIn, @Local SubmitNodeCollector submitNodeCollector) { + public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, 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); if (spectated != null && !spectated.isSpectator()) { @@ -65,6 +63,7 @@ public abstract class GameRendererMixin { final int packedLightCoords = this.minecraft.getEntityRenderDispatcher().getPackedLightCoords(spectated, partialTicks); final ItemInHandRendererAccessor accessor = ((ItemInHandRendererAccessor) this.itemInHandRenderer); + var submitNodeCollector = minecraft.gameRenderer.getSubmitNodeStorage(); if (handRenderSelection.renderMainHand) { final float swingProgress = interactionHand == InteractionHand.MAIN_HAND ? attackAnim : 0.0F; @@ -150,24 +149,17 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender } } - @ModifyExpressionValue(method = "bobView(Lcom/mojang/blaze3d/vertex/PoseStack;F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/AbstractClientPlayer;walkDist:F")) - private float spectatorplus$modifyBobWalkDist(float original, @Local AbstractClientPlayer cameraPlayer) { - return cameraPlayer == this.minecraft.player ? original : this.walkDist; + @Redirect(method = "bobView", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/ClientAvatarState;getBackwardsInterpolatedWalkDistance(F)F")) + float spectatorplus$modifyBobWalkDist(ClientAvatarState instance, float partialTick) { + if (minecraft.getCameraEntity() == this.minecraft.player) return instance.getBackwardsInterpolatedWalkDistance(partialTick); + float f = this.walkDist - this.walkDistO; + return -(this.walkDist + f * partialTick); } - @ModifyExpressionValue(method = "bobView(Lcom/mojang/blaze3d/vertex/PoseStack;F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/AbstractClientPlayer;walkDistO:F")) - private float spectatorplus$modifyBobWalkDistO(float original, @Local AbstractClientPlayer cameraPlayer) { - return cameraPlayer == this.minecraft.player ? original : this.walkDistO; - } - - @ModifyExpressionValue(method = "bobView(Lcom/mojang/blaze3d/vertex/PoseStack;F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/AbstractClientPlayer;bob:F")) - private float spectatorplus$modifyBobValue(float original, @Local AbstractClientPlayer cameraPlayer) { - return cameraPlayer == this.minecraft.player ? original : this.bob; - } - - @ModifyExpressionValue(method = "bobView(Lcom/mojang/blaze3d/vertex/PoseStack;F)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/player/AbstractClientPlayer;oBob:F")) - private float spectatorplus$modifyBobValueO(float original, @Local AbstractClientPlayer cameraPlayer) { - return cameraPlayer == this.minecraft.player ? original : this.bobO; + @Redirect(method = "bobView", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/ClientAvatarState;getInterpolatedBob(F)F")) + float spectatorplus$modifyBobValue(ClientAvatarState instance, float partialTick) { + if (minecraft.getCameraEntity() == this.minecraft.player) return instance.getInterpolatedBob(partialTick); + return Mth.lerp(partialTick, this.bobO, this.bob); } @ModifyExpressionValue(method = "pick(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;blockInteractionRange()D")) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java index 48724e0..04e4c23 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java @@ -103,11 +103,11 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { return dispatcher.getPlayerRenderer(this.minecraft.player); } - @Redirect( + @Redirect( method = "renderMapHand(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/world/entity/HumanoidArm;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isModelPartShown(Lnet/minecraft/world/entity/player/PlayerModelPart;)Z") - ) - private boolean spectatorplus$mapHandFixPartVisibility(AbstractClientPlayer instance, PlayerModelPart playerModelPart) { + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isModelPartShown(Lnet/minecraft/world/entity/player/PlayerModelPart;)Z") + ) + private boolean spectatorplus$mapHandFixPartVisibility(LocalPlayer instance, PlayerModelPart playerModelPart) { Entity cameraEntity = this.minecraft.getCameraEntity(); if (cameraEntity instanceof Player cameraPlayer) { return cameraPlayer.isModelPartShown(playerModelPart); @@ -115,11 +115,11 @@ private AbstractClientPlayer setArmPlayer(AbstractClientPlayer in) { return instance.isModelPartShown(playerModelPart); } - @Redirect(method = { + @Redirect(method = { "renderOneHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFLnet/minecraft/world/entity/HumanoidArm;FLnet/minecraft/world/item/ItemStack;)V", "renderTwoHandedMap(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;IFFF)V", - }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isInvisible()Z")) - private boolean spectatorplus$spectatedInvisibility(AbstractClientPlayer instance) { + }, at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isInvisible()Z")) + private boolean spectatorplus$spectatedInvisibility(LocalPlayer instance) { Entity cameraEntity = this.minecraft.getCameraEntity(); if (cameraEntity instanceof net.minecraft.world.entity.Entity entity) { return entity.isInvisible(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java index bf3766f..3c7fbfc 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java @@ -1,12 +1,10 @@ package com.hpfxd.spectatorplus.fabric.client.util; -import com.hpfxd.spectatorplus.fabric.client.SpectatorClientMod; import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.GameType; -import org.spongepowered.asm.mixin.Unique; import net.minecraft.core.Holder; import net.minecraft.world.effect.MobEffect; diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index f2e043b..30eadde 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.6", + "minecraft": "~1.21.10", "java": ">=21", "fabric-api": "*", "fabric-permissions-api-v0": "*" }, "suggests": { "cloth-config": "^19.0.147", - "modmenu": "^15.0.0-beta.3" + "modmenu": "^16.0.0-rc.1" } } diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index f93da29..d068769 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -47,7 +47,7 @@ tasks { } runServer { - minecraftVersion("1.21.6") + minecraftVersion("1.21.10") } named("build") { From 94b4d1c5a4280548299d78995206e5f561b425ce Mon Sep 17 00:00:00 2001 From: Ricardo Cunha Date: Wed, 19 Nov 2025 11:29:52 +0000 Subject: [PATCH 28/57] Fix cloth config config --- .../fabric/client/config/ModMenuIntegration.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java index bed0712..f06bb1f 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java @@ -3,15 +3,16 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.gui.screens.Screen; public class ModMenuIntegration implements ModMenuApi { @Override public ConfigScreenFactory getModConfigScreenFactory() { - if (FabricLoader.getInstance().isModLoaded("cloth-config")) { - return ClothConfigIntegration::getConfigScreen; - } else { - // cloth config isn't loaded, cannot provide config screen + return (Screen parent) -> { + if (FabricLoader.getInstance().isModLoaded("cloth-config")) { + return ClothConfigIntegration.getConfigScreen(parent); + } return null; - } + }; } } From afe59292c208151b8a0bac570e0d46e44ae05141 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:13:04 +0800 Subject: [PATCH 29/57] update cloth config version to 20.0.149 to fix crash bug while click LAN settings in multiplayer game --- fabric/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 29facc9..fe56e70 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -9,5 +9,5 @@ parchment_minecraft_version=1.21.10 parchment_version=2025.10.12 fabric_permissions_api_version=0.4.0 -cloth_config_version=19.0.147 +cloth_config_version=20.0.149 modmenu_version=16.0.0-rc.1 From 51f7266fd6478b2b40fa5bcc1da10418ed54fa42 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:32:40 +0800 Subject: [PATCH 30/57] =?UTF-8?q?fix=EF=BC=9ARefactor=20CustomPacketCodecs?= =?UTF-8?q?=20to=20use=20OPTIONAL=5FSTREAM=5FCODEC=20for=20ItemStack=20ser?= =?UTF-8?q?ialization=20for=20fixing=20HotBar=20item=20decode=20fail.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fabric/sync/CustomPacketCodecs.java | 62 +++---------------- 1 file changed, 10 insertions(+), 52 deletions(-) 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 141c452..3f92c3e 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,17 +1,10 @@ package com.hpfxd.spectatorplus.fabric.sync; +import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtAccounter; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtOps; 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() { } @@ -21,11 +14,7 @@ public static ItemStack[] readItems(RegistryFriendlyByteBuf buf) { final ItemStack[] items = new ItemStack[len]; for (int slot = 0; slot < len; slot++) { - if (buf.readBoolean()) { - final ItemStack stack = readItem(buf); - - items[slot] = stack; - } + items[slot] = ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); } return items; @@ -35,54 +24,23 @@ public static void writeItems(RegistryFriendlyByteBuf buf, ItemStack[] items) { buf.writeInt(items.length); for (final ItemStack item : items) { - buf.writeBoolean(item != null); - - if (item != null) { - writeItem(buf, item); - } + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item != null ? item : ItemStack.EMPTY); } } public static ItemStack readItem(RegistryFriendlyByteBuf 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()); - - var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); - return ItemStack.CODEC.parse(registryOps, tag).resultOrPartial().orElse(ItemStack.EMPTY); - } catch (IOException e) { - throw new EncoderException(e); + return ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); + } catch (Exception e) { + throw new DecoderException("Failed to read ItemStack", e); } } public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) { - if (item.isEmpty()) { - buf.writeInt(0); - return; - } - - final byte[] bytes; try { - final CompoundTag tag = new CompoundTag(); - var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); - ItemStack.CODEC.encode(item, registryOps, tag).getOrThrow(); - - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - NbtIo.writeCompressed(tag, out); - - bytes = out.toByteArray(); - } catch (IOException e) { - throw new EncoderException(e); + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item != null ? item : ItemStack.EMPTY); + } catch (Exception e) { + throw new EncoderException("Failed to write ItemStack", e); } - - buf.writeInt(bytes.length); - buf.writeBytes(bytes); } -} +} \ No newline at end of file From 0af75b7712678ff61171a03710a76a50847c232a Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:24:00 +0800 Subject: [PATCH 31/57] feat: Implement effects synchronization for players in SpectatorPlus --- .../client/sync/ClientSyncController.java | 50 +++++++------- .../fabric/sync/ServerSyncController.java | 2 + .../sync/handler/EffectsSyncHandler.java | 68 +++++++++++++++++++ 3 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index de56cb7..717ca99 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -42,32 +42,30 @@ public static void init() { private static void handle(ClientboundEffectsSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); - syncData.effects = packet.effects(); // Now List - System.out.println("[SpectatorPlus] Synced effects: " + syncData.effects); - - // var client = Minecraft.getInstance(); - // if (client.player != null) { - // // Remove all current effects from the client player - // List> toRemove = new ArrayList<>(client.player.getActiveEffectsMap().keySet()); - // for (Holder effect : toRemove) { - // System.out.println("[SpectatorPlus] Removing effect: " + BuiltInRegistries.MOB_EFFECT.getKey(effect.value())); - // client.player.removeEffect(effect); - // } - // // Add all synced effects to the client player - // for (SyncedEffect synced : syncData.effects) { - // System.out.println("[SpectatorPlus] Syncing effect: " + synced.effectKey + " duration=" + synced.duration + " amplifier=" + synced.amplifier); - // java.util.Optional> optHolder = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.tryParse(synced.effectKey)); - // if (optHolder.isPresent()) { - // MobEffect effect = optHolder.get().value(); - // Holder holder = Holder.direct(effect); - // MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); - // client.player.forceAddEffect(instance, client.player); - // System.out.println("[SpectatorPlus] Added effect: " + synced.effectKey); - // } else { - // System.out.println("[SpectatorPlus] Effect not found in registry: " + synced.effectKey); - // } - // } - // } + syncData.effects = packet.effects(); + + var client = Minecraft.getInstance(); + if (client.player != null) { + // 移除客户端玩家当前的所有效果 + List> toRemove = new ArrayList<>(client.player.getActiveEffectsMap().keySet()); + for (Holder effect : toRemove) { + client.player.removeEffect(effect); + } + + // 添加所有同步的效果到客户端玩家 + for (SyncedEffect synced : syncData.effects) { + ResourceLocation effectLocation = ResourceLocation.tryParse(synced.effectKey); + if (effectLocation != null) { + java.util.Optional> optHolder = BuiltInRegistries.MOB_EFFECT.get(effectLocation); + if (optHolder.isPresent()) { + MobEffect effect = optHolder.get().value(); + Holder holder = optHolder.get(); + MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); + client.player.forceAddEffect(instance, client.player); + } + } + } + } } private static void handle(ClientboundExperienceSyncPacket packet, ClientPlayNetworking.Context context) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java index a60b466..587735e 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.HotbarSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.ScreenSyncHandler; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; @@ -15,6 +16,7 @@ public static void init() { HotbarSyncHandler.init(); ScreenSyncHandler.init(); + EffectsSyncHandler.init(); } public static void sendPacket(ServerPlayer serverPlayer, ClientboundSyncPacket packet) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java new file mode 100644 index 0000000..baddb7b --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java @@ -0,0 +1,68 @@ +package com.hpfxd.spectatorplus.fabric.sync.handler; + +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; + +import java.util.*; + +public class EffectsSyncHandler { + private static final Map> EFFECTS = new HashMap<>(); + + public static void init() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> EFFECTS.remove(handler.getPlayer().getUUID())); + + ServerLifecycleEvents.SERVER_STOPPING.register(server -> EFFECTS.clear()); + + ServerTickEvents.END_WORLD_TICK.register(EffectsSyncHandler::tick); + } + + private static void tick(ServerLevel level) { + for (final ServerPlayer player : level.players()) { + final List cachedEffects = EFFECTS.computeIfAbsent(player.getUUID(), k -> new ArrayList<>()); + + final List currentEffects = new ArrayList<>(); + for (MobEffectInstance effectInstance : player.getActiveEffects()) { + String effectKey = BuiltInRegistries.MOB_EFFECT.getKey(effectInstance.getEffect().value()).toString(); + currentEffects.add(new SyncedEffect( + effectKey, + effectInstance.getAmplifier(), + effectInstance.getDuration() + )); + } + + if (!effectsEqual(currentEffects, cachedEffects)) { + cachedEffects.clear(); + cachedEffects.addAll(currentEffects); + + ServerSyncController.broadcastPacketToSpectators(player, new ClientboundEffectsSyncPacket(player.getUUID(), new ArrayList<>(currentEffects))); + } + } + } + + private static boolean effectsEqual(List list1, List list2) { + if (list1.size() != list2.size()) { + return false; + } + + final Map map1 = new HashMap<>(); + final Map map2 = new HashMap<>(); + + for (SyncedEffect effect : list1) { + map1.put(effect.effectKey, effect); + } + + for (SyncedEffect effect : list2) { + map2.put(effect.effectKey, effect); + } + + return map1.equals(map2); + } +} \ No newline at end of file From b93d50f468da9c0ed0784fd94b08ae75c7f2b017 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:06:36 +0800 Subject: [PATCH 32/57] fix: effect sync and render --- .../fabric/client/mixin/GuiMixin.java | 11 ++-- .../client/mixin/LivingEntityAccessor.java | 16 +++++ .../client/mixin/LivingEntityMixin.java | 21 +++++-- .../client/sync/ClientSyncController.java | 25 +------- .../fabric/client/util/EffectUtil.java | 60 +++++++++++++++++++ .../spectatorplus.client.mixins.json | 5 +- .../fabric/mixin/LivingEntityMixin.java | 40 +++++++++++++ .../fabric/mixin/ServerPlayerMixin.java | 2 + .../sync/handler/EffectsSyncHandler.java | 51 +++++++++------- .../main/resources/spectatorplus.mixins.json | 1 + 10 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java 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 cb37db1..6703c46 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 @@ -178,20 +178,19 @@ public abstract class GuiMixin { int effectBaseY = baseY + slots.length * (itemHeight + spacing) + spacing; // start below armor // Render all active effect icons down the right side below armor - LocalPlayer player = this.minecraft.player; - if (player != null && player.getActiveEffects() != null && !player.getActiveEffects().isEmpty()) { + if (ClientSyncController.syncData.effects != null && !ClientSyncController.syncData.effects.isEmpty()) { int effectIndex = 0; - for (var effectInstance : player.getActiveEffects()) { + for (var effectInstance : ClientSyncController.syncData.effects) { int y = effectBaseY + effectIndex * (itemWidth + spacing); // Draw vanilla effect background guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, EFFECT_BACKGROUND_SPRITE, baseX, y, itemWidth, itemHeight); - ResourceLocation effectIcon = Gui.getMobEffectSprite(effectInstance.getEffect()); + ResourceLocation effectIcon = GuiMixin.getEffectIcon(effectInstance.effectKey); guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX + 2, y + 2, itemWidth - 4, itemHeight - 4); // Draw effect level as a small white number on the top right of the icon - int level = effectInstance.getAmplifier() + 1; + int level = effectInstance.amplifier + 1; String levelText = String.valueOf(level); int levelTextWidth = this.minecraft.font.width(levelText); int levelTextX = baseX + itemWidth - (int)(levelTextWidth * 0.4F) - 3; // right-align inside top-right corner @@ -202,7 +201,7 @@ public abstract class GuiMixin { guiGraphics.pose().popMatrix(); // Draw duration bar (1px wide) to the left of the effect icon, color changes with percent - int duration = effectInstance.getDuration(); + int duration = effectInstance.duration; int maxDuration = 3600; // 3 minutes, adjust as needed float percent = maxDuration > 0 ? (duration / (float)maxDuration) : 1.0F; int maxBarHeight = itemHeight - 2; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java new file mode 100644 index 0000000..e318705 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java @@ -0,0 +1,16 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import net.minecraft.core.Holder; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(LivingEntity.class) +public interface LivingEntityAccessor { + @Accessor("activeEffects") + Map, MobEffectInstance> spectatorplus$getActiveEffects(); +} diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index 2da419c..a69714c 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.client.mixin; +import com.hpfxd.spectatorplus.fabric.client.util.EffectUtil; import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; @@ -7,23 +8,20 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.phys.HitResult; - -import java.util.Collection; - -import org.jetbrains.annotations.Nullable; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends Entity { @@ -54,4 +52,15 @@ private boolean isLookingAtBlock() { return ((GameRendererAccessor) Minecraft.getInstance().gameRenderer).invokePick(this, player.blockInteractionRange(), player.entityInteractionRange(), 1F).getType() == HitResult.Type.BLOCK; } + + @Redirect(method = {"hasEffect", "getEffect", "getActiveEffects", "tickEffects"}, + at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;activeEffects:Ljava/util/Map;")) + private Map, MobEffectInstance> spectatorplus$redirectActiveEffects(LivingEntity instance) { + // 只对玩家且满足条件时才重定向 + if (instance instanceof Player && EffectUtil.shouldUseSpectatorData()) { + return EffectUtil.getActiveEffectsMap(); + } + return ((LivingEntityAccessor) instance).spectatorplus$getActiveEffects(); + } + } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index 717ca99..00d78de 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -1,6 +1,7 @@ package com.hpfxd.spectatorplus.fabric.client.sync; import com.hpfxd.spectatorplus.fabric.client.sync.screen.ScreenSyncController; +import com.hpfxd.spectatorplus.fabric.client.util.EffectUtil; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -43,29 +44,7 @@ public static void init() { private static void handle(ClientboundEffectsSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); syncData.effects = packet.effects(); - - var client = Minecraft.getInstance(); - if (client.player != null) { - // 移除客户端玩家当前的所有效果 - List> toRemove = new ArrayList<>(client.player.getActiveEffectsMap().keySet()); - for (Holder effect : toRemove) { - client.player.removeEffect(effect); - } - - // 添加所有同步的效果到客户端玩家 - for (SyncedEffect synced : syncData.effects) { - ResourceLocation effectLocation = ResourceLocation.tryParse(synced.effectKey); - if (effectLocation != null) { - java.util.Optional> optHolder = BuiltInRegistries.MOB_EFFECT.get(effectLocation); - if (optHolder.isPresent()) { - MobEffect effect = optHolder.get().value(); - Holder holder = optHolder.get(); - MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); - client.player.forceAddEffect(instance, client.player); - } - } - } - } + EffectUtil.updateEffectInstances(packet.effects()); } private static void handle(ClientboundExperienceSyncPacket packet, ClientPlayNetworking.Context context) { diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java new file mode 100644 index 0000000..dbc5796 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java @@ -0,0 +1,60 @@ +package com.hpfxd.spectatorplus.fabric.client.util; + + +import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import net.minecraft.client.Minecraft; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; + +import java.util.*; + +public class EffectUtil { + private static final Map, MobEffectInstance> activeEffects = new HashMap<>(); + + public static void updateEffectInstances(List effects) { + // 收集新的效果 + Set> newEffects = new HashSet<>(); + + for (SyncedEffect syncedEffect : effects) { + Holder effect = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.parse(syncedEffect.effectKey)) + .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); + newEffects.add(effect); + } + + // 移除不再存在的效果 + activeEffects.entrySet().removeIf(entry -> !newEffects.contains(entry.getKey())); + + // 添加新效果(保持现有实例的BlendState) + for (SyncedEffect syncedEffect : effects) { + Holder effect = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.parse(syncedEffect.effectKey)) + .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); + + if (!activeEffects.containsKey(effect)) { + MobEffectInstance instance = new MobEffectInstance(effect, syncedEffect.duration, + syncedEffect.amplifier, false, true, true); + activeEffects.put(effect, instance); + } + } + } + + public static boolean hasValidSyncData() { + return ClientSyncController.syncData != null && + ClientSyncController.syncData.effects != null; + } + + public static boolean shouldUseSpectatorData() { + Minecraft mc = Minecraft.getInstance(); + return mc.player != null && + SpecUtil.getCameraPlayer(mc) != null && + hasValidSyncData(); + } + + // 直接返回原版格式的activeEffects + public static Map, MobEffectInstance> getActiveEffectsMap() { + return activeEffects; + } +} \ No newline at end of file diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index 59c3082..f39dd46 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -34,5 +34,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "LivingEntityAccessor" + ] } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java new file mode 100644 index 0000000..4a263c5 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java @@ -0,0 +1,40 @@ +package com.hpfxd.spectatorplus.fabric.mixin; + +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +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; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; + +@Mixin(LivingEntity.class) +public class LivingEntityMixin { + + @Inject(method = "onEffectAdded", at = @At("TAIL")) + private void onEffectAdded(MobEffectInstance effectInstance, @Nullable Entity entity, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } + + @Inject(method = "onEffectUpdated", at = @At("TAIL")) + private void onEffectUpdated(MobEffectInstance effectInstance, boolean forced, @Nullable Entity entity, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } + + @Inject(method = "onEffectsRemoved", at = @At("TAIL")) + private void onEffectsRemoved(Collection effects, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } +} \ No newline at end of file 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 d03126d..15c7b1f 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 @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import com.hpfxd.spectatorplus.fabric.SpectatorMod; import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -66,6 +67,7 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.sendPacket(spectator, ClientboundFoodSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundHotbarSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); + EffectsSyncHandler.onStartSpectating(spectator, target); // Send initial map data patch packet if the target has a map in inventory for (final ItemStack stack : target.getInventory().getNonEquipmentItems()) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java index baddb7b..ac878d4 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java @@ -4,10 +4,9 @@ import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffectInstance; @@ -18,31 +17,43 @@ public class EffectsSyncHandler { public static void init() { ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> EFFECTS.remove(handler.getPlayer().getUUID())); - ServerLifecycleEvents.SERVER_STOPPING.register(server -> EFFECTS.clear()); + } - ServerTickEvents.END_WORLD_TICK.register(EffectsSyncHandler::tick); + // 当玩家开始旁观另一个玩家时调用 + public static void onStartSpectating(ServerPlayer spectator, ServerPlayer target) { + syncPlayerEffects(spectator, target); } - private static void tick(ServerLevel level) { - for (final ServerPlayer player : level.players()) { - final List cachedEffects = EFFECTS.computeIfAbsent(player.getUUID(), k -> new ArrayList<>()); + // 当玩家的药水效果改变时调用 + public static void onEffectChanged(ServerPlayer player) { + // 获取所有正在旁观此玩家的观察者 + for (ServerPlayer spectator : ServerSyncController.getSpectators(player)) { + syncPlayerEffects(spectator, player); + } + } - final List currentEffects = new ArrayList<>(); - for (MobEffectInstance effectInstance : player.getActiveEffects()) { - String effectKey = BuiltInRegistries.MOB_EFFECT.getKey(effectInstance.getEffect().value()).toString(); - currentEffects.add(new SyncedEffect( - effectKey, - effectInstance.getAmplifier(), - effectInstance.getDuration() - )); - } + private static void syncPlayerEffects(ServerPlayer spectator, ServerPlayer target) { + final List currentEffects = new ArrayList<>(); + for (MobEffectInstance effectInstance : target.getActiveEffects()) { + String effectKey = BuiltInRegistries.MOB_EFFECT.getKey(effectInstance.getEffect().value()).toString(); + currentEffects.add(new SyncedEffect( + effectKey, + effectInstance.getAmplifier(), + effectInstance.getDuration() + )); + } + + final List cachedEffects = EFFECTS.computeIfAbsent(spectator.getUUID(), k -> new ArrayList<>()); - if (!effectsEqual(currentEffects, cachedEffects)) { - cachedEffects.clear(); - cachedEffects.addAll(currentEffects); + if (!effectsEqual(currentEffects, cachedEffects)) { + cachedEffects.clear(); + cachedEffects.addAll(currentEffects); - ServerSyncController.broadcastPacketToSpectators(player, new ClientboundEffectsSyncPacket(player.getUUID(), new ArrayList<>(currentEffects))); + // 使用ServerPlayNetworking发送包 + ClientboundEffectsSyncPacket packet = new ClientboundEffectsSyncPacket(target.getUUID(), new ArrayList<>(currentEffects)); + if (packet.canSend(spectator)) { + ServerPlayNetworking.send(spectator, packet); } } } diff --git a/fabric/src/main/resources/spectatorplus.mixins.json b/fabric/src/main/resources/spectatorplus.mixins.json index 3499f2b..780cf8a 100644 --- a/fabric/src/main/resources/spectatorplus.mixins.json +++ b/fabric/src/main/resources/spectatorplus.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "ChunkMapAccessor", + "LivingEntityMixin", "ServerGamePacketListenerImplMixin", "ServerPlayerMixin", "TrackedEntityMixin" From 3751651ced59692ace2bc811e586dadbb03c1bba Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sun, 14 Dec 2025 02:39:32 +0800 Subject: [PATCH 33/57] fix: hand height calculations bug --- .../fabric/client/mixin/ItemInHandRendererMixin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java index 04e4c23..96e0817 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java @@ -55,8 +55,10 @@ public abstract class ItemInHandRendererMixin { if (this.spectated == spectated) { float f = spectated.getAttackStrengthScale(1.0F); - this.mainHandHeight += Mth.clamp((this.mainHandItem == mainHandItem ? f * f * f : 0.0F) - this.mainHandHeight, -0.4F, 0.4F); - this.offHandHeight += Mth.clamp((float) (this.offHandItem == offHandItem ? 1 : 0) - this.offHandHeight, -0.4F, 0.4F); + float g = this.mainHandItem != mainHandItem ? 0.0F : f * f * f; + float h = this.offHandItem != offHandItem ? 0.0F : 1.0F; + this.mainHandHeight = this.mainHandHeight + Mth.clamp(g - this.mainHandHeight, -0.4F, 0.4F); + this.offHandHeight = this.offHandHeight + Mth.clamp(h - this.offHandHeight, -0.4F, 0.4F); if (this.mainHandHeight < 0.1F) { this.mainHandItem = mainHandItem; From d243f9c6d14bdfb0d8aef499bca59166d58f2a02 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sun, 14 Dec 2025 21:01:54 +0800 Subject: [PATCH 34/57] fix: handle null viewingEntity in sync data reset, which case a client crash --- .../spectatorplus/fabric/client/mixin/MinecraftMixin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java index 6c1d158..1e084e9 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java @@ -23,8 +23,10 @@ public abstract class MinecraftMixin { @Inject(method = "setCameraEntity(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "TAIL")) private void spectatorplus$resetSyncDataOnCameraSwitch(Entity viewingEntity, CallbackInfo ci) { - if (ClientSyncController.syncData != null && !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID())) { - ClientSyncController.setSyncData(null); + if (ClientSyncController.syncData != null) { + if (viewingEntity == null || !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID())) { + ClientSyncController.setSyncData(null); + } } } From 532e1fcb34297ae57a3fca77f68885dbe9f6946d Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:53:10 +0800 Subject: [PATCH 35/57] fix: handle null ItemStack serialization in CustomPacketCodecs,which case handler cant recognize null item --- .../fabric/sync/CustomPacketCodecs.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 3f92c3e..a1a7dfa 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 @@ -4,6 +4,7 @@ import io.netty.handler.codec.EncoderException; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; public final class CustomPacketCodecs { private CustomPacketCodecs() { @@ -14,7 +15,7 @@ public static ItemStack[] readItems(RegistryFriendlyByteBuf buf) { final ItemStack[] items = new ItemStack[len]; for (int slot = 0; slot < len; slot++) { - items[slot] = ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); + items[slot] = buf.readBoolean() ? ItemStack.OPTIONAL_STREAM_CODEC.decode(buf) : null; } return items; @@ -24,7 +25,10 @@ public static void writeItems(RegistryFriendlyByteBuf buf, ItemStack[] items) { buf.writeInt(items.length); for (final ItemStack item : items) { - ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item != null ? item : ItemStack.EMPTY); + buf.writeBoolean(item != null); + if (item != null) { + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item); + } } } @@ -36,9 +40,10 @@ public static ItemStack readItem(RegistryFriendlyByteBuf buf) { } } - public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) { + + public static void writeItem(RegistryFriendlyByteBuf buf, @NotNull ItemStack item) { try { - ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item != null ? item : ItemStack.EMPTY); + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item); } catch (Exception e) { throw new EncoderException("Failed to write ItemStack", e); } From 6db7fdf33c7c6e3d4a2f1d8525d1c15990b4d627 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:14:33 +0800 Subject: [PATCH 36/57] fix: prevent incorrect arm orientation when spectating by skipping mulPose in renderItemInHand --- .../fabric/client/mixin/GameRendererMixin.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 de281b2..a6f1873 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 @@ -14,10 +14,14 @@ import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.animal.CowVariant; +import net.minecraft.world.entity.player.PlayerModelType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameType; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -45,6 +49,18 @@ public abstract class GameRendererMixin { @Unique private float xBobO; @Unique private float yBobO; + @Redirect(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Matrix4fc;)V", ordinal = 0)) + private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { + // In spectator mode, the projection matrix contains rotation data from the spectated player, + // while arm rendering calculations are based on the localPlayer's viewpoint. + // We must skip this mulPose operation when spectating to avoid rendering arms with incorrect orientation. + if (this.minecraft.player == null + || this.minecraft.player.gameMode() != GameType.SPECTATOR + || !this.minecraft.options.getCameraType().isFirstPerson()) { + poseStack.mulPose(matrix); + } + } + @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, CallbackInfo ci, @Local PoseStack poseStackIn) { if (SpectatorClientMod.config.renderArms && this.minecraft.player != null && this.minecraft.options.getCameraType().isFirstPerson() && !this.minecraft.options.hideGui) { From 3116679e4b556da257710729039ce092cbb9ec0c Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:23:37 +0800 Subject: [PATCH 37/57] fix: update fabric-loom plugin version to 1.13-SNAPSHOT --- fabric/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index e1e4187..dcd77b6 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "1.11.7" + id("fabric-loom") version "1.13-SNAPSHOT" id("spectatorplus.platform") } From 8ae3bc68da07cf8a28d666052adfea2cd6509e45 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 3 Jan 2026 15:51:16 +0800 Subject: [PATCH 38/57] update to 1.21.11 --- fabric/build.gradle.kts | 2 +- fabric/gradle.properties | 18 +++++++++--------- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index dcd77b6..ecfe9db 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "1.13-SNAPSHOT" + id("fabric-loom") version "1.14-SNAPSHOT" id("spectatorplus.platform") } diff --git a/fabric/gradle.properties b/fabric/gradle.properties index fe56e70..b932122 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,13 +1,13 @@ -minecraft_version=1.21.10 -yarn_mappings=1.21.10+build.1 -loader_version=0.17.2 +minecraft_version=1.21.11 +yarn_mappings=1.21.11+build.3 +loader_version=0.18.4 # Fabric API -fabric_version=0.138.3+1.21.10 +fabric_version=0.140.2+1.21.11 -parchment_minecraft_version=1.21.10 -parchment_version=2025.10.12 +parchment_minecraft_version=1.21.11 +parchment_version=2025.12.20 -fabric_permissions_api_version=0.4.0 -cloth_config_version=20.0.149 -modmenu_version=16.0.0-rc.1 +fabric_permissions_api_version=0.6.1 +cloth_config_version=21.11.153 +modmenu_version=17.0.0-beta.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index af71930..ad01527 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,8 +13,8 @@ plugins { rootProject.name = "spectatorplus" includeBuild("build-logic") -this.setupSubproject("paper") -this.setupSubproject("fabric") +setupSubproject("paper") +setupSubproject("fabric") fun setupSubproject(moduleName: String) { val name = "spectatorplus-$moduleName" From ff06b36a95c945307db7403cf0da48a7764528e5 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sun, 4 Jan 2026 12:44:31 +0800 Subject: [PATCH 39/57] fix: replace ResourceLocation with Identifier for packet type definitions --- .../fabric/sync/packet/ClientboundEffectsSyncPacket.java | 4 ++-- .../fabric/sync/packet/ClientboundExperienceSyncPacket.java | 5 +++-- .../fabric/sync/packet/ClientboundFoodSyncPacket.java | 4 ++-- .../fabric/sync/packet/ClientboundHotbarSyncPacket.java | 4 ++-- .../fabric/sync/packet/ClientboundInventorySyncPacket.java | 5 +++-- .../sync/packet/ClientboundScreenCursorSyncPacket.java | 4 ++-- .../fabric/sync/packet/ClientboundScreenSyncPacket.java | 5 +++-- .../sync/packet/ClientboundSelectedSlotSyncPacket.java | 5 +++-- .../sync/packet/ServerboundOpenedInventorySyncPacket.java | 5 +++-- .../sync/packet/ServerboundRequestInventoryOpenPacket.java | 5 +++-- 10 files changed, 26 insertions(+), 20 deletions(-) diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java index cc9e68c..3468b4e 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java @@ -6,7 +6,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -19,7 +19,7 @@ public record ClientboundEffectsSyncPacket( List effects ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundEffectsSyncPacket::write, ClientboundEffectsSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:effects_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","effects_sync")); private static final String PERMISSION = "spectatorplus.sync.effects"; public ClientboundEffectsSyncPacket(FriendlyByteBuf buf) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java index f866588..ff1205a 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java @@ -5,7 +5,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; + +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -18,7 +19,7 @@ public record ClientboundExperienceSyncPacket( int level ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundExperienceSyncPacket::write, ClientboundExperienceSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:experience_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","experience_sync")); private static final String PERMISSION = "spectatorplus.sync.experience"; public static ClientboundExperienceSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java index ec16535..4fd363d 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -17,7 +17,7 @@ public record ClientboundFoodSyncPacket( float saturation ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundFoodSyncPacket::write, ClientboundFoodSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:food_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","food_sync")); private static final String PERMISSION = "spectatorplus.sync.food"; public static ClientboundFoodSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java index 533410f..4483d52 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java @@ -6,7 +6,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundHotbarSyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundHotbarSyncPacket::write, ClientboundHotbarSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:hotbar_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","hotbar_sync")); static final String PERMISSION = "spectatorplus.sync.hotbar"; public static ClientboundHotbarSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java index 9741792..7437cbe 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java @@ -6,7 +6,8 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; + +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -23,7 +24,7 @@ public record ClientboundInventorySyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundInventorySyncPacket::write, ClientboundInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","inventory_sync")); public static final int ITEMS_LENGTH = 4 * 9 + 4 + 1; // 36 main + 4 armor + 1 offhand = 41 private static final String PERMISSION = "spectatorplus.sync.inventory"; diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java index 433b518..c0d907a 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundScreenCursorSyncPacket( int originSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenCursorSyncPacket::write, ClientboundScreenCursorSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:screen_cursor_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","screen_cursor_sync")); public ClientboundScreenCursorSyncPacket(RegistryFriendlyByteBuf buf) { this(buf.readUUID(), CustomPacketCodecs.readItem(buf), buf.readByte()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java index ba92dde..0d17635 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java @@ -4,7 +4,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; + +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -15,7 +16,7 @@ public record ClientboundScreenSyncPacket( int flags ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenSyncPacket::write, ClientboundScreenSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:screen_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","screen_sync")); public ClientboundScreenSyncPacket(FriendlyByteBuf buf) { this(buf.readUUID(), buf.readByte()); 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 756a9fa..623b992 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 @@ -5,7 +5,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; + +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -18,7 +19,7 @@ public record ClientboundSelectedSlotSyncPacket( int selectedSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundSelectedSlotSyncPacket::write, ClientboundSelectedSlotSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:selected_slot_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","selected_slot_sync")); public static ClientboundSelectedSlotSyncPacket initializing(ServerPlayer target) { return new ClientboundSelectedSlotSyncPacket(target.getUUID(), target.getInventory().getSelectedSlot()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java index e00b7d6..6d3365c 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java @@ -4,11 +4,12 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; + public final class ServerboundOpenedInventorySyncPacket implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundOpenedInventorySyncPacket::write, ServerboundOpenedInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:opened_inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","opened_inventory_sync")); public ServerboundOpenedInventorySyncPacket() { } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java index f9f5c7e..10d7f4f 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java @@ -4,7 +4,8 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; + +import net.minecraft.resources.Identifier; import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -13,7 +14,7 @@ public record ServerboundRequestInventoryOpenPacket( UUID playerId ) implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundRequestInventoryOpenPacket::write, ServerboundRequestInventoryOpenPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:request_inventory_open")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","request_inventory_open")); public ServerboundRequestInventoryOpenPacket(FriendlyByteBuf buf) { this(buf.readUUID()); From a37be0f37d4e49ad9a4c1631211b4aec79fefbdd Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:23:00 +0800 Subject: [PATCH 40/57] beautiful code style --- .../spectatorplus/fabric/client/mixin/MinecraftMixin.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java index 1e084e9..e0cdcc2 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java @@ -23,10 +23,8 @@ public abstract class MinecraftMixin { @Inject(method = "setCameraEntity(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "TAIL")) private void spectatorplus$resetSyncDataOnCameraSwitch(Entity viewingEntity, CallbackInfo ci) { - if (ClientSyncController.syncData != null) { - if (viewingEntity == null || !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID())) { - ClientSyncController.setSyncData(null); - } + if (ClientSyncController.syncData != null && (viewingEntity == null || !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID()))) { + ClientSyncController.setSyncData(null); } } From ccc204db86cfcc3bbfa7cfc7ce02e441d793cfa7 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:35:00 +0800 Subject: [PATCH 41/57] Migrate to 1.21.11 # Conflicts: # fabric/gradle.properties # fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java # fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java # fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java # fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java # paper/gradle.properties # settings.gradle.kts --- fabric/build.gradle.kts | 1 - .../fabric/client/SpectatorKeybinds.java | 2 +- .../fabric/client/mixin/GuiMixin.java | 26 +++++++++---------- .../screen/AbstractContainerScreenMixin.java | 6 ++--- .../client/sync/ClientSyncController.java | 2 +- .../fabric/client/util/SpecUtil.java | 26 +++++++++---------- .../packet/ClientboundEffectsSyncPacket.java | 2 +- .../ClientboundExperienceSyncPacket.java | 3 +-- .../packet/ClientboundFoodSyncPacket.java | 2 +- .../packet/ClientboundHotbarSyncPacket.java | 2 +- .../ClientboundInventorySyncPacket.java | 3 +-- .../ClientboundScreenCursorSyncPacket.java | 2 +- .../packet/ClientboundScreenSyncPacket.java | 3 +-- .../ClientboundSelectedSlotSyncPacket.java | 3 +-- .../ServerboundOpenedInventorySyncPacket.java | 3 +-- ...ServerboundRequestInventoryOpenPacket.java | 3 +-- fabric/src/main/resources/fabric.mod.json | 2 +- paper/build.gradle.kts | 2 +- paper/gradle.properties | 2 +- 19 files changed, 44 insertions(+), 51 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index ecfe9db..0bfcc35 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -32,7 +32,6 @@ dependencies { officialMojangMappings() parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") }) - modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") include(modImplementation("me.lucko:fabric-permissions-api:${property("fabric_permissions_api_version")}")!!) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java index fc3c452..bccc9d7 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java @@ -7,7 +7,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.ChatFormatting; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.spectator.SpectatorGui; 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 6703c46..07f0272 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 @@ -40,7 +40,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context.Local; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.core.Holder; @Mixin(Gui.class) @@ -54,15 +54,15 @@ public abstract class GuiMixin { @Shadow @Final private SpectatorGui spectatorGui; - // Use correct ResourceLocations for vanilla empty armor slot icons from the GUI atlas - private static final ResourceLocation EMPTY_ARMOR_SLOT_HELMET = ResourceLocation.withDefaultNamespace("container/slot/helmet"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_CHESTPLATE = ResourceLocation.withDefaultNamespace("container/slot/chestplate"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_LEGGINGS = ResourceLocation.withDefaultNamespace("container/slot/leggings"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_BOOTS = ResourceLocation.withDefaultNamespace("container/slot/boots"); - private static final ResourceLocation EFFECT_BACKGROUND_AMBIENT_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background_ambient"); - private static final ResourceLocation EFFECT_BACKGROUND_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background"); + // Use correct Identifiers for vanilla empty armor slot icons from the GUI atlas + private static final Identifier EMPTY_ARMOR_SLOT_HELMET = Identifier.withDefaultNamespace("container/slot/helmet"); + private static final Identifier EMPTY_ARMOR_SLOT_CHESTPLATE = Identifier.withDefaultNamespace("container/slot/chestplate"); + private static final Identifier EMPTY_ARMOR_SLOT_LEGGINGS = Identifier.withDefaultNamespace("container/slot/leggings"); + private static final Identifier EMPTY_ARMOR_SLOT_BOOTS = Identifier.withDefaultNamespace("container/slot/boots"); + private static final Identifier EFFECT_BACKGROUND_AMBIENT_SPRITE = Identifier.withDefaultNamespace("hud/effect_background_ambient"); + private static final Identifier EFFECT_BACKGROUND_SPRITE = Identifier.withDefaultNamespace("hud/effect_background"); - private static final ResourceLocation[] TEXTURE_EMPTY_SLOTS = new ResourceLocation[]{ + private static final Identifier[] TEXTURE_EMPTY_SLOTS = new Identifier[]{ EMPTY_ARMOR_SLOT_BOOTS, EMPTY_ARMOR_SLOT_LEGGINGS, EMPTY_ARMOR_SLOT_CHESTPLATE, EMPTY_ARMOR_SLOT_HELMET }; @@ -186,7 +186,7 @@ public abstract class GuiMixin { // Draw vanilla effect background guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, EFFECT_BACKGROUND_SPRITE, baseX, y, itemWidth, itemHeight); - ResourceLocation effectIcon = GuiMixin.getEffectIcon(effectInstance.effectKey); + Identifier effectIcon = GuiMixin.getEffectIcon(effectInstance.effectKey); guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX + 2, y + 2, itemWidth - 4, itemHeight - 4); // Draw effect level as a small white number on the top right of the icon @@ -364,8 +364,8 @@ public abstract class GuiMixin { } return instance; } - // Map EffectType to vanilla effect icon ResourceLocation - private static ResourceLocation getEffectIcon(String effectKey) { + // Map EffectType to vanilla effect icon Identifier + private static Identifier getEffectIcon(String effectKey) { // If effectKey contains a namespace (e.g., minecraft:nausea), strip it String key = effectKey; int colonIdx = key.indexOf(":"); @@ -374,7 +374,7 @@ private static ResourceLocation getEffectIcon(String effectKey) { } // Vanilla effect icons are in the GUI atlas as effect/ // The effectKey should be lowercase, matching the registry name - return ResourceLocation.withDefaultNamespace("mob_effect/" + key.toLowerCase()); + return Identifier.withDefaultNamespace("mob_effect/" + key.toLowerCase()); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java index 72700ec..248ceb6 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java @@ -8,7 +8,7 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ClickType; @@ -46,8 +46,8 @@ public abstract class AbstractContainerScreenMixin { @Unique private boolean mouseMoved; - @Shadow @Final private static ResourceLocation SLOT_HIGHLIGHT_BACK_SPRITE; - @Shadow @Final private static ResourceLocation SLOT_HIGHLIGHT_FRONT_SPRITE; + @Shadow @Final private static Identifier SLOT_HIGHLIGHT_BACK_SPRITE; + @Shadow @Final private static Identifier SLOT_HIGHLIGHT_FRONT_SPRITE; @Shadow protected abstract void renderFloatingItem(GuiGraphics guiGraphics, ItemStack stack, int x, int y, String text); @Shadow @Final protected AbstractContainerMenu menu; @Shadow @Nullable protected abstract Slot getHoveredSlot(double mouseX, double mouseY); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index 00d78de..468e659 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -12,7 +12,7 @@ import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java index 3c7fbfc..4192260 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java @@ -21,17 +21,17 @@ public static AbstractClientPlayer getCameraPlayer(Minecraft minecraft) { return null; } - /** - * Returns the synced MobEffectInstance for the given entity and effect, or null if not found. - */ - public static net.minecraft.world.effect.MobEffectInstance getSyncedEffect(LivingEntity entity, Holder effect) { - if (ClientSyncController.syncData == null || ClientSyncController.syncData.effects == null) return null; - String queryKey = effect.unwrapKey().get().location().toString(); - for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : ClientSyncController.syncData.effects) { - if (queryKey.equals(synced.effectKey)) { - return new net.minecraft.world.effect.MobEffectInstance(effect, synced.duration, synced.amplifier); - } - } - return null; - } + // /** + // * Returns the synced MobEffectInstance for the given entity and effect, or null if not found. + // */ + // public static net.minecraft.world.effect.MobEffectInstance getSyncedEffect(LivingEntity entity, Holder effect) { + // if (ClientSyncController.syncData == null || ClientSyncController.syncData.effects == null) return null; + // String queryKey = effect.unwrapKey().get().identifier().toString(); + // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : ClientSyncController.syncData.effects) { + // if (queryKey.equals(synced.effectKey)) { + // return new net.minecraft.world.effect.MobEffectInstance(effect, synced.duration, synced.amplifier); + // } + // } + // return null; + // } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java index 3468b4e..b8d820f 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java @@ -19,7 +19,7 @@ public record ClientboundEffectsSyncPacket( List effects ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundEffectsSyncPacket::write, ClientboundEffectsSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","effects_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:effects_sync")); private static final String PERMISSION = "spectatorplus.sync.effects"; public ClientboundEffectsSyncPacket(FriendlyByteBuf buf) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java index ff1205a..2e513fd 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java @@ -5,7 +5,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -19,7 +18,7 @@ public record ClientboundExperienceSyncPacket( int level ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundExperienceSyncPacket::write, ClientboundExperienceSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","experience_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:experience_sync")); private static final String PERMISSION = "spectatorplus.sync.experience"; public static ClientboundExperienceSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java index 4fd363d..f6e8327 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java @@ -17,7 +17,7 @@ public record ClientboundFoodSyncPacket( float saturation ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundFoodSyncPacket::write, ClientboundFoodSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","food_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:food_sync")); private static final String PERMISSION = "spectatorplus.sync.food"; public static ClientboundFoodSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java index 4483d52..e39b7d0 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java @@ -18,7 +18,7 @@ public record ClientboundHotbarSyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundHotbarSyncPacket::write, ClientboundHotbarSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","hotbar_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:hotbar_sync")); static final String PERMISSION = "spectatorplus.sync.hotbar"; public static ClientboundHotbarSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java index 7437cbe..88a0aa0 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java @@ -6,7 +6,6 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; @@ -24,7 +23,7 @@ public record ClientboundInventorySyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundInventorySyncPacket::write, ClientboundInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:inventory_sync")); public static final int ITEMS_LENGTH = 4 * 9 + 4 + 1; // 36 main + 4 armor + 1 offhand = 41 private static final String PERMISSION = "spectatorplus.sync.inventory"; diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java index c0d907a..157a972 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java @@ -18,7 +18,7 @@ public record ClientboundScreenCursorSyncPacket( int originSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenCursorSyncPacket::write, ClientboundScreenCursorSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","screen_cursor_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_cursor_sync")); public ClientboundScreenCursorSyncPacket(RegistryFriendlyByteBuf buf) { this(buf.readUUID(), CustomPacketCodecs.readItem(buf), buf.readByte()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java index 0d17635..cb0464d 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java @@ -4,7 +4,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -16,7 +15,7 @@ public record ClientboundScreenSyncPacket( int flags ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenSyncPacket::write, ClientboundScreenSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","screen_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_sync")); public ClientboundScreenSyncPacket(FriendlyByteBuf buf) { this(buf.readUUID(), buf.readByte()); 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 623b992..2999fa2 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 @@ -5,7 +5,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -19,7 +18,7 @@ public record ClientboundSelectedSlotSyncPacket( int selectedSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundSelectedSlotSyncPacket::write, ClientboundSelectedSlotSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","selected_slot_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:selected_slot_sync")); public static ClientboundSelectedSlotSyncPacket initializing(ServerPlayer target) { return new ClientboundSelectedSlotSyncPacket(target.getUUID(), target.getInventory().getSelectedSlot()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java index 6d3365c..e6aea33 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java @@ -6,10 +6,9 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.Identifier; - public final class ServerboundOpenedInventorySyncPacket implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundOpenedInventorySyncPacket::write, ServerboundOpenedInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","opened_inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:opened_inventory_sync")); public ServerboundOpenedInventorySyncPacket() { } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java index 10d7f4f..562d9d8 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java @@ -4,7 +4,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - import net.minecraft.resources.Identifier; import org.jetbrains.annotations.NotNull; @@ -14,7 +13,7 @@ public record ServerboundRequestInventoryOpenPacket( UUID playerId ) implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundRequestInventoryOpenPacket::write, ServerboundRequestInventoryOpenPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("spectatorplus","request_inventory_open")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:request_inventory_open")); public ServerboundRequestInventoryOpenPacket(FriendlyByteBuf buf) { this(buf.readUUID()); diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 30eadde..502fe31 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -51,7 +51,7 @@ ], "depends": { "fabricloader": ">=0.15.0", - "minecraft": "~1.21.10", + "minecraft": "~1.21.11", "java": ">=21", "fabric-api": "*", "fabric-permissions-api-v0": "*" diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index d068769..c0ee068 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -47,7 +47,7 @@ tasks { } runServer { - minecraftVersion("1.21.10") + minecraftVersion("1.21.11") } named("build") { diff --git a/paper/gradle.properties b/paper/gradle.properties index 9ada3d7..67ca6fa 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ -paper_version=1.21.7-R0.1-SNAPSHOT +paper_version=1.21.11-R0.1-SNAPSHOT reflection_remapper_version=0.1.3 \ No newline at end of file From 058dbbaa67b2bbee8e29208f6bd2a2eba970abc5 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 4 Jan 2025 16:52:00 +0800 Subject: [PATCH 42/57] migrate entityInteractionRange() form gameRendererMixin to new localPlayerMixin Connors Fixes # Conflicts: # fabric/gradle.properties # fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java # paper/build.gradle.kts --- .../client/mixin/GameRendererAccessor.java | 13 -------- .../client/mixin/GameRendererMixin.java | 23 ------------- .../client/mixin/LivingEntityMixin.java | 5 ++- .../client/mixin/LocalPlayerAccessor.java | 15 +++++++++ .../fabric/client/mixin/LocalPlayerMixin.java | 32 +++++++++++++++++++ .../spectatorplus.client.mixins.json | 3 +- 6 files changed, 53 insertions(+), 38 deletions(-) delete mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java deleted file mode 100644 index 31562dc..0000000 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.hpfxd.spectatorplus.fabric.client.mixin; - -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.HitResult; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(GameRenderer.class) -public interface GameRendererAccessor { - @Invoker - HitResult invokePick(Entity entity, double blockRange, double entityRange, float partialTicks); -} 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 a6f1873..261783e 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 @@ -14,8 +14,6 @@ import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.animal.CowVariant; -import net.minecraft.world.entity.player.PlayerModelType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.GameType; @@ -177,25 +175,4 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender if (minecraft.getCameraEntity() == this.minecraft.player) return instance.getInterpolatedBob(partialTick); return Mth.lerp(partialTick, this.bobO, this.bob); } - - @ModifyExpressionValue(method = "pick(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;blockInteractionRange()D")) - private double spectatorplus$modifyBlockInteractionRange(double original) { - final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); - if (spectated != null) { - return spectated.blockInteractionRange(); - } - - return original; - } - - @ModifyExpressionValue(method = "pick(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;entityInteractionRange()D")) - private double spectatorplus$modifyEntityInteractionRange(double original) { - final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); - if (spectated != null) { - return spectated.entityInteractionRange(); - } - - return original; - } - } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index a69714c..608520b 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -50,7 +50,10 @@ private boolean isLookingAtBlock() { return false; } - return ((GameRendererAccessor) Minecraft.getInstance().gameRenderer).invokePick(this, player.blockInteractionRange(), player.entityInteractionRange(), 1F).getType() == HitResult.Type.BLOCK; + var spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + var blockRange = spectated == null ? player.blockInteractionRange() : spectated.blockInteractionRange(); + var entityRange = spectated == null ? player.entityInteractionRange() : spectated.entityInteractionRange(); + return LocalPlayerAccessor.invokePick(this, blockRange, entityRange, 1F).getType() == HitResult.Type.BLOCK; } @Redirect(method = {"hasEffect", "getEffect", "getActiveEffects", "tickEffects"}, diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java new file mode 100644 index 0000000..3667748 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java @@ -0,0 +1,15 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(LocalPlayer.class) +public interface LocalPlayerAccessor { + @Invoker + static HitResult invokePick(Entity cameraEntity, double blockRange, double entityRange, float partialTick) { + throw new AssertionError(); + } +} \ No newline at end of file diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java new file mode 100644 index 0000000..c5d58ac --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java @@ -0,0 +1,32 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LocalPlayer.class) +public class LocalPlayerMixin { + @ModifyExpressionValue(method = "raycastHitResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;blockInteractionRange()D")) + private double spectatorplus$modifyBlockInteractionRange(double original) { + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + if (spectated != null) { + return spectated.blockInteractionRange(); + } + + return original; + } + + @ModifyExpressionValue(method = "raycastHitResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;entityInteractionRange()D")) + private double spectatorplus$modifyEntityInteractionRange(double original) { + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + if (spectated != null) { + return spectated.entityInteractionRange(); + } + + return original; + } +} \ No newline at end of file diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index f39dd46..d302c21 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -9,7 +9,6 @@ "EntityMixin", "EntityRendererMixin", "ExperienceBarRendererMixin", - "GameRendererAccessor", "GameRendererMixin", "GuiMixin", "InventoryAccessor", @@ -18,6 +17,8 @@ "LevelRendererAccessor", "LevelRendererMixin", "LivingEntityMixin", + "LocalPlayerAccessor", + "LocalPlayerMixin", "MinecraftMixin", "PlayerMenuItemAccessor", "PlayerMenuItemMixin", From b326e00cf3f08fc7ac71f4f2c62dfd2e0470d9d0 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:51:46 +0800 Subject: [PATCH 43/57] fix: replace ResourceLocation with Identifier in effect handling --- .../hpfxd/spectatorplus/fabric/client/util/EffectUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java index dbc5796..3119807 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java @@ -6,7 +6,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; @@ -20,7 +20,7 @@ public static void updateEffectInstances(List effects) { Set> newEffects = new HashSet<>(); for (SyncedEffect syncedEffect : effects) { - Holder effect = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.parse(syncedEffect.effectKey)) + Holder effect = BuiltInRegistries.MOB_EFFECT.get(Identifier.parse(syncedEffect.effectKey)) .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); newEffects.add(effect); } @@ -30,7 +30,7 @@ public static void updateEffectInstances(List effects) { // 添加新效果(保持现有实例的BlendState) for (SyncedEffect syncedEffect : effects) { - Holder effect = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.parse(syncedEffect.effectKey)) + Holder effect = BuiltInRegistries.MOB_EFFECT.get(Identifier.parse(syncedEffect.effectKey)) .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); if (!activeEffects.containsKey(effect)) { From 7506b4b59a872e88ed0fcc5e37d4e38ad7ec0a3d Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:22:20 +0800 Subject: [PATCH 44/57] fix: renderItemInHand follow the update of source code --- .../client/mixin/GameRendererMixin.java | 31 ++++++++++--------- .../mixin/ItemInHandRendererAccessor.java | 4 +++ 2 files changed, 20 insertions(+), 15 deletions(-) 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 261783e..5165202 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 @@ -47,24 +47,24 @@ public abstract class GameRendererMixin { @Unique private float xBobO; @Unique private float yBobO; - @Redirect(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Matrix4fc;)V", ordinal = 0)) - private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { - // In spectator mode, the projection matrix contains rotation data from the spectated player, - // while arm rendering calculations are based on the localPlayer's viewpoint. - // We must skip this mulPose operation when spectating to avoid rendering arms with incorrect orientation. - if (this.minecraft.player == null - || this.minecraft.player.gameMode() != GameType.SPECTATOR - || !this.minecraft.options.getCameraType().isFirstPerson()) { - poseStack.mulPose(matrix); - } - } +// @Redirect(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Matrix4fc;)V", ordinal = 0)) +// private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { +// // In spectator mode, the projection matrix contains rotation data from the spectated player, +// // while arm rendering calculations are based on the localPlayer's viewpoint. +// // We must skip this mulPose operation when spectating to avoid rendering arms with incorrect orientation. +// if (this.minecraft.player == null +// || this.minecraft.player.gameMode() != GameType.SPECTATOR +// || !this.minecraft.options.getCameraType().isFirstPerson()) { +// poseStack.mulPose(matrix); +// } +// } @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, 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); if (spectated != null && !spectated.isSpectator()) { - this.lightTexture.turnOnLightLayer(); + //this.lightTexture.turnOnLightLayer(); float attackAnim = spectated.getAttackAnim(partialTicks); final InteractionHand interactionHand = MoreObjects.firstNonNull(spectated.swingingArm, InteractionHand.MAIN_HAND); @@ -81,7 +81,7 @@ private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { if (handRenderSelection.renderMainHand) { final float swingProgress = interactionHand == InteractionHand.MAIN_HAND ? attackAnim : 0.0F; - final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOMainHandHeight(), accessor.getMainHandHeight()); + final float equippedProgress = accessor.getItemModelResolver().swapAnimationScale(accessor.getMainHandItem()) * (1F - Mth.lerp(partialTicks, accessor.getOMainHandHeight(), accessor.getMainHandHeight())); accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.MAIN_HAND, swingProgress, accessor.getMainHandItem(), equippedProgress, @@ -90,14 +90,15 @@ private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { if (handRenderSelection.renderOffHand) { final float swingProgress = interactionHand == InteractionHand.OFF_HAND ? attackAnim : 0.0F; - final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOOffHandHeight(), accessor.getOffHandHeight()); + final float equippedProgress = accessor.getItemModelResolver().swapAnimationScale(accessor.getOffHandItem()) * (1F - Mth.lerp(partialTicks, accessor.getOOffHandHeight(), accessor.getOffHandHeight())); accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.OFF_HAND, swingProgress, accessor.getOffHandItem(), equippedProgress, poseStackIn, submitNodeCollector, packedLightCoords); } - this.lightTexture.turnOffLightLayer(); + //this.lightTexture.turnOffLightLayer(); + this.minecraft.gameRenderer.getFeatureRenderDispatcher().renderAllFeatures(); this.renderBuffers.bufferSource().endBatch(); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java index 3872649..f8a7663 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java @@ -4,6 +4,7 @@ import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.item.ItemModelResolver; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; @@ -35,6 +36,9 @@ void invokeRenderArmWithItem(AbstractClientPlayer player, float partialTick, flo @Accessor ItemStack getOffHandItem(); + @Accessor + ItemModelResolver getItemModelResolver(); + @Invoker("isChargedCrossbow") static boolean invokeIsChargedCrossbow(ItemStack stack) { throw new AssertionError(); From 6d94a62127e3da8ceeaeb4103adfbb1b0117a8d2 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 31 Jan 2026 01:25:49 +0800 Subject: [PATCH 45/57] close shadowJar of paper build --- paper/build.gradle.kts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index c0ee068..0ce70fe 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -32,18 +32,13 @@ tasks { } shadowJar { - archiveVersion = getByName("jar").archiveVersion - archiveClassifier.set("") - from("../LICENSE") - - listOf( - "xyz.jpenilla.reflectionremapper", - "net.fabricmc.mappingio", - ).forEach { relocate(it, "com.hpfxd.spectatorplus.paper.libs.$it") } + enabled = false } jar { - enabled = false // only output shadow jar + enabled = true + archiveClassifier.set("") + from("../LICENSE") } runServer { From e42b4a8f1b18309811b4072eac90a2e5f077efd5 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 31 Jan 2026 01:26:42 +0800 Subject: [PATCH 46/57] feat: configure run directories for client and server --- fabric/build.gradle.kts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 0bfcc35..05cd608 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -22,6 +22,20 @@ loom { } } + runs { + getByName("client") { + client() + ideConfigGenerated(true) + runDir("run/client") + } + + getByName("server") { + server() + ideConfigGenerated(true) + runDir("run") + } + } + accessWidenerPath = file("src/main/resources/spectatorplus.accesswidener") } From 0f095c09fc8b23a2f0943d7d12a05a5f801e2fbb Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 31 Jan 2026 01:30:03 +0800 Subject: [PATCH 47/57] fix: integrate server will be correctly use original effects data instead of using client side hacking data --- .../spectatorplus/fabric/client/mixin/LivingEntityMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index 608520b..44a7c14 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -60,7 +60,7 @@ private boolean isLookingAtBlock() { at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;activeEffects:Ljava/util/Map;")) private Map, MobEffectInstance> spectatorplus$redirectActiveEffects(LivingEntity instance) { // 只对玩家且满足条件时才重定向 - if (instance instanceof Player && EffectUtil.shouldUseSpectatorData()) { + if (instance.level().isClientSide() && instance instanceof Player && EffectUtil.shouldUseSpectatorData()) { return EffectUtil.getActiveEffectsMap(); } return ((LivingEntityAccessor) instance).spectatorplus$getActiveEffects(); From 0aa06193b0be9d0f72105e2063ae6bc896c5c323 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:22:49 +0800 Subject: [PATCH 48/57] feat: implement inventory and cursor synchronization for spectators --- .../gui/screens/SyncedInventoryScreen.java | 22 +-- .../sync/screen/ScreenSyncController.java | 31 +++- .../ServerGamePacketListenerImplMixin.java | 8 + .../fabric/mixin/ServerPlayerMixin.java | 15 +- .../fabric/sync/ServerSyncController.java | 5 + .../sync/handler/CursorSyncHandler.java | 74 +++++++++ .../sync/handler/InventorySyncHandler.java | 129 +++++++++++++++ .../sync/handler/ScreenSyncHandler.java | 154 ++++++++++++++++++ .../packet/ClientboundHotbarSyncPacket.java | 1 + .../ClientboundScreenCursorSyncPacket.java | 4 +- .../packet/ClientboundScreenSyncPacket.java | 4 +- 11 files changed, 428 insertions(+), 19 deletions(-) create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java 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 0ca8ea0..230e0e9 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,6 +1,5 @@ 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; @@ -10,7 +9,6 @@ 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; @@ -33,15 +31,17 @@ public void containerTick() { } private void syncOtherItems() { - final SyncedInventoryMenu menu = (SyncedInventoryMenu) this.menu; - final Inventory fakeInventory = menu.getInventory(); - - // Use synced inventory data for all slots (main, armor, offhand) - var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; - if (syncData != null && syncData.screen != null && syncData.screen.inventoryItems != null) { - var items = syncData.screen.inventoryItems; - for (int i = 0; i < items.size(); i++) { - fakeInventory.setItem(i, items.get(i)); + // If the mixin worked correctly, this.menu should be a SyncedInventoryMenu + if (this.menu instanceof SyncedInventoryMenu syncedMenu) { + final Inventory inventory = syncedMenu.getInventory(); + + // Use synced inventory data for all slots (main, armor, offhand) + var syncData = com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.syncData; + if (syncData != null && syncData.screen != null && syncData.screen.inventoryItems != null) { + var items = syncData.screen.inventoryItems; + for (int i = 0; i < items.size() && i < 41; i++) { + inventory.setItem(i, items.get(i)); + } } } } 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 19fc69d..bc9cf9b 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 @@ -9,11 +9,9 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; -import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; 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; @@ -50,6 +48,20 @@ private static void handle(ClientboundScreenSyncPacket packet, ClientPlayNetwork syncData.screen.isSurvivalInventory = packet.isSurvivalInventory(); syncData.screen.isClientRequested = packet.isClientRequested(); syncData.screen.hasDummySlots = packet.hasDummySlots(); + + // If this is a survival inventory screen sync, try to open it immediately + if (syncData.screen.isSurvivalInventory) { + final Minecraft mc = Minecraft.getInstance(); + mc.execute(() -> { + if (isPendingOpen && syncData.screen.isSurvivalInventory) { + // Create the synced inventory first, so the mixin can use it + final Player player = SpecUtil.getCameraPlayer(mc); + if (player != null && createInventory(player)) { + openPlayerInventory(mc); + } + } + }); + } } private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetworking.Context context) { @@ -79,6 +91,14 @@ private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetw } } } + +// // For survival inventory, create the synced inventory if pending open and not yet created +// if (isPendingOpen && syncData.screen.isSurvivalInventory && syncedInventory == null) { +// final Player spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); +// if (spectated != null) { +// createInventory(spectated); +// } +// } } private static void handle(ClientboundScreenCursorSyncPacket packet, ClientPlayNetworking.Context context) { @@ -98,9 +118,10 @@ public static void closeSyncedInventory() { public static void openPlayerInventory(Minecraft mc) { final Player player = SpecUtil.getCameraPlayer(mc); - final SyncedInventoryScreen screen = new SyncedInventoryScreen(player); - - handleNewSyncedScreen(mc, screen); + if (player != null && mc.player != null) { + final SyncedInventoryScreen screen = new SyncedInventoryScreen(player); + handleNewSyncedScreen(mc, screen); + } } public static > void handleNewSyncedScreen(Minecraft mc, S screen) { 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 15d8e13..b46f329 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 @@ -1,7 +1,9 @@ package com.hpfxd.spectatorplus.fabric.mixin; import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.handler.CursorSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundSelectedSlotSyncPacket; +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket; import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; @@ -20,4 +22,10 @@ public abstract class ServerGamePacketListenerImplMixin { private void spectatorplus$syncSelectedSlot(ServerboundSetCarriedItemPacket packet, CallbackInfo ci) { ServerSyncController.broadcastPacketToSpectators(this.player, new ClientboundSelectedSlotSyncPacket(this.player.getUUID(), packet.getSlot())); } + + @Inject(method = "handleContainerClick(Lnet/minecraft/network/protocol/game/ServerboundContainerClickPacket;)V", at = @At("TAIL")) + private void spectatorplus$syncCursorAfterClick(ServerboundContainerClickPacket packet, CallbackInfo ci) { + // After handling container click, check if cursor item changed + CursorSyncHandler.onCursorChanged(this.player, this.player.containerMenu.getCarried()); + } } 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 15c7b1f..cac1c10 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 @@ -3,14 +3,15 @@ import com.google.common.collect.Lists; import com.hpfxd.spectatorplus.fabric.SpectatorMod; import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.handler.CursorSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; +import com.hpfxd.spectatorplus.fabric.sync.handler.InventorySyncHandler; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundSelectedSlotSyncPacket; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.authlib.GameProfile; -import net.minecraft.core.BlockPos; import net.minecraft.core.component.DataComponents; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket; @@ -58,6 +59,16 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.broadcastPacketToSpectators((ServerPlayer) (Object) this, new ClientboundExperienceSyncPacket(this.getUUID(), this.experienceProgress, this.getXpNeededForNextLevel(), this.experienceLevel)); } + @Inject(method = "setCamera(Lnet/minecraft/world/entity/Entity;)V", at = @At("HEAD")) + private void spectatorplus$beforeCameraChange(Entity entityToSpectate, CallbackInfo ci) { + final ServerPlayer spectator = (ServerPlayer) (Object) this; + + // If we're changing from spectating a player to something else, clean up + if (spectator.getCamera() instanceof ServerPlayer oldTarget && spectator.getCamera() != entityToSpectate) { + // TODO: Could add cleanup logic here if needed + // This runs before the camera change, so we can still access the old camera + } + } @Inject(method = "setCamera(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V")) private void spectatorplus$syncToNewSpectator(Entity entityToSpectate, CallbackInfo ci) { if (entityToSpectate instanceof final ServerPlayer target) { @@ -67,6 +78,8 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.sendPacket(spectator, ClientboundFoodSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundHotbarSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); + InventorySyncHandler.sendPacket(spectator, target); + CursorSyncHandler.sendPacket(spectator, target); EffectsSyncHandler.onStartSpectating(spectator, target); // Send initial map data patch packet if the target has a map in inventory diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java index 587735e..fc88ab4 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java @@ -1,8 +1,11 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.handler.CursorSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.HotbarSyncHandler; +import com.hpfxd.spectatorplus.fabric.sync.handler.InventorySyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.ScreenSyncHandler; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -17,6 +20,8 @@ public static void init() { HotbarSyncHandler.init(); ScreenSyncHandler.init(); EffectsSyncHandler.init(); + InventorySyncHandler.init(); + CursorSyncHandler.init(); } public static void sendPacket(ServerPlayer serverPlayer, ClientboundSyncPacket packet) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java new file mode 100644 index 0000000..fd18882 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java @@ -0,0 +1,74 @@ +package com.hpfxd.spectatorplus.fabric.sync.handler; + +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundScreenCursorSyncPacket; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Handles synchronization of cursor items (items being dragged) to spectators + */ +public class CursorSyncHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(CursorSyncHandler.class); + private static final Map playerCursors = new HashMap<>(); + + public static void init() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> + playerCursors.remove(handler.getPlayer().getUUID())); + } + + /** + * Called when a player's cursor item changes + */ + public static void onCursorChanged(ServerPlayer player, ItemStack newCursor) { + try { + ItemStack oldCursor = playerCursors.get(player.getUUID()); + if (oldCursor == null) { + oldCursor = ItemStack.EMPTY; + } + if (newCursor == null) { + newCursor = ItemStack.EMPTY; + } + + // Only sync if the cursor actually changed + if (!ItemStack.isSameItem(oldCursor, newCursor)) { + playerCursors.put(player.getUUID(), newCursor.copy()); + + // Broadcast to spectators + ClientboundScreenCursorSyncPacket packet = new ClientboundScreenCursorSyncPacket( + player.getUUID(), + newCursor, + 0 //TODO: Origin slot using for recognize mouse on upon(container) or bottom(inventory),the number means the shift slot of mouse. + ); + + ServerSyncController.broadcastPacketToSpectators(player, packet); + } + } catch (Exception e) { + LOGGER.error("Error syncing cursor change for player {}", player.getGameProfile().name(), e); + } + } + + /** + * Send initial cursor data to a spectator + */ + public static void sendPacket(ServerPlayer spectator, ServerPlayer target) { + ItemStack cursor = playerCursors.getOrDefault(target.getUUID(), ItemStack.EMPTY); + + if (!cursor.isEmpty()) { + ClientboundScreenCursorSyncPacket packet = new ClientboundScreenCursorSyncPacket( + target.getUUID(), + cursor, + 0 + ); + + ServerSyncController.sendPacket(spectator, packet); + } + } +} diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java new file mode 100644 index 0000000..28e11d2 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java @@ -0,0 +1,129 @@ +package com.hpfxd.spectatorplus.fabric.sync.handler; + +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundInventorySyncPacket; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.IntStream; + +public class InventorySyncHandler { + private static final Map playerInventories = new HashMap<>(); + private static int tickCounter = 0; + private static final int SYNC_INTERVAL = 5; // Sync every 5 ticks (4 times per second) + + public static void init() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> + playerInventories.remove(handler.getPlayer().getUUID())); + ServerTickEvents.END_SERVER_TICK.register(InventorySyncHandler::tick); + } + + public static void tick(MinecraftServer server) { + tickCounter++; + if (tickCounter % SYNC_INTERVAL != 0) { + return; // Skip this tick + } + + for (ServerPlayer player : server.getPlayerList().getPlayers()) { + if (ServerSyncController.getSpectators(player).isEmpty()) { + continue; + } + syncPlayerInventory(player); + } + } + + private static void syncPlayerInventory(ServerPlayer player) { + final ItemStack[] slots = playerInventories.computeIfAbsent(player.getUUID(), k -> { + final ItemStack[] arr = new ItemStack[ClientboundInventorySyncPacket.ITEMS_LENGTH]; + Arrays.fill(arr, ItemStack.EMPTY); + return arr; + }); + + //as hotbar will change more than rest of inventory, we track it separately to avoid sending full inventory when only hotbar changes + final ItemStack[] currentSlots = extractPlayerInventory(player); + final ItemStack[] inventorySendSlots = new ItemStack[ClientboundInventorySyncPacket.ITEMS_LENGTH]; + final ItemStack[] hotbarSendSlots = new ItemStack[ClientboundHotbarSyncPacket.ITEMS_LENGTH]; + + boolean updatedHotbar = false; + boolean updatedInventory = false; + + final Inventory inventory = player.getInventory(); + + // Main inventory (0-35) including hotbar (0-8) + for (int i = 0; i < currentSlots.length; i++) { + if (!ItemStack.matches(currentSlots[i], slots[i])) { + slots[i] = currentSlots[i].copy(); + inventorySendSlots[i] = currentSlots[i]; + updatedInventory = true; + + // 热键栏是槽位 0-8 + if (i < ClientboundHotbarSyncPacket.ITEMS_LENGTH) { + hotbarSendSlots[i] = currentSlots[i]; + updatedHotbar = true; + } + } + } + + if (updatedInventory) { + ScreenSyncHandler.updatePlayerInventory(player, inventorySendSlots); + } + + if (updatedHotbar) { + ServerSyncController.broadcastPacketToSpectators(player, + new ClientboundHotbarSyncPacket(player.getUUID(), hotbarSendSlots)); + } + } + + //this for send full inventory packet immediately + public static void sendPacket(ServerPlayer spectator, ServerPlayer target) { + final ItemStack[] slots = extractPlayerInventory(target); + ServerSyncController.sendPacket(spectator, + new ClientboundInventorySyncPacket(target.getUUID(), slots)); + } + + private static ItemStack[] extractPlayerInventory(ServerPlayer player) { + final Inventory inventory = player.getInventory(); + final ItemStack[] slots = new ItemStack[ClientboundInventorySyncPacket.ITEMS_LENGTH]; + + // Main inventory (0-35) + IntStream.range(0, 36).forEach(i -> { + ItemStack item = inventory.getItem(i); + slots[i] = item != null ? item : ItemStack.EMPTY; + }); + + // Armor (36-39) + IntStream.range(36, 40).forEach(i -> { + ItemStack item = inventory.getItem(i); + slots[i] = item != null ? item : ItemStack.EMPTY; + }); + + // Offhand slot (40) + ItemStack offhand = inventory.getItem(40); + slots[40] = offhand != null ? offhand : ItemStack.EMPTY; + + return slots; + } + + public static void onPlayerDisconnect(UUID playerId) { + playerInventories.remove(playerId); + } + + /** + * Force an immediate inventory sync for a player, bypassing the tick counter + * Used for critical inventory changes that need immediate synchronization + */ + public static void forceSyncPlayer(ServerPlayer player) { + if (!ServerSyncController.getSpectators(player).isEmpty()) { + syncPlayerInventory(player); + } + } +} diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java index 4fa0e46..c5c6ee3 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java @@ -1,18 +1,172 @@ package com.hpfxd.spectatorplus.fabric.sync.handler; +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundInventorySyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundScreenSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ServerboundOpenedInventorySyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ServerboundRequestInventoryOpenPacket; +import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ScreenSyncHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(ScreenSyncHandler.class); + public static void init() { ServerPlayNetworking.registerGlobalReceiver(ServerboundRequestInventoryOpenPacket.TYPE, ScreenSyncHandler::handle); ServerPlayNetworking.registerGlobalReceiver(ServerboundOpenedInventorySyncPacket.TYPE, ScreenSyncHandler::handle); } private static void handle(ServerboundRequestInventoryOpenPacket packet, ServerPlayNetworking.Context ctx) { + try { + final var server = ctx.server(); + final var player = ctx.player(); + final var target = server.getPlayerList().getPlayer(packet.playerId()); + + if (target != null) { + onRequestOpen(player, target); + } else { + LOGGER.warn("Player {} requested to view inventory of non-existent player {}", + player.getGameProfile().name(), packet.playerId()); + } + } catch (Exception e) { + LOGGER.error("Error handling inventory open request", e); + } } private static void handle(ServerboundOpenedInventorySyncPacket packet, ServerPlayNetworking.Context ctx) { + try { + final var player = ctx.player(); + onPlayerOpenInventory(player); + } catch (Exception e) { + LOGGER.error("Error handling player inventory open", e); + } + } + + private static void onRequestOpen(ServerPlayer spectator, ServerPlayer target) { + // Check if spectator has permission to view inventory + if (canSyncInventory(spectator)) { + openPlayerInventory(spectator, target); + } + } + + /** + * Called when a player opens their inventory or any container screen. + * This method syncs the appropriate screen to all spectators who are viewing this player. + * + * @param target The player who opened their inventory/container + */ + private static void onPlayerOpenInventory(ServerPlayer target) { + try { + // Get all spectators who are currently viewing this player + var spectators = ServerSyncController.getSpectators(target); + if (spectators.isEmpty()) { + return; // No spectators, no need to sync + } + + for (ServerPlayer spectator : spectators) { + if (!canSyncInventory(spectator)) { + continue; + } + + // Determine what type of screen the target player has open + if (target.containerMenu != target.inventoryMenu) { + // Player has a container open (chest, crafting table, furnace, etc.) + LOGGER.debug("Syncing container screen for spectator {} viewing {}", + spectator.getGameProfile().name(), target.getGameProfile().name()); + syncContainerScreen(spectator, target); + } else { + // Player has their regular inventory open (survival/creative inventory) + LOGGER.debug("Syncing player inventory screen for spectator {} viewing {}", + spectator.getGameProfile().name(), target.getGameProfile().name()); + syncPlayerInventoryScreen(spectator, target); + } + } + } catch (Exception e) { + LOGGER.error("Error syncing player inventory screen for {}", target.getGameProfile().name(), e); + } + } + + private static void openPlayerInventory(ServerPlayer spectator, ServerPlayer target) { + // Send screen sync packet to indicate survival inventory + int flags = 1; // Survival inventory flag + flags |= (1 << 1); // Client requested flag + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); + + // Send full inventory data + InventorySyncHandler.sendPacket(spectator, target); + } + + private static boolean canSyncInventory(ServerPlayer player) { + return Permissions.check(player, "spectatorplus.sync.inventory", true); + } + + public static void updatePlayerInventory(ServerPlayer player, ItemStack[] inventorySendSlots) { + try { + // Only send to spectators who have permission and are actually spectating this player + for (ServerPlayer spectator : ServerSyncController.getSpectators(player)) { + if (canSyncInventory(spectator)) { + ServerSyncController.sendPacket(spectator, + new ClientboundInventorySyncPacket(player.getUUID(), inventorySendSlots)); + } + } + } catch (Exception e) { + LOGGER.error("Error updating inventory for player {}", player.getGameProfile().name(), e); + } + } + + /** + * Syncs a container screen (chest, crafting table, furnace, etc.) to a spectator. + * This is used when the target player has opened a container that isn't their inventory. + * + * @param spectator The spectator to sync the screen to + * @param target The player whose container screen should be synced + */ + private static void syncContainerScreen(ServerPlayer spectator, ServerPlayer target) { + // Send screen sync packet for container screen + int flags = 0; // Container screen (not survival inventory) + flags |= (1 << 2); // Has dummy slots flag for container screens + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); + + // Send the target's inventory data to the spectator + InventorySyncHandler.sendPacket(spectator, target); + + // For container screens (chests, crafting tables, etc.), we can send ClientboundOpenScreenPacket + // because container menus have proper MenuType implementations + try { + spectator.connection.send(new ClientboundOpenScreenPacket( + target.containerMenu.containerId, + target.containerMenu.getType(), + target.getDisplayName() + )); + } catch (Exception e) { + LOGGER.warn("Could not sync container screen type for {} to spectator {}: {}", + target.getGameProfile().name(), spectator.getGameProfile().name(), e.getMessage()); + // Continue without the screen packet - the client will handle it through sync packets + } + } + + /** + * Syncs a player's regular inventory screen (survival/creative inventory) to a spectator. + * This is used when the target player has opened their own inventory. + * + * @param spectator The spectator to sync the screen to + * @param target The player whose inventory screen should be synced + */ + private static void syncPlayerInventoryScreen(ServerPlayer spectator, ServerPlayer target) { + // Send screen sync packet for player inventory (survival inventory) + int flags = 1; // Survival inventory flag + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); + + // Send the target's inventory data to the spectator + InventorySyncHandler.sendPacket(spectator, target); + + // Note: The client will handle opening the synced inventory screen based on the packets sent above. + // No need to call openMenu() here as the client-side ScreenSyncController will process the + // ClientboundScreenSyncPacket and open the appropriate synced screen automatically. } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java index e39b7d0..db3a2d1 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java @@ -20,6 +20,7 @@ public record ClientboundHotbarSyncPacket( public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundHotbarSyncPacket::write, ClientboundHotbarSyncPacket::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:hotbar_sync")); static final String PERMISSION = "spectatorplus.sync.hotbar"; + public static final int ITEMS_LENGTH = 9; public static ClientboundHotbarSyncPacket initializing(ServerPlayer target) { final ItemStack[] items = new ItemStack[9]; diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java index 157a972..958ad88 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java @@ -2,6 +2,7 @@ import com.hpfxd.spectatorplus.fabric.sync.ClientboundSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.CustomPacketCodecs; +import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -19,6 +20,7 @@ public record ClientboundScreenCursorSyncPacket( ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenCursorSyncPacket::write, ClientboundScreenCursorSyncPacket::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_cursor_sync")); + private static final String PERMISSION = "spectatorplus.sync.screen"; public ClientboundScreenCursorSyncPacket(RegistryFriendlyByteBuf buf) { this(buf.readUUID(), CustomPacketCodecs.readItem(buf), buf.readByte()); @@ -37,6 +39,6 @@ public void write(RegistryFriendlyByteBuf buf) { @Override public boolean canSend(ServerPlayer receiver) { - return true; + return Permissions.check(receiver, PERMISSION, true); } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java index cb0464d..0e5ecb2 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java @@ -1,6 +1,7 @@ package com.hpfxd.spectatorplus.fabric.sync.packet; import com.hpfxd.spectatorplus.fabric.sync.ClientboundSyncPacket; +import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -16,6 +17,7 @@ public record ClientboundScreenSyncPacket( ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenSyncPacket::write, ClientboundScreenSyncPacket::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_sync")); + private static final String PERMISSION = "spectatorplus.sync.screen"; public ClientboundScreenSyncPacket(FriendlyByteBuf buf) { this(buf.readUUID(), buf.readByte()); @@ -45,6 +47,6 @@ public boolean hasDummySlots() { @Override public boolean canSend(ServerPlayer receiver) { - return true; + return Permissions.check(receiver, PERMISSION, true); } } From 792df45e4634b6383d805c1168754bb166043781 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:13:48 +0800 Subject: [PATCH 49/57] fix: reorder screen sync packet sending for spectators to ensure inventory data is sent correctly --- .../fabric/sync/handler/ScreenSyncHandler.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java index c5c6ee3..1caa3fd 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java @@ -95,10 +95,10 @@ private static void openPlayerInventory(ServerPlayer spectator, ServerPlayer tar // Send screen sync packet to indicate survival inventory int flags = 1; // Survival inventory flag flags |= (1 << 1); // Client requested flag - ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); - // Send full inventory data InventorySyncHandler.sendPacket(spectator, target); + + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); } private static boolean canSyncInventory(ServerPlayer player) { @@ -160,13 +160,9 @@ private static void syncContainerScreen(ServerPlayer spectator, ServerPlayer tar private static void syncPlayerInventoryScreen(ServerPlayer spectator, ServerPlayer target) { // Send screen sync packet for player inventory (survival inventory) int flags = 1; // Survival inventory flag - ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); - // Send the target's inventory data to the spectator InventorySyncHandler.sendPacket(spectator, target); - // Note: The client will handle opening the synced inventory screen based on the packets sent above. - // No need to call openMenu() here as the client-side ScreenSyncController will process the - // ClientboundScreenSyncPacket and open the appropriate synced screen automatically. + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); } } From c255adb984b72d95de4883aa7cf611b7addd9eb0 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:42:26 +0800 Subject: [PATCH 50/57] feat: add screen close synchronization for spectators and update inventory sync packet --- .../fabric/client/mixin/MinecraftMixin.java | 2 +- .../client/mixin/screen/ScreenMixin.java | 26 ++++++++++++ .../sync/screen/ScreenSyncController.java | 33 ++++++++++----- .../spectatorplus.client.mixins.json | 3 +- .../sync/handler/ScreenSyncHandler.java | 40 ++++++++++++++++++- .../packet/ClientboundScreenSyncPacket.java | 4 ++ .../ServerboundOpenedInventorySyncPacket.java | 11 ++++- 7 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java index e0cdcc2..c12091e 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java @@ -64,7 +64,7 @@ public abstract class MinecraftMixin { if (ClientPlayNetworking.canSend(ServerboundOpenedInventorySyncPacket.TYPE)) { // just let the server we've opened our inventory. this is used to sync with other users spectating this client - ClientPlayNetworking.send(new ServerboundOpenedInventorySyncPacket()); + ClientPlayNetworking.send(new ServerboundOpenedInventorySyncPacket(true)); } return true; } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java new file mode 100644 index 0000000..57236ee --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java @@ -0,0 +1,26 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin.screen; + +import com.hpfxd.spectatorplus.fabric.sync.packet.ServerboundOpenedInventorySyncPacket; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.InventoryScreen; +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(Screen.class) +public class ScreenMixin { + + @Inject(method = "onClose()V", at = @At("HEAD")) + private void spectatorplus$onScreenClose(CallbackInfo ci) { + // Check if this is an inventory or container screen being closed + if ((Object) this instanceof InventoryScreen || (Object) this instanceof AbstractContainerScreen) { + // Notify server that we closed our inventory/container + if (ClientPlayNetworking.canSend(ServerboundOpenedInventorySyncPacket.TYPE)) { + ClientPlayNetworking.send(new ServerboundOpenedInventorySyncPacket(false)); + } + } + } +} 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 bc9cf9b..ca6b121 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 @@ -44,6 +44,16 @@ private static void handle(ClientboundScreenSyncPacket packet, ClientPlayNetwork setSyncData(packet.playerId()); syncData.setScreen(); + // Check if this is a screen close event + if (packet.isScreenClosed()) { + // Close the synced inventory screen + final Minecraft mc = Minecraft.getInstance(); + mc.execute(() -> { + closeSyncedInventory(); + }); + return; + } + isPendingOpen = true; syncData.screen.isSurvivalInventory = packet.isSurvivalInventory(); syncData.screen.isClientRequested = packet.isClientRequested(); @@ -91,14 +101,6 @@ private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetw } } } - -// // For survival inventory, create the synced inventory if pending open and not yet created -// if (isPendingOpen && syncData.screen.isSurvivalInventory && syncedInventory == null) { -// final Player spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); -// if (spectated != null) { -// createInventory(spectated); -// } -// } } private static void handle(ClientboundScreenCursorSyncPacket packet, ClientPlayNetworking.Context context) { @@ -110,9 +112,20 @@ private static void handle(ClientboundScreenCursorSyncPacket packet, ClientPlayN } public static void closeSyncedInventory() { + final Minecraft mc = Minecraft.getInstance(); if (syncedScreen != null) { - syncedScreen.onClose(); - syncedInventory = null; + // Close the current synced screen + mc.setScreen(null); + syncedScreen = null; + } + // Clean up synced inventory and related state + syncedInventory = null; + syncedWindowId = Integer.MIN_VALUE; + isPendingOpen = false; + + // Clear screen sync data + if (syncData != null) { + syncData.screen = null; } } diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index d302c21..b650ae5 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -31,7 +31,8 @@ "screen.ImageButtonAccessor", "screen.InventoryMenuMixin", "screen.InventoryScreenMixin", - "screen.MenuScreensMixin" + "screen.MenuScreensMixin", + "screen.ScreenMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java index 1caa3fd..4d124c5 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java @@ -41,9 +41,13 @@ private static void handle(ServerboundRequestInventoryOpenPacket packet, ServerP private static void handle(ServerboundOpenedInventorySyncPacket packet, ServerPlayNetworking.Context ctx) { try { final var player = ctx.player(); - onPlayerOpenInventory(player); + if (packet.isOpened()) { + onPlayerOpenInventory(player); + } else { + onPlayerCloseInventory(player); + } } catch (Exception e) { - LOGGER.error("Error handling player inventory open", e); + LOGGER.error("Error handling player inventory open/close", e); } } @@ -91,6 +95,38 @@ private static void onPlayerOpenInventory(ServerPlayer target) { } } + /** + * Called when a player closes their inventory or any container screen. + * This method closes the synced screen for all spectators who are viewing this player. + * + * @param target The player who closed their inventory/container + */ + private static void onPlayerCloseInventory(ServerPlayer target) { + try { + // Get all spectators who are currently viewing this player + var spectators = ServerSyncController.getSpectators(target); + if (spectators.isEmpty()) { + return; // No spectators, no need to sync + } + + for (ServerPlayer spectator : spectators) { + if (!canSyncInventory(spectator)) { + continue; + } + + LOGGER.debug("Syncing inventory close for spectator {} viewing {}", + spectator.getGameProfile().name(), target.getGameProfile().name()); + + // Send screen sync packet to indicate inventory/container was closed + int flags = 0; // No flags means close screen + flags |= (1 << 3); // Screen closed flag + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); + } + } catch (Exception e) { + LOGGER.error("Error syncing player inventory close for {}", target.getGameProfile().name(), e); + } + } + private static void openPlayerInventory(ServerPlayer spectator, ServerPlayer target) { // Send screen sync packet to indicate survival inventory int flags = 1; // Survival inventory flag diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java index 0e5ecb2..be7f51b 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java @@ -40,6 +40,10 @@ public boolean hasDummySlots() { return (this.flags >> 2 & 1) == 1; } + public boolean isScreenClosed() { + return (this.flags >> 3 & 1) == 1; + } + @Override public @NotNull Type type() { return TYPE; diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java index e6aea33..6c21888 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java @@ -10,13 +10,22 @@ public final class ServerboundOpenedInventorySyncPacket implements ServerboundSy public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundOpenedInventorySyncPacket::write, ServerboundOpenedInventorySyncPacket::new); public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:opened_inventory_sync")); - public ServerboundOpenedInventorySyncPacket() { + private final boolean isOpened; + + public ServerboundOpenedInventorySyncPacket(boolean isOpened) { + this.isOpened = isOpened; } public ServerboundOpenedInventorySyncPacket(FriendlyByteBuf buf) { + this.isOpened = buf.readBoolean(); } public void write(FriendlyByteBuf buf) { + buf.writeBoolean(this.isOpened); + } + + public boolean isOpened() { + return this.isOpened; } @Override From eee507920a0aec1a4554334dddaf885508081804 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:23:56 +0800 Subject: [PATCH 51/57] feat: enhance cursor synchronization by adding origin slot tracking for spectators --- .../mixin/ServerGamePacketListenerImplMixin.java | 2 +- .../spectatorplus/fabric/mixin/ServerPlayerMixin.java | 2 +- .../fabric/sync/handler/CursorSyncHandler.java | 11 +++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) 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 b46f329..316ad4c 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 @@ -26,6 +26,6 @@ public abstract class ServerGamePacketListenerImplMixin { @Inject(method = "handleContainerClick(Lnet/minecraft/network/protocol/game/ServerboundContainerClickPacket;)V", at = @At("TAIL")) private void spectatorplus$syncCursorAfterClick(ServerboundContainerClickPacket packet, CallbackInfo ci) { // After handling container click, check if cursor item changed - CursorSyncHandler.onCursorChanged(this.player, this.player.containerMenu.getCarried()); + CursorSyncHandler.onCursorChanged(this.player, this.player.containerMenu.getCarried(), packet.slotNum()); } } 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 cac1c10..cb4057f 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 @@ -79,7 +79,7 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.sendPacket(spectator, ClientboundHotbarSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); InventorySyncHandler.sendPacket(spectator, target); - CursorSyncHandler.sendPacket(spectator, target); + //CursorSyncHandler.sendPacket(spectator, target); EffectsSyncHandler.onStartSpectating(spectator, target); // Send initial map data patch packet if the target has a map in inventory diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java index fd18882..3328a2e 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java @@ -28,6 +28,13 @@ public static void init() { * Called when a player's cursor item changes */ public static void onCursorChanged(ServerPlayer player, ItemStack newCursor) { + onCursorChanged(player, newCursor, -1); + } + + /** + * Called when a player's cursor item changes + */ + public static void onCursorChanged(ServerPlayer player, ItemStack newCursor, int originSlot) { try { ItemStack oldCursor = playerCursors.get(player.getUUID()); if (oldCursor == null) { @@ -45,7 +52,7 @@ public static void onCursorChanged(ServerPlayer player, ItemStack newCursor) { ClientboundScreenCursorSyncPacket packet = new ClientboundScreenCursorSyncPacket( player.getUUID(), newCursor, - 0 //TODO: Origin slot using for recognize mouse on upon(container) or bottom(inventory),the number means the shift slot of mouse. + originSlot ); ServerSyncController.broadcastPacketToSpectators(player, packet); @@ -65,7 +72,7 @@ public static void sendPacket(ServerPlayer spectator, ServerPlayer target) { ClientboundScreenCursorSyncPacket packet = new ClientboundScreenCursorSyncPacket( target.getUUID(), cursor, - 0 + -1 ); ServerSyncController.sendPacket(spectator, packet); From 77f902ee689333d4efb1b57e6a7d222f6c3640a2 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:39:59 +0800 Subject: [PATCH 52/57] feat: implement container synchronization for spectators --- .../client/gui/screens/DummyContainer.java | 19 ++- .../screen/AbstractContainerScreenMixin.java | 42 +++++ .../client/mixin/screen/MenuScreensMixin.java | 26 ++- .../client/sync/ClientSyncController.java | 19 +++ .../sync/screen/ScreenSyncController.java | 160 ++++++++++++++++-- .../client/sync/screen/ScreenSyncData.java | 115 +++++++++++++ .../fabric/mixin/ServerPlayerMixin.java | 27 ++- .../fabric/sync/ServerSyncController.java | 2 + .../fabric/sync/SyncPackets.java | 2 + .../sync/handler/ContainerSyncHandler.java | 152 +++++++++++++++++ .../sync/handler/ScreenSyncHandler.java | 47 ++--- .../ClientboundContainerSyncPacket.java | 58 +++++++ 12 files changed, 607 insertions(+), 62 deletions(-) create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java create mode 100644 fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java index 48a6400..16a9a12 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java @@ -1,29 +1,38 @@ package com.hpfxd.spectatorplus.fabric.client.gui.screens; +import net.minecraft.core.NonNullList; import net.minecraft.world.Container; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; public class DummyContainer implements Container { - private final int size; + private final NonNullList items; public DummyContainer(int size) { - this.size = size; + this.items = NonNullList.withSize(size, ItemStack.EMPTY); } @Override public int getContainerSize() { - return this.size; + return this.items.size(); } @Override public boolean isEmpty() { + for (ItemStack item : this.items) { + if (!item.isEmpty()) { + return false; + } + } return true; } @Override public @NotNull ItemStack getItem(int slot) { + if (slot >= 0 && slot < this.items.size()) { + return this.items.get(slot); + } return ItemStack.EMPTY; } @@ -39,6 +48,9 @@ public boolean isEmpty() { @Override public void setItem(int slot, ItemStack stack) { + if (slot >= 0 && slot < this.items.size()) { + this.items.set(slot, stack); + } } @Override @@ -52,5 +64,6 @@ public boolean stillValid(Player player) { @Override public void clearContent() { + this.items.clear(); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java index 248ceb6..3257810 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java @@ -5,6 +5,7 @@ import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; import com.hpfxd.spectatorplus.fabric.client.sync.screen.ScreenSyncController; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.renderer.RenderPipelines; @@ -143,6 +144,47 @@ public abstract class AbstractContainerScreenMixin { this.animations.removeIf(animation -> ++animation.tick >= MOVE_ANIMATION_TICKS); } + @Inject(method = "containerTick", at = @At("TAIL")) + private void spectatorplus$syncContainerItems(CallbackInfo ci) { + if (!this.spectatorplus$isSyncedScreen()) { + return; + } + + this.spectatorplus$syncContainerItems(); + } + + + @Unique + private void spectatorplus$syncContainerItems() { + AbstractContainerScreen self = (AbstractContainerScreen) (Object) this; + var minecraft = Minecraft.getInstance(); + + if (minecraft == null || minecraft.player == null) { + return; + } + + var syncData = ClientSyncController.syncData; + var containerItems = syncData.screen.containerItems; + + // Find container slots and sync items + int containerItemIndex = 0; + for (int slotIndex = 0; slotIndex < self.getMenu().slots.size() && containerItemIndex < containerItems.size(); slotIndex++) { + Slot slot = self.getMenu().slots.get(slotIndex); + + // Only sync container slots, not player inventory + if (slot.container != minecraft.player.getInventory()) { + ItemStack syncedItem = containerItems.get(containerItemIndex); + slot.set(syncedItem); + containerItemIndex++; + } + } + + // Sync cursor item + if (syncData.screen.cursorItem != null) { + minecraft.player.containerMenu.setCarried(syncData.screen.cursorItem); + } + } + @ModifyExpressionValue( method = "renderTooltip(Lnet/minecraft/client/gui/GuiGraphics;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/inventory/Slot;hasItem()Z") diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java index ddf08d7..dbb8c20 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java @@ -50,18 +50,26 @@ public abstract class MenuScreensMixin { // if no inventory could be created, we close the inventory } else { - final Inventory inventory; - if (hasInventory) { - inventory = ScreenSyncController.syncedInventory; + // Handle container screens + if (ClientSyncController.syncData.screen.containerType != null) { + // Open container screen with proper type and size + ScreenSyncController.openContainerScreen(mc, + ClientSyncController.syncData.screen.containerType, + ClientSyncController.syncData.screen.containerSize); } else { - inventory = mc.player.getInventory(); - } + // Fallback to original screen creation for unknown container types + final Inventory inventory; + if (hasInventory) { + inventory = ScreenSyncController.syncedInventory; + } else { + inventory = mc.player.getInventory(); + } - final M menu = type.create(windowId, inventory); - final S screen = screenConstructor.create(menu, inventory, title); + final M menu = type.create(windowId, inventory); + final S screen = screenConstructor.create(menu, inventory, title); - ScreenSyncController.handleNewSyncedScreen(mc, screen); - return; + ScreenSyncController.handleNewSyncedScreen(mc, screen); + } } } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index 468e659..7d87ab6 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -2,6 +2,7 @@ import com.hpfxd.spectatorplus.fabric.client.sync.screen.ScreenSyncController; import com.hpfxd.spectatorplus.fabric.client.util.EffectUtil; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundContainerSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -10,6 +11,7 @@ import java.util.List; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import net.minecraft.core.NonNullList; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.Holder; import net.minecraft.resources.Identifier; @@ -30,6 +32,7 @@ public class ClientSyncController { private static Minecraft minecraft = Minecraft.getInstance(); public static void init() { + ClientPlayNetworking.registerGlobalReceiver(ClientboundContainerSyncPacket.TYPE, ClientSyncController::handle); ClientPlayNetworking.registerGlobalReceiver(ClientboundExperienceSyncPacket.TYPE, ClientSyncController::handle); ClientPlayNetworking.registerGlobalReceiver(ClientboundFoodSyncPacket.TYPE, ClientSyncController::handle); ClientPlayNetworking.registerGlobalReceiver(ClientboundHotbarSyncPacket.TYPE, ClientSyncController::handle); @@ -41,6 +44,22 @@ public static void init() { ScreenSyncController.init(); } + private static void handle(ClientboundContainerSyncPacket packet, ClientPlayNetworking.Context context) { + setSyncData(packet.playerId()); + syncData.setScreen(); + + // Set container data + syncData.screen.containerType = packet.containerType(); + syncData.screen.containerSize = packet.containerSize(); + + // Initialize container items if not already done + syncData.screen.initializeContainerItems(packet.containerSize()); + + // Update container items + // Support incremental updates: null items in the array mean "don't update this slot" + syncData.screen.updateContainerItems(packet.items()); + } + private static void handle(ClientboundEffectsSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); syncData.effects = packet.effects(); 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 ca6b121..59a53c5 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.gui.screens.DummyContainer; import com.hpfxd.spectatorplus.fabric.client.mixin.InventoryAccessor; import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundInventorySyncPacket; @@ -11,11 +12,12 @@ import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.MenuAccess; -import net.minecraft.core.NonNullList; +import net.minecraft.client.gui.screens.inventory.*; +import net.minecraft.network.chat.Component; import net.minecraft.world.entity.EntityEquipment; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; import net.minecraft.world.item.ItemStack; import static com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController.setSyncData; @@ -78,15 +80,13 @@ private static void handle(ClientboundInventorySyncPacket packet, ClientPlayNetw setSyncData(packet.playerId()); syncData.setScreen(); - if (syncData.screen.inventoryItems == null || syncData.screen.inventoryItems.size() != ClientboundInventorySyncPacket.ITEMS_LENGTH) { - syncData.screen.inventoryItems = NonNullList.withSize(ClientboundInventorySyncPacket.ITEMS_LENGTH, ItemStack.EMPTY); - } + syncData.screen.initializeInventoryItems(ClientboundInventorySyncPacket.ITEMS_LENGTH); final ItemStack[] items = packet.items(); for (int slot = 0; slot < items.length; slot++) { final ItemStack item = items[slot]; if (item != null) { - syncData.screen.inventoryItems.set(slot, item); + syncData.screen.updateInventoryItem(slot, item); if (syncedInventory != null) { syncedInventory.setItem(slot, item); } @@ -137,6 +137,145 @@ public static void openPlayerInventory(Minecraft mc) { } } + public static void openContainerScreen(Minecraft mc, MenuType containerType, int containerSize) { + final Player player = SpecUtil.getCameraPlayer(mc); + if (player != null && mc.player != null && syncData != null && syncData.screen != null) { + // Create dummy container for the synced screen + DummyContainer containerInventory = new DummyContainer(containerSize); + + // Populate the container with synced items using helper method + syncData.screen.populateContainer(containerInventory); + + Inventory playerInventory = syncedInventory != null ? syncedInventory : mc.player.getInventory(); + Screen screen = createVanillaContainerScreen(containerType, syncedWindowId, playerInventory, containerInventory); + + if (screen instanceof MenuAccess) { + handleNewSyncedScreen(mc, (Screen & MenuAccess) screen); + } + } + } + + private static Screen createVanillaContainerScreen(MenuType type, int windowId, Inventory playerInventory, DummyContainer containerInventory) { + Component title = Component.literal("Container"); + + // For furnace-like containers + if (type == MenuType.FURNACE) { + FurnaceMenu menu = new FurnaceMenu(windowId, playerInventory, containerInventory, new SimpleContainerData(4)); + return new FurnaceScreen(menu, playerInventory, title); + } else if (type == MenuType.BLAST_FURNACE) { + BlastFurnaceMenu menu = new BlastFurnaceMenu(windowId, playerInventory, containerInventory, new SimpleContainerData(4)); + return new BlastFurnaceScreen(menu, playerInventory, title); + } else if (type == MenuType.SMOKER) { + SmokerMenu menu = new SmokerMenu(windowId, playerInventory, containerInventory, new SimpleContainerData(4)); + return new SmokerScreen(menu, playerInventory, title); + } + + // For brewing stand + else if (type == MenuType.BREWING_STAND) { + BrewingStandMenu menu = new BrewingStandMenu(windowId, playerInventory, containerInventory, new SimpleContainerData(2)); + return new BrewingStandScreen(menu, playerInventory, title); + } + + // For anvil + else if (type == MenuType.ANVIL) { + AnvilMenu menu = new AnvilMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new AnvilScreen(menu, playerInventory, title); + } + + // For enchanting table + else if (type == MenuType.ENCHANTMENT) { + EnchantmentMenu menu = new EnchantmentMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new EnchantmentScreen(menu, playerInventory, title); + } + + // For crafting table + else if (type == MenuType.CRAFTING) { + CraftingMenu menu = new CraftingMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new CraftingScreen(menu, playerInventory, title); + } + + // For grindstone + else if (type == MenuType.GRINDSTONE) { + GrindstoneMenu menu = new GrindstoneMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new GrindstoneScreen(menu, playerInventory, title); + } + + // For loom + else if (type == MenuType.LOOM) { + LoomMenu menu = new LoomMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new LoomScreen(menu, playerInventory, title); + } + + // For stonecutter + else if (type == MenuType.STONECUTTER) { + StonecutterMenu menu = new StonecutterMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new StonecutterScreen(menu, playerInventory, title); + } + + // For cartography table + else if (type == MenuType.CARTOGRAPHY_TABLE) { + CartographyTableMenu menu = new CartographyTableMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new CartographyTableScreen(menu, playerInventory, title); + } + + // For smithing table + else if (type == MenuType.SMITHING) { + SmithingMenu menu = new SmithingMenu(windowId, playerInventory, ContainerLevelAccess.NULL); + return new SmithingScreen(menu, playerInventory, title); + } + + // For generic containers (chests, etc.) + else if (type == MenuType.GENERIC_9x1) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x1, windowId, playerInventory, containerInventory, 1); + return new ContainerScreen(menu, playerInventory, title); + } else if (type == MenuType.GENERIC_9x2) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x2, windowId, playerInventory, containerInventory, 2); + return new ContainerScreen(menu, playerInventory, title); + } else if (type == MenuType.GENERIC_9x3) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x3, windowId, playerInventory, containerInventory, 3); + return new ContainerScreen(menu, playerInventory, title); + } else if (type == MenuType.GENERIC_9x4) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x4, windowId, playerInventory, containerInventory, 4); + return new ContainerScreen(menu, playerInventory, title); + } else if (type == MenuType.GENERIC_9x5) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x5, windowId, playerInventory, containerInventory, 5); + return new ContainerScreen(menu, playerInventory, title); + } else if (type == MenuType.GENERIC_9x6) { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x6, windowId, playerInventory, containerInventory, 6); + return new ContainerScreen(menu, playerInventory, title); + } + + // For dispenser and dropper + else if (type == MenuType.GENERIC_3x3) { + DispenserMenu menu = new DispenserMenu(windowId, playerInventory, containerInventory); + return new DispenserScreen(menu, playerInventory, title); + } + + // For hopper + else if (type == MenuType.HOPPER) { + HopperMenu menu = new HopperMenu(windowId, playerInventory, containerInventory); + return new HopperScreen(menu, playerInventory, title); + } + + // For shulker box + else if (type == MenuType.SHULKER_BOX) { + ShulkerBoxMenu menu = new ShulkerBoxMenu(windowId, playerInventory, containerInventory); + return new ShulkerBoxScreen(menu, playerInventory, title); + } + + // For beacon + else if (type == MenuType.BEACON) { + BeaconMenu menu = new BeaconMenu(windowId, containerInventory, new SimpleContainerData(3), ContainerLevelAccess.NULL); + return new BeaconScreen(menu, playerInventory, title); + } + + // Fallback to generic 3x9 chest for unknown types (including lectern which has no GUI) + else { + ChestMenu menu = new ChestMenu(MenuType.GENERIC_9x3, windowId, playerInventory, containerInventory, 3); + return new ContainerScreen(menu, playerInventory, title); + } + } + public static > void handleNewSyncedScreen(Minecraft mc, S screen) { isPendingOpen = false; mc.player.containerMenu = screen.getMenu(); @@ -159,18 +298,13 @@ public static > void handleNewSyncedScreen(Mine } public static boolean createInventory(Player spectated) { - if (syncData.screen.inventoryItems == null) { + if (!syncData.screen.hasInventoryItems()) { return false; } EntityEquipment equipment = ((InventoryAccessor)spectated.getInventory()).getEquipment(); syncedInventory = new Inventory(spectated, equipment); - - // Set all inventory slots (0-39) - for (int i = 0; i < syncData.screen.inventoryItems.size(); i++) { - syncedInventory.setItem(i, syncData.screen.inventoryItems.get(i)); - } - // No direct access to armor list; setting slots 36-39 is sufficient for GUI + syncData.screen.populateInventory(syncedInventory); return true; } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java index 8e69bac..e0a1017 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java @@ -1,6 +1,9 @@ package com.hpfxd.spectatorplus.fabric.client.sync.screen; import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; public class ScreenSyncData { @@ -11,10 +14,122 @@ public class ScreenSyncData { */ public NonNullList inventoryItems; + /** + * Container items for non-inventory screens + */ + public NonNullList containerItems; + + /** + * Container type for determining proper GUI rendering + */ + public MenuType containerType; + + /** + * Container size for dynamic containers like chests + */ + public int containerSize; + public boolean isSurvivalInventory; public boolean isClientRequested; public boolean hasDummySlots; public ItemStack cursorItem = ItemStack.EMPTY; public int cursorItemSlot = -1; + + /** + * Initialize or resize container items to the specified size + * @param size the desired size of the container + */ + public void initializeContainerItems(int size) { + if (containerItems == null || containerItems.size() != size) { + containerItems = NonNullList.withSize(size, ItemStack.EMPTY); + } + } + + /** + * Update container item at the specified slot + * @param slot the slot index + * @param item the item to set (null means skip this slot) + */ + public void updateContainerItem(int slot, ItemStack item) { + if (containerItems != null && slot >= 0 && slot < containerItems.size() && item != null) { + containerItems.set(slot, item.isEmpty() ? ItemStack.EMPTY : item); + } + } + + /** + * Update multiple container items from an array + * @param items array of items to update (null items are skipped) + */ + public void updateContainerItems(ItemStack[] items) { + if (containerItems == null || items == null) { + return; + } + for (int i = 0; i < items.length && i < containerItems.size(); i++) { + updateContainerItem(i, items[i]); + } + } + + /** + * Populate a Container with the synced container items + * @param container the container to populate + */ + public void populateContainer(Container container) { + if (containerItems == null || container == null) { + return; + } + for (int i = 0; i < containerItems.size() && i < container.getContainerSize(); i++) { + container.setItem(i, containerItems.get(i)); + } + } + + /** + * Check if container items are initialized + * @return true if containerItems is not null + */ + public boolean hasContainerItems() { + return containerItems != null; + } + + /** + * Initialize or resize inventory items to the specified size + * @param size the desired size of the inventory + */ + public void initializeInventoryItems(int size) { + if (inventoryItems == null || inventoryItems.size() != size) { + inventoryItems = NonNullList.withSize(size, ItemStack.EMPTY); + } + } + + /** + * Update inventory item at the specified slot + * @param slot the slot index + * @param item the item to set + */ + public void updateInventoryItem(int slot, ItemStack item) { + if (inventoryItems != null && slot >= 0 && slot < inventoryItems.size()) { + inventoryItems.set(slot, item); + } + } + + /** + * Populate an Inventory with the synced inventory items + * @param inventory the inventory to populate + */ + public void populateInventory(Inventory inventory) { + if (inventoryItems == null || inventory == null) { + return; + } + for (int i = 0; i < inventoryItems.size(); i++) { + inventory.setItem(i, inventoryItems.get(i)); + } + } + + /** + * Check if inventory items are initialized + * @return true if inventoryItems is not null + */ + public boolean hasInventoryItems() { + return inventoryItems != null; + } } 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 cb4057f..03fcd69 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 @@ -6,6 +6,7 @@ import com.hpfxd.spectatorplus.fabric.sync.handler.CursorSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.InventorySyncHandler; +import com.hpfxd.spectatorplus.fabric.sync.handler.ScreenSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -36,7 +37,9 @@ 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 java.util.OptionalInt; import java.util.Set; @Mixin(ServerPlayer.class) @@ -65,8 +68,8 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { // If we're changing from spectating a player to something else, clean up if (spectator.getCamera() instanceof ServerPlayer oldTarget && spectator.getCamera() != entityToSpectate) { - // TODO: Could add cleanup logic here if needed - // This runs before the camera change, so we can still access the old camera + // Clean up any container listeners when stopping spectating a player + ScreenSyncHandler.cleanupSpectatorListeners(spectator, oldTarget); } } @Inject(method = "setCamera(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V")) @@ -79,7 +82,6 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.sendPacket(spectator, ClientboundHotbarSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); InventorySyncHandler.sendPacket(spectator, target); - //CursorSyncHandler.sendPacket(spectator, target); EffectsSyncHandler.onStartSpectating(spectator, target); // Send initial map data patch packet if the target has a map in inventory @@ -143,4 +145,23 @@ private static ClientboundMapItemDataPacket getInitialMapDataPacket(MapId mapId, this.setCamera(entity); } } + + @Inject(method = "openMenu(Lnet/minecraft/world/MenuProvider;)Ljava/util/OptionalInt;", at = @At("RETURN")) + private void spectatorplus$onOpenMenu(CallbackInfoReturnable cir) { + + final ServerPlayer player = (ServerPlayer) (Object) this; + + if (cir.getReturnValue().isPresent() && player.containerMenu != player.inventoryMenu) { + ScreenSyncHandler.syncScreenOpened(player); + } + } + + @Inject(method = "doCloseContainer()V", at = @At("HEAD")) + private void spectatorplus$onCloseContainer(CallbackInfo ci) { + final ServerPlayer player = (ServerPlayer) (Object) this; + + if (player.containerMenu != player.inventoryMenu) { + ScreenSyncHandler.syncScreenClosed(player); + } + } } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java index fc88ab4..80ca8d2 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.handler.ContainerSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.CursorSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.HotbarSyncHandler; @@ -19,6 +20,7 @@ public static void init() { HotbarSyncHandler.init(); ScreenSyncHandler.init(); + ContainerSyncHandler.init(); EffectsSyncHandler.init(); InventorySyncHandler.init(); CursorSyncHandler.init(); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java index eee14b5..c66eea4 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundContainerSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; @@ -17,6 +18,7 @@ public static void registerAll() { PayloadTypeRegistry.playC2S().register(ServerboundOpenedInventorySyncPacket.TYPE, ServerboundOpenedInventorySyncPacket.STREAM_CODEC); PayloadTypeRegistry.playC2S().register(ServerboundRequestInventoryOpenPacket.TYPE, ServerboundRequestInventoryOpenPacket.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundContainerSyncPacket.TYPE, ClientboundContainerSyncPacket.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ClientboundExperienceSyncPacket.TYPE, ClientboundExperienceSyncPacket.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ClientboundFoodSyncPacket.TYPE, ClientboundFoodSyncPacket.STREAM_CODEC); PayloadTypeRegistry.playS2C().register(ClientboundHotbarSyncPacket.TYPE, ClientboundHotbarSyncPacket.STREAM_CODEC); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java new file mode 100644 index 0000000..32ef0a1 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java @@ -0,0 +1,152 @@ +package com.hpfxd.spectatorplus.fabric.sync.handler; + +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundContainerSyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundScreenSyncPacket; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.ContainerListener; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class ContainerSyncHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerSyncHandler.class); + + // Map to track container listeners for each spectator + // Key: spectator UUID, Value: listener instance + private static final Map containerListeners = new HashMap<>(); + + public static void init() { + // Clean up listeners when a player disconnects + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + containerListeners.remove(handler.getPlayer().getUUID()); + }); + } + + /** + * Subscribe a spectator to the target player's open container. + * The spectator will receive automatic updates when the container changes. + * This is like the spectator opening the same container themselves. + */ + public static void subscribeToContainer(ServerPlayer spectator, ServerPlayer target) { + try { + var containerMenu = target.containerMenu; + if (containerMenu == target.inventoryMenu) { + return; // No container open, just player inventory + } + + // Remove any existing listener for this spectator + unsubscribeFromContainer(spectator, target); + + // IMPORTANT: Send data in the correct order to prevent the client from clearing it + // 1. First, send the target's inventory data (player's backpack) + InventorySyncHandler.sendPacket(spectator, target); + + // 2. Prepare container data + int containerSize = containerMenu.slots.size() - 36; // Subtract player inventory slots + ItemStack[] containerItems = new ItemStack[containerSize]; + + for (int i = 0; i < containerSize; i++) { + var slot = containerMenu.getSlot(i); + containerItems[i] = slot.getItem().copy(); + } + + // 3. Send container sync packet (this populates container items before screen opens) + ServerSyncController.sendPacket(spectator, new ClientboundContainerSyncPacket( + target.getUUID(), + containerMenu.getType(), + containerSize, + containerItems + )); + + // 4. Finally, send screen sync packet to open the GUI (this must be last) + int flags = 0; // Container screen (not survival inventory) + flags |= (1 << 2); // Has dummy slots flag for container screens + ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); + + // Create and store the listener for this spectator + ContainerListener listener = new ContainerListener() { + @Override + public void slotChanged(@NotNull net.minecraft.world.inventory.AbstractContainerMenu menu, int slotIndex, @NotNull ItemStack stack) { + // Only sync container slots, not player inventory slots + if (slotIndex < containerSize && ServerSyncController.getSpectators(target).contains(spectator)) { + ItemStack[] update = new ItemStack[containerSize]; + for (int i = 0; i < containerSize; i++) { + if (i == slotIndex) { + update[i] = stack.copy(); + } else { + // For efficiency, only send changed slot + update[i] = null; + } + } + ServerSyncController.sendPacket(spectator, new ClientboundContainerSyncPacket( + target.getUUID(), + menu.getType(), + containerSize, + update + )); + } + } + + @Override + public void dataChanged(@NotNull net.minecraft.world.inventory.AbstractContainerMenu menu, int dataSlot, int value) { + // Handle furnace progress, brewing stand progress, etc. + // For now, we don't need to sync this separately + } + }; + + // Add spectator as a listener to the container menu + containerMenu.addSlotListener(listener); + + // Store the listener so we can remove it later + containerListeners.put(spectator.getUUID(), listener); + + // Try to open the actual screen on the spectator's client + try { + spectator.connection.send(new ClientboundOpenScreenPacket( + containerMenu.containerId, + containerMenu.getType(), + target.getDisplayName() + )); + } catch (Exception e) { + LOGGER.debug("Could not send ClientboundOpenScreenPacket to spectator {}: {}", + spectator.getGameProfile().name(), e.getMessage()); + // Continue without the packet - the client will handle it through sync packets + } + } catch (Exception e) { + LOGGER.error("Error subscribing spectator {} to container of {}", + spectator.getGameProfile().name(), target.getGameProfile().name(), e); + } + } + + /** + * Unsubscribe a spectator from a target player's container. + * This removes the container listener to prevent memory leaks. + */ + public static void unsubscribeFromContainer(ServerPlayer spectator, ServerPlayer target) { + try { + ContainerListener oldListener = containerListeners.remove(spectator.getUUID()); + if (oldListener != null && target.containerMenu != null) { + target.containerMenu.removeSlotListener(oldListener); + LOGGER.debug("Removed container listener for spectator {}", spectator.getGameProfile().name()); + } + } catch (Exception e) { + LOGGER.error("Error unsubscribing spectator {} from container", spectator.getGameProfile().name(), e); + } + } + + /** + * Clean up all listeners for a spectator when they stop spectating a target. + * Called from ServerPlayerMixin when camera changes. + */ + public static void cleanupSpectatorListeners(ServerPlayer spectator, ServerPlayer target) { + unsubscribeFromContainer(spectator, target); + } +} diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java index 4d124c5..e49f377 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java @@ -7,7 +7,6 @@ import com.hpfxd.spectatorplus.fabric.sync.packet.ServerboundRequestInventoryOpenPacket; import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.slf4j.Logger; @@ -42,9 +41,9 @@ private static void handle(ServerboundOpenedInventorySyncPacket packet, ServerPl try { final var player = ctx.player(); if (packet.isOpened()) { - onPlayerOpenInventory(player); + syncScreenOpened(player); } else { - onPlayerCloseInventory(player); + syncScreenClosed(player); } } catch (Exception e) { LOGGER.error("Error handling player inventory open/close", e); @@ -64,7 +63,7 @@ private static void onRequestOpen(ServerPlayer spectator, ServerPlayer target) { * * @param target The player who opened their inventory/container */ - private static void onPlayerOpenInventory(ServerPlayer target) { + public static void syncScreenOpened(ServerPlayer target) { try { // Get all spectators who are currently viewing this player var spectators = ServerSyncController.getSpectators(target); @@ -80,9 +79,9 @@ private static void onPlayerOpenInventory(ServerPlayer target) { // Determine what type of screen the target player has open if (target.containerMenu != target.inventoryMenu) { // Player has a container open (chest, crafting table, furnace, etc.) - LOGGER.debug("Syncing container screen for spectator {} viewing {}", + LOGGER.debug("Subscribing spectator {} to container of {}", spectator.getGameProfile().name(), target.getGameProfile().name()); - syncContainerScreen(spectator, target); + ContainerSyncHandler.subscribeToContainer(spectator, target); } else { // Player has their regular inventory open (survival/creative inventory) LOGGER.debug("Syncing player inventory screen for spectator {} viewing {}", @@ -101,7 +100,7 @@ private static void onPlayerOpenInventory(ServerPlayer target) { * * @param target The player who closed their inventory/container */ - private static void onPlayerCloseInventory(ServerPlayer target) { + public static void syncScreenClosed(ServerPlayer target) { try { // Get all spectators who are currently viewing this player var spectators = ServerSyncController.getSpectators(target); @@ -117,6 +116,9 @@ private static void onPlayerCloseInventory(ServerPlayer target) { LOGGER.debug("Syncing inventory close for spectator {} viewing {}", spectator.getGameProfile().name(), target.getGameProfile().name()); + // Remove any container listeners for this spectator + ContainerSyncHandler.unsubscribeFromContainer(spectator, target); + // Send screen sync packet to indicate inventory/container was closed int flags = 0; // No flags means close screen flags |= (1 << 3); // Screen closed flag @@ -156,34 +158,11 @@ public static void updatePlayerInventory(ServerPlayer player, ItemStack[] invent } /** - * Syncs a container screen (chest, crafting table, furnace, etc.) to a spectator. - * This is used when the target player has opened a container that isn't their inventory. - * - * @param spectator The spectator to sync the screen to - * @param target The player whose container screen should be synced + * Clean up all listeners for a spectator when they stop spectating a target. + * Called from ServerPlayerMixin when camera changes. */ - private static void syncContainerScreen(ServerPlayer spectator, ServerPlayer target) { - // Send screen sync packet for container screen - int flags = 0; // Container screen (not survival inventory) - flags |= (1 << 2); // Has dummy slots flag for container screens - ServerSyncController.sendPacket(spectator, new ClientboundScreenSyncPacket(target.getUUID(), flags)); - - // Send the target's inventory data to the spectator - InventorySyncHandler.sendPacket(spectator, target); - - // For container screens (chests, crafting tables, etc.), we can send ClientboundOpenScreenPacket - // because container menus have proper MenuType implementations - try { - spectator.connection.send(new ClientboundOpenScreenPacket( - target.containerMenu.containerId, - target.containerMenu.getType(), - target.getDisplayName() - )); - } catch (Exception e) { - LOGGER.warn("Could not sync container screen type for {} to spectator {}: {}", - target.getGameProfile().name(), spectator.getGameProfile().name(), e.getMessage()); - // Continue without the screen packet - the client will handle it through sync packets - } + public static void cleanupSpectatorListeners(ServerPlayer spectator, ServerPlayer target) { + ContainerSyncHandler.cleanupSpectatorListeners(spectator, target); } /** diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java new file mode 100644 index 0000000..f28225d --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java @@ -0,0 +1,58 @@ +package com.hpfxd.spectatorplus.fabric.sync.packet; + +import com.hpfxd.spectatorplus.fabric.sync.ClientboundSyncPacket; +import com.hpfxd.spectatorplus.fabric.sync.CustomPacketCodecs; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public record ClientboundContainerSyncPacket( + UUID playerId, + MenuType containerType, + int containerSize, + ItemStack[] items +) implements ClientboundSyncPacket { + public static final StreamCodec STREAM_CODEC = + CustomPacketPayload.codec(ClientboundContainerSyncPacket::write, ClientboundContainerSyncPacket::new); + public static final CustomPacketPayload.Type TYPE = + new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:container_sync")); + private static final String PERMISSION = "spectatorplus.sync.screen"; + + public ClientboundContainerSyncPacket(RegistryFriendlyByteBuf buf) { + this( + buf.readUUID(), + buf.readById(id -> { + MenuType type = BuiltInRegistries.MENU.byId(id); + return type != null ? type : MenuType.GENERIC_9x3; + }), + buf.readVarInt(), + CustomPacketCodecs.readItems(buf) + ); + } + + public void write(RegistryFriendlyByteBuf buf) { + buf.writeUUID(this.playerId); + buf.writeById(BuiltInRegistries.MENU::getId, this.containerType); + buf.writeVarInt(this.containerSize); + CustomPacketCodecs.writeItems(buf, this.items); + } + + @Override + public @NotNull Type type() { + return TYPE; + } + + @Override + public boolean canSend(ServerPlayer receiver) { + return Permissions.check(receiver, PERMISSION, true); + } +} From d76d20f4e4f0fcade0517a7443c2165bfc63fe30 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:38:17 +0800 Subject: [PATCH 53/57] refactor: Refactor project structure to support multi-version build - Split `fabric` module into `fabric-core`, `fabric-1.21.10`, and `fabric-1.21.11`. - Split `paper` module into `paper-core`, `paper-1.21.10`, and `paper-1.21.11`. - Configure specific dependencies for 1.21.10 and 1.21.11 version modules. --- fabric/build.gradle.kts | 83 ++---------------- fabric/fabric-1.21.10/build.gradle.kts | 16 ++++ fabric/fabric-1.21.10/gradle.properties | 13 +++ fabric/fabric-1.21.11/build.gradle.kts | 16 ++++ fabric/{ => fabric-1.21.11}/gradle.properties | 0 fabric/fabric-core/build.gradle.kts | 80 +++++++++++++++++ fabric/fabric-core/gradle.properties | 13 +++ .../fabric/client/ClientTargetController.java | 0 .../fabric/client/SpectatorClientMod.java | 0 .../fabric/client/SpectatorKeybinds.java | 0 .../fabric/client/config/ClientConfig.java | 0 .../client/config/ClothConfigIntegration.java | 0 .../client/config/ModMenuIntegration.java | 0 .../client/gui/screens/DummyContainer.java | 0 .../gui/screens/DummyInventorySlot.java | 0 .../client/gui/screens/ItemMoveAnimation.java | 0 .../gui/screens/SyncedInventoryMenu.java | 0 .../gui/screens/SyncedInventoryScreen.java | 0 .../client/mixin/ClientLevelAccessor.java | 0 .../fabric/client/mixin/ClientLevelMixin.java | 0 .../mixin/ClientWaypointManagerMixin.java | 0 .../fabric/client/mixin/EntityMixin.java | 0 .../client/mixin/EntityRendererMixin.java | 0 .../mixin/ExperienceBarRendererMixin.java | 0 .../client/mixin/GameRendererMixin.java | 0 .../fabric/client/mixin/GuiMixin.java | 0 .../client/mixin/InventoryAccessor.java | 0 .../mixin/ItemInHandRendererAccessor.java | 0 .../client/mixin/ItemInHandRendererMixin.java | 0 .../client/mixin/LevelRendererAccessor.java | 0 .../client/mixin/LevelRendererMixin.java | 0 .../client/mixin/LivingEntityAccessor.java | 0 .../client/mixin/LivingEntityMixin.java | 0 .../client/mixin/LocalPlayerAccessor.java | 0 .../fabric/client/mixin/LocalPlayerMixin.java | 0 .../fabric/client/mixin/MinecraftMixin.java | 0 .../client/mixin/PlayerMenuItemAccessor.java | 0 .../client/mixin/PlayerMenuItemMixin.java | 0 .../mixin/ScreenEffectRendererMixin.java | 0 .../client/mixin/SpectatorGuiAccessor.java | 0 .../client/mixin/SpectatorMenuAccessor.java | 0 .../screen/AbstractContainerMenuMixin.java | 0 .../screen/AbstractContainerScreenMixin.java | 0 .../AbstractRecipeBookScreenAccessor.java | 0 .../mixin/screen/ImageButtonAccessor.java | 0 .../mixin/screen/InventoryMenuMixin.java | 0 .../mixin/screen/InventoryScreenMixin.java | 0 .../client/mixin/screen/MenuScreensMixin.java | 0 .../client/mixin/screen/ScreenMixin.java | 0 .../client/sync/ClientSyncController.java | 0 .../fabric/client/sync/ClientSyncData.java | 0 .../sync/screen/ScreenSyncController.java | 0 .../client/sync/screen/ScreenSyncData.java | 0 .../fabric/client/util/EffectUtil.java | 0 .../fabric/client/util/SpecUtil.java | 0 .../spectatorplus.client.mixins.json | 0 .../spectatorplus/fabric/SpectatorMod.java | 0 .../fabric/config/ConfigLoader.java | 0 .../fabric/config/ServerConfig.java | 0 .../fabric/mixin/ChunkMapAccessor.java | 0 .../fabric/mixin/LivingEntityMixin.java | 0 .../ServerGamePacketListenerImplMixin.java | 0 .../fabric/mixin/ServerPlayerMixin.java | 0 .../fabric/mixin/TrackedEntityMixin.java | 0 .../fabric/sync/ClientboundSyncPacket.java | 0 .../fabric/sync/CustomPacketCodecs.java | 0 .../fabric/sync/ServerSyncController.java | 0 .../fabric/sync/ServerboundSyncPacket.java | 0 .../fabric/sync/SyncPackets.java | 0 .../fabric/sync/SyncedEffect.java | 0 .../sync/handler/ContainerSyncHandler.java | 0 .../sync/handler/CursorSyncHandler.java | 0 .../sync/handler/EffectsSyncHandler.java | 0 .../sync/handler/HotbarSyncHandler.java | 0 .../sync/handler/InventorySyncHandler.java | 0 .../sync/handler/ScreenSyncHandler.java | 0 .../ClientboundContainerSyncPacket.java | 0 .../packet/ClientboundEffectsSyncPacket.java | 0 .../ClientboundExperienceSyncPacket.java | 0 .../packet/ClientboundFoodSyncPacket.java | 0 .../packet/ClientboundHotbarSyncPacket.java | 0 .../ClientboundInventorySyncPacket.java | 0 .../ClientboundScreenCursorSyncPacket.java | 0 .../packet/ClientboundScreenSyncPacket.java | 0 .../ClientboundSelectedSlotSyncPacket.java | 0 .../ServerboundOpenedInventorySyncPacket.java | 0 ...ServerboundRequestInventoryOpenPacket.java | 0 .../resources/assets/spectatorplus/icon.png | Bin .../assets/spectatorplus/lang/en_us.json | 0 .../src/main/resources/fabric.mod.json | 0 .../resources/spectatorplus.accesswidener | 0 .../main/resources/spectatorplus.mixins.json | 0 paper/build.gradle.kts | 54 ++---------- paper/paper-1.21.10/build.gradle.kts | 16 ++++ paper/paper-1.21.10/gradle.properties | 2 + paper/paper-1.21.11/build.gradle.kts | 10 +++ paper/{ => paper-1.21.11}/gradle.properties | 0 paper/paper-core/build.gradle.kts | 51 +++++++++++ paper/paper-core/gradle.properties | 2 + .../spectatorplus/paper/SpectatorPlugin.java | 0 .../paper/SpectatorWorkarounds.java | 0 .../paper/config/ServerConfig.java | 0 .../paper/effect/EffectType.java | 0 .../paper/effect/SyncedEffect.java | 0 .../paper/sync/ClientboundSyncPacket.java | 0 .../paper/sync/ServerSyncController.java | 0 .../paper/sync/ServerboundSyncPacket.java | 0 .../spectatorplus/paper/sync/SyncPacket.java | 0 .../spectatorplus/paper/sync/SyncPackets.java | 0 .../sync/handler/EffectsSyncHandler.java | 0 .../sync/handler/ExperienceSyncHandler.java | 0 .../paper/sync/handler/FoodSyncHandler.java | 0 .../sync/handler/InventorySyncHandler.java | 0 .../paper/sync/handler/MapSyncHandler.java | 0 .../sync/handler/SelectedSlotSyncHandler.java | 0 .../screen/CraftingSyncedContainer.java | 0 .../handler/screen/DirectSyncedContainer.java | 0 .../screen/ReplicaSyncedContainer.java | 0 .../handler/screen/ScreenSyncHandler.java | 0 .../sync/handler/screen/SyncedContainer.java | 0 .../handler/screen/SyncedPlayerInventory.java | 0 .../sync/handler/screen/SyncedScreen.java | 0 .../packet/ClientboundEffectsSyncPacket.java | 0 .../ClientboundExperienceSyncPacket.java | 0 .../packet/ClientboundFoodSyncPacket.java | 0 .../packet/ClientboundHotbarSyncPacket.java | 0 .../ClientboundInventorySyncPacket.java | 0 .../ClientboundScreenCursorSyncPacket.java | 0 .../packet/ClientboundScreenSyncPacket.java | 0 .../ClientboundSelectedSlotSyncPacket.java | 0 .../ServerboundOpenedInventorySyncPacket.java | 0 ...ServerboundRequestInventoryOpenPacket.java | 0 .../paper/util/ReflectionUtil.java | 0 .../paper/util/SerializationUtil.java | 0 .../src/main/resources/config.yml | 0 .../src/main/resources/paper-plugin.yml | 0 settings.gradle.kts | 17 ++-- 137 files changed, 246 insertions(+), 127 deletions(-) create mode 100644 fabric/fabric-1.21.10/build.gradle.kts create mode 100644 fabric/fabric-1.21.10/gradle.properties create mode 100644 fabric/fabric-1.21.11/build.gradle.kts rename fabric/{ => fabric-1.21.11}/gradle.properties (100%) create mode 100644 fabric/fabric-core/build.gradle.kts create mode 100644 fabric/fabric-core/gradle.properties rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/ClientTargetController.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorClientMod.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClientConfig.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyInventorySlot.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/ItemMoveAnimation.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryMenu.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorGuiAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorMenuAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerMenuMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractRecipeBookScreenAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ImageButtonAccessor.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryMenuMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryScreenMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java (100%) rename fabric/{ => fabric-core}/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java (100%) rename fabric/{ => fabric-core}/src/client/resources/spectatorplus.client.mixins.json (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/SpectatorMod.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/config/ConfigLoader.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/config/ServerConfig.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ChunkMapAccessor.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/TrackedEntityMixin.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ClientboundSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerboundSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/HotbarSyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java (100%) rename fabric/{ => fabric-core}/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java (100%) rename fabric/{ => fabric-core}/src/main/resources/assets/spectatorplus/icon.png (100%) rename fabric/{ => fabric-core}/src/main/resources/assets/spectatorplus/lang/en_us.json (100%) rename fabric/{ => fabric-core}/src/main/resources/fabric.mod.json (100%) rename fabric/{ => fabric-core}/src/main/resources/spectatorplus.accesswidener (100%) rename fabric/{ => fabric-core}/src/main/resources/spectatorplus.mixins.json (100%) create mode 100644 paper/paper-1.21.10/build.gradle.kts create mode 100644 paper/paper-1.21.10/gradle.properties create mode 100644 paper/paper-1.21.11/build.gradle.kts rename paper/{ => paper-1.21.11}/gradle.properties (100%) create mode 100644 paper/paper-core/build.gradle.kts create mode 100644 paper/paper-core/gradle.properties rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorPlugin.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorWorkarounds.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/config/ServerConfig.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/ClientboundSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerboundSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/ExperienceSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/FoodSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/MapSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/SelectedSlotSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/CraftingSyncedContainer.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/DirectSyncedContainer.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedContainer.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedPlayerInventory.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedScreen.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundExperienceSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundFoodSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundHotbarSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenCursorSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundSelectedSlotSyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundOpenedInventorySyncPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundRequestInventoryOpenPacket.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java (100%) rename paper/{ => paper-core}/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java (100%) rename paper/{ => paper-core}/src/main/resources/config.yml (100%) rename paper/{ => paper-core}/src/main/resources/paper-plugin.yml (100%) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 05cd608..28d2e47 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,80 +1,15 @@ plugins { - id("fabric-loom") version "1.14-SNAPSHOT" - id("spectatorplus.platform") + id("fabric-loom") version "1.14-SNAPSHOT" apply false + id("spectatorplus.platform") apply false } -description = "A Fabric mod that improves spectator mode by showing the hotbar, health, and held item of the spectated player" +subprojects { + apply(plugin = "java") -repositories { - maven("https://maven.parchmentmc.org") - maven("https://oss.sonatype.org/content/repositories/snapshots") - maven("https://maven.shedaniel.me/") - maven("https://maven.terraformersmc.com/releases/") -} - -loom { - splitEnvironmentSourceSets() - - mods { - register("spectatorplus") { - sourceSet(sourceSets.getByName("main")) - sourceSet(sourceSets.getByName("client")) - } - } - - runs { - getByName("client") { - client() - ideConfigGenerated(true) - runDir("run/client") - } - - getByName("server") { - server() - ideConfigGenerated(true) - runDir("run") - } - } - - accessWidenerPath = file("src/main/resources/spectatorplus.accesswidener") -} - -dependencies { - minecraft("com.mojang:minecraft:${property("minecraft_version")}") - modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}") - mappings(loom.layered { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") - }) - modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") - - include(modImplementation("me.lucko:fabric-permissions-api:${property("fabric_permissions_api_version")}")!!) - - modImplementation("me.shedaniel.cloth:cloth-config-fabric:${property("cloth_config_version")}") { - exclude("net.fabricmc.fabric-api") - } - - modImplementation("com.terraformersmc:modmenu:${property("modmenu_version")}") -} - -tasks { - processResources { - inputs.property("version", project.version) - filesMatching("fabric.mod.json") { - expand( - mapOf( - "version" to project.version, - "description" to project.description, - ) - ) - } - } - - jar { - from("../LICENSE") - } - - remapJar { - archiveVersion = getByName("jar").archiveVersion + repositories { + maven("https://maven.parchmentmc.org") + maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://maven.shedaniel.me/") + maven("https://maven.terraformersmc.com/releases/") } } diff --git a/fabric/fabric-1.21.10/build.gradle.kts b/fabric/fabric-1.21.10/build.gradle.kts new file mode 100644 index 0000000..5ed21b1 --- /dev/null +++ b/fabric/fabric-1.21.10/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("fabric-loom") + id("spectatorplus.platform") +} + +dependencies { + minecraft("com.mojang:minecraft:${property("minecraft_version")}") + mappings(loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") + }) + modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}") + modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") + + implementation(project(":fabric:fabric-core")) +} diff --git a/fabric/fabric-1.21.10/gradle.properties b/fabric/fabric-1.21.10/gradle.properties new file mode 100644 index 0000000..fe56e70 --- /dev/null +++ b/fabric/fabric-1.21.10/gradle.properties @@ -0,0 +1,13 @@ +minecraft_version=1.21.10 +yarn_mappings=1.21.10+build.1 +loader_version=0.17.2 + +# Fabric API +fabric_version=0.138.3+1.21.10 + +parchment_minecraft_version=1.21.10 +parchment_version=2025.10.12 + +fabric_permissions_api_version=0.4.0 +cloth_config_version=20.0.149 +modmenu_version=16.0.0-rc.1 diff --git a/fabric/fabric-1.21.11/build.gradle.kts b/fabric/fabric-1.21.11/build.gradle.kts new file mode 100644 index 0000000..5ed21b1 --- /dev/null +++ b/fabric/fabric-1.21.11/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("fabric-loom") + id("spectatorplus.platform") +} + +dependencies { + minecraft("com.mojang:minecraft:${property("minecraft_version")}") + mappings(loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") + }) + modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}") + modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") + + implementation(project(":fabric:fabric-core")) +} diff --git a/fabric/gradle.properties b/fabric/fabric-1.21.11/gradle.properties similarity index 100% rename from fabric/gradle.properties rename to fabric/fabric-1.21.11/gradle.properties diff --git a/fabric/fabric-core/build.gradle.kts b/fabric/fabric-core/build.gradle.kts new file mode 100644 index 0000000..05cd608 --- /dev/null +++ b/fabric/fabric-core/build.gradle.kts @@ -0,0 +1,80 @@ +plugins { + id("fabric-loom") version "1.14-SNAPSHOT" + id("spectatorplus.platform") +} + +description = "A Fabric mod that improves spectator mode by showing the hotbar, health, and held item of the spectated player" + +repositories { + maven("https://maven.parchmentmc.org") + maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://maven.shedaniel.me/") + maven("https://maven.terraformersmc.com/releases/") +} + +loom { + splitEnvironmentSourceSets() + + mods { + register("spectatorplus") { + sourceSet(sourceSets.getByName("main")) + sourceSet(sourceSets.getByName("client")) + } + } + + runs { + getByName("client") { + client() + ideConfigGenerated(true) + runDir("run/client") + } + + getByName("server") { + server() + ideConfigGenerated(true) + runDir("run") + } + } + + accessWidenerPath = file("src/main/resources/spectatorplus.accesswidener") +} + +dependencies { + minecraft("com.mojang:minecraft:${property("minecraft_version")}") + modImplementation("net.fabricmc:fabric-loader:${property("loader_version")}") + mappings(loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") + }) + modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") + + include(modImplementation("me.lucko:fabric-permissions-api:${property("fabric_permissions_api_version")}")!!) + + modImplementation("me.shedaniel.cloth:cloth-config-fabric:${property("cloth_config_version")}") { + exclude("net.fabricmc.fabric-api") + } + + modImplementation("com.terraformersmc:modmenu:${property("modmenu_version")}") +} + +tasks { + processResources { + inputs.property("version", project.version) + filesMatching("fabric.mod.json") { + expand( + mapOf( + "version" to project.version, + "description" to project.description, + ) + ) + } + } + + jar { + from("../LICENSE") + } + + remapJar { + archiveVersion = getByName("jar").archiveVersion + } +} diff --git a/fabric/fabric-core/gradle.properties b/fabric/fabric-core/gradle.properties new file mode 100644 index 0000000..b932122 --- /dev/null +++ b/fabric/fabric-core/gradle.properties @@ -0,0 +1,13 @@ +minecraft_version=1.21.11 +yarn_mappings=1.21.11+build.3 +loader_version=0.18.4 + +# Fabric API +fabric_version=0.140.2+1.21.11 + +parchment_minecraft_version=1.21.11 +parchment_version=2025.12.20 + +fabric_permissions_api_version=0.6.1 +cloth_config_version=21.11.153 +modmenu_version=17.0.0-beta.1 diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/ClientTargetController.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/ClientTargetController.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/ClientTargetController.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/ClientTargetController.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorClientMod.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorClientMod.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorClientMod.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorClientMod.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClientConfig.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClientConfig.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClientConfig.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClientConfig.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ClothConfigIntegration.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/config/ModMenuIntegration.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyContainer.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyInventorySlot.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyInventorySlot.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyInventorySlot.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/DummyInventorySlot.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/ItemMoveAnimation.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/ItemMoveAnimation.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/ItemMoveAnimation.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/ItemMoveAnimation.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryMenu.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryMenu.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryMenu.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryMenu.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/gui/screens/SyncedInventoryScreen.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientLevelMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ClientWaypointManagerMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/EntityRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ExperienceBarRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/InventoryAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LevelRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/PlayerMenuItemMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ScreenEffectRendererMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorGuiAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorGuiAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorGuiAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorGuiAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorMenuAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorMenuAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorMenuAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/SpectatorMenuAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerMenuMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerMenuMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerMenuMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerMenuMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractRecipeBookScreenAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractRecipeBookScreenAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractRecipeBookScreenAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractRecipeBookScreenAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ImageButtonAccessor.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ImageButtonAccessor.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ImageButtonAccessor.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ImageButtonAccessor.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryMenuMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryMenuMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryMenuMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryMenuMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryScreenMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryScreenMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryScreenMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/InventoryScreenMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/MenuScreensMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/ScreenMixin.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncData.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncController.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/screen/ScreenSyncData.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java similarity index 100% rename from fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java rename to fabric/fabric-core/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/fabric-core/src/client/resources/spectatorplus.client.mixins.json similarity index 100% rename from fabric/src/client/resources/spectatorplus.client.mixins.json rename to fabric/fabric-core/src/client/resources/spectatorplus.client.mixins.json diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/SpectatorMod.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/SpectatorMod.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/SpectatorMod.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/SpectatorMod.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/config/ConfigLoader.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/config/ConfigLoader.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/config/ConfigLoader.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/config/ConfigLoader.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/config/ServerConfig.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/config/ServerConfig.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/config/ServerConfig.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/config/ServerConfig.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ChunkMapAccessor.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ChunkMapAccessor.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ChunkMapAccessor.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ChunkMapAccessor.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerGamePacketListenerImplMixin.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/TrackedEntityMixin.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/TrackedEntityMixin.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/TrackedEntityMixin.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/TrackedEntityMixin.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ClientboundSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ClientboundSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ClientboundSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ClientboundSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerboundSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerboundSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerboundSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerboundSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncPackets.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/SyncedEffect.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ContainerSyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/CursorSyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/HotbarSyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/HotbarSyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/HotbarSyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/HotbarSyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/InventorySyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/ScreenSyncHandler.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundContainerSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java b/fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java similarity index 100% rename from fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java rename to fabric/fabric-core/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java diff --git a/fabric/src/main/resources/assets/spectatorplus/icon.png b/fabric/fabric-core/src/main/resources/assets/spectatorplus/icon.png similarity index 100% rename from fabric/src/main/resources/assets/spectatorplus/icon.png rename to fabric/fabric-core/src/main/resources/assets/spectatorplus/icon.png diff --git a/fabric/src/main/resources/assets/spectatorplus/lang/en_us.json b/fabric/fabric-core/src/main/resources/assets/spectatorplus/lang/en_us.json similarity index 100% rename from fabric/src/main/resources/assets/spectatorplus/lang/en_us.json rename to fabric/fabric-core/src/main/resources/assets/spectatorplus/lang/en_us.json diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/fabric-core/src/main/resources/fabric.mod.json similarity index 100% rename from fabric/src/main/resources/fabric.mod.json rename to fabric/fabric-core/src/main/resources/fabric.mod.json diff --git a/fabric/src/main/resources/spectatorplus.accesswidener b/fabric/fabric-core/src/main/resources/spectatorplus.accesswidener similarity index 100% rename from fabric/src/main/resources/spectatorplus.accesswidener rename to fabric/fabric-core/src/main/resources/spectatorplus.accesswidener diff --git a/fabric/src/main/resources/spectatorplus.mixins.json b/fabric/fabric-core/src/main/resources/spectatorplus.mixins.json similarity index 100% rename from fabric/src/main/resources/spectatorplus.mixins.json rename to fabric/fabric-core/src/main/resources/spectatorplus.mixins.json diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index 0ce70fe..55865a4 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -1,51 +1,15 @@ plugins { - id("spectatorplus.platform") - id("io.github.goooler.shadow") version "8.1.7" - id("xyz.jpenilla.run-paper") version "2.3.1" + id("spectatorplus.platform") apply false + id("io.github.goooler.shadow") version "8.1.7" apply false + id("xyz.jpenilla.run-paper") version "2.3.1" apply false } -description = "Paper server-side companion for the SpectatorPlus mod" +subprojects { + apply(plugin = "java") -repositories { - mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") -} - -dependencies { - compileOnly("io.papermc.paper:paper-api:${property("paper_version")}") - - implementation("xyz.jpenilla:reflection-remapper:${property("reflection_remapper_version")}") -} - -tasks { - processResources { - inputs.property("version", project.version) - filesMatching("paper-plugin.yml") { - expand( - mapOf( - "version" to project.version, - "description" to project.description, - ) - ) - } - } - - shadowJar { - enabled = false - } - - jar { - enabled = true - archiveClassifier.set("") - from("../LICENSE") - } - - runServer { - minecraftVersion("1.21.11") - } - - named("build") { - dependsOn(shadowJar) + repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") } } diff --git a/paper/paper-1.21.10/build.gradle.kts b/paper/paper-1.21.10/build.gradle.kts new file mode 100644 index 0000000..38b73fa --- /dev/null +++ b/paper/paper-1.21.10/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("spectatorplus.platform") + id("io.github.goooler.shadow") + id("xyz.jpenilla.run-paper") +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:${property("paper_version")}") + implementation(project(":paper:paper-core")) +} + +tasks { + shadowJar { + // logic to shade core + } +} diff --git a/paper/paper-1.21.10/gradle.properties b/paper/paper-1.21.10/gradle.properties new file mode 100644 index 0000000..6c0c58a --- /dev/null +++ b/paper/paper-1.21.10/gradle.properties @@ -0,0 +1,2 @@ +paper_version=1.21.10-R0.1-SNAPSHOT +reflection_remapper_version=0.1.3 \ No newline at end of file diff --git a/paper/paper-1.21.11/build.gradle.kts b/paper/paper-1.21.11/build.gradle.kts new file mode 100644 index 0000000..9572211 --- /dev/null +++ b/paper/paper-1.21.11/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("spectatorplus.platform") + id("io.github.goooler.shadow") + id("xyz.jpenilla.run-paper") +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:${property("paper_version")}") + implementation(project(":paper:paper-core")) +} diff --git a/paper/gradle.properties b/paper/paper-1.21.11/gradle.properties similarity index 100% rename from paper/gradle.properties rename to paper/paper-1.21.11/gradle.properties diff --git a/paper/paper-core/build.gradle.kts b/paper/paper-core/build.gradle.kts new file mode 100644 index 0000000..0ce70fe --- /dev/null +++ b/paper/paper-core/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("spectatorplus.platform") + id("io.github.goooler.shadow") version "8.1.7" + id("xyz.jpenilla.run-paper") version "2.3.1" +} + +description = "Paper server-side companion for the SpectatorPlus mod" + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:${property("paper_version")}") + + implementation("xyz.jpenilla:reflection-remapper:${property("reflection_remapper_version")}") +} + +tasks { + processResources { + inputs.property("version", project.version) + filesMatching("paper-plugin.yml") { + expand( + mapOf( + "version" to project.version, + "description" to project.description, + ) + ) + } + } + + shadowJar { + enabled = false + } + + jar { + enabled = true + archiveClassifier.set("") + from("../LICENSE") + } + + runServer { + minecraftVersion("1.21.11") + } + + named("build") { + dependsOn(shadowJar) + } +} diff --git a/paper/paper-core/gradle.properties b/paper/paper-core/gradle.properties new file mode 100644 index 0000000..67ca6fa --- /dev/null +++ b/paper/paper-core/gradle.properties @@ -0,0 +1,2 @@ +paper_version=1.21.11-R0.1-SNAPSHOT +reflection_remapper_version=0.1.3 \ No newline at end of file diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorPlugin.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorPlugin.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorPlugin.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorPlugin.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorWorkarounds.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorWorkarounds.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorWorkarounds.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/SpectatorWorkarounds.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/config/ServerConfig.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/config/ServerConfig.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/config/ServerConfig.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/config/ServerConfig.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/effect/EffectType.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/effect/SyncedEffect.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ClientboundSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ClientboundSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ClientboundSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ClientboundSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerSyncController.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerboundSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerboundSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerboundSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/ServerboundSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/SyncPackets.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/EffectsSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/ExperienceSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/ExperienceSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/ExperienceSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/ExperienceSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/FoodSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/FoodSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/FoodSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/FoodSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/InventorySyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/MapSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/MapSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/MapSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/MapSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/SelectedSlotSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/SelectedSlotSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/SelectedSlotSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/SelectedSlotSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/CraftingSyncedContainer.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/CraftingSyncedContainer.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/CraftingSyncedContainer.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/CraftingSyncedContainer.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/DirectSyncedContainer.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/DirectSyncedContainer.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/DirectSyncedContainer.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/DirectSyncedContainer.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ReplicaSyncedContainer.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/ScreenSyncHandler.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedContainer.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedContainer.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedContainer.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedContainer.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedPlayerInventory.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedPlayerInventory.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedPlayerInventory.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedPlayerInventory.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedScreen.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedScreen.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedScreen.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/handler/screen/SyncedScreen.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundEffectsSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundExperienceSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundExperienceSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundExperienceSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundExperienceSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundFoodSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundFoodSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundFoodSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundFoodSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundHotbarSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundHotbarSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundHotbarSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundHotbarSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundInventorySyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenCursorSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenCursorSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenCursorSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenCursorSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundScreenSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundSelectedSlotSyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundSelectedSlotSyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundSelectedSlotSyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ClientboundSelectedSlotSyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundOpenedInventorySyncPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundOpenedInventorySyncPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundOpenedInventorySyncPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundOpenedInventorySyncPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundRequestInventoryOpenPacket.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundRequestInventoryOpenPacket.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundRequestInventoryOpenPacket.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/sync/packet/ServerboundRequestInventoryOpenPacket.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/util/ReflectionUtil.java diff --git a/paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java b/paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java similarity index 100% rename from paper/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java rename to paper/paper-core/src/main/java/com/hpfxd/spectatorplus/paper/util/SerializationUtil.java diff --git a/paper/src/main/resources/config.yml b/paper/paper-core/src/main/resources/config.yml similarity index 100% rename from paper/src/main/resources/config.yml rename to paper/paper-core/src/main/resources/config.yml diff --git a/paper/src/main/resources/paper-plugin.yml b/paper/paper-core/src/main/resources/paper-plugin.yml similarity index 100% rename from paper/src/main/resources/paper-plugin.yml rename to paper/paper-core/src/main/resources/paper-plugin.yml diff --git a/settings.gradle.kts b/settings.gradle.kts index ad01527..a1b9663 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,12 +13,13 @@ plugins { rootProject.name = "spectatorplus" includeBuild("build-logic") -setupSubproject("paper") -setupSubproject("fabric") -fun setupSubproject(moduleName: String) { - val name = "spectatorplus-$moduleName" - include(name) - val proj = project(":$name") - proj.projectDir = file(moduleName) -} +include("fabric") +include("fabric:fabric-core") +include("fabric:fabric-1.21.10") +include("fabric:fabric-1.21.11") + +include("paper") +include("paper:paper-core") +include("paper:paper-1.21.10") +include("paper:paper-1.21.11") From d092eec6160a4c3e548e09a0b97b4a6460660193 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:43:45 +0800 Subject: [PATCH 54/57] chore: update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4e1068b..fedc3c8 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ bin/ ### Mac OS ### .DS_Store +/SpectatorPlus_Structure_old.md +/SpectatorPlus_Structure.md From 945b1938450c07631b0eb8b0d878b09c4218156e Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:33:53 +0800 Subject: [PATCH 55/57] feat: add CI and release workflows for automated building and versioning --- .github/workflows/build.yml | 79 +++++++++++++++++++ .github/workflows/release.yml | 140 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 29 +++++++ gradle.properties | 2 +- 4 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1ed330e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,79 @@ +name: CI Build + +on: + push: + branches: + - master + paths-ignore: + - '**.md' + - '.gitignore' + - 'LICENSE' + - 'img/**' + pull_request: + branches: + - master + paths-ignore: + - '**.md' + - '.gitignore' + - 'LICENSE' + - 'img/**' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build --no-daemon --stacktrace + + - name: Test + run: ./gradlew test --no-daemon --stacktrace || true + + - name: List build artifacts + run: | + echo "=== Fabric artifacts ===" + ls -lh fabric/build/libs/ 2>/dev/null || echo "No Fabric artifacts found" + echo "" + echo "=== Paper artifacts ===" + ls -lh paper/build/libs/ 2>/dev/null || echo "No Paper artifacts found" + + - name: Upload Fabric artifacts + uses: actions/upload-artifact@v4 + if: success() + with: + name: fabric-builds + path: | + fabric/build/libs/*.jar + !fabric/build/libs/*-dev.jar + !fabric/build/libs/*-sources.jar + retention-days: 7 + + - name: Upload Paper artifacts + uses: actions/upload-artifact@v4 + if: success() + with: + name: paper-builds + path: | + paper/build/libs/*.jar + !paper/build/libs/*-dev.jar + !paper/build/libs/*-sources.jar + retention-days: 7 + + - name: Check build reports + if: failure() + run: | + echo "Build failed. Checking for error reports..." + find . -name "*.log" -type f 2>/dev/null | head -20 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a716547 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,140 @@ +name: Build and Release + +on: + push: + tags: + - 'v*.*.*' + branches: + - master + workflow_dispatch: + inputs: + version: + description: 'Version number (e.g., 1.2.3)' + required: false + type: string + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Extract version from tag or input + id: version + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ -n "${{ github.event.inputs.version }}" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION=${GITHUB_REF#refs/tags/v} + fi + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "Building version: $VERSION" + + - name: Update version in gradle.properties + run: | + sed -i "s/^version=.*/version=${{ steps.version.outputs.VERSION }}/" gradle.properties + cat gradle.properties + + - name: Build with Gradle + run: ./gradlew build --no-daemon --stacktrace + + - name: List build artifacts + run: | + echo "=== Fabric artifacts ===" + ls -lh fabric/build/libs/ || true + echo "=== Paper artifacts ===" + ls -lh paper/build/libs/ || true + + - name: Prepare release artifacts + id: artifacts + run: | + mkdir -p release-artifacts + + # Copy Fabric builds + if [ -d "fabric/build/libs" ]; then + find fabric/build/libs -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" -exec cp {} release-artifacts/ \; + fi + + # Copy Paper builds + if [ -d "paper/build/libs" ]; then + find paper/build/libs -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" -exec cp {} release-artifacts/ \; + fi + + echo "=== Release artifacts ===" + ls -lh release-artifacts/ + + # Generate artifact list for release notes + echo "ARTIFACTS<> $GITHUB_OUTPUT + ls -1 release-artifacts/ >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Generate changelog + id: changelog + run: | + if [ -f "CHANGELOG.md" ]; then + # Extract changelog for current version + sed -n "/^## \[*${{ steps.version.outputs.VERSION }}\]/,/^## \[/p" CHANGELOG.md | sed '$d' > current_changelog.txt + if [ -s current_changelog.txt ]; then + echo "CHANGELOG_CONTENT<> $GITHUB_OUTPUT + cat current_changelog.txt >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "CHANGELOG_CONTENT=No changelog available for this version." >> $GITHUB_OUTPUT + fi + else + echo "CHANGELOG_CONTENT=No changelog file found." >> $GITHUB_OUTPUT + fi + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', steps.version.outputs.VERSION) || github.ref_name }} + name: SpectatorPlus v${{ steps.version.outputs.VERSION }} + body: | + # SpectatorPlus v${{ steps.version.outputs.VERSION }} + + ## What's Changed + ${{ steps.changelog.outputs.CHANGELOG_CONTENT }} + + ## Downloads + + ### Fabric + - For Minecraft 1.21.10: `spectatorplus-fabric-${{ steps.version.outputs.VERSION }}-mc1.21.10.jar` + - For Minecraft 1.21.11: `spectatorplus-fabric-${{ steps.version.outputs.VERSION }}-mc1.21.11.jar` + + ### Paper + - For Paper servers: `spectatorplus-paper-${{ steps.version.outputs.VERSION }}.jar` + + --- + + **Full Changelog**: https://github.com/${{ github.repository }}/commits/v${{ steps.version.outputs.VERSION }} + files: release-artifacts/* + draft: false + prerelease: false + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: build-artifacts-${{ steps.version.outputs.VERSION }} + path: | + fabric/build/libs/*.jar + paper/build/libs/*.jar + !**/*-dev.jar + !**/*-sources.jar + retention-days: 30 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a46233f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.3.0 - 2026-02-07 + +### Added +- Added support for Minecraft 1.21.11. +- GitHub Actions workflows for automated building and releasing + - CI build workflow for continuous integration + - Release workflow for automatic GitHub releases +- Potion effect synchronization, allowing spectators to view the target player's potion effects. +- Container synchronization in spectator mode, displaying the container currently opened by the target player (e.g., chests, furnace, etc.). +- Cursor synchronization in spectator mode, displaying the hotbar slot and item currently selected by the target player (requires server-side installation and the target player to have the mod installed). +- Player inventory synchronization in spectator mode, displaying the target player's full inventory (requires server-side installation). +- Inventory screen state synchronization in spectator mode, reflecting when the target player opens or closes their inventory GUI (requires server-side installation and the target player to have the mod installed). + +### Fixed +- Fixed an issue with view rotate calculations in spectator mode that caused incorrect arm positioning. +- Fixed an issue where spectators would not correctly receive hotbar item updates from the target player. + +### Changed +- Upgraded the project structure to a multi-module architecture to support building for multiple Minecraft versions. + +### Chores +- Merged upstream changes regarding the update from 1.21.10 to 1.21.11. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index a5ac274..95e8604 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.hpfxd -version=1.2.3 +version=1.3.0 org.gradle.parallel=true org.gradle.caching=true From 0bf357b810ba9b3eea87bf37ac68fa5621266c91 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:39:49 +0800 Subject: [PATCH 56/57] fix: remove incorrect branch restriction from release workflow --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a716547..91778ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,8 +4,6 @@ on: push: tags: - 'v*.*.*' - branches: - - master workflow_dispatch: inputs: version: From d9d99f0aecf9bcdfcefe8f82b48c9cd4831ce2e5 Mon Sep 17 00:00:00 2001 From: RCUTANF <110910565+RCUTANF@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:58:23 +0800 Subject: [PATCH 57/57] fix: update artifact paths in build and release workflows --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/release.yml | 30 +++++++++++++++++------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1ed330e..c755166 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,10 +45,10 @@ jobs: - name: List build artifacts run: | echo "=== Fabric artifacts ===" - ls -lh fabric/build/libs/ 2>/dev/null || echo "No Fabric artifacts found" + find fabric/fabric-*/build/libs -name "*.jar" 2>/dev/null || echo "No Fabric artifacts found" echo "" echo "=== Paper artifacts ===" - ls -lh paper/build/libs/ 2>/dev/null || echo "No Paper artifacts found" + find paper/paper-*/build/libs -name "*.jar" 2>/dev/null || echo "No Paper artifacts found" - name: Upload Fabric artifacts uses: actions/upload-artifact@v4 @@ -56,9 +56,9 @@ jobs: with: name: fabric-builds path: | - fabric/build/libs/*.jar - !fabric/build/libs/*-dev.jar - !fabric/build/libs/*-sources.jar + fabric/fabric-*/build/libs/*.jar + !fabric/fabric-*/build/libs/*-dev.jar + !fabric/fabric-*/build/libs/*-sources.jar retention-days: 7 - name: Upload Paper artifacts @@ -67,9 +67,9 @@ jobs: with: name: paper-builds path: | - paper/build/libs/*.jar - !paper/build/libs/*-dev.jar - !paper/build/libs/*-sources.jar + paper/paper-*/build/libs/*.jar + !paper/paper-*/build/libs/*-dev.jar + !paper/paper-*/build/libs/*-sources.jar retention-days: 7 - name: Check build reports diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91778ab..c33ccbd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,31 +51,35 @@ jobs: - name: List build artifacts run: | echo "=== Fabric artifacts ===" - ls -lh fabric/build/libs/ || true + find fabric/fabric-*/build/libs -name "*.jar" 2>/dev/null || echo "No Fabric artifacts found" + echo "" echo "=== Paper artifacts ===" - ls -lh paper/build/libs/ || true + find paper/paper-*/build/libs -name "*.jar" 2>/dev/null || echo "No Paper artifacts found" - name: Prepare release artifacts id: artifacts run: | mkdir -p release-artifacts - # Copy Fabric builds - if [ -d "fabric/build/libs" ]; then - find fabric/build/libs -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" -exec cp {} release-artifacts/ \; - fi + # Copy Fabric builds from subprojects + find fabric/fabric-*/build/libs -type f -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" 2>/dev/null | while read jar; do + cp "$jar" release-artifacts/ + echo "Copied: $(basename $jar)" + done - # Copy Paper builds - if [ -d "paper/build/libs" ]; then - find paper/build/libs -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" -exec cp {} release-artifacts/ \; - fi + # Copy Paper builds from subprojects + find paper/paper-*/build/libs -type f -name "*.jar" -not -name "*-dev.jar" -not -name "*-sources.jar" 2>/dev/null | while read jar; do + cp "$jar" release-artifacts/ + echo "Copied: $(basename $jar)" + done + echo "" echo "=== Release artifacts ===" - ls -lh release-artifacts/ + ls -lh release-artifacts/ || echo "No artifacts prepared!" # Generate artifact list for release notes echo "ARTIFACTS<> $GITHUB_OUTPUT - ls -1 release-artifacts/ >> $GITHUB_OUTPUT + ls -1 release-artifacts/ 2>/dev/null >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Generate changelog @@ -114,7 +118,7 @@ jobs: ### Paper - For Paper servers: `spectatorplus-paper-${{ steps.version.outputs.VERSION }}.jar` - + --- **Full Changelog**: https://github.com/${{ github.repository }}/commits/v${{ steps.version.outputs.VERSION }}