From 1348ee05882ad64d4d7713ca03d54dc2f5142e6a Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Sat, 30 Jan 2021 15:28:11 -0800 Subject: [PATCH 1/7] Ported the generic peripheral feature from upstream forge version along with initial implementation of generic inventory peripherals. --- build.gradle | 3 + .../dan200/computercraft/ComputerCraft.java | 3 + .../computercraft/shared/Peripherals.java | 3 +- .../peripheral/generic/GenericPeripheral.java | 88 +++++ .../generic/GenericPeripheralProvider.java | 46 +++ .../peripheral/generic/SaturatedMethod.java | 58 ++++ .../peripheral/generic/data/BlockData.java | 39 +++ .../peripheral/generic/data/DataHelpers.java | 54 ++++ .../peripheral/generic/data/ItemData.java | 155 +++++++++ .../generic/methods/ArgumentHelpers.java | 37 +++ .../generic/methods/InventoryMethods.java | 306 ++++++++++++++++++ .../turtle/core/TurtleInspectCommand.java | 17 +- .../shared/util/ServiceUtil.java | 29 ++ 13 files changed, 822 insertions(+), 16 deletions(-) create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/data/DataHelpers.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/methods/ArgumentHelpers.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java create mode 100644 src/main/java/dan200/computercraft/shared/util/ServiceUtil.java diff --git a/build.gradle b/build.gradle index 568e733c6..778c5ad55 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,9 @@ dependencies { implementation "blue.endless:jankson:${jankson_version}" implementation 'com.google.code.findbugs:jsr305:3.0.2' + compileOnly 'com.google.auto.service:auto-service:1.0-rc7' + annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' + include "me.shedaniel.cloth:config-2:${cloth_config_version}" include "blue.endless:jankson:${jankson_version}" include 'javax.vecmath:vecmath:1.5.2' diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 7160aef49..a5d0ea5e8 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -21,6 +21,7 @@ import dan200.computercraft.api.turtle.event.TurtleAction; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRule; import dan200.computercraft.core.apis.http.websocket.Websocket; +import dan200.computercraft.core.asm.GenericSource; import dan200.computercraft.shared.common.ColourableRecipe; import dan200.computercraft.shared.computer.core.ClientComputerRegistry; import dan200.computercraft.shared.computer.core.ServerComputerRegistry; @@ -48,6 +49,7 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleTool; import dan200.computercraft.shared.util.Config; import dan200.computercraft.shared.util.ImpostorRecipe; import dan200.computercraft.shared.util.ImpostorShapelessRecipe; +import dan200.computercraft.shared.util.ServiceUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -154,6 +156,7 @@ public final class ComputerCraft implements ModInitializer { Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "player_creative"), PlayerCreativeLootCondition.TYPE); Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "has_id"), HasComputerIdLootCondition.TYPE); init(); + GenericSource.setup( () -> ServiceUtil.loadServices( GenericSource.class )); } } diff --git a/src/main/java/dan200/computercraft/shared/Peripherals.java b/src/main/java/dan200/computercraft/shared/Peripherals.java index 85da4746f..9ef3bb514 100644 --- a/src/main/java/dan200/computercraft/shared/Peripherals.java +++ b/src/main/java/dan200/computercraft/shared/Peripherals.java @@ -17,6 +17,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralProvider; +import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; import net.minecraft.block.entity.BlockEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -53,7 +54,7 @@ public final class Peripherals { } } - return null; + return GenericPeripheralProvider.getPeripheral(world, pos, side); } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java new file mode 100644 index 000000000..4c96f35f9 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java @@ -0,0 +1,88 @@ +/* + * 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; + +import dan200.computercraft.api.lua.IArguments; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IDynamicPeripheral; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.block.entity.LockableContainerBlockEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.Nameable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.locks.Lock; + +class GenericPeripheral implements IDynamicPeripheral +{ + private final String type; + private final BlockEntity tile; + private final List methods; + + GenericPeripheral( BlockEntity tile, List methods ) + { + Identifier type = BlockEntityType.getId(tile.getType()); + this.tile = tile; + + if ( tile instanceof Nameable && ((Nameable) tile).hasCustomName() ) + { + this.type = ((Nameable) tile).getName().asString(); + } else { + this.type = type == null ? "unknown" : type.toString(); + } + + this.methods = methods; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + String[] names = new String[methods.size()]; + for( int i = 0; i < methods.size(); i++ ) names[i] = methods.get( i ).getName(); + return names; + } + + @Nonnull + @Override + public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException + { + return methods.get( method ).apply( context, computer, arguments ); + } + + @Nonnull + @Override + public String getType() + { + return type; + } + + @Nullable + @Override + public Object getTarget() + { + return tile; + } + + @Override + public boolean equals( @Nullable IPeripheral other ) + { + if( other == this ) return true; + if( !(other instanceof GenericPeripheral) ) return false; + + GenericPeripheral generic = (GenericPeripheral) other; + return tile == generic.tile && methods.equals( generic.methods ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java new file mode 100644 index 000000000..b37104fe5 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java @@ -0,0 +1,46 @@ +/* + * 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; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.asm.NamedMethod; +import dan200.computercraft.core.asm.PeripheralMethod; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class GenericPeripheralProvider +{ + @Nullable + public static IPeripheral getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side ) + { + BlockEntity tile = world.getBlockEntity( pos ); + if( tile == null ) return null; + + ArrayList saturated = new ArrayList<>( 0 ); + + // This seems to add inventory methods, how??? + List> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() ); + if( !tileMethods.isEmpty() ) addSaturated( saturated, tile, tileMethods ); + + return saturated.isEmpty() ? null : new GenericPeripheral( tile, saturated ); + } + + private static void addSaturated( ArrayList saturated, Object target, List> methods ) + { + saturated.ensureCapacity( saturated.size() + methods.size() ); + for( NamedMethod method : methods ) + { + saturated.add( new SaturatedMethod( target, method ) ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java new file mode 100644 index 000000000..31a120e52 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java @@ -0,0 +1,58 @@ +/* + * 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; + +import dan200.computercraft.api.lua.IArguments; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.core.asm.NamedMethod; +import dan200.computercraft.core.asm.PeripheralMethod; + +import javax.annotation.Nonnull; + +final class SaturatedMethod +{ + private final Object target; + private final String name; + private final PeripheralMethod method; + + SaturatedMethod( Object target, NamedMethod method ) + { + this.target = target; + this.name = method.getName(); + this.method = method.getMethod(); + } + + @Nonnull + MethodResult apply( @Nonnull ILuaContext context, @Nonnull IComputerAccess computer, @Nonnull IArguments args ) throws LuaException + { + return method.apply( target, context, computer, args ); + } + + @Nonnull + String getName() + { + return name; + } + + @Override + public boolean equals( Object obj ) + { + if( obj == this ) return true; + if( !(obj instanceof SaturatedMethod) ) return false; + + SaturatedMethod other = (SaturatedMethod) obj; + return method == other.method && target.equals( other.target ); + } + + @Override + public int hashCode() + { + return 31 * target.hashCode() + method.hashCode(); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java new file mode 100644 index 000000000..2d7038b29 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java @@ -0,0 +1,39 @@ +/* + * 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.data; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +public class BlockData +{ + @Nonnull + public static > T fill( @Nonnull T data, @Nonnull BlockState state ) + { + data.put("name", DataHelpers.getId( state.getBlock() ) ); + + Map stateTable = new HashMap<>(); + for (ImmutableMap.Entry, ? extends Comparable> entry : state.getEntries().entrySet()) { + Property property = entry.getKey(); + stateTable.put(property.getName(), getPropertyValue(property, entry.getValue())); + } + data.put("state", stateTable); + + return data; + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private static Object getPropertyValue( Property property, Comparable value ) + { + if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value; + return property.name( value ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DataHelpers.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DataHelpers.java new file mode 100644 index 000000000..3d69d2849 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DataHelpers.java @@ -0,0 +1,54 @@ +/* + * 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.data; + +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.Block; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public final class DataHelpers +{ + private DataHelpers() + { } + + @Nonnull + public static Map getTags( @Nonnull Collection tags ) + { + Map result = new HashMap<>( tags.size() ); + for( Identifier location : tags ) result.put( location.toString(), true ); + return result; + } + + @Nullable + public static String getId( @Nonnull Block block ) + { + Identifier id = Registry.BLOCK.getId(block); + return id == null ? null : id.toString(); + } + + @Nullable + public static String getId( @Nonnull Item item ) + { + Identifier id = Registry.ITEM.getId(item); + return id == null ? null : id.toString(); + } + + @Nullable + public static String getId( @Nonnull Enchantment enchantment) + { + Identifier id = Registry.ENCHANTMENT.getId(enchantment); + return id == null ? null : id.toString(); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java new file mode 100644 index 000000000..8d7724b30 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java @@ -0,0 +1,155 @@ +/* + * 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.data; + +import com.google.gson.JsonParseException; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.item.EnchantedBookItem; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.ListTag; +import net.minecraft.text.Text; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +/** + * Data providers for items. + */ +public class ItemData +{ + @Nonnull + public static > T fillBasicSafe( @Nonnull T data, @Nonnull ItemStack stack ) + { + data.put( "name", DataHelpers.getId( stack.getItem() ) ); + data.put( "count", stack.getCount() ); + + return data; + } + + @Nonnull + public static > T fillBasic( @Nonnull T data, @Nonnull ItemStack stack ) + { + fillBasicSafe( data, stack ); + String hash = NBTUtil.getNBTHash( stack.getTag() ); + if( hash != null ) data.put( "nbt", hash ); + + return data; + } + + @Nonnull + public static > T fill( @Nonnull T data, @Nonnull ItemStack stack ) + { + if( stack.isEmpty() ) return data; + + fillBasic( data, stack ); + + data.put( "displayName", stack.toHoverableText().getString() ); + data.put( "maxCount", stack.getMaxCount() ); + + if( stack.isDamageable() ) + { + data.put( "damage", stack.getDamage() ); + data.put( "maxDamage", stack.getMaxDamage() ); + } + + if( stack.isDamaged() ) + { + data.put( "durability", 1.0 - ( stack.getDamage() / stack.getMaxDamage() ) ); + } + + /* + * Used to hide some data from ItemStack tooltip. + * @see https://minecraft.gamepedia.com/Tutorials/Command_NBT_tags + * @see ItemStack#getTooltip + */ + CompoundTag tag = stack.getTag(); + int hideFlags = tag != null ? tag.getInt( "HideFlags" ) : 0; + + List> enchants = getAllEnchants( stack, hideFlags ); + if( !enchants.isEmpty() ) data.put( "enchantments", enchants ); + + if( tag != null && tag.getBoolean( "Unbreakable" ) && (hideFlags & 4) == 0 ) + { + data.put( "unbreakable", true ); + } + + return data; + } + + @Nullable + private static Text parseTextComponent( @Nonnull Tag x ) + { + try + { + return Text.Serializer.fromJson( x.toString() ); + } + catch( JsonParseException e ) + { + return null; + } + } + + /** + * Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility. + * + * @param stack Stack to analyse + * @param hideFlags An int used as bit field to provide visibility rules. + * @return A filled list that contain all visible enchantments. + */ + @Nonnull + private static List> getAllEnchants( @Nonnull ItemStack stack, int hideFlags ) + { + ArrayList> enchants = new ArrayList<>( 0 ); + + if( stack.getItem() instanceof EnchantedBookItem && (hideFlags & 32) == 0 ) + { + addEnchantments( EnchantedBookItem.getEnchantmentTag( stack ), enchants ); + } + + if( stack.hasEnchantments() && (hideFlags & 1) == 0 ) + { + /* + * Mimic the EnchantmentHelper.getEnchantments(ItemStack stack) behavior without special case for Enchanted book. + * I'll do that to have the same data than ones displayed in tooltip. + * @see EnchantmentHelper.getEnchantments(ItemStack stack) + */ + addEnchantments( stack.getEnchantments(), enchants ); + } + + return enchants; + } + + /** + * Converts a Mojang enchant map to a Lua list. + * + * @param rawEnchants The raw NBT list of enchantments + * @param enchants The enchantment map to add it to. + * @see EnchantmentHelper + */ + private static void addEnchantments( @Nonnull ListTag rawEnchants, @Nonnull ArrayList> enchants ) + { + if( rawEnchants.isEmpty() ) return; + + enchants.ensureCapacity( enchants.size() + rawEnchants.size() ); + + + for( Map.Entry entry : EnchantmentHelper.fromTag( rawEnchants ).entrySet() ) + { + Enchantment enchantment = entry.getKey(); + Integer level = entry.getValue(); + HashMap enchant = new HashMap<>( 3 ); + enchant.put( "name", DataHelpers.getId( enchantment ) ); + enchant.put( "level", level ); + enchant.put( "displayName", enchantment.getName( level ).getString() ); + enchants.add( enchant ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/ArgumentHelpers.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/ArgumentHelpers.java new file mode 100644 index 000000000..119831409 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/ArgumentHelpers.java @@ -0,0 +1,37 @@ +/* + * 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 dan200.computercraft.api.lua.LuaException; + +/** + * A few helpers for working with arguments. + * + * This should really be moved into the public API. However, until I have settled on a suitable format, we'll keep it + * where it is used. + */ +final class ArgumentHelpers +{ + private ArgumentHelpers() + { + } + + public static void assertBetween( double value, double min, double max, String message ) throws LuaException + { + if( value < min || value > max || Double.isNaN( value ) ) + { + throw new LuaException( String.format( message, "between " + min + " and " + max ) ); + } + } + + public static void assertBetween( int value, int min, int max, String message ) throws LuaException + { + if( value < min || value > max ) + { + throw new LuaException( String.format( message, "between " + min + " and " + max ) ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java new file mode 100644 index 000000000..9d6923d9a --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -0,0 +1,306 @@ +/* + * 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.ComputerCraft; +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.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +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 Identifier id() + { + return new Identifier(ComputerCraft.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( Inventory inventory ) + { + return inventory.size(); + } + + /** + * 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 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( Inventory inventory ) + { + Map> result = new HashMap<>(); + int size = inventory.size(); + for( int i = 0; i < size; i++ ) + { + ItemStack stack = inventory.getStack( 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( Inventory inventory, int slot ) throws LuaException + { + assertBetween( slot, 1, inventory.size(), "Slot out of range (%s)" ); + + ItemStack stack = inventory.getStack( 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( + Inventory 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" ); + + Inventory 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.size(), "From slot out of range (%s)" ); + if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.size(), "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( + Inventory 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" ); + + Inventory 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.size(), "From slot out of range (%s)" ); + if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.size(), "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 Inventory extractHandler( @Nullable Object object ) + { + if ( object instanceof Inventory ) return (Inventory) 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( Inventory from, int fromSlot, Inventory 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 ); + + Boolean recurse = false; + + ItemStack source = from.getStack( fromSlot ); + int count = 0; + + if (toSlot > 0) { + int space = amountStackCanAddFrom(to.getStack(toSlot), source, to); + if (space == 0) return 0; + count = space; + } + if (toSlot < 0) { + recurse = true; + int[] result = getFirstValidSlotAndSpace(source, to); + toSlot = result[0]; + if(toSlot < 0) return 0; + count = result[1]; + } + + count = Math.min(count, limit); + if (count == 0) return 0; + ItemStack destination = to.getStack(toSlot); + + if (destination == ItemStack.EMPTY) { + ItemStack newStack = source.copy(); + newStack.setCount(count); + to.setStack(toSlot, newStack); + } else { + destination.increment(count); + } + source.decrement(count); + if (source.isEmpty()) from.setStack(fromSlot, ItemStack.EMPTY); + + to.markDirty(); + from.markDirty(); + + if (recurse && !source.isEmpty()) return count + moveItem(from, fromSlot, to, -1, limit - count); + return count; + } + + private static int[] getFirstValidSlotAndSpace(ItemStack fromStack, Inventory inventory) { + for (int i = 0; i < inventory.size(); i++) { + ItemStack stack = inventory.getStack(i); + int space = amountStackCanAddFrom(stack, fromStack, inventory); + if (space > 0) { + return new int[]{i, space}; + } + } + return new int[]{-1, 0}; + } + + private static int amountStackCanAddFrom(ItemStack existingStack, ItemStack fromStack, Inventory inventory) { + if (fromStack.isEmpty()) { + return 0; + } + else if (existingStack.isEmpty()) { + return Math.min(Math.min(existingStack.getMaxCount(), + inventory.getMaxCountPerStack()), + fromStack.getCount()); + } + else if (InventoryMethods.areItemsEqual(existingStack, fromStack) && + existingStack.isStackable() && + existingStack.getCount() < existingStack.getMaxCount() && + existingStack.getCount() < inventory.getMaxCountPerStack()) { + int stackSpace = existingStack.getMaxCount() - existingStack.getCount(); + int invSpace = inventory.getMaxCountPerStack() - existingStack.getCount(); + return Math.min(Math.min(stackSpace, invSpace), fromStack.getCount()); + } + return 0; + } + + private static boolean areItemsEqual(ItemStack stack1, ItemStack stack2) { + return stack1.getItem() == stack2.getItem() && ItemStack.areTagsEqual(stack1, stack2); + } +} diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index a961427a4..90a71d6b2 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -18,6 +18,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.shared.peripheral.generic.data.BlockData; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.state.property.Property; @@ -49,20 +50,7 @@ public class TurtleInspectCommand implements ITurtleCommand { return TurtleCommandResult.failure("No block to inspect"); } - Block block = state.getBlock(); - String name = Registry.BLOCK.getId(block) - .toString(); - - Map table = new HashMap<>(); - table.put("name", name); - - Map stateTable = new HashMap<>(); - for (ImmutableMap.Entry, ? extends Comparable> entry : state.getEntries() - .entrySet()) { - Property property = entry.getKey(); - stateTable.put(property.getName(), getPropertyValue(property, entry.getValue())); - } - table.put("state", stateTable); + Map table = BlockData.fill( new HashMap<>(), state ); // Fire the event, exiting if it is cancelled TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, oldPosition, direction); @@ -72,7 +60,6 @@ public class TurtleInspectCommand implements ITurtleCommand { } return TurtleCommandResult.success(new Object[] {table}); - } @SuppressWarnings ({ diff --git a/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java b/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java new file mode 100644 index 000000000..9e4772450 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java @@ -0,0 +1,29 @@ +/* + * 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.util; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import org.objectweb.asm.Type; + +import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public final class ServiceUtil +{ + private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" ); + + private ServiceUtil() + { + } + + public static Stream loadServices( Class target ) + { + return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false ); + } +} From 664df62d5df8c602aa01b2e97494b7bc25813083 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Sat, 30 Jan 2021 17:12:09 -0800 Subject: [PATCH 2/7] Cable modems can be placed against all blocks, including chests. --- .../peripheral/modem/wired/BlockCable.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java index f94b8023a..5f020ee57 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java @@ -181,17 +181,19 @@ public class BlockCable extends BlockGeneric implements Waterloggable { // : new ItemStack( ComputerCraftRegistry.ModItems.CABLE.get() ); // } - @Override - @Deprecated - public boolean canPlaceAt(BlockState state, @Nonnull WorldView world, @Nonnull BlockPos pos) { - Direction facing = state.get(MODEM) - .getFacing(); - if (facing == null) { - return true; - } - - return sideCoversSmallSquare(world, pos.offset(facing), facing.getOpposite()); - } + // Commenting override to allow cable modems to be placed on chests, so that chests can be generic inventory peripherals. + // TODO Perhaps there is a more selective way to achieve this? + // @Override + // @Deprecated + // public boolean canPlaceAt(BlockState state, @Nonnull WorldView world, @Nonnull BlockPos pos) { + // Direction facing = state.get(MODEM) + // .getFacing(); + // if (facing == null) { + // return true; + // } + // + // return sideCoversSmallSquare(world, pos.offset(facing), facing.getOpposite()); + // } @Nonnull @Override From f6a26f75c3498e61fd1411bffcf9ae8e0c510fd6 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Sat, 30 Jan 2021 20:45:08 -0800 Subject: [PATCH 3/7] Reverted change to how GenericPeripherals report type. Instead, added generic lua function that returns the name of Nameable targets. Might not be at home in InventoryMethods since it could apply to other types of targets. --- .../peripheral/generic/GenericPeripheral.java | 14 ++------------ .../generic/methods/InventoryMethods.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java index 4c96f35f9..15ae9320d 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java @@ -5,10 +5,7 @@ */ package dan200.computercraft.shared.peripheral.generic; -import dan200.computercraft.api.lua.IArguments; -import dan200.computercraft.api.lua.ILuaContext; -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.lua.*; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IDynamicPeripheral; import dan200.computercraft.api.peripheral.IPeripheral; @@ -35,14 +32,7 @@ class GenericPeripheral implements IDynamicPeripheral { Identifier type = BlockEntityType.getId(tile.getType()); this.tile = tile; - - if ( tile instanceof Nameable && ((Nameable) tile).hasCustomName() ) - { - this.type = ((Nameable) tile).getName().asString(); - } else { - this.type = type == null ? "unknown" : type.toString(); - } - + this.type = type == null ? "unknown" : type.toString(); this.methods = methods; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index 9d6923d9a..152e77148 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -16,6 +16,7 @@ import dan200.computercraft.shared.peripheral.generic.data.ItemData; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; +import net.minecraft.util.Nameable; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -54,6 +55,18 @@ public class InventoryMethods implements GenericSource return inventory.size(); } + /** + * Get the name of this inventory. + * + * @param inventory The current inventory. + * @return The name of this inventory, or {@code nil} if not present. + */ + @LuaFunction( mainThread = true ) + public static String name( Nameable inventory ) + { + return inventory.hasCustomName() ? inventory.getName().asString() : null; + } + /** * List all items in this inventory. This returns a table, with an entry for each slot. * From 83e70377f736fba148ea38e09156518ffe9a4947 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Sun, 31 Jan 2021 20:13:59 -0800 Subject: [PATCH 4/7] Fixed off by one error. Commented not very nice inventory code. --- .../generic/methods/InventoryMethods.java | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index 152e77148..aec2b4894 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -227,33 +227,39 @@ public class InventoryMethods implements GenericSource */ private static int moveItem( Inventory from, int fromSlot, Inventory 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 ); + /* ORIGINAL FORGE CODE + // See how much we can get out of this slot + // ItemStack extracted = from.extractItem( fromSlot, limit, true ); + if( extracted.isEmpty() ) return 0; - // 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; + // Limit the amount to extract + int extractCount = Math.min( extracted.getCount(), limit ); + extracted.setCount( extractCount ); - // 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 ); + // ItemStack remainder = toSlot < 0 ? bItemHandlerHelper.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 ); + */ + + // Vanilla minecraft inventory manipulation code Boolean recurse = false; - ItemStack source = from.getStack( fromSlot ); int count = 0; - if (toSlot > 0) { + // If target slot was selected, only push items to that slot. + if (toSlot >= 0) { int space = amountStackCanAddFrom(to.getStack(toSlot), source, to); if (space == 0) return 0; count = space; } - if (toSlot < 0) { + // If target slot not selected, push items where they will fit, possibly + // across slots (by recurring on this method). + else if (toSlot < 0) { recurse = true; int[] result = getFirstValidSlotAndSpace(source, to); toSlot = result[0]; @@ -261,10 +267,12 @@ public class InventoryMethods implements GenericSource count = result[1]; } + // Compare count available in target ItemStack to limit specified. count = Math.min(count, limit); if (count == 0) return 0; - ItemStack destination = to.getStack(toSlot); + // Mutate destination and source ItemStack + ItemStack destination = to.getStack(toSlot); if (destination == ItemStack.EMPTY) { ItemStack newStack = source.copy(); newStack.setCount(count); @@ -278,10 +286,13 @@ public class InventoryMethods implements GenericSource to.markDirty(); from.markDirty(); + // Recurse if no explicit destination slot and more items exist in source slot + // and limit hasn't been reached. Else, return items moved. if (recurse && !source.isEmpty()) return count + moveItem(from, fromSlot, to, -1, limit - count); return count; } + // Maybe there is a nicer existing way to do this in the minecraft codebase. I couldn't find it. private static int[] getFirstValidSlotAndSpace(ItemStack fromStack, Inventory inventory) { for (int i = 0; i < inventory.size(); i++) { ItemStack stack = inventory.getStack(i); From 89d5211bd775c9479c46c33e7b3da2a7365afaf9 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Mon, 1 Feb 2021 00:02:28 -0800 Subject: [PATCH 5/7] Fixed bad assumption about empty ItemStacks being reset to ItemStack.EMPTY. Items no longer transfer into an inventory as a different item than they began. --- .../shared/peripheral/generic/methods/InventoryMethods.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index aec2b4894..be4aba46c 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -273,7 +273,7 @@ public class InventoryMethods implements GenericSource // Mutate destination and source ItemStack ItemStack destination = to.getStack(toSlot); - if (destination == ItemStack.EMPTY) { + if (destination.isEmpty()) { ItemStack newStack = source.copy(); newStack.setCount(count); to.setStack(toSlot, newStack); From 42f23d56ae48298a30e34ce9cffb058552bbd143 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Mon, 1 Feb 2021 19:04:02 -0800 Subject: [PATCH 6/7] Double chest inventories are now handled correctly. --- .../generic/methods/InventoryMethods.java | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index be4aba46c..047444967 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -13,10 +13,20 @@ 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.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.InventoryProvider; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; import net.minecraft.util.Identifier; import net.minecraft.util.Nameable; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -52,6 +62,9 @@ public class InventoryMethods implements GenericSource @LuaFunction( mainThread = true ) public static int size( Inventory inventory ) { + // Get appropriate inventory for source peripheral + inventory = extractHandler(inventory); + return inventory.size(); } @@ -84,6 +97,9 @@ public class InventoryMethods implements GenericSource @LuaFunction( mainThread = true ) public static Map> list( Inventory inventory ) { + // Get appropriate inventory for source peripheral + inventory = extractHandler(inventory); + Map> result = new HashMap<>(); int size = inventory.size(); for( int i = 0; i < size; i++ ) @@ -108,6 +124,9 @@ public class InventoryMethods implements GenericSource @LuaFunction( mainThread = true ) public static Map getItemDetail( Inventory inventory, int slot ) throws LuaException { + // Get appropriate inventory + inventory = extractHandler(inventory); + assertBetween( slot, 1, inventory.size(), "Slot out of range (%s)" ); ItemStack stack = inventory.getStack( slot - 1 ); @@ -145,6 +164,9 @@ public class InventoryMethods implements GenericSource String toName, int fromSlot, Optional limit, Optional toSlot ) throws LuaException { + // Get appropriate inventory for source peripheral + from = extractHandler(from); + // Find location to transfer to IPeripheral location = computer.getAvailablePeripheral( toName ); if( location == null ) throw new LuaException( "Target '" + toName + "' does not exist" ); @@ -192,6 +214,9 @@ public class InventoryMethods implements GenericSource String fromName, int fromSlot, Optional limit, Optional toSlot ) throws LuaException { + // Get appropriate inventory for source peripheral + to = extractHandler(to); + // Find location to transfer to IPeripheral location = computer.getAvailablePeripheral( fromName ); if( location == null ) throw new LuaException( "Source '" + fromName + "' does not exist" ); @@ -207,12 +232,38 @@ public class InventoryMethods implements GenericSource if( actualLimit <= 0 ) return 0; return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit ); } - + + + /** + * Extracts the most appropriate inventory from the object + * e.g., the correct inventory for a double chest or a sided inventory. + * + * @param object The handler to move from. + * @return The appropriate Inventory. + */ @Nullable private static Inventory extractHandler( @Nullable Object object ) { - if ( object instanceof Inventory ) return (Inventory) object; - return null; + Inventory inventory = null; + + if (object instanceof BlockEntity ) { + BlockEntity blockEntity = (BlockEntity) object; + World world = blockEntity.getWorld(); + BlockPos blockPos = blockEntity.getPos(); + BlockState blockState = world.getBlockState(blockPos); + Block block = blockState.getBlock(); + + if (block instanceof InventoryProvider) { + inventory = ((InventoryProvider)block).getInventory(blockState, world, blockPos); + } else if (blockEntity instanceof Inventory) { + inventory = (Inventory)blockEntity; + if (inventory instanceof ChestBlockEntity && block instanceof ChestBlock) { + inventory = ChestBlock.getInventory((ChestBlock) block, blockState, world, blockPos, true); + } + } + } + + return inventory; } /** From 6d103e211423c5161a13baf05887c71bc3e25931 Mon Sep 17 00:00:00 2001 From: David Queneau <748280+davidqueneau@users.noreply.github.com> Date: Mon, 1 Feb 2021 23:21:25 -0800 Subject: [PATCH 7/7] Item movement methods now respect inventory slot rules (i.e. no pickaxes in the fuel slot of a furnace). --- .../shared/peripheral/generic/methods/InventoryMethods.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index 047444967..6642cccf2 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -318,6 +318,9 @@ public class InventoryMethods implements GenericSource count = result[1]; } + // Respect slot restrictions + if (!to.isValid(toSlot, source)) { return 0; } + // Compare count available in target ItemStack to limit specified. count = Math.min(count, limit); if (count == 0) return 0;