mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-25 16:36:55 +00:00
Generic peripherals for any tile entities (#478)
This exposes a basic peripheral for any tile entity which does not have methods already registered. We currently provide the following methods: - Inventories: size, list, getItemMeta, pushItems, pullItems. - Energy storage: getEnergy, getEnergyCapacity - Fluid tanks: tanks(), pushFluid, pullFluid. These methods are currently experimental - it must be enabled through `experimental.generic_peripherals`. While this is an initial step towards implementing #452, but is by no means complete.
This commit is contained in:
parent
613a28a5af
commit
08181f72d4
@ -105,6 +105,9 @@ dependencies {
|
||||
|
||||
runtimeOnly fg.deobf("mezz.jei:jei-1.15.2:6.0.0.3")
|
||||
|
||||
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
|
||||
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
|
@ -38,8 +38,6 @@ public final class ComputerCraft
|
||||
{
|
||||
public static final String MOD_ID = "computercraft";
|
||||
|
||||
public static final int DATAFIXER_VERSION = 0;
|
||||
|
||||
// Configuration options
|
||||
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
|
||||
public static final String[] DEFAULT_HTTP_DENY = new String[] {
|
||||
@ -93,6 +91,8 @@ public final class ComputerCraft
|
||||
public static boolean turtlesCanPush = true;
|
||||
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
|
||||
|
||||
public static boolean genericPeripheral = false;
|
||||
|
||||
public static final int terminalWidth_computer = 51;
|
||||
public static final int terminalHeight_computer = 19;
|
||||
|
||||
|
@ -34,7 +34,7 @@ import java.util.function.Function;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class Generator<T>
|
||||
public final class Generator<T>
|
||||
{
|
||||
private static final AtomicInteger METHOD_ID = new AtomicInteger();
|
||||
|
||||
@ -99,26 +99,28 @@ public class Generator<T>
|
||||
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||
if( annotation == null ) continue;
|
||||
|
||||
if( Modifier.isStatic( method.getModifiers() ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "LuaFunction method {}.{} should be an instance method.", method.getDeclaringClass(), method.getName() );
|
||||
continue;
|
||||
}
|
||||
|
||||
T instance = methodCache.getUnchecked( method ).orElse( null );
|
||||
if( instance == null ) continue;
|
||||
|
||||
if( methods == null ) methods = new ArrayList<>();
|
||||
addMethod( methods, method, annotation, instance );
|
||||
}
|
||||
|
||||
if( annotation.mainThread() ) instance = wrap.apply( instance );
|
||||
for( GenericSource.GenericMethod method : GenericSource.GenericMethod.all() )
|
||||
{
|
||||
if( !method.target.isAssignableFrom( klass ) ) continue;
|
||||
|
||||
String[] names = annotation.value();
|
||||
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||
if( names.length == 0 )
|
||||
{
|
||||
methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( String name : names )
|
||||
{
|
||||
methods.add( new NamedMethod<>( name, instance, isSimple ) );
|
||||
}
|
||||
}
|
||||
T instance = methodCache.getUnchecked( method.method ).orElse( null );
|
||||
if( instance == null ) continue;
|
||||
|
||||
if( methods == null ) methods = new ArrayList<>();
|
||||
addMethod( methods, method.method, method.annotation, instance );
|
||||
}
|
||||
|
||||
if( methods == null ) return Collections.emptyList();
|
||||
@ -126,19 +128,40 @@ public class Generator<T>
|
||||
return Collections.unmodifiableList( methods );
|
||||
}
|
||||
|
||||
private void addMethod( List<NamedMethod<T>> methods, Method method, LuaFunction annotation, T instance )
|
||||
{
|
||||
if( annotation.mainThread() ) instance = wrap.apply( instance );
|
||||
|
||||
String[] names = annotation.value();
|
||||
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||
if( names.length == 0 )
|
||||
{
|
||||
methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( String name : names )
|
||||
{
|
||||
methods.add( new NamedMethod<>( name, instance, isSimple ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Optional<T> build( Method method )
|
||||
{
|
||||
String name = method.getDeclaringClass().getName() + "." + method.getName();
|
||||
int modifiers = method.getModifiers();
|
||||
if( !Modifier.isFinal( modifiers ) )
|
||||
|
||||
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
|
||||
if( !Modifier.isStatic( modifiers ) && !Modifier.isFinal( modifiers ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Lua Method {} should be final.", name );
|
||||
}
|
||||
|
||||
if( Modifier.isStatic( modifiers ) || !Modifier.isPublic( modifiers ) )
|
||||
if( !Modifier.isPublic( modifiers ) )
|
||||
{
|
||||
ComputerCraft.log.error( "Lua Method {} should be a public instance method.", name );
|
||||
ComputerCraft.log.error( "Lua Method {} should be a public method.", name );
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@ -160,10 +183,14 @@ public class Generator<T>
|
||||
}
|
||||
}
|
||||
|
||||
// We have some rather ugly handling of static methods in both here and the main generate function. Static methods
|
||||
// only come from generic sources, so this should be safe.
|
||||
Class<?> target = Modifier.isStatic( modifiers ) ? method.getParameterTypes()[0] : method.getDeclaringClass();
|
||||
|
||||
try
|
||||
{
|
||||
String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement();
|
||||
byte[] bytes = generate( className, method );
|
||||
byte[] bytes = generate( className, target, method );
|
||||
if( bytes == null ) return Optional.empty();
|
||||
|
||||
Class<?> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() );
|
||||
@ -178,7 +205,7 @@ public class Generator<T>
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] generate( String className, Method method )
|
||||
private byte[] generate( String className, Class<?> target, Method method )
|
||||
{
|
||||
String internalName = className.replace( ".", "/" );
|
||||
|
||||
@ -200,19 +227,27 @@ public class Generator<T>
|
||||
{
|
||||
MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS );
|
||||
mw.visitCode();
|
||||
mw.visitVarInsn( ALOAD, 1 );
|
||||
mw.visitTypeInsn( CHECKCAST, Type.getInternalName( method.getDeclaringClass() ) );
|
||||
|
||||
// If we're an instance method, load the this parameter.
|
||||
if( !Modifier.isStatic( method.getModifiers() ) )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 1 );
|
||||
mw.visitTypeInsn( CHECKCAST, Type.getInternalName( target ) );
|
||||
}
|
||||
|
||||
int argIndex = 0;
|
||||
for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() )
|
||||
{
|
||||
Boolean loadedArg = loadArg( mw, method, genericArg, argIndex );
|
||||
Boolean loadedArg = loadArg( mw, target, method, genericArg, argIndex );
|
||||
if( loadedArg == null ) return null;
|
||||
if( loadedArg ) argIndex++;
|
||||
}
|
||||
|
||||
mw.visitMethodInsn( INVOKEVIRTUAL, Type.getInternalName( method.getDeclaringClass() ), method.getName(),
|
||||
Type.getMethodDescriptor( method ), false );
|
||||
mw.visitMethodInsn(
|
||||
Modifier.isStatic( method.getModifiers() ) ? INVOKESTATIC : INVOKEVIRTUAL,
|
||||
Type.getInternalName( method.getDeclaringClass() ), method.getName(),
|
||||
Type.getMethodDescriptor( method ), false
|
||||
);
|
||||
|
||||
// We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
|
||||
// we convert basic types into an immediate result.
|
||||
@ -250,8 +285,15 @@ public class Generator<T>
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private Boolean loadArg( MethodVisitor mw, Method method, java.lang.reflect.Type genericArg, int argIndex )
|
||||
private Boolean loadArg( MethodVisitor mw, Class<?> target, Method method, java.lang.reflect.Type genericArg, int argIndex )
|
||||
{
|
||||
if( genericArg == target )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 1 );
|
||||
mw.visitTypeInsn( CHECKCAST, Type.getInternalName( target ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> arg = Reflect.getRawType( method, genericArg, true );
|
||||
if( arg == null ) return null;
|
||||
|
||||
|
100
src/main/java/dan200/computercraft/core/asm/GenericSource.java
Normal file
100
src/main/java/dan200/computercraft/core/asm/GenericSource.java
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* A generic source of {@link LuaMethod} functions. This allows for injecting methods onto objects you do not own.
|
||||
*
|
||||
* Unlike conventional Lua objects, the annotated methods should be {@code static}, with their target as the first
|
||||
* parameter.
|
||||
*
|
||||
* This is used by the generic peripheral system ({@link GenericPeripheralProvider}) to provide methods for arbitrary
|
||||
* tile entities. Eventually this'll be be exposed in the public API. Until it is stabilised, it will remain in this
|
||||
* package - do not use it in external mods!
|
||||
*/
|
||||
public interface GenericSource
|
||||
{
|
||||
/**
|
||||
* A unique identifier for this generic source. This may be used in the future to allow disabling specific sources.
|
||||
*
|
||||
* @return This source's identifier.
|
||||
*/
|
||||
@Nonnull
|
||||
ResourceLocation id();
|
||||
|
||||
/**
|
||||
* A generic method is a method belonging to a {@link GenericSource} with a known target.
|
||||
*/
|
||||
class GenericMethod
|
||||
{
|
||||
final Method method;
|
||||
final LuaFunction annotation;
|
||||
final Class<?> target;
|
||||
|
||||
private static List<GenericMethod> cache;
|
||||
|
||||
GenericMethod( Method method, LuaFunction annotation, Class<?> target )
|
||||
{
|
||||
this.method = method;
|
||||
this.annotation = annotation;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}.
|
||||
*
|
||||
* @return All available generic methods.
|
||||
*/
|
||||
static List<GenericMethod> all()
|
||||
{
|
||||
if( cache != null ) return cache;
|
||||
return cache = StreamSupport
|
||||
.stream( ServiceLoader.load( GenericSource.class, GenericSource.class.getClassLoader() ).spliterator(), false )
|
||||
.flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) )
|
||||
.map( method -> {
|
||||
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||
if( annotation == null ) return null;
|
||||
|
||||
if( !Modifier.isStatic( method.getModifiers() ) )
|
||||
{
|
||||
ComputerCraft.log.error( "GenericSource method {}.{} should be static.", method.getDeclaringClass(), method.getName() );
|
||||
return null;
|
||||
}
|
||||
|
||||
Type[] types = method.getGenericParameterTypes();
|
||||
if( types.length == 0 )
|
||||
{
|
||||
ComputerCraft.log.error( "GenericSource method {}.{} has no parameters.", method.getDeclaringClass(), method.getName() );
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> target = Reflect.getRawType( method, types[0], false );
|
||||
if( target == null ) return null;
|
||||
|
||||
return new GenericMethod( method, annotation, target );
|
||||
} )
|
||||
.filter( Objects::nonNull )
|
||||
.collect( Collectors.toList() );
|
||||
}
|
||||
}
|
||||
}
|
@ -14,10 +14,8 @@ import java.util.Collections;
|
||||
public interface LuaMethod
|
||||
{
|
||||
Generator<LuaMethod> GENERATOR = new Generator<>( LuaMethod.class, Collections.singletonList( ILuaContext.class ),
|
||||
m -> ( target, context, args ) -> {
|
||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) );
|
||||
return new TaskCallback( id ).pull;
|
||||
} );
|
||||
m -> ( target, context, args ) -> TaskCallback.make( context, () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) )
|
||||
);
|
||||
|
||||
IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
||||
method -> ( instance, context, args ) -> ((IDynamicLuaObject) instance).callMethod( context, method, args )
|
||||
|
@ -8,7 +8,7 @@ package dan200.computercraft.core.asm;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class NamedMethod<T>
|
||||
public final class NamedMethod<T>
|
||||
{
|
||||
private final String name;
|
||||
private final T method;
|
||||
|
@ -19,10 +19,8 @@ import java.util.Arrays;
|
||||
public interface PeripheralMethod
|
||||
{
|
||||
Generator<PeripheralMethod> GENERATOR = new Generator<>( PeripheralMethod.class, Arrays.asList( ILuaContext.class, IComputerAccess.class ),
|
||||
m -> ( target, context, computer, args ) -> {
|
||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) );
|
||||
return new TaskCallback( id ).pull;
|
||||
} );
|
||||
m -> ( target, context, computer, args ) -> TaskCallback.make( context, () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) )
|
||||
);
|
||||
|
||||
IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
||||
method -> ( instance, context, computer, args ) -> ((IDynamicPeripheral) instance).callMethod( computer, context, method, args )
|
||||
|
@ -6,19 +6,17 @@
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
|
||||
class TaskCallback implements ILuaCallback
|
||||
public final class TaskCallback implements ILuaCallback
|
||||
{
|
||||
final MethodResult pull = MethodResult.pullEvent( "task_complete", this );
|
||||
private final MethodResult pull = MethodResult.pullEvent( "task_complete", this );
|
||||
private final long task;
|
||||
|
||||
TaskCallback( long task )
|
||||
private TaskCallback( long task )
|
||||
{
|
||||
this.task = task;
|
||||
}
|
||||
@ -55,4 +53,10 @@ class TaskCallback implements ILuaCallback
|
||||
if( result.getCallback() != null ) throw new IllegalStateException( "Cannot return MethodResult currently" );
|
||||
return result.getResult();
|
||||
}
|
||||
|
||||
public static MethodResult make( ILuaContext context, ILuaTask func ) throws LuaException
|
||||
{
|
||||
long task = context.issueMainThreadTask( func );
|
||||
return new TaskCallback( task ).pull;
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ public final class Config
|
||||
private static final ConfigValue<Boolean> turtlesCanPush;
|
||||
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
||||
|
||||
private static final ConfigValue<Boolean> genericPeripheral;
|
||||
|
||||
private static final ConfigValue<MonitorRenderer> monitorRenderer;
|
||||
|
||||
private static final ForgeConfigSpec serverSpec;
|
||||
@ -260,6 +262,17 @@ public final class Config
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
{
|
||||
builder.comment( "Options for various experimental features. These are not guaranteed to be stable, and may change or be removed across versions." );
|
||||
builder.push( "experimental" );
|
||||
|
||||
genericPeripheral = builder
|
||||
.comment( "Attempt to make any existing block (or tile entity) a peripheral.\n" +
|
||||
"This provides peripheral methods for any inventory, fluid tank or energy storage block. It will" +
|
||||
"_not_ provide methods which have an existing peripheral provider." )
|
||||
.define( "generic_peripherals", false );
|
||||
}
|
||||
|
||||
serverSpec = builder.build();
|
||||
|
||||
Builder clientBuilder = new Builder();
|
||||
@ -322,6 +335,8 @@ public final class Config
|
||||
ComputerCraft.turtleDisabledActions.clear();
|
||||
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
||||
|
||||
ComputerCraft.genericPeripheral = genericPeripheral.get();
|
||||
|
||||
// Client
|
||||
ComputerCraft.monitorRenderer = monitorRenderer.get();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package dan200.computercraft.shared;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
@ -66,7 +67,7 @@ public final class Peripherals
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return CapabilityUtil.unwrap( GenericPeripheralProvider.getPeripheral( world, pos, side ), invalidate );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. 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.tileentity.TileEntity;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
class GenericPeripheral implements IDynamicPeripheral
|
||||
{
|
||||
private final String type;
|
||||
private final TileEntity tile;
|
||||
private final List<SaturatedMethod> methods;
|
||||
|
||||
GenericPeripheral( TileEntity tile, List<SaturatedMethod> methods )
|
||||
{
|
||||
ResourceLocation type = tile.getType().getRegistryName();
|
||||
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 );
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.asm.NamedMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.energy.CapabilityEnergy;
|
||||
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
|
||||
import net.minecraftforge.items.CapabilityItemHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenericPeripheralProvider
|
||||
{
|
||||
private static final Capability<?>[] CAPABILITIES = new Capability<?>[] {
|
||||
CapabilityItemHandler.ITEM_HANDLER_CAPABILITY,
|
||||
CapabilityEnergy.ENERGY,
|
||||
CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY,
|
||||
};
|
||||
|
||||
@Nonnull
|
||||
public static LazyOptional<IPeripheral> getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side )
|
||||
{
|
||||
if( !ComputerCraft.genericPeripheral ) return LazyOptional.empty();
|
||||
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
if( tile == null ) return LazyOptional.empty();
|
||||
|
||||
ArrayList<SaturatedMethod> saturated = new ArrayList<>( 0 );
|
||||
LazyOptional<IPeripheral> peripheral = LazyOptional.of( () -> new GenericPeripheral( tile, saturated ) );
|
||||
|
||||
List<NamedMethod<PeripheralMethod>> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() );
|
||||
if( !tileMethods.isEmpty() ) addSaturated( saturated, tile, tileMethods );
|
||||
|
||||
for( Capability<?> capability : CAPABILITIES )
|
||||
{
|
||||
LazyOptional<?> wrapper = tile.getCapability( capability );
|
||||
wrapper.ifPresent( contents -> {
|
||||
List<NamedMethod<PeripheralMethod>> capabilityMethods = PeripheralMethod.GENERATOR.getMethods( contents.getClass() );
|
||||
if( capabilityMethods.isEmpty() ) return;
|
||||
|
||||
addSaturated( saturated, contents, capabilityMethods );
|
||||
wrapper.addListener( x -> peripheral.invalidate() );
|
||||
} );
|
||||
}
|
||||
|
||||
return saturated.isEmpty() ? LazyOptional.empty() : peripheral;
|
||||
}
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.meta;
|
||||
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FluidMeta
|
||||
{
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasicMeta( @Nonnull T data, @Nonnull FluidStack stack )
|
||||
{
|
||||
data.put( "name", Objects.toString( stack.getFluid().getRegistryName() ) );
|
||||
data.put( "amount", stack.getAmount() );
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.meta;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ItemMeta
|
||||
{
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasicMeta( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
data.put( "name", Objects.toString( stack.getItem().getRegistryName() ) );
|
||||
data.put( "count", stack.getCount() );
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillMeta( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
if( stack.isEmpty() ) return data;
|
||||
|
||||
fillBasicMeta( data, stack );
|
||||
|
||||
data.put( "displayName", stack.getDisplayName().getString() );
|
||||
data.put( "rawName", stack.getTranslationKey() );
|
||||
data.put( "maxCount", stack.getMaxStackSize() );
|
||||
|
||||
if( stack.isDamageable() )
|
||||
{
|
||||
data.put( "damage", stack.getDamage() );
|
||||
data.put( "maxDamage", stack.getMaxDamage() );
|
||||
}
|
||||
|
||||
if( stack.getItem().showDurabilityBar( stack ) )
|
||||
{
|
||||
data.put( "durability", stack.getItem().getDurabilityForDisplay( stack ) );
|
||||
}
|
||||
|
||||
CompoundNBT tag = stack.getTag();
|
||||
if( tag != null && tag.contains( "display", Constants.NBT.TAG_COMPOUND ) )
|
||||
{
|
||||
CompoundNBT displayTag = tag.getCompound( "display" );
|
||||
if( displayTag.contains( "Lore", Constants.NBT.TAG_LIST ) )
|
||||
{
|
||||
ListNBT loreTag = displayTag.getList( "Lore", Constants.NBT.TAG_STRING );
|
||||
data.put( "lore", loreTag.stream()
|
||||
.map( ItemMeta::parseTextComponent )
|
||||
.filter( Objects::nonNull )
|
||||
.map( ITextComponent::getString )
|
||||
.collect( Collectors.toList() ) );
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ITextComponent parseTextComponent( @Nonnull INBT x )
|
||||
{
|
||||
try
|
||||
{
|
||||
return ITextComponent.Serializer.fromJson( x.getString() );
|
||||
}
|
||||
catch( JsonParseException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.ResourceLocationException;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import net.minecraftforge.registries.IForgeRegistryEntry;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* 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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T extends IForgeRegistryEntry<T>> T getRegistryEntry( String name, String typeName, IForgeRegistry<T> registry ) throws LuaException
|
||||
{
|
||||
ResourceLocation id;
|
||||
try
|
||||
{
|
||||
id = new ResourceLocation( name );
|
||||
}
|
||||
catch( ResourceLocationException e )
|
||||
{
|
||||
id = null;
|
||||
}
|
||||
|
||||
T value;
|
||||
if( id == null || !registry.containsKey( id ) || (value = registry.getValue( id )) == null )
|
||||
{
|
||||
throw new LuaException( String.format( "Unknown %s '%s'", typeName, name ) );
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. 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.LuaFunction;
|
||||
import dan200.computercraft.core.asm.GenericSource;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.energy.IEnergyStorage;
|
||||
import net.minecraftforge.versions.forge.ForgeVersion;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@AutoService( GenericSource.class )
|
||||
public class EnergyMethods implements GenericSource
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public ResourceLocation id()
|
||||
{
|
||||
return new ResourceLocation( ForgeVersion.MOD_ID, "energy" );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int getEnergy( IEnergyStorage energy )
|
||||
{
|
||||
return energy.getEnergyStored();
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int getEnergyCapacity( IEnergyStorage energy )
|
||||
{
|
||||
return energy.getMaxEnergyStored();
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. 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.meta.FluidMeta;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
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.getRegistryEntry;
|
||||
|
||||
@AutoService( GenericSource.class )
|
||||
public class FluidMethods implements GenericSource
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public ResourceLocation id()
|
||||
{
|
||||
return new ResourceLocation( ForgeVersion.MOD_ID, "fluid" );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static Map<Integer, Map<String, ?>> tanks( IFluidHandler fluids )
|
||||
{
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
int size = fluids.getTanks();
|
||||
for( int i = 0; i < size; i++ )
|
||||
{
|
||||
FluidStack stack = fluids.getFluidInTank( i );
|
||||
if( !stack.isEmpty() ) result.put( i + 1, FluidMeta.fillBasicMeta( new HashMap<>( 4 ), stack ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pushFluid(
|
||||
IFluidHandler from, IComputerAccess computer,
|
||||
String toName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException
|
||||
{
|
||||
Fluid fluid = fluidName.isPresent()
|
||||
? getRegistryEntry( fluidName.get(), "fluid", ForgeRegistries.FLUIDS )
|
||||
: null;
|
||||
|
||||
// Find location to transfer to
|
||||
IPeripheral location = computer.getAvailablePeripheral( toName );
|
||||
if( location == null ) throw new LuaException( "Target '" + toName + "' does not exist" );
|
||||
|
||||
IFluidHandler to = extractHandler( location.getTarget() );
|
||||
if( to == null ) throw new LuaException( "Target '" + toName + "' is not an tank" );
|
||||
|
||||
int actualLimit = limit.orElse( Integer.MAX_VALUE );
|
||||
if( actualLimit <= 0 ) throw new LuaException( "Limit must be > 0" );
|
||||
|
||||
return fluid == null
|
||||
? moveFluid( from, actualLimit, to )
|
||||
: moveFluid( from, new FluidStack( fluid, actualLimit ), to );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pullFluid(
|
||||
IFluidHandler to, IComputerAccess computer,
|
||||
String fromName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException
|
||||
{
|
||||
Fluid fluid = fluidName.isPresent()
|
||||
? getRegistryEntry( fluidName.get(), "fluid", ForgeRegistries.FLUIDS )
|
||||
: null;
|
||||
|
||||
// Find location to transfer to
|
||||
IPeripheral location = computer.getAvailablePeripheral( fromName );
|
||||
if( location == null ) throw new LuaException( "Target '" + fromName + "' does not exist" );
|
||||
|
||||
IFluidHandler from = extractHandler( location.getTarget() );
|
||||
if( from == null ) throw new LuaException( "Target '" + fromName + "' is not an tank" );
|
||||
|
||||
int actualLimit = limit.orElse( Integer.MAX_VALUE );
|
||||
if( actualLimit <= 0 ) throw new LuaException( "Limit must be > 0" );
|
||||
|
||||
return fluid == null
|
||||
? moveFluid( from, actualLimit, to )
|
||||
: moveFluid( from, new FluidStack( fluid, actualLimit ), to );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IFluidHandler extractHandler( @Nullable Object object )
|
||||
{
|
||||
if( object instanceof ICapabilityProvider )
|
||||
{
|
||||
LazyOptional<IFluidHandler> cap = ((ICapabilityProvider) object).getCapability( CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY );
|
||||
if( cap.isPresent() ) return cap.orElseThrow( NullPointerException::new );
|
||||
}
|
||||
|
||||
if( object instanceof IFluidHandler ) return (IFluidHandler) object;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid( IFluidHandler from, int limit, IFluidHandler to )
|
||||
{
|
||||
return moveFluid( from, from.drain( limit, IFluidHandler.FluidAction.SIMULATE ), limit, to );
|
||||
}
|
||||
|
||||
/**
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param fluid The fluid and limit to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid( IFluidHandler from, FluidStack fluid, IFluidHandler to )
|
||||
{
|
||||
return moveFluid( from, from.drain( fluid, IFluidHandler.FluidAction.SIMULATE ), fluid.getAmount(), to );
|
||||
}
|
||||
|
||||
/**
|
||||
* Move fluid from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param extracted The fluid which is extracted from {@code from}.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param to The handler to move to.
|
||||
* @return The amount of fluid moved.
|
||||
*/
|
||||
private static int moveFluid( IFluidHandler from, FluidStack extracted, int limit, IFluidHandler to )
|
||||
{
|
||||
if( extracted == null || extracted.getAmount() <= 0 ) return 0;
|
||||
|
||||
// Limit the amount to extract.
|
||||
extracted = extracted.copy();
|
||||
extracted.setAmount( Math.min( extracted.getAmount(), limit ) );
|
||||
|
||||
int inserted = to.fill( extracted.copy(), IFluidHandler.FluidAction.EXECUTE );
|
||||
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.
|
||||
extracted.setAmount( inserted );
|
||||
from.drain( extracted, IFluidHandler.FluidAction.EXECUTE );
|
||||
return inserted;
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. 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.meta.ItemMeta;
|
||||
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;
|
||||
|
||||
@AutoService( GenericSource.class )
|
||||
public class InventoryMethods implements GenericSource
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public ResourceLocation id()
|
||||
{
|
||||
return new ResourceLocation( ForgeVersion.MOD_ID, "inventory" );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int size( IItemHandler inventory )
|
||||
{
|
||||
return inventory.getSlots();
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static Map<Integer, Map<String, ?>> list( IItemHandler inventory )
|
||||
{
|
||||
Map<Integer, Map<String, ?>> 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, ItemMeta.fillBasicMeta( new HashMap<>( 4 ), stack ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static Map<String, ?> getItemMeta( 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 : ItemMeta.fillMeta( new HashMap<>(), stack );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pushItems(
|
||||
IItemHandler from, IComputerAccess computer,
|
||||
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> 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 );
|
||||
if( actualLimit <= 0 ) throw new LuaException( "Limit must be > 0" );
|
||||
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)" );
|
||||
|
||||
return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit );
|
||||
}
|
||||
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pullItems(
|
||||
IItemHandler to, IComputerAccess computer,
|
||||
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> 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 );
|
||||
if( actualLimit <= 0 ) throw new LuaException( "Limit must be > 0" );
|
||||
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)" );
|
||||
|
||||
return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IItemHandler extractHandler( @Nullable Object object )
|
||||
{
|
||||
if( object instanceof ICapabilityProvider )
|
||||
{
|
||||
LazyOptional<IItemHandler> 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user