mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-29 12:57:46 +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:
		| @@ -2,7 +2,7 @@ | ||||
| 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 | ||||
| Prints a message when a redstone input changes: | ||||
|   | ||||
| @@ -10,6 +10,8 @@ import net.minecraftforge.common.capabilities.Capability; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Collections; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * The interface that defines a peripheral. | ||||
| @@ -31,6 +33,18 @@ public interface IPeripheral | ||||
|     @Nonnull | ||||
|     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. | ||||
|      * | ||||
|   | ||||
| @@ -6,9 +6,13 @@ | ||||
| package dan200.computercraft.api.peripheral; | ||||
| 
 | ||||
| import com.google.common.base.Strings; | ||||
| import com.google.common.collect.ImmutableSet; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * The type of a {@link GenericPeripheral}. | ||||
| @@ -18,13 +22,19 @@ import javax.annotation.Nullable; | ||||
|  */ | ||||
| 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 Set<String> additionalTypes; | ||||
| 
 | ||||
|     public PeripheralType( String type ) | ||||
|     public PeripheralType( String type, Set<String> additionalTypes ) | ||||
|     { | ||||
|         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 ) | ||||
|     { | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import dan200.computercraft.core.asm.NamedMethod; | ||||
| import dan200.computercraft.core.asm.PeripheralMethod; | ||||
| import dan200.computercraft.core.computer.ComputerSide; | ||||
| import dan200.computercraft.core.tracking.TrackingField; | ||||
| import dan200.computercraft.shared.util.LuaUtil; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| @@ -36,6 +37,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange | ||||
|         private final IPeripheral peripheral; | ||||
| 
 | ||||
|         private final String type; | ||||
|         private final Set<String> additionalTypes; | ||||
|         private final Map<String, PeripheralMethod> methodMap; | ||||
|         private boolean attached; | ||||
| 
 | ||||
| @@ -47,6 +49,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange | ||||
|             attached = false; | ||||
| 
 | ||||
|             type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" ); | ||||
|             additionalTypes = peripheral.getAdditionalTypes(); | ||||
| 
 | ||||
|             methodMap = PeripheralAPI.getMethods( peripheral ); | ||||
|         } | ||||
| @@ -61,6 +64,11 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange | ||||
|             return type; | ||||
|         } | ||||
| 
 | ||||
|         public Set<String> getAdditionalTypes() | ||||
|         { | ||||
|             return additionalTypes; | ||||
|         } | ||||
| 
 | ||||
|         public Collection<String> getMethods() | ||||
|         { | ||||
|             return methodMap.keySet(); | ||||
| @@ -298,7 +306,23 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange | ||||
|         synchronized( peripherals ) | ||||
|         { | ||||
|             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; | ||||
|     } | ||||
|   | ||||
| @@ -18,18 +18,21 @@ import net.minecraft.util.ResourceLocation; | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| class GenericPeripheral implements IDynamicPeripheral | ||||
| { | ||||
|     private final String type; | ||||
|     private final Set<String> additionalTypes; | ||||
|     private final TileEntity tile; | ||||
|     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(); | ||||
|         this.tile = tile; | ||||
|         this.type = name != null ? name : (type != null ? type.toString() : "unknown"); | ||||
|         this.additionalTypes = additionalTypes; | ||||
|         this.methods = methods; | ||||
|     } | ||||
| 
 | ||||
| @@ -56,6 +59,13 @@ class GenericPeripheral implements IDynamicPeripheral | ||||
|         return type; | ||||
|     } | ||||
| 
 | ||||
