rudimentary keyboard input is working

This commit is contained in:
Florian Nücke 2013-09-23 18:13:24 +02:00
parent 48cf012d67
commit 29a79b5ed0
16 changed files with 253 additions and 91 deletions

View File

@ -86,12 +86,15 @@ end
-- Main OS loop, keeps everything else running. -- Main OS loop, keeps everything else running.
while true do while true do
local signal, id = os.signal(nil, 2) local signal, param = os.signal(nil, 2)
if signal == "component_added" then if signal == "component_added" then
onInstall(id) onInstall(param)
elseif signal == "component_removed" then elseif signal == "component_removed" then
onUninstall(id) onUninstall(param)
end elseif signal == "key_down" then
write(param)
else
write("Clock: ") write("Clock: ")
print(os.clock()) print(os.clock())
end
end end

View File

@ -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

View File

@ -1,15 +1,13 @@
package li.cil.oc package li.cil.oc
import li.cil.oc.common.block.BlockComputer import li.cil.oc.common.block._
import li.cil.oc.common.block.BlockMulti
import li.cil.oc.common.block.BlockScreen
import li.cil.oc.common.block.BlockSpecialMulti
object Blocks { object Blocks {
var blockSimple: BlockMulti = null var blockSimple: BlockMulti = null
var blockSpecial: BlockMulti = null var blockSpecial: BlockMulti = null
var computer: BlockComputer = null var computer: BlockComputer = null
var screen: BlockScreen = null var screen: BlockScreen = null
var keyboard: BlockKeyboard = null
def init() { def init() {
// IMPORTANT: the multi block must come first, since the sub blocks will // IMPORTANT: the multi block must come first, since the sub blocks will
@ -20,5 +18,6 @@ object Blocks {
computer = new BlockComputer(blockSimple) computer = new BlockComputer(blockSimple)
screen = new BlockScreen(blockSimple) screen = new BlockScreen(blockSimple)
keyboard = new BlockKeyboard(blockSpecial)
} }
} }

View File

@ -1,6 +1,5 @@
package li.cil.oc.api package li.cil.oc.api
import _root_.scala.beans.BeanProperty
import net.minecraft.world.World 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 * placed in the world, but cannot be modified to or don't want to have their
* `TileEntities` implement `INetworkNode`. * `TileEntities` implement `INetworkNode`.
* <p/> * <p/>
* A block driver is used by proxy blocks to check its neighbors and whether
* those neighbors should be treated as components or not.
* <p/>
* Note that it is possible to write one driver that supports as many different * 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 * blocks as you wish. I'd recommend writing one per device (type), though, to
* keep things modular. * keep things modular.
@ -34,15 +36,17 @@ trait IBlockDriver extends IDriver {
/** /**
* Get a reference to the network node wrapping the specified block. * Get a reference to the network node wrapping the specified block.
* <p/> * <p/>
* This is used to provide context to the driver's methods, for example when * This is used to connect the component to the component network when it is
* an API method is called this will always be passed as the first parameter. * 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 world the world in which the block to get the node for lives.
* @param x the X coordinate of the block to get the component for. * @param x the X coordinate of the block to get the node for.
* @param y the Y coordinate of the block to get the component 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 component for. * @param z the Z coordinate of the block to get the node for.
* @return the block component at that location, controlled by this driver. * @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]
} }

View File

@ -10,36 +10,36 @@ import java.io.InputStream
* Lua state when the driver is installed, and provide general information used * Lua state when the driver is installed, and provide general information used
* by the computer. * by the computer.
* <p/> * <p/>
* Note that drivers themselves are singletons. They can define a parameter of * Do not implement this interface directly; use the `IItemDriver` and
* type {@link IComputerContext} in their API functions which will hold the * `IBlockDriver` interfaces for the respective component types.
* 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.
* <p/>
* Do not implement this interface directly; use the {@link IItemDriver} and
* {@link IBlockDriver} interfaces for the respective component types.
*/ */
trait IDriver { trait IDriver {
/** /**
* Some initialization code that is run when the driver is installed. * Some initialization code that is run when the driver is installed.
* <p/> * <p/>
* This is loaded * These will usually be some functions that generate network messages of
* into the Lua state and run in the global, un-sandboxed environment. This * the particular signature the node of the driver handles, but may contain
* means your scripts can mess things up bad, so make sure you know what * arbitrary other functions. However, whatever you do, keep in mind that
* you're doing and exposing. * 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`).
* <p/> * <p/>
* This can be null to do nothing. Otherwise this is expected to be valid Lua * This is loaded into the Lua state and run in the global, un-sandboxed
* code (it is simply loaded via <code>load()</code> and then executed). * environment. This means your scripts can mess things up bad, so make sure
* you know what you're doing and exposing.
* <p/>
* This can be `None` to do nothing. Otherwise this is expected to be valid
* Lua code (it is simply loaded via <code>load()</code> and then executed).
* <p/> * <p/>
* The stream has to be recreated each time this is called. Normally you will * The stream has to be recreated each time this is called. Normally you will
* return something along the lines of * return something along the lines of
* <code>Mod.class.getResourceAsStream("/assets/mod/lua/ocapi.lua")</code> * `Mod.class.getResourceAsStream("/assets/yourmod/lua/ocapi.lua")`
* from this method. If you wish to hard-code the returned script, you can use * from this method. If you wish to hard-code the returned script, you can use
* <code>new ByteArrayInputStream(yourScript.getBytes())</code> instead. Note * `new ByteArrayInputStream(yourScript.getBytes())` instead.
* that the stream will automatically closed. * <p/>
* 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 def api: Option[InputStream] = None
} }

View File

@ -13,7 +13,7 @@ import net.minecraft.item.ItemStack
* queried using the drivers' `worksWith` functions. The first driver that * queried using the drivers' `worksWith` functions. The first driver that
* replies positively and whose check against the slot type is successful, i.e. * 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 * 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. * the item will be rejected and cannot be installed.
* <p/> * <p/>
* Note that it is possible to write one driver that supports as many different * 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. * Gets a reference to the network node interfacing the specified item.
* <p/>
* 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. * @param item the item instance for which to get the node.
* @return the network node for that item. * @return the network node for that item.

View File

@ -27,20 +27,22 @@ object PacketSender {
pb.sendToServer() 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) val pb = new PacketBuilder(PacketType.KeyDown)
pb.writeTileEntity(t) pb.writeTileEntity(t)
pb.writeChar(c) pb.writeChar(char)
pb.writeInt(code)
pb.sendToServer() 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) val pb = new PacketBuilder(PacketType.KeyUp)
pb.writeTileEntity(t) pb.writeTileEntity(t)
pb.writeChar(c) pb.writeChar(char)
pb.writeInt(code)
pb.sendToServer() pb.sendToServer()
} }

View File

@ -1,13 +1,11 @@
package li.cil.oc.client.gui package li.cil.oc.client.gui
import li.cil.oc.client.PacketSender import li.cil.oc.client.PacketSender
import li.cil.oc.common.tileentity.TileEntityKeyboard
import li.cil.oc.common.tileentity.TileEntityScreen import li.cil.oc.common.tileentity.TileEntityScreen
import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.GLAllocation
import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.Tessellator
import net.minecraft.client.renderer.texture.TextureManager import net.minecraft.client.renderer.texture.TextureManager
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.ForgeDirection
import org.lwjgl.input.Keyboard import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
@ -49,15 +47,14 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g
/** Must be called whenever the buffer of the underlying screen changes. */ /** Must be called whenever the buffer of the underlying screen changes. */
def updateText() = GuiScreen.compileText(scale, tileEntity.screen.lines) def updateText() = GuiScreen.compileText(scale, tileEntity.screen.lines)
override def handleKeyboardInput() = { override def keyTyped(char: Char, code: Int) = {
// Find all keyboards next to this screen and type on them. super.keyTyped(char, code)
for (k <- neighboringKeyboards) { if (code != Keyboard.KEY_ESCAPE && code != Keyboard.KEY_F11)
if (Keyboard.getEventKeyState) { if (Keyboard.getEventKeyState) {
PacketSender.sendKeyDown(k, Keyboard.getEventCharacter) PacketSender.sendKeyDown(tileEntity, char, code)
} }
else { else {
PacketSender.sendKeyUp(k, Keyboard.getEventCharacter) PacketSender.sendKeyUp(tileEntity, char, code)
}
} }
} }
@ -85,16 +82,6 @@ class GuiScreen(val tileEntity: TileEntityScreen) extends net.minecraft.client.g
} }
override def doesGuiPauseGame = false 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. */ /** We cache OpenGL stuff in a singleton to avoid having to re-allocate. */

View File

@ -30,6 +30,7 @@ class Proxy {
NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler) NetworkRegistry.instance.registerGuiHandler(OpenComputers, GuiHandler)
OpenComputersAPI.addDriver(GraphicsCardDriver) OpenComputersAPI.addDriver(GraphicsCardDriver)
OpenComputersAPI.addDriver(KeyboardDriver)
MinecraftForge.EVENT_BUS.register(ForgeEventHandler) MinecraftForge.EVENT_BUS.register(ForgeEventHandler)
} }

