diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index c35bc1bf2..9c7884f0d 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -767,6 +767,10 @@ opencomputers { # Per-byte cost for ECDSA operation is controlled by `complex` value, # because data is hashed with SHA256 before signing/verifying dataCardAsymmetric: 10.0 + + # Energy required for one transposer operation (regardless of the number + # of items / fluid volume moved). + transposer: 1 } # The rate at which different blocks accept external power. All of these diff --git a/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md b/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md new file mode 100644 index 000000000..ccc3e07ff --- /dev/null +++ b/src/main/resources/assets/opencomputers/doc/en_US/block/transposer.md @@ -0,0 +1,9 @@ +# Transposer + +![Such a poser.](oredict:oc:transposer) + +The transposer bridges the gap between redstone controlled hoppers and [robots](robot.md), allowing [computer](../general/computer.md)-controlled transferral of items and fluids between adjacent blocks. + +*Note that this block has no internal inventory.* + +Besides moving things around, it can also be used to inspect the contents of the adjacent inventories, like an [adapter](adapter.md) with an [inventory controller upgrade](../item/inventoryControllerUpgrade.md) could, and the contents of adjacent tanks, like and adapter with a [tank controller upgrade](../item/tankControllerUpgrade.md) could. diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 12020008f..d656c91a3 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -23,6 +23,7 @@ tile.oc.hologram2.name=Hologram Projector (Tier 2) tile.oc.keyboard.name=Keyboard tile.oc.microcontroller.name=Microcontroller tile.oc.motionSensor.name=Motion Sensor +tile.oc.netSplitter.name=Net Splitter tile.oc.powerConverter.name=Power Converter tile.oc.powerDistributor.name=Power Distributor tile.oc.print.name=3D Print @@ -37,7 +38,7 @@ tile.oc.screen2.name=Screen (Tier 2) tile.oc.screen3.name=Screen (Tier 3) tile.oc.serverRack.name=Server Rack tile.oc.switch.name=§cSwitch§7 -tile.oc.netSplitter.name=Net Splitter +tile.oc.transposer.name=Transposer tile.oc.waypoint.name=Waypoint # Items @@ -348,6 +349,7 @@ oc:tooltip.Tier=§8Tier %s oc:tooltip.NetSplitter=Acts as a dynamic connector. Connectivity of each side can be toggled by hitting it with a wrench. Connectivity of all sides can be inverted by applying a redstone signal. oc:tooltip.TooLong=Hold [§f%s§7] for a detailed tooltip. oc:tooltip.Transistor=A basic element in most other computer parts. It's a bit twisted, but it does the job. +oc:tooltip.Transposer=Allows automated transferral of items and fluids between adjacent inventories and fluid containers. oc:tooltip.UpgradeAngel=Allows robots to place blocks in thin air, even if there is no point of reference. oc:tooltip.UpgradeBattery=Increase the amount of energy a device can store, allowing it work longer without having to be recharged. [nl] Capacity: §f%s§7 oc:tooltip.UpgradeChunkloader=If a robot moves in a forest and no one is around to see it, does it really move? This upgrades makes sure it does. It keeps the chunk a device is in loaded, but continually consumes energy while active. diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index 2350ace4a..79eedf014 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -634,6 +634,12 @@ screen3 { [yellowDust, "oc:circuitChip3", glass] [obsidian, yellowDust, obsidian]] } +transposer { + input: [[ingotIron, "oc:inventoryControllerUpgrade", ingotIron] + [hopper, bucket, hopper] + [ingotIron, "oc:tankControllerUpgrade", ingotIron]] + output: 4 +} waypoint { input: [[ingotIron, "oc:circuitChip1", ingotIron] ["oc:materialTransistor", "oc:materialInterweb", "oc:materialTransistor"], diff --git a/src/main/resources/assets/opencomputers/textures/blocks/TransposerOn.png b/src/main/resources/assets/opencomputers/textures/blocks/TransposerOn.png new file mode 100644 index 000000000..0f2a818a7 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/TransposerOn.png differ diff --git a/src/main/resources/assets/opencomputers/textures/blocks/TransposerSide.png b/src/main/resources/assets/opencomputers/textures/blocks/TransposerSide.png new file mode 100644 index 000000000..8d10906de Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/TransposerSide.png differ diff --git a/src/main/resources/assets/opencomputers/textures/blocks/TransposerTop.png b/src/main/resources/assets/opencomputers/textures/blocks/TransposerTop.png new file mode 100644 index 000000000..63f2b8fd0 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/TransposerTop.png differ diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala index 78b5ac786..9638f6b4f 100644 --- a/src/main/scala/li/cil/oc/Constants.scala +++ b/src/main/scala/li/cil/oc/Constants.scala @@ -25,6 +25,7 @@ object Constants { final val Keyboard = "keyboard" final val Microcontroller = "microcontroller" final val MotionSensor = "motionSensor" + final val NetSplitter = "netSplitter" final val PowerConverter = "powerConverter" final val PowerDistributor = "powerDistributor" final val Print = "print" @@ -39,7 +40,7 @@ object Constants { final val ScreenTier3 = "screen3" final val ServerRack = "serverRack" final val Switch = "switch" - final val NetSplitter = "netSplitter" + final val Transposer = "transposer" final val Waypoint = "waypoint" def Case(tier: Int) = ItemUtils.caseNameWithTierSuffix("case", tier) diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index baeed46bf..0846170be 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -201,6 +201,7 @@ class Settings(val config: Config) { val dataCardComplex = config.getDouble("power.cost.dataCardComplex") max 0 val dataCardComplexByte = config.getDouble("power.cost.dataCardComplexByte") max 0 val dataCardAsymmetric = config.getDouble("power.cost.dataCardAsymmetric") max 0 + val transposerCost = config.getDouble("power.cost.transposer") max 0 // power.rate val accessPointRate = config.getDouble("power.rate.accessPoint") max 0 diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index e05f3c4d9..e2f16d984 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -9,11 +9,11 @@ import li.cil.oc.Settings import li.cil.oc.api.component import li.cil.oc.api.event.FileSystemAccessEvent import li.cil.oc.client.renderer.PetRenderer +import li.cil.oc.common.Loot import li.cil.oc.common.PacketType import li.cil.oc.common.container import li.cil.oc.common.tileentity._ import li.cil.oc.common.tileentity.traits._ -import li.cil.oc.common.Loot import li.cil.oc.common.{PacketHandler => CommonPacketHandler} import li.cil.oc.util.Audio import li.cil.oc.util.ExtendedWorld._ @@ -79,6 +79,7 @@ object PacketHandler extends CommonPacketHandler { case PacketType.ServerPresence => onServerPresence(p) case PacketType.Sound => onSound(p) case PacketType.SoundPattern => onSoundPattern(p) + case PacketType.TransposerActivity => onTransposerActivity(p) case PacketType.WaypointLabel => onWaypointLabel(p) case _ => // Invalid packet. } @@ -263,6 +264,15 @@ object PacketHandler extends CommonPacketHandler { } } + def onNetSplitterState(p: PacketParser) = + p.readTileEntity[NetSplitter]() match { + case Some(t) => + t.isInverted = p.readBoolean() + t.openSides = t.uncompressSides(p.readByte()) + t.world.markBlockForUpdate(t.x, t.y, t.z) + case _ => // Invalid packet. + } + def onParticleEffect(p: PacketParser) = { val dimension = p.readInt() world(p.player, dimension) match { @@ -592,15 +602,6 @@ object PacketHandler extends CommonPacketHandler { buffer.rawSetForeground(col, row, color) } - def onNetSplitterState(p: PacketParser) = - p.readTileEntity[NetSplitter]() match { - case Some(t) => - t.isInverted = p.readBoolean() - t.openSides = t.uncompressSides(p.readByte()) - t.world.markBlockForUpdate(t.x, t.y, t.z) - case _ => // Invalid packet. - } - def onScreenTouchMode(p: PacketParser) = p.readTileEntity[Screen]() match { case Some(t) => t.invertTouchMode = p.readBoolean() @@ -641,6 +642,12 @@ object PacketHandler extends CommonPacketHandler { } } + def onTransposerActivity(p: PacketParser) = + p.readTileEntity[Transposer]() match { + case Some(transposer) => transposer.lastOperation = System.currentTimeMillis() + case _ => // Invalid packet. + } + def onWaypointLabel(p: PacketParser) = p.readTileEntity[Waypoint]() match { case Some(waypoint) => waypoint.label = p.readUTF() diff --git a/src/main/scala/li/cil/oc/client/Proxy.scala b/src/main/scala/li/cil/oc/client/Proxy.scala index 45930095c..c5a174afb 100644 --- a/src/main/scala/li/cil/oc/client/Proxy.scala +++ b/src/main/scala/li/cil/oc/client/Proxy.scala @@ -61,6 +61,7 @@ private[oc] class Proxy extends CommonProxy { else ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Hologram], HologramRendererFallback) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Microcontroller], MicrocontrollerRenderer) + ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.NetSplitter], NetSplitterRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.PowerDistributor], PowerDistributorRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Printer], PrinterRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Raid], RaidRenderer) @@ -70,7 +71,7 @@ private[oc] class Proxy extends CommonProxy { ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Relay], SwitchRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.RobotProxy], RobotRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Screen], ScreenRenderer) - ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.NetSplitter], NetSplitterRenderer) + ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Transposer], TransposerRenderer) MinecraftForgeClient.registerItemRenderer(Items.get(Constants.ItemName.Floppy).createItemStack(1).getItem, ItemRenderer) MinecraftForgeClient.registerItemRenderer(Items.get(Constants.BlockName.Print).createItemStack(1).getItem, ItemRenderer) diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala index 195f400da..e229115ae 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -104,6 +104,10 @@ object Textures { var iconOn: IIcon = _ } + object Transposer { + var iconOn: IIcon = _ + } + def init(tm: TextureManager) { tm.bindTexture(fontAntiAliased) tm.bindTexture(fontAliased) diff --git a/src/main/scala/li/cil/oc/client/renderer/block/BlockRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/block/BlockRenderer.scala index 782c7dcc9..8b6a1c051 100644 --- a/src/main/scala/li/cil/oc/client/renderer/block/BlockRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/block/BlockRenderer.scala @@ -67,6 +67,13 @@ object BlockRenderer extends ISimpleBlockRenderingHandler { Tessellator.instance.draw() RenderState.checkError(getClass.getName + ".renderInventoryBlock: splitter") + case _: Transposer => + GL11.glTranslatef(-0.5f, -0.5f, -0.5f) + Tessellator.instance.startDrawingQuads() + Transposer.render(block, metadata, renderer) + Tessellator.instance.draw() + + RenderState.checkError(getClass.getName + ".renderInventoryBlock: transposer") case _ => block match { case simple: SimpleBlock => @@ -103,7 +110,7 @@ object BlockRenderer extends ISimpleBlockRenderingHandler { RenderState.checkError(getClass.getName + ".renderWorldBlock: assembler") true - case cable: tileentity.Cable => + case _: tileentity.Cable => Cable.render(world, x, y, z, block, renderer) RenderState.checkError(getClass.getName + ".renderWorldBlock: cable") @@ -127,7 +134,7 @@ object BlockRenderer extends ISimpleBlockRenderingHandler { RenderState.checkError(getClass.getName + ".renderWorldBlock: print") true - case printer: tileentity.Printer => + case _: tileentity.Printer => Printer.render(block, x, y, z, renderer) RenderState.checkError(getClass.getName + ".renderWorldBlock: printer") @@ -144,6 +151,12 @@ object BlockRenderer extends ISimpleBlockRenderingHandler { RenderState.checkError(getClass.getName + ".renderWorldBlock: splitter") + true + case _: tileentity.Transposer => + Transposer.render(block, x, y, z, renderer) + + RenderState.checkError(getClass.getName + ".renderWorldBlock: transposer") + true case _ => val result = renderer.renderStandardBlock(block, x, y, z) @@ -158,7 +171,8 @@ object BlockRenderer extends ISimpleBlockRenderingHandler { block.isInstanceOf[Hologram] || block.isInstanceOf[Printer] || block.isInstanceOf[Print] || - block.isInstanceOf[NetSplitter] + block.isInstanceOf[NetSplitter] || + block.isInstanceOf[Transposer] // The texture flip this works around only seems to occur for blocks with custom block renderers? def patchedRenderer(renderer: RenderBlocks, block: Block) = diff --git a/src/main/scala/li/cil/oc/client/renderer/block/Transposer.scala b/src/main/scala/li/cil/oc/client/renderer/block/Transposer.scala new file mode 100644 index 000000000..7d22756a5 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/block/Transposer.scala @@ -0,0 +1,124 @@ +package li.cil.oc.client.renderer.block + +import net.minecraft.block.Block +import net.minecraft.client.renderer.RenderBlocks + +object Transposer { + def render(block: Block, x: Int, y: Int, z: Int, renderer: RenderBlocks) { + val previousRenderAllFaces = renderer.renderAllFaces + renderer.renderAllFaces = true + + // Corners. + renderer.setRenderBounds(0 / 16f, 0 / 16f, 0 / 16f, 7 / 16f, 7 / 16f, 7 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(0 / 16f, 0 / 16f, 9 / 16f, 7 / 16f, 7 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(0 / 16f, 9 / 16f, 0 / 16f, 7 / 16f, 16 / 16f, 7 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(0 / 16f, 9 / 16f, 9 / 16f, 7 / 16f, 16 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(9 / 16f, 0 / 16f, 0 / 16f, 16 / 16f, 7 / 16f, 7 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(9 / 16f, 0 / 16f, 9 / 16f, 16 / 16f, 7 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(9 / 16f, 9 / 16f, 0 / 16f, 16 / 16f, 16 / 16f, 7 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(9 / 16f, 9 / 16f, 9 / 16f, 16 / 16f, 16 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + + // Gaps. + renderer.setRenderBounds(0 / 16f, 0 / 16f, 7 / 16f, 5 / 16f, 5 / 16f, 9 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(0 / 16f, 11 / 16f, 7 / 16f, 5 / 16f, 16 / 16f, 9 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(11 / 16f, 0 / 16f, 7 / 16f, 16 / 16f, 5 / 16f, 9 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(11 / 16f, 11 / 16f, 7 / 16f, 16 / 16f, 16 / 16f, 9 / 16f) + renderer.renderStandardBlock(block, x, y, z) + + renderer.setRenderBounds(0 / 16f, 7 / 16f, 0 / 16f, 5 / 16f, 9 / 16f, 5 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(0 / 16f, 7 / 16f, 11 / 16f, 5 / 16f, 9 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(11 / 16f, 7 / 16f, 0 / 16f, 16 / 16f, 9 / 16f, 5 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(11 / 16f, 7 / 16f, 11 / 16f, 16 / 16f, 9 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + + renderer.setRenderBounds(7 / 16f, 0 / 16f, 0 / 16f, 9 / 16f, 5 / 16f, 5 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(7 / 16f, 0 / 16f, 11 / 16f, 9 / 16f, 5 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(7 / 16f, 11 / 16f, 0 / 16f, 9 / 16f, 16 / 16f, 5 / 16f) + renderer.renderStandardBlock(block, x, y, z) + renderer.setRenderBounds(7 / 16f, 11 / 16f, 11 / 16f, 9 / 16f, 16 / 16f, 16 / 16f) + renderer.renderStandardBlock(block, x, y, z) + + // Core. + renderer.setRenderBounds(1 / 16f, 1 / 16f, 1 / 16f, 15 / 16f, 15 / 16f, 15 / 16f) + renderer.renderStandardBlock(block, x, y, z) + + renderer.renderAllFaces = previousRenderAllFaces + } + + def render(block: Block, metadata: Int, renderer: RenderBlocks) { + // Corners. + renderer.setRenderBounds(0 / 16f, 0 / 16f, 0 / 16f, 7 / 16f, 7 / 16f, 7 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(0 / 16f, 0 / 16f, 9 / 16f, 7 / 16f, 7 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(0 / 16f, 9 / 16f, 0 / 16f, 7 / 16f, 16 / 16f, 7 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(0 / 16f, 9 / 16f, 9 / 16f, 7 / 16f, 16 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(9 / 16f, 0 / 16f, 0 / 16f, 16 / 16f, 7 / 16f, 7 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(9 / 16f, 0 / 16f, 9 / 16f, 16 / 16f, 7 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(9 / 16f, 9 / 16f, 0 / 16f, 16 / 16f, 16 / 16f, 7 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(9 / 16f, 9 / 16f, 9 / 16f, 16 / 16f, 16 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + + // Gaps. + renderer.setRenderBounds(0 / 16f, 0 / 16f, 7 / 16f, 5 / 16f, 5 / 16f, 9 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(0 / 16f, 11 / 16f, 7 / 16f, 5 / 16f, 16 / 16f, 9 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(11 / 16f, 0 / 16f, 7 / 16f, 16 / 16f, 5 / 16f, 9 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(11 / 16f, 11 / 16f, 7 / 16f, 16 / 16f, 16 / 16f, 9 / 16f) + renderAllFaces(block, metadata, renderer) + + renderer.setRenderBounds(0 / 16f, 7 / 16f, 0 / 16f, 5 / 16f, 9 / 16f, 5 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(0 / 16f, 7 / 16f, 11 / 16f, 5 / 16f, 9 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(11 / 16f, 7 / 16f, 0 / 16f, 16 / 16f, 9 / 16f, 5 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(11 / 16f, 7 / 16f, 11 / 16f, 16 / 16f, 9 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + + renderer.setRenderBounds(7 / 16f, 0 / 16f, 0 / 16f, 9 / 16f, 5 / 16f, 5 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(7 / 16f, 0 / 16f, 11 / 16f, 9 / 16f, 5 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(7 / 16f, 11 / 16f, 0 / 16f, 9 / 16f, 16 / 16f, 5 / 16f) + renderAllFaces(block, metadata, renderer) + renderer.setRenderBounds(7 / 16f, 11 / 16f, 11 / 16f, 9 / 16f, 16 / 16f, 16 / 16f) + renderAllFaces(block, metadata, renderer) + + // Core. + renderer.setRenderBounds(1 / 16f, 1 / 16f, 1 / 16f, 15 / 16f, 15 / 16f, 15 / 16f) + renderAllFaces(block, metadata, renderer) + } + + private def renderAllFaces(block: Block, metadata: Int, renderer: RenderBlocks): Unit = { + BlockRenderer.renderFaceYPos(block, metadata, renderer) + BlockRenderer.renderFaceYNeg(block, metadata, renderer) + BlockRenderer.renderFaceXPos(block, metadata, renderer) + BlockRenderer.renderFaceXNeg(block, metadata, renderer) + BlockRenderer.renderFaceZPos(block, metadata, renderer) + BlockRenderer.renderFaceZNeg(block, metadata, renderer) + } +} diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala new file mode 100644 index 000000000..ccd146088 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/TransposerRenderer.scala @@ -0,0 +1,77 @@ +package li.cil.oc.client.renderer.tileentity + +import li.cil.oc.client.Textures +import li.cil.oc.common.tileentity +import li.cil.oc.util.RenderState +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer +import net.minecraft.tileentity.TileEntity +import org.lwjgl.opengl.GL11 + +object TransposerRenderer extends TileEntitySpecialRenderer { + override def renderTileEntityAt(tileEntity: TileEntity, x: Double, y: Double, z: Double, f: Float) { + RenderState.checkError(getClass.getName + ".renderTileEntityAt: entering (aka: wasntme)") + + val transposer = tileEntity.asInstanceOf[tileentity.Transposer] + val activity = math.max(0, 1 - (System.currentTimeMillis() - transposer.lastOperation) / 1000.0) + if (activity > 0) { + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + + RenderState.disableLighting() + RenderState.makeItBlend() + RenderState.setBlendAlpha(activity.toFloat) + + GL11.glPushMatrix() + + GL11.glTranslated(x + 0.5, y + 0.5, z + 0.5) + GL11.glScaled(1.0025, -1.0025, 1.0025) + GL11.glTranslatef(-0.5f, -0.5f, -0.5f) + + bindTexture(TextureMap.locationBlocksTexture) + val t = Tessellator.instance + t.startDrawingQuads() + + val icon = Textures.Transposer.iconOn + + t.addVertexWithUV(0, 1, 0, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(1, 1, 0, icon.getMinU, icon.getMinV) + t.addVertexWithUV(1, 1, 1, icon.getMinU, icon.getMaxV) + t.addVertexWithUV(0, 1, 1, icon.getMaxU, icon.getMaxV) + + t.addVertexWithUV(0, 0, 0, icon.getMaxU, icon.getMaxV) + t.addVertexWithUV(0, 0, 1, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(1, 0, 1, icon.getMinU, icon.getMinV) + t.addVertexWithUV(1, 0, 0, icon.getMinU, icon.getMaxV) + + t.addVertexWithUV(1, 1, 0, icon.getMinU, icon.getMaxV) + t.addVertexWithUV(0, 1, 0, icon.getMaxU, icon.getMaxV) + t.addVertexWithUV(0, 0, 0, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(1, 0, 0, icon.getMinU, icon.getMinV) + + t.addVertexWithUV(0, 1, 1, icon.getMinU, icon.getMaxV) + t.addVertexWithUV(1, 1, 1, icon.getMaxU, icon.getMaxV) + t.addVertexWithUV(1, 0, 1, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(0, 0, 1, icon.getMinU, icon.getMinV) + + t.addVertexWithUV(0, 1, 0, icon.getMinU, icon.getMaxV) + t.addVertexWithUV(0, 1, 1, icon.getMaxU, icon.getMaxV) + t.addVertexWithUV(0, 0, 1, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(0, 0, 0, icon.getMinU, icon.getMinV) + + t.addVertexWithUV(1, 1, 1, icon.getMinU, icon.getMaxV) + t.addVertexWithUV(1, 1, 0, icon.getMaxU, icon.getMaxV) + t.addVertexWithUV(1, 0, 0, icon.getMaxU, icon.getMinV) + t.addVertexWithUV(1, 0, 1, icon.getMinU, icon.getMinV) + + t.draw() + + RenderState.enableLighting() + + GL11.glPopMatrix() + GL11.glPopAttrib() + } + + RenderState.checkError(getClass.getName + ".renderTileEntityAt: leaving") + } +} diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index 24ad97452..16f9be361 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -21,6 +21,7 @@ object PacketType extends Enumeration { HologramTranslation, HologramValues, LootDisk, + NetSplitterState, ParticleEffect, PetVisibility, // Goes both ways. PowerState, @@ -50,11 +51,11 @@ object PacketType extends Enumeration { TextBufferMultiRawSetBackground, TextBufferMultiRawSetForeground, TextBufferPowerChange, - NetSplitterState, ScreenTouchMode, ServerPresence, Sound, SoundPattern, + TransposerActivity, WaypointLabel, // Goes both ways. // Client -> Server diff --git a/src/main/scala/li/cil/oc/common/block/Geolyzer.scala b/src/main/scala/li/cil/oc/common/block/Geolyzer.scala index b79c2ea03..725cca253 100644 --- a/src/main/scala/li/cil/oc/common/block/Geolyzer.scala +++ b/src/main/scala/li/cil/oc/common/block/Geolyzer.scala @@ -1,5 +1,7 @@ package li.cil.oc.common.block +import cpw.mods.fml.relauncher.Side +import cpw.mods.fml.relauncher.SideOnly import li.cil.oc.Settings import li.cil.oc.client.Textures import li.cil.oc.common.tileentity @@ -19,6 +21,7 @@ class Geolyzer extends SimpleBlock { Some("GeolyzerSide") ) + @SideOnly(Side.CLIENT) override def registerBlockIcons(iconRegister: IIconRegister) = { super.registerBlockIcons(iconRegister) Textures.Geolyzer.iconTopOn = iconRegister.registerIcon(Settings.resourceDomain + ":GeolyzerTopOn") diff --git a/src/main/scala/li/cil/oc/common/block/NetSplitter.scala b/src/main/scala/li/cil/oc/common/block/NetSplitter.scala index 64892defa..609592753 100644 --- a/src/main/scala/li/cil/oc/common/block/NetSplitter.scala +++ b/src/main/scala/li/cil/oc/common/block/NetSplitter.scala @@ -23,8 +23,8 @@ class NetSplitter extends RedstoneAware { Some("NetSplitterSide") ) - @SideOnly(Side.CLIENT) override - def registerBlockIcons(iconRegister: IIconRegister): Unit = { + @SideOnly(Side.CLIENT) + override def registerBlockIcons(iconRegister: IIconRegister): Unit = { super.registerBlockIcons(iconRegister) Textures.NetSplitter.iconOn = iconRegister.registerIcon(Settings.resourceDomain + ":NetSplitterOn") } diff --git a/src/main/scala/li/cil/oc/common/block/Transposer.scala b/src/main/scala/li/cil/oc/common/block/Transposer.scala new file mode 100644 index 000000000..d0c717149 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/block/Transposer.scala @@ -0,0 +1,36 @@ +package li.cil.oc.common.block + +import cpw.mods.fml.relauncher.Side +import cpw.mods.fml.relauncher.SideOnly +import li.cil.oc.Settings +import li.cil.oc.client.Textures +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 Transposer extends SimpleBlock { + override protected def customTextures = Array( + Some("TransposerTop"), + Some("TransposerTop"), + Some("TransposerSide"), + Some("TransposerSide"), + Some("TransposerSide"), + Some("TransposerSide") + ) + + @SideOnly(Side.CLIENT) + override def registerBlockIcons(iconRegister: IIconRegister): Unit = { + super.registerBlockIcons(iconRegister) + Textures.Transposer.iconOn = iconRegister.registerIcon(Settings.resourceDomain + ":TransposerOn") + } + + override def isSideSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: ForgeDirection): Boolean = false + + // ----------------------------------------------------------------------- // + + override def hasTileEntity(metadata: Int) = true + + override def createTileEntity(world: World, metadata: Int) = new tileentity.Transposer() +} 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 73ea7bea2..feda54fbb 100644 --- a/src/main/scala/li/cil/oc/common/init/Blocks.scala +++ b/src/main/scala/li/cil/oc/common/init/Blocks.scala @@ -24,6 +24,7 @@ object Blocks { GameRegistry.registerTileEntity(classOf[tileentity.Geolyzer], Settings.namespace + "geolyzer") GameRegistry.registerTileEntity(classOf[tileentity.Microcontroller], Settings.namespace + "microcontroller") GameRegistry.registerTileEntity(classOf[tileentity.MotionSensor], Settings.namespace + "motion_sensor") + GameRegistry.registerTileEntity(classOf[tileentity.NetSplitter], Settings.namespace + "netSplitter") 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") @@ -35,7 +36,7 @@ object Blocks { GameRegistry.registerTileEntity(classOf[tileentity.Switch], Settings.namespace + "switch") GameRegistry.registerTileEntity(classOf[tileentity.Screen], Settings.namespace + "screen") GameRegistry.registerTileEntity(classOf[tileentity.ServerRack], Settings.namespace + "serverRack") - GameRegistry.registerTileEntity(classOf[tileentity.NetSplitter], Settings.namespace + "netSplitter") + GameRegistry.registerTileEntity(classOf[tileentity.Transposer], Settings.namespace + "transposer") GameRegistry.registerTileEntity(classOf[tileentity.Waypoint], Settings.namespace + "waypoint") Items.registerBlock(new AccessPoint(), Constants.BlockName.AccessPoint) @@ -81,5 +82,8 @@ object Blocks { // v1.5.14 Recipes.addBlock(new NetSplitter(), Constants.BlockName.NetSplitter, "oc:netSplitter") + + // v1.5.16 + Recipes.addBlock(new Transposer(), Constants.BlockName.Transposer, "oc:transposer") } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala b/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala new file mode 100644 index 000000000..b3e43b9f4 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/tileentity/Transposer.scala @@ -0,0 +1,25 @@ +package li.cil.oc.common.tileentity + +import li.cil.oc.server.component +import net.minecraft.nbt.NBTTagCompound + +class Transposer extends traits.Environment { + val transposer = new component.Transposer(this) + + def node = transposer.node + + // Used on client side to check whether to render activity indicators. + var lastOperation = 0L + + override def canUpdate = false + + override def readFromNBTForServer(nbt: NBTTagCompound) { + super.readFromNBTForServer(nbt) + transposer.load(nbt) + } + + override def writeToNBTForServer(nbt: NBTTagCompound) { + super.writeToNBTForServer(nbt) + transposer.save(nbt) + } +} diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index 406462a4a..59e35652a 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -234,6 +234,16 @@ object PacketSender { } } + def sendNetSplitterState(t: tileentity.NetSplitter): Unit = { + val pb = new SimplePacketBuilder(PacketType.NetSplitterState) + + pb.writeTileEntity(t) + pb.writeBoolean(t.isInverted) + pb.writeByte(t.compressSides) + + pb.sendToPlayersNearTileEntity(t) + } + def sendParticleEffect(position: BlockPosition, name: String, count: Int, velocity: Double, direction: Option[ForgeDirection] = None): Unit = if (count > 0) { val pb = new SimplePacketBuilder(PacketType.ParticleEffect) @@ -529,16 +539,6 @@ object PacketSender { pb.sendToPlayersNearHost(host) } - def sendNetSplitterState(t: tileentity.NetSplitter): Unit = { - val pb = new SimplePacketBuilder(PacketType.NetSplitterState) - - pb.writeTileEntity(t) - pb.writeBoolean(t.isInverted) - pb.writeByte(t.compressSides) - - pb.sendToPlayersNearTileEntity(t) - } - def sendScreenTouchMode(t: tileentity.Screen, value: Boolean) { val pb = new SimplePacketBuilder(PacketType.ScreenTouchMode) @@ -619,6 +619,14 @@ object PacketSender { pb.sendToNearbyPlayers(world, x, y, z, Option(32)) } + def sendTransposerActivity(t: tileentity.Transposer) { + val pb = new SimplePacketBuilder(PacketType.TransposerActivity) + + pb.writeTileEntity(t) + + pb.sendToPlayersNearTileEntity(t, Option(32)) + } + def sendWaypointLabel(t: Waypoint): Unit = { val pb = new SimplePacketBuilder(PacketType.WaypointLabel) diff --git a/src/main/scala/li/cil/oc/server/component/Transposer.scala b/src/main/scala/li/cil/oc/server/component/Transposer.scala new file mode 100644 index 000000000..636e1e27e --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/Transposer.scala @@ -0,0 +1,68 @@ +package li.cil.oc.server.component + +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.Visibility +import li.cil.oc.api.prefab +import li.cil.oc.common.tileentity +import li.cil.oc.server.{PacketSender => ServerPacketSender} +import li.cil.oc.util.BlockPosition +import li.cil.oc.util.ExtendedArguments._ +import li.cil.oc.util.FluidUtils +import li.cil.oc.util.InventoryUtils +import net.minecraftforge.common.util.ForgeDirection + +import scala.language.existentials + +class Transposer(val host: tileentity.Transposer) extends prefab.ManagedEnvironment with traits.WorldInventoryAnalytics with traits.WorldTankAnalytics { + override val node = api.Network.newNode(this, Visibility.Network). + withComponent("transposer"). + withConnector(). + create() + + override def position = BlockPosition(host) + + override protected def checkSideForAction(args: Arguments, n: Int) = + args.checkSide(n, ForgeDirection.VALID_DIRECTIONS: _*) + + @Callback(doc = """function(sourceSide:number, sinkSide:number[, count:number[, sourceSlot:number, sinkSlot:number]]):number -- Transfer some items between two inventories.""") + def transferItem(context: Context, args: Arguments): Array[AnyRef] = { + val sourceSide = checkSideForAction(args, 0) + val sourcePos = position.offset(sourceSide) + val sinkSide = checkSideForAction(args, 1) + val sinkPos = position.offset(sinkSide) + val count = args.optItemCount(2) + + if (node.tryChangeBuffer(-Settings.get.transposerCost)) { + ServerPacketSender.sendTransposerActivity(host) + + if (args.count > 3) { + val sourceSlot = args.checkSlot(InventoryUtils.inventoryAt(sourcePos).getOrElse(throw new IllegalArgumentException("no inventory")), 3) + val sinkSlot = args.checkSlot(InventoryUtils.inventoryAt(sinkPos).getOrElse(throw new IllegalArgumentException("no inventory")), 4) + + result(InventoryUtils.transferBetweenInventoriesSlotsAt(sourcePos, sourceSide.getOpposite, sourceSlot, sinkPos, Option(sinkSide.getOpposite), sinkSlot, count)) + } + else result(InventoryUtils.transferBetweenInventoriesAt(sourcePos, sourceSide.getOpposite, sinkPos, Option(sinkSide.getOpposite), count)) + } + else result(Unit, "not enough energy") + } + + @Callback(doc = """function(sourceSide:number, sinkSide:number[, count:number]):number -- Transfer some items between two inventories.""") + def transferFluid(context: Context, args: Arguments): Array[AnyRef] = { + val sourceSide = checkSideForAction(args, 0) + val sourcePos = position.offset(sourceSide) + val sinkSide = checkSideForAction(args, 1) + val sinkPos = position.offset(sinkSide) + val count = args.optFluidCount(2) + + if (node.tryChangeBuffer(-Settings.get.transposerCost)) { + ServerPacketSender.sendTransposerActivity(host) + + result(FluidUtils.transferBetweenFluidHandlersAt(sourcePos, sourceSide.getOpposite, sinkPos, sinkSide.getOpposite, count)) + } + else result(Unit, "not enough energy") + } +} diff --git a/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala b/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala index 91a08b8fd..ee6254298 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/TankWorldControl.scala @@ -4,14 +4,9 @@ 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.util.ExtendedArguments._ -import li.cil.oc.util.ExtendedBlock._ -import li.cil.oc.util.ExtendedWorld._ +import li.cil.oc.util.FluidUtils import li.cil.oc.util.ResultWrapper.result -import net.minecraft.block.BlockLiquid -import net.minecraftforge.fluids.FluidRegistry import net.minecraftforge.fluids.FluidStack -import net.minecraftforge.fluids.IFluidBlock -import net.minecraftforge.fluids.IFluidHandler trait TankWorldControl extends TankAware with WorldAware with SideRestricted { @Callback(doc = "function(side:number):boolean -- Compare the fluid in the selected tank with the fluid on the specified side. Returns true if equal.") @@ -19,16 +14,10 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted { val side = checkSideForAction(args, 0) fluidInTank(selectedTank) match { case Some(stack) => - val blockPos = position.offset(side) - if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match { - case handler: IFluidHandler => - result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid)))) - case _ => - val block = world.getBlock(blockPos) - val fluid = FluidRegistry.lookupFluidForBlock(block) - result(stack.getFluid == fluid) + FluidUtils.fluidHandlerAt(position.offset(side)) match { + case Some(handler) => result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid)))) + case _ => result(false) } - else result(false) case _ => result(false) } } @@ -36,18 +25,14 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted { @Callback(doc = "function(side:boolean[, amount:number=1000]):boolean, number or string -- Drains the specified amount of fluid from the specified side. Returns the amount drained, or an error message.") def drain(context: Context, args: Arguments): Array[AnyRef] = { val facing = checkSideForAction(args, 0) - val count = args.optFluidCount(1) + val count = args.optFluidCount(1) max 0 getTank(selectedTank) match { case Some(tank) => val space = tank.getCapacity - tank.getFluidAmount val amount = math.min(count, space) - if (count > 0 && amount == 0) { - result(Unit, "tank is full") - } - else { - val blockPos = position.offset(facing) - if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match { - case handler: IFluidHandler => + if (count < 1 || amount > 0) { + FluidUtils.fluidHandlerAt(position.offset(facing)) match { + case Some(handler) => tank.getFluid match { case stack: FluidStack => val drained = handler.drain(facing.getOpposite, new FluidStack(stack, amount), true) @@ -60,36 +45,10 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted { val transferred = tank.fill(handler.drain(facing.getOpposite, amount, true), true) result(transferred > 0, transferred) } - case _ => world.getBlock(blockPos) match { - case fluidBlock: IFluidBlock if fluidBlock.canDrain(world, blockPos.x, blockPos.y, blockPos.z) => - val drained = fluidBlock.drain(world, blockPos.x, blockPos.y, blockPos.z, false) - if ((drained != null && drained.amount > 0) && (drained.amount <= amount || amount == 0)) { - if (drained.amount <= amount) { - val filled = tank.fill(fluidBlock.drain(world, blockPos.x, blockPos.y, blockPos.z, true), true) - result(true, filled) - } - else /* if (amount == 0) */ { - result(true, 0) - } - } - else result(Unit, "tank is full") - case liquidBlock: BlockLiquid if world.getBlockMetadata(blockPos) == 0 => - val fluid = FluidRegistry.lookupFluidForBlock(liquidBlock) - if (fluid == null) { - result(Unit, "incompatible or no fluid") - } - else if (tank.fill(new FluidStack(fluid, 1000), false) == 1000) { - tank.fill(new FluidStack(fluid, 1000), true) - world.setBlockToAir(blockPos) - result(true, 1000) - } - else result(Unit, "tank is full") - case _ => - result(Unit, "incompatible or no fluid") - } + case _ => result(Unit, "incompatible or no fluid") } - else result(Unit, "incompatible or no fluid") } + else result(Unit, "tank is full") case _ => result(Unit, "no tank selected") } } @@ -97,49 +56,28 @@ trait TankWorldControl extends TankAware with WorldAware with SideRestricted { @Callback(doc = "function(side:number[, amount:number=1000]):boolean, number of string -- Eject the specified amount of fluid to the specified side. Returns the amount ejected or an error message.") def fill(context: Context, args: Arguments): Array[AnyRef] = { val facing = checkSideForAction(args, 0) - val count = args.optFluidCount(1) + val count = args.optFluidCount(1) max 0 getTank(selectedTank) match { case Some(tank) => val amount = math.min(count, tank.getFluidAmount) - if (count > 0 && amount == 0) { - result(Unit, "tank is empty") + if (count < 1 || amount > 0) { + FluidUtils.fluidHandlerAt(position.offset(facing)) match { + case Some(handler) => + tank.getFluid match { + case stack: FluidStack => + val filled = handler.fill(facing.getOpposite, new FluidStack(stack, amount), true) + if (filled > 0 || amount == 0) { + tank.drain(filled, true) + result(true, filled) + } + else result(Unit, "incompatible or no fluid") + case _ => + result(Unit, "tank is empty") + } + case _ => result(Unit, "no space") + } } - val blockPos = position.offset(facing) - if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match { - case handler: IFluidHandler => - tank.getFluid match { - case stack: FluidStack => - val filled = handler.fill(facing.getOpposite, new FluidStack(stack, amount), true) - if (filled > 0 || amount == 0) { - tank.drain(filled, true) - result(true, filled) - } - else result(Unit, "incompatible or no fluid") - case _ => - result(Unit, "tank is empty") - } - case _ => - val block = world.getBlock(blockPos) - if (!block.isAir(blockPos) && !block.isReplaceable(blockPos)) { - result(Unit, "no space") - } - else if (tank.getFluidAmount < 1000) { - result(Unit, "tank is empty") - } - else if (!tank.getFluid.getFluid.canBePlacedInWorld) { - result(Unit, "incompatible fluid") - } - else { - val fluidBlock = tank.getFluid.getFluid.getBlock - tank.drain(1000, true) - world.breakBlock(blockPos) - world.setBlock(blockPos, fluidBlock) - // This fake neighbor update is required to get stills to start flowing. - world.notifyBlockOfNeighborChange(blockPos, world.getBlock(position)) - result(true, 1000) - } - } - else result(Unit, "no space") + else result(Unit, "tank is empty") case _ => result(Unit, "no tank selected") } } diff --git a/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala b/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala index a0a863fd1..ec7950223 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/WorldTankAnalytics.scala @@ -5,15 +5,15 @@ 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.server.component.result -import li.cil.oc.util.ExtendedWorld._ -import net.minecraftforge.fluids.IFluidHandler +import li.cil.oc.util.FluidUtils trait WorldTankAnalytics extends WorldAware with SideRestricted { @Callback(doc = """function(side:number):number -- Get the amount of fluid in the tank on the specified side.""") def getTankLevel(context: Context, args: Arguments): Array[AnyRef] = { val facing = checkSideForAction(args, 0) - world.getTileEntity(position.offset(facing)) match { - case handler: IFluidHandler => + + FluidUtils.fluidHandlerAt(position.offset(facing)) match { + case Some(handler) => result(handler.getTankInfo(facing.getOpposite).map(info => Option(info.fluid).fold(0)(_.amount)).sum) case _ => result(Unit, "no tank") } @@ -22,8 +22,8 @@ trait WorldTankAnalytics extends WorldAware with SideRestricted { @Callback(doc = """function(side:number):number -- Get the capacity of the tank on the specified side.""") def getTankCapacity(context: Context, args: Arguments): Array[AnyRef] = { val facing = checkSideForAction(args, 0) - world.getTileEntity(position.offset(facing)) match { - case handler: IFluidHandler => + FluidUtils.fluidHandlerAt(position.offset(facing)) match { + case Some(handler) => result(handler.getTankInfo(facing.getOpposite).map(_.capacity).foldLeft(0)((max, capacity) => math.max(max, capacity))) case _ => result(Unit, "no tank") } @@ -32,8 +32,8 @@ trait WorldTankAnalytics extends WorldAware with SideRestricted { @Callback(doc = """function(side:number):table -- Get a description of the fluid in the the tank on the specified side.""") def getFluidInTank(context: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) { val facing = checkSideForAction(args, 0) - world.getTileEntity(position.offset(facing)) match { - case handler: IFluidHandler => + FluidUtils.fluidHandlerAt(position.offset(facing)) match { + case Some(handler) => result(handler.getTankInfo(facing.getOpposite)) case _ => result(Unit, "no tank") } diff --git a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala index d6dcc1dbf..06bc49d89 100644 --- a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala +++ b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala @@ -4,6 +4,7 @@ import li.cil.oc.api.internal.MultiTank import li.cil.oc.api.machine.Arguments import net.minecraft.inventory.IInventory import net.minecraftforge.common.util.ForgeDirection +import net.minecraftforge.fluids.FluidContainerRegistry import scala.language.implicitConversions @@ -16,7 +17,7 @@ object ExtendedArguments { if (!isDefined(index) || !hasValue(index)) default else math.max(0, math.min(64, args.checkInteger(index))) - def optFluidCount(index: Int, default: Int = 1000) = + def optFluidCount(index: Int, default: Int = FluidContainerRegistry.BUCKET_VOLUME) = if (!isDefined(index) || !hasValue(index)) default else math.max(0, args.checkInteger(index)) diff --git a/src/main/scala/li/cil/oc/util/ExtendedBlock.scala b/src/main/scala/li/cil/oc/util/ExtendedBlock.scala index 6cfabdd76..5dcc0eb77 100644 --- a/src/main/scala/li/cil/oc/util/ExtendedBlock.scala +++ b/src/main/scala/li/cil/oc/util/ExtendedBlock.scala @@ -1,6 +1,7 @@ package li.cil.oc.util import net.minecraft.block.Block +import net.minecraftforge.fluids.IFluidBlock import scala.language.implicitConversions @@ -20,4 +21,14 @@ object ExtendedBlock { def getCollisionBoundingBoxFromPool(position: BlockPosition) = block.getCollisionBoundingBoxFromPool(position.world.get, position.x, position.y, position.z) } + implicit def extendedFluidBlock(block: IFluidBlock): ExtendedFluidBlock = new ExtendedFluidBlock(block) + + class ExtendedFluidBlock(val block: IFluidBlock) { + def drain(position: BlockPosition, doDrain: Boolean) = block.drain(position.world.get, position.x, position.y, position.z, doDrain) + + def canDrain(position: BlockPosition) = block.canDrain(position.world.get, position.x, position.y, position.z) + + def getFilledPercentage(position: BlockPosition) = block.getFilledPercentage(position.world.get, position.x, position.y, position.z) + } + } diff --git a/src/main/scala/li/cil/oc/util/FluidUtils.scala b/src/main/scala/li/cil/oc/util/FluidUtils.scala new file mode 100644 index 000000000..754c22b93 --- /dev/null +++ b/src/main/scala/li/cil/oc/util/FluidUtils.scala @@ -0,0 +1,157 @@ +package li.cil.oc.util + +import li.cil.oc.util.ExtendedBlock._ +import li.cil.oc.util.ExtendedWorld._ +import net.minecraft.block.Block +import net.minecraft.block.BlockLiquid +import net.minecraftforge.common.util.ForgeDirection +import net.minecraftforge.fluids.Fluid +import net.minecraftforge.fluids.FluidContainerRegistry +import net.minecraftforge.fluids.FluidRegistry +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.FluidTank +import net.minecraftforge.fluids.FluidTankInfo +import net.minecraftforge.fluids.IFluidBlock +import net.minecraftforge.fluids.IFluidHandler + +object FluidUtils { + /** + * Retrieves an actual fluid handler implementation for a specified world coordinate. + *

+ * This performs special handling for in-world liquids. + */ + def fluidHandlerAt(position: BlockPosition): Option[IFluidHandler] = position.world match { + case Some(world) if world.blockExists(position) => world.getTileEntity(position) match { + case handler: IFluidHandler => Option(handler) + case _ => Option(new GenericBlockWrapper(position)) + } + case _ => None + } + + /** + * Transfers some fluid between two fluid handlers. + *

+ * This will try to extract up the specified amount of fluid from any handler, + * then insert it into the specified sink handler. If the insertion fails, the + * fluid will remain in the source handler. + *

+ * This returns true if some fluid was transferred. + */ + def transferBetweenFluidHandlers(source: IFluidHandler, sourceSide: ForgeDirection, sink: IFluidHandler, sinkSide: ForgeDirection, limit: Int = FluidContainerRegistry.BUCKET_VOLUME) = { + val drained = source.drain(sourceSide, limit, false) + val filled = sink.fill(sinkSide, drained, false) + sink.fill(sinkSide, source.drain(sourceSide, filled, true), true) + } + + /** + * Utility method for calling transferBetweenFluidHandlers on handlers + * in the world. + *

+ * This uses the fluidHandlerAt method, and therefore handles special + * cases such as fluid blocks. + */ + def transferBetweenFluidHandlersAt(sourcePos: BlockPosition, sourceSide: ForgeDirection, sinkPos: BlockPosition, sinkSide: ForgeDirection, limit: Int = FluidContainerRegistry.BUCKET_VOLUME) = + fluidHandlerAt(sourcePos).fold(0)(source => + fluidHandlerAt(sinkPos).fold(0)(sink => + transferBetweenFluidHandlers(source, sourceSide, sink, sinkSide, limit))) + + // ----------------------------------------------------------------------- // + + private class GenericBlockWrapper(position: BlockPosition) extends IFluidHandler { + override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = currentWrapper.fold(false)(_.canDrain(from, fluid)) + + override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean): FluidStack = currentWrapper.fold(null: FluidStack)(_.drain(from, resource, doDrain)) + + override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean): FluidStack = currentWrapper.fold(null: FluidStack)(_.drain(from, maxDrain, doDrain)) + + override def canFill(from: ForgeDirection, fluid: Fluid): Boolean = currentWrapper.fold(false)(_.canFill(from, fluid)) + + override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean): Int = currentWrapper.fold(0)(_.fill(from, resource, doFill)) + + override def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = currentWrapper.fold(Array.empty[FluidTankInfo])(_.getTankInfo(from)) + + def currentWrapper = if (position.world.get.blockExists(position)) position.world.get.getBlock(position) match { + case block: IFluidBlock => Option(new FluidBlockWrapper(position, block)) + case block: BlockLiquid if FluidRegistry.lookupFluidForBlock(block) != null => Option(new LiquidBlockWrapper(position, block)) + case block: Block if block.isAir(position) || block.isReplaceable(position) => Option(new AirBlockWrapper(position, block)) + case _ => None + } + else None + } + + private trait BlockWrapperBase extends IFluidHandler { + protected def uncheckedDrain(doDrain: Boolean): FluidStack + + override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean): FluidStack = { + val drained = uncheckedDrain(false) + if (drained != null && (resource == null || (drained.getFluid == resource.getFluid && drained.amount <= resource.amount))) { + uncheckedDrain(doDrain) + } + else null + } + + override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean): FluidStack = { + val drained = uncheckedDrain(false) + if (drained != null && drained.amount <= maxDrain) { + uncheckedDrain(doDrain) + } + else null + } + + override def canFill(from: ForgeDirection, fluid: Fluid): Boolean = false + + override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean): Int = 0 + } + + private class FluidBlockWrapper(val position: BlockPosition, val block: IFluidBlock) extends BlockWrapperBase { + final val AssumedCapacity = FluidContainerRegistry.BUCKET_VOLUME + + override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = block.canDrain(position) + + override def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = Array(new FluidTankInfo(new FluidTank(block.getFluid, (block.getFilledPercentage(position) * AssumedCapacity).toInt, AssumedCapacity))) + + override protected def uncheckedDrain(doDrain: Boolean): FluidStack = block.drain(position, doDrain) + } + + private class LiquidBlockWrapper(val position: BlockPosition, val block: BlockLiquid) extends BlockWrapperBase { + val fluid = FluidRegistry.lookupFluidForBlock(block) + + override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = true + + override def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = Array(new FluidTankInfo(new FluidTank(fluid, FluidContainerRegistry.BUCKET_VOLUME, FluidContainerRegistry.BUCKET_VOLUME))) + + override protected def uncheckedDrain(doDrain: Boolean): FluidStack = { + if (doDrain) { + position.world.get.setBlockToAir(position) + } + new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME) + } + } + + private class AirBlockWrapper(val position: BlockPosition, val block: Block) extends IFluidHandler { + override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = false + + override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean): FluidStack = null + + override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean): FluidStack = null + + override def canFill(from: ForgeDirection, fluid: Fluid): Boolean = fluid.canBePlacedInWorld + + override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean): Int = { + if (resource != null && resource.getFluid.canBePlacedInWorld && resource.getFluid.getBlock != null) { + if (doFill) { + val world = position.world.get + world.breakBlock(position) + world.setBlock(position, resource.getFluid.getBlock) + // This fake neighbor update is required to get stills to start flowing. + world.notifyBlockOfNeighborChange(position, world.getBlock(position)) + } + FluidContainerRegistry.BUCKET_VOLUME + } + else 0 + } + + override def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = Array.empty + } + +} diff --git a/src/main/scala/li/cil/oc/util/InventoryUtils.scala b/src/main/scala/li/cil/oc/util/InventoryUtils.scala index 951498bd9..35a691e66 100644 --- a/src/main/scala/li/cil/oc/util/InventoryUtils.scala +++ b/src/main/scala/li/cil/oc/util/InventoryUtils.scala @@ -221,6 +221,48 @@ object InventoryUtils { def extractFromInventoryAt(consumer: (ItemStack) => Unit, position: BlockPosition, side: ForgeDirection, limit: Int = 64) = inventoryAt(position).exists(extractFromInventory(consumer, _, side, limit)) + /** + * Transfers some items between two inventories. + *

+ * This will try to extract up the specified number of items from any inventory, + * then insert it into the specified sink inventory. If the insertion fails, the + * items will remain in the source inventory. + *

+ * This uses the extractFromInventory and insertIntoInventory + * methods, and therefore handles special cases such as sided inventories and + * stack size limits. + *

+ * This returns true if at least one item was transferred. + */ + def transferBetweenInventories(source: IInventory, sourceSide: ForgeDirection, sink: IInventory, sinkSide: Option[ForgeDirection], limit: Int = 64) = + extractFromInventory( + insertIntoInventory(_, sink, sinkSide, limit), source, sourceSide, limit) + + /** + * Like transferBetweenInventories but moving between specific slots. + */ + def transferBetweenInventoriesSlots(source: IInventory, sourceSide: ForgeDirection, sourceSlot: Int, sink: IInventory, sinkSide: Option[ForgeDirection], sinkSlot: Int, limit: Int = 64) = + extractFromInventorySlot( + insertIntoInventorySlot(_, sink, sinkSide, sinkSlot, limit), source, sourceSide, sourceSlot, limit) + + /** + * Utility method for calling transferBetweenInventories on inventories + * in the world. + */ + def transferBetweenInventoriesAt(source: BlockPosition, sourceSide: ForgeDirection, sink: BlockPosition, sinkSide: Option[ForgeDirection], limit: Int = 64) = + inventoryAt(source).exists(sourceInventory => + inventoryAt(sink).exists(sinkInventory => + transferBetweenInventories(sourceInventory, sourceSide, sinkInventory, sinkSide, limit))) + + /** + * Utility method for calling transferBetweenInventoriesSlots on inventories + * in the world. + */ + def transferBetweenInventoriesSlotsAt(sourcePos: BlockPosition, sourceSide: ForgeDirection, sourceSlot: Int, sinkPos: BlockPosition, sinkSide: Option[ForgeDirection], sinkSlot: Int, limit: Int = 64) = + inventoryAt(sourcePos).exists(sourceInventory => + inventoryAt(sinkPos).exists(sinkInventory => + transferBetweenInventoriesSlots(sourceInventory, sourceSide, sourceSlot, sinkInventory, sinkSide, sinkSlot, limit))) + /** * Utility method for dropping contents from a single inventory slot into * the world.