1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-13 19:50:31 +00:00

Allow peripherals to have multiple types (#963)

Peripherals can now have multiple types:
 - A single primary type. This is the same as the current idea of a
   type - some identifier which (mostly) uniquely identifies this kind
   of peripheral. For instance, "speaker" or "minecraft:chest".

 - 0 or more "additional" types. These are more like traits, and
   describe what other behaviour the peripheral has - is it an
   inventory? Does it supply additional peripherals (like a wired
   modem)?.

This is mostly intended for the generic peripheral system, but it might
prove useful elsewhere too - we'll have to see!

 - peripheral.getType (and modem.getTypeRemote) now returns 1 or more
   values, rather than exactly one.
 - Add a new peripheral.hasType (and modem.hasTypeRemote) function which
   determines if a peripheral has the given type (primary or
   additional).
 - Change peripheral.find and all internal peripheral methods to use
   peripheral.hasType instead.
 - Update the peripherals program to show all types

This effectively allows you to do things like
`peripheral.find("inventory")` to find all inventories.

This also rewrites the introduction to the peripheral API, hopefully
making it a little more useful.
This commit is contained in:
Jonathan Coates 2021-11-29 17:37:30 +00:00 committed by GitHub
parent 298f339376
commit 53811f8169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 405 additions and 53 deletions

View File

@ -2,7 +2,7 @@
module: [kind=event] redstone module: [kind=event] redstone
--- ---
The @{redstone} event is fired whenever any redstone inputs on the computer change. The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
## Example ## Example
Prints a message when a redstone input changes: Prints a message when a redstone input changes:

View File

@ -10,6 +10,8 @@ import net.minecraftforge.common.capabilities.Capability;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Set;
/** /**
* The interface that defines a peripheral. * The interface that defines a peripheral.
@ -31,6 +33,18 @@ public interface IPeripheral
@Nonnull @Nonnull
String getType(); String getType();
/**
* Return additional types/traits associated with this object.
*
* @return A collection of additional object traits.
* @see PeripheralType#getAdditionalTypes()
*/
@Nonnull
default Set<String> getAdditionalTypes()
{
return Collections.emptySet();
}
/** /**
* Is called when when a computer is attaching to the peripheral. * Is called when when a computer is attaching to the peripheral.
* *

View File

@ -6,9 +6,13 @@
package dan200.computercraft.api.peripheral; package dan200.computercraft.api.peripheral;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/** /**
* The type of a {@link GenericPeripheral}. * The type of a {@link GenericPeripheral}.
@ -18,13 +22,19 @@ import javax.annotation.Nullable;
*/ */
public final class PeripheralType public final class PeripheralType
{ {
private static final PeripheralType UNTYPED = new PeripheralType( null ); private static final PeripheralType UNTYPED = new PeripheralType( null, Collections.emptySet() );
private final String type; private final String type;
private final Set<String> additionalTypes;
public PeripheralType( String type ) public PeripheralType( String type, Set<String> additionalTypes )
{ {
this.type = type; this.type = type;
this.additionalTypes = additionalTypes;
if( additionalTypes.contains( null ) )
{
throw new IllegalArgumentException( "All additional types must be non-null" );
}
} }
/** /**
@ -46,7 +56,55 @@ public final class PeripheralType
public static PeripheralType ofType( @Nonnull String type ) public static PeripheralType ofType( @Nonnull String type )
{ {
if( Strings.isNullOrEmpty( type ) ) throw new IllegalArgumentException( "type cannot be null or empty" ); if( Strings.isNullOrEmpty( type ) ) throw new IllegalArgumentException( "type cannot be null or empty" );
return new PeripheralType( type ); return new PeripheralType( type, Collections.emptySet() );
}
/**
* Create a new non-empty peripheral type with additional traits.
*
* @param type The name of the type.
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
* @return The constructed peripheral type.
*/
public static PeripheralType ofType( @Nonnull String type, Collection<String> additionalTypes )
{
if( Strings.isNullOrEmpty( type ) ) throw new IllegalArgumentException( "type cannot be null or empty" );
return new PeripheralType( type, ImmutableSet.copyOf( additionalTypes ) );
}
/**
* Create a new non-empty peripheral type with additional traits.
*
* @param type The name of the type.
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
* @return The constructed peripheral type.
*/
public static PeripheralType ofType( @Nonnull String type, @Nonnull String... additionalTypes )
{
if( Strings.isNullOrEmpty( type ) ) throw new IllegalArgumentException( "type cannot be null or empty" );
return new PeripheralType( type, ImmutableSet.copyOf( additionalTypes ) );
}
/**
* Create a new peripheral type with no primary type but additional traits.
*
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
* @return The constructed peripheral type.
*/
public static PeripheralType ofAdditional( Collection<String> additionalTypes )
{
return new PeripheralType( null, ImmutableSet.copyOf( additionalTypes ) );
}
/**
* Create a new peripheral type with no primary type but additional traits.
*
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
* @return The constructed peripheral type.
*/
public static PeripheralType ofAdditional( @Nonnull String... additionalTypes )
{
return new PeripheralType( null, ImmutableSet.copyOf( additionalTypes ) );
} }
/** /**
@ -59,4 +117,15 @@ public final class PeripheralType
{ {
return type; return type;
} }
/**
* Get any additional types or "traits" of this peripheral. These effectively act as a standard set of interfaces
* a peripheral might have.
*
* @return All additional types.
*/
public Set<String> getAdditionalTypes()
{
return additionalTypes;
}
} }