View File

@ -19,14 +19,16 @@ class TileEntityComputer(isClient: Boolean) extends TileEntityRotatable with ICo
private val hasChanged = new AtomicBoolean(true) private val hasChanged = new AtomicBoolean(true)
private var isRunning = computer.isRunning private var isRunning = false
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// NetworkNode // NetworkNode
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def receive(message: INetworkMessage) = message.data match { 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 computer.signal("component_added", message.source.address); None
case Array() if message.name == "network.disconnect" => case Array() if message.name == "network.disconnect" =>
computer.signal("component_removed", message.source.address); None computer.signal("component_removed", message.source.address); None

View File

@ -7,16 +7,16 @@ class TileEntityKeyboard extends TileEntityRotatable with INetworkNode {
override def name = "keyboard" override def name = "keyboard"
override def receive(message: INetworkMessage) = message.data match { 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 // TODO check if player is close enough and only consume message if so
network.sendToAll(this, "signal", "key_down", c) network.sendToAll(this, "signal", "key_down", char.toString, code)
message.cancel() message.cancel() // One keyboard is enough.
None 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 // TODO check if player is close enough and only consume message if so
network.sendToAll(this, "signal", "key_up", c) network.sendToAll(this, "signal", "key_up", char.toString, code)
message.cancel() message.cancel() // One keyboard is enough.
None None
} }
case _ => None case _ => None

View File

@ -67,12 +67,12 @@ class PacketHandler extends CommonPacketHandler {
def onKeyDown(p: PacketParser) = def onKeyDown(p: PacketParser) =
p.readTileEntity[INetworkNode]() match { p.readTileEntity[INetworkNode]() match {
case None => // Invalid packet. 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) = def onKeyUp(p: PacketParser) =
p.readTileEntity[INetworkNode]() match { p.readTileEntity[INetworkNode]() match {
case None => // Invalid packet. 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())
} }
} }

View File

@ -660,12 +660,13 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// resuming the state again. // resuming the state again.
val sleep = (lua.toNumber(2) * 1000).toLong val sleep = (lua.toNumber(2) * 1000).toLong
lua.pop(results) lua.pop(results)
if (signals.isEmpty) {
state = State.Sleeping state = State.Sleeping
future = Some(Executor.pool. future = Some(Executor.pool.schedule(this, sleep, TimeUnit.MILLISECONDS))
schedule(this, sleep, TimeUnit.MILLISECONDS)) } else future = Some(Executor.pool.submit(this))
} }
else if (results == 1 && lua.isFunction(2)) { 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 state = State.SynchronizedCall
future = None future = None
} }
@ -673,7 +674,8 @@ class Computer(val owner: IComputerEnvironment) extends IComputer with Runnable
// Something else, just pop the results and try again. // Something else, just pop the results and try again.
lua.pop(results) lua.pop(results)
state = State.Suspended state = State.Suspended
future = Some(Executor.pool.submit(this)) if (signals.isEmpty) future = None
else future = Some(Executor.pool.submit(this))
} }
} }

