mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-27 09:24:47 +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")
|
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'
|
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
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 String MOD_ID = "computercraft";
|
||||||
|
|
||||||
public static final int DATAFIXER_VERSION = 0;
|
|
||||||
|
|
||||||
// Configuration options
|
// Configuration options
|
||||||
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
|
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
|
||||||
public static final String[] DEFAULT_HTTP_DENY = 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 boolean turtlesCanPush = true;
|
||||||
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
|
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 terminalWidth_computer = 51;
|
||||||
public static final int terminalHeight_computer = 19;
|
public static final int terminalHeight_computer = 19;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.*;
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
|
|
||||||
public class Generator<T>
|
public final class Generator<T>
|
||||||
{
|
{
|
||||||
private static final AtomicInteger METHOD_ID = new AtomicInteger();
|
private static final AtomicInteger METHOD_ID = new AtomicInteger();
|
||||||
|
|
||||||
@ -99,26 +99,28 @@ public class Generator<T>
|
|||||||
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||||
if( annotation == null ) continue;
|
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 );
|
T instance = methodCache.getUnchecked( method ).orElse( null );
|
||||||
if( instance == null ) continue;
|
if( instance == null ) continue;
|
||||||
|
|
||||||
if( methods == null ) methods = new ArrayList<>();
|
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();
|
T instance = methodCache.getUnchecked( method.method ).orElse( null );
|
||||||
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
if( instance == null ) continue;
|
||||||
if( names.length == 0 )
|
|
||||||
{
|
if( methods == null ) methods = new ArrayList<>();
|
||||||
methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
|
addMethod( methods, method.method, method.annotation, instance );
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for( String name : names )
|
|
||||||
{
|
|
||||||
methods.add( new NamedMethod<>( name, instance, isSimple ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( methods == null ) return Collections.emptyList();
|
if( methods == null ) return Collections.emptyList();
|
||||||
@ -126,19 +128,40 @@ public class Generator<T>
|
|||||||
return Collections.unmodifiableList( methods );
|
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
|
@Nonnull
|
||||||
private Optional<T> build( Method method )
|
private Optional<T> build( Method method )
|
||||||
{
|
{
|
||||||
String name = method.getDeclaringClass().getName() + "." + method.getName();
|
String name = method.getDeclaringClass().getName() + "." + method.getName();
|
||||||
int modifiers = method.getModifiers();
|
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 );
|
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();
|
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
|
try
|
||||||
{
|
{
|
||||||
String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement();
|
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();
|
if( bytes == null ) return Optional.empty();
|
||||||
|
|
||||||
Class<?> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() );
|
Class<?> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() );
|
||||||
@ -178,7 +205,7 @@ public class Generator<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] generate( String className, Method method )
|
private byte[] generate( String className, Class<?> target, Method method )
|
||||||
{
|
{
|
||||||
String internalName = className.replace( ".", "/" );
|
String internalName = className.replace( ".", "/" );
|
||||||
|
|
||||||
@ -200,19 +227,27 @@ public class Generator<T>
|
|||||||
{
|
{
|
||||||
MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS );
|
MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS );
|
||||||
mw.visitCode();
|
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;
|
int argIndex = 0;
|
||||||
for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() )
|
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 == null ) return null;
|
||||||
if( loadedArg ) argIndex++;
|
if( loadedArg ) argIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
mw.visitMethodInsn( INVOKEVIRTUAL, Type.getInternalName( method.getDeclaringClass() ), method.getName(),
|
mw.visitMethodInsn(
|
||||||
Type.getMethodDescriptor( method ), false );
|
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 allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
|
||||||
// we convert basic types into an immediate result.
|
// we convert basic types into an immediate result.
|
||||||
@ -250,8 +285,15 @@ public class Generator<T>
|
|||||||
return cw.toByteArray();
|
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 );
|
Class<?> arg = Reflect.getRawType( method, genericArg, true );
|
||||||
if( arg == null ) return null;
|
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
|
public interface LuaMethod
|
||||||
{
|
{
|
||||||
Generator<LuaMethod> GENERATOR = new Generator<>( LuaMethod.class, Collections.singletonList( ILuaContext.class ),
|
Generator<LuaMethod> GENERATOR = new Generator<>( LuaMethod.class, Collections.singletonList( ILuaContext.class ),
|
||||||
m -> ( target, context, args ) -> {
|
m -> ( target, context, args ) -> TaskCallback.make( context, () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) )
|
||||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) );
|
);
|
||||||
return new TaskCallback( id ).pull;
|
|
||||||
} );
|
|
||||||
|
|
||||||
IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
||||||
method -> ( instance, context, args ) -> ((IDynamicLuaObject) instance).callMethod( context, method, args )
|
method -> ( instance, context, args ) -> ((IDynamicLuaObject) instance).callMethod( context, method, args )
|
||||||
|
@ -8,7 +8,7 @@ package dan200.computercraft.core.asm;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class NamedMethod<T>
|
public final class NamedMethod<T>
|
||||||
{
|
{
|
||||||
private final String name;
|
private final String name;
|
||||||
private final T method;
|
private final T method;
|
||||||
|
@ -19,10 +19,8 @@ import java.util.Arrays;
|
|||||||
public interface PeripheralMethod
|
public interface PeripheralMethod
|
||||||
{
|
{
|
||||||
Generator<PeripheralMethod> GENERATOR = new Generator<>( PeripheralMethod.class, Arrays.asList( ILuaContext.class, IComputerAccess.class ),
|
Generator<PeripheralMethod> GENERATOR = new Generator<>( PeripheralMethod.class, Arrays.asList( ILuaContext.class, IComputerAccess.class ),
|
||||||
m -> ( target, context, computer, args ) -> {
|
m -> ( target, context, computer, args ) -> TaskCallback.make( context, () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) )
|
||||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) );
|
);
|
||||||
return new TaskCallback( id ).pull;
|
|
||||||
} );
|
|
||||||
|
|
||||||
IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
||||||
method -> ( instance, context, computer, args ) -> ((IDynamicPeripheral) instance).callMethod( computer, context, method, args )
|
method -> ( instance, context, computer, args ) -> ((IDynamicPeripheral) instance).callMethod( computer, context, method, args )
|
||||||
|
@ -6,19 +6,17 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.asm;
|
package dan200.computercraft.core.asm;
|
||||||
|
|
||||||
import dan200.computercraft.api.lua.ILuaCallback;
|
import dan200.computercraft.api.lua.*;
|
||||||
import dan200.computercraft.api.lua.LuaException;
|
|
||||||
import dan200.computercraft.api.lua.MethodResult;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Arrays;
|
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;
|
private final long task;
|
||||||
|
|
||||||
TaskCallback( long task )
|
private TaskCallback( long task )
|
||||||
{
|
{
|
||||||
this.task = task;
|
this.task = task;
|
||||||
}
|
}
|
||||||
@ -55,4 +53,10 @@ class TaskCallback implements ILuaCallback
|
|||||||
if( result.getCallback() != null ) throw new IllegalStateException( "Cannot return MethodResult currently" );
|
if( result.getCallback() != null ) throw new IllegalStateException( "Cannot return MethodResult currently" );
|
||||||
return result.getResult();
|
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<Boolean> turtlesCanPush;
|
||||||
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
||||||
|
|
||||||
|
private static final ConfigValue<Boolean> genericPeripheral;
|
||||||
|
|
||||||
private static final ConfigValue<MonitorRenderer> monitorRenderer;
|
private static final ConfigValue<MonitorRenderer> monitorRenderer;
|
||||||
|
|
||||||
private static final ForgeConfigSpec serverSpec;
|
private static final ForgeConfigSpec serverSpec;
|
||||||
@ -260,6 +262,17 @@ public final class Config
|
|||||||
builder.pop();
|
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();
|
serverSpec = builder.build();
|
||||||
|
|
||||||
Builder clientBuilder = new Builder();
|
Builder clientBuilder = new Builder();
|
||||||
@ -322,6 +335,8 @@ public final class Config
|
|||||||
ComputerCraft.turtleDisabledActions.clear();
|
ComputerCraft.turtleDisabledActions.clear();
|
||||||
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
||||||
|
|
||||||
|
ComputerCraft.genericPeripheral = genericPeripheral.get();
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
ComputerCraft.monitorRenderer = monitorRenderer.get();
|
ComputerCraft.monitorRenderer = monitorRenderer.get();
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ package dan200.computercraft.shared;
|
|||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||||
|
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
|
||||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||||
import net.minecraft.tileentity.TileEntity;
|
import net.minecraft.tileentity.TileEntity;
|
||||||
import net.minecraft.util.Direction;
|
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