From a2e2a5cb37e0e9c52e124c8c9688640c73588f3e Mon Sep 17 00:00:00 2001 From: SquidDev Date: Wed, 21 Feb 2018 15:25:20 +0000 Subject: [PATCH 1/5] Add the concept of "available peripherals" to IComputerAccess This provides a mechanism for peripherals to see what else a computer is connected to - and then interact with those peripherals. We also add the ability to query what block or tile a peripheral targets. This allows one to interact with the original block of adjacent peripherals instead. --- .../api/peripheral/IComputerAccess.java | 31 +++++++++++++ .../api/peripheral/IPeripheral.java | 12 +++++ .../core/apis/PeripheralAPI.java | 46 +++++++++++++++++-- .../computer/blocks/ComputerPeripheral.java | 7 +++ .../diskdrive/DiskDrivePeripheral.java | 7 +++ .../peripheral/printer/PrinterPeripheral.java | 7 +++ 6 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java b/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java index 306250936a..f3a063c22d 100644 --- a/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java +++ b/src/main/java/dan200/computercraft/api/peripheral/IComputerAccess.java @@ -13,6 +13,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Map; /** * The interface passed to peripherals by computers or turtles, providing methods @@ -154,4 +156,33 @@ public interface IComputerAccess */ @Nonnull String getAttachmentName(); + + /** + * Get a set of peripherals that this computer access can "see", along with their attachment name. + * + * This may include other peripherals on the wired network or peripherals on other sides of the computer. + * + * @return All reachable peripherals + * @see #getAttachmentName() + * @see #getAvailablePeripheral(String) + */ + @Nonnull + default Map getAvailablePeripherals() + { + return Collections.emptyMap(); + } + + /** + * Get a reachable peripheral with the given attachement name. This is a equivalent to + * {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant. + * + * @param name The peripheral's attached name + * @return The reachable peripheral, or {@code null} if none can be found. + * @see #getAvailablePeripherals() + */ + @Nullable + default IPeripheral getAvailablePeripheral( @Nonnull String name ) + { + return null; + } } diff --git a/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java b/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java index af2ecc9648..8c44424e2a 100644 --- a/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java +++ b/src/main/java/dan200/computercraft/api/peripheral/IPeripheral.java @@ -114,6 +114,18 @@ default void detach( @Nonnull IComputerAccess computer ) { } + /** + * Get the object that this peripheral provides methods for. This will generally be the tile entity + * or block, but may be an inventory, entity, etc... + * + * @return The object this peripheral targets + */ + @Nonnull + default Object getTarget() + { + return this; + } + /** * Determine whether this peripheral is equivalent to another one. * diff --git a/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java b/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java index d22328ea62..e5d71f7580 100644 --- a/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java @@ -15,10 +15,11 @@ import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.ComputerThread; import dan200.computercraft.core.computer.ITask; -import dan200.computercraft.core.filesystem.FileSystem; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -180,10 +181,49 @@ public String getAttachmentName() } return m_side; } + + @Nonnull + @Override + public Map getAvailablePeripherals() + { + if( !m_attached ) + { + throw new RuntimeException( "You are not attached to this Computer" ); + } + + Map peripherals = new HashMap<>(); + for( PeripheralWrapper wrapper : m_peripherals ) + { + if( wrapper != null && wrapper.isAttached() ) + { + peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() ); + } + } + + return Collections.unmodifiableMap( peripherals ); + } + + @Nullable + @Override + public IPeripheral getAvailablePeripheral( @Nonnull String name ) + { + if( !m_attached ) + { + throw new RuntimeException( "You are not attached to this Computer" ); + } + + for( PeripheralWrapper wrapper : m_peripherals ) + { + if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) ) + { + return wrapper.getPeripheral(); + } + } + return null; + } } private final IAPIEnvironment m_environment; - private FileSystem m_fileSystem; private final PeripheralWrapper[] m_peripherals; private boolean m_running; @@ -285,7 +325,6 @@ public void startup( ) { synchronized( m_peripherals ) { - m_fileSystem = m_environment.getFileSystem(); m_running = true; for( int i=0; i<6; ++i ) { @@ -312,7 +351,6 @@ public void shutdown( ) wrapper.detach(); } } - m_fileSystem = null; } } diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java b/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java index 3b5469c1bc..b401845a34 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java @@ -97,4 +97,11 @@ public boolean equals( IPeripheral other ) { return (other != null && other.getClass() == this.getClass()); } + + @Nonnull + @Override + public Object getTarget() + { + return m_computer.getTile(); + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java index 59ffea4bfc..33459ee6e6 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java @@ -196,4 +196,11 @@ public boolean equals( IPeripheral other ) } return false; } + + @Nonnull + @Override + public Object getTarget() + { + return m_diskDrive; + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java index 1a97be3d13..17d0713b80 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java @@ -145,6 +145,13 @@ public boolean equals( IPeripheral other ) return false; } + @Nonnull + @Override + public Object getTarget() + { + return m_printer; + } + private Terminal getCurrentPage() throws LuaException { Terminal currentPage = m_printer.getCurrentPage(); From 4651e362c9893990969e0f877891e84b3e10629c Mon Sep 17 00:00:00 2001 From: SquidDev Date: Wed, 21 Feb 2018 15:26:13 +0000 Subject: [PATCH 2/5] Add an API for wired networks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API is composed of three primary classes: - IWiredElement: Represents some physical entity in the network. This    will generally be a block (such as a cable or modem), but it is not    required to be.    Each element can provide a series of peripherals, which will be    exposed to other elements on the network. - IWiredNode: Every wired element has a unique wired node. This acts    as a thread-safe proxy for communicating with the rest of the    network (such as sending packets). Each node is also its own packet    network. - IWiredNetwork: This is responsible for keeping track of nodes and    peripherals in the network. It provides methods for forming and    breaking connections, correctly joining and splitting networks where    needed. Tiles which wish to be part of a wired network should implement IWiredElementTile or register a custom IWiredProvider. When loaded into the world, it should connect to adjacent nodes. Similarly, when removed (either due to being broken or chunk unloads), it should break those connections. There is no method to query the layout of the network, as that offers greater flexibility in changing or extending the implementation later on. --- .../computercraft/api/ComputerCraftAPI.java | 91 +++++++++++++++++ .../api/network/wired/IWiredElement.java | 51 ++++++++++ .../api/network/wired/IWiredElementTile.java | 22 +++++ .../api/network/wired/IWiredNetwork.java | 74 ++++++++++++++ .../network/wired/IWiredNetworkChange.java | 32 ++++++ .../api/network/wired/IWiredNode.java | 98 +++++++++++++++++++ .../api/network/wired/IWiredProvider.java | 29 ++++++ .../api/network/wired/IWiredSender.java | 25 +++++ .../api/network/wired/package-info.java | 10 ++ 9 files changed, 432 insertions(+) create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredElement.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredElementTile.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredNetwork.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredNetworkChange.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredNode.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredProvider.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/IWiredSender.java create mode 100644 src/main/java/dan200/computercraft/api/network/wired/package-info.java diff --git a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java index 88dd4bcde0..fd00693edf 100644 --- a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java +++ b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java @@ -12,6 +12,9 @@ import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredProvider; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralProvider; @@ -21,6 +24,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -328,6 +332,81 @@ public static void registerAPIFactory( @Nonnull ILuaAPIFactory upgrade ) } } + /** + * Registers a peripheral handler to convert blocks into {@link IPeripheral} implementations. + * + * @param handler The peripheral provider to register. + * @see dan200.computercraft.api.peripheral.IPeripheral + * @see dan200.computercraft.api.peripheral.IPeripheralProvider + */ + public static void registerWiredProvider( @Nonnull IWiredProvider handler ) + { + findCC(); + if ( computerCraft_registerWiredProvider != null) + { + try { + computerCraft_registerWiredProvider.invoke( null, handler ); + } catch (Exception e){ + // It failed + } + } + } + + /** + * Construct a new wired node for a given wired element + * + * @param element The element to construct it for + * @return The element's node + * @see IWiredElement#getNode() + */ + @Nonnull + public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ) + { + findCC(); + if( computerCraft_createWiredNodeForElement != null ) + { + try + { + return (IWiredNode) computerCraft_createWiredNodeForElement.invoke( null, element ); + } + catch( ReflectiveOperationException e ) + { + throw new IllegalStateException( "Error creating wired node", e ); + } + } + else + { + throw new IllegalStateException( "ComputerCraft cannot be found" ); + } + } + + /** + * Get the wired network element for a block in world + * + * @param world The world the block exists in + * @param pos The position the block exists in + * @param side The side to extract the network element from + * @return The element's node + * @see IWiredElement#getNode() + */ + @Nullable + public static IWiredElement getWiredElementAt( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side ) + { + findCC(); + if( computerCraft_getWiredElementAt != null ) + { + try + { + return (IWiredElement) computerCraft_getWiredElementAt.invoke( null, world, pos, side ); + } + catch( ReflectiveOperationException ignored ) + { + } + } + + return null; + } + // The functions below here are private, and are used to interface with the non-API ComputerCraft classes. // Reflection is used here so you can develop your mod without decompiling ComputerCraft and including // it in your solution, and so your mod won't crash if ComputerCraft is installed. @@ -374,6 +453,15 @@ private static void findCC() computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class[] { ILuaAPIFactory.class } ); + computerCraft_registerWiredProvider = findCCMethod( "registerWiredProvider", new Class[] { + IWiredProvider.class + } ); + computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class[] { + IWiredElement.class + } ); + computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class[]{ + IBlockAccess.class, BlockPos.class, EnumFacing.class + } ); } catch( Exception e ) { System.out.println( "ComputerCraftAPI: ComputerCraft not found." ); } finally { @@ -411,4 +499,7 @@ private static Method findCCMethod( String name, Class[] args ) private static Method computerCraft_registerPocketUpgrade = null; private static Method computerCraft_getWirelessNetwork = null; private static Method computerCraft_registerAPIFactory = null; + private static Method computerCraft_registerWiredProvider = null; + private static Method computerCraft_createWiredNodeForElement = null; + private static Method computerCraft_getWiredElementAt = null; } diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredElement.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredElement.java new file mode 100644 index 0000000000..8f078bb2f7 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredElement.java @@ -0,0 +1,51 @@ +package dan200.computercraft.api.network.wired; + +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Map; + +/** + * An object which may be part of a wired network. + * + * Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts + * as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant + * for its lifespan. + * + * Elements are generally tied to a block or tile entity in world. One should either register an {@link IWiredProvider} + * or implement {@link IWiredElementTile} on your tile entity. + * + * @see IWiredProvider + * @see ComputerCraftAPI#registerWiredProvider(IWiredProvider) + * @see IWiredElementTile + */ +public interface IWiredElement extends IWiredSender +{ + /** + * Fetch the peripherals this network element provides. + * + * This is only called when initially attaching to a network and after a call to {@link IWiredNode#invalidate()}}, so + * one does not need to cache the return value. + * + * @return The peripherals this node provides. + * @see IWiredNode#invalidate() + */ + @Nonnull + default Map getPeripherals() + { + return Collections.emptyMap(); + } + + /** + * Called when objects on the network change. This may occur when network nodes are added or removed, or when + * peripherals change. + * + * @param change The change which occurred. + * @see IWiredNetworkChange + */ + default void networkChanged( @Nonnull IWiredNetworkChange change ) + { + } +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredElementTile.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredElementTile.java new file mode 100644 index 0000000000..03e2f2f109 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredElementTile.java @@ -0,0 +1,22 @@ +package dan200.computercraft.api.network.wired; + +import net.minecraft.util.EnumFacing; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A {@link net.minecraft.tileentity.TileEntity} which provides a {@link IWiredElement}. This acts + * as a simpler alternative to a full-blown {@link IWiredProvider}. + */ +public interface IWiredElementTile +{ + /** + * Get the wired element of this tile for a given side. + * + * @param side The side to get the network element from. + * @return A network element, or {@code null} if there is no element here. + */ + @Nullable + IWiredElement getWiredElement( @Nonnull EnumFacing side ); +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredNetwork.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredNetwork.java new file mode 100644 index 0000000000..3ab387e6cf --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredNetwork.java @@ -0,0 +1,74 @@ +package dan200.computercraft.api.network.wired; + +import javax.annotation.Nonnull; + +/** + * A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series + * of peripherals. + * + * Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if + * there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically + * handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections + * change. + * + * This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently, + * it is generally preferred to use the methods provided by {@link IWiredNode}. + * + * @see IWiredNode#getNetwork() + */ +public interface IWiredNetwork +{ + /** + * Create a connection between two nodes. + * + * This should only be used on the server thread. + * + * @param left The first node to connect + * @param right The second node to connect + * @return {@code true} if a connection was created or {@code false} if the connection already exists. + * @throws IllegalStateException If neither node is on the network. + * @throws IllegalArgumentException If {@code left} and {@code right} are equal. + * @see IWiredNode#connectTo(IWiredNode) + * @see IWiredNetwork#connect(IWiredNode, IWiredNode) + */ + boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right ); + + /** + * Destroy a connection between this node and another. + * + * This should only be used on the server thread. + * + * @param left The first node in the connection. + * @param right The second node in the connection. + * @return {@code true} if a connection was destroyed or {@code false} if no connection exists. + * @throws IllegalArgumentException If either node is not on the network. + * @throws IllegalArgumentException If {@code left} and {@code right} are equal. + * @see IWiredNode#disconnectFrom(IWiredNode) + * @see IWiredNetwork#connect(IWiredNode, IWiredNode) + */ + boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right ); + + /** + * Sever all connections this node has, removing it from this network. + * + * This should only be used on the server thread. + * + * @param node The node to remove + * @return Whether this node was removed from the network. One cannot remove a node from a network where it is the + * only element. + * @throws IllegalArgumentException If the node is not in the network. + * @see IWiredNode#remove() + */ + boolean remove( @Nonnull IWiredNode node ); + + /** + * Mark this node's peripherals as having changed. + * + * This should only be used on the server thread. + * + * @param node The node to mark as invalid. + * @throws IllegalArgumentException If the node is not in the network. + * @see IWiredElement#getPeripherals() + */ + void invalidate( @Nonnull IWiredNode node ); +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredNetworkChange.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredNetworkChange.java new file mode 100644 index 0000000000..1f088b6690 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredNetworkChange.java @@ -0,0 +1,32 @@ +package dan200.computercraft.api.network.wired; + +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * Represents a change to the objects on a wired network. + * + * @see IWiredElement#networkChanged(IWiredNetworkChange) + */ +public interface IWiredNetworkChange +{ + /** + * A set of peripherals which have been removed. Note that there may be entries with the same name + * in the added and removed set, but with a different peripheral. + * + * @return The set of removed peripherals. + */ + @Nonnull + Map peripheralsRemoved(); + + /** + * A set of peripherals which have been added. Note that there may be entries with the same name + * in the added and removed set, but with a different peripheral. + * + * @return The set of added peripherals. + */ + @Nonnull + Map peripheralsAdded(); +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredNode.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredNode.java new file mode 100644 index 0000000000..076c7bc03f --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredNode.java @@ -0,0 +1,98 @@ +package dan200.computercraft.api.network.wired; + +import dan200.computercraft.api.network.IPacketNetwork; + +import javax.annotation.Nonnull; + +/** + * Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s. + * + * Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These + * methods may be safely used on any thread. + * + * When sending a packet, the system will attempt to find the shortest path between the two nodes based on their + * element's position. Note that packet senders and receivers can have different locations from their associated + * element: the distance between the two will be added to the total packet's distance. + * + * Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever + * be used on the main server thread. + */ +public interface IWiredNode extends IPacketNetwork +{ + /** + * The associated element for this network node. + * + * @return This node's element. + */ + @Nonnull + IWiredElement getElement(); + + /** + * The network this node is currently connected to. Note that this may change + * after any network operation, so it should not be cached. + * + * This should only be used on the server thread. + * + * @return This node's network. + */ + @Nonnull + IWiredNetwork getNetwork(); + + /** + * Create a connection from this node to another. + * + * This should only be used on the server thread. + * + * @param node The other node to connect to. + * @return {@code true} if a connection was created or {@code false} if the connection already exists. + * @see IWiredNetwork#connect(IWiredNode, IWiredNode) + * @see IWiredNode#disconnectFrom(IWiredNode) + */ + default boolean connectTo( @Nonnull IWiredNode node ) + { + return getNetwork().connect( this, node ); + } + + /** + * Destroy a connection between this node and another. + * + * This should only be used on the server thread. + * + * @param node The other node to disconnect from. + * @return {@code true} if a connection was destroyed or {@code false} if no connection exists. + * @throws IllegalArgumentException If {@code node} is not on the same network. + * @see IWiredNetwork#disconnect(IWiredNode, IWiredNode) + * @see IWiredNode#connectTo(IWiredNode) + */ + default boolean disconnectFrom( @Nonnull IWiredNode node ) + { + return getNetwork().disconnect( this, node ); + } + + /** + * Sever all connections this node has, removing it from this network. + * + * This should only be used on the server thread. + * + * @return Whether this node was removed from the network. One cannot remove a node from a network where it is the + * only element. + * @throws IllegalArgumentException If the node is not in the network. + * @see IWiredNetwork#remove(IWiredNode) + */ + default boolean remove() + { + return getNetwork().remove( this ); + } + + /** + * Mark this node's peripherals as having changed. + * + * This should only be used on the server thread. + * + * @see IWiredElement#getPeripherals() + */ + default void invalidate() + { + getNetwork().invalidate( this ); + } +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredProvider.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredProvider.java new file mode 100644 index 0000000000..10afae3e31 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredProvider.java @@ -0,0 +1,29 @@ +package dan200.computercraft.api.network.wired; + +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Fetch or create an {@link IWiredElement} for a block at a given position. + * + * @see dan200.computercraft.api.ComputerCraftAPI#registerWiredProvider(IWiredProvider) + * @see IWiredElementTile + */ +@FunctionalInterface +public interface IWiredProvider +{ + /** + * Extract a wired network element from a block location. + * + * @param world The world the block is in. + * @param pos The position the block is at. + * @param side The side to get the network element from. + * @return A network element, or {@code null} if there is not an element here you'd like to handle. + */ + @Nullable + IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side ); +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/IWiredSender.java b/src/main/java/dan200/computercraft/api/network/wired/IWiredSender.java new file mode 100644 index 0000000000..d29316799a --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/IWiredSender.java @@ -0,0 +1,25 @@ +package dan200.computercraft.api.network.wired; + +import dan200.computercraft.api.network.IPacketSender; + +import javax.annotation.Nonnull; + +/** + * An object on a {@link IWiredNetwork} capable of sending packets. + * + * Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to + * to send the packet from. + */ +public interface IWiredSender extends IPacketSender +{ + /** + * The node in the network representing this object. + * + * This should be used as a proxy for the main network. One should send packets + * and register receivers through this object. + * + * @return The node for this element. + */ + @Nonnull + IWiredNode getNode(); +} diff --git a/src/main/java/dan200/computercraft/api/network/wired/package-info.java b/src/main/java/dan200/computercraft/api/network/wired/package-info.java new file mode 100644 index 0000000000..3a81850867 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/network/wired/package-info.java @@ -0,0 +1,10 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. + * For help using the API, and posting your mods, visit the forums at computercraft.info. + */ + +@API( owner="ComputerCraft", provides="ComputerCraft|API|Network|Wired", apiVersion="${version}" ) +package dan200.computercraft.api.network.wired; + +import net.minecraftforge.fml.common.API; From 74f5093d2a29a529360279a8e9aff35b09062bfe Mon Sep 17 00:00:00 2001 From: SquidDev Date: Wed, 21 Feb 2018 15:29:34 +0000 Subject: [PATCH 3/5] Add the default implementation of wired networks --- build.gradle | 2 + .../dan200/computercraft/ComputerCraft.java | 34 ++ .../proxy/ComputerCraftProxyCommon.java | 6 +- .../shared/wired/DefaultWiredProvider.java | 23 + .../shared/wired/InvariantChecker.java | 46 ++ .../shared/wired/WiredNetwork.java | 458 ++++++++++++++++ .../shared/wired/WiredNetworkChange.java | 101 ++++ .../computercraft/shared/wired/WiredNode.java | 151 ++++++ .../shared/wired/NetworkTest.java | 506 ++++++++++++++++++ 9 files changed, 1326 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dan200/computercraft/shared/wired/DefaultWiredProvider.java create mode 100644 src/main/java/dan200/computercraft/shared/wired/InvariantChecker.java create mode 100644 src/main/java/dan200/computercraft/shared/wired/WiredNetwork.java create mode 100644 src/main/java/dan200/computercraft/shared/wired/WiredNetworkChange.java create mode 100644 src/main/java/dan200/computercraft/shared/wired/WiredNode.java create mode 100644 src/test/java/dan200/computercraft/shared/wired/NetworkTest.java diff --git a/build.gradle b/build.gradle index 1d96373a08..1cee602bbb 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,8 @@ dependencies { deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api" runtime "mezz.jei:jei_1.12:4.7.5.86" shade 'org.squiddev:Cobalt:0.3.1' + + testCompile 'junit:junit:4.11' } javadoc { diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index aa138019fc..4211e7eea1 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -14,6 +14,9 @@ import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredProvider; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.permissions.ITurtlePermissionProvider; @@ -57,6 +60,7 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.util.*; +import dan200.computercraft.shared.wired.WiredNode; import io.netty.buffer.Unpooled; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; @@ -69,6 +73,7 @@ import net.minecraft.util.NonNullList; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.Configuration; @@ -259,6 +264,7 @@ public static class Config { private static List permissionProviders = new ArrayList<>(); private static final Map pocketUpgrades = new HashMap<>(); private static final Set apiFactories = new LinkedHashSet<>(); + private static final Set wiredProviders = new LinkedHashSet<>(); // Implementation @Mod.Instance( value = ComputerCraft.MOD_ID ) @@ -730,6 +736,16 @@ public static void registerAPIFactory( ILuaAPIFactory provider ) } } + public static void registerWiredProvider( IWiredProvider provider ) + { + if( provider != null ) wiredProviders.add( provider ); + } + + public static IWiredNode createWiredNodeForElement( IWiredElement element ) + { + return new WiredNode( element ); + } + public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side ) { // Try the handlers in order: @@ -751,6 +767,24 @@ public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing return null; } + public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side ) + { + // Try the handlers in order: + for( IWiredProvider provider : wiredProviders ) + { + try + { + IWiredElement element = provider.getElement( world, pos, side ); + if( element != null ) return element; + } + catch( Exception e ) + { + ComputerCraft.log.error( "Wired element provider " + provider + " errored.", e ); + } + } + return null; + } + public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side ) { if( WorldUtil.isBlockInWorld( world, pos ) ) diff --git a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java index 34c0fff730..c4309a2067 100644 --- a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java +++ b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java @@ -52,6 +52,7 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; import dan200.computercraft.shared.util.*; +import dan200.computercraft.shared.wired.DefaultWiredProvider; import net.minecraft.block.Block; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.player.EntityPlayer; @@ -291,7 +292,7 @@ public void registerItems( RegistryEvent.Register event ) // Command Computer registry.register( new ItemCommandComputer( ComputerCraft.Blocks.commandComputer ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "command_computer" ) ) ); - // Command Computer + // Advanced modem registry.register( new ItemAdvancedModem( ComputerCraft.Blocks.advancedModem ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) ); // Items @@ -482,6 +483,9 @@ private void registerTileEntities() // Register media providers ComputerCraftAPI.registerMediaProvider( new DefaultMediaProvider() ); + + // Register network providers + ComputerCraftAPI.registerWiredProvider( new DefaultWiredProvider() ); } private void registerForgeHandlers() diff --git a/src/main/java/dan200/computercraft/shared/wired/DefaultWiredProvider.java b/src/main/java/dan200/computercraft/shared/wired/DefaultWiredProvider.java new file mode 100644 index 0000000000..a042d70103 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/wired/DefaultWiredProvider.java @@ -0,0 +1,23 @@ +package dan200.computercraft.shared.wired; + +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredElementTile; +import dan200.computercraft.api.network.wired.IWiredProvider; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DefaultWiredProvider implements IWiredProvider +{ + @Nullable + @Override + public IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side ) + { + TileEntity te = world.getTileEntity( pos ); + return te instanceof IWiredElementTile ? ((IWiredElementTile) te).getWiredElement( side ) : null; + } +} diff --git a/src/main/java/dan200/computercraft/shared/wired/InvariantChecker.java b/src/main/java/dan200/computercraft/shared/wired/InvariantChecker.java new file mode 100644 index 0000000000..ff3a163fe5 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/wired/InvariantChecker.java @@ -0,0 +1,46 @@ +package dan200.computercraft.shared.wired; + +import dan200.computercraft.ComputerCraft; + +/** + * Verifies certain elements of a network are "well formed". + * + * This adds substantial overhead to network modification, and so should only be enabled + * in a development environment. + */ +public class InvariantChecker +{ + private static final boolean ENABLED = false; + + public static void checkNode( WiredNode node ) + { + if( !ENABLED ) return; + + WiredNetwork network = node.network; + if( network == null ) + { + ComputerCraft.log.error( "Node's network is null", new Exception() ); + return; + } + + if( network.nodes == null || !network.nodes.contains( node ) ) + { + ComputerCraft.log.error( "Node's network does not contain node", new Exception() ); + } + + for( WiredNode neighbour : node.neighbours ) + { + if( !neighbour.neighbours.contains( node ) ) + { + ComputerCraft.log.error( "Neighbour is missing node", new Exception() ); + } + } + } + + public static void checkNetwork( WiredNetwork network ) + { + if( !ENABLED ) return; + + for( WiredNode node : network.nodes ) checkNode( node ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/wired/WiredNetwork.java b/src/main/java/dan200/computercraft/shared/wired/WiredNetwork.java new file mode 100644 index 0000000000..c09391364c --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/wired/WiredNetwork.java @@ -0,0 +1,458 @@ +package dan200.computercraft.shared.wired; + +import dan200.computercraft.api.network.Packet; +import dan200.computercraft.api.network.wired.IWiredNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public final class WiredNetwork implements IWiredNetwork +{ + final ReadWriteLock lock = new ReentrantReadWriteLock(); + HashSet nodes; + private HashMap peripherals = new HashMap<>(); + + public WiredNetwork( WiredNode node ) + { + nodes = new HashSet<>( 1 ); + nodes.add( node ); + } + + private WiredNetwork( HashSet nodes ) + { + this.nodes = nodes; + } + + @Override + public boolean connect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV ) + { + WiredNode wiredU = checkNode( nodeU ); + WiredNode wiredV = checkNode( nodeV ); + if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot add a connection to oneself." ); + + lock.writeLock().lock(); + try + { + if( nodes == null ) throw new IllegalStateException( "Cannot add a connection to an empty network." ); + + boolean hasU = wiredU.network == this; + boolean hasV = wiredV.network == this; + if( !hasU && !hasV ) throw new IllegalArgumentException( "Neither node is in the network." ); + + // We're going to assimilate a node. Copy across all edges and vertices. + if( !hasU || !hasV ) + { + WiredNetwork other = hasU ? wiredV.network : wiredU.network; + other.lock.writeLock().lock(); + try + { + // Cache several properties for iterating over later + Map otherPeripherals = other.peripherals; + Map thisPeripherals = otherPeripherals.isEmpty() ? peripherals : new HashMap<>( peripherals ); + + Collection thisNodes = otherPeripherals.isEmpty() ? nodes : new ArrayList<>( this.nodes ); + Collection otherNodes = other.nodes; + + // Move all nodes across into this network, destroying the original nodes. + nodes.addAll( otherNodes ); + for( WiredNode node : otherNodes ) node.network = this; + other.nodes = null; + + // Move all peripherals across, + other.peripherals = null; + peripherals.putAll( otherPeripherals ); + + if( !thisPeripherals.isEmpty() ) + { + WiredNetworkChange.added( thisPeripherals ).broadcast( otherNodes ); + } + + if( !otherPeripherals.isEmpty() ) + { + WiredNetworkChange.added( otherPeripherals ).broadcast( thisNodes ); + } + } + finally + { + other.lock.writeLock().unlock(); + } + } + + boolean added = wiredU.neighbours.add( wiredV ); + if( added ) wiredV.neighbours.add( wiredU ); + + InvariantChecker.checkNetwork( this ); + InvariantChecker.checkNode( wiredU ); + InvariantChecker.checkNode( wiredV ); + + return added; + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public boolean disconnect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV ) + { + WiredNode wiredU = checkNode( nodeU ); + WiredNode wiredV = checkNode( nodeV ); + if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot remove a connection to oneself." ); + + lock.writeLock().lock(); + try + { + boolean hasU = wiredU.network == this; + boolean hasV = wiredV.network == this; + if( !hasU || !hasV ) throw new IllegalArgumentException( "One node is not in the network." ); + + // If there was no connection to remove then split. + if( !wiredU.neighbours.remove( wiredV ) ) return false; + wiredV.neighbours.remove( wiredU ); + + // Determine if there is still some connection from u to v. + // Note this is an inlining of reachableNodes which short-circuits + // if all nodes are reachable. + Queue enqueued = new ArrayDeque<>(); + HashSet reachableU = new HashSet<>(); + + reachableU.add( wiredU ); + enqueued.add( wiredU ); + + while( !enqueued.isEmpty() ) + { + WiredNode node = enqueued.remove(); + for( WiredNode neighbour : node.neighbours ) + { + // If we can reach wiredV from wiredU then abort. + if( neighbour == wiredV ) return true; + + // Otherwise attempt to enqueue this neighbour as well. + if( reachableU.add( neighbour ) ) enqueued.add( neighbour ); + } + } + + // Create a new network with all U-reachable nodes/edges and remove them + // from the existing graph. + WiredNetwork networkU = new WiredNetwork( reachableU ); + networkU.lock.writeLock().lock(); + try + { + // Remove nodes from this network + nodes.removeAll( reachableU ); + + // Set network and transfer peripherals + for( WiredNode node : reachableU ) + { + node.network = networkU; + networkU.peripherals.putAll( node.peripherals ); + peripherals.keySet().removeAll( node.peripherals.keySet() ); + } + + // Broadcast changes + if( peripherals.size() != 0 ) WiredNetworkChange.removed( peripherals ).broadcast( networkU.nodes ); + if( networkU.peripherals.size() != 0 ) + { + WiredNetworkChange.removed( networkU.peripherals ).broadcast( nodes ); + } + + InvariantChecker.checkNetwork( this ); + InvariantChecker.checkNetwork( networkU ); + InvariantChecker.checkNode( wiredU ); + InvariantChecker.checkNode( wiredV ); + + return true; + } + finally + { + networkU.lock.writeLock().unlock(); + } + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public boolean remove( @Nonnull IWiredNode node ) + { + WiredNode wired = checkNode( node ); + + lock.writeLock().lock(); + try + { + // If we're the empty graph then just abort: nodes must have _some_ network. + if( nodes == null ) return false; + if( nodes.size() <= 1 ) return false; + if( wired.network != this ) return false; + + HashSet neighbours = wired.neighbours; + + // Remove this node and move into a separate network. + nodes.remove( wired ); + for( WiredNode neighbour : neighbours ) neighbour.neighbours.remove( wired ); + + WiredNetwork wiredNetwork = new WiredNetwork( wired ); + + // If we're a leaf node in the graph (only one neighbour) then we don't need to + // check for network splitting + if( neighbours.size() == 1 ) + { + // Broadcast our simple peripheral changes + removeSingleNode( wired, wiredNetwork ); + InvariantChecker.checkNode( wired ); + InvariantChecker.checkNetwork( wiredNetwork ); + return true; + } + + HashSet reachable = reachableNodes( neighbours.iterator().next() ); + + // If all nodes are reachable then exit. + if( reachable.size() == nodes.size() ) + { + // Broadcast our simple peripheral changes + removeSingleNode( wired, wiredNetwork ); + InvariantChecker.checkNode( wired ); + InvariantChecker.checkNetwork( wiredNetwork ); + return true; + } + + // A split may cause 2..neighbours.size() separate networks, so we + // iterate through our neighbour list, generating child networks. + neighbours.removeAll( reachable ); + ArrayList maximals = new ArrayList<>( neighbours.size() + 1 ); + maximals.add( wiredNetwork ); + maximals.add( new WiredNetwork( reachable ) ); + + while( neighbours.size() > 0 ) + { + reachable = reachableNodes( neighbours.iterator().next() ); + neighbours.removeAll( reachable ); + maximals.add( new WiredNetwork( reachable ) ); + } + + for( WiredNetwork network : maximals ) network.lock.writeLock().lock(); + + try + { + // We special case the original node: detaching all peripherals when needed. + wired.network = wiredNetwork; + wired.peripherals = Collections.emptyMap(); + + // Ensure every network is finalised + for( WiredNetwork network : maximals ) + { + for( WiredNode child : network.nodes ) + { + child.network = network; + network.peripherals.putAll( child.peripherals ); + } + } + + for( WiredNetwork network : maximals ) InvariantChecker.checkNetwork( network ); + InvariantChecker.checkNode( wired ); + + // Then broadcast network changes once all nodes are finalised + for( WiredNetwork network : maximals ) + { + WiredNetworkChange.changeOf( peripherals, network.peripherals ).broadcast( network.nodes ); + } + } + finally + { + for( WiredNetwork network : maximals ) network.lock.writeLock().unlock(); + } + + nodes.clear(); + peripherals.clear(); + + return true; + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public void invalidate( @Nonnull IWiredNode node ) + { + WiredNode wired = checkNode( node ); + + lock.writeLock().lock(); + try + { + if( wired.network != this ) throw new IllegalStateException( "Node is not on this network" ); + + Map oldPeripherals = wired.peripherals; + Map newPeripherals = wired.element.getPeripherals(); + WiredNetworkChange change = WiredNetworkChange.changeOf( oldPeripherals, newPeripherals ); + if( change.isEmpty() ) return; + + wired.peripherals = newPeripherals; + + // Detach the old peripherals then remove them. + peripherals.keySet().removeAll( change.peripheralsRemoved().keySet() ); + + // Add the new peripherals and attach them + peripherals.putAll( change.peripheralsAdded() ); + + change.broadcast( nodes ); + } + finally + { + lock.writeLock().unlock(); + } + } + + void transmitPacket( WiredNode start, Packet packet, double range, boolean interdimensional ) + { + Map points = new HashMap<>(); + TreeSet transmitTo = new TreeSet<>(); + + { + TransmitPoint startEntry = start.element.getWorld() != packet.getSender().getWorld() + ? new TransmitPoint( start, Double.POSITIVE_INFINITY, true ) + : new TransmitPoint( start, start.element.getPosition().distanceTo( packet.getSender().getPosition() ), false ); + points.put( start, startEntry ); + transmitTo.add( startEntry ); + } + + { + TransmitPoint point; + while( (point = transmitTo.pollFirst()) != null ) + { + World world = point.node.element.getWorld(); + Vec3d position = point.node.element.getPosition(); + for( WiredNode neighbour : point.node.neighbours ) + { + TransmitPoint neighbourPoint = points.get( neighbour ); + + boolean newInterdimensional; + double newDistance; + if( world != neighbour.element.getWorld() ) + { + newInterdimensional = true; + newDistance = Double.POSITIVE_INFINITY; + } + else + { + newInterdimensional = false; + newDistance = point.distance + position.distanceTo( neighbour.element.getPosition() ); + } + + if( neighbourPoint == null ) + { + neighbourPoint = new TransmitPoint( neighbour, newDistance, newInterdimensional ); + points.put( neighbour, neighbourPoint ); + transmitTo.add( neighbourPoint ); + } + else if( newDistance < neighbourPoint.distance ) + { + transmitTo.remove( neighbourPoint ); + neighbourPoint.distance = newDistance; + neighbourPoint.interdimensional = newInterdimensional; + transmitTo.add( neighbourPoint ); + } + } + } + } + + for( TransmitPoint point : points.values() ) + { + point.node.tryTransmit( packet, point.distance, point.interdimensional, range, interdimensional ); + } + } + + private void removeSingleNode( WiredNode wired, WiredNetwork wiredNetwork ) + { + wiredNetwork.lock.writeLock().lock(); + try + { + // Cache all the old nodes. + Map wiredPeripherals = new HashMap<>( wired.peripherals ); + + // Setup the new node's network + // Detach the old peripherals then remove them from the old network + wired.network = wiredNetwork; + wired.neighbours.clear(); + wired.peripherals = Collections.emptyMap(); + + // Broadcast the change + if( !peripherals.isEmpty() ) WiredNetworkChange.removed( peripherals ).broadcast( wired ); + + // Now remove all peripherals from this network and broadcast the change. + peripherals.keySet().removeAll( wiredPeripherals.keySet() ); + if( !wiredPeripherals.isEmpty() ) WiredNetworkChange.removed( wiredPeripherals ).broadcast( nodes ); + + } + finally + { + wiredNetwork.lock.writeLock().unlock(); + } + } + + private static class TransmitPoint implements Comparable + { + final WiredNode node; + double distance; + boolean interdimensional; + + TransmitPoint( WiredNode node, double distance, boolean interdimensional ) + { + this.node = node; + this.distance = distance; + this.interdimensional = interdimensional; + } + + @Override + public int compareTo( @Nonnull TransmitPoint o ) + { + // Objects with the same distance are not the same object, so we must add an additional layer of ordering. + return distance == o.distance + ? Integer.compare( node.hashCode(), o.node.hashCode() ) + : Double.compare( distance, o.distance ); + } + } + + private static WiredNode checkNode( IWiredNode node ) + { + if( node instanceof WiredNode ) + { + return (WiredNode) node; + } + else + { + throw new IllegalArgumentException( "Unknown implementation of IWiredNode: " + node ); + } + } + + private static HashSet reachableNodes( WiredNode start ) + { + Queue enqueued = new ArrayDeque<>(); + HashSet reachable = new HashSet<>(); + + reachable.add( start ); + enqueued.add( start ); + + WiredNode node; + while( (node = enqueued.poll()) != null ) + { + for( WiredNode neighbour : node.neighbours ) + { + // Otherwise attempt to enqueue this neighbour as well. + if( reachable.add( neighbour ) ) enqueued.add( neighbour ); + } + } + + return reachable; + } +} diff --git a/src/main/java/dan200/computercraft/shared/wired/WiredNetworkChange.java b/src/main/java/dan200/computercraft/shared/wired/WiredNetworkChange.java new file mode 100644 index 0000000000..4b53d24da9 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/wired/WiredNetworkChange.java @@ -0,0 +1,101 @@ +package dan200.computercraft.shared.wired; + +import dan200.computercraft.api.network.wired.IWiredNetworkChange; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class WiredNetworkChange implements IWiredNetworkChange +{ + private final Map removed; + private final Map added; + + private WiredNetworkChange( Map removed, Map added ) + { + this.removed = removed; + this.added = added; + } + + public static WiredNetworkChange changed( Map removed, Map added ) + { + return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.unmodifiableMap( added ) ); + } + + public static WiredNetworkChange added( Map added ) + { + return new WiredNetworkChange( Collections.emptyMap(), Collections.unmodifiableMap( added ) ); + } + + public static WiredNetworkChange removed( Map removed ) + { + return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.emptyMap() ); + } + + public static WiredNetworkChange changeOf( Map oldPeripherals, Map newPeripherals ) + { + Map added = new HashMap<>( newPeripherals ); + Map removed = new HashMap<>(); + + for( Map.Entry entry : oldPeripherals.entrySet() ) + { + String oldKey = entry.getKey(); + IPeripheral oldValue = entry.getValue(); + if( newPeripherals.containsKey( oldKey ) ) + { + IPeripheral rightValue = added.get( oldKey ); + if( oldValue.equals( rightValue ) ) + { + added.remove( oldKey ); + } + else + { + removed.put( oldKey, oldValue ); + } + } + else + { + removed.put( oldKey, oldValue ); + } + } + + return changed( removed, added ); + } + + @Nonnull + @Override + public Map peripheralsAdded() + { + return added; + } + + @Nonnull + @Override + public Map peripheralsRemoved() + { + return removed; + } + + public boolean isEmpty() + { + return added.isEmpty() && removed.isEmpty(); + } + + void broadcast( Iterable nodes ) + { + if( !isEmpty() ) + { + for( WiredNode node : nodes ) node.element.networkChanged( this ); + } + } + + void broadcast( WiredNode node ) + { + if( !isEmpty() ) + { + node.element.networkChanged( this ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/wired/WiredNode.java b/src/main/java/dan200/computercraft/shared/wired/WiredNode.java new file mode 100644 index 0000000000..f5485c59b4 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/wired/WiredNode.java @@ -0,0 +1,151 @@ +package dan200.computercraft.shared.wired; + +import com.google.common.base.Preconditions; +import dan200.computercraft.api.network.IPacketReceiver; +import dan200.computercraft.api.network.Packet; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredSender; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; + +public final class WiredNode implements IWiredNode +{ + private Set receivers; + + final IWiredElement element; + Map peripherals = Collections.emptyMap(); + + final HashSet neighbours = new HashSet<>(); + volatile WiredNetwork network; + + public WiredNode( IWiredElement element ) + { + this.element = element; + this.network = new WiredNetwork( this ); + } + + @Override + public synchronized void addReceiver( @Nonnull IPacketReceiver receiver ) + { + if( receivers == null ) receivers = new HashSet<>(); + receivers.add( receiver ); + } + + @Override + public synchronized void removeReceiver( @Nonnull IPacketReceiver receiver ) + { + if( receivers != null ) receivers.remove( receiver ); + } + + synchronized void tryTransmit( Packet packet, double packetDistance, boolean packetInterdimensional, double range, boolean interdimensional ) + { + if( receivers == null ) return; + + for( IPacketReceiver receiver : receivers ) + { + if( !packetInterdimensional ) + { + double receiveRange = Math.max( range, receiver.getRange() ); // Ensure range is symmetrical + if( interdimensional || receiver.isInterdimensional() || packetDistance < receiveRange ) + { + receiver.receiveSameDimension( packet, packetDistance + element.getPosition().distanceTo( receiver.getPosition() ) ); + } + } + else + { + if( interdimensional || receiver.isInterdimensional() ) + { + receiver.receiveDifferentDimension( packet ); + } + } + } + } + + @Override + public boolean isWireless() + { + return false; + } + + @Override + public void transmitSameDimension( @Nonnull Packet packet, double range ) + { + Preconditions.checkNotNull( packet, "packet cannot be null" ); + if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this ) + { + throw new IllegalArgumentException( "Sender is not in the network" ); + } + + acquireReadLock(); + try + { + network.transmitPacket( this, packet, range, false ); + } + finally + { + network.lock.readLock().unlock(); + } + } + + @Override + public void transmitInterdimensional( @Nonnull Packet packet ) + { + Preconditions.checkNotNull( packet, "packet cannot be null" ); + if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this ) + { + throw new IllegalArgumentException( "Sender is not in the network" ); + } + + acquireReadLock(); + try + { + network.transmitPacket( this, packet, 0, true ); + } + finally + { + network.lock.readLock().unlock(); + } + } + + @Nonnull + @Override + public IWiredElement getElement() + { + return element; + } + + @Nonnull + @Override + public IWiredNetwork getNetwork() + { + return network; + } + + @Override + public String toString() + { + return "WiredNode{@" + element.getPosition() + " (" + element.getClass().getSimpleName() + ")}"; + } + + private void acquireReadLock() + { + WiredNetwork currentNetwork = network; + while( true ) + { + Lock lock = currentNetwork.lock.readLock(); + lock.lock(); + if( currentNetwork == network ) return; + + + lock.unlock(); + } + } +} diff --git a/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java b/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java new file mode 100644 index 0000000000..a2ff905aa4 --- /dev/null +++ b/src/test/java/dan200/computercraft/shared/wired/NetworkTest.java @@ -0,0 +1,506 @@ +package dan200.computercraft.shared.wired; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNetwork; +import dan200.computercraft.api.network.wired.IWiredNetworkChange; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.logging.log4j.LogManager; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.*; + +public class NetworkTest +{ + @Before + public void setup() + { + ComputerCraft.log = LogManager.getLogger(); + } + + @Test + public void testConnect() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + bE = new NetworkElement( null, null, "b" ), + cE = new NetworkElement( null, null, "c" ); + + IWiredNode + aN = aE.getNode(), + bN = bE.getNode(), + cN = cE.getNode(); + + assertNotEquals( "A's and B's network must be different", aN.getNetwork(), bN.getNetwork() ); + assertNotEquals( "A's and C's network must be different", aN.getNetwork(), cN.getNetwork() ); + assertNotEquals( "B's and C's network must be different", bN.getNetwork(), cN.getNetwork() ); + + assertTrue( "Must be able to add connection", aN.getNetwork().connect( aN, bN ) ); + assertFalse( "Cannot add connection twice", aN.getNetwork().connect( aN, bN ) ); + + assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's network should be A and B", Sets.newHashSet( aN, bN ), nodes( aN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A, B", Sets.newHashSet( "a", "b" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be A, B", Sets.newHashSet( "a", "b" ), bE.allPeripherals().keySet() ); + + aN.getNetwork().connect( aN, cN ); + + assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() ); + assertEquals( "A's network should be A, B and C", Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ) ); + + assertEquals( "A's neighbour set should be B, C", Sets.newHashSet( bN, cN ), neighbours( aN ) ); + assertEquals( "B's neighbour set should be A", Sets.newHashSet( aN ), neighbours( bN ) ); + assertEquals( "C's neighbour set should be A", Sets.newHashSet( aN ), neighbours( cN ) ); + + assertEquals( "A's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet() ); + assertEquals( "C's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet() ); + } + + @Test + public void testDisconnectNoChange() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + bE = new NetworkElement( null, null, "b" ), + cE = new NetworkElement( null, null, "c" ); + + IWiredNode + aN = aE.getNode(), + bN = bE.getNode(), + cN = cE.getNode(); + + aN.getNetwork().connect( aN, bN ); + aN.getNetwork().connect( aN, cN ); + aN.getNetwork().connect( bN, cN ); + + aN.getNetwork().disconnect( aN, bN ); + + assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() ); + assertEquals( "A's network should be A, B and C", Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet() ); + assertEquals( "C's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet() ); + } + + @Test + public void testDisconnectLeaf() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + bE = new NetworkElement( null, null, "b" ), + cE = new NetworkElement( null, null, "c" ); + + IWiredNode + aN = aE.getNode(), + bN = bE.getNode(), + cN = cE.getNode(); + + aN.getNetwork().connect( aN, bN ); + aN.getNetwork().connect( aN, cN ); + + aN.getNetwork().disconnect( aN, bN ); + + assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() ); + assertEquals( "A's network should be A and C", Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ) ); + assertEquals( "B's network should be B", Sets.newHashSet( bN ), nodes( bN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be B", Sets.newHashSet( "b" ), bE.allPeripherals().keySet() ); + assertEquals( "C's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet() ); + } + + @Test + public void testDisconnectSplit() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + aaE = new NetworkElement( null, null, "a_" ), + bE = new NetworkElement( null, null, "b" ), + bbE = new NetworkElement( null, null, "b_" ); + + IWiredNode + aN = aE.getNode(), + aaN = aaE.getNode(), + bN = bE.getNode(), + bbN = bbE.getNode(); + + aN.getNetwork().connect( aN, aaN ); + bN.getNetwork().connect( bN, bbN ); + + aN.getNetwork().connect( aN, bN ); + + aN.getNetwork().disconnect( aN, bN ); + + assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and A_'s network must be equal", aN.getNetwork(), aaN.getNetwork() ); + assertEquals( "B's and B_'s network must be equal", bN.getNetwork(), bbN.getNetwork() ); + + assertEquals( "A's network should be A and A_", Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ) ); + assertEquals( "B's network should be B and B_", Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A and A_", Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be B and B_", Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet() ); + } + + @Test + public void testRemoveSingle() + { + NetworkElement aE = new NetworkElement( null, null, "a" ); + IWiredNode aN = aE.getNode(); + + IWiredNetwork network = aN.getNetwork(); + assertFalse( "Cannot remove node from an empty network", aN.remove() ); + assertEquals( "Networks are same before and after", network, aN.getNetwork() ); + } + + @Test + public void testRemoveLeaf() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + bE = new NetworkElement( null, null, "b" ), + cE = new NetworkElement( null, null, "c" ); + + IWiredNode + aN = aE.getNode(), + bN = bE.getNode(), + cN = cE.getNode(); + + aN.getNetwork().connect( aN, bN ); + aN.getNetwork().connect( aN, cN ); + + assertTrue( "Must be able to remove node", aN.getNetwork().remove( bN ) ); + assertFalse( "Cannot remove a second time", aN.getNetwork().remove( bN ) ); + + assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() ); + + assertEquals( "A's network should be A and C", Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ) ); + assertEquals( "B's network should be B", Sets.newHashSet( bN ), nodes( bN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be empty", Sets.newHashSet(), bE.allPeripherals().keySet() ); + assertEquals( "C's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet() ); + } + + @Test + public void testRemoveSplit() + { + NetworkElement + aE = new NetworkElement( null, null, "a" ), + aaE = new NetworkElement( null, null, "a_" ), + bE = new NetworkElement( null, null, "b" ), + bbE = new NetworkElement( null, null, "b_" ), + cE = new NetworkElement( null, null, "c" ); + + IWiredNode + aN = aE.getNode(), + aaN = aaE.getNode(), + bN = bE.getNode(), + bbN = bbE.getNode(), + cN = cE.getNode(); + + aN.getNetwork().connect( aN, aaN ); + bN.getNetwork().connect( bN, bbN ); + + cN.getNetwork().connect( aN, cN ); + cN.getNetwork().connect( bN, cN ); + + cN.getNetwork().remove( cN ); + + assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() ); + assertEquals( "A's and A_'s network must be equal", aN.getNetwork(), aaN.getNetwork() ); + assertEquals( "B's and B_'s network must be equal", bN.getNetwork(), bbN.getNetwork() ); + + assertEquals( "A's network should be A and A_", Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ) ); + assertEquals( "B's network should be B and B_", Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ) ); + assertEquals( "C's network should be C", Sets.newHashSet( cN ), nodes( cN.getNetwork() ) ); + + assertEquals( "A's peripheral set should be A and A_", Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet() ); + assertEquals( "B's peripheral set should be B and B_", Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet() ); + assertEquals( "C's peripheral set should be empty", Sets.newHashSet(), cE.allPeripherals().keySet() ); + } + + @Test + @Ignore("Takes a long time to run, mostly for stress testing") + public void testLarge() + { + final int BRUTE_SIZE = 16; + final int TOGGLE_CONNECTION_TIMES = 5; + final int TOGGLE_NODE_TIMES = 5; + + Grid grid = new Grid<>( BRUTE_SIZE ); + grid.map( ( existing, pos ) -> new NetworkElement( null, null, "n_" + pos ).getNode() ); + + // Test connecting + { + long start = System.nanoTime(); + + grid.forEach( ( existing, pos ) -> { + for( EnumFacing facing : EnumFacing.VALUES ) + { + BlockPos offset = pos.offset( facing ); + if( (offset.getX() > BRUTE_SIZE / 2) == (pos.getX() > BRUTE_SIZE / 2) ) + { + IWiredNode other = grid.get( offset ); + if( other != null ) existing.getNetwork().connect( existing, other ); + } + } + } ); + + long end = System.nanoTime(); + + System.out.printf( "Connecting %s³ nodes took %s seconds\n", BRUTE_SIZE, (end - start) * 1e-9 ); + } + + // Test toggling + { + IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) ); + IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) ); + assertNotEquals( left.getNetwork(), right.getNetwork() ); + + long start = System.nanoTime(); + for( int i = 0; i < TOGGLE_CONNECTION_TIMES; i++ ) + { + left.getNetwork().connect( left, right ); + left.getNetwork().disconnect( left, right ); + } + + long end = System.nanoTime(); + + System.out.printf( "Toggling connection %s times took %s seconds\n", TOGGLE_CONNECTION_TIMES, (end - start) * 1e-9 ); + } + + { + IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) ); + IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) ); + IWiredNode centre = new NetworkElement( null, null, "c" ).getNode(); + assertNotEquals( left.getNetwork(), right.getNetwork() ); + + long start = System.nanoTime(); + for( int i = 0; i < TOGGLE_NODE_TIMES; i++ ) + { + left.getNetwork().connect( left, centre ); + right.getNetwork().connect( right, centre ); + + left.getNetwork().remove( centre ); + } + + long end = System.nanoTime(); + + System.out.printf( "Toggling node %s times took %s seconds\n", TOGGLE_NODE_TIMES, (end - start) * 1e-9 ); + } + } + + private static class NetworkElement implements IWiredElement + { + private final World world; + private final Vec3d position; + private final String id; + private final IWiredNode node; + private final Map localPeripherals = Maps.newHashMap(); + private final Map remotePeripherals = Maps.newHashMap(); + + private NetworkElement( World world, Vec3d position, String id ) + { + this.world = world; + this.position = position; + this.id = id; + this.node = ComputerCraftAPI.createWiredNodeForElement( this ); + this.addPeripheral( id ); + } + + @Nonnull + @Override + public World getWorld() + { + return world; + } + + @Nonnull + @Override + public Vec3d getPosition() + { + return position; + } + + @Nonnull + @Override + public String getSenderID() + { + return id; + } + + @Override + public String toString() + { + return "NetworkElement{" + id + "}"; + } + + @Nonnull + @Override + public IWiredNode getNode() + { + return node; + } + + @Override + public void networkChanged( @Nonnull IWiredNetworkChange change ) + { + remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() ); + remotePeripherals.putAll( change.peripheralsAdded() ); + } + + @Nonnull + @Override + public Map getPeripherals() + { + return Collections.unmodifiableMap( localPeripherals ); + } + + public NetworkElement addPeripheral( String name ) + { + localPeripherals.put( name, new NetworkPeripheral() ); + getNode().invalidate(); + return this; + } + + @Nonnull + public Map allPeripherals() + { + return remotePeripherals; + } + } + + private static class NetworkPeripheral implements IPeripheral + { + @Nonnull + @Override + public String getType() + { + return "test"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[0]; + } + + @Nullable + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + return new Object[0]; + } + + @Override + public boolean equals( @Nullable IPeripheral other ) + { + return this == other; + } + } + + private static class Grid + { + private final int size; + private final T[] box; + + @SuppressWarnings("unchecked") + public Grid( int size ) + { + this.size = size; + this.box = (T[]) new Object[size * size * size]; + } + + public void set( BlockPos pos, T elem ) + { + int x = pos.getX(), y = pos.getY(), z = pos.getZ(); + + if( x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size ) + { + box[x * size * size + y * size + z] = elem; + } + else + { + throw new IndexOutOfBoundsException( pos.toString() ); + } + } + + public T get( BlockPos pos ) + { + int x = pos.getX(), y = pos.getY(), z = pos.getZ(); + + return x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size + ? box[x * size * size + y * size + z] + : null; + } + + public void forEach( BiConsumer transform ) + { + for( int x = 0; x < size; x++ ) + { + for( int y = 0; y < size; y++ ) + { + for( int z = 0; z < size; z++ ) + { + transform.accept( box[x * size * size + y * size + z], new BlockPos( x, y, z ) ); + } + } + } + } + + public void map( BiFunction transform ) + { + for( int x = 0; x < size; x++ ) + { + for( int y = 0; y < size; y++ ) + { + for( int z = 0; z < size; z++ ) + { + box[x * size * size + y * size + z] = transform.apply( box[x * size * size + y * size + z], new BlockPos( x, y, z ) ); + } + } + } + } + } + + private static Set nodes( IWiredNetwork network ) + { + return ((WiredNetwork) network).nodes; + } + + private static Set neighbours( IWiredNode node ) + { + return ((WiredNode) node).neighbours; + } +} From 5c7828dd799656ae324c9aa7f1827c2d9fa74a7e Mon Sep 17 00:00:00 2001 From: SquidDev Date: Wed, 21 Feb 2018 15:35:38 +0000 Subject: [PATCH 4/5] Convert TileCable to use the wired network API There are several important things to note here: - The network element is associated with the cable, whilst the peripheral (and so packet sender/receiver) is associated with the modem. This allows us to have the main element be in the centre of the cable block, whilst the modem is in the centre of the adjacent computer. - Cables will connect to any adjacent network element, not just other cables. - Rednet messages are now sent on the computer thread, rather than the cable tick. --- .../client/render/RenderOverlayCable.java | 4 +- .../shared/peripheral/common/BlockCable.java | 57 +- .../shared/peripheral/modem/TileCable.java | 805 ++++-------------- .../peripheral/modem/WiredModemElement.java | 59 ++ .../modem/WiredModemPeripheral.java | 410 +++++++++ 5 files changed, 651 insertions(+), 684 deletions(-) create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemElement.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemPeripheral.java diff --git a/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java b/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java index 5ee9e430fe..138ad3fffe 100644 --- a/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java +++ b/src/main/java/dan200/computercraft/client/render/RenderOverlayCable.java @@ -54,8 +54,6 @@ public void drawHighlight( DrawBlockHighlightEvent event ) GlStateManager.depthMask( false ); GlStateManager.pushMatrix(); - EnumFacing direction = type != PeripheralType.Cable ? cable.getDirection() : null; - { EntityPlayer player = event.getPlayer(); double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks(); @@ -78,7 +76,7 @@ public void drawHighlight( DrawBlockHighlightEvent event ) for( EnumFacing facing : EnumFacing.VALUES ) { - if( direction == facing || BlockCable.isCable( world, pos.offset( facing ) ) ) + if( BlockCable.doesConnectVisually( state, world, pos, facing ) ) { flags |= 1 << facing.ordinal(); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java index 801f001cbc..f0e04a4db9 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockCable.java @@ -11,7 +11,6 @@ import dan200.computercraft.shared.peripheral.PeripheralType; import dan200.computercraft.shared.peripheral.modem.TileCable; import dan200.computercraft.shared.util.WorldUtil; -import net.minecraft.block.Block; import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.state.BlockFaceShape; @@ -51,23 +50,6 @@ public static class Properties public static final PropertyBool DOWN = PropertyBool.create( "down" ); } - public static boolean isCable( IBlockAccess world, BlockPos pos ) - { - Block block = world.getBlockState( pos ).getBlock(); - if( block == ComputerCraft.Blocks.cable ) - { - switch( ComputerCraft.Blocks.cable.getPeripheralType( world, pos ) ) - { - case Cable: - case WiredModemWithCable: - { - return true; - } - } - } - return false; - } - // Members public BlockCable() @@ -175,20 +157,17 @@ public IBlockState getDefaultBlockState( PeripheralType type, EnumFacing placedS } } - private boolean doesConnect( IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing dir ) + public static boolean canConnectIn( IBlockState state, EnumFacing direction ) { - if( state.getValue( Properties.CABLE ) == BlockCableCableVariant.NONE ) - { - return false; - } - else if( state.getValue( Properties.MODEM ).getFacing() == dir ) - { - return true; - } - else - { - return isCable( world, pos.offset( dir ) ); - } + return state.getValue( BlockCable.Properties.CABLE ) != BlockCableCableVariant.NONE + && state.getValue( BlockCable.Properties.MODEM ).getFacing() != direction; + } + + public static boolean doesConnectVisually( IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing direction ) + { + if( state.getValue( Properties.CABLE ) == BlockCableCableVariant.NONE ) return false; + if( state.getValue( Properties.MODEM ).getFacing() == direction ) return true; + return ComputerCraft.getWiredElementAt( world, pos.offset( direction ), direction.getOpposite() ) != null; } @Nonnull @@ -196,12 +175,12 @@ else if( state.getValue( Properties.MODEM ).getFacing() == dir ) @Deprecated public IBlockState getActualState( @Nonnull IBlockState state, IBlockAccess world, BlockPos pos ) { - state = state.withProperty( Properties.NORTH, doesConnect( state, world, pos, EnumFacing.NORTH ) ); - state = state.withProperty( Properties.SOUTH, doesConnect( state, world, pos, EnumFacing.SOUTH ) ); - state = state.withProperty( Properties.EAST, doesConnect( state, world, pos, EnumFacing.EAST ) ); - state = state.withProperty( Properties.WEST, doesConnect( state, world, pos, EnumFacing.WEST ) ); - state = state.withProperty( Properties.UP, doesConnect( state, world, pos, EnumFacing.UP ) ); - state = state.withProperty( Properties.DOWN, doesConnect( state, world, pos, EnumFacing.DOWN ) ); + state = state.withProperty( Properties.NORTH, doesConnectVisually( state, world, pos, EnumFacing.NORTH ) ); + state = state.withProperty( Properties.SOUTH, doesConnectVisually( state, world, pos, EnumFacing.SOUTH ) ); + state = state.withProperty( Properties.EAST, doesConnectVisually( state, world, pos, EnumFacing.EAST ) ); + state = state.withProperty( Properties.WEST, doesConnectVisually( state, world, pos, EnumFacing.WEST ) ); + state = state.withProperty( Properties.UP, doesConnectVisually( state, world, pos, EnumFacing.UP ) ); + state = state.withProperty( Properties.DOWN, doesConnectVisually( state, world, pos, EnumFacing.DOWN ) ); if( state.getValue( Properties.CABLE ) != BlockCableCableVariant.NONE ) { @@ -345,7 +324,6 @@ public boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnul if( WorldUtil.isVecInsideInclusive( bb, hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) { world.setBlockState( pos, state.withProperty( Properties.MODEM, BlockCableModemVariant.None ), 3 ); - cable.modemChanged(); item = PeripheralItemFactory.create( PeripheralType.WiredModem, null, 1 ); } else @@ -365,6 +343,7 @@ public boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnul return super.removedByPlayer( state, world, pos, player, willHarvest ); } + @Nonnull @Override public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult hit, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player ) { @@ -373,7 +352,7 @@ public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult hit, @ { TileCable cable = (TileCable) tile; PeripheralType type = getPeripheralType( state ); - + if( type == PeripheralType.WiredModemWithCable ) { if( hit == null || WorldUtil.isVecInsideInclusive( cable.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java index c7aa49efb8..8bad648485 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileCable.java @@ -8,14 +8,8 @@ import com.google.common.base.Objects; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.filesystem.IMount; -import dan200.computercraft.api.filesystem.IWritableMount; -import dan200.computercraft.api.lua.ILuaContext; -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.api.network.IPacketNetwork; -import dan200.computercraft.api.network.IPacketReceiver; -import dan200.computercraft.api.network.Packet; -import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.shared.common.BlockGeneric; import dan200.computercraft.shared.peripheral.PeripheralType; @@ -24,11 +18,12 @@ import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory; import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.util.PeripheralUtil; +import dan200.computercraft.api.network.wired.IWiredElementTile; +import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.NonNullList; import net.minecraft.util.math.AxisAlignedBB; @@ -38,13 +33,13 @@ import net.minecraft.world.World; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; -import static dan200.computercraft.core.apis.ArgumentHelper.getString; - -public class TileCable extends TileModemBase - implements IPacketNetwork +public class TileCable extends TileModemBase implements IWiredElementTile { public static final double MIN = 0.375; public static final double MAX = 1 - MIN; @@ -59,33 +54,13 @@ public class TileCable extends TileModemBase new AxisAlignedBB( MAX, MIN, MIN, 1, MAX, MAX ), // East }; - // Statics - - private static class Peripheral extends ModemPeripheral + private static class CableElement extends WiredModemElement { - private TileCable m_entity; - - public Peripheral( TileCable entity ) - { - m_entity = entity; - } - - @Override - public boolean isInterdimensional() - { - return false; - } - - @Override - public double getRange() - { - return 256.0; - } + private final TileCable m_entity; - @Override - protected IPacketNetwork getNetwork() + private CableElement( TileCable m_entity ) { - return m_entity; + this.m_entity = m_entity; } @Nonnull @@ -99,168 +74,81 @@ public World getWorld() @Override public Vec3d getPosition() { - EnumFacing direction = m_entity.getCachedDirection(); - BlockPos pos = m_entity.getPos().offset( direction ); - return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + BlockPos pos = m_entity.getPos(); + return new Vec3d( (double) pos.getX() + 0.5, (double) pos.getY() + 0.5, (double) pos.getZ() + 0.5 ); } @Nonnull @Override - public String[] getMethodNames() - { - String[] methods = super.getMethodNames(); - String[] newMethods = new String[ methods.length + 5 ]; - System.arraycopy( methods, 0, newMethods, 0, methods.length ); - newMethods[ methods.length ] = "getNamesRemote"; - newMethods[ methods.length + 1 ] = "isPresentRemote"; - newMethods[ methods.length + 2 ] = "getTypeRemote"; - newMethods[ methods.length + 3 ] = "getMethodsRemote"; - newMethods[ methods.length + 4 ] = "callRemote"; - return newMethods; - } - - @Override - public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException - { - String[] methods = super.getMethodNames(); - switch( method - methods.length ) - { - case 0: - { - // getNamesRemote - synchronized( m_entity.m_peripheralsByName ) - { - int idx = 1; - Map table = new HashMap<>(); - for( String name : m_entity.m_peripheralWrappersByName.keySet() ) - { - table.put( idx++, name ); - } - return new Object[] { table }; - } - } - case 1: - { - // isPresentRemote - String type = m_entity.getTypeRemote( getString( arguments, 0 ) ); - return new Object[] { type != null }; - } - case 2: - { - // getTypeRemote - String type = m_entity.getTypeRemote( getString( arguments, 0 ) ); - if( type != null ) - { - return new Object[] { type }; - } - return null; - } - case 3: - { - // getMethodsRemote - String[] methodNames = m_entity.getMethodNamesRemote( getString( arguments, 0 ) ); - if( methodNames != null ) - { - Map table = new HashMap<>(); - for(int i=0; i getPeripherals() { - super.attach( computer ); - synchronized( m_entity.m_peripheralsByName ) - { - for (String periphName : m_entity.m_peripheralsByName.keySet()) - { - IPeripheral peripheral = m_entity.m_peripheralsByName.get( periphName ); - if( peripheral != null ) - { - m_entity.attachPeripheral( periphName, peripheral ); - } - } - } + IPeripheral peripheral = m_entity.getConnectedPeripheral(); + return peripheral != null + ? Collections.singletonMap( m_entity.getConnectedPeripheralName(), peripheral ) + : Collections.emptyMap(); } @Override - public synchronized void detach( @Nonnull IComputerAccess computer ) + protected void attachPeripheral( String name, IPeripheral peripheral ) { - synchronized( m_entity.m_peripheralsByName ) + if( !name.equals( m_entity.getConnectedPeripheralName() ) ) { - for (String periphName : m_entity.m_peripheralsByName.keySet()) - { - m_entity.detachPeripheral( periphName ); - } + ((WiredModemPeripheral) m_entity.m_modem).attachPeripheral( name, peripheral ); } - super.detach( computer ); } @Override - public boolean equals( IPeripheral other ) + protected void detachPeripheral( String name ) { - if( other instanceof Peripheral ) - { - Peripheral otherModem = (Peripheral)other; - return otherModem.m_entity == m_entity; - } - return false; + ((WiredModemPeripheral) m_entity.m_modem).detachPeripheral( name ); } } - private static int s_nextUniqueSearchID = 1; - // Members - private final Set m_receivers; - private final Queue m_transmitQueue; - private boolean m_peripheralAccessAllowed; private int m_attachedPeripheralID; - - private final Map m_peripheralsByName; - private Map m_peripheralWrappersByName; - private boolean m_peripheralsKnown; + private boolean m_destroyed; - - private int m_lastSearchID; private boolean m_hasDirection = false; - + private boolean m_connectionsFormed = false; + + private WiredModemElement m_cable; + private IWiredNode m_node; + public TileCable() { - m_receivers = new HashSet<>(); - m_transmitQueue = new LinkedList<>(); - m_peripheralAccessAllowed = false; m_attachedPeripheralID = -1; - - m_peripheralsByName = new HashMap<>(); - m_peripheralWrappersByName = new HashMap<>(); - m_peripheralsKnown = false; + m_destroyed = false; - - m_lastSearchID = 0; + } + + @Override + protected ModemPeripheral createPeripheral() + { + m_cable = new CableElement( this ); + m_node = m_cable.getNode(); + return new WiredModemPeripheral( m_cable ) + { + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = getPos().offset( getCachedDirection() ); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + }; + } + + private void remove() + { + if( world == null || !world.isRemote ) + { + m_node.remove(); + m_connectionsFormed = false; + } } @Override @@ -269,11 +157,25 @@ public void destroy() if( !m_destroyed ) { m_destroyed = true; - networkChanged(); + remove(); } super.destroy(); } + @Override + public void onChunkUnload() + { + super.onChunkUnload(); + remove(); + } + + @Override + public void invalidate() + { + super.invalidate(); + remove(); + } + @Override public void onLoad() { @@ -373,17 +275,20 @@ public void onNeighbourChange() case WiredModem: { // Drop everything and remove block - ((BlockGeneric)getBlockType()).dropAllItems( getWorld(), getPos(), false ); + ((BlockGeneric) getBlockType()).dropAllItems( getWorld(), getPos(), false ); getWorld().setBlockToAir( getPos() ); - break; + + // This'll call #destroy(), so we don't need to reset the network here. + return; } case WiredModemWithCable: { // Drop the modem and convert to cable - ((BlockGeneric)getBlockType()).dropItem( getWorld(), getPos(), PeripheralItemFactory.create( PeripheralType.WiredModem, getLabel(), 1 ) ); + ((BlockGeneric) getBlockType()).dropItem( getWorld(), getPos(), PeripheralItemFactory.create( PeripheralType.WiredModem, getLabel(), 1 ) ); setLabel( null ); setBlockState( getBlockState().withProperty( BlockCable.Properties.MODEM, BlockCableModemVariant.None ) ); - if( modemChanged() ) networkChanged(); + networkChanged(); + break; } } @@ -392,10 +297,10 @@ public void onNeighbourChange() public AxisAlignedBB getModemBounds() { - return super.getBounds(); + return super.getBounds(); } - - public AxisAlignedBB getCableBounds() + + private AxisAlignedBB getCableBounds() { double xMin = 0.375; double yMin = 0.375; @@ -405,33 +310,35 @@ public AxisAlignedBB getCableBounds() double zMax = 0.625; BlockPos pos = getPos(); World world = getWorld(); - if( BlockCable.isCable( world, pos.west() ) ) + + IBlockState state = getBlockState(); + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.WEST ) ) { xMin = 0.0; } - if( BlockCable.isCable( world, pos.east() ) ) + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.EAST ) ) { xMax = 1.0; } - if( BlockCable.isCable( world, pos.down() ) ) + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.DOWN ) ) { yMin = 0.0; } - if( BlockCable.isCable( world, pos.up() ) ) + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.UP ) ) { yMax = 1.0; } - if( BlockCable.isCable( world, pos.north() ) ) + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.NORTH ) ) { zMin = 0.0; } - if( BlockCable.isCable( world, pos.south() ) ) + if( BlockCable.doesConnectVisually( state, world, pos, EnumFacing.SOUTH ) ) { zMax = 1.0; } return new AxisAlignedBB( xMin, yMin, zMin, xMax, yMax, zMax ); } - + @Nonnull @Override public AxisAlignedBB getBounds() @@ -468,12 +375,13 @@ public void getCollisionBounds( @Nonnull List bounds ) if( type == PeripheralType.Cable || type == PeripheralType.WiredModemWithCable ) { bounds.add( BOX_CENTRE ); - BlockPos pos = getPos(); - for (EnumFacing facing : EnumFacing.VALUES) + + IBlockState state = getBlockState(); + for( EnumFacing facing : EnumFacing.VALUES ) { - if( BlockCable.isCable( getWorld(), pos.offset( facing ) ) ) + if( BlockCable.doesConnectVisually( state, world, pos, facing ) ) { - bounds.add( BOXES[ facing.ordinal() ] ); + bounds.add( BOXES[facing.ordinal()] ); } } } @@ -519,30 +427,24 @@ public boolean onActivate( EntityPlayer player, EnumFacing side, float hitX, flo } @Override - public void readFromNBT(NBTTagCompound nbttagcompound) + public void readFromNBT( NBTTagCompound nbttagcompound ) { // Read properties - super.readFromNBT(nbttagcompound); + super.readFromNBT( nbttagcompound ); m_peripheralAccessAllowed = nbttagcompound.getBoolean( "peripheralAccess" ); m_attachedPeripheralID = nbttagcompound.getInteger( "peripheralID" ); } @Nonnull @Override - public NBTTagCompound writeToNBT(NBTTagCompound nbttagcompound) + public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound ) { // Write properties - nbttagcompound = super.writeToNBT(nbttagcompound); + nbttagcompound = super.writeToNBT( nbttagcompound ); nbttagcompound.setBoolean( "peripheralAccess", m_peripheralAccessAllowed ); nbttagcompound.setInteger( "peripheralID", m_attachedPeripheralID ); return nbttagcompound; } - - @Override - protected ModemPeripheral createPeripheral() - { - return new Peripheral( this ); - } @Override protected void updateAnim() @@ -559,190 +461,55 @@ protected void updateAnim() setAnim( anim ); } - // IPeripheralTile - - @Override - public IPeripheral getPeripheral( EnumFacing side ) - { - if( getPeripheralType() != PeripheralType.Cable ) - { - return super.getPeripheral( side ); - } - return null; - } - @Override public void update() { super.update(); updateDirection(); if( !getWorld().isRemote ) - { - synchronized( m_peripheralsByName ) + { + if( !m_connectionsFormed ) { - if( !m_peripheralsKnown ) - { - findPeripherals(); - m_peripheralsKnown = true; - } + networkChanged(); + if( m_peripheralAccessAllowed ) m_node.invalidate(); + m_connectionsFormed = true; } - synchronized( m_transmitQueue ) - { - while( m_transmitQueue.peek() != null ) - { - PacketWrapper p = m_transmitQueue.remove(); - if( p != null ) - { - dispatchPacket( p ); - } - } - } - } - } - - // IPacketNetwork implementation - - @Override - public void addReceiver( @Nonnull IPacketReceiver receiver ) - { - synchronized( m_receivers ) - { - m_receivers.add( receiver ); - } - } - - @Override - public void removeReceiver( @Nonnull IPacketReceiver receiver ) - { - synchronized( m_receivers ) - { - m_receivers.remove( receiver ); - } - } - - @Override - public void transmitSameDimension( @Nonnull Packet packet, double range ) - { - synchronized( m_transmitQueue ) - { - m_transmitQueue.offer( new PacketWrapper( packet, range ) ); - } - } - - @Override - public void transmitInterdimensional( @Nonnull Packet packet ) - { - synchronized( m_transmitQueue ) - { - m_transmitQueue.offer( new PacketWrapper( packet, Double.MAX_VALUE ) ); } } - @Override - public boolean isWireless() - { - return false; - } - - private void attachPeripheral( String periphName, IPeripheral peripheral ) + public void networkChanged() { - if( !m_peripheralWrappersByName.containsKey( periphName ) ) - { - RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( peripheral, m_modem.getComputer(), periphName ); - m_peripheralWrappersByName.put( periphName, wrapper ); - wrapper.attach(); - } - } + if( getWorld().isRemote ) return; - private void detachPeripheral( String periphName ) - { - if( m_peripheralWrappersByName.containsKey( periphName ) ) - { - RemotePeripheralWrapper wrapper = m_peripheralWrappersByName.get( periphName ); - m_peripheralWrappersByName.remove( periphName ); - wrapper.detach(); - } - } + if( modemChanged() ) m_node.invalidate(); - private String getTypeRemote( String remoteName ) - { - synchronized( m_peripheralsByName ) - { - RemotePeripheralWrapper wrapper = m_peripheralWrappersByName.get( remoteName ); - if( wrapper != null ) - { - return wrapper.getType(); - } - } - return null; - } - - private String[] getMethodNamesRemote( String remoteName ) - { - synchronized( m_peripheralsByName ) - { - RemotePeripheralWrapper wrapper = m_peripheralWrappersByName.get( remoteName ); - if( wrapper != null ) - { - return wrapper.getMethodNames(); - } - } - return null; - } - - private Object[] callMethodRemote( String remoteName, ILuaContext context, String method, Object[] arguments ) throws LuaException, InterruptedException - { - RemotePeripheralWrapper wrapper; - synchronized( m_peripheralsByName ) - { - wrapper = m_peripheralWrappersByName.get( remoteName ); - } - if( wrapper != null ) + IBlockState state = getBlockState(); + World world = getWorld(); + BlockPos current = getPos(); + for( EnumFacing facing : EnumFacing.VALUES ) { - return wrapper.callMethod( context, method, arguments ); - } - throw new LuaException( "No peripheral: "+remoteName ); - } + if( !world.isBlockLoaded( pos ) ) continue; + + IWiredElement element = ComputerCraft.getWiredElementAt( world, current.offset( facing ), facing.getOpposite() ); + if( element == null ) continue; - public void networkChanged() - { - if( !getWorld().isRemote ) - { - if( !m_destroyed && getPeripheralType() != PeripheralType.WiredModem) + if( BlockCable.canConnectIn( state, facing ) ) { - // If this modem is alive, rebuild the network - searchNetwork( ( modem, distance ) -> - { - synchronized( modem.m_peripheralsByName ) - { - modem.m_peripheralsKnown = false; - } - } ); + // If we can connect to it then do so + m_node.connectTo( element.getNode() ); } - else + else if( m_node.getNetwork() == element.getNode().getNetwork() ) { - // If this modem is dead, rebuild the neighbours' networks - for( EnumFacing dir : EnumFacing.values() ) - { - BlockPos offset = getPos().offset( dir ); - if( offset.getY() >= 0 && offset.getY() < getWorld().getHeight() && BlockCable.isCable( getWorld(), offset ) ) - { - TileEntity tile = getWorld().getTileEntity( offset ); - if( tile != null && tile instanceof TileCable ) - { - TileCable modem = (TileCable)tile; - modem.networkChanged(); - } - } - } + // Otherwise if we're on the same network then attempt to void it. + m_node.disconnectFrom( element.getNode() ); } } } - public boolean modemChanged() + private boolean modemChanged() { if( getWorld().isRemote ) return false; - + boolean requiresUpdate = false; PeripheralType type = getPeripheralType(); @@ -758,220 +525,12 @@ public boolean modemChanged() markDirty(); updateAnim(); } - - return requiresUpdate; - } - - // private stuff - - // Packet sending - - private static class PacketWrapper - { - final Packet m_packet; - final double m_range; - - private PacketWrapper( Packet m_packet, double m_range ) - { - this.m_packet = m_packet; - this.m_range = m_range; - } - } - - private void dispatchPacket( final PacketWrapper packet ) - { - searchNetwork( ( modem, distance ) -> - { - if( distance <= packet.m_range) - { - modem.receivePacket( packet.m_packet, distance ); - } - } ); - } - - private void receivePacket( Packet packet, int distanceTravelled ) - { - synchronized( m_receivers ) - { - for (IPacketReceiver device : m_receivers) - { - device.receiveSameDimension( packet, distanceTravelled ); - } - } - } - - // Remote peripheral control - - private static class RemotePeripheralWrapper implements IComputerAccess - { - private IPeripheral m_peripheral; - private IComputerAccess m_computer; - private String m_name; - - private String m_type; - private String[] m_methods; - private Map m_methodMap; - - public RemotePeripheralWrapper( IPeripheral peripheral, IComputerAccess computer, String name ) - { - m_peripheral = peripheral; - m_computer = computer; - m_name = name; - - m_type = peripheral.getType(); - m_methods = peripheral.getMethodNames(); - assert( m_type != null ); - assert( m_methods != null ); - - m_methodMap = new HashMap<>(); - for( int i=0; i newPeripheralsByName = new HashMap<>(); - if( getPeripheralType() == PeripheralType.WiredModemWithCable ) - { - searchNetwork( ( modem, distance ) -> - { - if( modem != origin ) - { - IPeripheral peripheral = modem.getConnectedPeripheral(); - String periphName = modem.getConnectedPeripheralName(); - if( peripheral != null && periphName != null ) - { - newPeripheralsByName.put( periphName, peripheral ); - } - } - } ); - } - //System.out.println( newPeripheralsByName.size()+" peripherals discovered" ); - - // Detach all the old peripherals - Iterator it = m_peripheralsByName.keySet().iterator(); - while( it.hasNext() ) - { - String periphName = it.next(); - if( !newPeripheralsByName.containsKey( periphName ) ) - { - detachPeripheral( periphName ); - it.remove(); - } - } - - // Attach all the new peripherals - for( String periphName : newPeripheralsByName.keySet() ) - { - if( !m_peripheralsByName.containsKey( periphName ) ) - { - IPeripheral peripheral = newPeripheralsByName.get( periphName ); - if( peripheral != null ) - { - m_peripheralsByName.put( periphName, peripheral ); - if( isAttached() ) - { - attachPeripheral( periphName, peripheral ); - } - } - } - } - //System.out.println( m_peripheralsByName.size()+" connected" ); - } - } - - public void togglePeripheralAccess() + // private stuff + private void togglePeripheralAccess() { if( !m_peripheralAccessAllowed ) { @@ -986,11 +545,12 @@ public void togglePeripheralAccess() { m_peripheralAccessAllowed = false; } - updateAnim(); - networkChanged(); + + updateAnim(); + m_node.invalidate(); } - - public String getConnectedPeripheralName() + + private String getConnectedPeripheralName() { IPeripheral periph = getConnectedPeripheral(); if( periph != null ) @@ -998,16 +558,16 @@ public String getConnectedPeripheralName() String type = periph.getType(); if( m_attachedPeripheralID < 0 ) { - m_attachedPeripheralID = IDAssigner.getNextIDFromFile(new File( - ComputerCraft.getWorldDir(getWorld()), + m_attachedPeripheralID = IDAssigner.getNextIDFromFile( new File( + ComputerCraft.getWorldDir( getWorld() ), "computer/lastid_" + type + ".txt" - )); + ) ); } return type + "_" + m_attachedPeripheralID; } return null; } - + private IPeripheral getConnectedPeripheral() { if( m_peripheralAccessAllowed ) @@ -1016,84 +576,45 @@ private IPeripheral getConnectedPeripheral() { EnumFacing facing = getDirection(); BlockPos neighbour = getPos().offset( facing ); - return PeripheralUtil.getPeripheral( getWorld(), neighbour, facing.getOpposite() ); + IPeripheral peripheral = getPeripheral( getWorld(), neighbour, facing.getOpposite() ); + return peripheral == null || peripheral instanceof WiredModemPeripheral ? null : peripheral; } } return null; } - - // Generic network search stuff - - private interface ICableVisitor - { - void visit( TileCable modem, int distance ); - } - - private static class SearchLoc - { - public World world; - public BlockPos pos; - public int distanceTravelled; - } - - private static void enqueue( Queue queue, World world, BlockPos pos, int distanceTravelled ) + + public static IPeripheral getPeripheral( World world, BlockPos pos, EnumFacing facing ) { - int y = pos.getY(); - if( y >= 0 && y < world.getHeight() && BlockCable.isCable( world, pos ) ) - { - SearchLoc loc = new SearchLoc(); - loc.world = world; - loc.pos = pos; - loc.distanceTravelled = distanceTravelled; - queue.offer( loc ); - } + Block block = world.getBlockState( pos ).getBlock(); + if( block == ComputerCraft.Blocks.wiredModemFull || block == ComputerCraft.Blocks.cable ) return null; + + return PeripheralUtil.getPeripheral( world, pos, facing ); } - - private static void visitBlock( Queue queue, SearchLoc location, int searchID, ICableVisitor visitor ) + + @Override + public boolean canRenderBreaking() { - if( location.distanceTravelled >= 256 ) - { - return; - } - - TileEntity tile = location.world.getTileEntity( location.pos ); - if( tile != null && tile instanceof TileCable ) - { - TileCable modem = (TileCable)tile; - if( !modem.m_destroyed && modem.m_lastSearchID != searchID ) - { - modem.m_lastSearchID = searchID; - visitor.visit( modem, location.distanceTravelled + 1 ); - - enqueue( queue, location.world, location.pos.up(), location.distanceTravelled + 1 ); - enqueue( queue, location.world, location.pos.down(), location.distanceTravelled + 1 ); - enqueue( queue, location.world, location.pos.south(), location.distanceTravelled + 1 ); - enqueue( queue, location.world, location.pos.north(), location.distanceTravelled + 1 ); - enqueue( queue, location.world, location.pos.east(), location.distanceTravelled + 1 ); - enqueue( queue, location.world, location.pos.west(), location.distanceTravelled + 1 ); - } - } + return true; } - private void searchNetwork( ICableVisitor visitor ) + // IWiredElement tile + + @Nullable + @Override + public IWiredElement getWiredElement( @Nonnull EnumFacing side ) { - int searchID = ++s_nextUniqueSearchID; - Queue queue = new LinkedList<>(); - enqueue( queue, getWorld(), getPos(), 1 ); - - //int visited = 0; - while( queue.peek() != null ) - { - SearchLoc loc = queue.remove(); - visitBlock( queue, loc, searchID, visitor ); - //visited++; - } - //System.out.println( "Visited "+visited+" common" ); + return BlockCable.canConnectIn( getBlockState(), side ) ? m_cable : null; } + // IPeripheralTile + @Override - public boolean canRenderBreaking() + public IPeripheral getPeripheral( EnumFacing side ) { - return true; + if( getPeripheralType() != PeripheralType.Cable ) + { + return super.getPeripheral( side ); + } + return null; } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemElement.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemElement.java new file mode 100644 index 0000000000..682e8b1cf5 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemElement.java @@ -0,0 +1,59 @@ +package dan200.computercraft.shared.peripheral.modem; + +import dan200.computercraft.api.network.wired.IWiredNetworkChange; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.wired.WiredNode; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +public abstract class WiredModemElement implements IWiredElement +{ + private final IWiredNode node = new WiredNode( this ); + private final Map remotePeripherals = new HashMap<>(); + + @Nonnull + @Override + public IWiredNode getNode() + { + return node; + } + + @Nonnull + @Override + public String getSenderID() + { + return "modem"; + } + + @Override + public void networkChanged( @Nonnull IWiredNetworkChange change ) + { + synchronized( remotePeripherals ) + { + remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() ); + for( String name : change.peripheralsRemoved().keySet() ) + { + detachPeripheral( name ); + } + + for( Map.Entry peripheral : change.peripheralsAdded().entrySet() ) + { + attachPeripheral( peripheral.getKey(), peripheral.getValue() ); + } + remotePeripherals.putAll( change.peripheralsAdded() ); + } + } + + public Map getRemotePeripherals() + { + return remotePeripherals; + } + + protected abstract void attachPeripheral( String name, IPeripheral peripheral ); + + protected abstract void detachPeripheral( String name ); +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemPeripheral.java new file mode 100644 index 0000000000..f139dfc4fc --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/WiredModemPeripheral.java @@ -0,0 +1,410 @@ +package dan200.computercraft.shared.peripheral.modem; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredSender; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +import static dan200.computercraft.core.apis.ArgumentHelper.getString; + +public class WiredModemPeripheral extends ModemPeripheral implements IWiredSender +{ + private final WiredModemElement modem; + + private final Map peripheralWrappers = new HashMap<>(); + + public WiredModemPeripheral( WiredModemElement modem ) + { + this.modem = modem; + } + + //region IPacketSender implementation + @Override + public boolean isInterdimensional() + { + return false; + } + + @Override + public double getRange() + { + return 256.0; + } + + @Override + protected IPacketNetwork getNetwork() + { + return modem.getNode(); + } + + @Nonnull + @Override + public World getWorld() + { + return modem.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + return modem.getPosition(); + } + //endregion + + //region IPeripheral + @Nonnull + @Override + public String[] getMethodNames() + { + String[] methods = super.getMethodNames(); + String[] newMethods = new String[methods.length + 5]; + System.arraycopy( methods, 0, newMethods, 0, methods.length ); + newMethods[methods.length] = "getNamesRemote"; + newMethods[methods.length + 1] = "isPresentRemote"; + newMethods[methods.length + 2] = "getTypeRemote"; + newMethods[methods.length + 3] = "getMethodsRemote"; + newMethods[methods.length + 4] = "callRemote"; + return newMethods; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + String[] methods = super.getMethodNames(); + switch( method - methods.length ) + { + case 0: + { + // getNamesRemote + synchronized( peripheralWrappers ) + { + int idx = 1; + Map table = new HashMap<>(); + for( String name : peripheralWrappers.keySet() ) + { + table.put( idx++, name ); + } + return new Object[]{ table }; + } + } + case 1: + { + // isPresentRemote + String type = getTypeRemote( getString( arguments, 0 ) ); + return new Object[]{ type != null }; + } + case 2: + { + // getTypeRemote + String type = getTypeRemote( getString( arguments, 0 ) ); + if( type != null ) + { + return new Object[]{ type }; + } + return null; + } + case 3: + { + // getMethodsRemote + String[] methodNames = getMethodNamesRemote( getString( arguments, 0 ) ); + if( methodNames != null ) + { + Map table = new HashMap<>(); + for( int i = 0; i < methodNames.length; ++i ) + { + table.put( i + 1, methodNames[i] ); + } + return new Object[]{ table }; + } + return null; + } + case 4: + { + // callRemote + String remoteName = getString( arguments, 0 ); + String methodName = getString( arguments, 1 ); + Object[] methodArgs = new Object[arguments.length - 2]; + System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 ); + return callMethodRemote( remoteName, context, methodName, methodArgs ); + } + default: + { + // The regular modem methods + return super.callMethod( computer, context, method, arguments ); + } + } + } + + @Override + public void attach( @Nonnull IComputerAccess computer ) + { + super.attach( computer ); + synchronized( modem.getRemotePeripherals() ) + { + synchronized( peripheralWrappers ) + { + for( Map.Entry entry : modem.getRemotePeripherals().entrySet() ) + { + attachPeripheralImpl( entry.getKey(), entry.getValue() ); + } + } + } + } + + @Override + public synchronized void detach( @Nonnull IComputerAccess computer ) + { + synchronized( peripheralWrappers ) + { + for( RemotePeripheralWrapper wrapper : peripheralWrappers.values() ) + { + wrapper.detach(); + } + peripheralWrappers.clear(); + } + super.detach( computer ); + } + + @Override + public boolean equals( IPeripheral other ) + { + if( other instanceof WiredModemPeripheral ) + { + WiredModemPeripheral otherModem = (WiredModemPeripheral) other; + return otherModem.modem == modem; + } + return false; + } + //endregion + + @Nonnull + @Override + public IWiredNode getNode() + { + return modem.getNode(); + } + + public void attachPeripheral( String name, IPeripheral peripheral ) + { + if( getComputer() == null ) return; + + synchronized( peripheralWrappers ) + { + attachPeripheralImpl( name, peripheral ); + } + } + + public void detachPeripheral( String name ) + { + synchronized( peripheralWrappers ) + { + RemotePeripheralWrapper wrapper = peripheralWrappers.get( name ); + if( wrapper != null ) + { + peripheralWrappers.remove( name ); + wrapper.detach(); + } + } + } + + private void attachPeripheralImpl( String periphName, IPeripheral peripheral ) + { + if( !peripheralWrappers.containsKey( periphName ) ) + { + RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName ); + peripheralWrappers.put( periphName, wrapper ); + wrapper.attach(); + } + } + + private String getTypeRemote( String remoteName ) + { + synchronized( peripheralWrappers ) + { + RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName ); + if( wrapper != null ) + { + return wrapper.getType(); + } + } + return null; + } + + private String[] getMethodNamesRemote( String remoteName ) + { + synchronized( peripheralWrappers ) + { + RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName ); + if( wrapper != null ) + { + return wrapper.getMethodNames(); + } + } + return null; + } + + private Object[] callMethodRemote( String remoteName, ILuaContext context, String method, Object[] arguments ) throws LuaException, InterruptedException + { + RemotePeripheralWrapper wrapper; + synchronized( peripheralWrappers ) + { + wrapper = peripheralWrappers.get( remoteName ); + } + if( wrapper != null ) + { + return wrapper.callMethod( context, method, arguments ); + } + throw new LuaException( "No peripheral: " + remoteName ); + } + + private static class RemotePeripheralWrapper implements IComputerAccess + { + private final WiredModemElement m_element; + private final IPeripheral m_peripheral; + private final IComputerAccess m_computer; + private final String m_name; + + private final String m_type; + private final String[] m_methods; + private final Map m_methodMap; + + public RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name ) + { + m_element = element; + m_peripheral = peripheral; + m_computer = computer; + m_name = name; + + m_type = peripheral.getType(); + m_methods = peripheral.getMethodNames(); + assert (m_type != null); + assert (m_methods != null); + + m_methodMap = new HashMap<>(); + for( int i = 0; i < m_methods.length; ++i ) + { + if( m_methods[i] != null ) + { + m_methodMap.put( m_methods[i], i ); + } + } + } + + public void attach() + { + m_peripheral.attach( this ); + m_computer.queueEvent( "peripheral", new Object[]{ getAttachmentName() } ); + } + + public void detach() + { + m_peripheral.detach( this ); + m_computer.queueEvent( "peripheral_detach", new Object[]{ getAttachmentName() } ); + } + + public String getType() + { + return m_type; + } + + public String[] getMethodNames() + { + return m_methods; + } + + public Object[] callMethod( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException + { + if( m_methodMap.containsKey( methodName ) ) + { + int method = m_methodMap.get( methodName ); + return m_peripheral.callMethod( this, context, method, arguments ); + } + throw new LuaException( "No such method " + methodName ); + } + + // IComputerAccess implementation + + @Override + public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount ) + { + return m_computer.mount( desiredLocation, mount, m_name ); + } + + @Override + public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName ) + { + return m_computer.mount( desiredLocation, mount, driveName ); + } + + @Override + public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount ) + { + return m_computer.mountWritable( desiredLocation, mount, m_name ); + } + + @Override + public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName ) + { + return m_computer.mountWritable( desiredLocation, mount, driveName ); + } + + @Override + public void unmount( String location ) + { + m_computer.unmount( location ); + } + + @Override + public int getID() + { + return m_computer.getID(); + } + + @Override + public void queueEvent( @Nonnull String event, Object[] arguments ) + { + m_computer.queueEvent( event, arguments ); + } + + @Nonnull + @Override + public String getAttachmentName() + { + return m_name; + } + + @Nonnull + @Override + public Map getAvailablePeripherals() + { + synchronized( m_element.getRemotePeripherals() ) + { + return ImmutableMap.copyOf( m_element.getRemotePeripherals() ); + } + } + + @Nullable + @Override + public IPeripheral getAvailablePeripheral( @Nonnull String name ) + { + synchronized( m_element.getRemotePeripherals() ) + { + return m_element.getRemotePeripherals().get( name ); + } + } + } +} From 922f424a78a4ecc1854a39ff7c8debe16df181eb Mon Sep 17 00:00:00 2001 From: SquidDev Date: Wed, 21 Feb 2018 15:40:08 +0000 Subject: [PATCH 5/5] Add full block wired modems These act similarly to conventional wired modems, but with the advantage that they are a full block. This means they can be attached to peripherals which are not solid (such as chests). Further more, as they do not have a direction, they allow wrapping peripherals on all 6 sides. It's worth noting that wired modems do not require a cable - they will automatically form connections to adjacent network elements when placed. --- .../dan200/computercraft/ComputerCraft.java | 2 + .../proxy/ComputerCraftProxyClient.java | 1 + .../shared/peripheral/PeripheralType.java | 3 +- .../common/BlockWiredModemFull.java | 102 +++++ .../peripheral/common/ItemPeripheralBase.java | 4 +- .../peripheral/common/ItemWiredModemFull.java | 18 + .../common/PeripheralItemFactory.java | 2 + .../peripheral/modem/TileWiredModemFull.java | 416 ++++++++++++++++++ .../proxy/ComputerCraftProxyCommon.java | 15 +- .../advancements/recipes/wired_modem.json | 13 +- .../blockstates/wired_modem_full.json | 9 + .../models/block/wired_modem_full_off.json | 6 + .../wired_modem_full_off_peripheral.json | 6 + .../models/block/wired_modem_full_on.json | 6 + .../block/wired_modem_full_on_peripheral.json | 6 + .../models/item/wired_modem_full.json | 3 + .../recipes/wired_modem_full_from.json | 7 + .../recipes/wired_modem_full_to.json | 7 + 18 files changed, 618 insertions(+), 8 deletions(-) create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/common/BlockWiredModemFull.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/common/ItemWiredModemFull.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/modem/TileWiredModemFull.java create mode 100644 src/main/resources/assets/computercraft/blockstates/wired_modem_full.json create mode 100644 src/main/resources/assets/computercraft/models/block/wired_modem_full_off.json create mode 100644 src/main/resources/assets/computercraft/models/block/wired_modem_full_off_peripheral.json create mode 100644 src/main/resources/assets/computercraft/models/block/wired_modem_full_on.json create mode 100644 src/main/resources/assets/computercraft/models/block/wired_modem_full_on_peripheral.json create mode 100644 src/main/resources/assets/computercraft/models/item/wired_modem_full.json create mode 100644 src/main/resources/assets/computercraft/recipes/wired_modem_full_from.json create mode 100644 src/main/resources/assets/computercraft/recipes/wired_modem_full_to.json diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 4211e7eea1..013b203ca3 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -47,6 +47,7 @@ import dan200.computercraft.shared.network.PacketHandler; import dan200.computercraft.shared.peripheral.common.BlockCable; import dan200.computercraft.shared.peripheral.common.BlockPeripheral; +import dan200.computercraft.shared.peripheral.common.BlockWiredModemFull; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem; import dan200.computercraft.shared.peripheral.modem.WirelessNetwork; @@ -180,6 +181,7 @@ public static class Blocks public static BlockTurtle turtleAdvanced; public static BlockCommandComputer commandComputer; public static BlockAdvancedModem advancedModem; + public static BlockWiredModemFull wiredModemFull; } public static class Items diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index 7ce9e97033..305f0bcacb 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -115,6 +115,7 @@ public ModelResourceLocation getModelLocation( @Nonnull ItemStack stack ) registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" ); registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" ); registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" ); + registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" ); registerItemModel( ComputerCraft.Items.disk, "disk" ); registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/PeripheralType.java b/src/main/java/dan200/computercraft/shared/peripheral/PeripheralType.java index 2cfaaf0703..b209a60c2b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/PeripheralType.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/PeripheralType.java @@ -21,7 +21,8 @@ public enum PeripheralType implements IStringSerializable Cable( "cable" ), WiredModemWithCable( "wired_modem_with_cable" ), AdvancedModem( "advanced_modem" ), - Speaker( "speaker" ); + Speaker( "speaker" ), + WiredModemFull( "wired_modem_full" ); private String m_name; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/BlockWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockWiredModemFull.java new file mode 100644 index 0000000000..6936b12ca2 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/BlockWiredModemFull.java @@ -0,0 +1,102 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.shared.peripheral.common; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.peripheral.PeripheralType; +import dan200.computercraft.shared.peripheral.modem.TileWiredModemFull; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; + +import javax.annotation.Nonnull; + +public class BlockWiredModemFull extends BlockPeripheralBase +{ + // Statics + + public static class Properties + { + public static final PropertyBool MODEM_ON = PropertyBool.create( "modem" ); + public static final PropertyBool PERIPHERAL_ON = PropertyBool.create( "peripheral" ); + } + + // Members + + public BlockWiredModemFull() + { + setHardness( 1.5f ); + setUnlocalizedName( "computercraft:wired_modem_full" ); + setCreativeTab( ComputerCraft.mainCreativeTab ); + setDefaultState( blockState.getBaseState() + .withProperty( Properties.MODEM_ON, false ) + .withProperty( Properties.PERIPHERAL_ON, false ) + ); + } + + @Override + protected IBlockState getDefaultBlockState( PeripheralType type, EnumFacing placedSide ) + { + return getDefaultState(); + } + + @Nonnull + @Override + protected BlockStateContainer createBlockState() + { + return new BlockStateContainer( this, + Properties.MODEM_ON, + Properties.PERIPHERAL_ON + ); + } + + @Override + public int getMetaFromState( IBlockState state ) + { + return 0; + } + + @Nonnull + @Override + @Deprecated + public IBlockState getActualState( @Nonnull IBlockState state, IBlockAccess world, BlockPos pos ) + { + TileEntity te = world.getTileEntity( pos ); + if( te instanceof TileWiredModemFull ) + { + TileWiredModemFull modem = (TileWiredModemFull) te; + int anim = modem.getAnim(); + state = state + .withProperty( Properties.MODEM_ON, (anim & 1) != 0 ) + .withProperty( Properties.PERIPHERAL_ON, (anim & 2) != 0 ); + } + + return state; + } + + @Override + public PeripheralType getPeripheralType( int damage ) + { + return PeripheralType.WiredModemFull; + } + + @Override + public PeripheralType getPeripheralType( IBlockState state ) + { + return PeripheralType.WiredModemFull; + } + + @Override + public TilePeripheralBase createTile( PeripheralType type ) + { + return new TileWiredModemFull(); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/ItemPeripheralBase.java b/src/main/java/dan200/computercraft/shared/peripheral/common/ItemPeripheralBase.java index cd39a1b0f5..a08bbd5b03 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/common/ItemPeripheralBase.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/ItemPeripheralBase.java @@ -11,8 +11,8 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -102,6 +102,8 @@ public String getUnlocalizedName( @Nonnull ItemStack stack ) { return "tile.computercraft:speaker"; } + case WiredModemFull: + return "tile.computercraft:wired_modem"; } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/ItemWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/common/ItemWiredModemFull.java new file mode 100644 index 0000000000..11e8a399df --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/ItemWiredModemFull.java @@ -0,0 +1,18 @@ +package dan200.computercraft.shared.peripheral.common; + +import dan200.computercraft.shared.peripheral.PeripheralType; +import net.minecraft.block.Block; + +public class ItemWiredModemFull extends ItemPeripheralBase +{ + public ItemWiredModemFull( Block block ) + { + super( block ); + } + + @Override + public PeripheralType getPeripheralType( int damage ) + { + return PeripheralType.WiredModemFull; + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/common/PeripheralItemFactory.java b/src/main/java/dan200/computercraft/shared/peripheral/common/PeripheralItemFactory.java index 1585521ff2..a9ea01310c 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/common/PeripheralItemFactory.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/common/PeripheralItemFactory.java @@ -47,6 +47,8 @@ public static ItemStack create( PeripheralType type, String label, int quantity { return advancedModem.create( type, label, quantity ); } + case WiredModemFull: + return new ItemStack( ComputerCraft.Blocks.wiredModemFull, quantity ); } return ItemStack.EMPTY; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/TileWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileWiredModemFull.java new file mode 100644 index 0000000000..d46ab6a166 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/TileWiredModemFull.java @@ -0,0 +1,416 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.shared.peripheral.modem; + +import com.google.common.base.Objects; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredElementTile; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.peripheral.common.BlockCable; +import dan200.computercraft.shared.peripheral.common.TilePeripheralBase; +import dan200.computercraft.shared.util.IDAssigner; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; + +import javax.annotation.Nonnull; +import java.io.File; +import java.util.*; + +public class TileWiredModemFull extends TilePeripheralBase implements IWiredElementTile +{ + private static class FullElement extends WiredModemElement + { + private final TileWiredModemFull m_entity; + + private FullElement( TileWiredModemFull m_entity ) + { + this.m_entity = m_entity; + } + + @Override + protected void attachPeripheral( String name, IPeripheral peripheral ) + { + for( int i = 0; i < 6; i++ ) + { + WiredModemPeripheral modem = m_entity.m_modems[i]; + if( modem != null && !name.equals( m_entity.getCachedPeripheralName( EnumFacing.VALUES[i] ) ) ) + { + modem.attachPeripheral( name, peripheral ); + } + } + } + + @Override + protected void detachPeripheral( String name ) + { + for( int i = 0; i < 6; i++ ) + { + WiredModemPeripheral modem = m_entity.m_modems[i]; + if( modem != null ) modem.detachPeripheral( name ); + } + } + + @Nonnull + @Override + public World getWorld() + { + return m_entity.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = m_entity.getPos(); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + + @Nonnull + @Override + public Map getPeripherals() + { + return m_entity.getPeripherals(); + } + } + + private WiredModemPeripheral[] m_modems = new WiredModemPeripheral[6]; + + private boolean m_peripheralAccessAllowed = false; + private int[] m_attachedPeripheralIDs = new int[6]; + private String[] m_attachedPeripheralTypes = new String[6]; + + private boolean m_destroyed = false; + private boolean m_connectionsFormed = false; + + private final WiredModemElement m_element = new FullElement( this ); + private final IWiredNode node = m_element.getNode(); + + public TileWiredModemFull() + { + Arrays.fill( m_attachedPeripheralIDs, -1 ); + } + + private void remove() + { + if( world == null || !world.isRemote ) + { + node.remove(); + m_connectionsFormed = false; + } + } + + @Override + public void destroy() + { + if( !m_destroyed ) + { + m_destroyed = true; + remove(); + } + super.destroy(); + } + + @Override + public void onChunkUnload() + { + super.onChunkUnload(); + remove(); + } + + @Override + public void invalidate() + { + super.invalidate(); + remove(); + } + + @Override + public EnumFacing getDirection() + { + return EnumFacing.NORTH; + } + + @Override + public void setDirection( EnumFacing dir ) + { + } + + @Override + public void onNeighbourChange() + { + if( !world.isRemote && m_peripheralAccessAllowed ) + { + Map updated = getPeripherals(); + + if( updated.isEmpty() ) + { + // If there are no peripherals then disable access and update the display state. + m_peripheralAccessAllowed = false; + updateAnim(); + } + + // Always invalidate the node: it's more accurate than checking if the peripherals + // have changed + node.invalidate(); + } + } + + @Nonnull + @Override + public AxisAlignedBB getBounds() + { + return BlockCable.FULL_BLOCK_AABB; + } + + @Override + public boolean onActivate( EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ ) + { + if( !getWorld().isRemote ) + { + // On server, we interacted if a peripheral was found + Set oldPeriphName = getPeripherals().keySet(); + togglePeripheralAccess(); + Set periphName = getPeripherals().keySet(); + + if( !Objects.equal( periphName, oldPeriphName ) ) + { + if( !oldPeriphName.isEmpty() ) + { + List names = new ArrayList<>( oldPeriphName ); + names.sort( Comparator.naturalOrder() ); + + player.sendMessage( + new TextComponentTranslation( "gui.computercraft:wired_modem.peripheral_disconnected", String.join( ", ", names ) ) + ); + } + if( !periphName.isEmpty() ) + { + List names = new ArrayList<>( periphName ); + names.sort( Comparator.naturalOrder() ); + player.sendMessage( + new TextComponentTranslation( "gui.computercraft:wired_modem.peripheral_connected", String.join( ", ", names ) ) + ); + } + } + + return true; + } + else + { + // On client, we can't know this, so we assume so to be safe + // The server will correct us if we're wrong + return true; + } + } + + @Override + public void readFromNBT( NBTTagCompound tag ) + { + super.readFromNBT( tag ); + m_peripheralAccessAllowed = tag.getBoolean( "peripheralAccess" ); + for( int i = 0; i < m_attachedPeripheralIDs.length; i++ ) + { + if( tag.hasKey( "peripheralID_" + i, Constants.NBT.TAG_ANY_NUMERIC ) ) + { + m_attachedPeripheralIDs[i] = tag.getInteger( "peripheralID_" + i ); + } + if( tag.hasKey( "peripheralType_" + i, Constants.NBT.TAG_STRING ) ) + { + m_attachedPeripheralTypes[i] = tag.getString( "peripheralType_" + i ); + } + } + } + + @Nonnull + @Override + public NBTTagCompound writeToNBT( NBTTagCompound tag ) + { + tag = super.writeToNBT( tag ); + tag.setBoolean( "peripheralAccess", m_peripheralAccessAllowed ); + for( int i = 0; i < m_attachedPeripheralIDs.length; i++ ) + { + if( m_attachedPeripheralIDs[i] >= 0 ) + { + tag.setInteger( "peripheralID_" + i, m_attachedPeripheralIDs[i] ); + } + if( m_attachedPeripheralTypes[i] != null ) + { + tag.setString( "peripheralType_" + i, m_attachedPeripheralTypes[i] ); + } + } + return tag; + } + + protected void updateAnim() + { + int anim = 0; + for( WiredModemPeripheral modem : m_modems ) + { + if( modem != null && modem.isActive() ) + { + anim += 1; + break; + } + } + + if( m_peripheralAccessAllowed ) + { + anim += 2; + } + setAnim( anim ); + } + + @Override + public final void readDescription( @Nonnull NBTTagCompound tag ) + { + super.readDescription( tag ); + updateBlock(); + } + + @Override + public void update() + { + if( !getWorld().isRemote ) + { + boolean changed = false; + for( WiredModemPeripheral peripheral : m_modems ) + { + if( peripheral != null && peripheral.pollChanged() ) changed = true; + } + if( changed ) updateAnim(); + + if( !m_connectionsFormed ) + { + networkChanged(); + m_connectionsFormed = true; + } + } + + super.update(); + } + + private void networkChanged() + { + if( getWorld().isRemote ) return; + + World world = getWorld(); + BlockPos current = getPos(); + for( EnumFacing facing : EnumFacing.VALUES ) + { + if( !world.isBlockLoaded( pos ) ) continue; + + IWiredElement element = ComputerCraft.getWiredElementAt( world, current.offset( facing ), facing.getOpposite() ); + if( element == null ) continue; + + // If we can connect to it then do so + node.connectTo( element.getNode() ); + } + + node.invalidate(); + } + + // private stuff + private void togglePeripheralAccess() + { + if( !m_peripheralAccessAllowed ) + { + m_peripheralAccessAllowed = true; + if( getPeripherals().isEmpty() ) + { + m_peripheralAccessAllowed = false; + return; + } + } + else + { + m_peripheralAccessAllowed = false; + } + + updateAnim(); + node.invalidate(); + } + + @Nonnull + private Map getPeripherals() + { + if( !m_peripheralAccessAllowed ) return Collections.emptyMap(); + + Map peripherals = new HashMap<>( 6 ); + for( EnumFacing facing : EnumFacing.VALUES ) + { + BlockPos neighbour = getPos().offset( facing ); + IPeripheral peripheral = TileCable.getPeripheral( getWorld(), neighbour, facing.getOpposite() ); + if( peripheral != null && !(peripheral instanceof WiredModemPeripheral) ) + { + String type = peripheral.getType(); + int id = m_attachedPeripheralIDs[facing.ordinal()]; + String oldType = m_attachedPeripheralTypes[facing.ordinal()]; + if( id < 0 || !type.equals( oldType ) ) + { + m_attachedPeripheralTypes[facing.ordinal()] = type; + id = m_attachedPeripheralIDs[facing.ordinal()] = IDAssigner.getNextIDFromFile( new File( + ComputerCraft.getWorldDir( getWorld() ), + "computer/lastid_" + type + ".txt" + ) ); + } + + peripherals.put( type + "_" + id, peripheral ); + } + } + + return peripherals; + } + + private String getCachedPeripheralName( EnumFacing facing ) + { + if( !m_peripheralAccessAllowed ) return null; + + int id = m_attachedPeripheralIDs[facing.ordinal()]; + String type = m_attachedPeripheralTypes[facing.ordinal()]; + return id < 0 || type == null ? null : type + "_" + id; + } + + // IWiredElementTile + + @Nonnull + @Override + public IWiredElement getWiredElement( @Nonnull EnumFacing side ) + { + return m_element; + } + + // IPeripheralTile + + @Override + public IPeripheral getPeripheral( EnumFacing side ) + { + WiredModemPeripheral peripheral = m_modems[side.ordinal()]; + if( peripheral == null ) + { + peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_element ) + { + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = getPos().offset( side ); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + }; + } + return peripheral; + } +} diff --git a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java index c4309a2067..a03d90660a 100644 --- a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java +++ b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java @@ -35,10 +35,7 @@ import dan200.computercraft.shared.peripheral.common.*; import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; -import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem; -import dan200.computercraft.shared.peripheral.modem.TileAdvancedModem; -import dan200.computercraft.shared.peripheral.modem.TileCable; -import dan200.computercraft.shared.peripheral.modem.TileWirelessModem; +import dan200.computercraft.shared.peripheral.modem.*; import dan200.computercraft.shared.peripheral.monitor.TileMonitor; import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; import dan200.computercraft.shared.peripheral.printer.TilePrinter; @@ -273,6 +270,10 @@ public void registerBlocks( RegistryEvent.Register event ) // Command Computer ComputerCraft.Blocks.advancedModem = new BlockAdvancedModem(); registry.register( ComputerCraft.Blocks.advancedModem.setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) ); + + // Full block modem + ComputerCraft.Blocks.wiredModemFull = new BlockWiredModemFull(); + registry.register( ComputerCraft.Blocks.wiredModemFull.setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) ) ); } @SubscribeEvent @@ -294,7 +295,10 @@ public void registerItems( RegistryEvent.Register event ) // Advanced modem registry.register( new ItemAdvancedModem( ComputerCraft.Blocks.advancedModem ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) ); - + + // Full block modem + registry.register( new ItemWiredModemFull( ComputerCraft.Blocks.wiredModemFull ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) ) ); + // Items // Floppy Disk ComputerCraft.Items.disk = new ItemDiskLegacy(); @@ -470,6 +474,7 @@ private void registerTileEntities() GameRegistry.registerTileEntity( TileCommandComputer.class, ComputerCraft.LOWER_ID + " : " + "command_computer" ); GameRegistry.registerTileEntity( TileAdvancedModem.class, ComputerCraft.LOWER_ID + " : " + "advanced_modem" ); GameRegistry.registerTileEntity( TileSpeaker.class, ComputerCraft.LOWER_ID + " : " + "speaker" ); + GameRegistry.registerTileEntity( TileWiredModemFull.class, ComputerCraft.LOWER_ID + " : " + "wired_modem_full" ); // Register peripheral providers ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() ); diff --git a/src/main/resources/assets/computercraft/advancements/recipes/wired_modem.json b/src/main/resources/assets/computercraft/advancements/recipes/wired_modem.json index 88d3a1ea25..8b7110e693 100644 --- a/src/main/resources/assets/computercraft/advancements/recipes/wired_modem.json +++ b/src/main/resources/assets/computercraft/advancements/recipes/wired_modem.json @@ -1,7 +1,11 @@ { "parent": "minecraft:recipes/root", "rewards": { - "recipes": [ "computercraft:wired_modem" ] + "recipes": [ + "computercraft:wired_modem", + "computercraft:wired_modem_full_to", + "computercraft:wired_modem_full_from" + ] }, "criteria": { "has_normal": { @@ -22,6 +26,12 @@ "items": [ { "item": "computercraft:cable", "data": 0 } ] } }, + "has_modem_full": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ { "item": "computercraft:wired_modem_full", "data": 0 } ] + } + }, "has_the_recipe": { "trigger": "minecraft:recipe_unlocked", "conditions": { "recipe": "computercraft:wired_modem" } @@ -32,6 +42,7 @@ "has_normal", "has_advanced", "has_cable", + "has_modem_full", "has_the_recipe" ] ] diff --git a/src/main/resources/assets/computercraft/blockstates/wired_modem_full.json b/src/main/resources/assets/computercraft/blockstates/wired_modem_full.json new file mode 100644 index 0000000000..d2f6fb31ce --- /dev/null +++ b/src/main/resources/assets/computercraft/blockstates/wired_modem_full.json @@ -0,0 +1,9 @@ +{ + "variants": { + "modem=false,peripheral=false": { "model": "computercraft:wired_modem_full_off" }, + "modem=false,peripheral=true": { "model": "computercraft:wired_modem_full_off_peripheral" }, + "modem=true,peripheral=false": { "model": "computercraft:wired_modem_full_on" }, + "modem=true,peripheral=true": { "model": "computercraft:wired_modem_full_on_peripheral" } + } +} + diff --git a/src/main/resources/assets/computercraft/models/block/wired_modem_full_off.json b/src/main/resources/assets/computercraft/models/block/wired_modem_full_off.json new file mode 100644 index 0000000000..e6ea77dfa4 --- /dev/null +++ b/src/main/resources/assets/computercraft/models/block/wired_modem_full_off.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "computercraft:blocks/wired_modem_face" + } +} diff --git a/src/main/resources/assets/computercraft/models/block/wired_modem_full_off_peripheral.json b/src/main/resources/assets/computercraft/models/block/wired_modem_full_off_peripheral.json new file mode 100644 index 0000000000..318233585b --- /dev/null +++ b/src/main/resources/assets/computercraft/models/block/wired_modem_full_off_peripheral.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "computercraft:blocks/wired_modem_face_peripheral" + } +} diff --git a/src/main/resources/assets/computercraft/models/block/wired_modem_full_on.json b/src/main/resources/assets/computercraft/models/block/wired_modem_full_on.json new file mode 100644 index 0000000000..241e39ca04 --- /dev/null +++ b/src/main/resources/assets/computercraft/models/block/wired_modem_full_on.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "computercraft:blocks/wired_modem_face_on" + } +} diff --git a/src/main/resources/assets/computercraft/models/block/wired_modem_full_on_peripheral.json b/src/main/resources/assets/computercraft/models/block/wired_modem_full_on_peripheral.json new file mode 100644 index 0000000000..c8ef3c9d47 --- /dev/null +++ b/src/main/resources/assets/computercraft/models/block/wired_modem_full_on_peripheral.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "computercraft:blocks/wired_modem_face_peripheral_on" + } +} diff --git a/src/main/resources/assets/computercraft/models/item/wired_modem_full.json b/src/main/resources/assets/computercraft/models/item/wired_modem_full.json new file mode 100644 index 0000000000..77237e07a4 --- /dev/null +++ b/src/main/resources/assets/computercraft/models/item/wired_modem_full.json @@ -0,0 +1,3 @@ +{ + "parent": "computercraft:block/wired_modem_full_off" +} diff --git a/src/main/resources/assets/computercraft/recipes/wired_modem_full_from.json b/src/main/resources/assets/computercraft/recipes/wired_modem_full_from.json new file mode 100644 index 0000000000..9e1a326fa9 --- /dev/null +++ b/src/main/resources/assets/computercraft/recipes/wired_modem_full_from.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "computercraft:wired_modem_full", "data": 0 } + ], + "result": { "item": "computercraft:cable", "data": 1 } +} diff --git a/src/main/resources/assets/computercraft/recipes/wired_modem_full_to.json b/src/main/resources/assets/computercraft/recipes/wired_modem_full_to.json new file mode 100644 index 0000000000..3537e326d0 --- /dev/null +++ b/src/main/resources/assets/computercraft/recipes/wired_modem_full_to.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "computercraft:cable", "data": 1 } + ], + "result": { "item": "computercraft:wired_modem_full", "data": 0 } +}