blob: eeeade7e3f4af8495ca7bc06f2f868a7cc028d37 [file] [log] [blame] [raw]
package li.cil.oc.common.tileentity
import java.util.UUID
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import li.cil.oc._
import li.cil.oc.api.Driver
import li.cil.oc.api.driver.item
import li.cil.oc.api.driver.item.Container
import li.cil.oc.api.driver.item.Memory
import li.cil.oc.api.driver.item.Processor
import li.cil.oc.api.event.RobotAnalyzeEvent
import li.cil.oc.api.event.RobotMoveEvent
import li.cil.oc.api.internal
import li.cil.oc.api.network._
import li.cil.oc.client.gui
import li.cil.oc.common.Slot
import li.cil.oc.common.Tier
import li.cil.oc.integration.opencomputers.DriverKeyboard
import li.cil.oc.integration.opencomputers.DriverRedstoneCard
import li.cil.oc.integration.opencomputers.DriverScreen
import li.cil.oc.server.component.robot
import li.cil.oc.server.component.robot.Inventory
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.util.ItemUtils
import net.minecraft.block.Block
import net.minecraft.block.BlockLiquid
import net.minecraft.client.Minecraft
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.fluids._
import scala.collection.mutable
// Implementation note: this tile entity is never directly added to the world.
// It is always wrapped by a `RobotProxy` tile entity, which forwards any
// necessary calls to this class. This is done to make moves efficient: when a
// robot moves we only create a new proxy tile entity, hook the instance of this
// class that was held by the old proxy to it and can then safely forget the
// old proxy, which will be cleaned up by Minecraft like any other tile entity.
class Robot extends traits.Computer with traits.PowerInformation with IFluidHandler with internal.Robot {
var proxy: RobotProxy = _
val info = new ItemUtils.RobotData()
val bot = if (isServer) new robot.Robot(this) else null
val inventory = new Inventory(this)
if (isServer) {
machine.setCostPerTick(Settings.get.robotCost)
}
// ----------------------------------------------------------------------- //
val actualInventorySize = 86
def maxInventorySize = actualInventorySize - 1 - containerCount - componentCount
var inventorySize = -1
var selectedSlot = actualSlot(0)
var selectedTank = 0
// For client.
var renderingErrored = false
// Fixed number of containers (mostly due to GUI limitation, but also because
// I find three to be a large enough number for sufficient flexibility).
override def containerCount = 3
override def componentCount = info.components.length
override def getComponentInSlot(index: Int) = components(index).orNull
override def player() = player(facing, facing)
override def synchronizeSlot(slot: Int) = if (slot >= 0 && slot < getSizeInventory) this.synchronized {
val stack = getStackInSlot(slot)
components(slot) match {
case Some(component) =>
// We're guaranteed to have a driver for entries.
save(component, Driver.driverFor(stack, getClass), stack)
case _ =>
}
ServerPacketSender.sendRobotInventory(this, slot, stack)
}
def containerSlots = 1 to info.containers.length
def componentSlots = getSizeInventory - componentCount until getSizeInventory
def inventorySlots = actualSlot(0) until actualSlot(0) + inventorySize
// ----------------------------------------------------------------------- //
override def node = if (isServer) machine.node else null
var globalBuffer, globalBufferSize = 0.0
val maxComponents = 32
var owner = "OpenComputers"
var ownerUuid: Option[UUID] = None
var animationTicksLeft = 0
var animationTicksTotal = 0
var moveFromX, moveFromY, moveFromZ = Int.MaxValue
var swingingTool = false
var turnAxis = 0
var appliedToolEnchantments = false
private lazy val player_ = new robot.Player(this)
def determineUUID(playerUUID: Option[UUID] = None) = {
val format = Settings.get.uuidFormat
val randomUUID = UUID.randomUUID()
try UUID.fromString(format.
replaceAllLiterally("$random$", randomUUID.toString).
replaceAllLiterally("$player$", playerUUID.getOrElse(randomUUID).toString)) catch {
case t: Throwable =>
OpenComputers.log.warn("Failed determining robot UUID, check your config's `uuidFormat` entry!", t)
randomUUID
}
}
// ----------------------------------------------------------------------- //
def name = info.name
override def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = {
player.addChatMessage(Localization.Analyzer.RobotOwner(owner))
player.addChatMessage(Localization.Analyzer.RobotName(player_.getCommandSenderName))
MinecraftForge.EVENT_BUS.post(new RobotAnalyzeEvent(this, player))
super.onAnalyze(player, side, hitX, hitY, hitZ)
}
def player(facing: ForgeDirection = facing, side: ForgeDirection = facing) = {
player_.updatePositionAndRotation(facing, side)
player_
}
def actualSlot(n: Int) = n + 1 + containerCount
def move(direction: ForgeDirection): Boolean = {
val (ox, oy, oz) = (x, y, z)
val (nx, ny, nz) = (x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ)
if (!world.blockExists(nx, ny, nz)) {
return false // Don't fall off the earth.
}
if (isServer) {
val event = new RobotMoveEvent.Pre(this, direction)
MinecraftForge.EVENT_BUS.post(event)
if (event.isCanceled) return false
}
val blockRobotProxy = api.Items.get("robot").block.asInstanceOf[common.block.RobotProxy]
val blockRobotAfterImage = api.Items.get("robotAfterimage").block.asInstanceOf[common.block.RobotAfterimage]
val wasAir = world.isAirBlock(nx, ny, nz)
val block = world.getBlock(nx, ny, nz)
val metadata = world.getBlockMetadata(nx, ny, nz)
try {
// Setting this will make the tile entity created via the following call
// to setBlock to re-use our "real" instance as the inner object, instead
// of creating a new one.
blockRobotProxy.moving.set(Some(this))
// Do *not* immediately send the change to clients to allow checking if it
// worked before the client is notified so that we can use the same trick on
// the client by sending a corresponding packet. This also saves us from
// having to send the complete state again (e.g. screen buffer) each move.
world.setBlockToAir(nx, ny, nz)
// In some cases (though I couldn't quite figure out which one) setBlock
// will return true, even though the block was not created / adjusted.
val created = world.setBlock(nx, ny, nz, blockRobotProxy, 0, 1) &&
world.getTileEntity(nx, ny, nz) == proxy
if (created) {
assert(x == nx && y == ny && z == nz)
world.setBlock(ox, oy, oz, net.minecraft.init.Blocks.air, 0, 1)
world.setBlock(ox, oy, oz, blockRobotAfterImage, 0, 1)
assert(world.getBlock(ox, oy, oz) == blockRobotAfterImage)
// Here instead of Lua callback so that it gets called on client, too.
val moveTicks = math.max((Settings.get.moveDelay * 20).toInt, 1)
setAnimateMove(ox, oy, oz, moveTicks)
if (isServer) {
ServerPacketSender.sendRobotMove(this, ox, oy, oz, direction)
checkRedstoneInputChanged()
MinecraftForge.EVENT_BUS.post(new RobotMoveEvent.Post(this, direction))
}
else {
// If we broke some replaceable block (like grass) play its break sound.
if (!wasAir) {
if (block != null && block != blockRobotAfterImage) {
if (FluidRegistry.lookupFluidForBlock(block) == null &&
!block.isInstanceOf[BlockFluidBase] &&
!block.isInstanceOf[BlockLiquid]) {
world.playAuxSFX(2001, nx, ny, nz, Block.getIdFromBlock(block) + (metadata << 12))
}
else {
world.playSound(nx + 0.5, ny + 0.5, nz + 0.5, "liquid.water",
world.rand.nextFloat * 0.25f + 0.75f, world.rand.nextFloat * 1.0f + 0.5f, false)
}
}
}
world.markBlockForUpdate(ox, oy, oz)
world.markBlockForUpdate(nx, ny, nz)
}
assert(!isInvalid)
}
else {
world.setBlockToAir(nx, ny, nz)
}
created
}
finally {
blockRobotProxy.moving.set(None)
}
}
// ----------------------------------------------------------------------- //
def isAnimatingMove = animationTicksLeft > 0 && (moveFromX != Int.MaxValue || moveFromY != Int.MaxValue || moveFromZ != Int.MaxValue)
def isAnimatingSwing = animationTicksLeft > 0 && swingingTool
def isAnimatingTurn = animationTicksLeft > 0 && turnAxis != 0
def animateSwing(duration: Double) = if (items(0).isDefined) {
setAnimateSwing((duration * 20).toInt)
ServerPacketSender.sendRobotAnimateSwing(this)
}
def animateTurn(clockwise: Boolean, duration: Double) = {
setAnimateTurn(if (clockwise) 1 else -1, (duration * 20).toInt)
ServerPacketSender.sendRobotAnimateTurn(this)
}
def setAnimateMove(fromX: Int, fromY: Int, fromZ: Int, ticks: Int) {
animationTicksTotal = ticks
prepareForAnimation()
moveFromX = fromX
moveFromY = fromY
moveFromZ = fromZ
}
def setAnimateSwing(ticks: Int) {
animationTicksTotal = ticks
prepareForAnimation()
swingingTool = true
}
def setAnimateTurn(axis: Int, ticks: Int) {
animationTicksTotal = ticks
prepareForAnimation()
turnAxis = axis
}
private def prepareForAnimation() {
animationTicksLeft = animationTicksTotal
moveFromX = Int.MaxValue
moveFromY = Int.MaxValue
moveFromZ = Int.MaxValue
swingingTool = false
turnAxis = 0
}
// ----------------------------------------------------------------------- //
override def shouldRenderInPass(pass: Int) = true
override def getRenderBoundingBox =
getBlockType.getCollisionBoundingBoxFromPool(world, x, y, z).expand(0.5, 0.5, 0.5)
// ----------------------------------------------------------------------- //
override def updateEntity() {
if (animationTicksLeft > 0) {
animationTicksLeft -= 1
if (animationTicksLeft == 0) {
moveFromX = Int.MaxValue
moveFromY = Int.MaxValue
moveFromZ = Int.MaxValue
swingingTool = false
turnAxis = 0
}
}
super.updateEntity()
if (isServer) {
if (world.getTotalWorldTime % Settings.get.tickFrequency == 0) {
if (info.tier == 3) {
bot.node.changeBuffer(Double.PositiveInfinity)
}
globalBuffer = bot.node.globalBuffer
globalBufferSize = bot.node.globalBufferSize
info.totalEnergy = globalBuffer.toInt
info.robotEnergy = bot.node.localBuffer.toInt
updatePowerInformation()
}
if (!appliedToolEnchantments) {
appliedToolEnchantments = true
Option(getStackInSlot(0)) match {
case Some(item) => player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers)
case _ =>
}
}
}
else if (isRunning && isAnimatingMove) {
client.Sound.updatePosition(this)
}
inventory.decrementAnimations()
}
override protected def initialize() {
if (isServer) {
// Ensure we have a node address, because the proxy needs this to initialize
// its own node to the same address ours has.
api.Network.joinNewNetwork(node)
}
}
override def dispose() {
super.dispose()
if (isClient) {
Minecraft.getMinecraft.currentScreen match {
case robotGui: gui.Robot if robotGui.robot == this =>
Minecraft.getMinecraft.displayGuiScreen(null)
case _ =>
}
}
}
// ----------------------------------------------------------------------- //
override def readFromNBT(nbt: NBTTagCompound) {
updateInventorySize()
machine.onHostChanged()
bot.load(nbt.getCompoundTag(Settings.namespace + "robot"))
if (nbt.hasKey(Settings.namespace + "owner")) {
owner = nbt.getString(Settings.namespace + "owner")
}
if (nbt.hasKey(Settings.namespace + "ownerUuid")) {
ownerUuid = Option(UUID.fromString(nbt.getString(Settings.namespace + "ownerUuid")))
}
if (inventorySize > 0) {
selectedSlot = nbt.getInteger(Settings.namespace + "selectedSlot") max inventorySlots.min min inventorySlots.max
}
selectedTank = nbt.getInteger(Settings.namespace + "selectedTank")
animationTicksTotal = nbt.getInteger(Settings.namespace + "animationTicksTotal")
animationTicksLeft = nbt.getInteger(Settings.namespace + "animationTicksLeft")
if (animationTicksLeft > 0) {
moveFromX = nbt.getInteger(Settings.namespace + "moveFromX")
moveFromY = nbt.getInteger(Settings.namespace + "moveFromY")
moveFromZ = nbt.getInteger(Settings.namespace + "moveFromZ")
swingingTool = nbt.getBoolean(Settings.namespace + "swingingTool")
turnAxis = nbt.getByte(Settings.namespace + "turnAxis")
}
}
// Side check for Waila (and other mods that may call this client side).
override def writeToNBT(nbt: NBTTagCompound) = if (isServer) this.synchronized {
info.save(nbt)
// Note: computer is saved when proxy is saved (in proxy's super writeToNBT)
// which is a bit ugly, and may be refactored some day, but it works.
nbt.setNewCompoundTag(Settings.namespace + "robot", bot.save)
nbt.setString(Settings.namespace + "owner", owner)
ownerUuid.foreach(uuid => nbt.setString(Settings.namespace + "ownerUuid", uuid.toString))
nbt.setInteger(Settings.namespace + "selectedSlot", selectedSlot)
nbt.setInteger(Settings.namespace + "selectedTank", selectedTank)
if (isAnimatingMove || isAnimatingSwing || isAnimatingTurn) {
nbt.setInteger(Settings.namespace + "animationTicksTotal", animationTicksTotal)
nbt.setInteger(Settings.namespace + "animationTicksLeft", animationTicksLeft)
nbt.setInteger(Settings.namespace + "moveFromX", moveFromX)
nbt.setInteger(Settings.namespace + "moveFromY", moveFromY)
nbt.setInteger(Settings.namespace + "moveFromZ", moveFromZ)
nbt.setBoolean(Settings.namespace + "swingingTool", swingingTool)
nbt.setByte(Settings.namespace + "turnAxis", turnAxis.toByte)
}
}
@SideOnly(Side.CLIENT)
override def readFromNBTForClient(nbt: NBTTagCompound) {
super.readFromNBTForClient(nbt)
load(nbt)
info.load(nbt)
updateInventorySize()
selectedSlot = nbt.getInteger("selectedSlot")
animationTicksTotal = nbt.getInteger("animationTicksTotal")
animationTicksLeft = nbt.getInteger("animationTicksLeft")
moveFromX = nbt.getInteger("moveFromX")
moveFromY = nbt.getInteger("moveFromY")
moveFromZ = nbt.getInteger("moveFromZ")
if (animationTicksLeft > 0) {
swingingTool = nbt.getBoolean("swingingTool")
turnAxis = nbt.getByte("turnAxis")
}
connectComponents()
}
override def writeToNBTForClient(nbt: NBTTagCompound) = this.synchronized {
super.writeToNBTForClient(nbt)
save(nbt)
info.save(nbt)
nbt.setInteger("selectedSlot", selectedSlot)
if (isAnimatingMove || isAnimatingSwing || isAnimatingTurn) {
nbt.setInteger("animationTicksTotal", animationTicksTotal)
nbt.setInteger("animationTicksLeft", animationTicksLeft)
nbt.setInteger("moveFromX", moveFromX)
nbt.setInteger("moveFromY", moveFromY)
nbt.setInteger("moveFromZ", moveFromZ)
nbt.setBoolean("swingingTool", swingingTool)
nbt.setByte("turnAxis", turnAxis.toByte)
}
}
// ----------------------------------------------------------------------- //
override def onMachineConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
node.connect(bot.node)
node.asInstanceOf[Connector].setLocalBufferSize(0)
}
}
override def onMachineDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
node.remove()
bot.node.remove()
for (slot <- componentSlots) {
Option(getComponentInSlot(slot)).foreach(_.node.remove())
}
}
}
// ----------------------------------------------------------------------- //
override protected def onItemAdded(slot: Int, stack: ItemStack) {
if (isServer) {
if (isToolSlot(slot)) {
player_.getAttributeMap.applyAttributeModifiers(stack.getAttributeModifiers)
ServerPacketSender.sendRobotInventory(this, slot, stack)
}
if (isUpgradeSlot(slot)) {
ServerPacketSender.sendRobotInventory(this, slot, stack)
}
if (isFloppySlot(slot)) {
common.Sound.playDiskInsert(this)
}
if (isComponentSlot(slot)) {
super.onItemAdded(slot, stack)
world.notifyBlocksOfNeighborChange(x, y, z, getBlockType)
}
if (isInventorySlot(slot)) {
machine.signal("inventory_changed", Int.box(slot - actualSlot(0) + 1))
}
}
}
override protected def onItemRemoved(slot: Int, stack: ItemStack) {
super.onItemRemoved(slot, stack)
if (isServer) {
if (isToolSlot(slot)) {
player_.getAttributeMap.removeAttributeModifiers(stack.getAttributeModifiers)
ServerPacketSender.sendRobotInventory(this, slot, null)
}
if (isUpgradeSlot(slot)) {
ServerPacketSender.sendRobotInventory(this, slot, null)
}
if (isFloppySlot(slot)) {
common.Sound.playDiskEject(this)
}
if (isInventorySlot(slot)) {
machine.signal("inventory_changed", Int.box(slot - actualSlot(0) + 1))
}
if (isComponentSlot(slot)) {
world.notifyBlocksOfNeighborChange(x, y, z, getBlockType)
}
}
}
override def markDirty() {
super.markDirty()
// Avoid getting into a bad state on the client when updating before we
// got the descriptor packet from the server. If we manage to open the
// GUI before the descriptor packet arrived, close it again because it is
// invalid anyway.
if (inventorySize >= 0) {
updateInventorySize()
}
else if (isClient) {
Minecraft.getMinecraft.currentScreen match {
case robotGui: gui.Robot if robotGui.robot == this =>
Minecraft.getMinecraft.displayGuiScreen(null)
case _ =>
}
}
renderingErrored = false
}
override protected def connectItemNode(node: Node) {
super.connectItemNode(node)
if (node != null) node.host match {
case buffer: api.component.TextBuffer =>
for (slot <- componentSlots) {
getComponentInSlot(slot) match {
case keyboard: api.component.Keyboard => buffer.node.connect(keyboard.node)
case _ =>
}
}
case keyboard: api.component.Keyboard =>
for (slot <- componentSlots) {
getComponentInSlot(slot) match {
case buffer: api.component.TextBuffer => keyboard.node.connect(buffer.node)
case _ =>
}
}
case _ =>
}
}
override def isComponentSlot(slot: Int) = (containerSlots ++ componentSlots) contains slot
def containerSlotType(slot: Int) = if (containerSlots contains slot) {
val stack = info.containers(slot - 1)
Option(Driver.driverFor(stack, getClass)) match {
case Some(driver: Container) => driver.providedSlot(stack)
case _ => Slot.None
}
}
else Slot.None
def containerSlotTier(slot: Int) = if (containerSlots contains slot) {
val stack = info.containers(slot - 1)
Option(Driver.driverFor(stack, getClass)) match {
case Some(driver: Container) => driver.providedTier(stack)
case _ => Tier.None
}
}
else Tier.None
def isToolSlot(slot: Int) = slot == 0
def isContainerSlot(slot: Int) = containerSlots contains slot
def isInventorySlot(slot: Int) = inventorySlots contains slot
def isFloppySlot(slot: Int) = isComponentSlot(slot) && (Option(getStackInSlot(slot)) match {
case Some(stack) => Option(Driver.driverFor(stack, getClass)) match {
case Some(driver) => driver.slot(stack) == Slot.Floppy
case _ => false
}
case _ => false
})
def isUpgradeSlot(slot: Int) = containerSlotType(slot) == Slot.Upgrade
// ----------------------------------------------------------------------- //
override def callBudget = (containerSlots ++ componentSlots).foldLeft(0.0)((acc, slot) => acc + (Option(getStackInSlot(slot)) match {
case Some(stack) => Option(Driver.driverFor(stack, getClass)) match {
case Some(driver: Processor) if driver.slot(stack) == Slot.CPU => Settings.get.callBudgets(driver.tier(stack))
case _ => 0
}
case _ => 0
}))
override def installedMemory = (containerSlots ++ componentSlots).foldLeft(0)((acc, slot) => acc + (Option(getStackInSlot(slot)) match {
case Some(stack) => Option(Driver.driverFor(stack, getClass)) match {
case Some(driver: Memory) => driver.amount(stack)
case _ => 0
}
case _ => 0
}))
override def componentSlot(address: String) = components.indexWhere(_.exists(env => env.node != null && env.node.address == address))
override def hasRedstoneCard = (containerSlots ++ componentSlots).exists(slot => Option(getStackInSlot(slot)).fold(false)(DriverRedstoneCard.worksWith(_, getClass)))
private def computeInventorySize() = math.min(maxInventorySize, (containerSlots ++ componentSlots).foldLeft(0)((acc, slot) => acc + (Option(getStackInSlot(slot)) match {
case Some(stack) => Option(Driver.driverFor(stack, getClass)) match {
case Some(driver: item.Inventory) => driver.inventoryCapacity(stack)
case _ => 0
}
case _ => 0
})))
private var updatingInventorySize = false
def updateInventorySize() = this.synchronized(if (!updatingInventorySize) try {
updatingInventorySize = true
val newInventorySize = computeInventorySize()
if (newInventorySize != inventorySize) {
inventorySize = newInventorySize
val realSize = 1 + containerCount + inventorySize
val oldSelected = selectedSlot - actualSlot(0)
val removed = mutable.ArrayBuffer.empty[ItemStack]
for (slot <- realSize until getSizeInventory - componentCount) {
val stack = getStackInSlot(slot)
setInventorySlotContents(slot, null)
if (stack != null) removed += stack
}
val copyComponentCount = math.min(getSizeInventory, componentCount)
Array.copy(components, getSizeInventory - copyComponentCount, components, realSize, copyComponentCount)
for (slot <- math.max(0, getSizeInventory - componentCount) until getSizeInventory if slot < realSize || slot >= realSize + componentCount) {
components(slot) = None
}
getSizeInventory = realSize + componentCount
if (world != null && isServer) {
for (stack <- removed) {
inventory.addItemStackToInventory(stack)
spawnStackInWorld(stack, facing)
}
} // else: save is screwed and we potentially lose items. Life is hard.
selectedSlot = math.max(actualSlot(0), math.min(actualSlot(inventorySize) - 1, actualSlot(oldSelected)))
}
}
finally {
updatingInventorySize = false
})
// ----------------------------------------------------------------------- //
var getSizeInventory = actualInventorySize
override def getInventoryStackLimit = 64
override def getStackInSlot(slot: Int) = {
if (slot >= getSizeInventory) null // Required to always show 16 inventory slots in GUI.
else if (slot >= getSizeInventory - componentCount) {
info.components(slot - (getSizeInventory - componentCount))
}
else super.getStackInSlot(slot)
}
override def setInventorySlotContents(slot: Int, stack: ItemStack) {
if (slot < getSizeInventory - componentCount && isItemValidForSlot(slot, stack)) {
if (stack != null && stack.stackSize > 1 && isComponentSlot(slot)) {
super.setInventorySlotContents(slot, stack.splitStack(1))
if (stack.stackSize > 0 && isServer) {
inventory.addItemStackToInventory(stack)
spawnStackInWorld(stack, facing)
}
}
else super.setInventorySlotContents(slot, stack)
}
else if (stack != null && stack.stackSize > 0) spawnStackInWorld(stack, ForgeDirection.UP)
}
override def isItemValidForSlot(slot: Int, stack: ItemStack) = (slot, Option(Driver.driverFor(stack, getClass))) match {
case (0, _) => true // Allow anything in the tool slot.
case (i, Some(driver)) if isContainerSlot(i) =>
// Yay special cases! Dynamic screens kind of work, but are pretty derpy
// because the item gets send around on changes, including the screen
// state, which leads to weird effects. Also, it's really illogical that
// a screen (and keyboard) could be attached to the robot on the fly.
// Since these are very special (as they have special behavior in the
// GUI) I feel it's OK to handle it like this, instead of some extra API
// logic making the differentiation of assembler and containers generic.
driver != DriverScreen &&
driver != DriverKeyboard &&
driver.slot(stack) == containerSlotType(i) &&
driver.tier(stack) <= containerSlotTier(i)
case (i, _) if isInventorySlot(i) => true // Normal inventory.
case _ => false // Invalid slot.
}
// ----------------------------------------------------------------------- //
override def canExtractItem(slot: Int, stack: ItemStack, side: Int) =
getAccessibleSlotsFromSide(side).contains(slot)
override def canInsertItem(slot: Int, stack: ItemStack, side: Int) =
getAccessibleSlotsFromSide(side).contains(slot) &&
isItemValidForSlot(slot, stack)
override def getAccessibleSlotsFromSide(side: Int) =
toLocal(ForgeDirection.getOrientation(side)) match {
case ForgeDirection.WEST => Array(0) // Tool
case ForgeDirection.EAST => containerSlots.toArray
case _ => inventorySlots.toArray
}
// ----------------------------------------------------------------------- //
def getFluidTank(tank: Int) = tryGetTank(tank).orNull
def tryGetTank(tank: Int) = {
val tanks = components.collect {
case Some(tank: IFluidTank) => tank
}
if (tank < 0 || tank >= tanks.length) None
else Option(tanks(tank))
}
def tankCount = components.count {
case Some(tank: IFluidTank) => true
case _ => false
}
// ----------------------------------------------------------------------- //
override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean) =
tryGetTank(selectedTank) match {
case Some(tank) =>
tank.fill(resource, doFill)
case _ => 0
}
override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean) =
tryGetTank(selectedTank) match {
case Some(tank) if tank.getFluid != null && tank.getFluid.isFluidEqual(resource) =>
tank.drain(resource.amount, doDrain)
case _ => null
}
override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean) = {
tryGetTank(selectedTank) match {
case Some(tank) =>
tank.drain(maxDrain, doDrain)
case _ => null
}
}
override def canFill(from: ForgeDirection, fluid: Fluid) = {
tryGetTank(selectedTank) match {
case Some(tank) => tank.getFluid == null || tank.getFluid.getFluid == fluid
case _ => false
}
}
override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = {
tryGetTank(selectedTank) match {
case Some(tank) => tank.getFluid != null && tank.getFluid.getFluid == fluid
case _ => false
}
}
override def getTankInfo(from: ForgeDirection) =
components.collect {
case Some(tank: IFluidTank) => tank.getInfo
}.toArray
}