some groundwork for networking (e.g. for updating monitors on client when graphics card on server does something)

This commit is contained in:
Florian Nücke 2013-09-10 19:26:41 +02:00
parent f9cd7fd3b6
commit a059183133
26 changed files with 781 additions and 24 deletions

View File

@ -1,11 +1,14 @@
package li.cil.oc
import li.cil.oc.common.block.BlockComputer
import li.cil.oc.common.block.BlockScreen
object Blocks {
var computer: BlockComputer = null
var screen: BlockScreen = null
def init() {
computer = new BlockComputer()
computer = new BlockComputer
screen = new BlockScreen
}
}

View File

@ -2,7 +2,7 @@ package li.cil.oc
object Config {
var blockComputerId = 3650
var blockMonitorId = 3651
var blockScreenId = 3651
var itemHDDId = 4600
var itemGPUId = 4601

View File

@ -39,6 +39,10 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Callback {
/** The name under which the method will be available in Lua. */
String name();
/**
* The name under which the method will be available in Lua.
*
* If this is not specified, the method's name itself will be used.
*/
String name() default "";
}

View File

@ -1,6 +1,7 @@
package li.cil.oc.api
import net.minecraft.block.Block
import net.minecraft.world.World
/**
* Interface for block component drivers.
@ -36,7 +37,7 @@ trait IBlockDriver extends IDriver {
*
* param block the block type to check for.
*/
def worksWith(block: Block): Boolean
def worksWith(world: World, block: Block): Boolean
/**
* Get a reference to the actual component.
@ -50,5 +51,5 @@ trait IBlockDriver extends IDriver {
* @param z the Z coordinate of the block to get the component for.
* @return the block component at that location, controlled by this driver.
*/
def component(x: Int, y: Int, z: Int): Object
def component(world: World, x: Int, y: Int, z: Int): Any
}

View File

@ -0,0 +1,50 @@
package li.cil.oc.api
import scala.reflect.runtime.universe._
import net.minecraft.world.World
/**
* This interface is used to give drivers a controlled way of interacting with
* a computer. It can be passed to driver API functions if they declare a
* parameter of this type and is passed in the install and uninstall functions.
*/
trait IComputerContext {
/** The world the computer lives in. */
def world: World
/**
* Send a signal to the computer.
*
* Signals are like top level events. Signals are queued up and sequentially
* processed by the computer. The queue has a maximum length; if reached,
* this will return false. Signals only support simple types such as booleans,
* numbers and strings. This is because unprocessed signals have to be saved
* to NBT format when the game is saved.
*
* Lua programs can register a function as a callback for each signal type,
* which is the first parameter - the signal name. For example, two built-in
* signals are "component_install" and "component_uninstall".
*
* @param name the name of the signal.
* @param args any parameters to pass along with the signal.
*/
def signal(name: String, args: Any*): Boolean
/**
* Gets a component with the specified ID from the computer.
*
* The Lua state refers to components only by their ID. They may pass this ID
* along to a driver API function, so that it in turn may resolve it to the
* actual component (originally retrieved by the computer via
* {@see IItemDriver#getComponent(ItemStack)} or
* {@see IBlockDriver#getComponent(Int, Int, Int)}).
*
* This will try to convert the component to the specified type and throw an
* exception if the type does not match. It also throws an exception if there
* is no such component.
*
* @param id the id of the component to get.
*/
def component[T: TypeTag](id: Int): T
}

View File

@ -0,0 +1,18 @@
package li.cil.oc.api
/**
* Unlike all other component drivers, drivers for memory (RAM) need to
* implement an additional interface, since we want to keep control over
* memory under tight control. Like this, RAM components don't directly set
* the available memory, but instead we check all of them and decide how much
* memory to really make available to the computer (makes an upper limit
* realizable even if mods add custom RAM modules).
*/
trait IMemory extends IDriver {
/**
* The amount of memory this component provides. Note that this number may,
* in fact, be negative, so you could make components that reserve some
* portion of the memory, for example.
*/
def amount: Int
}