View File

@ -17,6 +17,7 @@ import dan200.computercraft.core.asm.NamedMethod;
import dan200.computercraft.core.asm.PeripheralMethod; import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.LuaUtil;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -36,6 +37,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
private final IPeripheral peripheral; private final IPeripheral peripheral;
private final String type; private final String type;
private final Set<String> additionalTypes;
private final Map<String, PeripheralMethod> methodMap; private final Map<String, PeripheralMethod> methodMap;
private boolean attached; private boolean attached;
@ -47,6 +49,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
attached = false; attached = false;
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" ); type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
additionalTypes = peripheral.getAdditionalTypes();
methodMap = PeripheralAPI.getMethods( peripheral ); methodMap = PeripheralAPI.getMethods( peripheral );
} }
@ -61,6 +64,11 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
return type; return type;
} }
public Set<String> getAdditionalTypes()
{
return additionalTypes;
}
public Collection<String> getMethods() public Collection<String> getMethods()
{ {
return methodMap.keySet(); return methodMap.keySet();
@ -298,7 +306,23 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
synchronized( peripherals ) synchronized( peripherals )
{ {
PeripheralWrapper p = peripherals[side.ordinal()]; PeripheralWrapper p = peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getType() }; return p == null ? null : LuaUtil.consArray( p.getType(), p.getAdditionalTypes() );
}
}
@LuaFunction
public final Object[] hasType( String sideName, String type )
{
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
if( side == null ) return null;
synchronized( peripherals )
{
PeripheralWrapper p = peripherals[side.ordinal()];
if( p != null )
{
return new Object[] { p.getType().equals( type ) || p.getAdditionalTypes().contains( type ) };
}
} }
return null; return null;
} }

View File

@ -18,18 +18,21 @@ import net.minecraft.util.ResourceLocation;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.Set;
class GenericPeripheral implements IDynamicPeripheral class GenericPeripheral implements IDynamicPeripheral
{ {
private final String type; private final String type;
private final Set<String> additionalTypes;
private final TileEntity tile; private final TileEntity tile;
private final List<SaturatedMethod> methods; private final List<SaturatedMethod> methods;
GenericPeripheral( TileEntity tile, String name, List<SaturatedMethod> methods ) GenericPeripheral( TileEntity tile, String name, Set<String> additionalTypes, List<SaturatedMethod> methods )
{ {
ResourceLocation type = tile.getType().getRegistryName(); ResourceLocation type = tile.getType().getRegistryName();
this.tile = tile; this.tile = tile;
this.type = name != null ? name : (type != null ? type.toString() : "unknown"); this.type = name != null ? name : (type != null ? type.toString() : "unknown");
this.additionalTypes = additionalTypes;
this.methods = methods; this.methods = methods;
} }
@ -56,6 +59,13 @@ class GenericPeripheral implements IDynamicPeripheral
return type; return type;
} }
@Nonnull
@Override
public Set<String> getAdditionalTypes()
{
return additionalTypes;
}
@Nullable @Nullable
@Override @Override
public Object getTarget() public Object getTarget()

