diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index be996ee43..f68159751 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -729,6 +729,11 @@ opencomputers { # Energy it costs to re-program an EEPROM. This is deliberately # expensive, to discourage frequent re-writing of EEPROMs. eepromWrite: 50 + + # How much energy is required per shape in a 3D print. With the + # default of 200 for example, it will take 1600 energy to print a + # block with 8 shapes in it. + printerShape: 200 } # The rate at which different blocks accept external power. All of these @@ -1011,6 +1016,12 @@ opencomputers { # format 'itemid@damage', were the damage is optional. # Examples: 'OpenComputers:case3', 'minecraft:stonebrick@1' assemblerBlacklist: [] + + # The maximum number of shape for a single 3D print allowed. This is for + # the sum of shapes in both states (off and on), so e.g. with the default + # of 32 you can use 10 shapes for the off state and 22 for the on state + # at maximum. + maxPrinterShapes: 32 } # Settings for mod integration (the mod previously known as OpenComponents). diff --git a/src/main/resources/assets/opencomputers/textures/gui/printer.png b/src/main/resources/assets/opencomputers/textures/gui/printer.png new file mode 100644 index 000000000..f7e756e23 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/gui/printer.png differ diff --git a/src/main/resources/assets/opencomputers/textures/gui/printer_progress.png b/src/main/resources/assets/opencomputers/textures/gui/printer_progress.png new file mode 100644 index 000000000..a4df8cc55 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/gui/printer_progress.png differ diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index e899e34a7..73a96a9f7 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -192,6 +192,7 @@ class Settings(val config: Config) { val chunkloaderCost = config.getDouble("power.cost.chunkloaderCost") max 0 val pistonCost = config.getDouble("power.cost.pistonPush") max 0 val eepromWriteCost = config.getDouble("power.cost.eepromWrite") max 0 + val printShapeCost = config.getDouble("power.cost.printerShape") max 0 // power.rate val accessPointRate = config.getDouble("power.rate.accessPoint") max 0 @@ -290,6 +291,7 @@ class Settings(val config: Config) { val databaseEntriesPerTier = Array(9, 25, 81) // Not configurable because of GUI design. val presentChance = config.getDouble("misc.presentChance") max 0 min 1 val assemblerBlacklist = config.getStringList("misc.assemblerBlacklist") + val maxPrintComplexity = config.getInt("misc.maxPrinterShapes") // ----------------------------------------------------------------------- // // integration diff --git a/src/main/scala/li/cil/oc/client/GuiHandler.scala b/src/main/scala/li/cil/oc/client/GuiHandler.scala index 6f1a078cf..944921e12 100644 --- a/src/main/scala/li/cil/oc/client/GuiHandler.scala +++ b/src/main/scala/li/cil/oc/client/GuiHandler.scala @@ -32,6 +32,8 @@ object GuiHandler extends CommonGuiHandler { new gui.Disassembler(player.inventory, t) case t: tileentity.DiskDrive if id == GuiType.DiskDrive.id => new gui.DiskDrive(player.inventory, t) + case t: tileentity.Printer if id == GuiType.Printer.id => + new gui.Printer(player.inventory, t) case t: tileentity.Raid if id == GuiType.Raid.id => new gui.Raid(player.inventory, t) case t: tileentity.RobotProxy if id == GuiType.Robot.id => diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala index 324cab790..4d02efe8f 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -25,6 +25,8 @@ object Textures { val guiDisassembler = new ResourceLocation(Settings.resourceDomain, "textures/gui/disassembler.png") val guiDrone = new ResourceLocation(Settings.resourceDomain, "textures/gui/drone.png") val guiKeyboardMissing = new ResourceLocation(Settings.resourceDomain, "textures/gui/keyboard_missing.png") + val guiPrinter = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer.png") + val guiPrinterProgress = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer_progress.png") val guiRaid = new ResourceLocation(Settings.resourceDomain, "textures/gui/raid.png") val guiRange = new ResourceLocation(Settings.resourceDomain, "textures/gui/range.png") val guiRobot = new ResourceLocation(Settings.resourceDomain, "textures/gui/robot.png") diff --git a/src/main/scala/li/cil/oc/client/gui/Printer.scala b/src/main/scala/li/cil/oc/client/gui/Printer.scala new file mode 100644 index 000000000..3ad735a26 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/gui/Printer.scala @@ -0,0 +1,66 @@ +package li.cil.oc.client.gui + +import li.cil.oc.Localization +import li.cil.oc.client.Textures +import li.cil.oc.client.gui.widget.ProgressBar +import li.cil.oc.client.{PacketSender => ClientPacketSender} +import li.cil.oc.common.container +import li.cil.oc.common.container.ComponentSlot +import li.cil.oc.common.tileentity +import net.minecraft.entity.player.InventoryPlayer +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 + +class Printer(playerInventory: InventoryPlayer, val printer: tileentity.Printer) extends DynamicGuiContainer(new container.Printer(playerInventory, printer)) { + xSize = 176 + ySize = 166 + + private val progress = addWidget(new ProgressBar(104, 21) { + override def width = 46 + + override def height = 46 + + override def barTexture = Textures.guiPrinterProgress + }) + + private def assemblerContainer = inventorySlots.asInstanceOf[container.Printer] + + override def initGui() { + super.initGui() + } + + override def drawSecondaryForegroundLayer(mouseX: Int, mouseY: Int) = { + super.drawSecondaryForegroundLayer(mouseX, mouseY) + fontRendererObj.drawString( + Localization.localizeImmediately(printer.getInventoryName), + 8, 6, 0x404040) + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) // Me lazy... prevents NEI render glitch. + if (assemblerContainer.isAssembling && func_146978_c(progress.x, progress.y, progress.width, progress.height, mouseX, mouseY)) { + val tooltip = new java.util.ArrayList[String] + val timeRemaining = formatTime(assemblerContainer.assemblyRemainingTime) + tooltip.add(Localization.Assembler.Progress(assemblerContainer.assemblyProgress, timeRemaining)) + copiedDrawHoveringText(tooltip, mouseX - guiLeft, mouseY - guiTop, fontRendererObj) + } + GL11.glPopAttrib() + } + + private def formatTime(seconds: Int) = { + // Assembly times should not / rarely exceed one hour, so this is good enough. + if (seconds < 60) f"0:$seconds%02d" + else f"${seconds / 60}:${seconds % 60}%02d" + } + + override def drawGuiContainerBackgroundLayer(dt: Float, mouseX: Int, mouseY: Int) { + GL11.glColor3f(1, 1, 1) // Required under Linux. + mc.renderEngine.bindTexture(Textures.guiPrinter) + drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize) + if (assemblerContainer.isAssembling) progress.level = assemblerContainer.assemblyProgress / 100.0 + else progress.level = 0 + drawWidgets() + drawInventorySlots() + } + + override protected def drawDisabledSlot(slot: ComponentSlot) {} + + override def doesGuiPauseGame = false +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/gui/widget/ProgressBar.scala b/src/main/scala/li/cil/oc/client/gui/widget/ProgressBar.scala index 7f9beb31e..22541fbe5 100644 --- a/src/main/scala/li/cil/oc/client/gui/widget/ProgressBar.scala +++ b/src/main/scala/li/cil/oc/client/gui/widget/ProgressBar.scala @@ -9,6 +9,8 @@ class ProgressBar(val x: Int, val y: Int) extends Widget { override def height = 12 + def barTexture = Textures.guiBar + var level = 0.0 def draw() { @@ -21,7 +23,7 @@ class ProgressBar(val x: Int, val y: Int) extends Widget { val ty = owner.windowY + y val w = width * level - Minecraft.getMinecraft.renderEngine.bindTexture(Textures.guiBar) + Minecraft.getMinecraft.renderEngine.bindTexture(barTexture) val t = Tessellator.instance t.startDrawingQuads() t.addVertexWithUV(tx, ty, owner.windowZ, u0, v0) diff --git a/src/main/scala/li/cil/oc/client/gui/widget/Widget.scala b/src/main/scala/li/cil/oc/client/gui/widget/Widget.scala index c97facd41..db1552137 100644 --- a/src/main/scala/li/cil/oc/client/gui/widget/Widget.scala +++ b/src/main/scala/li/cil/oc/client/gui/widget/Widget.scala @@ -11,5 +11,5 @@ abstract class Widget { def height: Int - def draw() + def draw(): Unit } diff --git a/src/main/scala/li/cil/oc/common/GuiHandler.scala b/src/main/scala/li/cil/oc/common/GuiHandler.scala index 366963ebc..b6a42bfee 100644 --- a/src/main/scala/li/cil/oc/common/GuiHandler.scala +++ b/src/main/scala/li/cil/oc/common/GuiHandler.scala @@ -24,6 +24,8 @@ abstract class GuiHandler extends IGuiHandler { new container.Disassembler(player.inventory, t) case t: tileentity.DiskDrive if id == GuiType.DiskDrive.id => new container.DiskDrive(player.inventory, t) + case t: tileentity.Printer if id == GuiType.Printer.id => + new container.Printer(player.inventory, t) case t: tileentity.Raid if id == GuiType.Raid.id => new container.Raid(player.inventory, t) case t: tileentity.RobotProxy if id == GuiType.Robot.id => diff --git a/src/main/scala/li/cil/oc/common/GuiType.scala b/src/main/scala/li/cil/oc/common/GuiType.scala index 7628f8178..17927f40b 100644 --- a/src/main/scala/li/cil/oc/common/GuiType.scala +++ b/src/main/scala/li/cil/oc/common/GuiType.scala @@ -21,6 +21,7 @@ object GuiType extends ScalaEnum { val Disassembler = new EnumVal { def name = "Disassembler"; def subType = GuiType.Category.Block } val DiskDrive = new EnumVal { def name = "DiskDrive"; def subType = GuiType.Category.Block } val Drone = new EnumVal { def name = "Drone"; def subType = GuiType.Category.Entity } + val Printer = new EnumVal { def name = "Printer"; def subType = GuiType.Category.Block } val Rack = new EnumVal { def name = "Rack"; def subType = GuiType.Category.Block } val Raid = new EnumVal { def name = "Raid"; def subType = GuiType.Category.Block } val Robot = new EnumVal { def name = "Robot"; def subType = GuiType.Category.Block } 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 ffde03aa7..f28a8540f 100644 --- a/src/main/scala/li/cil/oc/common/block/Print.scala +++ b/src/main/scala/li/cil/oc/common/block/Print.scala @@ -2,6 +2,7 @@ package li.cil.oc.common.block import li.cil.oc.Settings import li.cil.oc.common.tileentity +import li.cil.oc.util.ExtendedAABB import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack @@ -38,6 +39,10 @@ class Print(protected implicit val tileTag: ClassTag[tileentity.Print]) extends } } + override def setBlockBoundsForItemRender(metadata: Int): Unit = { + setBlockBounds(ExtendedAABB.unitBounds) + } + // ----------------------------------------------------------------------- // override def hasTileEntity(metadata: Int) = true diff --git a/src/main/scala/li/cil/oc/common/block/Printer.scala b/src/main/scala/li/cil/oc/common/block/Printer.scala new file mode 100644 index 000000000..d0be27b26 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/block/Printer.scala @@ -0,0 +1,42 @@ +package li.cil.oc.common.block + +import li.cil.oc.Settings +import li.cil.oc.client.Textures +import li.cil.oc.common.GuiType +import li.cil.oc.common.tileentity +import net.minecraft.client.renderer.texture.IIconRegister +import net.minecraft.world.IBlockAccess +import net.minecraft.world.World +import net.minecraftforge.common.util.ForgeDirection + +class Printer extends SimpleBlock with traits.SpecialBlock with traits.PowerAcceptor with traits.StateAware with traits.GUI { + override protected def customTextures = Array( + None, + Some("AssemblerTop"), + Some("AssemblerSide"), + Some("AssemblerSide"), + Some("AssemblerSide"), + Some("AssemblerSide") + ) + + override def registerBlockIcons(iconRegister: IIconRegister) = { + super.registerBlockIcons(iconRegister) + Textures.Assembler.iconSideAssembling = iconRegister.registerIcon(Settings.resourceDomain + ":AssemblerSideAssembling") + Textures.Assembler.iconSideOn = iconRegister.registerIcon(Settings.resourceDomain + ":AssemblerSideOn") + Textures.Assembler.iconTopOn = iconRegister.registerIcon(Settings.resourceDomain + ":AssemblerTopOn") + } + + override def isBlockSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = side == ForgeDirection.DOWN + + override def isSideSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection) = side == ForgeDirection.DOWN + + // ----------------------------------------------------------------------- // + + override def energyThroughput = Settings.get.assemblerRate + + override def guiType = GuiType.Printer + + override def hasTileEntity(metadata: Int) = true + + override def createTileEntity(world: World, metadata: Int) = new tileentity.Printer() +} diff --git a/src/main/scala/li/cil/oc/common/container/Printer.scala b/src/main/scala/li/cil/oc/common/container/Printer.scala new file mode 100644 index 000000000..c50c50acf --- /dev/null +++ b/src/main/scala/li/cil/oc/common/container/Printer.scala @@ -0,0 +1,53 @@ +package li.cil.oc.common.container + +import cpw.mods.fml.relauncher.Side +import cpw.mods.fml.relauncher.SideOnly +import li.cil.oc.common.tileentity +import li.cil.oc.util.SideTracker +import net.minecraft.entity.player.InventoryPlayer + +class Printer(playerInventory: InventoryPlayer, val printer: tileentity.Printer) extends Player(playerInventory, printer) { + addSlotToContainer(18, 19) + addSlotToContainer(18, 51) + addSlotToContainer(152, 35) + + // Show the player's inventory. + addPlayerInventorySlots(8, 84) + + var isAssembling = false + var assemblyProgress = 0.0 + var assemblyRemainingTime = 0 + + @SideOnly(Side.CLIENT) + override def updateProgressBar(id: Int, value: Int) { + super.updateProgressBar(id, value) + if (id == 0) { + isAssembling = value == 1 + } + + if (id == 1) { + assemblyProgress = value / 5.0 + } + + if (id == 2) { + assemblyRemainingTime = value + } + } + + override def detectAndSendChanges() { + super.detectAndSendChanges() + if (SideTracker.isServer) { + if (isAssembling != printer.isAssembling) { + isAssembling = printer.isAssembling + sendProgressBarUpdate(0, if (isAssembling) 1 else 0) + } + val timeRemaining = printer.timeRemaining + if (math.abs(printer.progress - assemblyProgress) > 0.2 || assemblyRemainingTime != timeRemaining) { + assemblyProgress = printer.progress + assemblyRemainingTime = timeRemaining + sendProgressBarUpdate(1, (assemblyProgress * 5).toInt) + sendProgressBarUpdate(2, timeRemaining) + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/common/init/Blocks.scala b/src/main/scala/li/cil/oc/common/init/Blocks.scala index 40576f02a..396fc42b9 100644 --- a/src/main/scala/li/cil/oc/common/init/Blocks.scala +++ b/src/main/scala/li/cil/oc/common/init/Blocks.scala @@ -26,6 +26,7 @@ object Blocks { GameRegistry.registerTileEntity(classOf[tileentity.PowerConverter], Settings.namespace + "power_converter") GameRegistry.registerTileEntity(classOf[tileentity.PowerDistributor], Settings.namespace + "power_distributor") GameRegistry.registerTileEntity(classOf[tileentity.Print], Settings.namespace + "print") + GameRegistry.registerTileEntity(classOf[tileentity.Printer], Settings.namespace + "printer") GameRegistry.registerTileEntity(classOf[tileentity.Raid], Settings.namespace + "raid") GameRegistry.registerTileEntity(classOf[tileentity.Redstone], Settings.namespace + "redstone") GameRegistry.registerTileEntity(classOf[tileentity.RobotProxy], Settings.namespace + "robot") @@ -85,5 +86,6 @@ object Blocks { // v1.5.4 Items.registerBlock(new Print(), "print") + Items.registerBlock(new Printer(), "printer") } } 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 ad0e30924..7b1973f21 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Print.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Print.scala @@ -3,15 +3,16 @@ package li.cil.oc.common.tileentity import cpw.mods.fml.relauncher.Side import cpw.mods.fml.relauncher.SideOnly import li.cil.oc.common.item.data.PrintData +import li.cil.oc.util.ExtendedAABB +import li.cil.oc.util.ExtendedAABB._ import li.cil.oc.util.ExtendedNBT._ import net.minecraft.nbt.NBTTagCompound -import net.minecraft.util.AxisAlignedBB class Print extends traits.TileEntity { val data = new PrintData() - var boundsOff = unitBounds - var boundsOn = unitBounds + var boundsOff = ExtendedAABB.unitBounds + var boundsOn = ExtendedAABB.unitBounds var state = false override def canUpdate: Boolean = false @@ -40,9 +41,9 @@ class Print extends traits.TileEntity { } def updateBounds(): Unit = { - boundsOff = data.stateOff.drop(1).foldLeft(data.stateOff.headOption.fold(unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) - boundsOn = data.stateOn.drop(1).foldLeft(data.stateOn.headOption.fold(unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) + boundsOff = data.stateOff.drop(1).foldLeft(data.stateOff.headOption.fold(ExtendedAABB.unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) + if (boundsOff.volume == 0) boundsOff = ExtendedAABB.unitBounds + boundsOn = data.stateOn.drop(1).foldLeft(data.stateOn.headOption.fold(ExtendedAABB.unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) + if (boundsOn.volume == 0) boundsOn = ExtendedAABB.unitBounds } - - private def unitBounds = AxisAlignedBB.getBoundingBox(0, 0, 0, 1, 1, 1) } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Printer.scala b/src/main/scala/li/cil/oc/common/tileentity/Printer.scala new file mode 100644 index 000000000..00954516d --- /dev/null +++ b/src/main/scala/li/cil/oc/common/tileentity/Printer.scala @@ -0,0 +1,247 @@ +package li.cil.oc.common.tileentity + +import java.util + +import cpw.mods.fml.relauncher.Side +import cpw.mods.fml.relauncher.SideOnly +import li.cil.oc.Settings +import li.cil.oc.api +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.network._ +import li.cil.oc.common.item.data.PrintData +import li.cil.oc.util.ExtendedAABB._ +import li.cil.oc.util.ExtendedNBT._ +import li.cil.oc.util.ItemUtils +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.AxisAlignedBB +import net.minecraftforge.common.util.ForgeDirection + +class Printer extends traits.Environment with traits.PowerAcceptor with traits.Inventory with traits.Rotatable with SidedEnvironment with traits.StateAware { + val node = api.Network.newNode(this, Visibility.Network). + withComponent("printer3d"). + withConnector(Settings.get.bufferConverter). + create() + + var data = new PrintData() + var isActive = false + var output: Option[ItemStack] = None + var totalRequiredEnergy = 0.0 + var requiredEnergy = 0.0 + + val slotPlastic = 0 + val slotInk = 1 + val slotOutput = 2 + + // ----------------------------------------------------------------------- // + + @SideOnly(Side.CLIENT) + override def canConnect(side: ForgeDirection) = side != ForgeDirection.UP + + override def sidedNode(side: ForgeDirection) = if (side != ForgeDirection.UP) node else null + + @SideOnly(Side.CLIENT) + override protected def hasConnector(side: ForgeDirection) = canConnect(side) + + override protected def connector(side: ForgeDirection) = Option(if (side != ForgeDirection.UP) node else null) + + override protected def energyThroughput = Settings.get.assemblerRate + + override def currentState = { + if (isAssembling) util.EnumSet.of(traits.State.IsWorking) + else if (canAssemble) util.EnumSet.of(traits.State.CanWork) + else util.EnumSet.noneOf(classOf[traits.State]) + } + + // ----------------------------------------------------------------------- // + + def canAssemble = { + val complexity = data.stateOff.size + data.stateOn.size + complexity > 0 && complexity <= Settings.get.maxPrintComplexity + } + + def isAssembling = requiredEnergy > 0 + + def progress = (1 - requiredEnergy / totalRequiredEnergy) * 100 + + def timeRemaining = (requiredEnergy / Settings.get.assemblerTickAmount / 20).toInt + + // ----------------------------------------------------------------------- // + + @Callback(doc = """function() -- Resets the configuration of the printer and stop printing (current job will finish).""") + def reset(context: Context, args: Arguments): Array[Object] = { + data = new PrintData() + isActive = false // Needs committing. + null + } + + @Callback(doc = """function(value:string) -- Set a label for the block being printed.""") + def setLabel(context: Context, args: Arguments): Array[Object] = { + data.label = Option(args.optString(0, null)) + isActive = false // Needs committing. + null + } + + @Callback(doc = """function():string -- Get the current label for the block being printed.""") + def getLabel(context: Context, args: Arguments): Array[Object] = { + result(data.label.orNull) + } + + @Callback(doc = """function(value:string) -- Set a tooltip for the block being printed.""") + def setTooltip(context: Context, args: Arguments): Array[Object] = { + data.tooltip = Option(args.optString(0, null)) + isActive = false // Needs committing. + null + } + + @Callback(doc = """function():string -- Get the current tooltip for the block being printed.""") + def getTooltip(context: Context, args: Arguments): Array[Object] = { + result(data.tooltip.orNull) + } + + @Callback(doc = """function(minX:number, minY:number, minZ:number, maxX:number, maxY:number, maxZ:number, texture:string[, state:boolean=false]) -- Adds a shape to the printers configuration, optionally specifying whether it is for the off or on state.""") + def addShape(context: Context, args: Arguments): Array[Object] = { + if (data.stateOff.size + data.stateOn.size >= Settings.get.maxPrintComplexity) { + return result(null, "model too complex") + } + val minX = (args.checkInteger(0) max 0 min 16) / 16f + val minY = (args.checkInteger(1) max 0 min 16) / 16f + val minZ = (args.checkInteger(2) max 0 min 16) / 16f + val maxX = (args.checkInteger(3) max 0 min 16) / 16f + val maxY = (args.checkInteger(4) max 0 min 16) / 16f + val maxZ = (args.checkInteger(5) max 0 min 16) / 16f + val texture = args.checkString(6) + val state = args.optBoolean(7, false) + + if (minX == maxX) throw new IllegalArgumentException("empty block") + if (minY == maxY) throw new IllegalArgumentException("empty block") + if (minZ == maxZ) throw new IllegalArgumentException("empty block") + + val list = if (state) data.stateOn else data.stateOff + list += new PrintData.Shape(AxisAlignedBB.getBoundingBox( + math.min(minX, maxX), + math.min(minY, maxY), + math.min(minZ, maxZ), + math.max(maxX, minX), + math.max(maxY, minY), + math.max(maxZ, minZ)), texture) + isActive = false // Needs committing. + + result(true) + } + + @Callback(doc = """function():number -- Get the number of shapes in the current configuration.""") + def getShapeCount(context: Context, args: Arguments): Array[Object] = result(data.stateOff.size + data.stateOn.size) + + @Callback(doc = """function():number -- Get the maximum allowed number of shapes.""") + def getMaxShapeCount(context: Context, args: Arguments): Array[Object] = result(Settings.get.maxPrintComplexity) + + @Callback(doc = """function():boolean -- Commit and begin printing the current configuration.""") + def commit(context: Context, args: Arguments): Array[Object] = { + if (!canAssemble) { + return result(null, "model invalid") + } + isActive = true + result(true) + } + + @Callback(doc = """function(): string, number or boolean -- The current state of the printer, `busy' or `idle', followed by the progress or model validity, respectively.""") + def status(context: Context, args: Arguments): Array[Object] = { + if (isAssembling) result("busy", progress) + else if (canAssemble) result("idle", true) + else result("idle", false) + } + + // ----------------------------------------------------------------------- // + + override def canUpdate = isServer + + // ----------------------------------------------------------------------- // + + override def updateEntity() { + super.updateEntity() + + if (world.getTotalWorldTime % Settings.get.tickFrequency == 0) { + if (isActive && output.isEmpty) { + val totalVolume = data.stateOn.foldLeft(0)((acc, shape) => acc + shape.bounds.volume) + data.stateOff.foldLeft(0)((acc, shape) => acc + shape.bounds.volume) + val totalSurface = data.stateOn.foldLeft(0)((acc, shape) => acc + shape.bounds.surface) + data.stateOff.foldLeft(0)((acc, shape) => acc + shape.bounds.surface) + val totalShapes = data.stateOn.size + data.stateOff.size + // TODO Consume plastic (totalVolume) and ink (totalSurface). + totalRequiredEnergy = totalShapes * Settings.get.printShapeCost + requiredEnergy = totalRequiredEnergy + output = Option(data.createItemStack()) + // ServerPacketSender.sendRobotAssembling(this, assembling = true) + } + + if (output.isDefined) { + val want = math.max(1, math.min(requiredEnergy, Settings.get.assemblerTickAmount * Settings.get.tickFrequency)) + val success = Settings.get.ignorePower || node.tryChangeBuffer(-want) + if (success) { + requiredEnergy -= want + } + if (requiredEnergy <= 0) { + val result = getStackInSlot(slotOutput) + if (result == null) { + setInventorySlotContents(slotOutput, output.get) + } + else if (output.get.isItemEqual(result) && ItemStack.areItemStackTagsEqual(output.get, result) && result.stackSize < result.getMaxStackSize) { + result.stackSize += 1 + markDirty() + } + else { + return + } + requiredEnergy = 0 + output = None + } + // ServerPacketSender.sendRobotAssembling(this, success && output.isDefined) + } + } + } + + override def readFromNBTForServer(nbt: NBTTagCompound) { + super.readFromNBTForServer(nbt) + data.load(nbt.getCompoundTag("data")) + isActive = nbt.getBoolean(Settings.namespace + "active") + if (nbt.hasKey(Settings.namespace + "output")) { + output = Option(ItemUtils.loadStack(nbt.getCompoundTag(Settings.namespace + "output"))) + } + totalRequiredEnergy = nbt.getDouble(Settings.namespace + "total") + requiredEnergy = nbt.getDouble(Settings.namespace + "remaining") + } + + override def writeToNBTForServer(nbt: NBTTagCompound) { + super.writeToNBTForServer(nbt) + nbt.setNewCompoundTag("data", data.save) + nbt.setBoolean(Settings.namespace + "active", isActive) + output.foreach(stack => nbt.setNewCompoundTag(Settings.namespace + "output", stack.writeToNBT)) + nbt.setDouble(Settings.namespace + "total", totalRequiredEnergy) + nbt.setDouble(Settings.namespace + "remaining", requiredEnergy) + } + + @SideOnly(Side.CLIENT) override + def readFromNBTForClient(nbt: NBTTagCompound) { + super.readFromNBTForClient(nbt) + requiredEnergy = nbt.getDouble("remaining") + } + + override def writeToNBTForClient(nbt: NBTTagCompound) { + super.writeToNBTForClient(nbt) + nbt.setDouble("remaining", requiredEnergy) + } + + // ----------------------------------------------------------------------- // + + override def getSizeInventory = 3 + + override def getInventoryStackLimit = 64 + + override def isItemValidForSlot(slot: Int, stack: ItemStack) = + if (slot == 0) + true // TODO Plastic + else if (slot == 1) + true // TODO Color + else false +} diff --git a/src/main/scala/li/cil/oc/util/ExtendedAABB.scala b/src/main/scala/li/cil/oc/util/ExtendedAABB.scala new file mode 100644 index 000000000..80ec8af81 --- /dev/null +++ b/src/main/scala/li/cil/oc/util/ExtendedAABB.scala @@ -0,0 +1,28 @@ +package li.cil.oc.util + +import net.minecraft.util.AxisAlignedBB + +import scala.language.implicitConversions + +object ExtendedAABB { + implicit def extendedAABB(bounds: AxisAlignedBB): ExtendedAABB = new ExtendedAABB(bounds) + + def unitBounds = AxisAlignedBB.getBoundingBox(0, 0, 0, 1, 1, 1) + + class ExtendedAABB(val bounds: AxisAlignedBB) { + def volume: Int = { + val sx = ((bounds.maxX - bounds.minX) * 16).round.toInt + val sy = ((bounds.maxY - bounds.minY) * 16).round.toInt + val sz = ((bounds.maxZ - bounds.minZ) * 16).round.toInt + sx * sy * sz + } + + def surface: Int = { + val sx = ((bounds.maxX - bounds.minX) * 16).round.toInt + val sy = ((bounds.maxY - bounds.minY) * 16).round.toInt + val sz = ((bounds.maxZ - bounds.minZ) * 16).round.toInt + sx * sy * 2 + sx * sz * 2 + sy * sz * 2 + } + } + +}