/* * This file is part of ComputerCraft - http://www.computercraft.info * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ package dan200.computercraft.shared.peripheral.generic.methods; import com.google.auto.service.AutoService; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.asm.GenericSource; import dan200.computercraft.shared.peripheral.generic.data.ItemData; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemHandlerHelper; import net.minecraftforge.items.wrapper.InvWrapper; import net.minecraftforge.versions.forge.ForgeVersion; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; import java.util.Optional; import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHelpers.assertBetween; /** * Methods for interacting with inventories. * * @cc.module inventory */ @AutoService( GenericSource.class ) public class InventoryMethods implements GenericSource { @Nonnull @Override public ResourceLocation id() { return new ResourceLocation( ForgeVersion.MOD_ID, "inventory" ); } /** * Get the size of this inventory. * * @param inventory The current inventory. * @return The number of slots in this inventory. */ @LuaFunction( mainThread = true ) public static int size( IItemHandler inventory ) { return inventory.getSlots(); } /** * List all items in this inventory. This returns a table, with an entry for each slot. * * Each item in the inventory is represented by a table containing some basic information, much like * {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail} includes. More information can be fetched * with {@link #getItemDetail}. * * The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs` * rather than `ipairs`. * * @param inventory The current inventory. * @return All items in this inventory. * @cc.treturn { (table|nil)... } All items in this inventory. */ @LuaFunction( mainThread = true ) public static Map> list( IItemHandler inventory ) { Map> result = new HashMap<>(); int size = inventory.getSlots(); for( int i = 0; i < size; i++ ) { ItemStack stack = inventory.getStackInSlot( i ); if( !stack.isEmpty() ) result.put( i + 1, ItemData.fillBasic( new HashMap<>( 4 ), stack ) ); } return result; } /** * Get detailed information about an item. * * @param inventory The current inventory. * @param slot The slot to get information about. * @return Information about the item in this slot, or {@code nil} if not present. * @throws LuaException If the slot is out of range. * @cc.treturn table Information about the item in this slot, or {@code nil} if not present. */ @Nullable @LuaFunction( mainThread = true ) public static Map getItemDetail( IItemHandler inventory, int slot ) throws LuaException { assertBetween( slot, 1, inventory.getSlots(), "Slot out of range (%s)" ); ItemStack stack = inventory.getStackInSlot( slot - 1 ); return stack.isEmpty() ? null : ItemData.fill( new HashMap<>(), stack ); } /** * Push items from one inventory to another connected one. * * This allows you to push an item in an inventory to another inventory on the same wired network. Both * inventories must attached to wired modems which are connected via a cable. * * @param from Inventory to move items from. * @param computer The current computer. * @param toName The name of the peripheral/inventory to push to. This is the string given to @{peripheral.wrap}, * and displayed by the wired modem. * @param fromSlot The slot in the current inventory to move items to. * @param limit The maximum number of items to move. Defaults to the current stack limit. * @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot. * @return The number of transferred items. * @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory. * @throws LuaException If either source or destination slot is out of range. * @cc.see peripheral.getName Allows you to get the name of a @{peripheral.wrap|wrapped} peripheral. * @cc.usage Wrap two chests, and push an item from one to another. *
{@code
     * local chest_a = peripheral.wrap("minecraft:chest_0")
     * local chest_b = peripheral.wrap("minecraft:chest_1")
     *
     * chest_a.pushItems(peripheral.getName(chest_b), 1)
     * }
*/ @LuaFunction( mainThread = true ) public static int pushItems( IItemHandler from, IComputerAccess computer, String toName, int fromSlot, Optional limit, Optional toSlot ) throws LuaException { // Find location to transfer to IPeripheral location = computer.getAvailablePeripheral( toName ); if( location == null ) throw new LuaException( "Target '" + toName + "' does not exist" ); IItemHandler to = extractHandler( location.getTarget() ); if( to == null ) throw new LuaException( "Target '" + toName + "' is not an inventory" ); // Validate slots int actualLimit = limit.orElse( Integer.MAX_VALUE ); assertBetween( fromSlot, 1, from.getSlots(), "From slot out of range (%s)" ); if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)" ); if( actualLimit <= 0 ) return 0; return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit ); } /** * Pull items from a connected inventory into this one. * * This allows you to transfer items between inventories on the same wired network. Both this and the source * inventory must attached to wired modems which are connected via a cable. * * @param to Inventory to move items to. * @param computer The current computer. * @param fromName The name of the peripheral/inventory to pull from. This is the string given to @{peripheral.wrap}, * and displayed by the wired modem. * @param fromSlot The slot in the source inventory to move items from. * @param limit The maximum number of items to move. Defaults to the current stack limit. * @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot. * @return The number of transferred items. * @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory. * @throws LuaException If either source or destination slot is out of range. * @cc.see peripheral.getName Allows you to get the name of a @{peripheral.wrap|wrapped} peripheral. * @cc.usage Wrap two chests, and push an item from one to another. *
{@code
     * local chest_a = peripheral.wrap("minecraft:chest_0")
     * local chest_b = peripheral.wrap("minecraft:chest_1")
     *
     * chest_a.pullItems(peripheral.getName(chest_b), 1)
     * }
*/ @LuaFunction( mainThread = true ) public static int pullItems( IItemHandler to, IComputerAccess computer, String fromName, int fromSlot, Optional limit, Optional toSlot ) throws LuaException { // Find location to transfer to IPeripheral location = computer.getAvailablePeripheral( fromName ); if( location == null ) throw new LuaException( "Source '" + fromName + "' does not exist" ); IItemHandler from = extractHandler( location.getTarget() ); 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(), "From slot out of range (%s)" ); if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)" ); if( actualLimit <= 0 ) return 0; return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit ); } @Nullable private static IItemHandler extractHandler( @Nullable Object object ) { if( object instanceof ICapabilityProvider ) { LazyOptional cap = ((ICapabilityProvider) object).getCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY ); if( cap.isPresent() ) return cap.orElseThrow( NullPointerException::new ); } if( object instanceof IItemHandler ) return (IItemHandler) object; if( object instanceof IInventory ) return new InvWrapper( (IInventory) object ); 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( IItemHandler from, int fromSlot, IItemHandler to, int toSlot, final int limit ) { // See how much we can get out of this slot ItemStack extracted = from.extractItem( fromSlot, limit, true ); if( extracted.isEmpty() ) return 0; // Limit the amount to extract int extractCount = Math.min( extracted.getCount(), limit ); extracted.setCount( extractCount ); ItemStack remainder = toSlot < 0 ? ItemHandlerHelper.insertItem( to, extracted, false ) : to.insertItem( toSlot, extracted, false ); int inserted = remainder.isEmpty() ? extractCount : extractCount - remainder.getCount(); if( inserted <= 0 ) return 0; // Remove the item from the original inventory. Technically this could fail, but there's little we can do // about that. from.extractItem( fromSlot, inserted, false ); return inserted; } }