diff --git a/gradle.properties b/gradle.properties index 7a0ff20..0c8c518 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,12 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/versions.html -minecraft_version=1.21-pre2 -yarn_mappings=1.21-pre2+build.2 -loader_version=0.15.11 +minecraft_version=1.21.1 +yarn_mappings=1.21.1+build.3 +loader_version=0.16.5 # Fabric API -fabric_version=0.99.4+1.21 +fabric_version=0.105.0+1.21.1 # Mod Properties diff --git a/src/main/java/eu/pb4/sgui/api/gui/HotbarGui.java b/src/main/java/eu/pb4/sgui/api/gui/HotbarGui.java index 5e2375e..dd09e06 100644 --- a/src/main/java/eu/pb4/sgui/api/gui/HotbarGui.java +++ b/src/main/java/eu/pb4/sgui/api/gui/HotbarGui.java @@ -107,6 +107,17 @@ public boolean click(int index, ClickType type, SlotActionType action) { return super.click(index, type, action); } + @Override + public int getHotbarSlotIndex(int slots, int index) { + // We add the offhand before the inventory, so we need to shift by -1 + return super.getHotbarSlotIndex(slots, index) - 1; + } + + @Override + public int getOffhandSlotIndex() { + return 9; + } + @Override public boolean open() { if (this.player.isDisconnected() || this.isOpen()) { diff --git a/src/main/java/eu/pb4/sgui/api/gui/SlotGuiInterface.java b/src/main/java/eu/pb4/sgui/api/gui/SlotGuiInterface.java index b271116..1aa2bfe 100644 --- a/src/main/java/eu/pb4/sgui/api/gui/SlotGuiInterface.java +++ b/src/main/java/eu/pb4/sgui/api/gui/SlotGuiInterface.java @@ -22,6 +22,7 @@ public interface SlotGuiInterface extends SlotHolder, GuiInterface { int getSize(); boolean getLockPlayerInventory(); + void setLockPlayerInventory(boolean value); /** @@ -64,6 +65,27 @@ default boolean onClick(int index, ClickType type, SlotActionType action, GuiEle return false; } + + /** + * Maps a hotbar index into a slot index. + * + * @param slots The number of slots in the screen handler. + * @param index The hotbar index, this should be [0-8] + * @return The mapped slot index + */ + default int getHotbarSlotIndex(int slots, int index) { + return slots + index - 9; + } + + /** + * Gets the offhand slot index + * + * @return The offhand slot index + */ + default int getOffhandSlotIndex() { + return -1; + } + @Nullable default Slot getSlotRedirectOrPlayer(int index) { if (index < this.getSize()) { diff --git a/src/main/java/eu/pb4/sgui/mixin/ServerPlayNetworkHandlerMixin.java b/src/main/java/eu/pb4/sgui/mixin/ServerPlayNetworkHandlerMixin.java index 3303d3d..c6f0c47 100644 --- a/src/main/java/eu/pb4/sgui/mixin/ServerPlayNetworkHandlerMixin.java +++ b/src/main/java/eu/pb4/sgui/mixin/ServerPlayNetworkHandlerMixin.java @@ -13,6 +13,7 @@ import eu.pb4.sgui.virtual.inventory.VirtualScreenHandler; import eu.pb4.sgui.virtual.merchant.VirtualMerchantScreenHandler; import io.netty.buffer.Unpooled; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.network.ClientConnection; import net.minecraft.network.PacketByteBuf; @@ -67,6 +68,10 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co if (ignore && !handler.getGui().getLockPlayerInventory() && (slot >= handler.getGui().getSize() || slot < 0 || handler.getGui().getSlotRedirect(slot) != null)) { if (type == ClickType.MOUSE_DOUBLE_CLICK || (type.isDragging && type.value == 2)) { GuiHelpers.sendPlayerScreenHandler(this.player); + } else if (type == ClickType.OFFHAND_SWAP) { + int index = handler.getGui().getOffhandSlotIndex(); + ItemStack updated = index >= 0 ? handler.getSlot(index).getStack() : ItemStack.EMPTY; + GuiHelpers.sendSlotUpdate(this.player, -2, PlayerInventory.OFF_HAND_SLOT, updated, handler.getRevision()); } return; @@ -81,8 +86,12 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co GuiHelpers.sendSlotUpdate(this.player, -1, -1, this.player.currentScreenHandler.getCursorStack(), handler.getRevision()); if (type.numKey) { - int x = type.value + handler.slots.size() - 10; - GuiHelpers.sendSlotUpdate(player, handler.syncId, x, handler.getSlot(x).getStack(), handler.nextRevision()); + int index = handler.getGui().getHotbarSlotIndex(handler.slots.size(), type.value - 1); + GuiHelpers.sendSlotUpdate(this.player, handler.syncId, index, handler.getSlot(index).getStack(), handler.nextRevision()); + } else if (type == ClickType.OFFHAND_SWAP) { + int index = handler.getGui().getOffhandSlotIndex(); + ItemStack updated = index >= 0 ? handler.getSlot(index).getStack() : ItemStack.EMPTY; + GuiHelpers.sendSlotUpdate(this.player, -2, PlayerInventory.OFF_HAND_SLOT, updated, handler.getRevision()); } else if (type == ClickType.MOUSE_DOUBLE_CLICK || type == ClickType.MOUSE_LEFT_SHIFT || type == ClickType.MOUSE_RIGHT_SHIFT || (type.isDragging && type.value == 2)) { GuiHelpers.sendPlayerScreenHandler(this.player); } @@ -121,8 +130,8 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co @Inject(method = "onCloseHandledScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V", shift = At.Shift.AFTER), cancellable = true) private void sgui$storeScreenHandler(CloseHandledScreenC2SPacket packet, CallbackInfo info) { if (this.player.currentScreenHandler instanceof VirtualScreenHandlerInterface handler) { - if (sgui$bookIgnoreClose && this.player.currentScreenHandler instanceof BookScreenHandler) { - sgui$bookIgnoreClose = false; + if (this.sgui$bookIgnoreClose && this.player.currentScreenHandler instanceof BookScreenHandler) { + this.sgui$bookIgnoreClose = false; info.cancel(); return; } @@ -177,7 +186,7 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co } } - @Inject(method = "onCraftRequest", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;updateLastActionTime()V", shift = At.Shift.BEFORE), cancellable = true) + @Inject(method = "onCraftRequest", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;updateLastActionTime()V", shift = At.Shift.BEFORE)) private void sgui$catchRecipeRequests(CraftRequestC2SPacket packet, CallbackInfo ci) { if (this.player.currentScreenHandler instanceof VirtualScreenHandler handler && handler.getGui() instanceof SimpleGui gui) { try { @@ -199,7 +208,7 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co ci.cancel(); } } catch (Throwable e) { - if (this.player.currentScreenHandler instanceof VirtualScreenHandlerInterface handler ) { + if (this.player.currentScreenHandler instanceof VirtualScreenHandlerInterface handler) { handler.getGui().handleException(e); } else { e.printStackTrace(); @@ -245,28 +254,22 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co @Inject(method = "onPlayerInteractItem", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V"), cancellable = true) private void sgui$clickWithItem(PlayerInteractItemC2SPacket packet, CallbackInfo ci) { - if (this.player.currentScreenHandler instanceof HotbarScreenHandler screenHandler) { - var gui = screenHandler.getGui(); - if (screenHandler.slotsOld != null) { - screenHandler.slotsOld.set(gui.getSelectedSlot() + 36, ItemStack.EMPTY); - screenHandler.slotsOld.set(45, ItemStack.EMPTY); - } + if (this.player.currentScreenHandler instanceof HotbarScreenHandler handler) { + var gui = handler.getGui(); gui.onClickItem(); + handler.syncSelectedSlot(); ci.cancel(); } } @Inject(method = "onPlayerInteractBlock", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V"), cancellable = true) private void sgui$clickOnBlock(PlayerInteractBlockC2SPacket packet, CallbackInfo ci) { - if (this.player.currentScreenHandler instanceof HotbarScreenHandler screenHandler) { - var gui = screenHandler.getGui(); + if (this.player.currentScreenHandler instanceof HotbarScreenHandler handler) { + var gui = handler.getGui(); if (!gui.onClickBlock(packet.getBlockHitResult())) { var pos = packet.getBlockHitResult().getBlockPos(); - if (screenHandler.slotsOld != null) { - screenHandler.slotsOld.set(gui.getSelectedSlot() + 36, ItemStack.EMPTY); - screenHandler.slotsOld.set(45, ItemStack.EMPTY); - } + handler.syncSelectedSlot(); this.sendPacket(new BlockUpdateS2CPacket(pos, this.player. getServerWorld().getBlockState(pos))); pos = pos.offset(packet.getBlockHitResult().getSide()); @@ -280,15 +283,16 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co @Inject(method = "onPlayerAction", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V"), cancellable = true) private void sgui$onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) { - if (this.player.currentScreenHandler instanceof HotbarScreenHandler screenHandler) { - var gui = screenHandler.getGui(); + if (this.player.currentScreenHandler instanceof HotbarScreenHandler handler) { + var gui = handler.getGui(); if (!gui.onPlayerAction(packet.getAction(), packet.getDirection())) { var pos = packet.getPos(); - if (screenHandler.slotsOld != null) { - screenHandler.slotsOld.set(gui.getSelectedSlot() + 36, ItemStack.EMPTY); - screenHandler.slotsOld.set(45, ItemStack.EMPTY); + handler.syncSelectedSlot(); + if (packet.getAction() == PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND) { + handler.syncOffhandSlot(); } + this.sendPacket(new BlockUpdateS2CPacket(pos, this.player.getServerWorld().getBlockState(pos))); pos = pos.offset(packet.getDirection()); this.sendPacket(new BlockUpdateS2CPacket(pos, this.player.getServerWorld().getBlockState(pos))); @@ -300,8 +304,8 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co @Inject(method = "onPlayerInteractEntity", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/server/world/ServerWorld;)V"), cancellable = true) private void sgui$clickOnEntity(PlayerInteractEntityC2SPacket packet, CallbackInfo ci) { - if (this.player.currentScreenHandler instanceof HotbarScreenHandler screenHandler) { - var gui = screenHandler.getGui(); + if (this.player.currentScreenHandler instanceof HotbarScreenHandler handler) { + var gui = handler.getGui(); var buf = new PacketByteBuf(Unpooled.buffer()); ((PlayerInteractEntityC2SPacketAccessor)packet).invokeWrite(buf); @@ -322,10 +326,7 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co var isSneaking = buf.readBoolean(); if (!gui.onClickEntity(entityId, type, isSneaking, interactionPos)) { - if (screenHandler.slotsOld != null) { - screenHandler.slotsOld.set(gui.getSelectedSlot() + 36, ItemStack.EMPTY); - screenHandler.slotsOld.set(45, ItemStack.EMPTY); - } + handler.syncSelectedSlot(); ci.cancel(); } } @@ -348,7 +349,7 @@ public ServerPlayNetworkHandlerMixin(MinecraftServer server, ClientConnection co private void sgui$onCommand(CommandExecutionC2SPacket packet, CallbackInfo ci) { if (this.player.currentScreenHandler instanceof BookScreenHandler handler) { try { - sgui$bookIgnoreClose = true; + this.sgui$bookIgnoreClose = true; if (handler.getGui().onCommand("/" + packet.command())) { ci.cancel(); } diff --git a/src/main/java/eu/pb4/sgui/virtual/hotbar/HotbarScreenHandler.java b/src/main/java/eu/pb4/sgui/virtual/hotbar/HotbarScreenHandler.java index 7881276..3ad042b 100644 --- a/src/main/java/eu/pb4/sgui/virtual/hotbar/HotbarScreenHandler.java +++ b/src/main/java/eu/pb4/sgui/virtual/hotbar/HotbarScreenHandler.java @@ -10,6 +10,7 @@ import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.slot.Slot; import net.minecraft.util.collection.DefaultedList; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; public class HotbarScreenHandler extends VirtualScreenHandler { @@ -76,4 +77,22 @@ public void sendContentUpdates() { this.getGui().handleException(e); } } + + @ApiStatus.Internal + public void syncSelectedSlot() { + var gui = this.getGui(); + if (gui.isOpen()) { + int index = gui.getHotbarSlotIndex(this.slots.size(), gui.getSelectedSlot()); + GuiHelpers.sendSlotUpdate(gui.getPlayer(), this.syncId, index, this.getSlot(index).getStack(), this.nextRevision()); + } + } + + @ApiStatus.Internal + public void syncOffhandSlot() { + var gui = this.getGui(); + if (gui.isOpen()) { + int index = gui.getOffhandSlotIndex(); + GuiHelpers.sendSlotUpdate(gui.getPlayer(), this.syncId, index, this.getSlot(index).getStack(), this.nextRevision()); + } + } } diff --git a/src/main/java/eu/pb4/sgui/virtual/inventory/VirtualScreenHandler.java b/src/main/java/eu/pb4/sgui/virtual/inventory/VirtualScreenHandler.java index 76d2b1c..947db4b 100644 --- a/src/main/java/eu/pb4/sgui/virtual/inventory/VirtualScreenHandler.java +++ b/src/main/java/eu/pb4/sgui/virtual/inventory/VirtualScreenHandler.java @@ -1,5 +1,6 @@ package eu.pb4.sgui.virtual.inventory; +import eu.pb4.sgui.api.GuiHelpers; import eu.pb4.sgui.api.gui.SlotGuiInterface; import eu.pb4.sgui.virtual.VirtualScreenHandlerInterface; import net.minecraft.entity.player.PlayerEntity; @@ -61,6 +62,15 @@ public void addListener(ScreenHandlerListener listener) { this.gui.afterOpen(); } + @Override + public void syncState() { + super.syncState(); + // We have to manually sync offhand state + int index = this.getGui().getOffhandSlotIndex(); + ItemStack updated = index >= 0 ? this.getSlot(index).getStack() : ItemStack.EMPTY; + GuiHelpers.sendSlotUpdate(this.gui.getPlayer(), -2, PlayerInventory.OFF_HAND_SLOT, updated, this.getRevision()); + } + @Override public SlotGuiInterface getGui() { return this.gui; diff --git a/src/testmod/java/eu/pb4/sgui/testmod/SGuiTest.java b/src/testmod/java/eu/pb4/sgui/testmod/SGuiTest.java index 9037009..98afd59 100644 --- a/src/testmod/java/eu/pb4/sgui/testmod/SGuiTest.java +++ b/src/testmod/java/eu/pb4/sgui/testmod/SGuiTest.java @@ -14,6 +14,7 @@ import net.minecraft.block.Blocks; import net.minecraft.component.DataComponentTypes; import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.WrittenBookItem; @@ -648,6 +649,57 @@ private static int test11(CommandContext objectCommandConte return 0; } + private static int test12(CommandContext objectCommandContext) { + try { + var player = objectCommandContext.getSource().getPlayerOrThrow(); + player.sendMessage( + Text.literal("Pickaxe should *only* be able to be swapped only to offhand, both in and out of inventory gui") + ); + + var hotbar = new HotbarGui(player); + var elements = new GuiElement[1]; + elements[0] = new GuiElement(new ItemStack(Items.GOLDEN_PICKAXE), (a, type, c, gui) -> { + if (type != ClickType.OFFHAND_SWAP) { + return; + } + var offhand = gui.getSlot(9); + if (offhand == null || offhand.getItemStack().isEmpty()) { + gui.setSlot(9, elements[0].getItemStack()); + elements[0].setItemStack(ItemStack.EMPTY); + } else if (elements[0].getItemStack().isEmpty()) { + elements[0].setItemStack(offhand.getItemStack()); + gui.setSlot(9, ItemStack.EMPTY); + } + }); + hotbar.setSlot(0, elements[0]); + hotbar.open(); + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + private static int test13(CommandContext objectCommandContext) { + try { + var player = objectCommandContext.getSource().getPlayerOrThrow(); + player.getInventory().setStack(PlayerInventory.OFF_HAND_SLOT, new ItemStack(Items.DIAMOND)); + + var stack = new ItemStack(Items.GOLDEN_PICKAXE); + stack.set( + DataComponentTypes.CUSTOM_NAME, + Text.literal("Can't swap to offhand") + ); + + var gui = new SimpleGui(ScreenHandlerType.GENERIC_9X3, player, true); + gui.setTitle(Text.literal("Offhand item should be invisible in gui")); + gui.setSlot(0, stack); + gui.open(); + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + private static int snake(CommandContext objectCommandContext) { try { ServerPlayerEntity player = objectCommandContext.getSource().getPlayer(); @@ -660,7 +712,7 @@ private static int snake(CommandContext objectCommandContex } - public void onInitialize() { + public void onInitialize() { CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { dispatcher.register( literal("test").executes(SGuiTest::test) @@ -695,6 +747,12 @@ public void onInitialize() { dispatcher.register( literal("test11").executes(SGuiTest::test11) ); + dispatcher.register( + literal("test12").executes(SGuiTest::test12) + ); + dispatcher.register( + literal("test13").executes(SGuiTest::test13) + ); dispatcher.register( literal("snake").executes(SGuiTest::snake) );