Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fabric/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id("fabric-loom") version "1.9-SNAPSHOT"
id("fabric-loom") version "1.10.1"
id("spectatorplus.platform")
}

Expand Down
16 changes: 8 additions & 8 deletions fabric/gradle.properties
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.minecraft.network.chat.MutableComponent;

import java.io.IOException;
import java.net.URI;

public class ClothConfigIntegration {
private ClothConfigIntegration() {
Expand Down Expand Up @@ -131,7 +132,7 @@ private static void setupServerConfig(ConfigBuilder builder) {
.build());

category.addEntry(entryBuilder.startBooleanToggle(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.name")
.withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://bugs.mojang.com/browse/MC-148993"))), config.autoUpdatePosition)
.withStyle(style -> style.withClickEvent(new ClickEvent.OpenUrl(URI.create("https://bugs.mojang.com/browse/MC-148993")))), config.autoUpdatePosition)
.setTooltip(Component.translatable("gui.spectatorplus.config.server.autoUpdatePosition.tooltip", Component.literal("MC-148993").withStyle(ChatFormatting.BLUE, ChatFormatting.UNDERLINE)))
.setSaveConsumer(val -> config.autoUpdatePosition = val)
.setDefaultValue(defaults.autoUpdatePosition)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;"))
Expand All @@ -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<ItemStack> spectatorplus$showSyncedItems(NonNullList<ItemStack> 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"))
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"GameRendererAccessor",
"GameRendererMixin",
"GuiMixin",
"InventoryAccessor",
"ItemInHandRendererAccessor",
"ItemInHandRendererMixin",
"LevelRendererAccessor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.hpfxd.spectatorplus.fabric.sync;

import io.netty.handler.codec.EncoderException;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.world.item.ItemStack;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public final class CustomPacketCodecs {
private CustomPacketCodecs() {
}
Expand Down Expand Up @@ -35,10 +43,42 @@ public static void writeItems(RegistryFriendlyByteBuf buf, ItemStack[] items) {
}

public static ItemStack readItem(RegistryFriendlyByteBuf buf) {
return ItemStack.OPTIONAL_STREAM_CODEC.decode(buf);
final int len = buf.readInt();
if (len == 0) {
return ItemStack.EMPTY;
}

try {
final byte[] in = new byte[len];
buf.readBytes(in);

final CompoundTag tag = NbtIo.readCompressed(new ByteArrayInputStream(in), NbtAccounter.unlimitedHeap());
return ItemStack.parse(buf.registryAccess(), tag).orElse(ItemStack.EMPTY);
} catch (IOException e) {
throw new EncoderException(e);
}
}

public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) {
ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item);
if (item.isEmpty()) {
buf.writeInt(0);
return;
}

final byte[] bytes;
try {
final CompoundTag tag = new CompoundTag();
item.save(buf.registryAccess(), tag);

final ByteArrayOutputStream out = new ByteArrayOutputStream();
NbtIo.writeCompressed(tag, out);

bytes = out.toByteArray();
} catch (IOException e) {
throw new EncoderException(e);
}

buf.writeInt(bytes.length);
buf.writeBytes(bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public record ClientboundSelectedSlotSyncPacket(
public static final CustomPacketPayload.Type<ClientboundSelectedSlotSyncPacket> 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) {
Expand Down
6 changes: 3 additions & 3 deletions fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}