View File

@ -1,5 +1,6 @@
package li.cil.oc.server.computer package li.cil.oc.server.computer
import li.cil.oc.OpenComputers
import li.cil.oc.api.INetwork import li.cil.oc.api.INetwork
import li.cil.oc.api.INetworkMessage import li.cil.oc.api.INetworkMessage
import li.cil.oc.api.INetworkNode 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) 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( if (locked) throw new IllegalStateException(
"Cannot modify network while it is already updating its structure.") "Cannot modify network while it is already updating its structure.")
locked = true
val containsA = nodes.get(nodeA.address).exists(_.exists(_.data == nodeA)) val containsA = nodes.get(nodeA.address).exists(_.exists(_.data == nodeA))
val containsB = nodes.get(nodeB.address).exists(_.exists(_.data == nodeB)) 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 if (containsA) add(nodes(nodeA.address).find(_.data == nodeA).get, nodeB)
else add(nodes(nodeB.address).find(_.data == nodeB).get, nodeA) else add(nodes(nodeB.address).find(_.data == nodeB).get, nodeA)
} }
finally {
locked = false
}
def remove(node: INetworkNode) = nodes.get(node.address) match { def remove(node: INetworkNode) = nodes.get(node.address) match {
case None => false case None => false
@ -103,10 +100,15 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
private def send(message: Network.Message, nodes: Iterator[INetworkNode]) = { private def send(message: Network.Message, nodes: Iterator[INetworkNode]) = {
var result = None: Option[Array[Any]] var result = None: Option[Array[Any]]
while (!message.isCanceled && nodes.hasNext) { while (!message.isCanceled && nodes.hasNext) {
try {
nodes.next().receive(message) match { nodes.next().receive(message) match {
case None => // Ignore. case None => // Ignore.
case r => result = r case r => result = r
} }
} catch {
case e: Throwable =>
OpenComputers.log.warning("Error in message handler:\n" + e.getStackTraceString)
}
} }
result result
} }
@ -124,7 +126,6 @@ class Network private(private val nodes: mutable.Map[Int, ArrayBuffer[Network.No
newNode newNode
} }
else { else {
val otherNetwork = node.network.asInstanceOf[Network]
// We have to merge. First create a copy of the old nodes to have the // 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. // list of nodes to which to send "network.connect" messages.
val oldNodes = nodes.values.flatten.map(_.data).toArray 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" // 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 // messages to old nodes in case we have to change a node's address to
// ensure unique addresses in the merged network. // ensure unique addresses in the merged network.
val otherNetwork = node.network.asInstanceOf[Network]
val otherNodes = otherNetwork.nodes.values.flatten.map(_.data) 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. // Pre-merge step: ensure addresses are unique.
for (node <- otherNodes if nodes.contains(node.address)) { for (node <- otherNodes if nodes.contains(node.address)) {
val oldAddress = 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 node.data.network = this
send(new Network.Message(node.data, "network.connect"), oldNodes.iterator) 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. // Return the node object of the newly connected node for the next step.
nodes(node.address).find(_.data == node).get nodes(node.address).find(_.data == node).get
} }

View File

@ -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
}