1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-28 16:22:18 +00:00

Merge pull request #29 from davidqueneau/fabric

Porting GenericPeripherals from upstream CC: Tweaked
This commit is contained in:
Devan-Kerman 2021-02-03 10:44:02 -06:00 committed by GitHub
commit 88722d484f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 903 additions and 27 deletions

View File

@ -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'

View File

@ -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 ));
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.*;
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<SaturatedMethod> methods;
GenericPeripheral( BlockEntity tile, List<SaturatedMethod> methods )
{
Identifier type = BlockEntityType.getId(tile.getType());
this.tile = tile;
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 );
}
}

View File

@ -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<SaturatedMethod> saturated = new ArrayList<>( 0 );
// This seems to add inventory methods, how???
List<NamedMethod<PeripheralMethod>> 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<SaturatedMethod> saturated, Object target, List<NamedMethod<PeripheralMethod>> methods )
{
saturated.ensureCapacity( saturated.size() + methods.size() );
for( NamedMethod<PeripheralMethod> method : methods )
{
saturated.add( new SaturatedMethod( target, method ) );
}
}
}

View File

@ -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<PeripheralMethod> 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();
}
}

View File

@ -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 extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull BlockState state )
{
data.put("name", DataHelpers.getId( state.getBlock() ) );
Map<Object, Object> stateTable = new HashMap<>();
for (ImmutableMap.Entry<Property<?>, ? 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 );
}
}

View File

@ -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<String, Boolean> getTags( @Nonnull Collection<Identifier> tags )
{
Map<String, Boolean> 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();
}
}

View File

@ -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 extends Map<? super String, Object>> 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 extends Map<? super String, Object>> 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 extends Map<? super String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> getAllEnchants( @Nonnull ItemStack stack, int hideFlags )
{
ArrayList<Map<String, Object>> 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<Map<String, Object>> enchants )
{
if( rawEnchants.isEmpty() ) return;
enchants.ensureCapacity( enchants.size() + rawEnchants.size() );
for( Map.Entry<Enchantment, Integer> entry : EnchantmentHelper.fromTag( rawEnchants ).entrySet() )
{
Enchantment enchantment = entry.getKey();
Integer level = entry.getValue();
HashMap<String, Object> enchant = new HashMap<>( 3 );
enchant.put( "name", DataHelpers.getId( enchantment ) );
enchant.put( "level", level );
enchant.put( "displayName", enchantment.getName( level ).getString() );
enchants.add( enchant );
}
}
}

View File

@ -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 ) );
}
}
}

View File

@ -0,0 +1,384 @@
/*
* 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.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;
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 )
{
// Get appropriate inventory for source peripheral
inventory = extractHandler(inventory);
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.
*
* 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<Integer, Map<String, ?>> list( Inventory inventory )
{
// Get appropriate inventory for source peripheral
inventory = extractHandler(inventory);
Map<Integer, Map<String, ?>> 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<String, ?> 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 );
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 <em>on the same wired network</em>. 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.
* <pre>{@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)
* }</pre>
*/
@LuaFunction( mainThread = true )
public static int pushItems(
Inventory from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> 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" );
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 <em>on the same wired network</em>. 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.
* <pre>{@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)
* }</pre>
*/
@LuaFunction( mainThread = true )
public static int pullItems(
Inventory to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> 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" );
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 );
}
/**
* 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 )
{
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;
}
/**
* 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 )
{
/* 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;
// Limit the amount to extract
int extractCount = Math.min( extracted.getCount(), limit );
extracted.setCount( extractCount );
// 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 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 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];
if(toSlot < 0) return 0;
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;
// Mutate destination and source ItemStack
ItemStack destination = to.getStack(toSlot);
if (destination.isEmpty()) {
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();
// 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);
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);
}
}

View File

@ -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

View File

@ -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<String, Object> table = new HashMap<>();
table.put("name", name);
Map<Object, Object> stateTable = new HashMap<>();
for (ImmutableMap.Entry<Property<?>, ? extends Comparable<?>> entry : state.getEntries()
.entrySet()) {
Property<?> property = entry.getKey();
stateTable.put(property.getName(), getPropertyValue(property, entry.getValue()));
}
table.put("state", stateTable);
Map<String, Object> 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 ({

View File

@ -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 <T> Stream<T> loadServices( Class<T> target )
{
return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false );
}
}