View File

@ -0,0 +1,79 @@
package li.cil.oc.client
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import cpw.mods.fml.common.network.IPacketHandler
import cpw.mods.fml.common.network.Player
import li.cil.oc.OpenComputers
import li.cil.oc.client.components.Screen
import li.cil.oc.common.PacketType
import net.minecraft.network.INetworkManager
import net.minecraft.network.packet.Packet250CustomPayload
/**
* Client side packet handler, processes packets sent from the server.
*
* @see li.cil.oc.server.PacketSender
*/
class PacketHandler extends IPacketHandler {
/** Top level dispatcher based on packet type. */
def onPacketData(manager: INetworkManager, packet: Packet250CustomPayload, player: Player) {
val p = new PacketParser(packet, player)
p.packetType match {
case PacketType.ScreenResolutionChange => onScreenResolutionChange(p)
case PacketType.ScreenSet => onScreenSet(p)
case PacketType.ScreenFill => onScreenFill(p)
case PacketType.ScreenCopy => onScreenCopy(p)
}
}
def onScreenResolutionChange(p: PacketParser) = {
val t = p.readTileEntity[Screen]()
val w = p.readInt()
val h = p.readInt()
t.resolution = (w, h)
}
def onScreenSet(p: PacketParser) = {
val t = p.readTileEntity[Screen]()
val col = p.readInt()
val row = p.readInt()
val s = p.readUTF()
t.set(col, row, s)
}
def onScreenFill(p: PacketParser) = {
val t = p.readTileEntity[Screen]()
val col = p.readInt()
val row = p.readInt()
val w = p.readInt()
val h = p.readInt()
val c = p.readChar()
t.fill(col, row, w, h, c)
}
def onScreenCopy(p: PacketParser) = {
val t = p.readTileEntity[Screen]()
val col = p.readInt()
val row = p.readInt()
val w = p.readInt()
val h = p.readInt()
val tx = p.readInt()
val ty = p.readInt()
t.copy(col, row, w, h, tx, ty)
}
/** Utility class for packet parsing. */
private class PacketParser(packet: Packet250CustomPayload, player: Player) extends DataInputStream(new ByteArrayInputStream(packet.data)) {
val world = OpenComputers.proxy.getWorldForPlayer(player)
val packetType = PacketType(readByte())
def readTileEntity[T]() = {
val x = readInt()
val y = readInt()
val z = readInt()
world.getBlockTileEntity(x, y, z).asInstanceOf[T]
}
}
}

View File

@ -0,0 +1,29 @@
package li.cil.oc.client.components
import li.cil.oc.common.components.IScreen
import li.cil.oc.common.tileentity.TileEntityScreen
import li.cil.oc.common.util.TextBuffer
class Screen(owner: TileEntityScreen) extends IScreen {
val buffer = new TextBuffer(40, 24)
def resolution_=(value: (Int, Int)) = {
buffer.size = value
owner.updateGui(buffer.toString)
}
def set(col: Int, row: Int, s: String) = {
buffer.set(col, row, s)
owner.updateGui(buffer.toString)
}
def fill(col: Int, row: Int, w: Int, h: Int, c: Char) = {
buffer.fill(col, row, w, h, c)
owner.updateGui(buffer.toString)
}
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = {
buffer.copy(col, row, w, h, tx, ty)
owner.updateGui(buffer.toString)
}
}

View File

