diff --git a/assets/opencomputers/lua/rom/bin/ls.lua b/assets/opencomputers/lua/rom/bin/ls.lua index 595f71e63..7a76c7213 100644 --- a/assets/opencomputers/lua/rom/bin/ls.lua +++ b/assets/opencomputers/lua/rom/bin/ls.lua @@ -13,7 +13,19 @@ for i = 1, #dirs do if not list then print(reason) else + local function setColor(c) + if component.gpu.getForeground() ~= c then + component.gpu.setForeground(c) + end + end for f in list do + if f:sub(-1) == "/" then + setColor(0x99CCFF) + elseif f:sub(-4) == ".lua" then + setColor(0x00FF00) + else + setColor(0xFFFFFF) + end if options.a or f:sub(1, 1) ~= "." then if options.l then print(f, fs.size(fs.concat(path, f))) @@ -22,6 +34,7 @@ for i = 1, #dirs do end end end + setColor(0xFFFFFF) if not options.l then print() end diff --git a/assets/opencomputers/lua/rom/bin/lua.lua b/assets/opencomputers/lua/rom/bin/lua.lua index 43dbb9acd..630cdd1c3 100644 --- a/assets/opencomputers/lua/rom/bin/lua.lua +++ b/assets/opencomputers/lua/rom/bin/lua.lua @@ -2,7 +2,9 @@ print("Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio") local history = {} local env = setmetatable({}, {__index=_ENV}) while term.isAvailable() do + local foreground = component.gpu.setForeground(0x00FF00) term.write("lua> ") + component.gpu.setForeground(foreground) local command = term.read(history) if command == nil then -- eof return diff --git a/assets/opencomputers/lua/rom/bin/sh.lua b/assets/opencomputers/lua/rom/bin/sh.lua index 5a727e652..889f7a4d1 100644 --- a/assets/opencomputers/lua/rom/bin/sh.lua +++ b/assets/opencomputers/lua/rom/bin/sh.lua @@ -16,7 +16,9 @@ while true do end end while term.isAvailable() do + local foreground = component.gpu.setForeground(0xFF0000) term.write("# ") + component.gpu.setForeground(foreground) local command = term.read(history) if not command then print("exit") diff --git a/assets/opencomputers/lua/rom/lib/gpu.lua b/assets/opencomputers/lua/rom/lib/gpu.lua index 2b196f0d8..d0fc1faaa 100644 --- a/assets/opencomputers/lua/rom/lib/gpu.lua +++ b/assets/opencomputers/lua/rom/lib/gpu.lua @@ -4,8 +4,6 @@ local function onComponentAvailable(_, componentType) then local gpu = component.primary("gpu") gpu.bind(component.primary("screen").address) - local maxX, maxY = gpu.maxResolution() - gpu.setResolution(maxX, maxY) end end diff --git a/assets/opencomputers/textures/font/chars.png b/assets/opencomputers/textures/font/chars.png index d6b81c9ef..fdda7e9d3 100644 Binary files a/assets/opencomputers/textures/font/chars.png and b/assets/opencomputers/textures/font/chars.png differ diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index c9ca3b027..8494c5a9e 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -1,6 +1,7 @@ package li.cil.oc import java.io.File +import li.cil.oc.util.PackedColor object Config { val resourceDomain = "opencomputers" @@ -10,6 +11,7 @@ object Config { // ----------------------------------------------------------------------- // val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50)) + val screenDepthsByTier = Array(PackedColor.Depth.OneBit, PackedColor.Depth.FourBit, PackedColor.Depth.EightBit) // ----------------------------------------------------------------------- // @@ -39,6 +41,7 @@ object Config { var maxScreenTextRenderDistance = 10.0 var screenTextFadeStartDistance = 8.0 + var textLinearFiltering = false // ----------------------------------------------------------------------- // @@ -97,6 +100,15 @@ object Config { "instantly disappear when moving away from the screen displaying it."). getDouble(screenTextFadeStartDistance) + textLinearFiltering = config.get("client", "textLinearFiltering", textLinearFiltering, "" + + "Whether to apply linear filtering for text displayed on screens when the\n" + + "screen has to be scaled down - i.e. the text is rendered at a resolution\n" + + "lower than their native one, e.g. when the GUI scale is less than one or\n" + + "when looking at a far away screen. This leads to smoother text for scaled\n" + + "down text but results in characters not perfectly connecting anymore (for\n" + + "example for box drawing characters. Look it up on Wikipedia.)"). + getBoolean(textLinearFiltering) + // --------------------------------------------------------------------- // config.getCategory("power"). diff --git a/li/cil/oc/client/PacketHandler.scala b/li/cil/oc/client/PacketHandler.scala index be2e84573..687399ef4 100644 --- a/li/cil/oc/client/PacketHandler.scala +++ b/li/cil/oc/client/PacketHandler.scala @@ -5,6 +5,7 @@ import li.cil.oc.common.PacketType import li.cil.oc.common.tileentity.{PowerDistributor, Computer, Rotatable, Screen} import li.cil.oc.common.{PacketHandler => CommonPacketHandler} import li.cil.oc.server.component.Redstone +import li.cil.oc.util.PackedColor import net.minecraft.client.gui.GuiScreen import net.minecraft.entity.player.EntityPlayer import net.minecraft.tileentity.TileEntity @@ -25,7 +26,9 @@ class PacketHandler extends CommonPacketHandler { case PacketType.RedstoneStateResponse => onRedstoneStateResponse(p) case PacketType.RotatableStateResponse => onRotatableStateResponse(p) case PacketType.ScreenBufferResponse => onScreenBufferResponse(p) + case PacketType.ScreenColorChange => onScreenColorChange(p) case PacketType.ScreenCopy => onScreenCopy(p) + case PacketType.ScreenDepthChange => onScreenDepthChange(p) case PacketType.ScreenFill => onScreenFill(p) case PacketType.ScreenResolutionChange => onScreenResolutionChange(p) case PacketType.ScreenSet => onScreenSet(p) @@ -74,12 +77,31 @@ class PacketHandler extends CommonPacketHandler { def onScreenBufferResponse(p: PacketParser) = p.readTileEntity[Screen]() match { case Some(t) => + val screen = t.instance val w = p.readInt() val h = p.readInt() - t.instance.resolution = (w, h) + screen.resolution = (w, h) p.readUTF.split('\n').zipWithIndex.foreach { - case (line, i) => t.instance.set(0, i, line) + case (line, i) => screen.set(0, i, line) } + screen.depth = PackedColor.Depth(p.readInt()) + screen.foreground = p.readInt() + screen.background = p.readInt() + for (row <- 0 until h) { + val rowColor = screen.colors(row) + for (col <- 0 until w) { + rowColor(col) = p.readShort() + } + } + case _ => // Invalid packet. + } + + def onScreenColorChange(p: PacketParser) = + p.readTileEntity[Screen]() match { + case Some(t) => { + t.instance.foreground = p.readInt() + t.instance.background = p.readInt() + } case _ => // Invalid packet. } @@ -97,6 +119,14 @@ class PacketHandler extends CommonPacketHandler { case _ => // Invalid packet. } + def onScreenDepthChange(p: PacketParser) = + p.readTileEntity[Screen]() match { + case Some(t) => { + t.instance.depth = PackedColor.Depth(p.readInt()) + } + case _ => // Invalid packet. + } + def onScreenFill(p: PacketParser) = p.readTileEntity[Screen]() match { case Some(t) => { diff --git a/li/cil/oc/client/gui/Screen.scala b/li/cil/oc/client/gui/Screen.scala index e2f28cb2c..f34ef5b72 100644 --- a/li/cil/oc/client/gui/Screen.scala +++ b/li/cil/oc/client/gui/Screen.scala @@ -4,6 +4,7 @@ import li.cil.oc.Config import li.cil.oc.client.PacketSender import li.cil.oc.client.renderer.MonospaceFontRenderer import li.cil.oc.common.tileentity +import li.cil.oc.util.PackedColor import net.minecraft.client.gui.{GuiScreen => MCGuiScreen} import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.Tessellator @@ -12,7 +13,6 @@ import net.minecraft.util.ResourceLocation import org.lwjgl.input.Keyboard import org.lwjgl.opengl.GL11 import scala.collection.mutable -import li.cil.oc.util.PackedColor /** * This GUI shows the buffer of a single screen. @@ -184,9 +184,11 @@ object Screen { GL11.glEndList() } - private[gui] def compileText(scale: Double, lines: Array[Array[Char]], colors:Array[Array[Int]], depth: PackedColor.Depth.Value) = + private[gui] def compileText(scale: Double, lines: Array[Array[Char]], colors: Array[Array[Short]], depth: PackedColor.Depth.Value) = if (textureManager.isDefined) { GL11.glNewList(displayLists.get + 1, GL11.GL_COMPILE) + GL11.glPushAttrib(GL11.GL_DEPTH_BUFFER_BIT) + GL11.glDepthMask(false) GL11.glTranslatef(margin + innerMargin, margin + innerMargin, 0) GL11.glScaled(scale, scale, 1) @@ -194,6 +196,7 @@ object Screen { case ((line, color), i) => MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, depth) } + GL11.glPopAttrib() GL11.glEndList() } diff --git a/li/cil/oc/client/renderer/MonospaceFontRenderer.scala b/li/cil/oc/client/renderer/MonospaceFontRenderer.scala index b2313c5a8..68dd38c1d 100644 --- a/li/cil/oc/client/renderer/MonospaceFontRenderer.scala +++ b/li/cil/oc/client/renderer/MonospaceFontRenderer.scala @@ -1,6 +1,6 @@ package li.cil.oc.client.renderer -import li.cil.oc.util.{RenderState, PackedColor} +import li.cil.oc.util.PackedColor import li.cil.oc.{OpenComputers, Config} import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.Tessellator @@ -10,7 +10,7 @@ import org.lwjgl.opengl.GL11 import scala.io.Source object MonospaceFontRenderer { - private val font = new ResourceLocation(Config.resourceDomain, "textures/font/chars.png") + val font = new ResourceLocation(Config.resourceDomain, "textures/font/chars.png") private val chars = Source.fromInputStream(MonospaceFontRenderer.getClass.getResourceAsStream("/assets/" + Config.resourceDomain + "/textures/font/chars.txt")).mkString @@ -21,7 +21,7 @@ object MonospaceFontRenderer { val (fontWidth, fontHeight) = (5, 9) - def drawString(x: Int, y: Int, value: Array[Char], color: Array[Int], depth: PackedColor.Depth.Value) = instance match { + def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], depth: PackedColor.Depth.Value) = instance match { case None => OpenComputers.log.warning("Trying to render string with uninitialized MonospaceFontRenderer.") case Some(renderer) => renderer.drawString(x, y, value, color, depth) } @@ -36,7 +36,9 @@ object MonospaceFontRenderer { private val (charWidth, charHeight) = (MonospaceFontRenderer.fontWidth * 2, MonospaceFontRenderer.fontHeight * 2) private val cols = 256 / charWidth private val uStep = charWidth / 256.0 - private val vStep = charHeight / 256.0 + private val uSize = uStep + private val vStep = (charHeight + 1) / 256.0 + private val vSize = charHeight / 256.0 // Set up the display lists. { @@ -49,9 +51,9 @@ object MonospaceFontRenderer { val v = y * vStep GL11.glNewList(charLists + index, GL11.GL_COMPILE) t.startDrawingQuads() - t.addVertexWithUV(0, charHeight, 0, u, v + vStep) - t.addVertexWithUV(charWidth, charHeight, 0, u + uStep, v + vStep) - t.addVertexWithUV(charWidth, 0, 0, u + uStep, v) + t.addVertexWithUV(0, charHeight, 0, u, v + vSize) + t.addVertexWithUV(charWidth, charHeight, 0, u + uSize, v + vSize) + t.addVertexWithUV(charWidth, 0, 0, u + uSize, v) t.addVertexWithUV(0, 0, 0, u, v) t.draw() GL11.glTranslatef(charWidth, 0, 0) @@ -63,35 +65,39 @@ object MonospaceFontRenderer { GL11.glEndList() } - def drawString(x: Int, y: Int, value: Array[Char], color: Array[Int], depth: PackedColor.Depth.Value) = { + def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], depth: PackedColor.Depth.Value) = { if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.") textureManager.bindTexture(MonospaceFontRenderer.font) GL11.glPushMatrix() - GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT) + GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_TEXTURE_BIT) GL11.glTranslatef(x, y, 0) GL11.glScalef(0.5f, 0.5f, 1) GL11.glDepthMask(false) - RenderState.makeItBlend() // Background first. We try to merge adjacent backgrounds of the same // color to reduce the number of quads we have to draw. var cbg = 0x000000 + var offset = 0 var width = 0 for (col <- color.map(PackedColor.unpackBackground(_, depth))) { if (col != cbg) { - draw(cbg, width) + draw(cbg, offset, width) cbg = col + offset += width width = 0 } width = width + 1 } - draw(cbg, width) + draw(cbg, offset, width) + + if (Config.textLinearFiltering) { + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR) + } // Foreground second. We only have to flush when the color changes, so // unless every char has a different color this should be quite efficient. - var cfg = 0x000000 - GL11.glColor3f(0, 0, 0) + var cfg = -1 for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, depth)))) { val index = 1 + chars.indexOf(ch) match { case -1 => chars.indexOf('?') @@ -102,9 +108,9 @@ object MonospaceFontRenderer { flush() cfg = col GL11.glColor3ub( - (cfg & 0xFF0000 >> 16).toByte, - (cfg & 0x00FF00 >> 8).toByte, - (cfg & 0x0000FF).toByte) + ((cfg & 0xFF0000) >> 16).toByte, + ((cfg & 0x00FF00) >> 8).toByte, + ((cfg & 0x0000FF) >> 0).toByte) } listBuffer.put(charLists + index) if (listBuffer.remaining == 0) @@ -116,18 +122,23 @@ object MonospaceFontRenderer { GL11.glPopMatrix() } - private def draw(color: Int, width: Int) = if (color != 0 && width > 0) { + private val bgu1 = 254.0 / 256.0 + private val bgu2 = 255.0 / 256.0 + private val bgv1 = 255.0 / 256.0 + private val bgv2 = 256.0 / 256.0 + + private def draw(color: Int, offset: Int, width: Int) = if (color != 0 && width > 0) { val t = Tessellator.instance t.startDrawingQuads() t.setColorOpaque_I(color) - t.addVertexWithUV(0, charHeight, 0, 0, vStep) - t.addVertexWithUV(charWidth, charHeight, 0, width * uStep, vStep) - t.addVertexWithUV(charWidth, 0, 0, width * uStep, 0) - t.addVertexWithUV(0, 0, 0, 0, 0) + t.addVertexWithUV(charWidth * offset, charHeight, 0, bgu1, bgv2) + t.addVertexWithUV(charWidth * (offset + width), charHeight, 0, bgu2, bgv2) + t.addVertexWithUV(charWidth * (offset + width), 0, 0, bgu2, bgv1) + t.addVertexWithUV(charWidth * offset, 0, 0, bgu1, bgv1) t.draw() } - private def flush() = { + private def flush() = if (listBuffer.position > 0) { listBuffer.flip() GL11.glCallLists(listBuffer) listBuffer.clear() diff --git a/li/cil/oc/common/PacketType.scala b/li/cil/oc/common/PacketType.scala index 66370604d..bc037068f 100644 --- a/li/cil/oc/common/PacketType.scala +++ b/li/cil/oc/common/PacketType.scala @@ -34,7 +34,9 @@ object PacketType extends Enumeration { val ScreenBufferResponse = Value("ScreenBufferResponse") /** These are sent from the server to the client for partial updates. */ + val ScreenColorChange = Value("ScreenColorChange") val ScreenCopy = Value("ScreenCopy") + val ScreenDepthChange = Value("ScreenDepthChange") val ScreenFill = Value("ScreenFill") val ScreenResolutionChange = Value("ScreenResolutionChange") val ScreenSet = Value("ScreenSet") diff --git a/li/cil/oc/common/component/Screen.scala b/li/cil/oc/common/component/Screen.scala index 661bfabb8..04aadf124 100644 --- a/li/cil/oc/common/component/Screen.scala +++ b/li/cil/oc/common/component/Screen.scala @@ -7,7 +7,7 @@ import li.cil.oc.util.{PackedColor, TextBuffer} import li.cil.oc.{Config, util, api} import net.minecraft.nbt.NBTTagCompound -class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) extends Persistable { +class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int), val maxDepth: PackedColor.Depth.Value) extends Persistable { private val buffer = new TextBuffer(maxResolution, PackedColor.Depth.OneBit) // ----------------------------------------------------------------------- // @@ -20,12 +20,48 @@ class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) exten def depth = buffer.depth + def depth_=(value: PackedColor.Depth.Value) = { + if (value > maxDepth) + throw new IllegalArgumentException("unsupported depth") + if (buffer.depth = value) { + owner.onScreenDepthChange(value) + true + } + else false + } + + def foreground = buffer.foreground + + def foreground_=(value: Int) = { + if (buffer.foreground != value) { + val result = buffer.foreground + buffer.foreground = value + owner.onScreenColorChange(foreground, background) + result + } + else value + } + + def background = buffer.background + + def background_=(value: Int) = { + if (buffer.background != value) { + val result = buffer.background + buffer.background = value + owner.onScreenColorChange(foreground, background) + result + } + else value + } + def resolution = buffer.size def resolution_=(value: (Int, Int)) = { val (w, h) = value val (mw, mh) = maxResolution - if (w <= mw && h <= mh && (buffer.size = value)) { + if (w < 1 || w > mw || h < 1 || h > mh) + throw new IllegalArgumentException("unsupported resolution") + if (buffer.size = value) { owner.onScreenResolutionChange(w, h) true } @@ -58,12 +94,12 @@ class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) exten // ----------------------------------------------------------------------- // override def load(nbt: NBTTagCompound) = { - buffer.readFromNBT(nbt.getCompoundTag("oc.screen")) + buffer.load(nbt.getCompoundTag("oc.screen")) } override def save(nbt: NBTTagCompound) = { val screenNbt = new NBTTagCompound - buffer.writeToNBT(screenNbt) + buffer.save(screenNbt) nbt.setCompoundTag("oc.screen", screenNbt) } @@ -81,7 +117,7 @@ object Screen { withConnector(Config.bufferScreen * (tier + 1)). create() - final val instance = new component.Screen(this, Config.screenResolutionsByTier(tier)) + final val instance = new component.Screen(this, Config.screenResolutionsByTier(tier), Config.screenDepthsByTier(tier)) protected def tier: Int @@ -101,8 +137,12 @@ object Screen { // ----------------------------------------------------------------------- // + def onScreenColorChange(foreground: Int, background: Int) {} + def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) {} + def onScreenDepthChange(depth: PackedColor.Depth.Value) {} + def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) {} def onScreenResolutionChange(w: Int, h: Int) { diff --git a/li/cil/oc/common/item/GraphicsCard.scala b/li/cil/oc/common/item/GraphicsCard.scala index 38e5938fe..f77b94b44 100644 --- a/li/cil/oc/common/item/GraphicsCard.scala +++ b/li/cil/oc/common/item/GraphicsCard.scala @@ -8,6 +8,8 @@ class GraphicsCard(val parent: Delegator, val tier: Int) extends Delegate { val maxResolution = Config.screenResolutionsByTier(tier) + val maxDepth = Config.screenDepthsByTier(tier) + override def registerIcons(iconRegister: IconRegister) { super.registerIcons(iconRegister) diff --git a/li/cil/oc/common/tileentity/Screen.scala b/li/cil/oc/common/tileentity/Screen.scala index 59ed6d4b2..14b2de87b 100644 --- a/li/cil/oc/common/tileentity/Screen.scala +++ b/li/cil/oc/common/tileentity/Screen.scala @@ -7,11 +7,12 @@ import li.cil.oc.client.gui import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.common.component.Screen.{Environment => ScreenEnvironment} import li.cil.oc.server.{PacketSender => ServerPacketSender} +import li.cil.oc.util.PackedColor +import net.minecraft.client.Minecraft import net.minecraft.nbt.NBTTagCompound import net.minecraft.util.AxisAlignedBB import net.minecraftforge.common.ForgeDirection import scala.collection.mutable -import net.minecraft.client.Minecraft class ScreenTier1 extends Screen { protected def tier = 0 @@ -225,6 +226,46 @@ abstract class Screen extends Rotatable with ScreenEnvironment { // ----------------------------------------------------------------------- // + override def onScreenColorChange(foreground: Int, background: Int) { + super.onScreenColorChange(foreground, background) + if (!worldObj.isRemote) { + worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) + ServerPacketSender.sendScreenColorChange(this, foreground, background) + } + } + + override def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = { + super.onScreenCopy(col, row, w, h, tx, ty) + if (worldObj.isRemote) { + currentGui.foreach(_.updateText()) + hasChanged = true + } + else { + worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) + ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty) + } + } + + override def onScreenDepthChange(depth: PackedColor.Depth.Value) { + super.onScreenDepthChange(depth) + if (!worldObj.isRemote) { + worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) + ServerPacketSender.sendScreenDepthChange(this, depth) + } + } + + override def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) = { + super.onScreenFill(col, row, w, h, c) + if (worldObj.isRemote) { + currentGui.foreach(_.updateText()) + hasChanged = true + } + else { + worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) + ServerPacketSender.sendScreenFill(this, col, row, w, h, c) + } + } + override def onScreenResolutionChange(w: Int, h: Int) = { super.onScreenResolutionChange(w, h) if (worldObj.isRemote) { @@ -248,28 +289,4 @@ abstract class Screen extends Rotatable with ScreenEnvironment { ServerPacketSender.sendScreenSet(this, col, row, s) } } - - override def onScreenFill(col: Int, row: Int, w: Int, h: Int, c: Char) = { - super.onScreenFill(col, row, w, h, c) - if (worldObj.isRemote) { - currentGui.foreach(_.updateText()) - hasChanged = true - } - else { - worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) - ServerPacketSender.sendScreenFill(this, col, row, w, h, c) - } - } - - override def onScreenCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) = { - super.onScreenCopy(col, row, w, h, tx, ty) - if (worldObj.isRemote) { - currentGui.foreach(_.updateText()) - hasChanged = true - } - else { - worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) - ServerPacketSender.sendScreenCopy(this, col, row, w, h, tx, ty) - } - } } diff --git a/li/cil/oc/server/PacketHandler.scala b/li/cil/oc/server/PacketHandler.scala index 95989ae6c..2bd329cc4 100644 --- a/li/cil/oc/server/PacketHandler.scala +++ b/li/cil/oc/server/PacketHandler.scala @@ -52,9 +52,7 @@ class PacketHandler extends CommonPacketHandler { def onScreenBufferRequest(p: PacketParser) = p.readTileEntity[tileentity.Screen]() match { - case Some(t) => - val (w, h) = t.instance.resolution - PacketSender.sendScreenBufferState(t, w, h, t.instance.text, Option(p.player)) + case Some(t) => PacketSender.sendScreenBufferState(t, Option(p.player)) case _ => // Invalid packet. } diff --git a/li/cil/oc/server/PacketSender.scala b/li/cil/oc/server/PacketSender.scala index 46072134a..b1ad27afa 100644 --- a/li/cil/oc/server/PacketSender.scala +++ b/li/cil/oc/server/PacketSender.scala @@ -3,8 +3,10 @@ package li.cil.oc.server import cpw.mods.fml.common.network.Player import li.cil.oc.common.PacketBuilder import li.cil.oc.common.PacketType +import li.cil.oc.common.component.Screen import li.cil.oc.common.tileentity.{PowerDistributor, Rotatable} import li.cil.oc.server.component.Redstone +import li.cil.oc.util.PackedColor import net.minecraft.tileentity.TileEntity import net.minecraftforge.common.ForgeDirection @@ -70,13 +72,20 @@ object PacketSender { } } - def sendScreenBufferState(t: TileEntity, w: Int, h: Int, text: String, player: Option[Player] = None) { + def sendScreenBufferState(t: TileEntity with Screen.Environment, player: Option[Player] = None) { val pb = new PacketBuilder(PacketType.ScreenBufferResponse) pb.writeTileEntity(t) + + val screen = t.instance + val (w, h) = screen.resolution pb.writeInt(w) pb.writeInt(h) - pb.writeUTF(text) + pb.writeUTF(screen.text) + pb.writeInt(screen.depth.id) + pb.writeInt(screen.foreground) + pb.writeInt(screen.background) + for (cs <- screen.colors) for (c <- cs) pb.writeShort(c) player match { case Some(p) => pb.sendToPlayer(p) @@ -84,6 +93,16 @@ object PacketSender { } } + def sendScreenColorChange(t: TileEntity, foreground: Int, background: Int) { + val pb = new PacketBuilder(PacketType.ScreenColorChange) + + pb.writeTileEntity(t) + pb.writeInt(foreground) + pb.writeInt(background) + + pb.sendToAllPlayers() + } + def sendScreenCopy(t: TileEntity, col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int) { val pb = new PacketBuilder(PacketType.ScreenCopy) @@ -98,6 +117,15 @@ object PacketSender { pb.sendToAllPlayers() } + def sendScreenDepthChange(t: TileEntity, value: PackedColor.Depth.Value) { + val pb = new PacketBuilder(PacketType.ScreenDepthChange) + + pb.writeTileEntity(t) + pb.writeInt(value.id) + + pb.sendToAllPlayers() + } + def sendScreenFill(t: TileEntity, col: Int, row: Int, w: Int, h: Int, c: Char) { val pb = new PacketBuilder(PacketType.ScreenFill) diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 66881bb70..7644fa463 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -3,10 +3,11 @@ package li.cil.oc.server.component import li.cil.oc.api import li.cil.oc.api.network._ import li.cil.oc.common.component.Screen +import li.cil.oc.util.PackedColor import net.minecraft.nbt.NBTTagCompound import scala.Some -class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { +class GraphicsCard(val maxResolution: (Int, Int), val maxDepth: PackedColor.Depth.Value) extends ManagedComponent { val node = api.Network.newNode(this, Visibility.Neighbors). withComponent("gpu"). create() @@ -15,7 +16,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { private var screenInstance: Option[Screen] = None - private def screen(f: (Screen) => Array[AnyRef]) = { + private def screen(f: (Screen) => Array[AnyRef]) = this.synchronized { if (screenInstance.isEmpty && screenAddress.isDefined) { Option(node.network.node(screenAddress.get)) match { case Some(node: Node) if node.host.isInstanceOf[Screen.Environment] => @@ -38,18 +39,73 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { // ----------------------------------------------------------------------- // @LuaCallback("bind") - def bind(context: Context, args: Arguments): Array[AnyRef] = { + def bind(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { val address = args.checkString(0) node.network.node(address) match { case null => Array(Unit, "invalid address") case node: Node if node.host.isInstanceOf[Screen.Environment] => screenAddress = Option(address) screenInstance = None - result(true) + screen(s => { + val (gmw, gmh) = maxResolution + val (smw, smh) = s.maxResolution + s.resolution = (gmw min smw, gmh min smh) + s.depth = PackedColor.Depth(maxDepth.id min s.maxDepth.id) + s.foreground = 0xFFFFFF + s.background = 0x000000 + result(true) + }) case _ => Array(Unit, "not a screen") } } + @LuaCallback(value = "getBackground", direct = true) + def getBackground(context: Context, args: Arguments): Array[AnyRef] = + screen(s => result(s.background)) + + @LuaCallback("setBackground") + def setBackground(context: Context, args: Arguments): Array[AnyRef] = { + val color = args.checkInteger(0) + screen(s => result(s.background = color)) + } + + @LuaCallback(value = "getForeground", direct = true) + def getForeground(context: Context, args: Arguments): Array[AnyRef] = + screen(s => result(s.foreground)) + + @LuaCallback("setForeground") + def setForeground(context: Context, args: Arguments): Array[AnyRef] = { + val color = args.checkInteger(0) + screen(s => result(s.foreground = color)) + } + + @LuaCallback(value = "getDepth", direct = true) + def getDepth(context: Context, args: Arguments): Array[AnyRef] = + screen(s => result(s.depth match { + case PackedColor.Depth.OneBit => 1 + case PackedColor.Depth.FourBit => 4 + case PackedColor.Depth.EightBit => 8 + })) + + @LuaCallback("setDepth") + def setDepth(context: Context, args: Arguments): Array[AnyRef] = { + val depth = args.checkInteger(0) + screen(s => result(s.depth = depth match { + case 1 => PackedColor.Depth.OneBit + case 4 if maxDepth >= PackedColor.Depth.FourBit => PackedColor.Depth.FourBit + case 8 if maxDepth >= PackedColor.Depth.EightBit => PackedColor.Depth.EightBit + case _ => throw new IllegalArgumentException("unsupported depth") + })) + } + + @LuaCallback(value = "maxDepth", direct = true) + def maxDepth(context: Context, args: Arguments): Array[AnyRef] = + screen(s => result(PackedColor.Depth(maxDepth.id min s.maxDepth.id) match { + case PackedColor.Depth.OneBit => 1 + case PackedColor.Depth.FourBit => 4 + case PackedColor.Depth.EightBit => 8 + })) + @LuaCallback(value = "getResolution", direct = true) def getResolution(context: Context, args: Arguments): Array[AnyRef] = screen(s => { @@ -62,10 +118,8 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { val w = args.checkInteger(0) val h = args.checkInteger(1) val (mw, mh) = maxResolution - if (w > 0 && h > 0 && w <= mw && h <= mh) - screen(s => result(s.resolution = (w, h))) - else - Array(Unit, "unsupported resolution") + if (w > 0 && h > 0 && w <= mw && h <= mh) screen(s => result(s.resolution = (w, h))) + else throw new IllegalArgumentException("unsupported resolution") } @LuaCallback(value = "maxResolution", direct = true) diff --git a/li/cil/oc/server/driver/GraphicsCard.scala b/li/cil/oc/server/driver/GraphicsCard.scala index 7cfad37ce..20e880688 100644 --- a/li/cil/oc/server/driver/GraphicsCard.scala +++ b/li/cil/oc/server/driver/GraphicsCard.scala @@ -12,7 +12,7 @@ object GraphicsCard extends Item { override def createEnvironment(item: ItemStack, container: AnyRef) = Items.multi.subItem(item) match { case Some(gpu: common.item.GraphicsCard) => - new component.GraphicsCard(gpu.maxResolution) + new component.GraphicsCard(gpu.maxResolution, gpu.maxDepth) case _ => null } diff --git a/li/cil/oc/util/PackedColor.scala b/li/cil/oc/util/PackedColor.scala index 93d92c418..45f419bf9 100644 --- a/li/cil/oc/util/PackedColor.scala +++ b/li/cil/oc/util/PackedColor.scala @@ -3,7 +3,7 @@ package li.cil.oc.util object PackedColor { object Depth extends Enumeration { - val OneBit, EightBit, SixteenBit = Value + val OneBit, FourBit, EightBit = Value } private val rMask32 = 0xFF0000 @@ -13,88 +13,65 @@ object PackedColor { private val gShift32 = 8 private val bShift32 = 0 - // 7 6 5 4 3 2 1 0 - // r r r g g g b b : 3.3.2 - private val rMask8 = Integer.parseInt("11100000", 2) - private val gMask8 = Integer.parseInt("00011100", 2) - private val bMask8 = Integer.parseInt("00000011", 2) - private val rShift8 = 5 - private val gShift8 = 2 - private val bShift8 = 0 - private val rScale8 = 255.0 / 0x7 - private val gScale8 = 255.0 / 0x7 - private val bScale8 = 255.0 / 0x3 + private abstract class ColorFormat { + def inflate(value: Int): Int - // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - // r r r r r g g g g g g b b b b b : 5.6.5 - private val rMask16 = Integer.parseInt("1111100000000000", 2) - private val gMask16 = Integer.parseInt("0000011111100000", 2) - private val bMask16 = Integer.parseInt("0000000000011111", 2) - private val rShift16 = 11 - private val gShift16 = 5 - private val bShift16 = 0 - private val rScale16 = 255.0 / 0x1F - private val gScale16 = 255.0 / 0x3F - private val bScale16 = 255.0 / 0x1F - - private def extractFrom1Bit(c: Short) = if (c == 0) 0x000000 else 0xFFFFFF - - private def compressTo1Bit(c: Int) = (if (c == 0) 0 else 1).toShort - - private def extractFrom8Bit(c: Short) = { - val r = ((((c & rMask8) >>> rShift8) * rScale8).toInt << rShift32) & rMask32 - val g = ((((c & gMask8) >>> gShift8) * gScale8).toInt << gShift32) & gMask32 - val b = ((((c & bMask8) >>> bShift8) * bScale8).toInt << bShift32) & bMask32 - r | g | b + def deflate(value: Int): Int } - private def compressTo8Bit(c: Int) = { - val r = ((((c & rMask32) >>> rShift32) / rScale8).toInt << rShift8) & rMask8 - val g = ((((c & gMask32) >>> gShift32) / gScale8).toInt << gShift8) & gMask8 - val b = ((((c & bMask32) >>> bShift32) / bScale8).toInt << bShift8) & bMask8 - (r | g | b).toShort + private class SingleBitFormat extends ColorFormat { + def inflate(value: Int) = if (value == 0) 0x000000 else 0xFFFFFF + + def deflate(value: Int) = if (value == 0) 0 else 1 } - private def extractFrom16Bit(c: Short) = { - val r = ((((c & rMask16) >>> rShift16) * rScale16).toInt << rShift32) & rMask32 - val g = ((((c & gMask16) >>> gShift16) * gScale16).toInt << gShift32) & gMask32 - val b = ((((c & bMask16) >>> bShift16) * bScale16).toInt << bShift32) & bMask32 - r | g | b - } + private class MultiBitFormat(rBits: Int, gBits: Int, bBits: Int) extends ColorFormat { + def mask(nBits: Int) = 0xFFFFFFFF >>> (32 - nBits) - private def compressTo16Bit(c: Int) = { - val r = ((((c & rMask32) >>> rShift32) / rScale16).toInt << rShift16) & rMask16 - val g = ((((c & gMask32) >>> gShift32) / gScale16).toInt << gShift16) & gMask16 - val b = ((((c & bMask32) >>> bShift32) / bScale16).toInt << bShift16) & bMask16 - (r | g | b).toShort - } + private val bShift = 0 + private val gShift = bBits + private val rShift = gShift + gBits - // Colors are packed: 0xFFFFBBBB (F = foreground, B = background) - private val fgShift = 16 - private val bgShift = 0 + private val bMask = mask(bBits) << bShift + private val gMask = mask(gBits) << gShift + private val rMask = mask(rBits) << rShift - def pack(foreground: Int, background: Int, depth: Depth.Value) = - depth match { - case Depth.OneBit => (compressTo1Bit(foreground) << fgShift) | (compressTo1Bit(background) << bgShift) - case Depth.EightBit => (compressTo8Bit(foreground) << fgShift) | (compressTo8Bit(background) << bgShift) - case Depth.SixteenBit => (compressTo16Bit(foreground) << fgShift) | (compressTo16Bit(background) << bgShift) + private val bScale = 255.0 / ((1 << bBits) - 1) + private val gScale = 255.0 / ((1 << gBits) - 1) + private val rScale = 255.0 / ((1 << rBits) - 1) + + def inflate(value: Int) = { + val r = ((((value & rMask) >>> rShift) * rScale).toInt << rShift32) & rMask32 + val g = ((((value & gMask) >>> gShift) * gScale).toInt << gShift32) & gMask32 + val b = ((((value & bMask) >>> bShift) * bScale).toInt << bShift32) & bMask32 + r | g | b } - def unpackForeground(color: Int, depth: Depth.Value) = { - val c = (color >>> fgShift).toShort - depth match { - case Depth.OneBit => extractFrom1Bit(c) - case Depth.EightBit => extractFrom8Bit(c) - case Depth.SixteenBit => extractFrom16Bit(c) + def deflate(value: Int) = { + val r = ((((value & rMask32) >>> rShift32) / rScale).toInt << rShift) & rMask + val g = ((((value & gMask32) >>> gShift32) / gScale).toInt << gShift) & gMask + val b = ((((value & bMask32) >>> bShift32) / bScale).toInt << bShift) & bMask + r | g | b } } - def unpackBackground(color: Int, depth: Depth.Value) = { - val c = (color >>> bgShift).toShort - depth match { - case Depth.OneBit => extractFrom1Bit(c) - case Depth.EightBit => extractFrom8Bit(c) - case Depth.SixteenBit => extractFrom16Bit(c) - } + private val formats = Map( + Depth.OneBit -> new SingleBitFormat(), + Depth.FourBit -> new MultiBitFormat(1, 2, 1), + Depth.EightBit -> new MultiBitFormat(3, 3, 2)) + + // Colors are packed: 0xFFBB (F = foreground, B = background) + private val fgShift = 8 + private val bgMask = 0x000000FF + + def pack(foreground: Int, background: Int, depth: Depth.Value) = { + val format = formats(depth) + ((format.deflate(foreground) << fgShift) | format.deflate(background)).toShort } + + def unpackForeground(color: Short, depth: Depth.Value) = + formats(depth).inflate((color & 0xFFFF) >>> fgShift) + + def unpackBackground(color: Short, depth: Depth.Value) = + formats(depth).inflate(color & bgMask) } diff --git a/li/cil/oc/util/RenderState.scala b/li/cil/oc/util/RenderState.scala index fdff8a8fe..7b982b9c4 100644 --- a/li/cil/oc/util/RenderState.scala +++ b/li/cil/oc/util/RenderState.scala @@ -24,7 +24,7 @@ object RenderState { def makeItBlend() { GL11.glEnable(GL11.GL_BLEND) - GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_COLOR) + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA) GL11.glDepthFunc(GL11.GL_LEQUAL) } diff --git a/li/cil/oc/util/TextBuffer.scala b/li/cil/oc/util/TextBuffer.scala index d09a6b824..f06aa5565 100644 --- a/li/cil/oc/util/TextBuffer.scala +++ b/li/cil/oc/util/TextBuffer.scala @@ -1,6 +1,6 @@ package li.cil.oc.util -import net.minecraft.nbt.{NBTTagIntArray, NBTTagCompound, NBTTagList, NBTTagString} +import net.minecraft.nbt._ /** * This stores chars in a 2D-Array and provides some manipulation functions. @@ -10,27 +10,49 @@ import net.minecraft.nbt.{NBTTagIntArray, NBTTagCompound, NBTTagList, NBTTagStri * relatively fast updates, given a smart algorithm (using copy()/fill() * instead of set()ing everything). */ -class TextBuffer(var width: Int, var height: Int, val depth: PackedColor.Depth.Value) { +class TextBuffer(var width: Int, var height: Int, initialDepth: PackedColor.Depth.Value) extends Persistable { def this(size: (Int, Int), depth: PackedColor.Depth.Value) = this(size._1, size._2, depth) + private var depth_ = initialDepth + private var foreground_ = 0xFFFFFF private var background_ = 0x000000 - private var packed = PackedColor.pack(foreground_, background_, depth) + private var packed = PackedColor.pack(foreground_, background_, depth_) def foreground = foreground_ def foreground_=(value: Int) = { foreground_ = value - packed = PackedColor.pack(foreground_, background_, depth) + packed = PackedColor.pack(foreground_, background_, depth_) } def background = background_ def background_=(value: Int) = { background_ = value - packed = PackedColor.pack(foreground_, background_, depth) + packed = PackedColor.pack(foreground_, background_, depth_) + } + + def depth = depth_ + + def depth_=(value: PackedColor.Depth.Value) = { + if (depth != value) { + for (row <- 0 until height) { + val rowColor = color(row) + for (col <- 0 until width) { + val packed = rowColor(col) + val fg = PackedColor.unpackForeground(packed, depth_) + val bg = PackedColor.unpackBackground(packed, depth_) + rowColor(col) = PackedColor.pack(fg, bg, value) + } + } + depth_ = value + packed = PackedColor.pack(foreground_, background_, depth_) + true + } + else false } var color = Array.fill(height, width)(packed) @@ -143,33 +165,50 @@ class TextBuffer(var width: Int, var height: Int, val depth: PackedColor.Depth.V changed } - def readFromNBT(nbt: NBTTagCompound): Unit = { + override def load(nbt: NBTTagCompound): Unit = { val w = nbt.getInteger("width") val h = nbt.getInteger("height") size = (w, h) + val b = nbt.getTagList("buffer") for (i <- 0 until (h min b.tagCount)) { val line = b.tagAt(i).asInstanceOf[NBTTagString].data set(0, i, line) } + + depth_ = PackedColor.Depth(nbt.getInteger("depth")) + foreground = nbt.getInteger("foreground") + background = nbt.getInteger("background") + val c = nbt.getTagList("color") - for (i <- 0 until (h min c.tagCount)) { - val line = c.tagAt(i).asInstanceOf[NBTTagIntArray].intArray - Array.copy(line, 0, color(i), 0, line.length min width) + for (i <- 0 until h) { + val rowColor = color(i) + for (j <- 0 until w) { + rowColor(j) = c.tagAt(j + i * w).asInstanceOf[NBTTagShort].data + } } } - def writeToNBT(nbt: NBTTagCompound): Unit = { + override def save(nbt: NBTTagCompound): Unit = { nbt.setInteger("width", width) nbt.setInteger("height", height) + val b = new NBTTagList() for (i <- 0 until height) { b.appendTag(new NBTTagString(null, String.valueOf(buffer(i)))) } nbt.setTag("buffer", b) + + nbt.setInteger("depth", depth_.id) + nbt.setInteger("foreground", foreground_) + nbt.setInteger("background", background_) + val c = new NBTTagList() for (i <- 0 until height) { - c.appendTag(new NBTTagIntArray(null, color(i))) + val rowColor = color(i) + for (j <- 0 until width) { + c.appendTag(new NBTTagShort(null, rowColor(j))) + } } nbt.setTag("color", c) }