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, # Per-byte cost for ECDSA operation is controlled by `complex` value,
# because data is hashed with SHA256 before signing/verifying # because data is hashed with SHA256 before signing/verifying
dataCardAsymmetric: 10.0 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 # 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.keyboard.name=Keyboard
tile.oc.microcontroller.name=Microcontroller tile.oc.microcontroller.name=Microcontroller
tile.oc.motionSensor.name=Motion Sensor tile.oc.motionSensor.name=Motion Sensor
tile.oc.netSplitter.name=Net Splitter
tile.oc.powerConverter.name=Power Converter tile.oc.powerConverter.name=Power Converter
tile.oc.powerDistributor.name=Power Distributor tile.oc.powerDistributor.name=Power Distributor
tile.oc.print.name=3D Print 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.screen3.name=Screen (Tier 3)
tile.oc.serverRack.name=Server Rack tile.oc.serverRack.name=Server Rack
tile.oc.switch.name=§cSwitch§7 tile.oc.switch.name=§cSwitch§7
tile.oc.netSplitter.name=Net Splitter tile.oc.transposer.name=Transposer
tile.oc.waypoint.name=Waypoint tile.oc.waypoint.name=Waypoint
# Items # 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.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.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.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.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.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. 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] [yellowDust, "oc:circuitChip3", glass]
[obsidian, yellowDust, obsidian]] [obsidian, yellowDust, obsidian]]
} }
transposer {
input: [[ingotIron, "oc:inventoryControllerUpgrade", ingotIron]
[hopper, bucket, hopper]
[ingotIron, "oc:tankControllerUpgrade", ingotIron]]
output: 4
}
waypoint { waypoint {
input: [[ingotIron, "oc:circuitChip1", ingotIron] input: [[ingotIron, "oc:circuitChip1", ingotIron]
["oc:materialTransistor", "oc:materialInterweb", "oc:materialTransistor"], ["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 Keyboard = "keyboard"
final val Microcontroller = "microcontroller" final val Microcontroller = "microcontroller"
final val MotionSensor = "motionSensor" final val MotionSensor = "motionSensor"
final val NetSplitter = "netSplitter"
final val PowerConverter = "powerConverter" final val PowerConverter = "powerConverter"
final val PowerDistributor = "powerDistributor" final val PowerDistributor = "powerDistributor"
final val Print = "print" final val Print = "print"
@ -39,7 +40,7 @@ object Constants {
final val ScreenTier3 = "screen3" final val ScreenTier3 = "screen3"
final val ServerRack = "serverRack" final val ServerRack = "serverRack"
final val Switch = "switch" final val Switch = "switch"
final val NetSplitter = "netSplitter" final val Transposer = "transposer"
final val Waypoint = "waypoint" final val Waypoint = "waypoint"
def Case(tier: Int) = ItemUtils.caseNameWithTierSuffix("case", tier) 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 dataCardComplex = config.getDouble("power.cost.dataCardComplex") max 0
val dataCardComplexByte = config.getDouble("power.cost.dataCardComplexByte") max 0 val dataCardComplexByte = config.getDouble("power.cost.dataCardComplexByte") max 0
val dataCardAsymmetric = config.getDouble("power.cost.dataCardAsymmetric") max 0 val dataCardAsymmetric = config.getDouble("power.cost.dataCardAsymmetric") max 0
val transposerCost = config.getDouble("power.cost.transposer") max 0
// power.rate // power.rate
val accessPointRate = config.getDouble("power.rate.accessPoint") max 0 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.component
import li.cil.oc.api.event.FileSystemAccessEvent import li.cil.oc.api.event.FileSystemAccessEvent
import li.cil.oc.client.renderer.PetRenderer import li.cil.oc.client.renderer.PetRenderer
import li.cil.oc.common.Loot
import li.cil.oc.common.PacketType import li.cil.oc.common.PacketType
import li.cil.oc.common.container import li.cil.oc.common.container
import li.cil.oc.common.tileentity._ import li.cil.oc.common.tileentity._
import li.cil.oc.common.tileentity.traits._ import li.cil.oc.common.tileentity.traits._
import li.cil.oc.common.Loot
import li.cil.oc.common.{PacketHandler => CommonPacketHandler} import li.cil.oc.common.{PacketHandler => CommonPacketHandler}
import li.cil.oc.util.Audio import li.cil.oc.util.Audio
import li.cil.oc.util.ExtendedWorld._ import li.cil.oc.util.ExtendedWorld._
@ -79,6 +79,7 @@ object PacketHandler extends CommonPacketHandler {
case PacketType.ServerPresence => onServerPresence(p) case PacketType.ServerPresence => onServerPresence(p)
case PacketType.Sound => onSound(p) case PacketType.Sound => onSound(p)
case PacketType.SoundPattern => onSoundPattern(p) case PacketType.SoundPattern => onSoundPattern(p)
case PacketType.TransposerActivity => onTransposerActivity(p)
case PacketType.WaypointLabel => onWaypointLabel(p) case PacketType.WaypointLabel => onWaypointLabel(p)
case _ => // Invalid packet. 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) = { def onParticleEffect(p: PacketParser) = {
val dimension = p.readInt() val dimension = p.readInt()
world(p.player, dimension) match { world(p.player, dimension) match {
@ -592,15 +602,6 @@ object PacketHandler extends CommonPacketHandler {
buffer.rawSetForeground(col, row, color) 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) = def onScreenTouchMode(p: PacketParser) =
p.readTileEntity[Screen]() match { p.readTileEntity[Screen]() match {
case Some(t) => t.invertTouchMode = p.readBoolean() 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) = def onWaypointLabel(p: PacketParser) =
p.readTileEntity[Waypoint]() match { p.readTileEntity[Waypoint]() match {
case Some(waypoint) => waypoint.label = p.readUTF() case Some(waypoint) => waypoint.label = p.readUTF()

View File

@ -61,6 +61,7 @@ private[oc] class Proxy extends CommonProxy {
else else
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Hologram], HologramRendererFallback) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Hologram], HologramRendererFallback)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Microcontroller], MicrocontrollerRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Microcontroller], MicrocontrollerRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.NetSplitter], NetSplitterRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.PowerDistributor], PowerDistributorRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.PowerDistributor], PowerDistributorRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Printer], PrinterRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Printer], PrinterRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Raid], RaidRenderer) 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.Relay], SwitchRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.RobotProxy], RobotRenderer) ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.RobotProxy], RobotRenderer)
ClientRegistry.bindTileEntitySpecialRenderer(classOf[tileentity.Screen], ScreenRenderer) 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.ItemName.Floppy).createItemStack(1).getItem, ItemRenderer)
MinecraftForgeClient.registerItemRenderer(Items.get(Constants.BlockName.Print).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 = _ var iconOn: IIcon = _
} }
object Transposer {
var iconOn: IIcon = _
}
def init(tm: TextureManager) { def init(tm: TextureManager) {
tm.bindTexture(fontAntiAliased) tm.bindTexture(fontAntiAliased)
tm.bindTexture(fontAliased) tm.bindTexture(fontAliased)

View File

@ -67,6 +67,13 @@ object BlockRenderer extends ISimpleBlockRenderingHandler {
Tessellator.instance.draw() Tessellator.instance.draw()
RenderState.checkError(getClass.getName + ".renderInventoryBlock: splitter") 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 _ => case _ =>
block match { block match {
case simple: SimpleBlock => case simple: SimpleBlock =>
@ -103,7 +110,7 @@ object BlockRenderer extends ISimpleBlockRenderingHandler {
RenderState.checkError(getClass.getName + ".renderWorldBlock: assembler") RenderState.checkError(getClass.getName + ".renderWorldBlock: assembler")
true true
case cable: tileentity.Cable => case _: tileentity.Cable =>
Cable.render(world, x, y, z, block, renderer) Cable.render(world, x, y, z, block, renderer)
RenderState.checkError(getClass.getName + ".renderWorldBlock: cable") RenderState.checkError(getClass.getName + ".renderWorldBlock: cable")
@ -127,7 +134,7 @@ object BlockRenderer extends ISimpleBlockRenderingHandler {
RenderState.checkError(getClass.getName + ".renderWorldBlock: print") RenderState.checkError(getClass.getName + ".renderWorldBlock: print")
true true
case printer: tileentity.Printer => case _: tileentity.Printer =>
Printer.render(block, x, y, z, renderer) Printer.render(block, x, y, z, renderer)
RenderState.checkError(getClass.getName + ".renderWorldBlock: printer") RenderState.checkError(getClass.getName + ".renderWorldBlock: printer")
@ -144,6 +151,12 @@ object BlockRenderer extends ISimpleBlockRenderingHandler {
RenderState.checkError(getClass.getName + ".renderWorldBlock: splitter") RenderState.checkError(getClass.getName + ".renderWorldBlock: splitter")
true
case _: tileentity.Transposer =>
Transposer.render(block, x, y, z, renderer)
RenderState.checkError(getClass.getName + ".renderWorldBlock: transposer")
true true
case _ => case _ =>
val result = renderer.renderStandardBlock(block, x, y, z) val result = renderer.renderStandardBlock(block, x, y, z)
@ -158,7 +171,8 @@ object BlockRenderer extends ISimpleBlockRenderingHandler {
block.isInstanceOf[Hologram] || block.isInstanceOf[Hologram] ||
block.isInstanceOf[Printer] || block.isInstanceOf[Printer] ||
block.isInstanceOf[Print] || 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? // The texture flip this works around only seems to occur for blocks with custom block renderers?
def patchedRenderer(renderer: RenderBlocks, block: Block) = 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, HologramTranslation,
HologramValues, HologramValues,
LootDisk, LootDisk,
NetSplitterState,
ParticleEffect, ParticleEffect,
PetVisibility, // Goes both ways. PetVisibility, // Goes both ways.
PowerState, PowerState,
@ -50,11 +51,11 @@ object PacketType extends Enumeration {
TextBufferMultiRawSetBackground, TextBufferMultiRawSetBackground,
TextBufferMultiRawSetForeground, TextBufferMultiRawSetForeground,
TextBufferPowerChange, TextBufferPowerChange,
NetSplitterState,
ScreenTouchMode, ScreenTouchMode,
ServerPresence, ServerPresence,
Sound, Sound,
SoundPattern, SoundPattern,
TransposerActivity,
WaypointLabel, // Goes both ways. WaypointLabel, // Goes both ways.
// Client -> Server // Client -> Server

View File

@ -1,5 +1,7 @@
package li.cil.oc.common.block 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.Settings
import li.cil.oc.client.Textures import li.cil.oc.client.Textures
import li.cil.oc.common.tileentity import li.cil.oc.common.tileentity
@ -19,6 +21,7 @@ class Geolyzer extends SimpleBlock {
Some("GeolyzerSide") Some("GeolyzerSide")
) )
@SideOnly(Side.CLIENT)
override def registerBlockIcons(iconRegister: IIconRegister) = { override def registerBlockIcons(iconRegister: IIconRegister) = {
super.registerBlockIcons(iconRegister) super.registerBlockIcons(iconRegister)
Textures.Geolyzer.iconTopOn = iconRegister.registerIcon(Settings.resourceDomain + ":GeolyzerTopOn") Textures.Geolyzer.iconTopOn = iconRegister.registerIcon(Settings.resourceDomain + ":GeolyzerTopOn")

View File

@ -23,8 +23,8 @@ class NetSplitter extends RedstoneAware {
Some("NetSplitterSide") Some("NetSplitterSide")
) )
@SideOnly(Side.CLIENT) override @SideOnly(Side.CLIENT)
def registerBlockIcons(iconRegister: IIconRegister): Unit = { override def registerBlockIcons(iconRegister: IIconRegister): Unit = {
super.registerBlockIcons(iconRegister) super.registerBlockIcons(iconRegister)
Textures.NetSplitter.iconOn = iconRegister.registerIcon(Settings.resourceDomain + ":NetSplitterOn") 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.Geolyzer], Settings.namespace + "geolyzer")
GameRegistry.registerTileEntity(classOf[tileentity.Microcontroller], Settings.namespace + "microcontroller") GameRegistry.registerTileEntity(classOf[tileentity.Microcontroller], Settings.namespace + "microcontroller")
GameRegistry.registerTileEntity(classOf[tileentity.MotionSensor], Settings.namespace + "motion_sensor") 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.PowerConverter], Settings.namespace + "power_converter")
GameRegistry.registerTileEntity(classOf[tileentity.PowerDistributor], Settings.namespace + "power_distributor") GameRegistry.registerTileEntity(classOf[tileentity.PowerDistributor], Settings.namespace + "power_distributor")
GameRegistry.registerTileEntity(classOf[tileentity.Print], Settings.namespace + "print") 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.Switch], Settings.namespace + "switch")
GameRegistry.registerTileEntity(classOf[tileentity.Screen], Settings.namespace + "screen") GameRegistry.registerTileEntity(classOf[tileentity.Screen], Settings.namespace + "screen")
GameRegistry.registerTileEntity(classOf[tileentity.ServerRack], Settings.namespace + "serverRack") 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") GameRegistry.registerTileEntity(classOf[tileentity.Waypoint], Settings.namespace + "waypoint")
Items.registerBlock(new AccessPoint(), Constants.BlockName.AccessPoint) Items.registerBlock(new AccessPoint(), Constants.BlockName.AccessPoint)
@ -81,5 +82,8 @@ object Blocks {
// v1.5.14 // v1.5.14
Recipes.addBlock(new NetSplitter(), Constants.BlockName.NetSplitter, "oc:netSplitter") 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) { def sendParticleEffect(position: BlockPosition, name: String, count: Int, velocity: Double, direction: Option[ForgeDirection] = None): Unit = if (count > 0) {
val pb = new SimplePacketBuilder(PacketType.ParticleEffect) val pb = new SimplePacketBuilder(PacketType.ParticleEffect)
@ -529,16 +539,6 @@ object PacketSender {
pb.sendToPlayersNearHost(host) 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) { def sendScreenTouchMode(t: tileentity.Screen, value: Boolean) {
val pb = new SimplePacketBuilder(PacketType.ScreenTouchMode) val pb = new SimplePacketBuilder(PacketType.ScreenTouchMode)
@ -619,6 +619,14 @@ object PacketSender {
pb.sendToNearbyPlayers(world, x, y, z, Option(32)) 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 = { def sendWaypointLabel(t: Waypoint): Unit = {
val pb = new SimplePacketBuilder(PacketType.WaypointLabel) 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.Callback
import li.cil.oc.api.machine.Context import li.cil.oc.api.machine.Context
import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ExtendedArguments._
import li.cil.oc.util.ExtendedBlock._ import li.cil.oc.util.FluidUtils
import li.cil.oc.util.ExtendedWorld._
import li.cil.oc.util.ResultWrapper.result 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.FluidStack
import net.minecraftforge.fluids.IFluidBlock
import net.minecraftforge.fluids.IFluidHandler
trait TankWorldControl extends TankAware with WorldAware with SideRestricted { 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.") @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) val side = checkSideForAction(args, 0)
fluidInTank(selectedTank) match { fluidInTank(selectedTank) match {
case Some(stack) => case Some(stack) =>
val blockPos = position.offset(side) FluidUtils.fluidHandlerAt(position.offset(side)) match {
if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match { case Some(handler) => result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid))))
case handler: IFluidHandler => case _ => result(false)
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)
} }
else result(false)
case _ => 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.") @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] = { def drain(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0) val facing = checkSideForAction(args, 0)
val count = args.optFluidCount(1) val count = args.optFluidCount(1) max 0
getTank(selectedTank) match { getTank(selectedTank) match {
case Some(tank) => case Some(tank) =>
val space = tank.getCapacity - tank.getFluidAmount val space = tank.getCapacity - tank.getFluidAmount
val amount = math.min(count, space) val amount = math.min(count, space)
if (count > 0 && amount == 0) { if (count < 1 || amount > 0) {
result(Unit, "tank is full") FluidUtils.fluidHandlerAt(position.offset(facing)) match {
} case Some(handler) =>
else {
val blockPos = position.offset(facing)
if (world.blockExists(blockPos)) world.getTileEntity(blockPos) match {
case handler: IFluidHandler =>
tank.getFluid match { tank.getFluid match {
case stack: FluidStack => case stack: FluidStack =>
val drained = handler.drain(facing.getOpposite, new FluidStack(stack, amount), true) 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) val transferred = tank.fill(handler.drain(facing.getOpposite, amount, true), true)
result(transferred > 0, transferred) result(transferred > 0, transferred)
} }
case _ => world.getBlock(blockPos) match { case _ => result(Unit, "incompatible or no fluid")
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")
}
} }
else result(Unit, "incompatible or no fluid")
} }
else result(Unit, "tank is full")
case _ => result(Unit, "no tank selected") 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.") @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] = { def fill(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0) val facing = checkSideForAction(args, 0)
val count = args.optFluidCount(1) val count = args.optFluidCount(1) max 0
getTank(selectedTank) match { getTank(selectedTank) match {
case Some(tank) => case Some(tank) =>
val amount = math.min(count, tank.getFluidAmount) val amount = math.min(count, tank.getFluidAmount)
if (count > 0 && amount == 0) { if (count < 1 || amount > 0) {
result(Unit, "tank is empty") 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) else result(Unit, "tank is empty")
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")
case _ => result(Unit, "no tank selected") 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.Callback
import li.cil.oc.api.machine.Context import li.cil.oc.api.machine.Context
import li.cil.oc.server.component.result import li.cil.oc.server.component.result
import li.cil.oc.util.ExtendedWorld._ import li.cil.oc.util.FluidUtils
import net.minecraftforge.fluids.IFluidHandler
trait WorldTankAnalytics extends WorldAware with SideRestricted { trait WorldTankAnalytics extends WorldAware with SideRestricted {
@Callback(doc = """function(side:number):number -- Get the amount of fluid in the tank on the specified side.""") @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] = { def getTankLevel(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0) 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) result(handler.getTankInfo(facing.getOpposite).map(info => Option(info.fluid).fold(0)(_.amount)).sum)
case _ => result(Unit, "no tank") 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.""") @Callback(doc = """function(side:number):number -- Get the capacity of the tank on the specified side.""")
def getTankCapacity(context: Context, args: Arguments): Array[AnyRef] = { def getTankCapacity(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0) val facing = checkSideForAction(args, 0)
world.getTileEntity(position.offset(facing)) match { FluidUtils.fluidHandlerAt(position.offset(facing)) match {
case handler: IFluidHandler => case Some(handler) =>
result(handler.getTankInfo(facing.getOpposite).map(_.capacity).foldLeft(0)((max, capacity) => math.max(max, capacity))) result(handler.getTankInfo(facing.getOpposite).map(_.capacity).foldLeft(0)((max, capacity) => math.max(max, capacity)))
case _ => result(Unit, "no tank") 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.""") @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) { def getFluidInTank(context: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) {
val facing = checkSideForAction(args, 0) val facing = checkSideForAction(args, 0)
world.getTileEntity(position.offset(facing)) match { FluidUtils.fluidHandlerAt(position.offset(facing)) match {
case handler: IFluidHandler => case Some(handler) =>
result(handler.getTankInfo(facing.getOpposite)) result(handler.getTankInfo(facing.getOpposite))
case _ => result(Unit, "no tank") 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 li.cil.oc.api.machine.Arguments
import net.minecraft.inventory.IInventory import net.minecraft.inventory.IInventory
import net.minecraftforge.common.util.ForgeDirection import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.fluids.FluidContainerRegistry
import scala.language.implicitConversions import scala.language.implicitConversions
@ -16,7 +17,7 @@ object ExtendedArguments {
if (!isDefined(index) || !hasValue(index)) default if (!isDefined(index) || !hasValue(index)) default
else math.max(0, math.min(64, args.checkInteger(index))) 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 if (!isDefined(index) || !hasValue(index)) default
else math.max(0, args.checkInteger(index)) else math.max(0, args.checkInteger(index))

View File

@ -1,6 +1,7 @@
package li.cil.oc.util package li.cil.oc.util
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraftforge.fluids.IFluidBlock
import scala.language.implicitConversions 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) 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) = def extractFromInventoryAt(consumer: (ItemStack) => Unit, position: BlockPosition, side: ForgeDirection, limit: Int = 64) =
inventoryAt(position).exists(extractFromInventory(consumer, _, side, limit)) 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 * Utility method for dropping contents from a single inventory slot into
* the world. * the world.