View File

@ -20,9 +20,7 @@ import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Objects;
public class GenericPeripheralProvider public class GenericPeripheralProvider
{ {
@ -62,15 +60,16 @@ public class GenericPeripheralProvider
private static class GenericPeripheralBuilder private static class GenericPeripheralBuilder
{ {
String name; private String name;
final ArrayList<SaturatedMethod> methods = new ArrayList<>( 0 ); private final Set<String> additionalTypes = new HashSet<>( 0 );
private final ArrayList<SaturatedMethod> methods = new ArrayList<>( 0 );
IPeripheral toPeripheral( TileEntity tile ) IPeripheral toPeripheral( TileEntity tile )
{ {
if( methods.isEmpty() ) return null; if( methods.isEmpty() ) return null;
methods.trimToSize(); methods.trimToSize();
return new GenericPeripheral( tile, name, methods ); return new GenericPeripheral( tile, name, additionalTypes, methods );
} }
void addMethods( Object target, List<NamedMethod<PeripheralMethod>> methods ) void addMethods( Object target, List<NamedMethod<PeripheralMethod>> methods )
@ -89,6 +88,7 @@ public class GenericPeripheralProvider
String name = type.getPrimaryType(); String name = type.getPrimaryType();
if( this.name == null || this.name.compareTo( name ) > 0 ) this.name = name; if( this.name == null || this.name.compareTo( name ) > 0 ) this.name = name;
} }
if( type != null ) additionalTypes.addAll( type.getAdditionalTypes() );
} }
} }
} }

View File