|     @Nonnull | ||||
|     @Override | ||||
|     public Set<String> getAdditionalTypes() | ||||
|     { | ||||
|         return additionalTypes; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public Object getTarget() | ||||
|   | ||||
| @@ -20,9 +20,7 @@ import net.minecraftforge.common.util.NonNullConsumer; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.*; | ||||
| 
 | ||||
| public class GenericPeripheralProvider | ||||
| { | ||||
| @@ -62,15 +60,16 @@ public class GenericPeripheralProvider | ||||
| 
 | ||||
|     private static class GenericPeripheralBuilder | ||||
|     { | ||||
|         String name; | ||||
|         final ArrayList<SaturatedMethod> methods = new ArrayList<>( 0 ); | ||||
|         private String name; | ||||
|         private final Set<String> additionalTypes = new HashSet<>( 0 ); | ||||
|         private final ArrayList<SaturatedMethod> methods = new ArrayList<>( 0 ); | ||||
| 
 | ||||
|         IPeripheral toPeripheral( TileEntity tile ) | ||||
|         { | ||||
|             if( methods.isEmpty() ) return null; | ||||
| 
 | ||||
|             methods.trimToSize(); | ||||
|             return new GenericPeripheral( tile, name, methods ); | ||||
|             return new GenericPeripheral( tile, name, additionalTypes, methods ); | ||||
|         } | ||||
| 
 | ||||
|         void addMethods( Object target, List<NamedMethod<PeripheralMethod>> methods ) | ||||
| @@ -89,6 +88,7 @@ public class GenericPeripheralProvider | ||||
|                     String name = type.getPrimaryType(); | ||||
|                     if( this.name == null || this.name.compareTo( name ) > 0 ) this.name = name; | ||||
|                 } | ||||
|                 if( type != null ) additionalTypes.addAll( type.getAdditionalTypes() ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -6,8 +6,9 @@ | ||||
| package dan200.computercraft.shared.peripheral.generic.methods; | ||||
| 
 | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.api.lua.GenericSource; | ||||
| 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.minecraftforge.energy.IEnergyStorage; | ||||
| 
 | ||||
| @@ -25,8 +26,15 @@ import javax.annotation.Nonnull; | ||||
|  * | ||||
|  * @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 | ||||
|     @Override | ||||
|     public ResourceLocation id() | ||||
|   | ||||
| @@ -6,11 +6,12 @@ | ||||
| package dan200.computercraft.shared.peripheral.generic.methods; | ||||
| 
 | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.api.lua.GenericSource; | ||||
| import dan200.computercraft.api.lua.LuaException; | ||||
| import dan200.computercraft.api.lua.LuaFunction; | ||||
| import dan200.computercraft.api.peripheral.GenericPeripheral; | ||||
| import dan200.computercraft.api.peripheral.IComputerAccess; | ||||
| import dan200.computercraft.api.peripheral.IPeripheral; | ||||
| import dan200.computercraft.api.peripheral.PeripheralType; | ||||
| import dan200.computercraft.shared.peripheral.generic.data.FluidData; | ||||
| import net.minecraft.fluid.Fluid; | ||||
| import net.minecraft.tileentity.TileEntity; | ||||
| @@ -35,8 +36,15 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel | ||||
|  * | ||||
|  * @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 | ||||
|     @Override | ||||
|     public ResourceLocation id() | ||||
|   | ||||
| @@ -6,11 +6,12 @@ | ||||
| package dan200.computercraft.shared.peripheral.generic.methods; | ||||
| 
 | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.api.lua.GenericSource; | ||||
| import dan200.computercraft.api.lua.LuaException; | ||||
| import dan200.computercraft.api.lua.LuaFunction; | ||||
| import dan200.computercraft.api.peripheral.GenericPeripheral; | ||||
| import dan200.computercraft.api.peripheral.IComputerAccess; | ||||
| import dan200.computercraft.api.peripheral.IPeripheral; | ||||
| import dan200.computercraft.api.peripheral.PeripheralType; | ||||
| import dan200.computercraft.shared.peripheral.generic.data.ItemData; | ||||
| import net.minecraft.inventory.IInventory; | ||||
| import net.minecraft.item.ItemStack; | ||||
| @@ -36,8 +37,15 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel | ||||
|  * | ||||
|  * @cc.module inventory | ||||
|  */ | ||||
| public class InventoryMethods implements GenericSource | ||||
| public class InventoryMethods implements GenericPeripheral | ||||
| { | ||||
|     @Nonnull | ||||
|     @Override | ||||
|     public PeripheralType getType() | ||||
|     { | ||||
|         return PeripheralType.ofAdditional( "inventory" ); | ||||
|     } | ||||
| 
 | ||||
|     @Nonnull | ||||
|     @Override | ||||
|     public ResourceLocation id() | ||||
|   | ||||
| @@ -21,6 +21,7 @@ import dan200.computercraft.core.apis.PeripheralAPI; | ||||
| import dan200.computercraft.core.asm.PeripheralMethod; | ||||
| import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; | ||||
| import dan200.computercraft.shared.peripheral.modem.ModemState; | ||||
| import dan200.computercraft.shared.util.LuaUtil; | ||||
| import net.minecraft.world.World; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| @@ -118,13 +119,35 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|      * @param name     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.changed 1.99 Peripherals can have multiple types - this function returns multiple values. | ||||
|      * @see PeripheralAPI#getType | ||||
|      */ | ||||
|     @LuaFunction | ||||
|     public final Object[] getTypeRemote( IComputerAccess computer, String 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 type; | ||||
|         private final Set<String> additionalTypes; | ||||
|         private final Map<String, PeripheralMethod> methodMap; | ||||
| 
 | ||||
|         private volatile boolean attached; | ||||
| @@ -321,6 +345,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|             this.name = name; | ||||
| 
 | ||||
|             type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" ); | ||||
|             additionalTypes = peripheral.getAdditionalTypes(); | ||||
|             methodMap = PeripheralAPI.getMethods( peripheral ); | ||||
|         } | ||||
| 
 | ||||
| @@ -354,6 +379,11 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|             return type; | ||||
|         } | ||||
| 
 | ||||
|         public Set<String> getAdditionalTypes() | ||||
|         { | ||||
|             return additionalTypes; | ||||
|         } | ||||
| 
 | ||||
|         public Collection<String> getMethodNames() | ||||
|         { | ||||
|             return methodMap.keySet(); | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/main/java/dan200/computercraft/shared/util/LuaUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/main/java/dan200/computercraft/shared/util/LuaUtil.java
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +1,90 @@ | ||||
| --- The Peripheral API is for interacting with peripherals connected to the | ||||
| -- computer, such as the Disk Drive, the Advanced Monitor and Monitor. | ||||
| -- | ||||
| -- Each peripheral block has a name, either referring to the side the peripheral | ||||
| -- can be found on, or a name on an adjacent wired network. | ||||
| -- | ||||
| -- 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 | ||||
| -- a cable, its side will follow the format `type_id`, for example `printer_0`. | ||||
| -- | ||||
| -- Peripheral functions are called *methods*, a term borrowed from Java. | ||||
| -- | ||||
| -- @module peripheral | ||||
| -- @since 1.3 | ||||
| -- @changed 1.51 Add support for wired modems. | ||||
| --[[- Peripherals are blocks (or turtle and pocket computer upgrades) which can | ||||
| 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 | ||||
| in the world. | ||||
|  | ||||
| ## Referencing peripherals | ||||
|  | ||||
| Computers can interact with adjacent peripherals. Each peripheral is given a | ||||
| 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 | ||||
| `"left"` , and so on for all 6 directions (`"bottom"`, `"top"`, `"left"`, | ||||
| `"right"`, `"front"`, `"back"`). | ||||
|  | ||||
| You can list the names of all peripherals with the `peripherals` program, or the | ||||
| @{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 | ||||
|  | ||||
| @@ -33,7 +105,7 @@ function getNames() | ||||
|         local side = sides[n] | ||||
|         if native.isPresent(side) then | ||||
|             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") | ||||
|                 for _, name in ipairs(remote) do | ||||
|                     table.insert(results, name) | ||||
| @@ -58,7 +130,7 @@ function isPresent(name) | ||||
|  | ||||
|     for n = 1, #sides do | ||||
|         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) | ||||
|         then | ||||
|             return true | ||||
| @@ -67,12 +139,17 @@ function isPresent(name) | ||||
|     return false | ||||
| end | ||||
|  | ||||
| --- Get the type of a wrapped peripheral, or a peripheral with the given name. | ||||
| -- | ||||
| -- @tparam string|table peripheral The name of the peripheral to find, or a | ||||
| -- wrapped peripheral instance. | ||||
| -- @treturn string|nil The peripheral's type, or `nil` if it is not present. | ||||
| -- @changed 1.88.0 Accepts a wrapped peripheral as an argument. | ||||
| --[[- Get the types of a named or wrapped peripheral. | ||||
|  | ||||
| @tparam string|table peripheral The name of the peripheral to find, or a | ||||
| wrapped peripheral instance. | ||||
| @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.99 Now returns multiple types. | ||||
| @usage Get the type of a peripheral above this computer. | ||||
|  | ||||
|     peripheral.getType("top") | ||||
| ]] | ||||
| function getType(peripheral) | ||||
|     expect(1, peripheral, "string", "table") | ||||
|     if type(peripheral) == "string" then -- Peripheral name passed | ||||
| @@ -81,7 +158,7 @@ function getType(peripheral) | ||||
|         end | ||||
|         for n = 1, #sides do | ||||
|             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) | ||||
|             then | ||||
|                 return native.call(side, "getTypeRemote", peripheral) | ||||
| @@ -90,10 +167,43 @@ function getType(peripheral) | ||||
|         return nil | ||||
|     else | ||||
|         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) | ||||
|         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 | ||||
|  | ||||
| @@ -109,7 +219,7 @@ function getMethods(name) | ||||
|     end | ||||
|     for n = 1, #sides do | ||||
|         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) | ||||
|         then | ||||
|             return native.call(side, "getMethodsRemote", name) | ||||
| @@ -151,7 +261,7 @@ function call(name, method, ...) | ||||
|  | ||||
|     for n = 1, #sides do | ||||
|         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) | ||||
|         then | ||||
|             return native.call(side, "callRemote", name, method, ...) | ||||
| @@ -160,15 +270,16 @@ function call(name, method, ...) | ||||
|     return nil | ||||
| end | ||||
|  | ||||
| --- Get a table containing functions pointing to the peripheral's methods, which | ||||
| -- can then be called as if using @{peripheral.call}. | ||||
| --- Get a table containing all functions available on a peripheral. These can | ||||
| -- then be called instead of using @{peripheral.call} every time. | ||||
| -- | ||||
| -- @tparam string name The name of the peripheral to wrap. | ||||
| -- @treturn table|nil The table containing the peripheral's methods, or `nil` if | ||||
| -- there is no peripheral present with the given name. | ||||
| -- @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) | ||||
|     expect(1, name, "string") | ||||
|  | ||||
| @@ -177,10 +288,14 @@ function wrap(name) | ||||
|         return nil | ||||
|     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({}, { | ||||
|         __name = "peripheral", | ||||
|         name = name, | ||||
|         type = peripheral.getType(name), | ||||
|         type = types[1], | ||||
|         types = types, | ||||
|     }) | ||||
|     for _, method in ipairs(methods) do | ||||
|         result[method] = function(...) | ||||
| @@ -222,7 +337,7 @@ function find(ty, filter) | ||||
|  | ||||
|     local results = {} | ||||
|     for _, name in ipairs(peripheral.getNames()) do | ||||
|         if peripheral.getType(name) == ty then | ||||
|         if peripheral.hasType(name, ty) then | ||||
|             local wrapped = peripheral.wrap(name) | ||||
|             if filter == nil or filter(name, wrapped) then | ||||
|                 table.insert(results, wrapped) | ||||
|   | ||||
| @@ -3,7 +3,7 @@ print("Attached Peripherals:") | ||||
| if #tPeripherals > 0 then | ||||
|     for n = 1, #tPeripherals do | ||||
|         local sPeripheral = tPeripherals[n] | ||||
|         print(sPeripheral .. " (" .. peripheral.getType(sPeripheral) .. ")") | ||||
|         print(sPeripheral .. " (" .. table.concat({ peripheral.getType(sPeripheral) }, ", ") .. ")") | ||||
|     end | ||||
| else | ||||
|     print("None") | ||||
|   | ||||
| @@ -1,6 +1,13 @@ | ||||
| describe("The peripheral library", function() | ||||
|     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() | ||||
|         it("validates arguments", function() | ||||
|             peripheral.isPresent("") | ||||
| @@ -26,6 +33,10 @@ describe("The peripheral library", function() | ||||
|             expect.error(peripheral.getType, {}):eq("bad argument #1 (table is not a peripheral)") | ||||
|         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() | ||||
|             expect(peripheral.getType("top")):eq("modem") | ||||
|         end) | ||||
| @@ -33,6 +44,38 @@ describe("The peripheral library", function() | ||||
|         it_modem("can get the type of a wrapped peripheral", function() | ||||
|             expect(peripheral.getType(peripheral.wrap("top"))):eq("modem") | ||||
|         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) | ||||
|  | ||||
|     describe("peripheral.getMethods", function() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates