diff --git a/build.properties b/build.properties index 5455fea41..97c025135 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ minecraft.version=1.8 forge.version=11.14.1.1354 -oc.version=1.5.6 +oc.version=1.5.7 oc.subversion=dev ae2.version=rv2-beta-22 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/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") diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 07a2b2759..122fc4a3e 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -297,6 +297,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 91d9aab85..d315aa202 100644 --- a/src/main/scala/li/cil/oc/common/block/Print.scala +++ b/src/main/scala/li/cil/oc/common/block/Print.scala @@ -74,6 +74,18 @@ class Print(protected implicit val tileTag: ClassTag[tileentity.Print]) extends override def isOpaqueCube = false + override def getLightValue(world: IBlockAccess, pos: BlockPos): Int = + world.getTileEntity(pos) match { + case print: tileentity.Print => print.data.lightLevel + case _ => super.getLightValue(world, pos) + } + + override def getLightOpacity(world: IBlockAccess, pos: BlockPos): Int = + world.getTileEntity(pos) match { + case print: tileentity.Print => (print.data.opacity * 4).toInt + case _ => super.getLightOpacity(world, pos) + } + override def isVisuallyOpaque = false override def isFullCube = false 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 eaf765f0b..f63817e24 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,6 +1,7 @@ package li.cil.oc.common.item.data import li.cil.oc.Constants +import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.util.ExtendedNBT._ import net.minecraft.item.ItemStack @@ -19,34 +20,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() = { @@ -57,16 +78,42 @@ 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.fromBounds( + 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")) { // Compatibility with shapes created with earlier dev-builds. - val minX = nbt.getByte("minX") / 16f - val minY = nbt.getByte("minY") / 16f - val minZ = nbt.getByte("minZ") / 16f - val maxX = nbt.getByte("maxX") / 16f - val maxY = nbt.getByte("maxY") / 16f - val maxZ = nbt.getByte("maxZ") / 16f + val minX = nbt.getByte("minX") / 16f + val minY = nbt.getByte("minY") / 16f + val minZ = nbt.getByte("minZ") / 16f + val maxX = nbt.getByte("maxX") / 16f + val maxY = nbt.getByte("maxY") / 16f + val maxZ = nbt.getByte("maxZ") / 16f AxisAlignedBB.fromBounds(minX, minY, minZ, maxX, maxY, maxZ) } else { 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 f0f51ea69..92f179f9e 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(getPos) if (data.emitRedstone) { - EnumFacing.values().foreach(output(_, if (state) 15 else 0)) + EnumFacing.values().foreach(output(_, if (state) data.redstoneLevel else 0)) } if (state && data.isButtonMode) { world.scheduleUpdate(getPos, blockType, blockType.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 375939af6..285facf1e 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 914d14ddd..f22fa1a57 100644 --- a/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala +++ b/src/main/scala/li/cil/oc/integration/fmp/PrintPart.scala @@ -94,6 +94,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 = { @@ -127,7 +129,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 // ----------------------------------------------------------------------- // @@ -250,11 +252,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 = { 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 29c4f6db1..4d47bc182 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -40,6 +40,8 @@ import net.minecraftforge.common.util.FakePlayerFactory import net.minecraftforge.fluids.FluidRegistry import net.minecraftforge.fluids.FluidStack import net.minecraftforge.fluids.IFluidHandler +import net.minecraftforge.fml.common.Loader +import net.minecraftforge.fml.common.ModAPIManager import scala.collection.convert.WrapAsScala._ import scala.collection.mutable @@ -110,6 +112,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 ba3a22f61..b71c2adc4 100644 --- a/src/main/scala/li/cil/oc/server/driver/Registry.scala +++ b/src/main/scala/li/cil/oc/server/driver/Registry.scala @@ -45,17 +45,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, pos: BlockPos) =