@ -1,14 +1,13 @@
package li.cil.oc.common
import cpw.mods.fml.common.event.FMLInitializationEvent
import cpw.mods.fml.common.event.FMLPostInitializationEvent
import cpw.mods.fml.common.event.FMLPreInitializationEvent
import cpw.mods.fml.common.event._
import cpw.mods.fml.common.network.Player
import cpw.mods.fml.common.registry.LanguageRegistry
import li.cil.oc.Blocks
import li.cil.oc.Config
import li.cil.oc.Items
import li.cil.oc.server.computer.Computer
import li.cil.oc._
import li.cil.oc.api.OpenComputersAPI
import li.cil.oc.server.computer.Drivers
import li.cil.oc.server.drivers._
import net.minecraft.world.World
class CommonProxy {
def preInit(e: FMLPreInitializationEvent): Unit = {
@ -17,8 +16,8 @@ class CommonProxy {
Config.blockComputerId = config.getBlock("computer", Config.blockComputerId,
"The block ID used for computers.").getInt(Config.blockComputerId)
Config.blockMonitorId = config.getBlock("computer", Config.blockMonitorId,
"The block ID used for monitors.").getInt(Config.blockMonitorId)
Config.blockScreenId = config.getBlock("screen", Config.blockScreenId,
"The block ID used for screens.").getInt(Config.blockScreenId)
}
def init(e: FMLInitializationEvent): Unit = {
@ -28,7 +27,8 @@ class CommonProxy {
// TODO Figure out how resource pack based localization works.
LanguageRegistry.addName(Blocks.computer, "Computer")
new Computer(null)
OpenComputersAPI.addDriver(GraphicsCardDriver)
OpenComputersAPI.addDriver(ScreenDriver)
}
def postInit(e: FMLPostInitializationEvent): Unit = {
@ -37,4 +37,7 @@ class CommonProxy {
// over the course of a game, since that could lead to weird effects.
Drivers.locked = true
}
// TODO
def getWorldForPlayer(player: Player): World = null
}

View File

@ -0,0 +1,8 @@
package li.cil.oc.common
object PacketType extends Enumeration {
val ScreenResolutionChange = Value("ScreenResolutionChange")
val ScreenSet = Value("ScreenSet")
val ScreenFill = Value("ScreenFill")
val ScreenCopy = Value("ScreenCopy")
}

View File

@ -0,0 +1,29 @@
package li.cil.oc.common.block
import cpw.mods.fml.common.registry.GameRegistry
import li.cil.oc.Config
import li.cil.oc.CreativeTab
import li.cil.oc.common.tileentity.TileEntityScreen
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.world.World
class BlockScreen extends Block(Config.blockScreenId, Material.iron) {
// ----------------------------------------------------------------------- //
// Construction
// ----------------------------------------------------------------------- //
setHardness(2f)
GameRegistry.registerBlock(this, "oc.screen")
GameRegistry.registerTileEntity(classOf[TileEntityScreen], "oc.screen")
setUnlocalizedName("oc.screen")
setCreativeTab(CreativeTab)
// ----------------------------------------------------------------------- //
// Tile entity
// ----------------------------------------------------------------------- //
override def hasTileEntity(metadata: Int) = true
override def createTileEntity(world: World, metadata: Int) = new TileEntityScreen(world.isRemote)
}

View File

@ -0,0 +1,12 @@
package li.cil.oc.common.components
trait IScreen {
def resolution_=(value: (Int, Int)): Unit
def resolution {} // Required for setter.
def set(col: Int, row: Int, s: String): Unit
def fill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit
}

View File

@ -0,0 +1,71 @@
package li.cil.oc.common.computer
import li.cil.oc.api.IBlockDriver
import li.cil.oc.api.IItemDriver
import net.minecraft.block.Block
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
/**
* This interface is used to be able to use the same basic type for storing a
* computer on both client and server. There are two implementations of this,
* one for the server, which does hold the actual computer logic, and one for
* the client, which does nothing at all.
*/
trait IComputer {
/**
* Tries to add the specified item as a component to the computer.
*
* This can fail if there's either no driver for that item type, or another
* component with that ID is already installed in the computer. It returns
* the driver used for that item, to allow further checks (such as whether
* the slot the item should be installed into is valid based on the component
* type specified in the driver).
*
* This will add the component and driver to the list of installed components
* and send the install signal to the computer.
*/
def add(item: ItemStack, id: Int): Option[IItemDriver]
/**
* Tries to add the specified block as a component to the computer.
*
* This can fail if there's either no driver for that block type, or another
* component with that ID is already installed in the computer. It returns
* the driver used for that block, to allow further checks.
*
* This will add the component and driver to the list of installed components
* and send the install signal to the computer.
*/
def add(block: Block, x: Int, y: Int, z: Int, id: Int): Option[IBlockDriver]
/**
* Tries to remove the component with the specified ID from the computer.
*
* This can fail if there is no such component installed in the computer. The
* driver's {@see IDriver#close()} function will be called, and the uninstall
* signal will be sent to the computer.
*/
def remove(id: Int): Boolean
// ----------------------------------------------------------------------- //
/** Starts asynchronous execution of this computer if it isn't running. */
def start(): Boolean
/** Stops a computer, possibly asynchronously, possibly blocking. */
def stop(): Unit
/**
* Passively drives the computer and performs driver calls. If this is not
* called regularly the computer will pause. If a computer is currently
* trying to perform a driver call, this will perform that driver call in a
* synchronized manner.
*/
def update()
// ----------------------------------------------------------------------- //
def readFromNBT(nbt: NBTTagCompound)
def writeToNBT(nbt: NBTTagCompound)
}

View File

@ -0,0 +1,38 @@
package li.cil.oc.common.items
import li.cil.oc.Config
import li.cil.oc.CreativeTab
import net.minecraft.item.Item
import net.minecraft.world.World
import scala.collection.mutable.WeakHashMap
import net.minecraft.nbt.NBTTagCompound
import li.cil.oc.server.components.Disk
import net.minecraft.item.ItemStack
import li.cil.oc.server.components.GraphicsCard
object ItemGraphicsCard {
private val instances = WeakHashMap.empty[NBTTagCompound, GraphicsCard]
def getComponent(item: ItemStack): Option[GraphicsCard] =
if (item.itemID == Config.itemGPUId) {
val tag = item.getTagCompound match {
case null => new NBTTagCompound
case tag => tag
}
instances.get(tag).orElse {
val component = new GraphicsCard(tag)
instances += tag -> component
Some(component)
}
}
else throw new IllegalArgumentException("Invalid item type.")
}
class ItemGraphicsCard extends Item(Config.itemGPUId) {
setMaxStackSize(1)
setHasSubtypes(true)
setUnlocalizedName("oc.gpu")
setCreativeTab(CreativeTab)
override def shouldPassSneakingClickToBlock(world: World, x: Int, y: Int, z: Int) = true
}

View File

@ -0,0 +1,22 @@
package li.cil.oc.common.tileentity
import cpw.mods.fml.relauncher._
import li.cil.oc.client.components.{ Screen => ClientScreen }
import li.cil.oc.server.components.{ Screen => ServerScreen }
import net.minecraft.tileentity.TileEntity
class TileEntityScreen(isClient: Boolean) extends TileEntity {
def this() = this(false)
val component =
if (isClient) new ClientScreen(this)
else new ServerScreen(this)
@SideOnly(Side.CLIENT)
def updateGui(value: () => String): Unit = {
// TODO if GUI is open, call value() to get the new display string and show it
}
// TODO open GUI on right click
}

View File

@ -0,0 +1,91 @@
package li.cil.oc.common.util
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.nbt.NBTTagList
import net.minecraft.nbt.NBTTagString
/** This stores chars in a 2D-Array and provides some manipulation functions. */
class TextBuffer(var width: Int, var height: Int) {
var buffer = Array.fill(height, width)(' ')
def size = (width, height)
def size_=(value: (Int, Int)): Unit = {
val (w, h) = value
val nbuffer = Array.fill(h, w)(' ')
(0 to (h min height)) foreach {
y => Array.copy(buffer(y), 0, nbuffer(y), 0, w min width)
}
buffer = nbuffer
width = w
height = h
}
def set(col: Int, row: Int, s: String): Unit =
for (i <- col until ((col + s.length) min width))
buffer(row)(i) = s(i - col)
def fill(x: Int, y: Int, w: Int, h: Int, c: Char): Unit =
for (y <- (y max 0 min height) until ((y + h) max 0 min height))
for (x <- (x max 0 min width) until ((x + w) max 0 min width))
buffer(y)(x) = c
/** Copies a portion of the buffer. */
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {
// Anything to do at all?
if (w <= 0 || h <= 0) return
if (tx == 0 && ty == 0) return
// Loop over the target rectangle, starting from the directions away from
// the source rectangle and copy the data. This way we ensure we don't
// overwrite anything we still need to copy.
val (dx0, dx1) = ((col + tx + w - 1) max 0 min (width - 1), (col + tx) max 0 min width) match {
case destx if tx > 0 => destx
case destx => destx.swap
}
val (dy0, dy1) = ((row + ty + h - 1) max 0 min (height - 1), (row + ty) max 0 min height) match {
case desty if (ty > 0) => desty
case desty => desty.swap
}
val (sx, sy) = ((if (tx > 0) -1 else 1), (if (ty > 0) -1 else 1))
// Copy values to destination rectangle if there source is valid.
for (ny <- dy0 to dy1 by sy) (ny - ty) match {
case oy if oy >= 0 && oy < height =>
for (nx <- dx0 to dx1 by sx) (nx - tx) match {
case ox if ox >= 0 && ox < width => buffer(ny)(nx) = buffer(oy)(ox)
case _ => /* Got no source column. */
}
case _ => /* Got no source row. */
}
}
def readFromNBT(nbt: NBTTagCompound): Unit = {
val w = nbt.getInteger("width")
val h = nbt.getInteger("height")
size = (w, h)
val b = nbt.getTagList("buffer")
for (i <- 0 to (h min b.tagCount())) {
set(0, i, b.tagAt(i).asInstanceOf[NBTTagString].data)
}
}
def writeToNBT(nbt: NBTTagCompound): Unit = {
nbt.setInteger("width", width)
nbt.setInteger("height", height)
val b = new NBTTagList("buffer")
for (i <- 0 to height) {
b.appendTag(new NBTTagString(null, String.valueOf(buffer(i))))
}
}
override def toString = {
val b = StringBuilder.newBuilder
if (buffer.length > 0) {
b.appendAll(buffer(0))
for (y <- 1 until height) {
b.append('\n').appendAll(buffer(y))
}
}
b.toString
}
}

View File

@ -0,0 +1,81 @@
package li.cil.oc.server
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import cpw.mods.fml.common.network.PacketDispatcher
import li.cil.oc.common.PacketType
import net.minecraft.network.packet.Packet
import net.minecraft.network.packet.Packet250CustomPayload
import net.minecraft.tileentity.TileEntity
/** Centralized packet dispatcher for sending updates to the client. */
object PacketSender {
def sendScreenResolutionChange(t: TileEntity, w: Int, h: Int) = {
val p = new PacketBuilder(PacketType.ScreenResolutionChange)
p.writeTileEntity(t)
p.writeInt(w)
p.writeInt(h)
p.sendToAllPlayers()
}
def sendScreenSet(t: TileEntity, col: Int, row: Int, s: String) = {
val p = new PacketBuilder(PacketType.ScreenSet)
p.writeTileEntity(t)
p.writeInt(col)
p.writeInt(row)
p.writeUTF(s)
p.sendToAllPlayers()
}
def sendScreenFill(t: TileEntity, col: Int, row: Int, w: Int, h: Int, c: Char) = {
val p = new PacketBuilder(PacketType.ScreenFill)
p.writeTileEntity(t)
p.writeInt(col)
p.writeInt(row)
p.writeInt(w)
p.writeInt(h)
p.writeChar(c)
p.sendToAllPlayers()
}
def sendScreenCopy(t: TileEntity, col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = {
val p = new PacketBuilder(PacketType.ScreenCopy)
p.writeTileEntity(t)
p.writeInt(col)
p.writeInt(row)
p.writeInt(w)
p.writeInt(h)
p.writeInt(tx)
p.writeInt(ty)
p.sendToAllPlayers()
}
/** Utility class for packet creation. */
private class PacketBuilder(packetType: PacketType.Value, private val stream: ByteArrayOutputStream = new ByteArrayOutputStream) extends DataOutputStream(stream) {
writeByte(packetType.id)
def writeTileEntity(t: TileEntity) = {
writeInt(t.xCoord)
writeInt(t.yCoord)
writeInt(t.zCoord)
}
def sendToAllPlayers() = PacketDispatcher.sendPacketToAllPlayers(packet)
private def packet: Packet = {
val p = new Packet250CustomPayload
p.channel = "OpenComp"
p.data = stream.toByteArray
p.length = stream.size
return p
}
}
}

View File

@ -0,0 +1,87 @@
package li.cil.oc.server.components
import li.cil.oc.common.util.TextBuffer
import net.minecraft.nbt.NBTTagCompound
/**
* Graphics cards are what we use to render text to screens. They have an
* internal text buffer that can be manipulated from the Lua side via the
* GPU driver. These changes are forwarded to any monitors the card is bound
* to, if any. Note that the screen component on the server does not have an
* internal state. It merely generates packets to be sent to the client, whose
* screen component in turn has a state similar to a graphics card which is
* used by the GUI to display the text in the buffer.
*
* TODO minimize NBT updates, i.e. only write what really changed?
*/
class GraphicsCard(val nbt: NBTTagCompound) {
readFromNBT()
val resolutions = List(List(40, 24), List(80, 24))
private val buffer = new TextBuffer(40, 24)
var screen: Screen = null
def resolution = buffer.size
def resolution_=(value: (Int, Int)) =
if (resolutions.contains(value)) {
buffer.size = value
if (screen != null) {
screen.resolution = value
}
writeToNBT()
}
else throw new IllegalArgumentException("unsupported resolution")
def set(x: Int, y: Int, s: String): Unit = {
// Make sure the string isn't longer than it needs to be, in particular to
// avoid sending too much data to our clients.
val truncated = s.substring(0, buffer.width)
buffer.set(x, y, truncated)
if (screen != null) {
screen.set(x, y, truncated)
}
writeToNBT()
}
def fill(x: Int, y: Int, w: Int, h: Int, c: Char) = {
buffer.fill(x, y, w, h, c)
if (screen != null) {
screen.fill(x, y, w, h, c)
}
writeToNBT()
}
def copy(x: Int, y: Int, w: Int, h: Int, tx: Int, ty: Int) = {
buffer.copy(x, y, w, h, tx, ty)
if (screen != null) {
screen.copy(x, y, w, h, tx, ty)
}
writeToNBT()
}
def bind(m: Screen): Unit = {
screen = m
writeToNBT()
}
def readFromNBT(): Unit = {
// A new instance has no data written to its NBT tag compound.
if (!nbt.hasKey("monitor.x")) return
val x = nbt.getInteger("monitor.x")
val y = nbt.getInteger("monitor.y")
val z = nbt.getInteger("monitor.z")
// TODO get tile entity in world, get its monitor component
buffer.readFromNBT(nbt)
}
def writeToNBT(): Unit = {
nbt.setInteger("monitor.x", screen.owner.xCoord)
nbt.setInteger("monitor.y", screen.owner.yCoord)
nbt.setInteger("monitor.z", screen.owner.zCoord)
buffer.writeToNBT(nbt)
}
}

View File

@ -0,0 +1,21 @@
package li.cil.oc.server.components
import li.cil.oc.common.components.IScreen
import li.cil.oc.common.tileentity.TileEntityScreen
import li.cil.oc.server.PacketSender
class Screen(val owner: TileEntityScreen) extends IScreen {
def resolution_=(value: (Int, Int)) = {
val (w, h) = value
PacketSender.sendScreenResolutionChange(owner, w, h)
}
def set(col: Int, row: Int, s: String) =
PacketSender.sendScreenSet(owner, col, row, s)
def fill(col: Int, row: Int, w: Int, h: Int, c: Char) =
PacketSender.sendScreenFill(owner, col, row, w, h, c)
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) =
PacketSender.sendScreenCopy(owner, col, row, w, h, tx, ty)
}

View File

@ -1,22 +1,24 @@
package li.cil.oc.server.computer
import java.util.Calendar
import java.util.Locale
import java.util.concurrent._
import java.util.concurrent.atomic.AtomicInteger
import scala.Array.canBuildFrom
import scala.collection.JavaConversions._
import scala.collection.mutable._
import scala.reflect.runtime.universe._
import scala.util.Random
import com.naef.jnlua._
import li.cil.oc.Config
import li.cil.oc.api.IComputerContext
import li.cil.oc.common.computer.IComputer
import net.minecraft.block.Block
import net.minecraft.item.ItemStack
import net.minecraft.nbt._
import java.util.Date
import java.util.Calendar
import java.util.Locale
/**
* Wrapper class for Lua states set up to behave like a pseudo-OS.
@ -148,6 +150,7 @@ class Computer(val owner: IComputerEnvironment) extends IComputerContext with IC
def component[T: TypeTag](id: Int) = components.get(id) match {
case None => throw new IllegalArgumentException("no such component")
case Some(component) =>
// TODO is this right?
if (component.getClass() == typeOf[T]) component.asInstanceOf[T]
else throw new IllegalArgumentException("bad component type")
}
@ -241,10 +244,10 @@ class Computer(val owner: IComputerEnvironment) extends IComputerContext with IC
}
def add(block: Block, x: Int, y: Int, z: Int, id: Int) =
Drivers.driverFor(block) match {
Drivers.driverFor(owner.world, block) match {
case None => None
case Some(driver) if !components.contains(id) =>
val component = driver.instance.component(x, y, z)
val component = driver.instance.component(owner.world, x, y, z)
components += id -> (component, driver)
driver.instance.onInstall(this, component)
signal("component_install", id)

View File

@ -55,7 +55,10 @@ abstract private[oc] class Driver {
for (method <- instanceType.members collect { case m if m.isMethod => m.asMethod })
method.annotations collect {
case annotation: Callback => {
val name = annotation.name
val name = annotation.name match {
case s if s == null || s.isEmpty() => method.name.decoded
case name => name
}
lua.getField(-1, name) // ... drivers api func?
if (lua.isNil(-1)) { // ... drivers api nil
// No such entry yet.

View File

@ -6,6 +6,7 @@ import li.cil.oc.api.IBlockDriver
import li.cil.oc.api.IItemDriver
import net.minecraft.block.Block
import net.minecraft.item.ItemStack
import net.minecraft.world.World
/**
* This class keeps track of registered drivers and provides installation logic
@ -66,7 +67,7 @@ private[oc] object Drivers {
* @param block the type of block to check for a driver for.
* @return the driver for that block type if we have one.
*/
def driverFor(block: Block) = blocks.find(_.instance.worksWith(block))
def driverFor(world: World, block: Block) = blocks.find(_.instance.worksWith(world: World, block))
/**
* Used when an item component is added to a computer to see if we have a

View File

@ -0,0 +1,78 @@
package li.cil.oc.server.drivers
import li.cil.oc.Config
import li.cil.oc.api.Callback
import li.cil.oc.api.ComponentType
import li.cil.oc.api.IComputerContext
import li.cil.oc.api.IItemDriver
import li.cil.oc.common.items.ItemGraphicsCard
import li.cil.oc.server.components.GraphicsCard
import li.cil.oc.server.components.Screen
import net.minecraft.item.ItemStack
object GraphicsCardDriver extends IItemDriver {
// ----------------------------------------------------------------------- //
// API
// ----------------------------------------------------------------------- //
@Callback
def setResolution(computer: IComputerContext, idGpu: Int, w: Int, h: Int) =
computer.component[GraphicsCard](idGpu).resolution = (w, h)
@Callback
def getResolution(computer: IComputerContext, idGpu: Int) = {
val res = computer.component[GraphicsCard](idGpu).resolution
Array(res._1, res._2)
}
@Callback
def resolutions(computer: IComputerContext, idGpu: Int) =
computer.component[GraphicsCard](idGpu).resolutions
@Callback
def set(computer: IComputerContext, idGpu: Int, x: Int, y: Int, value: String) =
computer.component[GraphicsCard](idGpu).set(x, y, value)
@Callback
def fill(computer: IComputerContext, idGpu: Int, value: String, x: Int, y: Int, w: Int, h: Int) = {
if (value == null || value.length < 1)
throw new IllegalArgumentException("bad argument #2 (invalid string)")
computer.component[GraphicsCard](idGpu).fill(x, y, w, h, value.charAt(0))
}
@Callback
def copy(computer: IComputerContext, idGpu: Int, x: Int, y: Int, w: Int, h: Int, tx: Int, ty: Int) =
computer.component[GraphicsCard](idGpu).copy(x, y, w, h, tx, ty)
/**
* Binds the GPU to the specified monitor, meaning it'll send its output to
* that monitor from now on.
*
* TODO Add another parameter to define the buffer to bind to the monitor, in
* case we add advanced GPUs that support multiple buffers + monitors.
*/
@Callback
def bind(computer: IComputerContext, idGpu: Int, idScreen: Int) = {
val gpu = computer.component[GraphicsCard](idGpu)
if (idScreen > 0) gpu.bind(computer.component[Screen](idScreen))
else gpu.bind(null)
}
// ----------------------------------------------------------------------- //
// IDriver
// ----------------------------------------------------------------------- //
def componentName = "gpu"
override def apiName = "gpu"
// ----------------------------------------------------------------------- //
// IItemDriver
// ----------------------------------------------------------------------- //
def worksWith(item: ItemStack) = item.itemID == Config.itemGPUId
def componentType(item: ItemStack) = ComponentType.PCI
def component(item: ItemStack) = ItemGraphicsCard.getComponent(item)
}

View File

@ -21,7 +21,7 @@ object HDDDriver extends IItemDriver {
def componentType(item: ItemStack) = ComponentType.HDD
def component(item: ItemStack) = new HDDComponent(item)
def component(item: ItemStack) = null
def close(component: Any) {
component.asInstanceOf[HDDComponent].close()

View File

@ -0,0 +1,25 @@
package li.cil.oc.server.drivers
import li.cil.oc.Config
import li.cil.oc.api.IBlockDriver
import li.cil.oc.common.tileentity.TileEntityScreen
import net.minecraft.block.Block
import net.minecraft.world.World
object ScreenDriver extends IBlockDriver {
// ----------------------------------------------------------------------- //
// IDriver
// ----------------------------------------------------------------------- //
def componentName = "screen"
// ----------------------------------------------------------------------- //
// IBlockDriver
// ----------------------------------------------------------------------- //
def worksWith(world: World, block: Block) =
block.blockID == Config.blockScreenId
def component(world: World, x: Int, y: Int, z: Int) =
world.getBlockTileEntity(x, y, z).asInstanceOf[TileEntityScreen].component
}