diff --git a/assets/opencomputers/lua/gpu.lua b/assets/opencomputers/lua/gpu.lua index d669b2bc0..fe63b1610 100644 --- a/assets/opencomputers/lua/gpu.lua +++ b/assets/opencomputers/lua/gpu.lua @@ -2,25 +2,25 @@ driver.gpu = {} function driver.gpu.setResolution(gpu, screen, w, h) - sendToNode(gpu, "gpu.resolution=", screen, w, h) + sendToNode(gpu, "gpu.resolution=", screen, w, h) end function driver.gpu.getResolution(gpu, screen) - return sendToNode(gpu, "gpu.resolution", screen) + return sendToNode(gpu, "gpu.resolution", screen) end function driver.gpu.getResolutions(gpu, screen) - return sendToNode(gpu, "gpu.resolutions", screen) + return sendToNode(gpu, "gpu.resolutions", screen) end function driver.gpu.set(gpu, screen, col, row, value) - sendToNode(gpu, "gpu.set", screen, col, row, value) + sendToNode(gpu, "gpu.set", screen, col, row, value) end function driver.gpu.fill(gpu, screen, col, row, w, h, value) - sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1)) + sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1)) end function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty) - sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty) + sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty) end \ No newline at end of file diff --git a/assets/opencomputers/lua/init.lua b/assets/opencomputers/lua/init.lua index d3ad64e23..5af3be214 100644 --- a/assets/opencomputers/lua/init.lua +++ b/assets/opencomputers/lua/init.lua @@ -86,12 +86,15 @@ end -- Main OS loop, keeps everything else running. while true do - local signal, id = os.signal(nil, 2) + local signal, param = os.signal(nil, 2) if signal == "component_added" then - onInstall(id) + onInstall(param) elseif signal == "component_removed" then - onUninstall(id) + onUninstall(param) + elseif signal == "key_down" then + write(param) + else + write("Clock: ") + print(os.clock()) end - write("Clock: ") - print(os.clock()) end \ No newline at end of file diff --git a/assets/opencomputers/lua/keyboard.lua b/assets/opencomputers/lua/keyboard.lua new file mode 100644 index 000000000..a243c9bed --- /dev/null +++ b/assets/opencomputers/lua/keyboard.lua @@ -0,0 +1,137 @@ +--[[ API for keyboards. ]] +driver.keyboard = {} + +driver.keyboard.keys = { + ["1"] = 0x02, + ["2"] = 0x03, + ["3"] = 0x04, + ["4"] = 0x05, + ["5"] = 0x06, + ["6"] = 0x07, + ["7"] = 0x08, + ["8"] = 0x09, + ["9"] = 0x0A, + ["0"] = 0x0B, + a = 0x1E, + b = 0x30, + c = 0x2E, + d = 0x20, + e = 0x12, + f = 0x21, + g = 0x22, + h = 0x23, + i = 0x17, + j = 0x24, + k = 0x25, + l = 0x26, + m = 0x32, + n = 0x31, + o = 0x18, + p = 0x19, + q = 0x10, + r = 0x13, + s = 0x1F, + t = 0x14, + u = 0x16, + v = 0x2F, + w = 0x11, + x = 0x2D, + y = 0x15, + z = 0x2C, + + apostrophe = 0x28, + at = 0x91, + back = 0x0E, -- backspace + backslash = 0x2B, + colon = 0x92, + comma = 0x33, + enter = 0x1C, + equals = 0x0D, + grave = 0x29, -- accent grave + lbracket = 0x1A, + lcontrol = 0x1D, + lmenu = 0x38, -- left Alt + lshift = 0x2A, + minus = 0x0C, + numlock = 0x45, + pause = 0xC5, + period = 0x34, + rbracket = 0x1B, + rcontrol = 0x9D, + rmenu = 0xB8, -- right Alt + rshift = 0x36, + scroll = 0x46, -- Scroll Lock + semicolon = 0x27, + slash = 0x35, -- / on main keyboard + space = 0x39, + stop = 0x95, + tab = 0x0F, + underline = 0x93, + + -- Keypad (and numpad with numlock off) + up = 0xC8, + down = 0xD0, + left = 0xCB, + right = 0xCD, + home = 0xC7, + ["end"] = 0xCF, + pageUp = 0xC9, + pageDown = 0xD1, + insert = 0xD2, + delete = 0xD3, + + -- Function keys + f1 = 0x3B, + f2 = 0x3C, + f3 = 0x3D, + f4 = 0x3E, + f5 = 0x3F, + f6 = 0x40, + f7 = 0x41, + f8 = 0x42, + f9 = 0x43, + f10 = 0x44, + f11 = 0x57, + f12 = 0x58, + f13 = 0x64, + f14 = 0x65, + f15 = 0x66, + f16 = 0x67, + f17 = 0x68, + f18 = 0x69, + f19 = 0x71, + + -- Japanese keyboards + kana = 0x70, + kanji = 0x94, + convert = 0x79, + noconvert = 0x7B, + yen = 0x7D, + circumflex = 0x90, + ax = 0x96, + + -- Numpad + numpad0 = 0x52, + numpad1 = 0x4F, + numpad2 = 0x50, + numpad3 = 0x51, + numpad4 = 0x4B, + numpad5 = 0x4C, + numpad6 = 0x4D, + numpad7 = 0x47, + numpad8 = 0x48, + numpad9 = 0x49, + numpadmul = 0x37, + numpaddiv = 0xB5, + numpadsub = 0x4A, + numpadadd = 0x4E, + numpaddecimal = 0x53, + numpadcomma = 0xB3, + numpadenter = 0x9C, + numpadequals = 0x8D, +} + +-- Create inverse mapping for name lookup. +for k, v in pairs(driver.keyboard.keys) do + driver.keyboard.keys[v] = k +end \ No newline at end of file diff --git a/li/cil/oc/Blocks.scala b/li/cil/oc/Blocks.scala index b7246dd88..55bcf8db2 100644 --- a/li/cil/oc/Blocks.scala +++ b/li/cil/oc/Blocks.scala @@ -1,15 +1,13 @@ package li.cil.oc -import li.cil.oc.common.block.BlockComputer -import li.cil.oc.common.block.BlockMulti -import li.cil.oc.common.block.BlockScreen -import li.cil.oc.common.block.BlockSpecialMulti +import li.cil.oc.common.block._ object Blocks { var blockSimple: BlockMulti = null var blockSpecial: BlockMulti = null var computer: BlockComputer = null var screen: BlockScreen = null + var keyboard: BlockKeyboard = null def init() { // IMPORTANT: the multi block must come first, since the sub blocks will @@ -20,5 +18,6 @@ object Blocks { computer = new BlockComputer(blockSimple) screen = new BlockScreen(blockSimple) + keyboard = new BlockKeyboard(blockSpecial) } } \ No newline at end of file diff --git a/li/cil/oc/api/IBlockDriver.scala b/li/cil/oc/api/IBlockDriver.scala index 7423954fd..387a5138f 100644 --- a/li/cil/oc/api/IBlockDriver.scala +++ b/li/cil/oc/api/IBlockDriver.scala @@ -1,6 +1,5 @@ package li.cil.oc.api -import _root_.scala.beans.BeanProperty import net.minecraft.world.World /** @@ -10,6 +9,9 @@ import net.minecraft.world.World * placed in the world, but cannot be modified to or don't want to have their * `TileEntities` implement `INetworkNode`. *

+ * A block driver is used by proxy blocks to check its neighbors and whether + * those neighbors should be treated as components or not. + *

* Note that it is possible to write one driver that supports as many different * blocks as you wish. I'd recommend writing one per device (type), though, to * keep things modular. @@ -34,15 +36,17 @@ trait IBlockDriver extends IDriver { /** * Get a reference to the network node wrapping the specified block. *

- * This is used to provide context to the driver's methods, for example when - * an API method is called this will always be passed as the first parameter. + * This is used to connect the component to the component network when it is + * detected next to a proxy. Components that are not part of the component + * network probably don't make much sense (can't think of any uses at this + * time), but you may still opt to not implement this. * - * @param world the world in which the block to get the component for lives. - * @param x the X coordinate of the block to get the component for. - * @param y the Y coordinate of the block to get the component for. - * @param z the Z coordinate of the block to get the component for. - * @return the block component at that location, controlled by this driver. + * @param world the world in which the block to get the node for lives. + * @param x the X coordinate of the block to get the node for. + * @param y the Y coordinate of the block to get the node for. + * @param z the Z coordinate of the block to get the node for. + * @return the network node for the block at that location. */ - @BeanProperty - def node(world: World, x: Int, y: Int, z: Int): INetworkNode + def node(world: World, x: Int, y: Int, z: Int): INetworkNode = + world.getBlockTileEntity(x, y, z).asInstanceOf[INetworkNode] } \ No newline at end of file diff --git a/li/cil/oc/api/IDriver.scala b/li/cil/oc/api/IDriver.scala index ce85eae6d..da9b2ff06 100644 --- a/li/cil/oc/api/IDriver.scala +++ b/li/cil/oc/api/IDriver.scala @@ -10,36 +10,36 @@ import java.io.InputStream * Lua state when the driver is installed, and provide general information used * by the computer. *

- * Note that drivers themselves are singletons. They can define a parameter of - * type {@link IComputerContext} in their API functions which will hold the - * context in which they are called - essentially a representation of the - * computer they were called form. This context can be used to get a component - * in the computer (e.g. passed as another parameter) and to send signals to the - * computer. - *

- * Do not implement this interface directly; use the {@link IItemDriver} and - * {@link IBlockDriver} interfaces for the respective component types. + * Do not implement this interface directly; use the `IItemDriver` and + * `IBlockDriver` interfaces for the respective component types. */ trait IDriver { /** * Some initialization code that is run when the driver is installed. *

- * This is loaded - * into the Lua state and run in the global, un-sandboxed environment. This - * means your scripts can mess things up bad, so make sure you know what - * you're doing and exposing. + * These will usually be some functions that generate network messages of + * the particular signature the node of the driver handles, but may contain + * arbitrary other functions. However, whatever you do, keep in mind that + * only certain parts of the global namespace will be made available to the + * computer at runtime, so it's best to keep all you declare in the driver + * table (global variable `driver`). *

- * This can be null to do nothing. Otherwise this is expected to be valid Lua - * code (it is simply loaded via load() and then executed). + * This is loaded into the Lua state and run in the global, un-sandboxed + * environment. This means your scripts can mess things up bad, so make sure + * you know what you're doing and exposing. + *

+ * This can be `None` to do nothing. Otherwise this is expected to be valid + * Lua code (it is simply loaded via load() and then executed). *

* The stream has to be recreated each time this is called. Normally you will * return something along the lines of - * Mod.class.getResourceAsStream("/assets/mod/lua/ocapi.lua") + * `Mod.class.getResourceAsStream("/assets/yourmod/lua/ocapi.lua")` * from this method. If you wish to hard-code the returned script, you can use - * new ByteArrayInputStream(yourScript.getBytes()) instead. Note - * that the stream will automatically closed. + * `new ByteArrayInputStream(yourScript.getBytes())` instead. + *

+ * IMPORTANT: Note that the stream will automatically closed. * - * @return the Lua code to run after installing the API table. + * @return the Lua code to run when a computer is started up. */ def api: Option[InputStream] = None } \ No newline at end of file diff --git a/li/cil/oc/api/IItemDriver.scala b/li/cil/oc/api/IItemDriver.scala index 2565fb007..d2a09d6bc 100644 --- a/li/cil/oc/api/IItemDriver.scala +++ b/li/cil/oc/api/IItemDriver.scala @@ -13,7 +13,7 @@ import net.minecraft.item.ItemStack * queried using the drivers' `worksWith` functions. The first driver that * replies positively and whose check against the slot type is successful, i.e. * for which the `componentType` matches the slot, will be used as the - * component's driver and the component will be installed. If no driver is found + * component's driver and the component will be added. If no driver is found * the item will be rejected and cannot be installed. *

* Note that it is possible to write one driver that supports as many different @@ -47,6 +47,11 @@ trait IItemDriver extends IDriver { /** * Gets a reference to the network node interfacing the specified item. + *

+ * This is used to connect the component to the component network when it is + * added to a computer, for example. Components that are not part of the + * component network probably don't make much sense (can't think of any uses + * at this time), but you may still opt to not implement this. * * @param item the item instance for which to get the node. * @return the network node for that item. diff --git a/li/cil/oc/client/PacketSender.scala b/li/cil/oc/client/PacketSender.scala index 546af586a..d85352160 100644 --- a/li/cil/oc/client/PacketSender.scala +++ b/li/cil/oc/client/PacketSender.scala @@ -27,20 +27,22 @@ object PacketSender { pb.sendToServer() } - def sendKeyDown[T <: TileEntity with INetworkNode](t: T, c: Char) = { + def sendKeyDown[T <: TileEntity with INetworkNode](t: T, char: Char, code: Int) = { val pb = new PacketBuilder(PacketType.KeyDown) pb.writeTileEntity(t) - pb.writeChar(c) + pb.writeChar(char) + pb.writeInt(code) pb.sendToServer() } - def sendKeyUp[T <: TileEntity with INetworkNode](t: T, c: Char) = { + def sendKeyUp[T <: TileEntity with INetworkNode](t: T, char: Char, code: Int) = { val pb = new PacketBuilder(PacketType.KeyUp) pb.writeTileEntity(t) - pb.writeChar(c) + pb.writeChar(char) + pb.writeInt(code) pb.sendToServer() } diff --git a/li/cil/oc/client/gui/GuiScreen.scala b/li/cil/oc/client/gui/GuiScreen.scala index a719fbb27..2a7c7d936 100644 --- a/li/cil/oc/client/gui/GuiScreen.scala +++ b/li/cil/oc/client/gui/GuiScreen.scala @@ -1,13 +1,11 @@ package li.cil.oc.client.gui import li.cil.oc.client.PacketSender -import li.cil.oc.common.tileentity.TileEntityKeyboard import li.cil.oc.common.tileentity.TileEntityScreen import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.texture.TextureManager import net.minecraft.util.ResourceLocation -import net.minecraftforge.common.ForgeDirection import org.lwjgl.input.Keyboard import org.lwjgl.opengl.GL11 @@ -49,16 +47,15 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g /** Must be called whenever the buffer of the underlying screen changes. */ def updateText() = GuiScreen.compileText(scale, tileEntity.screen.lines) - override def handleKeyboardInput() = { - // Find all keyboards next to this screen and type on them. - for (k <- neighboringKeyboards) { + override def keyTyped(char: Char, code: Int) = { + super.keyTyped(char, code) + if (code != Keyboard.KEY_ESCAPE && code != Keyboard.KEY_F11) if (Keyboard.getEventKeyState) { - PacketSender.sendKeyDown(k, Keyboard.getEventCharacter) + PacketSender.sendKeyDown(tileEntity, char, code) } else { - PacketSender.sendKeyUp(k, Keyboard.getEventCharacter) + PacketSender.sendKeyUp(tileEntity, char, code) } - } } override def initGui() = { @@ -85,16 +82,6 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g } override def doesGuiPauseGame = false - - private def neighboringKeyboards = - ForgeDirection.VALID_DIRECTIONS. - map(d => tileEntity.worldObj.getBlockTileEntity( - tileEntity.xCoord + d.offsetX, - tileEntity.yCoord + d.offsetY, - tileEntity.zCoord + d.offsetZ)). - filter(_ != null). - filter(_.isInstanceOf[TileEntityKeyboard]). - map(_.asInstanceOf[TileEntityKeyboard]) } /** We cache OpenGL stuff in a singleton to avoid having to re-allocate. */ diff --git a/li/cil/oc/common/Proxy.scala b/li/cil/oc/common/Proxy.scala index c2f45517c..2401e5e13 100644 --- a/li/cil/oc/common/Proxy.scala +++ b/li/cil/oc/common/Proxy.scala @@ -30,6 +30,7 @@ class Proxy { NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler) OpenComputersAPI.addDriver(GraphicsCardDriver) + OpenComputersAPI.addDriver(KeyboardDriver) MinecraftForge.EVENT_BUS.register(ForgeEventHandler) } diff --git a/li/cil/oc/common/tileentity/TileEntityComputer.scala b/li/cil/oc/common/tileentity/TileEntityComputer.scala index 0bda0fa1e..c16dbfa60 100644 --- a/li/cil/oc/common/tileentity/TileEntityComputer.scala +++ b/li/cil/oc/common/tileentity/TileEntityComputer.scala @@ -19,14 +19,16 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo private val hasChanged = new AtomicBoolean(true) - private var isRunning = computer.isRunning + private var isRunning = false // ----------------------------------------------------------------------- // // NetworkNode // ----------------------------------------------------------------------- // override def receive(message: INetworkMessage) = message.data match { - case Array() if message.name == "network.connect" => + // The isRunning check is here to avoid network.connect messages being sent + // while loading a chunk (thus leading to "false" component_added signals). + case Array() if message.name == "network.connect" && isRunning => computer.signal("component_added", message.source.address); None case Array() if message.name == "network.disconnect" => computer.signal("component_removed", message.source.address); None diff --git a/li/cil/oc/common/tileentity/TileEntityKeyboard.scala b/li/cil/oc/common/tileentity/TileEntityKeyboard.scala index e41fbfde2..9ded7fdbe 100644 --- a/li/cil/oc/common/tileentity/TileEntityKeyboard.scala +++ b/li/cil/oc/common/tileentity/TileEntityKeyboard.scala @@ -7,16 +7,16 @@ class TileEntityKeyboard extends TileEntityRotatable with INetworkNode { override def name = "keyboard" override def receive(message: INetworkMessage) = message.data match { - case Array(name: String, p: Player, c: Character) if name == "keyboard.keyDown" => { + case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyDown" => { // TODO check if player is close enough and only consume message if so - network.sendToAll(this, "signal", "key_down", c) - message.cancel() + network.sendToAll(this, "signal", "key_down", char.toString, code) + message.cancel() // One keyboard is enough. None } - case Array(name: String, p: Player, c: Character) if name == "keyboard.keyUp" => { + case Array(p: Player, char: Char, code: Int) if message.name == "keyboard.keyUp" => { // TODO check if player is close enough and only consume message if so - network.sendToAll(this, "signal", "key_up", c) - message.cancel() + network.sendToAll(this, "signal", "key_up", char.toString, code) + message.cancel() // One keyboard is enough. None } case _ => None diff --git a/li/cil/oc/server/PacketHandler.scala b/li/cil/oc/server/PacketHandler.scala index 5c4d60e8a..f0851bfb3 100644 --- a/li/cil/oc/server/PacketHandler.scala +++ b/li/cil/oc/server/PacketHandler.scala @@ -67,12 +67,12 @@ class PacketHandler extends CommonPacketHandler { def onKeyDown(p: PacketParser) = p.readTileEntity[INetworkNode]() match { case None => // Invalid packet. - case Some(n) => n.network.sendToAll(n, "keyboard.keyDown", p.player, p.readChar()) + case Some(n) => n.network.sendToAll(n, "keyboard.keyDown", p.player, p.readChar(), p.readInt()) } def onKeyUp(p: PacketParser) = p.readTileEntity[INetworkNode]() match { case None => // Invalid packet. - case Some(n) => n.network.sendToAll(n, "keyboard.keyUp", p.player, p.readChar()) + case Some(n) => n.network.sendToAll(n, "keyboard.keyUp", p.player, p.readChar(), p.readInt()) } } \ No newline at end of file diff --git a/li/cil/oc/server/computer/Computer.scala b/li/cil/oc/server/computer/Computer.scala index 926b3c089..e0d8fae54 100644 --- a/li/cil/oc/server/computer/Computer.scala +++ b/li/cil/oc/server/computer/Computer.scala @@ -660,12 +660,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable // resuming the state again. val sleep = (lua.toNumber(2) * 1000).toLong lua.pop(results) - state = State.Sleeping - future = Some(Executor.pool. - schedule(this, sleep, TimeUnit.MILLISECONDS)) + if (signals.isEmpty) { + state = State.Sleeping + future = Some(Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS)) + } else future = Some(Executor.pool.submit(this)) } else if (results == 1 && lua.isFunction(2)) { - // If we get one function it's a wrapper for a driver call. + // If we get one function it's a wrapper for a synchronized call. state = State.SynchronizedCall future = None } @@ -673,7 +674,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable // Something else, just pop the results and try again. lua.pop(results) state = State.Suspended - future = Some(Executor.pool.submit(this)) + if (signals.isEmpty) future = None + else future = Some(Executor.pool.submit(this)) } } diff --git a/li/cil/oc/server/computer/Network.scala b/li/cil/oc/server/computer/Network.scala index 68227aa50..54d9f9f41 100644 --- a/li/cil/oc/server/computer/Network.scala +++ b/li/cil/oc/server/computer/Network.scala @@ -1,5 +1,6 @@ package li.cil.oc.server.computer +import li.cil.oc.OpenComputers import li.cil.oc.api.INetwork import li.cil.oc.api.INetworkMessage import li.cil.oc.api.INetworkNode @@ -35,10 +36,9 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No nodes.values.flatten.foreach(_.data.network = this) - def connect(nodeA: INetworkNode, nodeB: INetworkNode) = try { + def connect(nodeA: INetworkNode, nodeB: INetworkNode) = { if (locked) throw new IllegalStateException( "Cannot modify network while it is already updating its structure.") - locked = true val containsA = nodes.get(nodeA.address).exists(_.exists(_.data == nodeA)) val containsB = nodes.get(nodeB.address).exists(_.exists(_.data == nodeB)) @@ -66,9 +66,6 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No else if (containsA) add(nodes(nodeA.address).find(_.data == nodeA).get, nodeB) else add(nodes(nodeB.address).find(_.data == nodeB).get, nodeA) } - finally { - locked = false - } def remove(node: INetworkNode) = nodes.get(node.address) match { case None => false @@ -103,9 +100,14 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No private def send(message: Network.Message, nodes: Iterator[INetworkNode]) = { var result = None: Option[Array[Any]] while (!message.isCanceled && nodes.hasNext) { - nodes.next().receive(message) match { - case None => // Ignore. - case r => result = r + try { + nodes.next().receive(message) match { + case None => // Ignore. + case r => result = r + } + } catch { + case e: Throwable => + OpenComputers.log.warning("Error in message handler:\n" + e.getStackTraceString) } } result @@ -124,7 +126,6 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No newNode } else { - val otherNetwork = node.network.asInstanceOf[Network] // We have to merge. First create a copy of the old nodes to have the // list of nodes to which to send "network.connect" messages. val oldNodes = nodes.values.flatten.map(_.data).toArray @@ -132,7 +133,12 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No // iteration to merge into this network, used to send "network.reconnect" // messages to old nodes in case we have to change a node's address to // ensure unique addresses in the merged network. + val otherNetwork = node.network.asInstanceOf[Network] val otherNodes = otherNetwork.nodes.values.flatten.map(_.data) + // Lock this network to avoid message handlers adding nodes, which could + // lead to addresses getting taken that we will need for the nodes we are + // about to merge into this network. + locked = true // Pre-merge step: ensure addresses are unique. for (node <- otherNodes if nodes.contains(node.address)) { val oldAddress = node.address @@ -148,6 +154,8 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No node.data.network = this send(new Network.Message(node.data, "network.connect"), oldNodes.iterator) } + // Done, unlock again. + locked = false // Return the node object of the newly connected node for the next step. nodes(node.address).find(_.data == node).get } diff --git a/li/cil/oc/server/drivers/KeyboardDriver.scala b/li/cil/oc/server/drivers/KeyboardDriver.scala new file mode 100644 index 000000000..4f0e87943 --- /dev/null +++ b/li/cil/oc/server/drivers/KeyboardDriver.scala @@ -0,0 +1,12 @@ +package li.cil.oc.server.drivers + +import li.cil.oc.Blocks +import li.cil.oc.api.IBlockDriver +import net.minecraft.world.World + +object KeyboardDriver extends IBlockDriver { + override def api = Option(getClass.getResourceAsStream("/assets/opencomputers/lua/keyboard.lua")) + + override def worksWith(world: World, x: Int, y: Int, z: Int) = + world.getBlockId(x, y, z) == Blocks.keyboard.blockId +}