mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-09 07:15:11 -04:00
some groundwork for networking (e.g. for updating monitors on client when graphics card on server does something)
This commit is contained in:
parent
f9cd7fd3b6
commit
a059183133
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 "";
|
||||
}
|
@ -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
|
||||
}
|
50
li/cil/oc/api/IComputerContext.scala
Normal file
50
li/cil/oc/api/IComputerContext.scala
Normal 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
|
||||
}
|
18
li/cil/oc/api/IMemory.scala
Normal file
18
li/cil/oc/api/IMemory.scala
Normal 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
|
||||
}
|
79
li/cil/oc/client/PacketHandler.scala
Normal file
79
li/cil/oc/client/PacketHandler.scala
Normal 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]
|
||||
}
|
||||
}
|
||||
}
|
29
li/cil/oc/client/components/Screen.scala
Normal file
29
li/cil/oc/client/components/Screen.scala
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
8
li/cil/oc/common/PacketType.scala
Normal file
8
li/cil/oc/common/PacketType.scala
Normal 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")
|
||||
}
|
29
li/cil/oc/common/block/BlockScreen.scala
Normal file
29
li/cil/oc/common/block/BlockScreen.scala
Normal 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)
|
||||
}
|
12
li/cil/oc/common/components/IScreen.scala
Normal file
12
li/cil/oc/common/components/IScreen.scala
Normal 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
|
||||
}
|
71
li/cil/oc/common/computer/IComputer.scala
Normal file
71
li/cil/oc/common/computer/IComputer.scala
Normal 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)
|
||||
}
|
38
li/cil/oc/common/items/ItemGraphicsCard.scala
Normal file
38
li/cil/oc/common/items/ItemGraphicsCard.scala
Normal 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
|
||||
}
|
22
li/cil/oc/common/tileentity/TileEntityScreen.scala
Normal file
22
li/cil/oc/common/tileentity/TileEntityScreen.scala
Normal 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
|
||||
}
|
91
li/cil/oc/common/util/TextBuffer.scala
Normal file
91
li/cil/oc/common/util/TextBuffer.scala
Normal 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
|
||||
}
|
||||
}
|
81
li/cil/oc/server/PacketSender.scala
Normal file
81
li/cil/oc/server/PacketSender.scala
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
87
li/cil/oc/server/components/GraphicsCard.scala
Normal file
87
li/cil/oc/server/components/GraphicsCard.scala
Normal 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)
|
||||
}
|
||||
}
|
21
li/cil/oc/server/components/Screen.scala
Normal file
21
li/cil/oc/server/components/Screen.scala
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
0
li/cil/oc/server/computer/ItemDriver.scala
Normal file
0
li/cil/oc/server/computer/ItemDriver.scala
Normal file
78
li/cil/oc/server/drivers/GraphicsCardDriver.scala
Normal file
78
li/cil/oc/server/drivers/GraphicsCardDriver.scala
Normal 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)
|
||||
}
|
@ -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()
|
||||
|
25
li/cil/oc/server/drivers/ScreenDriver.scala
Normal file
25
li/cil/oc/server/drivers/ScreenDriver.scala
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user