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..276d71fb5e 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -25,13 +25,17 @@ 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; 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; +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; @@ -359,10 +363,23 @@ 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 + * 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, @@ -395,6 +412,7 @@ private static void handleCreativeBundleHover( } slot.set(result.carried()); screen.getMenu().broadcastChanges(); + ClientEventListener.playTerminalSound(carried, true); }) ); } else { @@ -409,11 +427,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; @@ -423,7 +461,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(); 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 c74b29ac22..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; @@ -184,6 +212,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; @@ -740,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, @@ -792,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 宫格、③④ 结果槽、切石机配方选择。 */ @@ -1371,6 +1393,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 +1518,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 +1560,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 +1603,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 +1765,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 +1843,10 @@ private void moveSameToStorage(int slot) { } private void undoLastMove() { + StorageClientStub.undo(this.sourcePos).whenCompleteAsync( (result, error) -> { + if (error != null || !result.changed()) { return; } @@ -1865,6 +1906,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; @@ -1985,6 +2032,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); } @@ -2010,6 +2063,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); } @@ -2035,6 +2094,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); } @@ -2098,6 +2163,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); } @@ -2165,6 +2236,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); } @@ -2191,7 +2268,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( @@ -2221,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; } @@ -2356,6 +2425,8 @@ protected boolean checkHotbarKeyPressed(int keyCode, int scanCode) { @Override public void removed() { + this.closed = true; + this.reorderRequest++; this.syncRequest++; this.metadataPending = false; @@ -2364,42 +2435,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} 的对应实现——那些实现会通过 @@ -2609,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) -> { @@ -2650,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 @@ -2679,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); @@ -2736,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 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/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 ac4a7338b9..25e3fb763b 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, @@ -2079,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); @@ -2095,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); @@ -2484,7 +2502,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(); } @@ -2932,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;