Added transposer block for moving items and fluids between adjacent blocks. Closes #1244.

Also refactored fluid stuffs quite a bit; may lead to slight changes in behavior here and there (e.g. returning empty table for tank info instead of "no tank" errors).
This commit is contained in:
Florian Nücke 2015-08-29 02:27:22 +02:00
parent 9ab9861ef4
commit fb816b9da7
29 changed files with 673 additions and 129 deletions

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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"],

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -104,6 +104,10 @@ object Textures {
var iconOn: IIcon = _
}
object Transposer {
var iconOn: IIcon = _
}
def init(tm: TextureManager) {
tm.bindTexture(fontAntiAliased)
tm.bindTexture(fontAliased)

View File

@ -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) =

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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

View File

@ -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")

View File

@ -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")
}

View File

@ -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()
}

View File

@ -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")
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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")
}

View File

@ -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))

View File

@ -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)
}
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* This returns <tt>true</tt> 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 <tt>transferBetweenFluidHandlers</tt> on handlers
* in the world.
* <p/>
* This uses the <tt>fluidHandlerAt</tt> 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
}
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* This uses the <tt>extractFromInventory</tt> and <tt>insertIntoInventory</tt>
* methods, and therefore handles special cases such as sided inventories and
* stack size limits.
* <p/>
* This returns <tt>true</tt> 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 <tt>transferBetweenInventories</tt> 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 <tt>transferBetweenInventories</tt> 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 <tt>transferBetweenInventoriesSlots</tt> 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.