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 = {