|  |  |  | @@ -14,18 +14,15 @@ import dan200.computercraft.api.peripheral.IPeripheral; | 
		
	
		
			
				|  |  |  |  | import dan200.computercraft.api.peripheral.PeripheralType; | 
		
	
		
			
				|  |  |  |  | import dan200.computercraft.shared.peripheral.generic.SidedGenericPeripheral; | 
		
	
		
			
				|  |  |  |  | import dan200.computercraft.shared.platform.FabricContainerTransfer; | 
		
	
		
			
				|  |  |  |  | import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage; | 
		
	
		
			
				|  |  |  |  | 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.Storage; | 
		
	
		
			
				|  |  |  |  | import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage; | 
		
	
		
			
				|  |  |  |  | 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; | 
		
	
		
			
				|  |  |  |  | import net.minecraft.world.level.Level; | 
		
	
		
			
				|  |  |  |  | import net.minecraft.world.level.block.ChestBlock; | 
		
	
		
			
				|  |  |  |  | import net.minecraft.world.level.block.entity.BlockEntity; | 
		
	
		
			
				|  |  |  |  | import net.minecraft.world.level.block.entity.ChestBlockEntity; | 
		
	
		
			
				|  |  |  |  | import net.minecraft.world.level.block.state.BlockState; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | import javax.annotation.Nullable; | 
		
	
	
		
			
				
					
					|  |  |  | @@ -50,15 +47,25 @@ public class InventoryMethods implements GenericPeripheral { | 
		
	
		
			
				|  |  |  |  |         return ComputerCraftAPI.MOD_ID + ":inventory"; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static int size(InventoryStorage inventory) { | 
		
	
		
			
				|  |  |  |  |         return inventory.getSlots().size(); | 
		
	
		
			
				|  |  |  |  |     /** | 
		
	
		
			
				|  |  |  |  |      * Wrapper over a {@link SlottedStorage}. | 
		
	
		
			
				|  |  |  |  |      * <p> | 
		
	
		
			
				|  |  |  |  |      * The generic peripheral system doesn't (currently) support generics, and so we need put the inventory in a box. | 
		
	
		
			
				|  |  |  |  |      * | 
		
	
		
			
				|  |  |  |  |      * @param storage The underlying storage | 
		
	
		
			
				|  |  |  |  |      */ | 
		
	
		
			
				|  |  |  |  |     public record StorageWrapper(SlottedStorage<ItemVariant> storage) { | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static Map<Integer, Map<String, ?>> list(InventoryStorage inventory) { | 
		
	
		
			
				|  |  |  |  |     public static int size(StorageWrapper inventory) { | 
		
	
		
			
				|  |  |  |  |         return inventory.storage().getSlots().size(); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static Map<Integer, Map<String, ?>> list(StorageWrapper inventory) { | 
		
	
		
			
				|  |  |  |  |         Map<Integer, Map<String, ?>> result = new HashMap<>(); | 
		
	
		
			
				|  |  |  |  |         var slots = inventory.getSlots(); | 
		
	
		
			
				|  |  |  |  |         var slots = inventory.storage().getSlots(); | 
		
	
		
			
				|  |  |  |  |         var size = slots.size(); | 
		
	
		
			
				|  |  |  |  |         for (var i = 0; i < size; i++) { | 
		
	
		
			
				|  |  |  |  |             var stack = toStack(slots.get(i)); | 
		
	
	
		
			
				
					
					|  |  |  | @@ -70,22 +77,22 @@ public class InventoryMethods implements GenericPeripheral { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @Nullable | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static Map<String, ?> getItemDetail(InventoryStorage inventory, int slot) throws LuaException { | 
		
	
		
			
				|  |  |  |  |         assertBetween(slot, 1, inventory.getSlots().size(), "Slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |     public static Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException { | 
		
	
		
			
				|  |  |  |  |         assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var stack = toStack(inventory.getSlot(slot - 1)); | 
		
	
		
			
				|  |  |  |  |         var stack = toStack(inventory.storage().getSlot(slot - 1)); | 
		
	
		
			
				|  |  |  |  |         return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static long getItemLimit(InventoryStorage inventory, int slot) throws LuaException { | 
		
	
		
			
				|  |  |  |  |         assertBetween(slot, 1, inventory.getSlots().size(), "Slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |         return inventory.getSlot(slot - 1).getCapacity(); | 
		
	
		
			
				|  |  |  |  |     public static 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(); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static int pushItems( | 
		
	
		
			
				|  |  |  |  |         InventoryStorage from, IComputerAccess computer, | 
		
	
		
			
				|  |  |  |  |         StorageWrapper from, IComputerAccess computer, | 
		
	
		
			
				|  |  |  |  |         String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot | 
		
	
		
			
				|  |  |  |  |     ) throws LuaException { | 
		
	
		
			
				|  |  |  |  |         // Find location to transfer to | 
		
	
	
		
			
				
					
					|  |  |  | @@ -95,65 +102,68 @@ public class InventoryMethods implements GenericPeripheral { | 
		
	
		
			
				|  |  |  |  |         var to = extractHandler(location); | 
		
	
		
			
				|  |  |  |  |         if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var fromStorage = from.storage(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         // Validate slots | 
		
	
		
			
				|  |  |  |  |         int actualLimit = limit.orElse(Integer.MAX_VALUE); | 
		
	
		
			
				|  |  |  |  |         assertBetween(fromSlot, 1, from.getSlots().size(), "From slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |         assertBetween(fromSlot, 1, fromStorage.getSlotCount(), "From slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |         if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots().size(), "To slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (actualLimit <= 0) return 0; | 
		
	
		
			
				|  |  |  |  |         return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit); | 
		
	
		
			
				|  |  |  |  |         return moveItem(fromStorage, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @LuaFunction(mainThread = true) | 
		
	
		
			
				|  |  |  |  |     public static int pullItems( | 
		
	
		
			
				|  |  |  |  |         InventoryStorage to, IComputerAccess computer, | 
		
	
		
			
				|  |  |  |  |         StorageWrapper to, IComputerAccess computer, | 
		
	
		
			
				|  |  |  |  |         String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot | 
		
	
		
			
				|  |  |  |  |     ) throws LuaException { | 
		
	
		
			
				|  |  |  |  |         // Find location to transfer to | 
		
	
		
			
				|  |  |  |  |         var location = computer.getAvailablePeripheral(fromName); | 
		
	
		
			
				|  |  |  |  |         if (location == null) throw new LuaException("Source '" + fromName + "' does not exist"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var toStorage = to.storage(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var from = extractHandler(location); | 
		
	
		
			
				|  |  |  |  |         if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         // Validate slots | 
		
	
		
			
				|  |  |  |  |         int actualLimit = limit.orElse(Integer.MAX_VALUE); | 
		
	
		
			
				|  |  |  |  |         assertBetween(fromSlot, 1, from.getSlots().size(), "From slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |         if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots().size(), "To slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  |         if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, toStorage.getSlotCount(), "To slot out of range (%s)"); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (actualLimit <= 0) return 0; | 
		
	
		
			
				|  |  |  |  |         return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit); | 
		
	
		
			
				|  |  |  |  |         return moveItem(from, fromSlot - 1, toStorage, toSlot.orElse(0) - 1, actualLimit); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     public static @Nullable StorageWrapper extractContainer(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) { | 
		
	
		
			
				|  |  |  |  |         var storage = extractContainerImpl(level, pos, state, blockEntity, direction); | 
		
	
		
			
				|  |  |  |  |         return storage == null ? null : new StorageWrapper(storage); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @SuppressWarnings("NullAway") // FIXME: Doesn't cope with @Nullable type parameter. | 
		
	
		
			
				|  |  |  |  |     public static @Nullable Storage<ItemVariant> extractContainer(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) { | 
		
	
		
			
				|  |  |  |  |         // ItemStorage returns a CombinedStorage rather than an InventoryStorage for double chests. | 
		
	
		
			
				|  |  |  |  |         if (blockEntity instanceof ChestBlockEntity && state.getBlock() instanceof ChestBlock chestBlock) { | 
		
	
		
			
				|  |  |  |  |             var inventory = ChestBlock.getContainer(chestBlock, state, level, pos, true); | 
		
	
		
			
				|  |  |  |  |             return inventory == null ? null : InventoryStorage.of(inventory, null); | 
		
	
		
			
				|  |  |  |  |     private static @Nullable SlottedStorage<ItemVariant> 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<ItemVariant> storage) return storage; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (direction != null) { | 
		
	
		
			
				|  |  |  |  |             var external = ItemStorage.SIDED.find(level, pos, state, blockEntity, direction); | 
		
	
		
			
				|  |  |  |  |             if (external instanceof SlottedStorage<ItemVariant> storage) return storage; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var internal = ItemStorage.SIDED.find(level, pos, state, blockEntity, null); | 
		
	
		
			
				|  |  |  |  |         if (internal instanceof InventoryStorage || direction == null) return internal; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         var external = ItemStorage.SIDED.find(level, pos, state, blockEntity, direction); | 
		
	
		
			
				|  |  |  |  |         if (external instanceof InventoryStorage) return external; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         return internal != null ? internal : external; | 
		
	
		
			
				|  |  |  |  |         return null; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     @Nullable | 
		
	
		
			
				|  |  |  |  |     private static InventoryStorage extractHandler(IPeripheral peripheral) { | 
		
	
		
			
				|  |  |  |  |     private static SlottedStorage<ItemVariant> extractHandler(IPeripheral peripheral) { | 
		
	
		
			
				|  |  |  |  |         var object = peripheral.getTarget(); | 
		
	
		
			
				|  |  |  |  |         var direction = peripheral instanceof SidedGenericPeripheral sided ? sided.direction() : null; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (object instanceof BlockEntity blockEntity && blockEntity.isRemoved()) return null; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (object instanceof InventoryStorage storage) return storage; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if (object instanceof BlockEntity blockEntity) { | 
		
	
		
			
				|  |  |  |  |             var found = extractContainer(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction); | 
		
	
		
			
				|  |  |  |  |             if (found instanceof InventoryStorage storage) return storage; | 
		
	
		
			
				|  |  |  |  |             if (blockEntity.isRemoved()) return null; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |             var found = extractContainerImpl(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction); | 
		
	
		
			
				|  |  |  |  |             if (found != null) return found; | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         return null; | 
		
	
	
		
			
				
					
					|  |  |  | @@ -169,7 +179,7 @@ public class InventoryMethods implements GenericPeripheral { | 
		
	
		
			
				|  |  |  |  |      * @param limit    The max number to move. {@link Integer#MAX_VALUE} for no limit. | 
		
	
		
			
				|  |  |  |  |      * @return The number of items moved. | 
		
	
		
			
				|  |  |  |  |      */ | 
		
	
		
			
				|  |  |  |  |     private static int moveItem(InventoryStorage from, int fromSlot, InventoryStorage to, int toSlot, final int limit) { | 
		
	
		
			
				|  |  |  |  |     private static int moveItem(SlottedStorage<ItemVariant> from, int fromSlot, SlottedStorage<ItemVariant> to, int toSlot, final int limit) { | 
		
	
		
			
				|  |  |  |  |         var fromWrapper = FabricContainerTransfer.of(from).singleSlot(fromSlot); | 
		
	
		
			
				|  |  |  |  |         var toWrapper = FabricContainerTransfer.of(to); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					
					|  |  |  |   |