Skip to content

Commit

Permalink
Fixes #37 (#40)
Browse files Browse the repository at this point in the history
* Update dependencies

* Fix #37

* Add tests for fix

* Fix desync in non-hotbar guis & hotbar guis syncing after closing
  • Loading branch information
senseiwells authored Sep 24, 2024
1 parent 1553516 commit 67d2119
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 35 deletions.
8 changes: 4 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/eu/pb4/sgui/api/gui/HotbarGui.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/eu/pb4/sgui/api/gui/SlotGuiInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public interface SlotGuiInterface extends SlotHolder, GuiInterface {
int getSize();

boolean getLockPlayerInventory();

void setLockPlayerInventory(boolean value);

/**
Expand Down Expand Up @@ -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()) {
Expand Down
61 changes: 31 additions & 30 deletions src/main/java/eu/pb4/sgui/mixin/ServerPlayNetworkHandlerMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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();
Expand Down Expand Up @@ -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());
Expand All @@ -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)));
Expand All @@ -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);

Expand All @@ -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();
}
}
Expand All @@ -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();
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/eu/pb4/sgui/virtual/hotbar/HotbarScreenHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down
60 changes: 59 additions & 1 deletion src/testmod/java/eu/pb4/sgui/testmod/SGuiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -648,6 +649,57 @@ private static int test11(CommandContext<ServerCommandSource> objectCommandConte
return 0;
}

private static int test12(CommandContext<ServerCommandSource> 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<ServerCommandSource> 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<ServerCommandSource> objectCommandContext) {
try {
ServerPlayerEntity player = objectCommandContext.getSource().getPlayer();
Expand All @@ -660,7 +712,7 @@ private static int snake(CommandContext<ServerCommandSource> objectCommandContex
}


public void onInitialize() {
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(
literal("test").executes(SGuiTest::test)
Expand Down Expand Up @@ -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)
);
Expand Down

0 comments on commit 67d2119

Please sign in to comment.