From ffd0908567aa2eec449a0ebbb59ded928fa08a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 6 Apr 2015 13:11:52 +0200 Subject: [PATCH 1/4] Allow specifying level of redstone signal emitted by prints. Allow prints to emit light, if so configured. Max light level capped to 8 by default. --- src/main/resources/application.conf | 5 ++ src/main/scala/li/cil/oc/Settings.scala | 1 + .../scala/li/cil/oc/common/block/Print.scala | 12 +++++ .../cil/oc/common/item/data/PrintData.scala | 53 +++++++++++++++++-- .../li/cil/oc/common/tileentity/Print.scala | 2 +- .../li/cil/oc/common/tileentity/Printer.scala | 21 ++++++-- .../li/cil/oc/integration/fmp/PrintPart.scala | 12 +++-- 7 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index db5fecc34..70fc15b86 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1045,6 +1045,11 @@ opencomputers { # Whether Chamelium is edible or not. When eaten, it gives a (short) # invisibility buff, and (slightly longer) blindness debuff. chameliumEdible: true + + # The maximum light level a printed block can emit. This defaults to + # a value similar to that of a redstone torch, because by default the + # material prints are made of contains redstone, but no glowstone. + maxPrintLightLevel: 8 } # Settings for mod integration (the mod previously known as OpenComponents). diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 177e9206c..522df4207 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -296,6 +296,7 @@ class Settings(val config: Config) { val maxPrintComplexity = config.getInt("misc.maxPrinterShapes") val printRecycleRate = config.getDouble("misc.printRecycleRate") val chameliumEdible = config.getBoolean("misc.chameliumEdible") + val maxPrintLightLevel = config.getInt("misc.maxPrintLightLevel") max 0 min 15 // ----------------------------------------------------------------------- // // integration diff --git a/src/main/scala/li/cil/oc/common/block/Print.scala b/src/main/scala/li/cil/oc/common/block/Print.scala index 4f65738a0..4e884208e 100644 --- a/src/main/scala/li/cil/oc/common/block/Print.scala +++ b/src/main/scala/li/cil/oc/common/block/Print.scala @@ -56,6 +56,18 @@ class Print(protected implicit val tileTag: ClassTag[tileentity.Print]) extends data.tooltip.foreach(s => tooltip.addAll(s.lines.toIterable)) } + override def getLightValue(world: IBlockAccess, x: Int, y: Int, z: Int): Int = + world.getTileEntity(x, y, z) match { + case print: tileentity.Print => print.data.lightLevel + case _ => super.getLightValue(world, x, y, z) + } + + override def getLightOpacity(world: IBlockAccess, x: Int, y: Int, z: Int): Int = + world.getTileEntity(x, y, z) match { + case print: tileentity.Print => (print.data.opacity * 4).toInt + case _ => super.getLightOpacity(world, x, y, z) + } + override def shouldSideBeRendered(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = true override def isBlockSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = isSideSolid(world, x, y, z, side) diff --git a/src/main/scala/li/cil/oc/common/item/data/PrintData.scala b/src/main/scala/li/cil/oc/common/item/data/PrintData.scala index 62f7f013b..945b12cc0 100644 --- a/src/main/scala/li/cil/oc/common/item/data/PrintData.scala +++ b/src/main/scala/li/cil/oc/common/item/data/PrintData.scala @@ -1,5 +1,6 @@ package li.cil.oc.common.item.data +import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.util.ExtendedNBT._ import net.minecraft.item.ItemStack @@ -18,34 +19,54 @@ class PrintData extends ItemData { var label: Option[String] = None var tooltip: Option[String] = None var isButtonMode = false - var emitRedstone = false + var redstoneLevel = 0 var pressurePlate = false val stateOff = mutable.Set.empty[PrintData.Shape] val stateOn = mutable.Set.empty[PrintData.Shape] var isBeaconBase = false + var lightLevel = 0 + + def emitRedstone = redstoneLevel > 0 + + def opacity = { + if (opacityDirty) { + opacityDirty = false + opacity_ = PrintData.computeApproximateOpacity(stateOn) min PrintData.computeApproximateOpacity(stateOff) + } + opacity_ + } + + // lazily computed and stored, because potentially slow + private var opacity_ = 0f + private var opacityDirty = true override def load(nbt: NBTTagCompound): Unit = { if (nbt.hasKey("label")) label = Option(nbt.getString("label")) else label = None if (nbt.hasKey("tooltip")) tooltip = Option(nbt.getString("tooltip")) else tooltip = None isButtonMode = nbt.getBoolean("isButtonMode") - emitRedstone = nbt.getBoolean("emitRedstone") + redstoneLevel = nbt.getInteger("redstoneLevel") max 0 min 15 + if (nbt.getBoolean("emitRedstone")) redstoneLevel = 15 pressurePlate = nbt.getBoolean("pressurePlate") stateOff.clear() stateOff ++= nbt.getTagList("stateOff", NBT.TAG_COMPOUND).map(PrintData.nbtToShape) stateOn.clear() stateOn ++= nbt.getTagList("stateOn", NBT.TAG_COMPOUND).map(PrintData.nbtToShape) isBeaconBase = nbt.getBoolean("isBeaconBase") + lightLevel = (nbt.getByte("lightLevel") & 0xFF) max 0 min Settings.get.maxPrintLightLevel + + opacityDirty = true } override def save(nbt: NBTTagCompound): Unit = { label.foreach(nbt.setString("label", _)) tooltip.foreach(nbt.setString("tooltip", _)) nbt.setBoolean("isButtonMode", isButtonMode) - nbt.setBoolean("emitRedstone", emitRedstone) + nbt.setInteger("redstoneLevel", redstoneLevel) nbt.setBoolean("pressurePlate", pressurePlate) nbt.setNewTagList("stateOff", stateOff.map(PrintData.shapeToNBT)) nbt.setNewTagList("stateOn", stateOn.map(PrintData.shapeToNBT)) nbt.setBoolean("isBeaconBase", isBeaconBase) + nbt.setByte("lightLevel", lightLevel.toByte) } def createItemStack() = { @@ -56,6 +77,32 @@ class PrintData extends ItemData { } object PrintData { + // The following logic is used to approximate the opacity of a print, for + // which we use the volume as a heuristic. Because computing the actual + // volume is a) expensive b) not necessarily a good heuristic (e.g. a + // "dotted grid") we take a shortcut and divide the space into a few + // sub-sections, for each of which we check if there's anything in it. + // If so, we consider that area "opaque". To compensate, prints can never + // be fully light-opaque. This gives a little bit of shading as a nice + // effect, but avoid it looking derpy when there are only a few sparse + // shapes in the model. + private val stepping = 4 + private val step = stepping / 16f + private val invMaxVolume = 1f / (stepping * stepping * stepping) + + def computeApproximateOpacity(shapes: Iterable[PrintData.Shape]) = { + var volume = 1f + if (shapes.size > 0) for (x <- 0 until 16 / stepping; y <- 0 until 16 / stepping; z <- 0 until 16 / stepping) { + val bounds = AxisAlignedBB.getBoundingBox( + x * step, y * step, z * step, + (x + 1) * step, (y + 1) * step, (z + 1) * step) + if (!shapes.exists(_.bounds.intersectsWith(bounds))) { + volume -= invMaxVolume + } + } + volume + } + def nbtToShape(nbt: NBTTagCompound): Shape = { val aabb = if (nbt.hasKey("minX")) { diff --git a/src/main/scala/li/cil/oc/common/tileentity/Print.scala b/src/main/scala/li/cil/oc/common/tileentity/Print.scala index 676eca332..cb6926052 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Print.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Print.scala @@ -33,7 +33,7 @@ class Print extends traits.TileEntity with traits.RedstoneAware with traits.Rota world.playSoundEffect(x + 0.5, y + 0.5, z + 0.5, "random.click", 0.3F, if (state) 0.6F else 0.5F) world.markBlockForUpdate(x, y, z) if (data.emitRedstone) { - ForgeDirection.VALID_DIRECTIONS.foreach(output(_, if (state) 15 else 0)) + ForgeDirection.VALID_DIRECTIONS.foreach(output(_, if (state) data.redstoneLevel else 0)) } if (state && data.isButtonMode) { world.scheduleBlockUpdate(x, y, z, block, block.tickRate(world)) diff --git a/src/main/scala/li/cil/oc/common/tileentity/Printer.scala b/src/main/scala/li/cil/oc/common/tileentity/Printer.scala index 7e6016d50..f1805c50c 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Printer.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Printer.scala @@ -104,16 +104,29 @@ class Printer extends traits.Environment with traits.Inventory with traits.Rotat result(data.tooltip.orNull) } - @Callback(doc = """function(value:boolean) -- Set whether the printed block should emit redstone when in its active state.""") + @Callback(doc = """function(value:boolean or number) -- Set whether the printed block should emit redstone when in its active state.""") def setRedstoneEmitter(context: Context, args: Arguments): Array[Object] = { - data.emitRedstone = args.checkBoolean(0) + if (args.isBoolean(0)) data.redstoneLevel = if (args.checkBoolean(0)) 15 else 0 + else data.redstoneLevel = args.checkInteger(0) max 0 min 15 isActive = false // Needs committing. null } - @Callback(doc = """function():boolean -- Get whether the printed block should emit redstone when in its active state.""") + @Callback(doc = """function():boolean, number -- Get whether the printed block should emit redstone when in its active state.""") def isRedstoneEmitter(context: Context, args: Arguments): Array[Object] = { - result(data.emitRedstone) + result(data.emitRedstone, data.redstoneLevel) + } + + @Callback(doc = """function(value:number) -- Set what light level the printed block should have.""") + def setLightLevel(context: Context, args: Arguments): Array[Object] = { + data.lightLevel = args.checkInteger(0) max 0 min Settings.get.maxPrintLightLevel + isActive = false // Needs committing. + null + } + + @Callback(doc = """function():number -- Get which light level the printed block should have.""") + def getLightLevel(context: Context, args: Arguments): Array[Object] = { + result(data.lightLevel) } @Callback(doc = """function(value:boolean) -- Set whether the printed block should automatically return to its off state.""") diff --git a/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala b/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala index cd6df08e2..2a41b71d0 100644 --- a/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala +++ b/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala @@ -110,6 +110,8 @@ class PrintPart(val original: Option[tileentity.Print] = None) extends SimpleBlo override def doesTick = false + override def getLightValue: Int = data.lightLevel + override def getBounds = new Cuboid6(if (state) boundsOn else boundsOff) override def getOcclusionBoxes = { @@ -143,7 +145,7 @@ class PrintPart(val original: Option[tileentity.Print] = None) extends SimpleBlo override def strongPowerLevel(side: Int): Int = weakPowerLevel(side) - override def weakPowerLevel(side: Int): Int = if (data.emitRedstone && state) 15 else 0 + override def weakPowerLevel(side: Int): Int = if (data.emitRedstone && state) data.redstoneLevel else 0 // ----------------------------------------------------------------------- // @@ -266,11 +268,11 @@ class PrintPart(val original: Option[tileentity.Print] = None) extends SimpleBlo } protected def computeInput(): Int = { - val inner = tile.partList.foldLeft(false)((powered, part) => part match { - case print: PrintPart => powered || (print.state && print.data.emitRedstone) - case _ => powered + val inner = tile.partList.foldLeft(0)((power, part) => part match { + case print: PrintPart if print.state && print.data.emitRedstone => math.max(power, print.data.redstoneLevel) + case _ => power }) - if (inner) 15 else ForgeDirection.VALID_DIRECTIONS.map(computeInput).max + math.max(inner, ForgeDirection.VALID_DIRECTIONS.map(computeInput).max) } protected def computeInput(side: ForgeDirection): Int = { From 69cc18bcf888ebb612173ea6eaa5071fda9c3c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 6 Apr 2015 13:54:54 +0200 Subject: [PATCH 2/4] In the meantime... this was easier than I thought, after remembering I could just getmetatable my way up... --- .../opencomputers/loot/OpenOS/bin/lua.lua | 73 ++++++++++++++++++- .../loot/OpenOS/boot/04_component.lua | 24 +++++- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/lua.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/lua.lua index 7265a9c3f..d1176b111 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/lua.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/lua.lua @@ -27,12 +27,77 @@ if #args == 0 or options.i then return module end end - setmetatable(env, {__index = function(t, k) - return _ENV[k] or optrequire(k) - end}) + setmetatable(env, { + __index = function(t, k) + _ENV[k] = _ENV[k] or optrequire(k) + return _ENV[k] + end, + __pairs = function(self) + local t = self + return function(_, key) + local k, v = next(t, key) + if not k and t == env then + t = _ENV + k, v = next(t) + end + if not k and t == _ENV then + t = package.loaded + k, v = next(t) + end + return k, v + end + end + }) local history = {} + local function findTable(t, path) + if type(t) ~= "table" then return nil end + if not path or #path == 0 then return t end + local name = string.match(path, "[^.]+") + for k, v in pairs(t) do + if k == name then + return findTable(v, string.sub(path, #name + 2)) + end + end + local mt = getmetatable(t) + if t == env then mt = {__index=_ENV} end + if mt then + return findTable(mt.__index, path) + end + return nil + end + local function findKeys(t, r, prefix, name) + if type(t) ~= "table" then return end + for k, v in pairs(t) do + if string.match(k, "^"..name) then + local postfix = "" + if type(v) == "function" then postfix = "()" + elseif type(v) == "table" and getmetatable(v) and getmetatable(v).__call then postfix = "()" + elseif type(v) == "table" then postfix = "." + end + table.insert(r, prefix..k..postfix) + end + end + local mt = getmetatable(t) + if t == env then mt = {__index=_ENV} end + if mt then + return findKeys(mt.__index, r, prefix, name) + end + end + local function hint(line, index) + local path = string.match(line, "[a-zA-Z_][a-zA-Z0-9_.]*$") + if not path then return nil end + local suffix = string.match(path, "[^.]+$") or "" + local prefix = string.sub(path, 1, #path - #suffix) + local t = findTable(env, prefix) + if not t then return nil end + local r = {} + findKeys(t, r, string.sub(line, 1, #line - #suffix), suffix) + table.sort(r) + return r + end + component.gpu.setForeground(0xFFFFFF) term.write("Lua 5.2.3 Copyright (C) 1994-2013 Lua.org, PUC-Rio\n") component.gpu.setForeground(0xFFFF00) @@ -45,7 +110,7 @@ if #args == 0 or options.i then local foreground = component.gpu.setForeground(0x00FF00) term.write(tostring(env._PROMPT or "lua> ")) component.gpu.setForeground(foreground) - local command = term.read(history) + local command = term.read(history, nil, hint) if command == nil then -- eof return end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/04_component.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/04_component.lua index c58ca54c0..5c084a5ca 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/boot/04_component.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/boot/04_component.lua @@ -10,9 +10,27 @@ local primaries = {} -- This allows writing component.modem.open(123) instead of writing -- component.getPrimary("modem").open(123), which may be nicer to read. -setmetatable(component, { __index = function(_, key) - return component.getPrimary(key) - end }) +setmetatable(component, { + __index = function(_, key) + return component.getPrimary(key) + end, + __pairs = function(self) + local parent = false + return function(_, key) + if parent then + return next(primaries, key) + else + local k, v = next(self, key) + if not k then + parent = true + return next(primaries) + else + return k, v + end + end + end + end +}) function component.get(address, componentType) checkArg(1, address, "string") From ed516fc9d6eff002cebd3d56f5121636d97c7584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 6 Apr 2015 14:33:55 +0200 Subject: [PATCH 3/4] Added debug.isModLoaded. Added debug level log messages for registered drivers. --- .../li/cil/oc/server/component/DebugCard.scala | 9 +++++++++ .../scala/li/cil/oc/server/driver/Registry.scala | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) 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 c0c965246..efa2c27ad 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -1,6 +1,8 @@ package li.cil.oc.server.component import com.google.common.base.Strings +import cpw.mods.fml.common.Loader +import cpw.mods.fml.common.ModAPIManager import li.cil.oc.OpenComputers import li.cil.oc.Settings import li.cil.oc.api.Network @@ -108,6 +110,13 @@ class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { result(new DebugCard.PlayerValue(args.checkString(0))) } + @Callback(doc = """function(name:string):boolean -- Get whether a mod or API is loaded.""") + def isModLoaded(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() + val name = args.checkString(0) + result(Loader.isModLoaded(name) || ModAPIManager.INSTANCE.hasAPI(name)) + } + @Callback(doc = """function(command:string):number -- Runs an arbitrary command using a fake player.""") def runCommand(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() diff --git a/src/main/scala/li/cil/oc/server/driver/Registry.scala b/src/main/scala/li/cil/oc/server/driver/Registry.scala index 811ad3c87..a2f58e0bd 100644 --- a/src/main/scala/li/cil/oc/server/driver/Registry.scala +++ b/src/main/scala/li/cil/oc/server/driver/Registry.scala @@ -44,17 +44,26 @@ private[oc] object Registry extends api.detail.DriverAPI { override def add(driver: api.driver.Block) { if (locked) throw new IllegalStateException("Please register all drivers in the init phase.") - if (!blocks.contains(driver)) blocks += driver + if (!blocks.contains(driver)) { + OpenComputers.log.debug(s"Registering block driver ${driver.getClass.getName}.") + blocks += driver + } } override def add(driver: api.driver.Item) { if (locked) throw new IllegalStateException("Please register all drivers in the init phase.") - if (!blocks.contains(driver)) items += driver + if (!blocks.contains(driver)) { + OpenComputers.log.debug(s"Registering item driver ${driver.getClass.getName}.") + items += driver + } } override def add(converter: Converter) { if (locked) throw new IllegalStateException("Please register all converters in the init phase.") - if (!converters.contains(converter)) converters += converter + if (!converters.contains(converter)) { + OpenComputers.log.debug(s"Registering converter ${converter.getClass.getName}.") + converters += converter + } } override def driverFor(world: World, x: Int, y: Int, z: Int) = From 57efc24995a2be0edc088399c8eb4ddad0d284c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 6 Apr 2015 14:34:21 +0200 Subject: [PATCH 4/4] Version bump. --- build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.properties b/build.properties index eba276f99..8974771ba 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ minecraft.version=1.7.10 forge.version=10.13.2.1291 -oc.version=1.5.6 +oc.version=1.5.7 oc.subversion=dev ae2.version=rv2-beta-22