@ -6,8 +6,9 @@
package dan200.computercraft.shared.peripheral.generic.methods; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.energy.IEnergyStorage; import net.minecraftforge.energy.IEnergyStorage;
@ -25,8 +26,15 @@ import javax.annotation.Nonnull;
* *
* @cc.module energy_storage * @cc.module energy_storage
*/ */
public class EnergyMethods implements GenericSource public class EnergyMethods implements GenericPeripheral
{ {
@Nonnull
@Override
public PeripheralType getType()
{
return PeripheralType.ofAdditional( "energy_storage" );
}
@Nonnull @Nonnull
@Override @Override
public ResourceLocation id() public ResourceLocation id()

View File

@ -6,11 +6,12 @@
package dan200.computercraft.shared.peripheral.generic.methods; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.generic.data.FluidData; import dan200.computercraft.shared.peripheral.generic.data.FluidData;
import net.minecraft.fluid.Fluid; import net.minecraft.fluid.Fluid;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
@ -35,8 +36,15 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel
* *
* @cc.module fluid_storage * @cc.module fluid_storage
*/ */
public class FluidMethods implements GenericSource public class FluidMethods implements GenericPeripheral
{ {
@Nonnull
@Override
public PeripheralType getType()
{
return PeripheralType.ofAdditional( "fluid_storage" );
}
@Nonnull @Nonnull
@Override @Override
public ResourceLocation id() public ResourceLocation id()

View File

@ -6,11 +6,12 @@
package dan200.computercraft.shared.peripheral.generic.methods; package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.generic.data.ItemData; import dan200.computercraft.shared.peripheral.generic.data.ItemData;
import net.minecraft.inventory.IInventory; import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -36,8 +37,15 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel
* *
* @cc.module inventory * @cc.module inventory
*/ */
public class InventoryMethods implements GenericSource public class InventoryMethods implements GenericPeripheral
{ {
@Nonnull
@Override
public PeripheralType getType()
{
return PeripheralType.ofAdditional( "inventory" );
}
@Nonnull @Nonnull
@Override @Override
public ResourceLocation id() public ResourceLocation id()

View File

@ -21,6 +21,7 @@ import dan200.computercraft.core.apis.PeripheralAPI;
import dan200.computercraft.core.asm.PeripheralMethod; import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.LuaUtil;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -118,13 +119,35 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
* @param name The peripheral's name. * @param name The peripheral's name.
* @return The peripheral's name. * @return The peripheral's name.
* @cc.treturn string|nil The peripheral's type, or {@code nil} if it is not present. * @cc.treturn string|nil The peripheral's type, or {@code nil} if it is not present.
* @cc.changed 1.99 Peripherals can have multiple types - this function returns multiple values.
* @see PeripheralAPI#getType * @see PeripheralAPI#getType
*/ */
@LuaFunction @LuaFunction
public final Object[] getTypeRemote( IComputerAccess computer, String name ) public final Object[] getTypeRemote( IComputerAccess computer, String name )
{ {
RemotePeripheralWrapper wrapper = getWrapper( computer, name ); RemotePeripheralWrapper wrapper = getWrapper( computer, name );
return wrapper != null ? new Object[] { wrapper.getType() } : null; return wrapper == null ? null : LuaUtil.consArray( wrapper.getType(), wrapper.getAdditionalTypes() );
}
/**
* Check a peripheral is of a particular type.
*
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
* returns false before calling it.</blockquote>
*
* @param computer The calling computer.
* @param name The peripheral's name.
* @param type The type to check.
* @return The peripheral's name.
* @cc.treturn boolean|nil If a peripheral has a particular type, or {@literal nil} if it is not present.
* @cc.since 1.99
* @see PeripheralAPI#getType
*/
@LuaFunction
public final Object[] hasTypeRemote( IComputerAccess computer, String name, String type )
{
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
return wrapper == null ? null : new Object[] { wrapper.getType().equals( type ) || wrapper.getAdditionalTypes().contains( getType() ) };
} }
/** /**
@ -308,6 +331,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
private final String name; private final String name;
private final String type; private final String type;
private final Set<String> additionalTypes;
private final Map<String, PeripheralMethod> methodMap; private final Map<String, PeripheralMethod> methodMap;
private volatile boolean attached; private volatile boolean attached;
@ -321,6 +345,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
this.name = name; this.name = name;
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" ); type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
additionalTypes = peripheral.getAdditionalTypes();
methodMap = PeripheralAPI.getMethods( peripheral ); methodMap = PeripheralAPI.getMethods( peripheral );
} }
@ -354,6 +379,11 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
return type; return type;
} }
public Set<String> getAdditionalTypes()
{
return additionalTypes;
}
public Collection<String> getMethodNames() public Collection<String> getMethodNames()
{ {
return methodMap.keySet(); return methodMap.keySet();

View File

@ -0,0 +1,23 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import java.util.Collection;
public class LuaUtil
{
public static Object[] consArray( Object value, Collection<?> rest )
{
if( rest.isEmpty() ) return new Object[] { value };
// I'm not proud of this code.
Object[] out = new Object[rest.size() + 1];
out[0] = value;
int i = 1;
for( Object additionalType : rest ) out[i++] = additionalType;
return out;
}
}

View File

@ -1,18 +1,90 @@
--- The Peripheral API is for interacting with peripherals connected to the --[[- Peripherals are blocks (or turtle and pocket computer upgrades) which can
-- computer, such as the Disk Drive, the Advanced Monitor and Monitor. be controlled by a computer. For instance, the @{speaker} peripheral allows a
-- computer to play music and the @{monitor} peripheral allows you to display text
-- Each peripheral block has a name, either referring to the side the peripheral in the world.
-- can be found on, or a name on an adjacent wired network.
-- ## Referencing peripherals
-- If the peripheral is next to the computer, its side is either `front`,
-- `back`, `left`, `right`, `top` or `bottom`. If the peripheral is attached by Computers can interact with adjacent peripherals. Each peripheral is given a
-- a cable, its side will follow the format `type_id`, for example `printer_0`. name based on which direction it is in. For instance, a disk drive below your
-- computer will be called `"bottom"` in your Lua code, one to the left called
-- Peripheral functions are called *methods*, a term borrowed from Java. `"left"` , and so on for all 6 directions (`"bottom"`, `"top"`, `"left"`,
-- `"right"`, `"front"`, `"back"`).
-- @module peripheral
-- @since 1.3 You can list the names of all peripherals with the `peripherals` program, or the
-- @changed 1.51 Add support for wired modems. @{peripheral.getNames} function.
It's also possible to use peripherals which are further away from your computer
through the use of @{modem|Wired Modems}. Place one modem against your computer,
run Networking Cable to your peripheral, and then place another modem against
that block. You can then right click the modem to use (or *attach*) the
peripheral. This will print a peripheral name to chat, which can then be used
just like a direction name to access the peripheral. You can click on the message
to copy the name to your clipboard.
## Using peripherals
Once you have the name of a peripheral, you can call functions on it using the
@{peripheral.call} function. This takes the name of our peripheral, the name of
the function we want to call, and then its arguments.
> Some bits of the peripheral API call peripheral functions *methods* instead
> (for example, the @{peripheral.getMethods} function). Don't worry, they're the
> same thing!
Let's say we have a monitor above our computer (and so "top") and want to
@{monitor.write|write some text to it}. We'd write the following:
```lua
peripheral.call("top", "write", "This is displayed on a monitor!")
```
Once you start calling making a couple of peripheral calls this can get very
repetitive, and so we can @{peripheral.wrap|wrap} a peripheral. This builds a
table of all the peripheral's functions so you can use it like an API or module.
For instance, we could have written the above example as follows:
```lua
local my_monitor = peripheral.wrap("top")
my_monitor.write("This is displayed on a monitor!")
```
## Finding peripherals
Sometimes when you're writing a program you don't care what a peripheral is
called, you just need to know it's there. For instance, if you're writing a
music player, you just need a speaker - it doesn't matter if it's above or below
the computer.
Thankfully there's a quick way to do this: @{peripheral.find}. This takes a
*peripheral type* and returns all the attached peripherals which are of this
type.
What is a peripheral type though? This is a string which describes what a
peripheral is, and so what functions are available on it. For instance, speakers
are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might
have more than one type; a Minecraft chest is both a `"minecraft:chest"` and
`"inventory"`.
You can get all the types a peripheral has with @{peripheral.getType}, and check
a peripheral is a specific type with @{peripheral.hasType}.
To return to our original example, let's use @{peripheral.find} to find an
attached speaker:
```lua
local speaker = peripheral.find("speaker")
speaker.playNote("harp")
```
@module peripheral
@see event!peripheral This event is fired whenever a new peripheral is attached.
@see event!peripheral_detach This event is fired whenever a peripheral is detached.
@since 1.3
@changed 1.51 Add support for wired modems.
@changed 1.99 Peripherals can have multiple types.
]]
local expect = dofile("rom/modules/main/cc/expect.lua").expect local expect = dofile("rom/modules/main/cc/expect.lua").expect
@ -33,7 +105,7 @@ function getNames()
local side = sides[n] local side = sides[n]
if native.isPresent(side) then if native.isPresent(side) then
table.insert(results, side) table.insert(results, side)
if native.getType(side) == "modem" and not native.call(side, "isWireless") then if native.hasType(side, "modem") and not native.call(side, "isWireless") then
local remote = native.call(side, "getNamesRemote") local remote = native.call(side, "getNamesRemote")
for _, name in ipairs(remote) do for _, name in ipairs(remote) do
table.insert(results, name) table.insert(results, name)
@ -58,7 +130,7 @@ function isPresent(name)
for n = 1, #sides do for n = 1, #sides do
local side = sides[n] local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and if native.hasType(side, "modem") and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", name) native.call(side, "isPresentRemote", name)
then then
return true return true
@ -67,12 +139,17 @@ function isPresent(name)
return false return false
end end
--- Get the type of a wrapped peripheral, or a peripheral with the given name. --[[- Get the types of a named or wrapped peripheral.
--
-- @tparam string|table peripheral The name of the peripheral to find, or a @tparam string|table peripheral The name of the peripheral to find, or a
-- wrapped peripheral instance. wrapped peripheral instance.
-- @treturn string|nil The peripheral's type, or `nil` if it is not present. @treturn string... The peripheral's types, or `nil` if it is not present.
-- @changed 1.88.0 Accepts a wrapped peripheral as an argument. @changed 1.88.0 Accepts a wrapped peripheral as an argument.
@changed 1.99 Now returns multiple types.
@usage Get the type of a peripheral above this computer.
peripheral.getType("top")
]]
function getType(peripheral) function getType(peripheral)
expect(1, peripheral, "string", "table") expect(1, peripheral, "string", "table")
if type(peripheral) == "string" then -- Peripheral name passed if type(peripheral) == "string" then -- Peripheral name passed
@ -81,7 +158,7 @@ function getType(peripheral)
end end
for n = 1, #sides do for n = 1, #sides do
local side = sides[n] local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and if native.hasType(side, "modem") and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", peripheral) native.call(side, "isPresentRemote", peripheral)
then then
return native.call(side, "getTypeRemote", peripheral) return native.call(side, "getTypeRemote", peripheral)
@ -90,10 +167,43 @@ function getType(peripheral)
return nil return nil
else else
local mt = getmetatable(peripheral) local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.type) ~= "string" then if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2) error("bad argument #1 (table is not a peripheral)", 2)
end end
return mt.type return table.unpack(mt.types)
end
end
--[[- Check if a peripheral is of a particular type.
@tparam string|table peripheral The name of the peripheral or a wrapped peripheral instance.
@tparam string peripheral_type The type to check.
@treturn boolean|nil If a peripheral has a particular type, or `nil` if it is not present.
@since 1.99
]]
function hasType(peripheral, peripheral_type)
expect(1, peripheral, "string", "table")
expect(2, peripheral_type, "string")
if type(peripheral) == "string" then -- Peripheral name passed
if native.isPresent(peripheral) then
return native.hasType(peripheral, peripheral_type)
end
for n = 1, #sides do
local side = sides[n]
if native.hasType(side, "modem") and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", peripheral)
then
return native.call(side, "hasTypeRemote", peripheral, peripheral_type)
end
end
return nil
else
local mt = getmetatable(peripheral)
if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then
error("bad argument #1 (table is not a peripheral)", 2)
end
return mt.types[peripheral_type] ~= nil
end end
end end
@ -109,7 +219,7 @@ function getMethods(name)
end end
for n = 1, #sides do for n = 1, #sides do
local side = sides[n] local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and if native.hasType(side, "modem") and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", name) native.call(side, "isPresentRemote", name)
then then
return native.call(side, "getMethodsRemote", name) return native.call(side, "getMethodsRemote", name)
@ -151,7 +261,7 @@ function call(name, method, ...)
for n = 1, #sides do for n = 1, #sides do
local side = sides[n] local side = sides[n]
if native.getType(side) == "modem" and not native.call(side, "isWireless") and if native.hasType(side, "modem") and not native.call(side, "isWireless") and
native.call(side, "isPresentRemote", name) native.call(side, "isPresentRemote", name)
then then
return native.call(side, "callRemote", name, method, ...) return native.call(side, "callRemote", name, method, ...)
@ -160,15 +270,16 @@ function call(name, method, ...)
return nil return nil
end end
--- Get a table containing functions pointing to the peripheral's methods, which --- Get a table containing all functions available on a peripheral. These can
-- can then be called as if using @{peripheral.call}. -- then be called instead of using @{peripheral.call} every time.
-- --
-- @tparam string name The name of the peripheral to wrap. -- @tparam string name The name of the peripheral to wrap.
-- @treturn table|nil The table containing the peripheral's methods, or `nil` if -- @treturn table|nil The table containing the peripheral's methods, or `nil` if
-- there is no peripheral present with the given name. -- there is no peripheral present with the given name.
-- @usage Open the modem on the top of this computer. -- @usage Open the modem on the top of this computer.
-- --
-- peripheral.wrap("top").open(1) -- local modem = peripheral.wrap("top")
-- modem.open(1)
function wrap(name) function wrap(name)
expect(1, name, "string") expect(1, name, "string")
@ -177,10 +288,14 @@ function wrap(name)
return nil return nil
end end
-- We store our types array as a list (for getType) and a lookup table (for hasType).
local types = { peripheral.getType(name) }
for i = 1, #types do types[types[i]] = true end
local result = setmetatable({}, { local result = setmetatable({}, {
__name = "peripheral", __name = "peripheral",
name = name, name = name,
type = peripheral.getType(name), type = types[1],
types = types,
}) })
for _, method in ipairs(methods) do for _, method in ipairs(methods) do
result[method] = function(...) result[method] = function(...)
@ -222,7 +337,7 @@ function find(ty, filter)
local results = {} local results = {}
for _, name in ipairs(peripheral.getNames()) do for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == ty then if peripheral.hasType(name, ty) then
local wrapped = peripheral.wrap(name) local wrapped = peripheral.wrap(name)
if filter == nil or filter(name, wrapped) then if filter == nil or filter(name, wrapped) then
table.insert(results, wrapped) table.insert(results, wrapped)

View File

@ -3,7 +3,7 @@ print("Attached Peripherals:")
if #tPeripherals > 0 then if #tPeripherals > 0 then
for n = 1, #tPeripherals do for n = 1, #tPeripherals do
local sPeripheral = tPeripherals[n] local sPeripheral = tPeripherals[n]
print(sPeripheral .. " (" .. peripheral.getType(sPeripheral) .. ")") print(sPeripheral .. " (" .. table.concat({ peripheral.getType(sPeripheral) }, ", ") .. ")")
end end
else else
print("None") print("None")

View File

@ -1,6 +1,13 @@
describe("The peripheral library", function() describe("The peripheral library", function()
local it_modem = peripheral.getType("top") == "modem" and it or pending local it_modem = peripheral.getType("top") == "modem" and it or pending
local multitype_peripheral = setmetatable({}, {
__name = "peripheral",
name = "top",
type = "modem",
types = { "modem", "inventory", modem = true, inventory = true },
})
describe("peripheral.isPresent", function() describe("peripheral.isPresent", function()
it("validates arguments", function() it("validates arguments", function()
peripheral.isPresent("") peripheral.isPresent("")
@ -26,6 +33,10 @@ describe("The peripheral library", function()
expect.error(peripheral.getType, {}):eq("bad argument #1 (table is not a peripheral)") expect.error(peripheral.getType, {}):eq("bad argument #1 (table is not a peripheral)")
end) end)
it("returns nil when no peripheral is present", function()
expect(peripheral.getType("bottom")):eq(nil)
end)
it_modem("can get the type of a peripheral by side", function() it_modem("can get the type of a peripheral by side", function()
expect(peripheral.getType("top")):eq("modem") expect(peripheral.getType("top")):eq("modem")
end) end)
@ -33,6 +44,38 @@ describe("The peripheral library", function()
it_modem("can get the type of a wrapped peripheral", function() it_modem("can get the type of a wrapped peripheral", function()
expect(peripheral.getType(peripheral.wrap("top"))):eq("modem") expect(peripheral.getType(peripheral.wrap("top"))):eq("modem")
end) end)
it("can return multiple types", function()
expect({ peripheral.getType(multitype_peripheral) }):same { "modem", "inventory" }
end)
end)
describe("peripheral.hasType", function()
it("validates arguments", function()
peripheral.getType("")
expect.error(peripheral.hasType, nil):eq("bad argument #1 (expected string or table, got nil)")
expect.error(peripheral.hasType, {}, ""):eq("bad argument #1 (table is not a peripheral)")
expect.error(peripheral.hasType, ""):eq("bad argument #2 (expected string, got nil)")
end)
it("returns nil when no peripherals are present", function()
expect(peripheral.hasType("bottom", "modem")):eq(nil)
end)
it_modem("can check type of a peripheral by side", function()
expect(peripheral.hasType("top", "modem")):eq(true)
expect(peripheral.hasType("top", "not_a_modem")):eq(false)
end)
it_modem("can check the type of a wrapped peripheral (true)", function()
expect(peripheral.hasType(peripheral.wrap("top"), "modem")):eq(true)
end)
it("can check the type of a wrapped peripheral (fake)", function()
expect(peripheral.hasType(multitype_peripheral, "modem")):eq(true)
expect(peripheral.hasType(multitype_peripheral, "inventory")):eq(true)
expect(peripheral.hasType(multitype_peripheral, "something else")):eq(false)
end)
end) end)
describe("peripheral.getMethods", function() describe("peripheral.getMethods", function()