diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 04ffd26c0..3781fcaf3 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -244,16 +244,6 @@ opencomputers { 1024 ] - # Video ram can be allocated on a gpu. The amount of vram you can allocate - # is equal to the width*height of the max resolution of the gpu multiplied - # by the "vramSize" for that tier. For example, a T2 gpu can have 80*25*2 of - # text buffer space allocated - vramSizes: [ - 1, - 2, - 3 - ] - # This setting allows you to fine-tune how RAM sizes are scaled internally # on 64 Bit machines (i.e. when the Minecraft server runs in a 64 Bit VM). # Why is this even necessary? Because objects consume more memory in a 64 @@ -1596,4 +1586,20 @@ opencomputers { # whitelist and the blacklist, the blacklist will win. dimWhitelist: [] } + + # Graphics Card Component Settings + gpu { + # Video ram can be allocated on a gpu. The amount of vram you can allocate + # is equal to the width*height of the max resolution of the gpu multiplied + # by the "vramSize" for that tier. For example, a T2 gpu can have 80*25*2 of + # text buffer space allocated + vramSizes: [ 1, 2, 3 ] + + # This setting assigns the budget call cost to invoke bitblt to write vram + # to a screen. Video ram can bitblit to a screen which can cause real life + # network laod the defaults settings put bitblit network impact close to gpu.set + # Increase these values to throttle bitblt more. The cost tier N is bitbltCost * 2^(tier) + # default is .5, which gives: .5, 1, 4 + bitbltCost: 0.5 + } } diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 462712c71..e4b0e569a 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -228,6 +228,7 @@ oc:gui.Rack.None=None oc:gui.Rack.Right=Right oc:gui.Rack.Enabled=Enabled oc:gui.Rack.Disabled=Disabled +oc:gui.Rack.RelayModeTooltip=Relay Mode oc:gui.Rack.Top=Top oc:gui.Switch.PacketsPerCycle=Packets / cycle oc:gui.Switch.QueueSize=Queue size @@ -380,6 +381,7 @@ oc:tooltip.upgradesolargenerator=Can be used to generate energy from sunlight on oc:tooltip.upgradetank=This upgrade provides a tank for fluid storage for robots and drones. Without one of these, they will not be able to store fluids internally. oc:tooltip.upgradetankcontroller=This upgrade allows robots and drones more control in how they interact with external tanks, and allows them to transfer fluids into and out of fluid tank items in their inventory. oc:tooltip.upgradetractorbeam=Equips a device with extremely advanced technology, nicknamed the "Item Magnet". Allows the device to pick up items anywhere within 3 blocks of its location. +oc:tooltip.upgradetrading=Allows robots and drones to trade with villagers. oc:tooltip.waypoint=Provides a point of reference to devices with a navigation upgrade. oc:tooltip.wirelessnetworkcard=Allows wireless sending of network messages in addition to normal ones. You can adjust the §fsignal strength§7 to control how far messages are sent. Higher signal strength results in higher energy consumption. oc:tooltip.worldsensorcard=Allows reading out information about the world, such as its gravity and whether it has a breathable atmosphere. Use results at own risk. The manufacturer takes no responsibility for bodily or material harm caused by decisions made upon the cards' outputs. We have lawyers. And money. Don't even try. diff --git a/src/main/resources/assets/opencomputers/loot/openos/.prop b/src/main/resources/assets/opencomputers/loot/openos/.prop index 5cf0ed07b..3425482c1 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/.prop +++ b/src/main/resources/assets/opencomputers/loot/openos/.prop @@ -1 +1 @@ -{label = "OpenOS", reboot=true, setlabel=true, setboot=true} +{label = "OpenOS", reboot=true, setlabel=true, setboot=true, noclobber={"etc/rc.cfg","home/.shrc"}} diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua index d173e219a..3341709a6 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua @@ -30,7 +30,7 @@ options = P = options.P, v = options.v, x = options.x, - skip = options.skip, + skip = {options.skip}, } return transfer.batch(args, options) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua index f8410e98b..d3a39b476 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua @@ -1,6 +1,4 @@ local computer = require("computer") -local shell = require("shell") - local options do @@ -12,21 +10,23 @@ do options = basic(...) end -if not options then return end +if not options then + return +end if computer.freeMemory() < 50000 then print("Low memory, collecting garbage") - for i=1,20 do os.sleep(0) end + for i = 1, 20 do + os.sleep(0) + end end -local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G) -assert(cp, reason) - -local ok, ec = pcall(cp, table.unpack(options.cp_args)) -assert(ok, ec) - -if ec ~= nil and ec ~= 0 then - return ec +local transfer = require("tools/transfer") +for _, inst in ipairs(options.cp_args) do + local ec = transfer.batch(table.unpack(inst)) + if ec ~= nil and ec ~= 0 then + return ec + end end print("Installation complete!") @@ -44,7 +44,7 @@ end if options.reboot then io.write("Reboot now? [Y/n] ") - if ((io.read() or "n").."y"):match("^%s*[Yy]") then + if ((io.read() or "n") .. "y"):match("^%s*[Yy]") then print("\nRebooting now!\n") computer.shutdown(true) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/mv.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/mv.lua index 9567d6f64..d5f0b4796 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/mv.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/mv.lua @@ -24,7 +24,7 @@ options = i = options.i, v = options.v, n = options.n, -- no clobber - skip = options.skip, + skip = {options.skip}, P = true, -- move operations always preserve r = true, -- move is allowed to move entire dirs x = true, -- cannot move mount points diff --git a/src/main/resources/assets/opencomputers/loot/openos/etc/motd b/src/main/resources/assets/opencomputers/loot/openos/etc/motd index 0c73a8250..2d07a9b1b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/etc/motd +++ b/src/main/resources/assets/opencomputers/loot/openos/etc/motd @@ -32,3 +32,7 @@ for _,line in ipairs(lines) do io.write(borders[2][1], " ", line, (" "):rep(maxLine - #line + 1), borders[2][3], " \n") end io.write(borders[3][1] .. string.rep(borders[3][2], maxLine + 2) .. borders[3][3] .. "\n") + +if require("filesystem").get("home").isReadOnly() then + io.write("\27[33mNote: Your home directory is readonly. Run `install` and reboot.\27[m\n") +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua b/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua index 52e054734..6c54cb4d1 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua @@ -20,7 +20,6 @@ shell.setAlias("cls", "clear") shell.setAlias("rs", "redstone") shell.setAlias("view", "edit -r") shell.setAlias("help", "man") -shell.setAlias("cp", "cp -i") shell.setAlias("l", "ls -lhp") shell.setAlias("..", "cd ..") shell.setAlias("df", "df -h") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua index 48064960d..ce42c8b76 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua @@ -27,7 +27,7 @@ local utils local rootfs = fs.get("/") if not rootfs then - io.stderr:write("no root filesystem, aborting\n"); + io.stderr:write("no root filesystem, aborting\n") os.exit(1) end @@ -86,11 +86,13 @@ for dev, path in pairs(devices) do io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n") os.exit(1) end - elseif specified or - not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source - not target_filter and - address ~= tmpAddress then - table.insert(targets, {dev=dev, path=install_path, specified=specified}) + elseif + specified or + not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source + not target_filter and + address ~= tmpAddress + then + table.insert(targets, {dev = dev, path = install_path, specified = specified}) end end @@ -105,11 +107,11 @@ for dev, path in pairs(devices) do local install_path = dev == source_filter_dev and options.from or path local specified = source_filter and address:find(source_filter, 1, true) == 1 - if fs.list(install_path)() - and (specified or - not source_filter and - address ~= tmpAddress and - not (address == rootfs.address and not rootfs.isReadOnly())) then + if + fs.list(install_path)() and + (specified or + not source_filter and address ~= tmpAddress and not (address == rootfs.address and not rootfs.isReadOnly())) + then local prop = {} local prop_path = install_path .. "/.prop" local prop_file = fs.open(prop_path) @@ -125,7 +127,7 @@ for dev, path in pairs(devices) do end if not prop.ignore then if not label or label:lower() == (prop.label or dev.getLabel() or ""):lower() then - table.insert(sources, {dev=dev, path=install_path, prop=prop, specified=specified}) + table.insert(sources, {dev = dev, path = install_path, prop = prop, specified = specified}) end end end @@ -137,23 +139,24 @@ if #sources ~= 1 then utils = loadfile(utils_path, "bt", _G) source = utils("select", "sources", options, sources) end -if not source then return end +if not source then + return +end -options = -{ - from = source.path .. '/', - fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""), - root = fs.canonical(options.root or options.toDir or source.prop.root or ""), - update = options.update or options.u, - label = source.prop.label or label, +options = { + from = source.path .. "/", + fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""), + root = fs.canonical(options.root or options.toDir or source.prop.root or ""), + update = options.update or options.u, + label = source.prop.label or label, setlabel = not (options.nosetlabel or options.nolabelset) and source.prop.setlabel, - setboot = not (options.nosetboot or options.noboot) and source.prop.setboot, - reboot = not options.noreboot and source.prop.reboot, + setboot = not (options.nosetboot or options.noboot) and source.prop.setboot, + reboot = not options.noreboot and source.prop.reboot } local source_display = options.label or source.dev.getLabel() or source.path -- Remove the source from the target options -for index,entry in ipairs(targets) do +for index, entry in ipairs(targets) do if entry.dev == source.dev then table.remove(targets, index) target = targets[1] @@ -162,47 +165,67 @@ end -- Ask the user to select a target if #targets ~= 1 then - if #sources == 1 then - io.write(source_display, " selected for install\n") - end + if #sources == 1 then + io.write(source_display, " selected for install\n") + end - utils = utils or loadfile(utils_path, "bt", _G) + utils = utils or loadfile(utils_path, "bt", _G) target = utils("select", "targets", options, targets) end -if not target then return end +if not target then + return +end -options.to = target.path .. '/' +options.to = target.path .. "/" -local cp_args = -{ - "-vrx" .. (options.update and "ui" or ""), - "--skip=.prop", - fs.concat(options.from, options.fromDir) .. "/.", - fs.concat(options.to , options.root) +local function resolveFrom(path) + return fs.concat(options.from, options.fromDir) .. "/" .. path +end + +local fullTargetPath = fs.concat(options.to, options.root) +local transfer_args = { + { + {resolveFrom("."), fullTargetPath}, + { + cmd = "cp", + r = true, v = true, x = true, u = options.update, i = options.update, + skip = {resolveFrom(".prop")}, + } + } } +if source.prop.noclobber and #source.prop.noclobber > 0 then + local noclobber_opts = {cmd = "cp", v = true, n = true} + for _, noclobber in ipairs(source.prop.noclobber or {}) do + local noclobberFrom = resolveFrom(noclobber) + local noclobberTo = fs.concat(fullTargetPath, noclobber) + table.insert(transfer_args[1][2].skip, noclobberFrom) + table.insert(transfer_args, {{noclobberFrom, noclobberTo}, noclobber_opts}) + end +end + local special_target = "" if #targets > 1 or target_filter or source_filter then - special_target = " to " .. cp_args[4] + special_target = " to " .. transfer_args[1][1][2] end io.write("Install " .. source_display .. special_target .. "? [Y/n] ") -if not ((io.read() or "n").."y"):match("^%s*[Yy]") then +if not ((io.read() or "n") .. "y"):match("^%s*[Yy]") then io.write("Installation cancelled\n") os.exit() end local installer_path = options.from .. "/.install" if fs.exists(installer_path) then - local installer, reason = loadfile(installer_path, "bt", setmetatable({install=options}, {__index = _G})) + local installer, reason = loadfile(installer_path, "bt", setmetatable({install = options}, {__index = _G})) if not installer then - io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n') + io.stderr:write("installer failed to load: " .. tostring(reason) .. "\n") os.exit(1) end os.exit(installer()) end -options.cp_args = cp_args +options.cp_args = transfer_args options.target = target return options diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua index 12cf51f94..ab07bd6a3 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua @@ -116,15 +116,12 @@ while term.isAvailable() do else local ok, why = pcall(function() for i = 2, result.n do - io.write(require("serialization").serialize(result[i], true) .. "\t") + io.write(require("serialization").serialize(result[i], true), i < result.n and "\t" or "\n") end end) if not ok then io.stderr:write("crashed serializing result: ", tostring(why)) end - if term.getCursor() > 1 then - io.write("\n") - end end else io.stderr:write(tostring(reason) .. "\n") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/transfer.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/transfer.lua index 3e3f4b725..ec847252f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/transfer.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/transfer.lua @@ -93,7 +93,7 @@ function lib.recurse(fromPath, toPath, options, origin, top) local toPathFull = shell.resolve(toPath) local mv = options.cmd == "mv" local verbose = options.v and (not mv or top) - if select(2, fromPathFull:find(options.skip)) == #fromPathFull then + if options.skip[fromPathFull] then status(verbose, string.format("skipping %s", fromPath)) return true end @@ -221,8 +221,13 @@ function lib.batch(args, options) -- standardized options options.i = options.i and not options.f options.P = options.P or options.r - options.skip = text.escapeMagic(options.skip or "") - + + local skips = options.skip or {} + options.skip = {} + for _, skip_item in ipairs(skips) do + options.skip[shell.resolve(skip_item)] = true + end + local origin = {} for dev,path in fs.mounts() do origin[path] = dev diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index 1177f5f71..7c42c3b49 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -796,7 +796,7 @@ sandbox = { local handled = false checkArg(2, msgh, "function") local result = table.pack(xpcall(f, function(...) - if (...) == tooLongWithoutYielding then + if rawequal((...), tooLongWithoutYielding) then return tooLongWithoutYielding elseif handled then return ... @@ -805,7 +805,7 @@ sandbox = { return msgh(...) end end, ...)) - if result[2] == tooLongWithoutYielding then + if rawequal(result[2], tooLongWithoutYielding) then result = table.pack(result[1], select(2, pcallTimeoutCheck(pcall(msgh, tostring(tooLongWithoutYielding))))) end return table.unpack(result, 1, result.n) diff --git a/src/main/resources/assets/opencomputers/robot.names b/src/main/resources/assets/opencomputers/robot.names index 455c42972..3d862d926 100644 --- a/src/main/resources/assets/opencomputers/robot.names +++ b/src/main/resources/assets/opencomputers/robot.names @@ -121,6 +121,7 @@ Rinzler # Tron Twiki # Buck Rodgers Uniblab # The Jetsons Unimate # First programmable robot. +VEGA # Doom 2016 Vertigo # Perry Rhodan Vexatos # Contributor V.I.K.I. # Virtual Interactive Kinetic Intelligence - I, Robot diff --git a/src/main/scala/li/cil/oc/Localization.scala b/src/main/scala/li/cil/oc/Localization.scala index c1d19f5bb..d66c0be78 100644 --- a/src/main/scala/li/cil/oc/Localization.scala +++ b/src/main/scala/li/cil/oc/Localization.scala @@ -143,6 +143,8 @@ object Localization { def RelayEnabled: String = localizeImmediately("gui.Rack.Enabled") def RelayDisabled: String = localizeImmediately("gui.Rack.Disabled") + + def RelayModeTooltip: String = localizeImmediately("gui.Rack.RelayModeTooltip") } object Switch { diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 4b1d17360..d632a6828 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -92,12 +92,6 @@ class Settings(val config: Config) { OpenComputers.log.warn("Bad number of RAM sizes, ignoring.") Array(192, 256, 384, 512, 768, 1024) } - val vramSizes = Array(config.getIntList("computer.lua.vramSizes"): _*) match { - case Array(tier1, tier2, tier3) => Array(tier1: Int, tier2: Int, tier3: Int) - case _ => - OpenComputers.log.warn("Bad number of VRAM sizes, ignoring.") - Array(1, 2, 3) - } val ramScaleFor64Bit = config.getDouble("computer.lua.ramScaleFor64Bit") max 1 val maxTotalRam = config.getInt("computer.lua.maxTotalRam") max 0 @@ -476,24 +470,34 @@ class Settings(val config: Config) { // >= 1.7.4 val maxSignalQueueSize: Int = (if (config.hasPath("computer.maxSignalQueueSize")) config.getInt("computer.maxSignalQueueSize") else 256) min 256 + + // >= 1.7.6 + val vramSizes: Array[Double] = Array(config.getDoubleList("gpu.vramSizes"): _*) match { + case Array(tier1, tier2, tier3) => Array(tier1: Double, tier2: Double, tier3: Double) + case _ => + OpenComputers.log.warn("Bad number of VRAM sizes (expected 3), ignoring.") + Array(1, 2, 3) + } + + val bitbltCost: Double = if (config.hasPath("gpu.bitbltCost")) config.getDouble("gpu.bitbltCost") else 0.5 } object Settings { val resourceDomain = "opencomputers" val namespace = "oc:" val savePath = "opencomputers/" - val scriptPath = "/assets/" + resourceDomain + "/lua/" - val screenResolutionsByTier = Array((50, 16), (80, 25), (160, 50)) - val screenDepthsByTier = Array(api.internal.TextBuffer.ColorDepth.OneBit, api.internal.TextBuffer.ColorDepth.FourBit, api.internal.TextBuffer.ColorDepth.EightBit) - val deviceComplexityByTier = Array(12, 24, 32, 9001) + val scriptPath: String = "/assets/" + resourceDomain + "/lua/" + val screenResolutionsByTier: Array[(Int, Int)] = Array((50, 16), (80, 25), (160, 50)) + val screenDepthsByTier: Array[api.internal.TextBuffer.ColorDepth] = Array(api.internal.TextBuffer.ColorDepth.OneBit, api.internal.TextBuffer.ColorDepth.FourBit, api.internal.TextBuffer.ColorDepth.EightBit) + val deviceComplexityByTier: Array[Int] = Array(12, 24, 32, 9001) var rTreeDebugRenderer = false - var blockRenderId = -1 + var blockRenderId: Int = -1 - def basicScreenPixels = screenResolutionsByTier(0)._1 * screenResolutionsByTier(0)._2 + def basicScreenPixels: Int = screenResolutionsByTier(0)._1 * screenResolutionsByTier(0)._2 private var settings: Settings = _ - def get = settings + def get: Settings = settings def load(file: File) = { import scala.compat.Platform.EOL diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index 95acca50b..9cf0b967a 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -733,13 +733,11 @@ object PacketHandler extends CommonPacketHandler { } def onTextBufferRamInit(p: PacketParser, buffer: api.internal.TextBuffer): Unit = { + val owner = p.readUTF() val id = p.readInt() val nbt = p.readNBT() - buffer match { - case screen: component.traits.VideoRamAware => screen.loadBuffer(id, nbt) - case _ => // ignore - } + component.ClientGpuTextBufferHandler.loadBuffer(buffer, owner, id, nbt) } def onTextBufferBitBlt(p: PacketParser, buffer: api.internal.TextBuffer): Unit = { @@ -747,24 +745,19 @@ object PacketHandler extends CommonPacketHandler { val row = p.readInt() val w = p.readInt() val h = p.readInt() + val owner = p.readUTF() val id = p.readInt() val fromCol = p.readInt() val fromRow = p.readInt() - component.GpuTextBuffer.bitblt(buffer, col, row, w, h, id, fromCol, fromRow) + component.ClientGpuTextBufferHandler.bitblt(buffer, col, row, w, h, owner, id, fromCol, fromRow) } def onTextBufferRamDestroy(p: PacketParser, buffer: api.internal.TextBuffer): Unit = { - val length = p.readInt() - val ids = new Array[Int](length) - for (i <- 0 until length) { - ids(i) = p.readInt() - } + val owner = p.readUTF() + val id = p.readInt() - buffer match { - case screen: component.traits.VideoRamAware => screen.removeBuffers(ids) - case _ => // ignore, not compatible with bitblts - } + component.ClientGpuTextBufferHandler.removeBuffer(buffer, owner, id) } def onTextBufferMultiRawSetText(p: PacketParser, buffer: api.internal.TextBuffer) { diff --git a/src/main/scala/li/cil/oc/client/gui/Rack.scala b/src/main/scala/li/cil/oc/client/gui/Rack.scala index b371cffcf..8929fe3ac 100644 --- a/src/main/scala/li/cil/oc/client/gui/Rack.scala +++ b/src/main/scala/li/cil/oc/client/gui/Rack.scala @@ -14,6 +14,8 @@ import net.minecraft.entity.player.InventoryPlayer import net.minecraft.util.EnumFacing import org.lwjgl.opengl.GL11 +import scala.collection.convert.WrapAsJava.asJavaCollection + class Rack(playerInventory: InventoryPlayer, val rack: tileentity.Rack) extends DynamicGuiContainer(new container.Rack(playerInventory, rack)) { ySize = 210 @@ -251,6 +253,12 @@ class Rack(playerInventory: InventoryPlayer, val rack: tileentity.Rack) extends x, y, 0x404040) } + if (relayButton.isMouseOver) { + val tooltip = new java.util.ArrayList[String] + tooltip.addAll(asJavaCollection(Localization.Rack.RelayModeTooltip.lines.toIterable)) + copiedDrawHoveringText(tooltip, mouseX - guiLeft, mouseY - guiTop, fontRenderer) + } + RenderState.popAttrib() } diff --git a/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala b/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala index 8c7d8d414..2d9f8f4df 100644 --- a/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala @@ -1,14 +1,24 @@ package li.cil.oc.common.component -import li.cil.oc.api.network.{ManagedEnvironment, Message, Node} +import java.io.InvalidObjectException +import java.security.InvalidParameterException + +import li.cil.oc.api.network.{Environment, Message, Node} import net.minecraft.entity.player.EntityPlayer import net.minecraft.nbt.NBTTagCompound import li.cil.oc.api.internal.TextBuffer.ColorDepth import li.cil.oc.api -import li.cil.oc.common.component.traits.TextBufferProxy -import li.cil.oc.util.PackedColor +import li.cil.oc.common.component.traits.{TextBufferProxy, VideoRamDevice, VideoRamRasterizer} + +class GpuTextBuffer(val owner: String, val id: Int, val data: li.cil.oc.util.TextBuffer) extends traits.TextBufferProxy { + + // the gpu ram does not join nor is searchable to the network + // this field is required because the api TextBuffer is an Environment + override def node(): Node = { + throw new InvalidObjectException("GpuTextBuffers do not have nodes") + } + -class GpuTextBuffer(val id: Int, val data: li.cil.oc.util.TextBuffer) extends ManagedEnvironment with traits.TextBufferProxy { override def getMaximumWidth: Int = data.width override def getMaximumHeight: Int = data.height override def getViewportWidth: Int = data.height @@ -17,10 +27,19 @@ class GpuTextBuffer(val id: Int, val data: li.cil.oc.util.TextBuffer) extends Ma var dirty: Boolean = true override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = dirty = true override def onBufferColorChange(): Unit = dirty = true - override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = dirty = true override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = dirty = true override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = dirty = true - override def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = dirty = false + + override def load(nbt: NBTTagCompound): Unit = { + // the data is initially dirty because other devices don't know about it yet + data.load(nbt) + dirty = true + } + + override def save(nbt: NBTTagCompound): Unit = { + data.save(nbt) + dirty = false + } override def setEnergyCostPerTick(value: Double): Unit = {} override def getEnergyCostPerTick: Double = 0 @@ -47,29 +66,42 @@ class GpuTextBuffer(val id: Int, val data: li.cil.oc.util.TextBuffer) extends Ma override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer): Unit = {} override def canUpdate: Boolean = false override def update(): Unit = {} - override def node: li.cil.oc.api.network.Node = null override def onConnect(node: Node): Unit = {} override def onDisconnect(node: Node): Unit = {} override def onMessage(message: Message): Unit = {} - override def load(nbt: NBTTagCompound): Unit = {} - override def save(nbt: NBTTagCompound): Unit = {} } -object GpuTextBuffer { - def wrap(id: Int, data: li.cil.oc.util.TextBuffer): GpuTextBuffer = new GpuTextBuffer(id, data) - - def bitblt(dst: api.internal.TextBuffer, col: Int, row: Int, w: Int, h: Int, srcId: Int, fromCol: Int, fromRow: Int): Unit = { +object ClientGpuTextBufferHandler { + def bitblt(dst: api.internal.TextBuffer, col: Int, row: Int, w: Int, h: Int, owner: String, srcId: Int, fromCol: Int, fromRow: Int): Unit = { dst match { - case screen: traits.TextBufferProxy => screen.getBuffer(srcId) match { + case videoDevice: VideoRamRasterizer => videoDevice.getBuffer(owner, srcId) match { case Some(buffer: GpuTextBuffer) => { - bitblt(dst, col, row, w, h, buffer, fromCol, fromRow) + GpuTextBuffer.bitblt(dst, col, row, w, h, buffer, fromCol, fromRow) } case _ => // ignore - got a bitblt for a missing buffer } - case _ => // ignore - weird packet handler called this, should only happen for screens that know about thsi + case _ => // ignore - weird packet handler called this, should only happen for video ram aware devices } } + def removeBuffer(buffer: api.internal.TextBuffer, owner: String, id: Int): Boolean = { + buffer match { + case screen: VideoRamRasterizer => screen.removeBuffer(owner, id) + case _ => false // ignore, not compatible with bitblts + } + } + + def loadBuffer(buffer: api.internal.TextBuffer, owner: String, id: Int, nbt: NBTTagCompound): Boolean = { + buffer match { + case screen: VideoRamRasterizer => screen.loadBuffer(owner, id, nbt) + case _ => false // ignore, not compatible with bitblts + } + } +} + +object GpuTextBuffer { + def wrap(owner: String, id: Int, data: li.cil.oc.util.TextBuffer): GpuTextBuffer = new GpuTextBuffer(owner, id, data) + def bitblt(dst: api.internal.TextBuffer, col: Int, row: Int, w: Int, h: Int, src: api.internal.TextBuffer, fromCol: Int, fromRow: Int): Unit = { val x = col - 1 val y = row - 1 @@ -118,37 +150,28 @@ object GpuTextBuffer { } dst match { - case dstRam: GpuTextBuffer => src match { - case srcRam: GpuTextBuffer => write_vram_to_vram(dstRam, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcRam, adjustedSourceX, adjustedSourceY) - case srcScreen: traits.TextBufferProxy => write_screen_to_vram(dstRam, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcScreen, adjustedSourceX, adjustedSourceY) - case _ => throw new UnsupportedOperationException("Source buffer does not support bitblt operations") + case dstScreen: TextBuffer => src match { + case srcGpu: GpuTextBuffer => write_vram_to_screen(dstScreen, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcGpu, adjustedSourceX, adjustedSourceY) + case _ => throw new UnsupportedOperationException("Source buffer does not support bitblt operations to a screen") } - case dstScreen: traits.TextBufferProxy => src match { - case srcRam: GpuTextBuffer => write_vram_to_screen(dstScreen, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcRam, adjustedSourceX, adjustedSourceY) - case _: traits.TextBufferProxy => throw new UnsupportedOperationException("Screen to screen bitblt not supported") + case dstGpu: GpuTextBuffer => src match { + case srcProxy: TextBufferProxy => write_to_vram(dstGpu, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcProxy, adjustedSourceX, adjustedSourceY) case _ => throw new UnsupportedOperationException("Source buffer does not support bitblt operations") } case _ => throw new UnsupportedOperationException("Destination buffer does not support bitblt operations") } } - def write_vram_to_vram(dstRam: GpuTextBuffer, x: Int, y: Int, w: Int, h: Int, srcRam: GpuTextBuffer, fx: Int, fy: Int): Boolean = { - dstRam.data.rawcopy(x + 1, y + 1, w, h, srcRam.data, fx + 1, fx + 1) - } - - def write_vram_to_screen(dstScreen: traits.TextBufferProxy, x: Int, y: Int, w: Int, h: Int, srcRam: GpuTextBuffer, fx: Int, fy: Int): Boolean = { + def write_vram_to_screen(dstScreen: TextBuffer, x: Int, y: Int, w: Int, h: Int, srcRam: GpuTextBuffer, fx: Int, fy: Int): Boolean = { if (dstScreen.data.rawcopy(x + 1, y + 1, w, h, srcRam.data, fx + 1, fy + 1)) { // rawcopy returns true only if data was modified dstScreen.addBuffer(srcRam) - dstScreen.onBufferBitBlt(x + 1, y + 1, w, h, srcRam.id, fx + 1, fy + 1) + dstScreen.onBufferBitBlt(x + 1, y + 1, w, h, srcRam, fx + 1, fy + 1) true } else false } - def write_screen_to_vram(dstRam: GpuTextBuffer, x: Int, y: Int, w: Int, h: Int, srcScreen: traits.TextBufferProxy, fx: Int, fy: Int): Boolean = { - val format: PackedColor.ColorFormat = PackedColor.Depth.format(srcScreen.getColorDepth) - val tempGpu = GpuTextBuffer.wrap(id = -1, new li.cil.oc.util.TextBuffer(w, h, format)) - tempGpu.data.rawcopy(col = 1, row = 1, w, h, srcScreen.data, fx + 1, fy + 1) - write_vram_to_vram(dstRam, x, y, w, h, tempGpu, fx = 0, fy = 0) + def write_to_vram(dstRam: GpuTextBuffer, x: Int, y: Int, w: Int, h: Int, src: TextBufferProxy, fx: Int, fy: Int): Boolean = { + dstRam.data.rawcopy(x + 1, y + 1, w, h, src.data, fx + 1, fy + 1) } } diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala index c541f1c25..cacaf762f 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -22,6 +22,7 @@ import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.common._ import li.cil.oc.common.item.data.NodeData import li.cil.oc.common.component.traits.TextBufferProxy +import li.cil.oc.common.component.traits.VideoRamRasterizer import li.cil.oc.server.component.Keyboard import li.cil.oc.server.{ComponentTracker => ServerComponentTracker} import li.cil.oc.server.{PacketSender => ServerPacketSender} @@ -43,7 +44,7 @@ import scala.collection.convert.WrapAsJava._ import scala.collection.convert.WrapAsScala._ import scala.collection.mutable -class TextBuffer(val host: EnvironmentHost) extends AbstractManagedEnvironment with traits.TextBufferProxy with DeviceInfo { +class TextBuffer(val host: EnvironmentHost) extends AbstractManagedEnvironment with traits.TextBufferProxy with VideoRamRasterizer with DeviceInfo { override val node = api.Network.newNode(this, Visibility.Network). withComponent("screen"). withConnector(). @@ -326,16 +327,16 @@ class TextBuffer(val host: EnvironmentHost) extends AbstractManagedEnvironment w proxy.onBufferSet(col, row, s, vertical) } - override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = { - proxy.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow) + override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, ram: component.GpuTextBuffer, fromCol: Int, fromRow: Int): Unit = { + proxy.onBufferBitBlt(col, row, w, h, ram, fromCol, fromRow) } - override def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = { - proxy.onBufferRamInit(id, ram) + override def onBufferRamInit(ram: component.GpuTextBuffer): Unit = { + proxy.onBufferRamInit(ram) } - override def onBufferRamDestroy(ids: Array[Int]): Unit = { - proxy.onBufferRamDestroy(ids) + override def onBufferRamDestroy(ram: component.GpuTextBuffer): Unit = { + proxy.onBufferRamDestroy(ram) } override def rawSetText(col: Int, row: Int, text: Array[Array[Char]]): Unit = { @@ -568,15 +569,15 @@ object TextBuffer { owner.relativeLitArea = -1 } - def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = { + def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, ram: component.GpuTextBuffer, fromCol: Int, fromRow: Int): Unit = { owner.relativeLitArea = -1 } - def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = { + def onBufferRamInit(ram: component.GpuTextBuffer): Unit = { owner.relativeLitArea = -1 } - def onBufferRamDestroy(ids: Array[Int]): Unit = { + def onBufferRamDestroy(ram: component.GpuTextBuffer): Unit = { owner.relativeLitArea = -1 } @@ -663,17 +664,17 @@ object TextBuffer { markDirty() } - override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = { - super.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow) + override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, ram: component.GpuTextBuffer, fromCol: Int, fromRow: Int): Unit = { + super.onBufferBitBlt(col, row, w, h, ram, fromCol, fromRow) markDirty() } - override def onBufferRamInit(id: Int, buffer: TextBufferProxy): Unit = { - super.onBufferRamInit(id, buffer) + override def onBufferRamInit(ram: component.GpuTextBuffer): Unit = { + super.onBufferRamInit(ram) } - override def onBufferRamDestroy(ids: Array[Int]): Unit = { - super.onBufferRamDestroy(ids) + override def onBufferRamDestroy(ram: component.GpuTextBuffer): Unit = { + super.onBufferRamDestroy(ram) } override def keyDown(character: Char, code: Int, player: EntityPlayer) { @@ -778,24 +779,24 @@ object TextBuffer { owner.synchronized(ServerPacketSender.appendTextBufferSet(owner.pendingCommands, col, row, s, vertical)) } - override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = { - super.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow) + override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, ram: component.GpuTextBuffer, fromCol: Int, fromRow: Int): Unit = { + super.onBufferBitBlt(col, row, w, h, ram, fromCol, fromRow) owner.host.markChanged() - owner.synchronized(ServerPacketSender.appendTextBufferBitBlt(owner.pendingCommands, col, row, w, h, id, fromCol, fromRow)) + owner.synchronized(ServerPacketSender.appendTextBufferBitBlt(owner.pendingCommands, col, row, w, h, ram.owner, ram.id, fromCol, fromRow)) } - override def onBufferRamInit(id: Int, buffer: TextBufferProxy): Unit = { - super.onBufferRamInit(id, buffer) + override def onBufferRamInit(ram: component.GpuTextBuffer): Unit = { + super.onBufferRamInit(ram) owner.host.markChanged() val nbt = new NBTTagCompound() - buffer.data.save(nbt) - owner.synchronized(ServerPacketSender.appendTextBufferRamInit(owner.pendingCommands, id, nbt)) + ram.save(nbt) + owner.synchronized(ServerPacketSender.appendTextBufferRamInit(owner.pendingCommands, ram.owner, ram.id, nbt)) } - override def onBufferRamDestroy(ids: Array[Int]): Unit = { - super.onBufferRamDestroy(ids) + override def onBufferRamDestroy(ram: component.GpuTextBuffer): Unit = { + super.onBufferRamDestroy(ram) owner.host.markChanged() - owner.synchronized(ServerPacketSender.appendTextBufferRamDestroy(owner.pendingCommands, ids)) + owner.synchronized(ServerPacketSender.appendTextBufferRamDestroy(owner.pendingCommands, ram.owner, ram.id)) } override def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) { diff --git a/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala b/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala index 082670998..ae55093ff 100644 --- a/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala +++ b/src/main/scala/li/cil/oc/common/component/traits/TextBufferProxy.scala @@ -5,7 +5,7 @@ import li.cil.oc.api import li.cil.oc.api.internal.TextBuffer import li.cil.oc.util.PackedColor -trait TextBufferProxy extends api.internal.TextBuffer with VideoRamAware { +trait TextBufferProxy extends api.internal.TextBuffer { def data: util.TextBuffer override def getWidth: Int = data.width diff --git a/src/main/scala/li/cil/oc/common/component/traits/VideoRamAware.scala b/src/main/scala/li/cil/oc/common/component/traits/VideoRamAware.scala deleted file mode 100644 index 55b30b2fe..000000000 --- a/src/main/scala/li/cil/oc/common/component/traits/VideoRamAware.scala +++ /dev/null @@ -1,70 +0,0 @@ -package li.cil.oc.common.component.traits - -import li.cil.oc.common.component -import net.minecraft.nbt.NBTTagCompound - -trait VideoRamAware { - private val internalBuffers = new scala.collection.mutable.HashMap[Int, component.GpuTextBuffer] - val RESERVED_SCREEN_INDEX: Int = 0 - - def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {} - def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = {} - def onBufferRamDestroy(ids: Array[Int]): Unit = {} - - def bufferIndexes(): Array[Int] = internalBuffers.collect { - case (index: Int, _: Any) => index - }.toArray - - def addBuffer(buffer: component.GpuTextBuffer): Boolean = { - val preexists = internalBuffers.contains(buffer.id) - internalBuffers += buffer.id -> buffer - if (!preexists || buffer.dirty) { - buffer.onBufferRamInit(buffer.id, buffer) - onBufferRamInit(buffer.id, buffer) - } - preexists - } - - def removeBuffers(ids: Array[Int]): Boolean = { - var allRemoved: Boolean = true - if (ids.nonEmpty) { - onBufferRamDestroy(ids) - for (id <- ids) { - if (internalBuffers.remove(id).isEmpty) - allRemoved = false - } - } - allRemoved - } - - def removeAllBuffers(): Boolean = removeBuffers(bufferIndexes()) - - def loadBuffer(id: Int, nbt: NBTTagCompound): Unit = { - val src = new li.cil.oc.util.TextBuffer(width = 1, height = 1, li.cil.oc.util.PackedColor.SingleBitFormat) - src.load(nbt) - addBuffer(component.GpuTextBuffer.wrap(id, src)) - } - - def getBuffer(id: Int): Option[component.GpuTextBuffer] = { - if (internalBuffers.contains(id)) - Option(internalBuffers(id)) - else - None - } - - def nextAvailableBufferIndex: Int = { - var index = RESERVED_SCREEN_INDEX + 1 - while (internalBuffers.contains(index)) { - index += 1; - } - index - } - - def calculateUsedMemory(): Int = { - var sum: Int = 0 - for ((_, buffer: component.GpuTextBuffer) <- internalBuffers) { - sum += buffer.data.width * buffer.data.height - } - sum - } -} diff --git a/src/main/scala/li/cil/oc/common/component/traits/VideoRamDevice.scala b/src/main/scala/li/cil/oc/common/component/traits/VideoRamDevice.scala new file mode 100644 index 000000000..7bd2b4115 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/component/traits/VideoRamDevice.scala @@ -0,0 +1,68 @@ +package li.cil.oc.common.component.traits + +import li.cil.oc.common.component +import net.minecraft.nbt.NBTTagCompound +import scala.collection.mutable + +trait VideoRamDevice { + private val internalBuffers = new mutable.HashMap[Int, component.GpuTextBuffer] + val RESERVED_SCREEN_INDEX: Int = 0 + + def isEmpty: Boolean = internalBuffers.isEmpty + + def onBufferRamDestroy(id: Int): Unit = {} + + def bufferIndexes(): Array[Int] = internalBuffers.collect { + case (index: Int, _: Any) => index + }.toArray + + def addBuffer(ram: component.GpuTextBuffer): Boolean = { + val preexists = internalBuffers.contains(ram.id) + internalBuffers += ram.id -> ram + preexists + } + + def removeBuffers(ids: Array[Int]): Int = { + var count = 0 + if (ids.nonEmpty) { + for (id <- ids) { + if (internalBuffers.remove(id).nonEmpty) { + onBufferRamDestroy(id) + count += 1 + } + } + } + count + } + + def removeAllBuffers(): Int = removeBuffers(bufferIndexes()) + + def loadBuffer(address: String, id: Int, nbt: NBTTagCompound): Unit = { + val src = new li.cil.oc.util.TextBuffer(width = 1, height = 1, li.cil.oc.util.PackedColor.SingleBitFormat) + src.load(nbt) + addBuffer(component.GpuTextBuffer.wrap(address, id, src)) + } + + def getBuffer(id: Int): Option[component.GpuTextBuffer] = { + if (internalBuffers.contains(id)) + Option(internalBuffers(id)) + else + None + } + + def nextAvailableBufferIndex: Int = { + var index = RESERVED_SCREEN_INDEX + 1 + while (internalBuffers.contains(index)) { + index += 1; + } + index + } + + def calculateUsedMemory(): Int = { + var sum: Int = 0 + for ((_, buffer: component.GpuTextBuffer) <- internalBuffers) { + sum += buffer.data.width * buffer.data.height + } + sum + } +} diff --git a/src/main/scala/li/cil/oc/common/component/traits/VideoRamRasterizer.scala b/src/main/scala/li/cil/oc/common/component/traits/VideoRamRasterizer.scala new file mode 100644 index 000000000..1aa3910cc --- /dev/null +++ b/src/main/scala/li/cil/oc/common/component/traits/VideoRamRasterizer.scala @@ -0,0 +1,82 @@ +package li.cil.oc.common.component.traits + +import li.cil.oc.common.component +import li.cil.oc.common.component.GpuTextBuffer +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.village.VillageDoorInfo + +import scala.collection.mutable + +trait VideoRamRasterizer { + class VirtualRamDevice(val owner: String) extends VideoRamDevice {} + private val internalBuffers = new mutable.HashMap[String, VideoRamDevice] + + def onBufferRamInit(ram: component.GpuTextBuffer): Unit + def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, ram: component.GpuTextBuffer, fromCol: Int, fromRow: Int): Unit + def onBufferRamDestroy(ram: component.GpuTextBuffer): Unit + + def addBuffer(ram: GpuTextBuffer): Boolean = { + var gpu = internalBuffers.get(ram.owner) + if (gpu.isEmpty) { + gpu = Option(new VirtualRamDevice(ram.owner)) + internalBuffers += ram.owner -> gpu.get + } + val preexists: Boolean = gpu.get.addBuffer(ram) + if (!preexists || ram.dirty) { + onBufferRamInit(ram) + } + preexists + } + + def removeBuffer(owner: String, id: Int): Boolean = { + internalBuffers.get(owner) match { + case Some(gpu: VideoRamDevice) => { + gpu.getBuffer(id) match { + case Some(ram: component.GpuTextBuffer) => { + onBufferRamDestroy(ram) + gpu.removeBuffers(Array(id)) == 1 + } + case _ => false + } + } + case _ => false + } + } + + def removeAllBuffers(owner: String): Int = { + var count = 0 + internalBuffers.get(owner) match { + case Some(gpu: VideoRamDevice) => { + val ids = gpu.bufferIndexes() + for (id <- ids) { + if (removeBuffer(owner, id)) { + count += 1 + } + } + } + case _ => Unit + } + count + } + + def removeAllBuffers(): Int = { + var count = 0 + for ((owner: String, _: Any) <- internalBuffers) { + count += removeAllBuffers(owner) + } + count + } + + def loadBuffer(owner: String, id: Int, nbt: NBTTagCompound): Boolean = { + val src = new li.cil.oc.util.TextBuffer(width = 1, height = 1, li.cil.oc.util.PackedColor.SingleBitFormat) + src.load(nbt) + addBuffer(component.GpuTextBuffer.wrap(owner, id, src)) + } + + def getBuffer(owner: String, id: Int): Option[component.GpuTextBuffer] = { + internalBuffers.get(owner) match { + case Some(gpu: VideoRamDevice) => gpu.getBuffer(id) + case _ => None + } + } +} diff --git a/src/main/scala/li/cil/oc/common/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index 29b086d1f..920d8dfb7 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -261,6 +261,8 @@ object Items extends ItemAPI { safeGetStack(Constants.ItemName.CraftingUpgrade), safeGetStack(Constants.ItemName.HoverUpgradeTier2), safeGetStack(Constants.ItemName.AngelUpgrade), + safeGetStack(Constants.ItemName.TradingUpgrade), + safeGetStack(Constants.ItemName.ExperienceUpgrade), safeGetStack(Constants.ItemName.GraphicsCardTier3), safeGetStack(Constants.ItemName.RedstoneCardTier2), diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeTrading.scala b/src/main/scala/li/cil/oc/common/item/UpgradeTrading.scala index 1cb2b27ab..30806741d 100644 --- a/src/main/scala/li/cil/oc/common/item/UpgradeTrading.scala +++ b/src/main/scala/li/cil/oc/common/item/UpgradeTrading.scala @@ -1,3 +1,5 @@ package li.cil.oc.common.item -class UpgradeTrading(val parent: Delegator) extends traits.Delegate with traits.ItemTier \ No newline at end of file +class UpgradeTrading(val parent: Delegator) extends traits.Delegate with traits.ItemTier { + override protected def tooltipName: Option[String] = Option(super.unlocalizedName) +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala index 309e5f16b..eff335baf 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -32,7 +32,7 @@ import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly class Rack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalancer with traits.ComponentInventory with traits.Rotatable with traits.BundledRedstoneAware with Analyzable with internal.Rack with traits.StateAware { - var isRelayEnabled = true + var isRelayEnabled = false val lastData = new Array[NBTTagCompound](getSizeInventory) val hasChanged: Array[Boolean] = Array.fill(getSizeInventory)(true) diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index f4ff4ee05..dc4624e7a 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -670,31 +670,31 @@ object PacketSender { pb.writeBoolean(vertical) } - def appendTextBufferBitBlt(pb: PacketBuilder, col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = { + def appendTextBufferBitBlt(pb: PacketBuilder, col: Int, row: Int, w: Int, h: Int, owner: String, id: Int, fromCol: Int, fromRow: Int): Unit = { pb.writePacketType(PacketType.TextBufferBitBlt) pb.writeInt(col) pb.writeInt(row) pb.writeInt(w) pb.writeInt(h) + pb.writeUTF(owner) pb.writeInt(id) pb.writeInt(fromCol) pb.writeInt(fromRow) } - def appendTextBufferRamInit(pb: PacketBuilder, id: Int, nbt: NBTTagCompound): Unit = { + def appendTextBufferRamInit(pb: PacketBuilder, address: String, id: Int, nbt: NBTTagCompound): Unit = { pb.writePacketType(PacketType.TextBufferRamInit) + pb.writeUTF(address) pb.writeInt(id) pb.writeNBT(nbt) } - def appendTextBufferRamDestroy(pb: PacketBuilder, ids: Array[Int]): Unit = { + def appendTextBufferRamDestroy(pb: PacketBuilder, owner: String, id: Int): Unit = { pb.writePacketType(PacketType.TextBufferRamDestroy) - pb.writeInt(ids.length) - for (idx <- ids) { - pb.writeInt(idx) - } + pb.writeUTF(owner) + pb.writeInt(id) } def appendTextBufferRawSetText(pb: PacketBuilder, col: Int, row: Int, text: Array[Array[Char]]) { diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index 554a8b5da..f074217a5 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -211,7 +211,7 @@ class DebugCard(host: EnvironmentHost) extends AbstractManagedEnvironment with D } } - @Callback(doc = """function(x:number, y:number, z:number):boolean -- Connect the debug card to the block at the specified coordinates.""") + @Callback(doc = """function(x:number, y:number, z:number):boolean -- Add a component block at the specified coordinates to the computer network.""") def connectToBlock(context: Context, args: Arguments): Array[AnyRef] = { checkAccess() val x = args.checkInteger(0) @@ -821,7 +821,7 @@ object DebugCard { } val count = args.checkInteger(1) val damage = args.checkInteger(2) - val tagJson = args.checkString(3) + val tagJson = args.optString(3, "") val tag = if (Strings.isNullOrEmpty(tagJson)) null else JsonToNBT.getTagFromJson(tagJson) val position = BlockPosition(args.checkDouble(4), args.checkDouble(5), args.checkDouble(6), world) val side = args.checkSideAny(7) diff --git a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala index c4e5f8bfe..cac1beed1 100644 --- a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala +++ b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala @@ -7,9 +7,7 @@ import li.cil.oc.api.Network import li.cil.oc.api.driver.DeviceInfo import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute import li.cil.oc.api.driver.DeviceInfo.DeviceClass -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.machine.{Arguments, Callback, Context, LimitReachedException} import li.cil.oc.api.network._ import li.cil.oc.api.prefab import li.cil.oc.api.prefab.AbstractManagedEnvironment @@ -33,7 +31,7 @@ import scala.util.matching.Regex // saved, but before the computer was saved, leading to mismatching states in // the save file - a Bad Thing (TM). -class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with DeviceInfo with component.traits.VideoRamAware { +class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with DeviceInfo with component.traits.VideoRamDevice { override val node = Network.newNode(this, Visibility.Neighbors). withComponent("gpu"). withConnector(). @@ -71,8 +69,13 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device final val setCosts = Array(1.0 / 64, 1.0 / 128, 1.0 / 256) final val copyCosts = Array(1.0 / 16, 1.0 / 32, 1.0 / 64) final val fillCosts = Array(1.0 / 32, 1.0 / 64, 1.0 / 128) - final val bitbltCosts = Array(32, 16, 8) - final val totalVRAM: Int = (maxResolution._1 * maxResolution._2) * Settings.get.vramSizes(0 max tier min Settings.get.vramSizes.length) + // These are dirty page bitblt budget costs + // a single bitblt can send a screen of data, which is n*set calls where set is writing an entire line + // So for each tier, we multiple the set cost with the number of lines the screen may have + final val bitbltCost: Double = Settings.get.bitbltCost * scala.math.pow(2, tier) + final val totalVRAM: Double = (maxResolution._1 * maxResolution._2) * Settings.get.vramSizes(0 max tier min 2) + + var budgetExhausted: Boolean = false // for especially expensive calls, bitblt // ----------------------------------------------------------------------- // @@ -96,12 +99,12 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device // ----------------------------------------------------------------------- // - private def consumeViewportPower(buffer: api.internal.TextBuffer, context: Context, budgetCost: Double, units: Int, factor: Double): Boolean = { - buffer match { - case _: component.GpuTextBuffer => true - case _ => + private def resolveInvokeCosts(idx: Int, context: Context, budgetCost: Double, units: Int, factor: Double): Boolean = { + idx match { + case RESERVED_SCREEN_INDEX => context.consumeCallBudget(budgetCost) consumePower(units, factor) + case _ => true } } @@ -140,38 +143,40 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device } else if (size > (totalVRAM - calculateUsedMemory)) { result(Unit, "not enough video memory") + } else if (node == null) { + result(Unit, "graphics card appears disconnected") } else { val format: PackedColor.ColorFormat = PackedColor.Depth.format(Settings.screenDepthsByTier(tier)) val buffer = new li.cil.oc.util.TextBuffer(width, height, format) - val page = component.GpuTextBuffer.wrap(nextAvailableBufferIndex, buffer) + val page = component.GpuTextBuffer.wrap(node.address, nextAvailableBufferIndex, buffer) addBuffer(page) result(page.id) } } // this event occurs when the gpu is told a page was removed - we need to notify the screen of this - // we do this because the VideoRamAware trait only notifies itself, it doesn't assume there is a screen - override def onBufferRamDestroy(ids: Array[Int]): Unit = { + // we do this because the VideoRamDevice trait only notifies itself, it doesn't assume there is a screen + override def onBufferRamDestroy(id: Int): Unit = { // first protect our buffer index - it needs to fall back to the screen if its buffer was removed - if (ids.contains(bufferIndex)) { - bufferIndex = RESERVED_SCREEN_INDEX - } - if (ids.nonEmpty) { + if (id != RESERVED_SCREEN_INDEX) { screen(RESERVED_SCREEN_INDEX, s => s match { - case oc: component.traits.VideoRamAware => result(oc.removeBuffers(ids)) + case oc: component.traits.VideoRamRasterizer => result(oc.removeBuffer(node.address, id)) case _ => result(true)// addon mod screen type that is not video ram aware }) - } else result(true) + } + if (id == bufferIndex) { + bufferIndex = RESERVED_SCREEN_INDEX + } } @Callback(direct = true, doc = """function(index: number): boolean -- Closes buffer at `index`. Returns true if a buffer closed. If the current buffer is closed, index moves to 0""") def freeBuffer(context: Context, args: Arguments): Array[AnyRef] = { val index: Int = args.optInteger(0, bufferIndex) - if (removeBuffers(Array(index))) result(true) + if (removeBuffers(Array(index)) == 1) result(true) else result(Unit, "no buffer at index") } - @Callback(direct = true, doc = """function(): number -- Closes all buffers and returns true on success. If the active buffer is closed, index moves to 0""") + @Callback(direct = true, doc = """function(): number -- Closes all buffers and returns the count. If the active buffer is closed, index moves to 0""") def freeAllBuffers(context: Context, args: Arguments): Array[AnyRef] = result(removeAllBuffers()) @Callback(direct = true, doc = """function(): number -- returns the total memory size of the gpu vram. This does not include the screen.""") @@ -194,13 +199,14 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device // large dirty buffers need throttling so their budget cost is more // clean buffers have no budget cost. src match { - case page: GpuTextBuffer if page.dirty => dst match { + case page: GpuTextBuffer => dst match { case _: GpuTextBuffer => 0.0 // no cost to write to ram - case _ => // screen target will need the new buffer + case _ if page.dirty => // screen target will need the new buffer // small buffers are cheap, so increase with size of buffer source - bitbltCosts(tier) * (src.getWidth * src.getHeight) / (maxResolution._1 * maxResolution._2) + bitbltCost * (src.getWidth * src.getHeight) / (maxResolution._1 * maxResolution._2) + case _ => .001 // bitblt a clean page to screen has a minimal cost } - case _ => 0.0 // from screen or from clean buffer is free + case _ => 0.0 // from screen is free } } @@ -209,7 +215,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device // rasterizing to the screen has the same cost as copy (in fact, screen-to-screen blt _is_ a copy dst match { case _: GpuTextBuffer => 0 - case _ => Settings.get.gpuCopyCost / (maxResolution._1 * maxResolution._2) + case _ => Settings.get.gpuCopyCost / 15 } } @@ -226,10 +232,27 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device val fromCol = args.optInteger(6, 1) val fromRow = args.optInteger(7, 1) - val budgetCost: Double = determineBitbltBudgetCost(dst, src) + var budgetCost: Double = determineBitbltBudgetCost(dst, src) val energyCost: Double = determineBitbltEnergyCost(dst) + val tierCredit: Double = ((tier + 1) * .5) + val overBudget: Double = budgetCost - tierCredit - if (consumeViewportPower(dst, context, budgetCost, w * h, energyCost)) { + if (overBudget > 0) { + if (budgetExhausted) { // we've thrown once before + if (overBudget > tierCredit) { // we need even more pause than just a single tierCredit + val pauseNeeded = overBudget - tierCredit + val seconds: Double = (pauseNeeded / tierCredit) / 20 + context.pause(seconds) + } + budgetCost = 0 // remove the rest of the budget cost at this point + } else { + budgetExhausted = true + throw new LimitReachedException() + } + } + budgetExhausted = false + + if (resolveInvokeCosts(dstIdx, context, budgetCost, w * h, energyCost)) { if (dstIdx == srcIdx) { val tx = col - fromCol val ty = row - fromRow @@ -264,7 +287,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device s.setForegroundColor(0xFFFFFF) s.setBackgroundColor(0x000000) s match { - case oc: component.traits.VideoRamAware => oc.removeAllBuffers() + case oc: component.traits.VideoRamRasterizer => oc.removeAllBuffers() case _ => } } @@ -276,13 +299,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device } @Callback(direct = true, doc = """function():string -- Get the address of the screen the GPU is currently bound to.""") - def getScreen(context: Context, args: Arguments): Array[AnyRef] = { - if (bufferIndex == RESERVED_SCREEN_INDEX) { - screen(s => result(s.node.address)) - } else { - result(Unit, "the current text buffer is video ram") - } - } + def getScreen(context: Context, args: Arguments): Array[AnyRef] = screen(RESERVED_SCREEN_INDEX, s => result(s.node.address)) @Callback(direct = true, doc = """function():number, boolean -- Get the current background color and whether it's from the palette or not.""") def getBackground(context: Context, args: Arguments): Array[AnyRef] = @@ -290,8 +307,10 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device @Callback(direct = true, doc = """function(value:number[, palette:boolean]):number, number or nil -- Sets the background color to the specified value. Optionally takes an explicit palette index. Returns the old value and if it was from the palette its palette index.""") def setBackground(context: Context, args: Arguments): Array[AnyRef] = { - context.consumeCallBudget(setBackgroundCosts(tier)) val color = args.checkInteger(0) + if (bufferIndex == RESERVED_SCREEN_INDEX) { + context.consumeCallBudget(setBackgroundCosts(tier)) + } screen(s => { val oldValue = s.getBackgroundColor val (oldColor, oldIndex) = @@ -312,8 +331,10 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device @Callback(direct = true, doc = """function(value:number[, palette:boolean]):number, number or nil -- Sets the foreground color to the specified value. Optionally takes an explicit palette index. Returns the old value and if it was from the palette its palette index.""") def setForeground(context: Context, args: Arguments): Array[AnyRef] = { - context.consumeCallBudget(setForegroundCosts(tier)) val color = args.checkInteger(0) + if (bufferIndex == RESERVED_SCREEN_INDEX) { + context.consumeCallBudget(setForegroundCosts(tier)) + } screen(s => { val oldValue = s.getForegroundColor val (oldColor, oldIndex) = @@ -338,10 +359,12 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device @Callback(direct = true, doc = """function(index:number, color:number):number -- Set the palette color at the specified palette index. Returns the previous value.""") def setPaletteColor(context: Context, args: Arguments): Array[AnyRef] = { - context.consumeCallBudget(setPaletteColorCosts(tier)) val index = args.checkInteger(0) val color = args.checkInteger(1) - context.pause(0.1) + if (bufferIndex == RESERVED_SCREEN_INDEX) { + context.consumeCallBudget(setPaletteColorCosts(tier)) + context.pause(0.1) + } screen(s => try { val oldColor = s.getPaletteColor(index) s.setPaletteColor(index, color) @@ -422,6 +445,16 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device @Callback(direct = true, doc = """function(x:number, y:number):string, number, number, number or nil, number or nil -- Get the value displayed on the screen at the specified index, as well as the foreground and background color. If the foreground or background is from the palette, returns the palette indices as fourth and fifth results, else nil, respectively.""") def get(context: Context, args: Arguments): Array[AnyRef] = { + // maybe one day: +// if (bufferIndex != RESERVED_SCREEN_INDEX && args.count() == 0) { +// return screen { +// case ram: GpuTextBuffer => { +// val nbt = new NBTTagCompound +// ram.data.save(nbt) +// result(nbt) +// } +// } +// } val x = args.checkInteger(0) - 1 val y = args.checkInteger(1) - 1 screen(s => { @@ -455,11 +488,10 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device val vertical = args.optBoolean(3, false) screen(s => { - if (consumeViewportPower(s, context, setCosts(tier), value.length, Settings.get.gpuSetCost)) { + if (resolveInvokeCosts(bufferIndex, context, setCosts(tier), value.length, Settings.get.gpuSetCost)) { s.set(x, y, value, vertical) result(true) - } - else result(Unit, "not enough energy") + } else result(Unit, "not enough energy") }) } @@ -472,7 +504,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device val tx = args.checkInteger(4) val ty = args.checkInteger(5) screen(s => { - if (consumeViewportPower(s, context, copyCosts(tier), w * h, Settings.get.gpuCopyCost)) { + if (resolveInvokeCosts(bufferIndex, context, copyCosts(tier), w * h, Settings.get.gpuCopyCost)) { s.copy(x, y, w, h, tx, ty) result(true) } @@ -490,7 +522,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device if (value.length == 1) screen(s => { val c = value.charAt(0) val cost = if (c == ' ') Settings.get.gpuClearCost else Settings.get.gpuFillCost - if (consumeViewportPower(s, context, fillCosts(tier), w * h, cost)) { + if (resolveInvokeCosts(bufferIndex, context, fillCosts(tier), w * h, cost)) { s.fill(x, y, w, h, value.charAt(0)) result(true) } @@ -608,7 +640,7 @@ class GraphicsCard(val tier: Int) extends AbstractManagedEnvironment with Device val nbtPage = nbtPages.getCompoundTagAt(i) val idx: Int = nbtPage.getInteger(NBT_PAGE_IDX) val data = nbtPage.getCompoundTag(NBT_PAGE_DATA) - loadBuffer(idx, data) + loadBuffer(node.address, idx, data) } } } diff --git a/src/main/scala/li/cil/oc/server/component/Trade.scala b/src/main/scala/li/cil/oc/server/component/Trade.scala index 3b7b86f73..c3c7a8f71 100644 --- a/src/main/scala/li/cil/oc/server/component/Trade.scala +++ b/src/main/scala/li/cil/oc/server/component/Trade.scala @@ -102,27 +102,32 @@ class Trade(val info: TradeInfo) extends AbstractValue { def completeTrade(inventory: IInventory, recipe: MerchantRecipe, exact: Boolean) : Boolean = { // Now we'll check if we have enough items to perform the trade, caching first - val firstInputStack = recipe.getItemToBuy - val secondInputStack = if (recipe.hasSecondItemToBuy) Option(recipe.getSecondItemToBuy) else None + info.merchant.get match { + case Some(merchant) => { + val firstInputStack = recipe.getItemToBuy + val secondInputStack = if (recipe.hasSecondItemToBuy) Option(recipe.getSecondItemToBuy) else None - def containsAccumulativeItemStack(stack: ItemStack) = - InventoryUtils.extractFromInventory(stack, inventory, null, simulate = true, exact = exact).getCount == 0 + def containsAccumulativeItemStack(stack: ItemStack) = + InventoryUtils.extractFromInventory(stack, inventory, null, simulate = true, exact = exact).getCount == 0 - // Check if we have enough to perform the trade. - if (!containsAccumulativeItemStack(firstInputStack) || !secondInputStack.forall(containsAccumulativeItemStack)) - return false + // Check if we have enough to perform the trade. + if (!containsAccumulativeItemStack(firstInputStack) || !secondInputStack.forall(containsAccumulativeItemStack)) + return false - // Now we need to check if we have enough inventory space to accept the item we get for the trade. - val outputStack = recipe.getItemToSell.copy() + // Now we need to check if we have enough inventory space to accept the item we get for the trade. + val outputStack = recipe.getItemToSell.copy() - // We established that out inventory allows to perform the trade, now actually do the trade. - InventoryUtils.extractFromInventory(firstInputStack, InventoryUtils.asItemHandler(inventory), exact = exact) - secondInputStack.map(InventoryUtils.extractFromInventory(_, InventoryUtils.asItemHandler(inventory), exact = exact)) - InventoryUtils.insertIntoInventory(outputStack, InventoryUtils.asItemHandler(inventory), outputStack.getCount) + // We established that out inventory allows to perform the trade, now actually do the trade. + InventoryUtils.extractFromInventory(firstInputStack, InventoryUtils.asItemHandler(inventory), exact = exact) + secondInputStack.map(InventoryUtils.extractFromInventory(_, InventoryUtils.asItemHandler(inventory), exact = exact)) + InventoryUtils.insertIntoInventory(outputStack, InventoryUtils.asItemHandler(inventory), outputStack.getCount) - // Tell the merchant we used the recipe, so MC can disable it and/or enable more recipes. - info.merchant.get.orNull.useRecipe(recipe) - true + // Tell the merchant we used the recipe, so MC can disable it and/or enable more recipes. + merchant.useRecipe(recipe) + true + } + case _ => false + } } } diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala index 0e6ab860a..40b707cc7 100644 --- a/src/main/scala/li/cil/oc/server/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala @@ -282,7 +282,7 @@ class Machine(val host: MachineHost) extends AbstractManagedEnvironment with mac override def consumeCallBudget(callCost: Double): Unit = { if (architecture.isInitialized && !inSynchronizedCall) { - val clampedCost = math.max(0.001, callCost) + val clampedCost = math.max(0.0, callCost) if (clampedCost > callBudget) { throw new LimitReachedException() } diff --git a/src/main/scala/li/cil/oc/util/NbtDataStream.scala b/src/main/scala/li/cil/oc/util/NbtDataStream.scala new file mode 100644 index 000000000..b56149310 --- /dev/null +++ b/src/main/scala/li/cil/oc/util/NbtDataStream.scala @@ -0,0 +1,49 @@ +package li.cil.oc.util + +import net.minecraft.nbt.NBTTagCompound + +object NbtDataStream { + def getShortArray(nbt: NBTTagCompound, key: String, array2d: Array[Array[Short]], w: Int, h: Int) : Boolean = { + if (!nbt.hasKey(key)) { + return false + } + + val rawByteReader = new java.io.ByteArrayInputStream(nbt.getByteArray(key)) + val memReader = new java.io.DataInputStream(rawByteReader) + for (y <- 0 until h) { + for (x <- 0 until w) { + if (2 > memReader.available()) { + return true // not great, but get out now + } + array2d(y)(x) = memReader.readShort() + } + } + true + } + + def getIntArrayLegacy(nbt: NBTTagCompound, key: String, array2d: Array[Array[Short]], w: Int, h: Int) : Boolean = { + if (!nbt.hasKey(key)) { + return false + } + // legacy format + val c = nbt.getIntArray(key) + for (y <- 0 until h) { + val rowColor = array2d(y) + for (x <- 0 until w) { + val index = x + y * w + if (index >= c.length) { + return true // not great, but, the read at least started + } + rowColor(x) = c(index).toShort + } + } + true + } + + def setShortArray(nbt: NBTTagCompound, key: String, array: Array[Short]): Unit = { + val rawByteWriter = new java.io.ByteArrayOutputStream() + val memWriter = new java.io.DataOutputStream(rawByteWriter) + array.foreach(memWriter.writeShort(_)) + nbt.setByteArray(key, rawByteWriter.toByteArray) + } +} diff --git a/src/main/scala/li/cil/oc/util/TextBuffer.scala b/src/main/scala/li/cil/oc/util/TextBuffer.scala index 3b4de3105..ce4393a47 100644 --- a/src/main/scala/li/cil/oc/util/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/util/TextBuffer.scala @@ -215,7 +215,14 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col val dstColorLine = color(row_index + yOffset) for (xOffset <- 0 until w) { val srcChar = src.buffer(fromRow + yOffset - 1)(fromCol + xOffset - 1) - val srcColor = src.color(fromRow + yOffset - 1)(fromCol + xOffset - 1) + var srcColor = src.color(fromRow + yOffset - 1)(fromCol + xOffset - 1) + + if (this.format.depth != src.format.depth) { + val fg = PackedColor.Color(PackedColor.unpackForeground(srcColor, src.format)) + val bg = PackedColor.Color(PackedColor.unpackBackground(srcColor, src.format)) + srcColor = PackedColor.pack(fg, bg, format) + } + if (srcChar != dstCharLine(col_index + xOffset) || srcColor != dstColorLine(col_index + xOffset)) { changed = true dstCharLine(col_index + xOffset) = srcChar @@ -262,15 +269,8 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col foreground = PackedColor.Color(nbt.getInteger("foreground"), nbt.getBoolean("foregroundIsPalette")) background = PackedColor.Color(nbt.getInteger("background"), nbt.getBoolean("backgroundIsPalette")) - val c = nbt.getIntArray("color") - for (i <- 0 until h) { - val rowColor = color(i) - for (j <- 0 until w) { - val index = j + i * w - if (index < c.length) { - rowColor(j) = c(index).toShort - } - } + if (!NbtDataStream.getShortArray(nbt, "colors", color, w, h)) { + NbtDataStream.getIntArrayLegacy(nbt, "color", color, w, h) } } @@ -291,10 +291,10 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col nbt.setInteger("background", _background.value) nbt.setBoolean("backgroundIsPalette", _background.isPalette) - nbt.setTag("color", new NBTTagIntArray(color.flatten.map(_.toInt))) + NbtDataStream.setShortArray(nbt, "colors", color.flatten.map(_.toShort)) } - override def toString = { + override def toString: String = { val b = StringBuilder.newBuilder if (buffer.length > 0) { b.appendAll(buffer(0))