From ac9ef3b2f12cc43b4029cceeffa10bb52f584050 Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 11:10:34 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(storage):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=A7=BB=E9=80=9F=E6=8B=96=E6=8B=BD=E6=93=8D=E4=BD=9C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=92=8C=E9=98=B2=E6=AD=A2=E9=BC=A0=E6=A0=87=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E6=8F=92=E4=BB=B6=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在客户端事件监听中添加高优先级事件阻止鼠标增强插件劫持shift+左键拖拽快速移动 - StorageScreen中新增quickMoveDrag方法处理shift+左键拖拽快速移动操作逻辑 - 鼠标拖拽和释放事件中集成快速移动拖拽逻辑,支持批量操作并添加撤销分组管理 - StorageServerStub中推送撤销记录时复制传入的物品移动映射,避免并发修改问题 --- .../client/event/ClientEventListener.java | 20 ++++++++ .../client/gui/screen/StorageScreen.java | 49 +++++++++++++------ .../anvilcraft/rpc/StorageServerStub.java | 2 +- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 1ce31214dd..64db94165e 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -25,6 +25,7 @@ import dev.dubhe.anvilcraft.util.BlockHighlightUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; import net.minecraft.client.multiplayer.ClientLevel; @@ -32,6 +33,7 @@ import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; @@ -363,6 +365,24 @@ public static void onScreenMousePressedTerminal(ScreenEvent.MouseButtonPressed.P * vanilla 只做本地预测(不发包),服务端无法执行 TerminalItem 的存储操作, * 这里直接走 RPC:空槽+右键取出存储第一物品放槽,有物品+左键把槽内物品放入存储。 */ + /** + * Prevents Mouse Tweaks from intercepting shift+left-drag quick-move in StorageScreen. + * Mouse Tweaks listens on {@code ScreenEvent.MouseDragged.Pre} at default priority and issues + * a vanilla QUICK_MOVE for every hovered inventory slot. Handling the event at HIGHEST priority + * and cancelling it lets StorageScreen's own quick-move-to-storage logic take over. + */ + @SubscribeEvent(priority = EventPriority.HIGHEST) + public static void onScreenMouseDraggedStorage(ScreenEvent.MouseDragged.Pre event) { + if (!(event.getScreen() instanceof StorageScreen storageScreen)) { + return; + } + if (!storageScreen.isQuickMoveDragging() || event.getMouseButton() != 0 || !Screen.hasShiftDown()) { + return; + } + storageScreen.quickMoveDrag(event.getMouseX(), event.getMouseY()); + event.setCanceled(true); + } + private static void handleCreativeBundleHover( CreativeModeInventoryScreen creative, AbstractContainerScreen screen, diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java index c74b29ac22..45d4efc7aa 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -1371,6 +1371,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { this.interactWithStorage(slot, button, StorageInput.QUICK_MOVE_TO_STORAGE); } else if (this.carried.isEmpty()) { this.quickMoveDragging = true; + StorageClientStub.beginUndoGroup(this.sourcePos); this.queueQuickMove(slot); } else { @@ -1495,21 +1496,7 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double dra } if (this.quickMoveDragging) { if (button == 0 && Screen.hasShiftDown()) { - Integer storageSlot = this.getStorageSlot(mouseX, mouseY); - if (storageSlot != null) { - int key = -1 - storageSlot; - if (!this.quickMoveSlots.add(key)) { - this.quickMoveSlots.remove(key); - this.storageQuickMoveSlots.remove(storageSlot); - this.pendingQuickMoveSlots.remove(storageSlot); - } - this.queueQuickMove(key); - return true; - } - int inventorySlot = this.getInventorySlot(mouseX, mouseY); - if (inventorySlot != -1) { - this.queueQuickMove(inventorySlot); - } + this.quickMoveDrag(mouseX, mouseY); } return true; } @@ -1551,6 +1538,34 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double dra return true; } + /** + * Handles a shift+left-drag frame for quick-moving items into/out of storage. + * Called from {@code mouseDragged} and from the high-priority screen drag event + * listener so Mouse Tweaks never gets a chance to intercept the drag. + */ + public boolean isQuickMoveDragging() { + return this.quickMoveDragging; + } + + public void quickMoveDrag(double mouseX, double mouseY) { + Integer storageSlot = this.getStorageSlot(mouseX, mouseY); + if (storageSlot != null) { + int key = -1 - storageSlot; + if (this.quickMoveSlots.add(key)) { + this.storageQuickMoveSlots.add(storageSlot); + } else { + this.quickMoveSlots.remove(key); + this.storageQuickMoveSlots.remove(storageSlot); + this.pendingQuickMoveSlots.remove(storageSlot); + } + return; + } + int inventorySlot = this.getInventorySlot(mouseX, mouseY); + if (inventorySlot != -1) { + this.queueQuickMove(inventorySlot); + } + } + @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { this.dispatchMouseReleased(mouseX, mouseY, button); @@ -1566,6 +1581,7 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { this.quickMoveDragging = false; this.recordQuickMoveMovedFromSelection(); this.quickMoveSlots.clear(); + this.flushQuickMoves(); StorageClientStub.endUndoGroup(this.sourcePos); return true; @@ -1727,6 +1743,7 @@ private void flushQuickMoves() { IntList slots = new IntArrayList(this.pendingQuickMoveSlots); this.pendingQuickMoveSlots.clear(); if (!slots.isEmpty()) { + StorageClientStub.quickMoveToStorage(this.sourcePos, slots).whenCompleteAsync( (moved, error) -> { if (error != null) { @@ -1804,8 +1821,10 @@ private void moveSameToStorage(int slot) { } private void undoLastMove() { + StorageClientStub.undo(this.sourcePos).whenCompleteAsync( (result, error) -> { + if (error != null || !result.changed()) { return; } diff --git a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java index ac4a7338b9..0f79bc20eb 100644 --- a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java +++ b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java @@ -2484,7 +2484,7 @@ private static void recordUndo(StorageServerStub stub, Map m } private static void pushUndo(StorageServerStub stub, Map moved) { - stub.undoRecords.addFirst(new UndoRecord(moved)); + stub.undoRecords.addFirst(new UndoRecord(new HashMap<>(moved))); while (stub.undoRecords.size() > StorageServerStub.MAX_UNDO_RECORDS) { stub.undoRecords.removeLast(); } From c98af6a63fde126ee89f8bdcf8c5af15696bf307 Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 11:13:34 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix(client):=20=E4=BF=AE=E6=AD=A3=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E4=B8=AD=E7=9A=84=E9=93=BE=E6=8E=A5=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 去除了注释中 {@link AbstractContainerScreen#isHovering} 的花括号 - 保持了注释语义,避免依赖 hoveredSlot 渲染帧判断 - 优化代码注释可读性和一致性 --- .../dev/dubhe/anvilcraft/client/event/ClientEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 64db94165e..4eb530b865 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -443,7 +443,7 @@ private static boolean isBundleClick(int button, boolean inverted, boolean remov /** * 根据 GUI 坐标在容器菜单槽位中查找鼠标悬停的槽位,复刻 - * {@link AbstractContainerScreen#isHovering} 的判定,不依赖渲染帧的 hoveredSlot。 + * AbstractContainerScreen.isHovering 的判定,不依赖渲染帧的 hoveredSlot。 */ private static @Nullable Slot findSlotAt(AbstractContainerScreen screen, double mouseX, double mouseY) { double x = mouseX - screen.getGuiLeft(); From be7265e1287848758349d987f5b58749dfb6662a Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 11:41:56 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat(storage):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E7=95=8C=E9=9D=A2=E6=97=B6=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E6=8C=87=E9=92=88=E7=89=A9=E5=93=81=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 StorageClientStub 添加 returnCarriedToInventory 方法通过 RPC 调用服务端放回背包 - StorageScreen 增加 closed 标志位,在界面关闭后避免鼠标残留物品 - 多处接收服务端返回结果的地方检查 closed,关闭时调用 returnCarriedToInventory - removed 方法中设置 closed 并调用 returnCarriedToInventory 让服务端处理指针物品 - StorageServerStub 添加 returnCarriedToInventory 实现,服务端直接操作背包并广播变化 - 改写界面关闭清理逻辑,避免客户端在容器关闭后误操作被服务端忽略 --- .../client/gui/screen/StorageScreen.java | 84 ++++++++++++------- .../client/rpc/StorageClientStub.java | 9 ++ .../anvilcraft/rpc/StorageServerStub.java | 18 ++++ 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java index 45d4efc7aa..62bbf85b09 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -184,6 +184,7 @@ public class StorageScreen extends AbstractContainerScreen { private boolean metadataPending; private boolean interactionPending; private boolean interactionSyncPending; + private boolean closed; private boolean nbtFolded; private boolean preservingOrder; private boolean remappedOrder; @@ -1884,6 +1885,12 @@ private void interactWithStorage(int slot, int button, StorageInput action) { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { if (this.preservingOrder) { this.interactionSyncPending = true; @@ -2004,6 +2011,12 @@ private void interactWithCraftingSlot(int slot, int button) { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { this.loadCrafting(false); } @@ -2029,6 +2042,12 @@ private void pickupAllCraftingSlot(int slot) { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { this.loadCrafting(false); } @@ -2054,6 +2073,12 @@ private void pickupAllInputsIntoCarried() { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { this.loadCrafting(false); } @@ -2117,6 +2142,12 @@ private void quickCraftToCraftingSlots(int button) { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { this.loadCrafting(false); } @@ -2184,6 +2215,12 @@ private void takeAllChunk(int request, boolean stonecutter, int chunkIndex) { } this.carried = result.carried(); this.player.inventoryMenu.setCarried(this.carried); + if (this.closed) { + // 界面已关闭:把 RPC 返回的指针物品放回背包,避免鼠标上残留物品 + + this.returnCarriedToInventory(); + return; + } if (result.changed()) { this.loadCrafting(false); } @@ -2375,6 +2412,8 @@ protected boolean checkHotbarKeyPressed(int keyCode, int scanCode) { @Override public void removed() { + this.closed = true; + this.reorderRequest++; this.syncRequest++; this.metadataPending = false; @@ -2383,42 +2422,27 @@ public void removed() { if (this.tracksOpenState && this.minecraft.player != null) { StorageClientStub.setOpen(this.sourcePos, false); } - if (!this.carried.isEmpty() && this.minecraft.gameMode != null) { - this.player.inventoryMenu.setCarried(this.carried); - Inventory inventory = this.player.getInventory(); - while (!this.carried.isEmpty()) { - int slot = inventory.getSlotWithRemainingSpace(this.carried); - if (slot == -1) { - slot = inventory.getFreeSlot(); - } - if (slot == -1) { - this.minecraft.gameMode.handleInventoryMouseClick( - this.player.inventoryMenu.containerId, - -999, - 0, - ClickType.PICKUP, - this.player - ); - break; - } - - this.minecraft.gameMode.handleInventoryMouseClick( - this.player.inventoryMenu.containerId, - slot < 9 ? slot + 36 : slot, - 0, - ClickType.PICKUP, - this.player - ); - this.carried = this.player.inventoryMenu.getCarried(); - } - this.carried = ItemStack.EMPTY; - } + // 关闭界面时让服务端把指针物品放回背包 + this.returnCarriedToInventory(); if (SettingClientStub.setting().storage().getSearch() == SearchMode.CLEAR) { SettingClientStub.update(""); } super.removed(); } + /** + * 让服务端把指针物品放回玩家背包。关闭界面时服务端 {@code containerMenu} + * 仍是 {@code inventoryMenu},由 RPC 直接操作背包并广播,避免客户端 + * {@code handleInventoryMouseClick} 在容器关闭后被服务端忽略。 + */ + private void returnCarriedToInventory() { + if (this.minecraft == null || this.minecraft.player == null) { + return; + } + StorageClientStub.returnCarriedToInventory(this.sourcePos); + this.carried = this.player.inventoryMenu.getCarried(); + } + /** * 以下 dispatch 系列复刻 {@code Screen} 的默认输入分发(遍历子组件), * 刻意不调用 {@code AbstractContainerScreen} 的对应实现——那些实现会通过 diff --git a/src/main/java/dev/dubhe/anvilcraft/client/rpc/StorageClientStub.java b/src/main/java/dev/dubhe/anvilcraft/client/rpc/StorageClientStub.java index 3b275594f6..1cca1e7b13 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/rpc/StorageClientStub.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/rpc/StorageClientStub.java @@ -147,6 +147,15 @@ public static CompletableFuture undo(BlockPos s ); } + public static void returnCarriedToInventory(BlockPos sourcePos) { + RPC.call( + RpcTarget.server(), + StorageServerStub::returnCarriedToInventory, + StorageClientStub.playerId(), + sourcePos.asLong() + ); + } + public static void beginUndoGroup(BlockPos sourcePos) { RPC.call( RpcTarget.server(), diff --git a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java index 0f79bc20eb..5007cd741d 100644 --- a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java +++ b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java @@ -246,6 +246,24 @@ public static InteractionResult interact(UUID playerId, long sourcePos, int slot return new InteractionResult(carried, changed); } + @RemoteCallable(validator = StorageAccessValidator.class) + public static void returnCarriedToInventory(UUID playerId, long sourcePos) { + StorageView view = StorageServerStub.getView(StorageServerStub.getAndClear(), playerId, sourcePos); + ServerPlayer player = StorageServerStub.getServerPlayer(playerId); + ItemStack carried = player.containerMenu.getCarried(); + if (carried.isEmpty()) { + return; + } + if (player.getInventory().add(carried)) { + carried = ItemStack.EMPTY; + } + if (!carried.isEmpty()) { + player.drop(carried, false); + } + player.containerMenu.setCarried(carried); + player.containerMenu.broadcastChanges(); + } + @RemoteCallable(validator = StorageAccessValidator.class) public static boolean clonePut( UUID playerId, From 30c92f9202216efe8aa0bb5bb304fa39433a737c Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 12:48:07 +0800 Subject: [PATCH 4/6] =?UTF-8?q?refactor(sound):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=B9=B6=E7=BB=9F=E4=B8=80=E8=83=8C=E5=8C=85=E7=B1=BB=E7=89=A9?= =?UTF-8?q?=E5=93=81=E5=8F=8A=E7=BB=88=E7=AB=AF=E7=9A=84=E9=9F=B3=E6=95=88?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BundleLikeItem中新增静态方法playSound,改为在服务端播放广播音效,确保玩家能听到 - 修改原有播放移除/放入的音效方法,调用新playSound方法以统一处理 - HyperdimensionTerminalItem和ShulkerTerminalItem继承改写音效播放方法,使用对应的特殊音效 - 客户端事件监听(ClientEventListener)新增playTerminalSound方法,根据物品类型播放对应音效 - StorageServerStub中的terminalExtractFirst方法调整为按玩家配置排序提取物品,提升取物逻辑一致性与准确性 - 修正终端提取物品时对最大堆叠数的判断,防止超出物品最大堆叠限制 --- .../client/event/ClientEventListener.java | 23 ++++++++++ .../dubhe/anvilcraft/item/BundleLikeItem.java | 20 ++++++++- .../item/HyperdimensionTerminalItem.java | 12 ++++++ .../anvilcraft/item/ShulkerTerminalItem.java | 12 ++++++ .../anvilcraft/rpc/StorageServerStub.java | 43 +++++++++++-------- 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 4eb530b865..9c82f765c7 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -30,6 +30,8 @@ import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.neoforged.api.distmarker.Dist; @@ -415,6 +417,7 @@ private static void handleCreativeBundleHover( } slot.set(result.carried()); screen.getMenu().broadcastChanges(); + ClientEventListener.playTerminalSound(carried, true); }) ); } else { @@ -429,11 +432,31 @@ private static void handleCreativeBundleHover( } slot.set(remain); screen.getMenu().broadcastChanges(); + ClientEventListener.playTerminalSound(carried, false); }) ); } } + /** + * 按终端类型播放与生存模式一致的音效:超维→传送、潜影→潜影盒开/关、其他→收纳袋。 + */ + private static void playTerminalSound(ItemStack terminal, boolean remove) { + var player = Minecraft.getInstance().player; + if (player == null) { + return; + } + SoundEvent sound; + if (terminal.is(ModItems.HYPERDIMENSION_TERMINAL)) { + sound = SoundEvents.ENDERMAN_TELEPORT; + } else if (terminal.is(ModItems.SHULKER_TERMINAL)) { + sound = remove ? SoundEvents.SHULKER_BOX_OPEN : SoundEvents.SHULKER_BOX_CLOSE; + } else { + sound = remove ? SoundEvents.BUNDLE_REMOVE_ONE : SoundEvents.BUNDLE_INSERT; + } + player.playSound(sound, 0.8F, 0.8F + player.getRandom().nextFloat() * 0.4F); + } + /** BundleLike 按键判定:取出用右键(inverted 时左键),放入用左键(inverted 时右键)。 */ private static boolean isBundleClick(int button, boolean inverted, boolean remove) { boolean wantRemoveClick = inverted ? button == 0 : button == 1; diff --git a/src/main/java/dev/dubhe/anvilcraft/item/BundleLikeItem.java b/src/main/java/dev/dubhe/anvilcraft/item/BundleLikeItem.java index 78ac79e2c4..eb36327151 100644 --- a/src/main/java/dev/dubhe/anvilcraft/item/BundleLikeItem.java +++ b/src/main/java/dev/dubhe/anvilcraft/item/BundleLikeItem.java @@ -4,6 +4,7 @@ import lombok.Data; import lombok.RequiredArgsConstructor; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.SlotAccess; @@ -124,11 +125,26 @@ public boolean overrideOtherStackedOnMe( } protected void playRemoveOneSound(Entity entity) { - entity.playSound(SoundEvents.BUNDLE_REMOVE_ONE, 0.8F, 0.8F + entity.level().getRandom().nextFloat() * 0.4F); + this.playSound(entity, SoundEvents.BUNDLE_REMOVE_ONE); } protected void playInsertSound(Entity entity) { - entity.playSound(SoundEvents.BUNDLE_INSERT, 0.8F, 0.8F + entity.level().getRandom().nextFloat() * 0.4F); + this.playSound(entity, SoundEvents.BUNDLE_INSERT); + } + + /** + * 播放音效:服务端 {@code Player.playSound} 会排除玩家本人(听不到), + * 这里改用 {@code level.playSound(null, ...)} 广播给附近所有玩家(含操作者)。 + */ + protected static void playSound(Entity entity, SoundEvent sound) { + entity.level().playSound( + null, + entity, + sound, + entity.getSoundSource(), + 0.8F, + 0.8F + entity.level().getRandom().nextFloat() * 0.4F + ); } protected void broadcastChangesOnContainerMenu(Player player) { diff --git a/src/main/java/dev/dubhe/anvilcraft/item/HyperdimensionTerminalItem.java b/src/main/java/dev/dubhe/anvilcraft/item/HyperdimensionTerminalItem.java index 0e54318ddb..5b9427cbf1 100644 --- a/src/main/java/dev/dubhe/anvilcraft/item/HyperdimensionTerminalItem.java +++ b/src/main/java/dev/dubhe/anvilcraft/item/HyperdimensionTerminalItem.java @@ -9,9 +9,11 @@ import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; @@ -26,6 +28,16 @@ public HyperdimensionTerminalItem(Properties properties) { super(properties); } + @Override + protected void playRemoveOneSound(Entity entity) { + BundleLikeItem.playSound(entity, SoundEvents.ENDERMAN_TELEPORT); + } + + @Override + protected void playInsertSound(Entity entity) { + BundleLikeItem.playSound(entity, SoundEvents.ENDERMAN_TELEPORT); + } + @Override public InteractionResultHolder use(Level level, Player player, InteractionHand usedHand) { ItemStack stack = player.getItemInHand(usedHand); diff --git a/src/main/java/dev/dubhe/anvilcraft/item/ShulkerTerminalItem.java b/src/main/java/dev/dubhe/anvilcraft/item/ShulkerTerminalItem.java index b51dacebdc..899fd620b5 100644 --- a/src/main/java/dev/dubhe/anvilcraft/item/ShulkerTerminalItem.java +++ b/src/main/java/dev/dubhe/anvilcraft/item/ShulkerTerminalItem.java @@ -5,9 +5,11 @@ import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvents; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -22,6 +24,16 @@ public ShulkerTerminalItem(Properties properties) { super(properties); } + @Override + protected void playRemoveOneSound(Entity entity) { + BundleLikeItem.playSound(entity, SoundEvents.SHULKER_BOX_OPEN); + } + + @Override + protected void playInsertSound(Entity entity) { + BundleLikeItem.playSound(entity, SoundEvents.SHULKER_BOX_CLOSE); + } + @Override public InteractionResultHolder use(Level level, Player player, InteractionHand usedHand) { ItemStack stack = player.getItemInHand(usedHand); diff --git a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java index 5007cd741d..25e3fb763b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java +++ b/src/main/java/dev/dubhe/anvilcraft/rpc/StorageServerStub.java @@ -2097,7 +2097,7 @@ public static InteractionResult terminalExtractFirst( if (!StorageServerStub.terminalTargetReachable(player, targetId)) { return new InteractionResult(ItemStack.EMPTY, false); } - HolderLookup.Provider registries = StorageServerStub.getAndClear(); + HolderLookup.Provider registries = player.level().registryAccess(); StorageView view = new StorageView(StorageServerStub.terminalStorages(player, targetId), List.of()); if (view.size() <= 0) { return new InteractionResult(ItemStack.EMPTY, false); @@ -2113,7 +2113,7 @@ public static InteractionResult terminalExtractFirst( if (stackAmount <= 0) { continue; } - int take = (int) Math.min(amount, stackAmount); + int take = (int) Math.min(Math.min(amount, view.resource(index).getMaxStackSize()), stackAmount); int got = view.extract(index, take); if (got > 0) { extracted = view.resource(index).copyWithCount(got); @@ -2950,24 +2950,33 @@ public static int insertIntoTerminal(ServerPlayer player, UUID targetId, ItemSta } /** - * 从终端连接的目标存储取出一个物品(按存储顺序取第一个可取槽位)。 + * 从终端连接的目标存储取出一个物品,与创造模式 {@link #terminalExtractFirst} 一致, + * 按玩家绑定的存储界面排序(SortMode + OrderMode)取第一个可取槽位。 * 目标不可达或存储为空时返回空栈。 */ public static ItemStack extractFromTerminal(ServerPlayer player, UUID targetId, int amount) { - List> storages = StorageServerStub.terminalStorages(player, targetId); - for (BaseStorage storage : storages) { - UnlimitedItemStacksResourceHandler items = storage.getItems(); - for (int slot = 0; slot < items.size(); slot++) { - if (items.getAmountAsLong(slot) <= 0) { - continue; - } - int take = (int) Math.min(amount, items.getAmountAsLong(slot)); - ItemStack got = items.extractUnlimited(slot, take, false).toStack(); - if (!got.isEmpty()) { - player.getInventory().setChanged(); - player.containerMenu.broadcastChanges(); - return got; - } + HolderLookup.Provider registries = player.level().registryAccess(); + StorageView view = new StorageView(StorageServerStub.terminalStorages(player, targetId), List.of()); + if (view.size() <= 0) { + return ItemStack.EMPTY; + } + PlayerSetting setting = PlayerSettings.getSetting(registries, player.getGameProfile().getId()); + StorageSetting storage = setting.storage(); + SortOptions options = new SortOptions(storage.getSort(), storage.getOrder()); + IntList order = StorageServerStub.createOrder(view, options, "", setting.listed()); + for (int i = 0; i < order.size(); i++) { + int index = order.getInt(i); + long stackAmount = view.amount(index); + if (stackAmount <= 0) { + continue; + } + int take = (int) Math.min(Math.min(amount, view.resource(index).getMaxStackSize()), stackAmount); + int got = view.extract(index, take); + if (got > 0) { + ItemStack extracted = view.resource(index).copyWithCount(got); + player.getInventory().setChanged(); + player.containerMenu.broadcastChanges(); + return extracted; } } return ItemStack.EMPTY; From 483ea09ddd56444ecf00fa713a40579891a2a376 Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 13:03:28 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(gui):=20=E4=BF=AE=E5=A4=8D=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E6=9D=A1=E5=81=8F=E7=A7=BB=E5=92=8C=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E5=AE=BD=E5=BA=A6=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化CategoryList中滚动条偏移量的计算,改用scrollable对象获取偏移值 - 调整StorageScreen中切石机配方选择区域的宽度,增加6像素间距 - 提升滚动交互的准确性和界面元素的布局合理性 --- .../anvilcraft/client/gui/component/category/CategoryList.java | 3 +-- .../dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java index 7b4ba8a739..2fb166c570 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java @@ -192,8 +192,7 @@ private void renderScrollbar(GuiGraphics graphics) { if (this.canScroll()) { int top = this.getY(); int bottom = top + this.getHeight(); - int scrollable = Math.max(0, this.size() - this.info.buttons()); - int offs = scrollable == 0 ? 0 : Math.round((float) (bottom - top - 10) * this.head / scrollable); + int offs = Math.round((float) (bottom - top - 10) * this.scrollable.getScrollOffs()); graphics.blit( CategoryList.SLIDER, this.getX() + 88, diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java index 62bbf85b09..b07da45dbe 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -2247,7 +2247,7 @@ public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, doubl // 悬停在切石机配方选择区:滚动配方列表 if (this.mode == ScreenMode.CRAFTING && !this.stonecutterRecipes.isEmpty()) { int recipeRight = this.leftPos + StorageScreen.CRAFTING_RECIPE_X - + StorageScreen.CRAFTING_RECIPE_COLUMNS * StorageScreen.CRAFTING_SLOT_SIZE; + + StorageScreen.CRAFTING_RECIPE_COLUMNS * StorageScreen.CRAFTING_SLOT_SIZE + 6; int recipeBottom = this.topPos + StorageScreen.CRAFTING_RECIPE_Y + StorageScreen.CRAFTING_RECIPE_ROWS * StorageScreen.CRAFTING_SLOT_SIZE; if (MathUtil.isInRange( From 368a83d837e26a2809b981f7f2d20bed8c163f93 Mon Sep 17 00:00:00 2001 From: Pigeon_Nian Date: Wed, 2 Sep 2026 13:27:25 +0800 Subject: [PATCH 6/6] =?UTF-8?q?refactor(storage):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=88=97=E8=A1=A8=E6=BB=9A=E5=8A=A8=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E7=9B=B8=E5=85=B3=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 Scrollable 接口抽象,实现存储列表滚动行为及偏移管理 - 修改滑动条渲染使用 Scrollable 提供的滚动偏移值 - 重构滑动条拖拽响应,改为通过 Scrollable 处理滚动位置 - 优化鼠标滚轮滚动行为,使用 Scrollable 判断并处理滚动 - 同步重排序和折叠操作时更新 Scrollable 的滚动状态 - 移除 ClientEventListener 中关于 INVENTORY 标签页 BUNDLE_HOVER_ITEM 的多余注释 --- .../client/event/ClientEventListener.java | 5 -- .../client/gui/screen/StorageScreen.java | 73 ++++++++++++------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 9c82f765c7..276d71fb5e 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -362,11 +362,6 @@ public static void onScreenMousePressedTerminal(ScreenEvent.MouseButtonPressed.P } } - /** - * 创造背包 INVENTORY 标签页的 BUNDLE_HOVER_ITEM(捏着终端点击背包槽): - * vanilla 只做本地预测(不发包),服务端无法执行 TerminalItem 的存储操作, - * 这里直接走 RPC:空槽+右键取出存储第一物品放槽,有物品+左键把槽内物品放入存储。 - */ /** * Prevents Mouse Tweaks from intercepting shift+left-drag quick-move in StorageScreen. * Mouse Tweaks listens on {@code ScreenEvent.MouseDragged.Pre} at default priority and issues diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java index b07da45dbe..f32f070f10 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -175,6 +175,34 @@ public class StorageScreen extends AbstractContainerScreen { private long version = -1; private long orderVersion = -1; private int scrollRow; + /** 存储列表滚动(与分类栏/配方滑条一致的连续 0..1 偏移)。 */ + private final Scrollable storageScrollable = new Scrollable() { + @Override + public int row() { + return StorageScreen.STORAGE_ROWS; + } + + @Override + public int column() { + return StorageScreen.STORAGE_COLUMNS; + } + + @Override + public int size() { + return StorageScreen.this.displayOrder.size(); + } + + @Override + public void setHead(int head) { + int next = Mth.clamp(head / StorageScreen.STORAGE_COLUMNS, 0, StorageScreen.this.getMaxScrollRow()); + if (next != StorageScreen.this.scrollRow) { + StorageScreen.this.scrollRow = next; + if (!StorageScreen.this.nbtFolded) { + StorageScreen.this.syncVisible(); + } + } + } + }; private boolean draggingSlider; private int reorderRequest; private int syncRequest; @@ -741,13 +769,10 @@ private void renderStorageContents(GuiGraphics graphics, int mouseX, int mouseY) } private void renderStorageSlider(GuiGraphics graphics) { - int maxScrollRow = this.getMaxScrollRow(); - int sliderOffset = maxScrollRow == 0 - ? 0 - : Math.round( - (StorageScreen.SLIDER_TRACK_HEIGHT - StorageScreen.SLIDER_HEIGHT) - * (float) this.scrollRow / maxScrollRow - ); + int sliderOffset = Math.round( + (StorageScreen.SLIDER_TRACK_HEIGHT - StorageScreen.SLIDER_HEIGHT) + * this.storageScrollable.getScrollOffs() + ); graphics.blit( StorageScreen.SLIDER, this.leftPos + StorageScreen.SLIDER_X, @@ -793,18 +818,14 @@ private boolean isOverRecipeSliderTrack(double mouseX, double mouseY) { ); } - /** 按鼠标纵坐标把滚动条定位到对应行,并刷新可视内容。 */ + /** 按鼠标纵坐标定位存储滚动(连续偏移),并刷新可视内容。 */ private void scrollSliderTo(double mouseY) { - float trackTop = (float) (this.topPos + StorageScreen.SLIDER_Y); - float usable = StorageScreen.SLIDER_TRACK_HEIGHT - StorageScreen.SLIDER_HEIGHT; - float fraction = Mth.clamp((float) (mouseY - trackTop - StorageScreen.SLIDER_HEIGHT / 2.0F) / usable, 0.0F, 1.0F); - int next = Math.round(fraction * this.getMaxScrollRow()); - if (next != this.scrollRow) { - this.scrollRow = next; - if (!this.nbtFolded) { - this.syncVisible(); - } - } + this.storageScrollable.scrollOnDrag( + StorageScreen.SLIDER_HEIGHT, + mouseY, + this.topPos + StorageScreen.SLIDER_Y, + this.topPos + StorageScreen.SLIDER_Y + StorageScreen.SLIDER_TRACK_HEIGHT + ); } /** 渲染合成面板:① 切石机输入、② 合成 9 宫格、③④ 结果槽、切石机配方选择。 */ @@ -2277,16 +2298,8 @@ public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, doubl return this.dispatchMouseScrolled(mouseX, mouseY, scrollX, scrollY); } - int nextScrollRow = Mth.clamp( - this.scrollRow + (scrollY > 0 ? -1 : 1), - 0, - this.getMaxScrollRow() - ); - if (nextScrollRow != this.scrollRow) { - this.scrollRow = nextScrollRow; - if (!this.nbtFolded) { - this.syncVisible(); - } + if (this.storageScrollable.canScroll()) { + this.storageScrollable.scrollOnScroll(scrollY / 1.2); } return true; } @@ -2652,6 +2665,7 @@ private void reorder(boolean resetScroll) { int request = ++this.reorderRequest; if (resetScroll) { this.scrollRow = 0; + this.storageScrollable.reset(); } StorageClientStub.reorder(this.sourcePos).whenCompleteAsync( (updatedOrder, error) -> { @@ -2693,6 +2707,7 @@ private void syncReordered(IntList reordered, int requestedScrollRow, int reorde this.resetServerSlots(reordered); this.rebuildDisplayOrder(foldNbt); this.scrollRow = Mth.clamp(reorderedScrollRow, 0, this.getMaxScrollRow()); + this.storageScrollable.calculateScroll(this.scrollRow); this.finishInteractionSync(); }, this.screenExecutor @@ -2722,6 +2737,7 @@ private void syncVisible() { if (foldNbt) { this.rebuildFoldedDisplay(true); this.scrollRow = Mth.clamp(this.scrollRow, 0, this.getMaxScrollRow()); + this.storageScrollable.calculateScroll(this.scrollRow); } } else { this.reorder(false); @@ -2779,6 +2795,7 @@ private void syncPreservedOrder(int attempt) { } this.orderVersion = this.version; this.scrollRow = Mth.clamp(this.scrollRow, 0, this.getMaxScrollRow()); + this.storageScrollable.calculateScroll(this.scrollRow); this.finishInteractionSync(); }, this.screenExecutor