diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index 5db5b9da3..f683c6268 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -4,6 +4,7 @@ package dan200.computercraft.shared.peripheral.generic.methods; +import com.google.common.collect.Iterables; import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; @@ -13,9 +14,10 @@ import dan200.computercraft.shared.platform.FabricContainerTransfer; import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage; +import net.fabricmc.fabric.api.transfer.v1.storage.Storage; +import net.fabricmc.fabric.api.transfer.v1.storage.StorageView; import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedSlottedStorage; import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage; -import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.item.ItemStack; @@ -41,9 +43,16 @@ public final class InventoryMethods extends AbstractInventoryMethods storage) { + public static final class StorageWrapper { + private final Storage storage; + private final @Nullable SlottedStorage slottedStorage; + + public StorageWrapper(Storage storage) { + this.storage = storage; + this.slottedStorage = storage instanceof SlottedStorage slotted ? slotted : null; + } + @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -72,22 +81,33 @@ public final class InventoryMethods extends AbstractInventoryMethods cs ? cs.parts.hashCode() : storage.hashCode(); } + + private int size() { + return slottedStorage != null ? slottedStorage.getSlotCount() : Iterables.size(storage); + } + + private StorageView getSlot(int slot) { + if (slottedStorage != null) return slottedStorage.getSlot(slot); + + var singleSlot = Iterables.get(storage, slot); + if (singleSlot == null) throw new IndexOutOfBoundsException(); + return singleSlot; + } } @Override @LuaFunction(mainThread = true) public int size(StorageWrapper inventory) { - return inventory.storage().getSlots().size(); + return inventory.size(); } @Override @LuaFunction(mainThread = true) public Map> list(StorageWrapper inventory) { Map> result = new HashMap<>(); - var slots = inventory.storage().getSlots(); - var size = slots.size(); - for (var i = 0; i < size; i++) { - var stack = toStack(slots.get(i)); + var i = 0; + for (var slots = inventory.storage.iterator(); slots.hasNext(); i++) { + var stack = toStack(slots.next()); if (!stack.isEmpty()) result.put(i + 1, VanillaDetailRegistries.ITEM_STACK.getBasicDetails(stack)); } @@ -98,17 +118,17 @@ public final class InventoryMethods extends AbstractInventoryMethods getItemDetail(StorageWrapper inventory, int slot) throws LuaException { - assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)"); + assertBetween(slot, 1, inventory.size(), "Slot out of range (%s)"); - var stack = toStack(inventory.storage().getSlot(slot - 1)); + var stack = toStack(inventory.getSlot(slot - 1)); return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack); } @Override @LuaFunction(mainThread = true) public long getItemLimit(StorageWrapper inventory, int slot) throws LuaException { - assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)"); - return inventory.storage().getSlot(slot - 1).getCapacity(); + assertBetween(slot, 1, inventory.size(), "Slot out of range (%s)"); + return inventory.getSlot(slot - 1).getCapacity(); } @Override @@ -124,15 +144,7 @@ public final class InventoryMethods extends AbstractInventoryMethods toSlot, Optional limit) throws LuaException { + assertBetween(fromSlot, 1, from.size(), "From slot out of range (%s)"); + var fromStorage = from.getSlot(fromSlot - 1); + + Storage toStorage; + if (toSlot.isPresent()) { + if (to.slottedStorage == null) throw new LuaException("Cannot specify toSlot for this inventory"); + assertBetween(toSlot.get(), 1, to.size(), "To slot out of range (%s)"); + toStorage = to.slottedStorage.getSlot(toSlot.get() - 1); + } else { + toStorage = to.storage; + } + + int actualLimit = limit.orElse(Integer.MAX_VALUE); if (actualLimit <= 0) return 0; - return moveItem(from, fromSlot - 1, toStorage, toSlot.orElse(0) - 1, actualLimit); + + return Math.max(0, FabricContainerTransfer.move(fromStorage, toStorage, actualLimit)); } public static @Nullable StorageWrapper extractContainer(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) { @@ -165,51 +188,29 @@ public final class InventoryMethods extends AbstractInventoryMethods extractContainerImpl(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) { + private static @Nullable Storage extractContainerImpl(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) { var internal = ItemStorage.SIDED.find(level, pos, state, blockEntity, null); - if (internal instanceof SlottedStorage storage) return storage; + if (internal != null) return internal; - if (direction != null) { - var external = ItemStorage.SIDED.find(level, pos, state, blockEntity, direction); - if (external instanceof SlottedStorage storage) return storage; - } - - return null; + return direction != null ? ItemStorage.SIDED.find(level, pos, state, blockEntity, direction) : null; } @Nullable - private static SlottedStorage extractHandler(IPeripheral peripheral) { + private static StorageWrapper extractHandler(IPeripheral peripheral) { var object = peripheral.getTarget(); var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null; if (object instanceof BlockEntity blockEntity) { if (blockEntity.isRemoved()) return null; - var found = extractContainerImpl(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction); + var found = extractContainer(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction); if (found != null) return found; } return null; } - /** - * Move an item from one handler to another. - * - * @param from The handler to move from. - * @param fromSlot The slot to move from. - * @param to The handler to move to. - * @param toSlot The slot to move to. Use any number < 0 to represent any slot. - * @param limit The max number to move. {@link Integer#MAX_VALUE} for no limit. - * @return The number of items moved. - */ - private static int moveItem(SlottedStorage from, int fromSlot, SlottedStorage to, int toSlot, final int limit) { - var fromWrapper = FabricContainerTransfer.of(from).singleSlot(fromSlot); - var toWrapper = FabricContainerTransfer.of(to); - - return Math.max(0, fromWrapper.moveTo(toSlot >= 0 ? toWrapper.singleSlot(toSlot) : toWrapper, limit)); - } - - private static ItemStack toStack(SingleSlotStorage variant) { + private static ItemStack toStack(StorageView variant) { if (variant.isResourceBlank() || variant.getAmount() <= 0) return ItemStack.EMPTY; return toStack(variant.getResource(), variant.getAmount()); } diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricContainerTransfer.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricContainerTransfer.java index 465eb3514..51d6bbb04 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricContainerTransfer.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/FabricContainerTransfer.java @@ -14,6 +14,7 @@ import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.function.Predicate; @SuppressWarnings("UnstableApiUsage") public class FabricContainerTransfer implements ContainerTransfer { @@ -31,30 +32,48 @@ public class FabricContainerTransfer implements ContainerTransfer { return new SlottedImpl(storage); } + /** + * Move an item from a slot into another storage. + * + * @param from The slot to move from. + * @param to The storage to move to. + * @param maxAmount The max amount to move. + * @return The number of items which were transferred, or one of {@link #NO_ITEMS} or {@link #NO_SPACE}. This will + * NEVER return 0. + * @see StorageUtil#move(Storage, Storage, Predicate, long, TransactionContext) + * @see #moveTo(ContainerTransfer, int) + */ + public static int move(StorageView from, Storage to, long maxAmount) { + var resource = from.getResource(); + if (resource.isBlank()) return NO_ITEMS; + + try (var transaction = Transaction.openOuter()) { + // Check how much can be extracted and inserted. + var maxExtracted = StorageUtil.simulateExtract(from, resource, maxAmount, transaction); + if (maxExtracted == 0) return NO_ITEMS; + + var accepted = to.insert(resource, maxExtracted, transaction); + if (accepted == 0) return NO_SPACE; + + // Extract or rollback. + if (from.extract(resource, accepted, transaction) == accepted) { + transaction.commit(); + return accepted > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) accepted; + } + + return NO_SPACE; + } + } + @Override public int moveTo(ContainerTransfer destination, int maxAmount) { var hasItem = false; - var destStorage = ((FabricContainerTransfer) destination).storage; for (var slot : storage.nonEmptyViews()) { - var resource = slot.getResource(); + var result = move(slot, destStorage, maxAmount); + if (result >= 0) return result; - try (var transaction = Transaction.openOuter()) { - // Check how much can be extracted and inserted. - var maxExtracted = StorageUtil.simulateExtract(slot, resource, maxAmount, transaction); - if (maxExtracted == 0) continue; - - hasItem = true; - - var accepted = destStorage.insert(resource, maxExtracted, transaction); - if (accepted == 0) continue; - - // Extract or rollback. - if (slot.extract(resource, accepted, transaction) == accepted) { - transaction.commit(); - return (int) accepted; - } - } + if (result == NO_SPACE) hasItem = true; } return hasItem ? NO_SPACE : NO_ITEMS;