diff --git a/li/cil/oc/Blocks.scala b/li/cil/oc/Blocks.scala index d934ec866..476e85944 100644 --- a/li/cil/oc/Blocks.scala +++ b/li/cil/oc/Blocks.scala @@ -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 } } \ No newline at end of file diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index b5fdc2dcc..8f23ff0e9 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -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 diff --git a/li/cil/oc/api/Callback.java b/li/cil/oc/api/Callback.java index 3d2224d20..b5f88807c 100644 --- a/li/cil/oc/api/Callback.java +++ b/li/cil/oc/api/Callback.java @@ -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 ""; } \ No newline at end of file diff --git a/li/cil/oc/api/IBlockDriver.scala b/li/cil/oc/api/IBlockDriver.scala index 6a4659fb2..c83e1fc3c 100644 --- a/li/cil/oc/api/IBlockDriver.scala +++ b/li/cil/oc/api/IBlockDriver.scala @@ -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 } \ No newline at end of file diff --git a/li/cil/oc/api/IComputerContext.scala b/li/cil/oc/api/IComputerContext.scala new file mode 100644 index 000000000..175c4dc17 --- /dev/null +++ b/li/cil/oc/api/IComputerContext.scala @@ -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 +} \ No newline at end of file diff --git a/li/cil/oc/api/IMemory.scala b/li/cil/oc/api/IMemory.scala new file mode 100644 index 000000000..b6b06634e --- /dev/null +++ b/li/cil/oc/api/IMemory.scala @@ -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 +} \ No newline at end of file diff --git a/li/cil/oc/client/PacketHandler.scala b/li/cil/oc/client/PacketHandler.scala new file mode 100644 index 000000000..70dd6d4e4 --- /dev/null +++ b/li/cil/oc/client/PacketHandler.scala @@ -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] + } + } +} \ No newline at end of file diff --git a/li/cil/oc/client/components/Screen.scala b/li/cil/oc/client/components/Screen.scala new file mode 100644 index 000000000..73ad7c3b1 --- /dev/null +++ b/li/cil/oc/client/components/Screen.scala @@ -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) + } +} \ No newline at end of file diff --git a/li/cil/oc/common/CommonProxy.scala b/li/cil/oc/common/CommonProxy.scala index 965d7969d..7b3f3eb20 100644 --- a/li/cil/oc/common/CommonProxy.scala +++ b/li/cil/oc/common/CommonProxy.scala @@ -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 } \ No newline at end of file diff --git a/li/cil/oc/common/PacketType.scala b/li/cil/oc/common/PacketType.scala new file mode 100644 index 000000000..424253d4d --- /dev/null +++ b/li/cil/oc/common/PacketType.scala @@ -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") +} \ No newline at end of file diff --git a/li/cil/oc/common/block/BlockScreen.scala b/li/cil/oc/common/block/BlockScreen.scala new file mode 100644 index 000000000..b09ad726d --- /dev/null +++ b/li/cil/oc/common/block/BlockScreen.scala @@ -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) +} \ No newline at end of file diff --git a/li/cil/oc/common/components/IScreen.scala b/li/cil/oc/common/components/IScreen.scala new file mode 100644 index 000000000..8aa96514f --- /dev/null +++ b/li/cil/oc/common/components/IScreen.scala @@ -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 +} \ No newline at end of file diff --git a/li/cil/oc/common/computer/IComputer.scala b/li/cil/oc/common/computer/IComputer.scala new file mode 100644 index 000000000..ad37dfe8e --- /dev/null +++ b/li/cil/oc/common/computer/IComputer.scala @@ -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) +} \ No newline at end of file diff --git a/li/cil/oc/common/items/ItemGraphicsCard.scala b/li/cil/oc/common/items/ItemGraphicsCard.scala new file mode 100644 index 000000000..07ee262cb --- /dev/null +++ b/li/cil/oc/common/items/ItemGraphicsCard.scala @@ -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 +} \ No newline at end of file diff --git a/li/cil/oc/common/tileentity/TileEntityScreen.scala b/li/cil/oc/common/tileentity/TileEntityScreen.scala new file mode 100644 index 000000000..b5e8be0ef --- /dev/null +++ b/li/cil/oc/common/tileentity/TileEntityScreen.scala @@ -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 +} \ No newline at end of file diff --git a/li/cil/oc/common/util/TextBuffer.scala b/li/cil/oc/common/util/TextBuffer.scala new file mode 100644 index 000000000..b8d91357b --- /dev/null +++ b/li/cil/oc/common/util/TextBuffer.scala @@ -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 + } +} \ No newline at end of file diff --git a/li/cil/oc/server/PacketSender.scala b/li/cil/oc/server/PacketSender.scala new file mode 100644 index 000000000..12d00b2f4 --- /dev/null +++ b/li/cil/oc/server/PacketSender.scala @@ -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 + } + } +} \ No newline at end of file diff --git a/li/cil/oc/server/components/GraphicsCard.scala b/li/cil/oc/server/components/GraphicsCard.scala new file mode 100644 index 000000000..61ff14c59 --- /dev/null +++ b/li/cil/oc/server/components/GraphicsCard.scala @@ -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) + } +} \ No newline at end of file diff --git a/li/cil/oc/server/components/Screen.scala b/li/cil/oc/server/components/Screen.scala new file mode 100644 index 000000000..074cc7862 --- /dev/null +++ b/li/cil/oc/server/components/Screen.scala @@ -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) +} \ 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 7db270699..e2dd7d7af 100644 --- a/li/cil/oc/server/computer/Computer.scala +++ b/li/cil/oc/server/computer/Computer.scala @@ -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) diff --git a/li/cil/oc/server/computer/Driver.scala b/li/cil/oc/server/computer/Driver.scala index 2d814431f..53b726aa7 100644 --- a/li/cil/oc/server/computer/Driver.scala +++ b/li/cil/oc/server/computer/Driver.scala @@ -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. diff --git a/li/cil/oc/server/computer/Drivers.scala b/li/cil/oc/server/computer/Drivers.scala index 4bbe30198..55f9a2c7b 100644 --- a/li/cil/oc/server/computer/Drivers.scala +++ b/li/cil/oc/server/computer/Drivers.scala @@ -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 diff --git a/li/cil/oc/server/computer/ItemDriver.scala b/li/cil/oc/server/computer/ItemDriver.scala new file mode 100644 index 000000000..e69de29bb diff --git a/li/cil/oc/server/drivers/GraphicsCardDriver.scala b/li/cil/oc/server/drivers/GraphicsCardDriver.scala new file mode 100644 index 000000000..b0db3faeb --- /dev/null +++ b/li/cil/oc/server/drivers/GraphicsCardDriver.scala @@ -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) +} \ No newline at end of file diff --git a/li/cil/oc/server/drivers/HDD.scala b/li/cil/oc/server/drivers/HDD.scala index 455137cbd..2aa0baa62 100644 --- a/li/cil/oc/server/drivers/HDD.scala +++ b/li/cil/oc/server/drivers/HDD.scala @@ -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() diff --git a/li/cil/oc/server/drivers/ScreenDriver.scala b/li/cil/oc/server/drivers/ScreenDriver.scala new file mode 100644 index 000000000..6d55981ce --- /dev/null +++ b/li/cil/oc/server/drivers/ScreenDriver.scala @@ -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 +} \ No newline at end of file