blob: b923b762db501ec958771d8a089da52a1eca5e67 [file] [log] [blame] [raw]
package li.cil.oc.common.component
import com.google.common.base.Strings
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import li.cil.oc.Constants
import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute
import li.cil.oc.api.driver.DeviceInfo.DeviceClass
import li.cil.oc.OpenComputers
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.driver.DeviceInfo
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
import li.cil.oc.api.machine.Context
import li.cil.oc.api.network.EnvironmentHost
import li.cil.oc.api.network._
import li.cil.oc.api.prefab
import li.cil.oc.client.renderer.TextBufferRenderCache
import li.cil.oc.client.renderer.font.TextBufferRenderData
import li.cil.oc.client.{ComponentTracker => ClientComponentTracker}
import li.cil.oc.client.{PacketSender => ClientPacketSender}
import li.cil.oc.common._
import li.cil.oc.server.component.Keyboard
import li.cil.oc.server.{ComponentTracker => ServerComponentTracker}
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util
import li.cil.oc.util.BlockPosition
import li.cil.oc.util.PackedColor
import li.cil.oc.util.SideTracker
import net.minecraft.client.Minecraft
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import scala.collection.convert.WrapAsJava._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment with api.internal.TextBuffer with DeviceInfo {
override val node = api.Network.newNode(this, Visibility.Network).
withComponent("screen").
withConnector().
create()
private var maxResolution = Settings.screenResolutionsByTier(Tier.One)
private var maxDepth = Settings.screenDepthsByTier(Tier.One)
private var aspectRatio = (1.0, 1.0)
private var powerConsumptionPerTick = Settings.get.screenCost
private var precisionMode = false
// For client side only.
private var isRendering = true
private var isDisplaying = true
private var hasPower = true
private var relativeLitArea = -1.0
private var _pendingCommands: Option[PacketBuilder] = None
private val syncInterval = 100
private var syncCooldown = syncInterval
private def pendingCommands = _pendingCommands.getOrElse {
val pb = new CompressedPacketBuilder(PacketType.TextBufferMulti)
pb.writeUTF(node.address)
_pendingCommands = Some(pb)
pb
}
var fullyLitCost = computeFullyLitCost()
// This computes the energy cost (per tick) to keep the screen running if
// every single "pixel" is lit. This cost increases with higher tiers as
// their maximum resolution (pixel density) increases. For a basic screen
// this is simply the configured cost.
def computeFullyLitCost() = {
val (w, h) = Settings.screenResolutionsByTier(0)
val mw = getMaximumWidth
val mh = getMaximumHeight
powerConsumptionPerTick * (mw * mh) / (w * h)
}
val proxy =
if (SideTracker.isClient) new TextBuffer.ClientProxy(this)
else new TextBuffer.ServerProxy(this)
val data = new util.TextBuffer(maxResolution, PackedColor.Depth.format(maxDepth))
var viewport = data.size
def markInitialized(): Unit = {
syncCooldown = -1 // Stop polling for init state.
relativeLitArea = -1 // Recompute lit area, avoid screens blanking out until something changes.
}
private final lazy val deviceInfo = Map(
DeviceAttribute.Class -> DeviceClass.Display,
DeviceAttribute.Description -> "Text buffer",
DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor,
DeviceAttribute.Product -> "Text Screen V0",
DeviceAttribute.Capacity -> (maxResolution._1 * maxResolution._2).toString,
DeviceAttribute.Width -> Array("1", "4", "8").apply(maxDepth.ordinal())
)
override def getDeviceInfo: java.util.Map[String, String] = deviceInfo
// ----------------------------------------------------------------------- //
override val canUpdate = true
override def update() {
super.update()
if (isDisplaying && host.world.getTotalWorldTime % Settings.get.tickFrequency == 0) {
if (relativeLitArea < 0) {
// The relative lit area is the number of pixels that are not blank
// versus the number of pixels in the *current* resolution. This is
// scaled to multi-block screens, since we only compute this for the
// origin.
val w = getViewportWidth
val h = getViewportHeight
var acc = 0f
for (y <- 0 until h) {
val line = data.buffer(y)
val colors = data.color(y)
for (x <- 0 until w) {
val char = line(x)
val color = colors(x)
val bg = PackedColor.unpackBackground(color, data.format)
val fg = PackedColor.unpackForeground(color, data.format)
acc += (if (char == ' ') if (bg == 0) 0 else 1
else if (char == 0x2588) if (fg == 0) 0 else 1
else if (fg == 0 && bg == 0) 0 else 1)
}
}
relativeLitArea = acc / (w * h).toDouble
}
if (node != null) {
val hadPower = hasPower
val neededPower = relativeLitArea * fullyLitCost * Settings.get.tickFrequency
hasPower = node.tryChangeBuffer(-neededPower)
if (hasPower != hadPower) {
ServerPacketSender.sendTextBufferPowerChange(node.address, isDisplaying && hasPower, host)
}
}
}
this.synchronized {
_pendingCommands.foreach(_.sendToPlayersNearHost(host, Option(Settings.get.maxWirelessRange * Settings.get.maxWirelessRange)))
_pendingCommands = None
}
if (SideTracker.isClient && syncCooldown > 0) {
syncCooldown -= 1
if (syncCooldown == 0) {
syncCooldown = syncInterval
ClientPacketSender.sendTextBufferInit(proxy.nodeAddress)
}
}
}
// ----------------------------------------------------------------------- //
@Callback(direct = true, doc = """function():boolean -- Returns whether the screen is currently on.""")
def isOn(computer: Context, args: Arguments): Array[AnyRef] = result(isDisplaying)
@Callback(doc = """function():boolean -- Turns the screen on. Returns true if it was off.""")
def turnOn(computer: Context, args: Arguments): Array[AnyRef] = {
val oldPowerState = isDisplaying
setPowerState(value = true)
result(isDisplaying != oldPowerState, isDisplaying)
}
@Callback(doc = """function():boolean -- Turns off the screen. Returns true if it was on.""")
def turnOff(computer: Context, args: Arguments): Array[AnyRef] = {
val oldPowerState = isDisplaying
setPowerState(value = false)
result(isDisplaying != oldPowerState, isDisplaying)
}
@Callback(direct = true, doc = """function():number, number -- The aspect ratio of the screen. For multi-block screens this is the number of blocks, horizontal and vertical.""")
def getAspectRatio(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
result(aspectRatio._1, aspectRatio._2)
}
@Callback(doc = """function():table -- The list of keyboards attached to the screen.""")
def getKeyboards(context: Context, args: Arguments): Array[AnyRef] = {
context.pause(0.25)
host match {
case screen: tileentity.Screen =>
Array(screen.screens.map(_.node).flatMap(_.neighbors.filter(_.host.isInstanceOf[Keyboard]).map(_.address)).toArray)
case _ =>
Array(node.neighbors.filter(_.host.isInstanceOf[Keyboard]).map(_.address).toArray)
}
}
@Callback(direct = true, doc = """function():boolean -- Returns whether the screen is in high precision mode (sub-pixel mouse event positions).""")
def isPrecise(computer: Context, args: Arguments): Array[AnyRef] = result(precisionMode)
@Callback(doc = """function(enabled:boolean):boolean -- Set whether to use high precision mode (sub-pixel mouse event positions).""")
def setPrecise(computer: Context, args: Arguments): Array[AnyRef] = {
// Available for T3 screens only... easiest way to check for us is to
// base it off of the maximum color depth.
if (maxDepth == Settings.screenDepthsByTier(Tier.Three)) {
val oldValue = precisionMode
precisionMode = args.checkBoolean(0)
result(oldValue)
}
else result(Unit, "unsupported operation")
}
// ----------------------------------------------------------------------- //
override def setEnergyCostPerTick(value: Double) {
powerConsumptionPerTick = value
fullyLitCost = computeFullyLitCost()
}
override def getEnergyCostPerTick = powerConsumptionPerTick
override def setPowerState(value: Boolean) {
if (isDisplaying != value) {
isDisplaying = value
if (isDisplaying) {
val neededPower = fullyLitCost * Settings.get.tickFrequency
hasPower = node.changeBuffer(-neededPower) == 0
}
ServerPacketSender.sendTextBufferPowerChange(node.address, isDisplaying && hasPower, host)
}
}
override def getPowerState = isDisplaying
override def setMaximumResolution(width: Int, height: Int) {
if (width < 1) throw new IllegalArgumentException("width must be larger or equal to one")
if (height < 1) throw new IllegalArgumentException("height must be larger or equal to one")
maxResolution = (width, height)
fullyLitCost = computeFullyLitCost()
proxy.onBufferMaxResolutionChange(width, width)
}
override def getMaximumWidth = maxResolution._1
override def getMaximumHeight = maxResolution._2
override def setAspectRatio(width: Double, height: Double) = this.synchronized(aspectRatio = (width, height))
override def getAspectRatio = aspectRatio._1 / aspectRatio._2
override def setResolution(w: Int, h: Int) = {
val (mw, mh) = maxResolution
if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh)
throw new IllegalArgumentException("unsupported resolution")
// Always send to clients, their state might be dirty.
proxy.onBufferResolutionChange(w, h)
// Force set viewport to new resolution. This is partially for
// backwards compatibility, and partially to enforce a valid one.
val sizeChanged = data.size = (w, h)
val viewportChanged = setViewport(w, h)
if (sizeChanged || viewportChanged) {
if (!viewportChanged && node != null) {
node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h))
}
true
}
else false
}
override def getWidth = data.width
override def getHeight = data.height
override def setViewport(w: Int, h: Int): Boolean = {
val (mw, mh) = data.size
if (w < 1 || h < 1 || w > mw || h > mh)
throw new IllegalArgumentException("unsupported viewport resolution")
// Always send to clients, their state might be dirty.
proxy.onBufferViewportResolutionChange(w, h)
val (cw, ch) = viewport
if (w != cw || h != ch) {
viewport = (w, h)
if (node != null) {
node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h))
}
true
}
else false
}
override def getViewportWidth: Int = viewport._1
override def getViewportHeight: Int = viewport._2
override def setMaximumColorDepth(depth: api.internal.TextBuffer.ColorDepth) = maxDepth = depth
override def getMaximumColorDepth = maxDepth
override def setColorDepth(depth: api.internal.TextBuffer.ColorDepth) = {
if (depth.ordinal > maxDepth.ordinal)
throw new IllegalArgumentException("unsupported depth")
// Always send to clients, their state might be dirty.
proxy.onBufferDepthChange(depth)
data.format = PackedColor.Depth.format(depth)
}
override def getColorDepth = data.format.depth
override def setPaletteColor(index: Int, color: Int) = data.format match {
case palette: PackedColor.MutablePaletteFormat =>
palette(index) = color
proxy.onBufferPaletteChange(index)
case _ => throw new Exception("palette not available")
}
override def getPaletteColor(index: Int) = data.format match {
case palette: PackedColor.MutablePaletteFormat => palette(index)
case _ => throw new Exception("palette not available")
}
override def setForegroundColor(color: Int) = setForegroundColor(color, isFromPalette = false)
override def setForegroundColor(color: Int, isFromPalette: Boolean) {
val value = PackedColor.Color(color, isFromPalette)
if (data.foreground != value) {
data.foreground = value
proxy.onBufferColorChange()
}
}
override def getForegroundColor = data.foreground.value
override def isForegroundFromPalette = data.foreground.isPalette
override def setBackgroundColor(color: Int) = setBackgroundColor(color, isFromPalette = false)
override def setBackgroundColor(color: Int, isFromPalette: Boolean) {
val value = PackedColor.Color(color, isFromPalette)
if (data.background != value) {
data.background = value
proxy.onBufferColorChange()
}
}
override def getBackgroundColor = data.background.value
override def isBackgroundFromPalette = data.background.isPalette
def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) =
if (data.copy(col, row, w, h, tx, ty))
proxy.onBufferCopy(col, row, w, h, tx, ty)
def fill(col: Int, row: Int, w: Int, h: Int, c: Char) =
if (data.fill(col, row, w, h, c))
proxy.onBufferFill(col, row, w, h, c)
def set(col: Int, row: Int, s: String, vertical: Boolean): Unit =
if (col < data.width && (col >= 0 || -col < s.length)) {
// Make sure the string isn't longer than it needs to be, in particular to
// avoid sending too much data to our clients.
val (x, y, truncated) =
if (vertical) {
if (row < 0) (col, 0, s.substring(-row))
else (col, row, s.substring(0, math.min(s.length, data.height - row)))
}
else {
if (col < 0) (0, row, s.substring(-col))
else (col, row, s.substring(0, math.min(s.length, data.width - col)))
}
if (data.set(x, y, truncated, vertical))
proxy.onBufferSet(x, row, truncated, vertical)
}
def get(col: Int, row: Int) = data.get(col, row)
override def getForegroundColor(column: Int, row: Int) =
if (isForegroundFromPalette(column, row)) {
PackedColor.extractForeground(color(column, row))
}
else {
PackedColor.unpackForeground(color(column, row), data.format)
}
override def isForegroundFromPalette(column: Int, row: Int) =
data.format.isFromPalette(PackedColor.extractForeground(color(column, row)))
override def getBackgroundColor(column: Int, row: Int) =
if (isBackgroundFromPalette(column, row)) {
PackedColor.extractBackground(color(column, row))
}
else {
PackedColor.unpackBackground(color(column, row), data.format)
}
override def isBackgroundFromPalette(column: Int, row: Int) =
data.format.isFromPalette(PackedColor.extractBackground(color(column, row)))
override def rawSetText(col: Int, row: Int, text: Array[Array[Char]]): Unit = {
for (y <- row until ((row + text.length) min data.height)) {
val line = text(y - row)
Array.copy(line, 0, data.buffer(y), col, line.length min data.width)
}
proxy.onBufferRawSetText(col, row, text)
}
override def rawSetBackground(col: Int, row: Int, color: Array[Array[Int]]): Unit = {
for (y <- row until ((row + color.length) min data.height)) {
val line = color(y - row)
for (x <- col until ((col + line.length) min data.width)) {
val packedBackground = data.color(row)(col) & 0x00FF
val packedForeground = (data.format.deflate(PackedColor.Color(line(x - col))) << PackedColor.ForegroundShift) & 0xFF00
data.color(row)(col) = (packedForeground | packedBackground).toShort
}
}
// Better for bandwidth to send packed shorts here. Would need a special case for handling on client,
// though, so let's be wasteful for once...
proxy.onBufferRawSetBackground(col, row, color)
}
override def rawSetForeground(col: Int, row: Int, color: Array[Array[Int]]): Unit = {
for (y <- row until ((row + color.length) min data.height)) {
val line = color(y - row)
for (x <- col until ((col + line.length) min data.width)) {
val packedBackground = data.format.deflate(PackedColor.Color(line(x - col))) & 0x00FF
val packedForeground = data.color(row)(col) & 0xFF00
data.color(row)(col) = (packedForeground | packedBackground).toShort
}
}
// Better for bandwidth to send packed shorts here. Would need a special case for handling on client,
// though, so let's be wasteful for once...
proxy.onBufferRawSetForeground(col, row, color)
}
private def color(column: Int, row: Int) = {
if (column < 0 || column >= getWidth || row < 0 || row >= getHeight)
throw new IndexOutOfBoundsException()
else data.color(row)(column)
}
@SideOnly(Side.CLIENT)
override def renderText() = relativeLitArea != 0 && proxy.render()
@SideOnly(Side.CLIENT)
override def renderWidth = TextBufferRenderCache.renderer.charRenderWidth * getViewportWidth
@SideOnly(Side.CLIENT)
override def renderHeight = TextBufferRenderCache.renderer.charRenderHeight * getViewportHeight
@SideOnly(Side.CLIENT)
override def setRenderingEnabled(enabled: Boolean) = isRendering = enabled
@SideOnly(Side.CLIENT)
override def isRenderingEnabled = isRendering
override def keyDown(character: Char, code: Int, player: EntityPlayer) =
proxy.keyDown(character, code, player)
override def keyUp(character: Char, code: Int, player: EntityPlayer) =
proxy.keyUp(character, code, player)
override def clipboard(value: String, player: EntityPlayer) =
proxy.clipboard(value, player)
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) =
proxy.mouseDown(x, y, button, player)
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer) =
proxy.mouseDrag(x, y, button, player)
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer) =
proxy.mouseUp(x, y, button, player)
override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer) =
proxy.mouseScroll(x, y, delta, player)
def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = {
proxy.copyToAnalyzer(line, player)
}
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
ServerComponentTracker.add(host.world, node.address, this)
}
}
override def onDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
ServerComponentTracker.remove(host.world, this)
}
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (SideTracker.isClient) {
if (!Strings.isNullOrEmpty(proxy.nodeAddress)) return // Only load once.
proxy.nodeAddress = nbt.getCompoundTag("node").getString("address")
TextBuffer.registerClientBuffer(this)
}
else {
if (nbt.hasKey("buffer")) {
data.load(nbt.getCompoundTag("buffer"))
}
else if (!Strings.isNullOrEmpty(node.address)) {
data.load(SaveHandler.loadNBT(nbt, node.address + "_buffer"))
}
}
if (nbt.hasKey(Settings.namespace + "isOn")) {
isDisplaying = nbt.getBoolean(Settings.namespace + "isOn")
}
if (nbt.hasKey(Settings.namespace + "hasPower")) {
hasPower = nbt.getBoolean(Settings.namespace + "hasPower")
}
if (nbt.hasKey(Settings.namespace + "maxWidth") && nbt.hasKey(Settings.namespace + "maxHeight")) {
val maxWidth = nbt.getInteger(Settings.namespace + "maxWidth")
val maxHeight = nbt.getInteger(Settings.namespace + "maxHeight")
maxResolution = (maxWidth, maxHeight)
}
precisionMode = nbt.getBoolean(Settings.namespace + "precise")
if (nbt.hasKey(Settings.namespace + "viewportWidth")) {
val vpw = nbt.getInteger(Settings.namespace + "viewportWidth")
val vph = nbt.getInteger(Settings.namespace + "viewportHeight")
viewport = (vpw min data.width max 1, vph min data.height max 1)
} else {
viewport = data.size
}
}
// Null check for Waila (and other mods that may call this client side).
override def save(nbt: NBTTagCompound) = if (node != null) {
super.save(nbt)
// Happy thread synchronization hack! Here's the problem: GPUs allow direct
// calls for modifying screens to give a more responsive experience. This
// causes the following problem: when saving, if the screen is saved first,
// then the executor runs in parallel and changes the screen *before* the
// server thread begins saving that computer, the saved computer will think
// it changed the screen, although the saved screen wasn't. To avoid that we
// wait for all computers the screen is connected to to finish their current
// execution and pausing them (which will make them resume in the next tick
// when their update() runs).
if (node.network != null) {
for (node <- node.network.nodes) node.host match {
case computer: tileentity.traits.Computer if !computer.machine.isPaused =>
computer.machine.pause(0.1)
case _ =>
}
}
SaveHandler.scheduleSave(host, nbt, node.address + "_buffer", data.save _)
nbt.setBoolean(Settings.namespace + "isOn", isDisplaying)
nbt.setBoolean(Settings.namespace + "hasPower", hasPower)
nbt.setInteger(Settings.namespace + "maxWidth", maxResolution._1)
nbt.setInteger(Settings.namespace + "maxHeight", maxResolution._2)
nbt.setBoolean(Settings.namespace + "precise", precisionMode)
nbt.setInteger(Settings.namespace + "viewportWidth", viewport._1)
nbt.setInteger(Settings.namespace + "viewportHeight", viewport._2)
}
}
object TextBuffer {
var clientBuffers = mutable.ListBuffer.empty[TextBuffer]
@SubscribeEvent
def onChunkUnload(e: ChunkEvent.Unload) {
val chunk = e.getChunk
clientBuffers = clientBuffers.filter(t => {
val blockPos = BlockPosition(t.host)
val keep = t.host.world != e.world || !chunk.isAtLocation(blockPos.x >> 4, blockPos.z >> 4)
if (!keep) {
ClientComponentTracker.remove(t.host.world, t)
}
keep
})
}
@SubscribeEvent
def onWorldUnload(e: WorldEvent.Unload) {
clientBuffers = clientBuffers.filter(t => {
val keep = t.host.world != e.world
if (!keep) {
ClientComponentTracker.remove(t.host.world, t)
}
keep
})
}
def registerClientBuffer(t: TextBuffer) {
ClientPacketSender.sendTextBufferInit(t.proxy.nodeAddress)
ClientComponentTracker.add(t.host.world, t.proxy.nodeAddress, t)
clientBuffers += t
}
abstract class Proxy {
def owner: TextBuffer
var dirty = false
var nodeAddress = ""
def markDirty() {
dirty = true
}
def render() = false
def onBufferColorChange(): Unit
def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {
owner.relativeLitArea = -1
}
def onBufferDepthChange(depth: api.internal.TextBuffer.ColorDepth): Unit
def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) {
owner.relativeLitArea = -1
}
def onBufferPaletteChange(index: Int): Unit
def onBufferResolutionChange(w: Int, h: Int) {
owner.relativeLitArea = -1
}
def onBufferViewportResolutionChange(w: Int, h: Int) {
owner.relativeLitArea = -1
}
def onBufferMaxResolutionChange(w: Int, h: Int) {
}
def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) {
owner.relativeLitArea = -1
}
def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
owner.relativeLitArea = -1
}
def onBufferRawSetBackground(col: Int, row: Int, color: Array[Array[Int]]) {
owner.relativeLitArea = -1
}
def onBufferRawSetForeground(col: Int, row: Int, color: Array[Array[Int]]) {
owner.relativeLitArea = -1
}
def keyDown(character: Char, code: Int, player: EntityPlayer): Unit
def keyUp(character: Char, code: Int, player: EntityPlayer): Unit
def clipboard(value: String, player: EntityPlayer): Unit
def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit
def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit
def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit
def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer): Unit
def copyToAnalyzer(line: Int, player: EntityPlayer): Unit
}
class ClientProxy(val owner: TextBuffer) extends Proxy {
val renderer = new TextBufferRenderData {
override def dirty = ClientProxy.this.dirty
override def dirty_=(value: Boolean) = ClientProxy.this.dirty = value
override def data = owner.data
override def viewport: (Int, Int) = owner.viewport
}
override def render() = {
val wasDirty = dirty
TextBufferRenderCache.render(renderer)
wasDirty
}
override def onBufferColorChange() {
markDirty()
}
override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {
super.onBufferCopy(col, row, w, h, tx, ty)
markDirty()
}
override def onBufferDepthChange(depth: api.internal.TextBuffer.ColorDepth) {
markDirty()
}
override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) {
super.onBufferFill(col, row, w, h, c)
markDirty()
}
override def onBufferPaletteChange(index: Int) {
markDirty()
}
override def onBufferResolutionChange(w: Int, h: Int) {
super.onBufferResolutionChange(w, h)
markDirty()
}
override def onBufferViewportResolutionChange(w: Int, h: Int) {
super.onBufferViewportResolutionChange(w, h)
markDirty()
}
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) {
super.onBufferSet(col, row, s, vertical)
dirty = true
}
override def keyDown(character: Char, code: Int, player: EntityPlayer) {
debug(s"{type = keyDown, char = $character, code = $code}")
ClientPacketSender.sendKeyDown(nodeAddress, character, code)
}
override def keyUp(character: Char, code: Int, player: EntityPlayer) {
debug(s"{type = keyUp, char = $character, code = $code}")
ClientPacketSender.sendKeyUp(nodeAddress, character, code)
}
override def clipboard(value: String, player: EntityPlayer) {
debug(s"{type = clipboard}")
ClientPacketSender.sendClipboard(nodeAddress, value)
}
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) {
debug(s"{type = mouseDown, x = $x, y = $y, button = $button}")
ClientPacketSender.sendMouseClick(nodeAddress, x, y, drag = false, button)
}
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer) {
debug(s"{type = mouseDrag, x = $x, y = $y, button = $button}")
ClientPacketSender.sendMouseClick(nodeAddress, x, y, drag = true, button)
}
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer) {
debug(s"{type = mouseUp, x = $x, y = $y, button = $button}")
ClientPacketSender.sendMouseUp(nodeAddress, x, y, button)
}
override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer) {
debug(s"{type = mouseScroll, x = $x, y = $y, delta = $delta}")
ClientPacketSender.sendMouseScroll(nodeAddress, x, y, delta)
}
override def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = {
ClientPacketSender.sendCopyToAnalyzer(nodeAddress, line)
}
private lazy val Debugger = api.Items.get(Constants.ItemName.Debugger)
private def debug(message: String) {
if (Minecraft.getMinecraft != null && Minecraft.getMinecraft.thePlayer != null && api.Items.get(Minecraft.getMinecraft.thePlayer.getHeldItem) == Debugger) {
OpenComputers.log.info(s"[NETWORK DEBUGGER] Sending packet to node $nodeAddress: " + message)
}
}
}
class ServerProxy(val owner: TextBuffer) extends Proxy {
override def onBufferColorChange() {
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferColorChange(owner.pendingCommands, owner.data.foreground, owner.data.background))
}
override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {
super.onBufferCopy(col, row, w, h, tx, ty)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferCopy(owner.pendingCommands, col, row, w, h, tx, ty))
}
override def onBufferDepthChange(depth: api.internal.TextBuffer.ColorDepth) {
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferDepthChange(owner.pendingCommands, depth))
}
override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char) {
super.onBufferFill(col, row, w, h, c)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferFill(owner.pendingCommands, col, row, w, h, c))
}
override def onBufferPaletteChange(index: Int) {
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferPaletteChange(owner.pendingCommands, index, owner.getPaletteColor(index)))
}
override def onBufferResolutionChange(w: Int, h: Int) {
super.onBufferResolutionChange(w, h)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferResolutionChange(owner.pendingCommands, w, h))
}
override def onBufferViewportResolutionChange(w: Int, h: Int) {
super.onBufferViewportResolutionChange(w, h)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferViewportResolutionChange(owner.pendingCommands, w, h))
}
override def onBufferMaxResolutionChange(w: Int, h: Int) {
if (owner.node.network != null) {
super.onBufferMaxResolutionChange(w, h)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferMaxResolutionChange(owner.pendingCommands, w, h))
}
}
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean) {
super.onBufferSet(col, row, s, vertical)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferSet(owner.pendingCommands, col, row, s, vertical))
}
override def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
super.onBufferRawSetText(col, row, text)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferRawSetText(owner.pendingCommands, col, row, text))
}
override def onBufferRawSetBackground(col: Int, row: Int, color: Array[Array[Int]]) {
super.onBufferRawSetBackground(col, row, color)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferRawSetBackground(owner.pendingCommands, col, row, color))
}
override def onBufferRawSetForeground(col: Int, row: Int, color: Array[Array[Int]]) {
super.onBufferRawSetForeground(col, row, color)
owner.host.markChanged()
owner.synchronized(ServerPacketSender.appendTextBufferRawSetForeground(owner.pendingCommands, col, row, color))
}
override def keyDown(character: Char, code: Int, player: EntityPlayer) {
sendToKeyboards("keyboard.keyDown", player, Char.box(character), Int.box(code))
}
override def keyUp(character: Char, code: Int, player: EntityPlayer) {
sendToKeyboards("keyboard.keyUp", player, Char.box(character), Int.box(code))
}
override def clipboard(value: String, player: EntityPlayer) {
sendToKeyboards("keyboard.clipboard", player, value)
}
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) {
sendMouseEvent(player, "touch", x, y, button)
}
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer) {
sendMouseEvent(player, "drag", x, y, button)
}
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer) {
sendMouseEvent(player, "drop", x, y, button)
}
override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer) {
sendMouseEvent(player, "scroll", x, y, delta)
}
override def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = {
val stack = player.getHeldItem
if (stack != null) {
if (!stack.hasTagCompound) {
stack.setTagCompound(new NBTTagCompound())
}
stack.getTagCompound.removeTag(Settings.namespace + "clipboard")
if (line >= 0 && line < owner.getViewportHeight) {
val text = new String(owner.data.buffer(line)).trim
if (!Strings.isNullOrEmpty(text)) {
stack.getTagCompound.setString(Settings.namespace + "clipboard", text)
}
}
if (stack.getTagCompound.hasNoTags) {
stack.setTagCompound(null)
}
}
}
private def sendMouseEvent(player: EntityPlayer, name: String, x: Double, y: Double, data: Int) = {
val args = mutable.ArrayBuffer.empty[AnyRef]
args += player
args += name
if (owner.precisionMode) {
args += Double.box(x)
args += Double.box(y)
}
else {
args += Int.box(x.toInt + 1)
args += Int.box(y.toInt + 1)
}
args += Int.box(data)
if (Settings.get.inputUsername) {
args += player.getCommandSenderName
}
owner.node.sendToReachable("computer.checked_signal", args: _*)
}
private def sendToKeyboards(name: String, values: AnyRef*) {
owner.host match {
case screen: tileentity.Screen =>
screen.screens.foreach(_.node.sendToNeighbors(name, values: _*))
case _ =>
owner.node.sendToNeighbors(name, values: _*)
}
}
}
}