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 viaload()
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
+}