diff --git a/assets/items.psd b/assets/items.psd index 7cbc4eeb7..18f11db5b 100644 Binary files a/assets/items.psd and b/assets/items.psd differ diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 96ad84362..4675034f8 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -891,8 +891,9 @@ opencomputers { # Allow robots to get a table representation of item stacks using the # inventory controller upgrade? (i.e. whether the getStackInSlot method - # of said upgrade is enabled or not). - allowItemStackInspection: false + # of said upgrade is enabled or not). Also applies to tank controller + # upgrade and it's fluid getter method. + allowItemStackInspection: true } # Settings that are intended for debugging issues, not for normal use. @@ -987,5 +988,9 @@ opencomputers { # not have an overly noticeable impact on performance, but it's disabled # by default because it is unnecessary in *most* cases. periodicallyForceLightUpdate: false + + # Pass along IDs of items and fluids when converting them to a table + # representation for Lua. + insertIdsInConverters: false } } \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/lang/de_DE.lang b/src/main/resources/assets/opencomputers/lang/de_DE.lang index 41da1a976..6b3a8902c 100644 --- a/src/main/resources/assets/opencomputers/lang/de_DE.lang +++ b/src/main/resources/assets/opencomputers/lang/de_DE.lang @@ -104,6 +104,8 @@ oc:item.UpgradeNavigation.name=Navigations-Upgrade oc:item.UpgradePiston.name=Kolben-Upgrade oc:item.UpgradeSign.name=Schild-I/O-Upgrade oc:item.UpgradeSolarGenerator.name=Solargenerator-Upgrade +oc:item.UpgradeTank.name=Tank-Upgrade +oc:item.UpgradeTankController.name=Tankbedienungs-Upgrade oc:item.UpgradeTractorBeam.name=Traktorstrahl-Upgrade oc:item.WirelessNetworkCard.name=Drahtlosnetzwerkkarte @@ -262,5 +264,7 @@ oc:tooltip.UpgradeNavigation=Erlaubt es Robotern, ihre Position und Ausrichtung oc:tooltip.UpgradePiston=Dieses Upgrade erlaubt es zu drängeln. Es macht es möglich Blöcke zu verschieben, ähnlich dem Kolben. Es kann jedoch §lkeine§7 Entities bewegen. oc:tooltip.UpgradeSign=Erlaubt das Lesen und Schreiben von Text auf Schildern. oc:tooltip.UpgradeSolarGenerator=Kann verwendet werden, um unterwegs Energie aus Sonnenlicht zu generieren. Benötigt eine ungehinderte Sicht zum Himmel über dem Roboter. Generiert Energie mit %s%% der Geschwindigkeit eines Stirlingmotors. +oc:tooltip.UpgradeTank=Dieses Upgrade gibt Robotern einen internen Tank. Ohne ein solches Upgrade können Roboter keine Flüssigkeiten verwahren. +oc:tooltip.UpgradeTankController=Dieses Upgrade erlaubt es dem Roboter, präziser mit externen Tanks zu interagieren, und erlaubt es ihm, Flüssigkeiten in und aus sich im Inventar befindlichen Tank-Gegenständen zu pumpen. oc:tooltip.UpgradeTractorBeam=Stattet den Roboter mit unglaublich fortschrittlicher Technologie - Kosename: "Gegenstandsmagnet" - aus. Erlaubt es dem Roboter, Gegenstände, innerhalb von 3 Blöcken um sich herum, einzusammeln. oc:tooltip.WirelessNetworkCard=Erlaubt das drahtlose Senden von Netzwerknachrichten, zusätzlich zu normalen. Drahtlose Nachrichten werden nur gesendet, wenn eine §fSignalstärke§7 festgelegt wurde! diff --git a/src/main/resources/assets/opencomputers/lang/en_US.lang b/src/main/resources/assets/opencomputers/lang/en_US.lang index 5b279af19..2267adf3b 100644 --- a/src/main/resources/assets/opencomputers/lang/en_US.lang +++ b/src/main/resources/assets/opencomputers/lang/en_US.lang @@ -104,6 +104,8 @@ oc:item.UpgradeNavigation.name=Navigation Upgrade oc:item.UpgradePiston.name=Piston Upgrade oc:item.UpgradeSign.name=Sign I/O Upgrade oc:item.UpgradeSolarGenerator.name=Solar Generator Upgrade +oc:item.UpgradeTank.name=Tank Upgrade +oc:item.UpgradeTankController.name=Tank Controller Upgrade oc:item.UpgradeTractorBeam.name=Tractor Beam Upgrade oc:item.WirelessNetworkCard.name=Wireless Network Card @@ -262,5 +264,7 @@ oc:tooltip.UpgradeNavigation=Can be used to determine the position and orientati oc:tooltip.UpgradePiston=This upgrade is very pushy. It allows moving blocks, similar to when using a piston. It does §lnot§7 move entities, however. oc:tooltip.UpgradeSign=Allows reading text on and writing text to signs. oc:tooltip.UpgradeSolarGenerator=Can be used to generate energy from sunlight on the go. Requires a clear line of sight to the sky above the robot. Generates energy at %s%% of the speed of a Stirling Engine. +oc:tooltip.UpgradeTank=This upgrade provides a tank for fluid storage to the robot. Without one of these, robots will not be able to store fluids internally. +oc:tooltip.UpgradeTankController=This upgrade allows the robot more control in how it interacts with external tanks, and allows it to transfer fluids into and out of fluid tank items in its inventory. oc:tooltip.UpgradeTractorBeam=Equips the robot with extremely advanced technology, nicknamed the "Item Magnet". Allows it to pick up items anywhere within 3 blocks of its location. oc:tooltip.WirelessNetworkCard=Allows wireless sending of network messages in addition to normal ones. You can adjust the §fsignal strength§7 to control how far messages are sent. Higher signal strength results in higher energy consumption. diff --git a/src/main/resources/assets/opencomputers/lua/component/robot/lib/robot.lua b/src/main/resources/assets/opencomputers/lua/component/robot/lib/robot.lua index 97825c793..eb2c3daa9 100644 --- a/src/main/resources/assets/opencomputers/lua/component/robot/lib/robot.lua +++ b/src/main/resources/assets/opencomputers/lua/component/robot/lib/robot.lua @@ -41,24 +41,24 @@ function robot.inventorySize() end -function robot.select(slot) - return component.robot.select(slot) +function robot.select(...) + return component.robot.select(...) end -function robot.count(slot) - return component.robot.count(slot) +function robot.count(...) + return component.robot.count(...) end -function robot.space(slot) - return component.robot.space(slot) +function robot.space(...) + return component.robot.space(...) end -function robot.compareTo(slot) - return component.robot.compareTo(slot) +function robot.compareTo(...) + return component.robot.compareTo(...) end -function robot.transferTo(slot, count) - return component.robot.transferTo(slot, count) +function robot.transferTo(...) + return component.robot.transferTo(...) end ------------------------------------------------------------------------------- @@ -195,6 +195,79 @@ function robot.turnAround() return turn() and turn() end +------------------------------------------------------------------------------- +-- Tank + +function robot.tankCount() + return component.robot.tankCount() +end + + +function robot.selectTank(tank) + return component.robot.selectTank(tank) +end + +function robot.tankLevel() + return component.robot.tankLevel() +end + +function robot.tankSpace() + return component.robot.tankSpace() +end + +function robot.compareFluidTo(...) + return component.robot.compareFluidTo(...) +end + +function robot.transferFluidTo(...) + return component.robot.transferFluidTo(...) +end + +------------------------------------------------------------------------------- +-- Tank + World + +function robot.compareFluid() + return component.robot.compareFluid(sides.front) +end + +function robot.compareFluidUp() + return component.robot.compareFluid(sides.up) +end + +function robot.compareFluidDown() + return component.robot.compareFluid(sides.down) +end + +function robot.drain(count) + checkArg(1, count, "nil", "number") + return component.robot.drain(sides.front, count) +end + +function robot.drainUp(count) + checkArg(1, count, "nil", "number") + return component.robot.drain(sides.up, count) +end + +function robot.drainDown(count) + checkArg(1, count, "nil", "number") + return component.robot.drain(sides.down, count) +end + +function robot.fill(count) + checkArg(1, count, "nil", "number") + return component.robot.fill(sides.front, count) +end + +function robot.fillUp(count) + checkArg(1, count, "nil", "number") + return component.robot.fill(sides.up, count) +end + +function robot.fillDown(count) + checkArg(1, count, "nil", "number") + return component.robot.fill(sides.down, count) +end + ------------------------------------------------------------------------------- return robot diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index a70bb5cdb..d7110d8fb 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -196,6 +196,16 @@ solarGeneratorUpgrade { ["oc:circuitChip3", blockLapis, "oc:circuitChip3"] [ingotIron, "oc:materialCircuitBoardPrinted", ingotIron]] } +tankUpgrade { + input: [[plankWood, fenceIron, plankWood] + [dispenser, cauldron, craftingPiston] + [plankWood, "oc:circuitChip1", plankWood]] +} +tankControllerUpgrade { + input: [[ingotGold, glassBottle, ingotGold] + [dispenser, "oc:circuitChip2", craftingPiston] + [ingotGold, "oc:materialCircuitBoardPrinted", ingotGold]] +} tractorBeamUpgrade { input: [[ingotGold, craftingPiston, ingotGold] [ingotIron, "oc:capacitor", ingotIron] diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeTank.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeTank.png new file mode 100644 index 000000000..18b580fc1 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/UpgradeTank.png differ diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeTankController.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeTankController.png new file mode 100644 index 000000000..2ace96705 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/items/UpgradeTankController.png differ diff --git a/src/main/scala/li/cil/oc/Items.scala b/src/main/scala/li/cil/oc/Items.scala index 0cd6fc3f2..8838023eb 100644 --- a/src/main/scala/li/cil/oc/Items.scala +++ b/src/main/scala/li/cil/oc/Items.scala @@ -261,5 +261,7 @@ object Items extends ItemAPI { // 1.3.5 Recipes.addItem(new item.TabletCase(multi), "tabletCase", "oc:tabletCase") Recipes.addItem(new item.UpgradePiston(multi), "pistonUpgrade", "oc:pistonUpgrade") + Recipes.addItem(new item.UpgradeTank(multi), "tankUpgrade", "oc:tankUpgrade") + Recipes.addItem(new item.UpgradeTankController(multi), "tankControllerUpgrade", "oc:tankControllerUpgrade") } } \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 267a48813..d098300f9 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -248,6 +248,7 @@ class Settings(config: Config) { val disassembleAllTheThings = config.getBoolean("misc.disassembleAllTheThings") val disassemblerBreakChance = config.getDouble("misc.disassemblerBreakChance") max 0 min 1 val hideOwnPet = config.getBoolean("misc.hideOwnSpecial") + val allowItemStackInspection = config.getBoolean("misc.allowItemStackInspection") // ----------------------------------------------------------------------- // // debug @@ -265,6 +266,7 @@ class Settings(config: Config) { val debugPersistence = config.getBoolean("debug.verbosePersistenceErrors") val nativeInTmpDir = config.getBoolean("debug.nativeInTmpDir") val periodicallyForceLightUpdate = config.getBoolean("debug.periodicallyForceLightUpdate") + val insertIdsInConverters = config.getBoolean("debug.insertIdsInConverters") } object Settings { @@ -352,6 +354,11 @@ object Settings { "computer.debug", "misc.alwaysTryNative", "misc.verbosePersistenceErrors" + ), + // Upgrading to version 1.3.5, added forgotten check for item stack, + // inspection, patch to true to avoid stuff suddenly breaking. + VersionRange.createFromVersionSpec("1.3.4") -> Array( + "misc.allowItemStackInspection" ) ) diff --git a/src/main/scala/li/cil/oc/common/Proxy.scala b/src/main/scala/li/cil/oc/common/Proxy.scala index abef79d81..45920a41c 100644 --- a/src/main/scala/li/cil/oc/common/Proxy.scala +++ b/src/main/scala/li/cil/oc/common/Proxy.scala @@ -102,6 +102,8 @@ class Proxy { api.Driver.add(driver.item.UpgradePiston) api.Driver.add(driver.item.UpgradeSign) api.Driver.add(driver.item.UpgradeSolarGenerator) + api.Driver.add(driver.item.UpgradeTank) + api.Driver.add(driver.item.UpgradeTankController) api.Driver.add(driver.item.UpgradeTractorBeam) api.Driver.add(driver.item.WirelessNetworkCard) @@ -116,6 +118,7 @@ class Proxy { } OpenComputers.log.info("Initializing vanilla converters.") + api.Driver.add(driver.converter.FluidStack) api.Driver.add(driver.converter.FluidTankInfo) api.Driver.add(driver.converter.ItemStack) diff --git a/src/main/scala/li/cil/oc/common/item/Tablet.scala b/src/main/scala/li/cil/oc/common/item/Tablet.scala index 1962b46f2..75fe9a8f5 100644 --- a/src/main/scala/li/cil/oc/common/item/Tablet.scala +++ b/src/main/scala/li/cil/oc/common/item/Tablet.scala @@ -17,7 +17,7 @@ import li.cil.oc.server.component import li.cil.oc.util.ExtendedNBT._ import li.cil.oc.util.ItemUtils.TabletData import li.cil.oc.util.{ItemUtils, RotationHelper} -import li.cil.oc.{OpenComputers, Settings, api} +import li.cil.oc.{Localization, OpenComputers, Settings, api} import net.minecraft.entity.Entity import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack @@ -251,7 +251,14 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp override def isPaused = computer.isPaused - override def start() = computer.start() + override def start() = { + val result = computer.start() + computer.lastError match { + case message if message != null => holder.addChatMessage(Localization.Analyzer.LastError(message)) + case _ => + } + result + } override def pause(seconds: Double) = computer.pause(seconds) diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeTank.scala b/src/main/scala/li/cil/oc/common/item/UpgradeTank.scala new file mode 100644 index 000000000..c5c38438b --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/UpgradeTank.scala @@ -0,0 +1,23 @@ +package li.cil.oc.common.item + +import java.util + +import cpw.mods.fml.relauncher.{Side, SideOnly} +import li.cil.oc.Settings +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.ItemStack +import net.minecraftforge.fluids.FluidStack + +class UpgradeTank(val parent: Delegator) extends Delegate with ItemTier { + @SideOnly(Side.CLIENT) override + def tooltipLines(stack: ItemStack, player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) = { + if (stack.hasTagCompound) { + FluidStack.loadFluidStackFromNBT(stack.getTagCompound.getCompoundTag(Settings.namespace + "data")) match { + case stack: FluidStack => + tooltip.add(stack.getFluid.getLocalizedName(stack) + ": " + stack.amount + "/16000") + case _ => + } + } + super.tooltipLines(stack, player, tooltip, advanced) + } +} diff --git a/src/main/scala/li/cil/oc/common/item/UpgradeTankController.scala b/src/main/scala/li/cil/oc/common/item/UpgradeTankController.scala new file mode 100644 index 000000000..34ccea9b5 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/item/UpgradeTankController.scala @@ -0,0 +1,3 @@ +package li.cil.oc.common.item + +class UpgradeTankController(val parent: Delegator) extends Delegate with ItemTier \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/common/recipe/ExtendedRecipe.scala b/src/main/scala/li/cil/oc/common/recipe/ExtendedRecipe.scala index c8e197ef2..01a7583bd 100644 --- a/src/main/scala/li/cil/oc/common/recipe/ExtendedRecipe.scala +++ b/src/main/scala/li/cil/oc/common/recipe/ExtendedRecipe.scala @@ -7,7 +7,9 @@ import li.cil.oc.util.{Color, SideTracker} import li.cil.oc.{Settings, api} import net.minecraft.inventory.InventoryCrafting import net.minecraft.item.ItemStack -import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.{NBTBase, NBTTagCompound} + +import scala.collection.convert.WrapAsScala._ object ExtendedRecipe { private lazy val navigationUpgrade = api.Items.get("navigationUpgrade") @@ -41,10 +43,18 @@ object ExtendedRecipe { val nbt = craftedStack.getTagCompound for (i <- 0 until inventory.getSizeInventory) { val stack = inventory.getStackInSlot(i) - if (stack != null) Color.findDye(stack) match { - case Some(oreDictName) => - nbt.setInteger(Settings.namespace + "color", Color.dyes.indexOf(oreDictName)) - case _ => + if (stack != null) { + Color.findDye(stack) match { + case Some(oreDictName) => + nbt.setInteger(Settings.namespace + "color", Color.dyes.indexOf(oreDictName)) + case _ => + } + if (api.Items.get(stack) == floppy && stack.hasTagCompound) { + val oldData = stack.getTagCompound + for (oldTagName <- oldData.func_150296_c().map(_.asInstanceOf[String])) { + nbt.setTag(oldTagName, oldData.getTag(oldTagName).copy()) + } + } } } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala index 955d4f8ec..c0252bb32 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -22,7 +22,7 @@ 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.{BlockFluidBase, FluidRegistry} +import net.minecraftforge.fluids._ import scala.collection.mutable @@ -32,7 +32,7 @@ import scala.collection.mutable // 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 api.machine.Robot { +class Robot extends traits.Computer with traits.PowerInformation with api.machine.Robot with IFluidHandler { var proxy: RobotProxy = _ val info = new ItemUtils.RobotData() @@ -55,6 +55,8 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin var selectedSlot = actualSlot(0) + var selectedTank = 0 + // For client. var renderingErrored = false @@ -336,6 +338,7 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin 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) { @@ -357,6 +360,7 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin 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) @@ -443,6 +447,7 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin } if (isComponentSlot(slot)) { super.onItemAdded(slot, stack) + world.notifyBlocksOfNeighborChange(x, y, z, getBlockType) } if (isInventorySlot(slot)) { computer.signal("inventory_changed", Int.box(slot - actualSlot(0) + 1)) @@ -466,6 +471,9 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin if (isInventorySlot(slot)) { computer.signal("inventory_changed", Int.box(slot - actualSlot(0) + 1)) } + if (isComponentSlot(slot)) { + world.notifyBlocksOfNeighborChange(x, y, z, getBlockType) + } } } @@ -652,4 +660,62 @@ class Robot extends traits.Computer with traits.PowerInformation with api.machin case ForgeDirection.EAST => containerSlots.toArray case _ => inventorySlots.toArray } -} \ No newline at end of file + + // ----------------------------------------------------------------------- // + + def getFluidTank(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) = + getFluidTank(selectedTank) match { + case Some(tank) => + tank.fill(resource, doFill) + case _ => 0 + } + + override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean) = + getFluidTank(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) = { + getFluidTank(selectedTank) match { + case Some(tank) => + tank.drain(maxDrain, doDrain) + case _ => null + } + } + + override def canFill(from: ForgeDirection, fluid: Fluid) = { + getFluidTank(selectedTank) match { + case Some(tank) => tank.getFluid == null || tank.getFluid.getFluid == fluid + case _ => false + } + } + + override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = { + getFluidTank(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 +} diff --git a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala index b92381ddb..fd06b4a16 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/RobotProxy.scala @@ -12,8 +12,9 @@ import net.minecraft.inventory.ISidedInventory import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.common.util.ForgeDirection +import net.minecraftforge.fluids.{Fluid, FluidStack, IFluidHandler} -class RobotProxy(val robot: Robot) extends traits.Computer with traits.PowerInformation with api.machine.Robot with ISidedInventory { +class RobotProxy(val robot: Robot) extends traits.Computer with traits.PowerInformation with api.machine.Robot with ISidedInventory with IFluidHandler { def this() = this(new Robot()) // ----------------------------------------------------------------------- // @@ -250,4 +251,18 @@ class RobotProxy(val robot: Robot) extends traits.Computer with traits.PowerInfo override def globalBufferSize = robot.globalBufferSize override def globalBufferSize_=(value: Double) = robot.globalBufferSize = value + + // ----------------------------------------------------------------------- // + + override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean) = robot.fill(from, resource, doFill) + + override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean) = robot.drain(from, resource, doDrain) + + override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean) = robot.drain(from, maxDrain, doDrain) + + override def canFill(from: ForgeDirection, fluid: Fluid) = robot.canFill(from, fluid) + + override def canDrain(from: ForgeDirection, fluid: Fluid) = robot.canDrain(from, fluid) + + override def getTankInfo(from: ForgeDirection) = robot.getTankInfo(from) } diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeInventoryController.scala b/src/main/scala/li/cil/oc/server/component/UpgradeInventoryController.scala index e16783daf..160e7f579 100644 --- a/src/main/scala/li/cil/oc/server/component/UpgradeInventoryController.scala +++ b/src/main/scala/li/cil/oc/server/component/UpgradeInventoryController.scala @@ -29,7 +29,7 @@ class UpgradeInventoryController(val owner: Container with Robot) extends compon } @Callback(doc = """function():table -- Get a description of the stack in the the inventory on the specified side of the robot. Back refers to the robot's own inventory.""") - def getStackInSlot(context: Context, args: Arguments): Array[AnyRef] = { + def getStackInSlot(context: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) { val facing = checkSideForInventory(args, 0) val slot = args.checkInteger(1) - 1 if (facing == owner.facing.getOpposite) { @@ -43,6 +43,7 @@ class UpgradeInventoryController(val owner: Container with Robot) extends compon case _ => result(Unit, "no inventory") } } + else result(Unit, "not enabled in config") @Callback(doc = """function(facing:number, slot:number[, count:number]):boolean -- Drops the selected item stack into the specified slot of an inventory.""") def dropIntoSlot(context: Context, args: Arguments): Array[AnyRef] = { diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeTank.scala b/src/main/scala/li/cil/oc/server/component/UpgradeTank.scala new file mode 100644 index 000000000..76525a36a --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/UpgradeTank.scala @@ -0,0 +1,38 @@ +package li.cil.oc.server.component + +import li.cil.oc.api.Network +import li.cil.oc.api.driver.Container +import li.cil.oc.api.network.Visibility +import li.cil.oc.common.component.ManagedComponent +import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.fluids.{FluidStack, FluidTank, IFluidTank} + +class UpgradeTank(val owner: Container, val capacity: Int) extends ManagedComponent with IFluidTank { + val node = Network.newNode(this, Visibility.None).create() + + val tank = new FluidTank(capacity) + + override def load(nbt: NBTTagCompound) { + super.load(nbt) + tank.readFromNBT(nbt) + } + + override def save(nbt: NBTTagCompound) { + super.save(nbt) + tank.writeToNBT(nbt) + } + + // ----------------------------------------------------------------------- // + + override def getFluid = tank.getFluid + + override def getFluidAmount = tank.getFluidAmount + + override def getCapacity = tank.getCapacity + + override def getInfo = tank.getInfo + + override def fill(stack: FluidStack, doFill: Boolean) = tank.fill(stack, doFill) + + override def drain(maxDrain: Int, doDrain: Boolean) = tank.drain(maxDrain, doDrain) +} diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeTankController.scala b/src/main/scala/li/cil/oc/server/component/UpgradeTankController.scala new file mode 100644 index 000000000..ca512077d --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/UpgradeTankController.scala @@ -0,0 +1,125 @@ +package li.cil.oc.server.component + +import li.cil.oc.Settings +import li.cil.oc.api.Network +import li.cil.oc.api.driver.Container +import li.cil.oc.api.network._ +import li.cil.oc.common.component +import li.cil.oc.common.tileentity.Robot +import li.cil.oc.util.ExtendedArguments._ +import net.minecraft.item.ItemStack +import net.minecraftforge.common.util.ForgeDirection +import net.minecraftforge.fluids.{FluidContainerRegistry, IFluidContainerItem, IFluidHandler} + +class UpgradeTankController(val owner: Container with Robot) extends component.ManagedComponent { + val node = Network.newNode(this, Visibility.Network). + withComponent("tank_controller", Visibility.Neighbors). + withConnector(). + create() + + // ----------------------------------------------------------------------- // + + @Callback(doc = """function(side:number):number -- Get the capacity of the tank on the specified side of the robot. Back refers to the robot's own selected tank.""") + def getTankCapacity(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForTank(args, 0) + if (facing == owner.facing.getOpposite) result(owner.getFluidTank(owner.selectedTank).fold(0)(_.getCapacity)) + else owner.world.getTileEntity(math.floor(owner.xPosition).toInt + facing.offsetX, math.floor(owner.yPosition).toInt + facing.offsetY, math.floor(owner.zPosition).toInt + facing.offsetZ) match { + case handler: IFluidHandler => + result((owner.getFluidTank(owner.selectedTank) match { + case Some(tank) => handler.getTankInfo(facing.getOpposite).filter(info => info.fluid == null || info.fluid.isFluidEqual(tank.getFluid)) + case _ => handler.getTankInfo(facing.getOpposite) + }).map(_.capacity).foldLeft(0)((max, capacity) => math.max(max, capacity))) + case _ => result(Unit, "no tank") + } + } + + @Callback(doc = """function(side:number):table -- Get a description of the fluid in the the tank on the specified side of the robot. Back refers to the robot's own selected tank.""") + def getFluidInTank(context: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) { + val facing = checkSideForTank(args, 0) + if (facing == owner.facing.getOpposite) result(owner.getFluidTank(owner.selectedTank).map(_.getFluid).orNull) + else owner.world.getTileEntity(math.floor(owner.xPosition).toInt + facing.offsetX, math.floor(owner.yPosition).toInt + facing.offsetY, math.floor(owner.zPosition).toInt + facing.offsetZ) match { + case handler: IFluidHandler => result(Option(handler.getTankInfo(facing.getOpposite)).map(_.map(_.fluid)).orNull) + case _ => result(Unit, "no tank") + } + } + else result(Unit, "not enabled in config") + + @Callback(doc = """function([amount:number]):boolean -- Transfers fluid from a tank in the selected inventory slot to the selected tank.""") + def drain(context: Context, args: Arguments): Array[AnyRef] = { + val amount = args.optionalFluidCount(0) + owner.getFluidTank(owner.selectedTank) match { + case Some(tank) => + owner.getStackInSlot(owner.selectedSlot) match { + case stack: ItemStack => + if (FluidContainerRegistry.isFilledContainer(stack)) { + val contents = FluidContainerRegistry.getFluidForFilledItem(stack) + val container = stack.getItem.getContainerItem(stack) + if (tank.getCapacity - tank.getFluidAmount < contents.amount) { + result(Unit, "tank is full") + } + else if (tank.fill(contents, false) < contents.amount) { + result(Unit, "incompatible fluid") + } + else { + tank.fill(contents, true) + owner.decrStackSize(owner.selectedSlot, 1) + owner.player().inventory.addItemStackToInventory(container) + result(true) + } + } + else stack.getItem match { + case container: IFluidContainerItem => + val drained = container.drain(stack, amount, false) + val transferred = tank.fill(drained, true) + if (transferred > 0) { + container.drain(stack, transferred, true) + result(true) + } + else result(Unit, "incompatible or no fluid") + case _ => result(Unit, "item is empty or not a fluid container") + } + case _ => result(Unit, "nothing selected") + } + case _ => result(Unit, "no tank") + } + } + + @Callback(doc = """function([amount:number]):boolean -- Transfers fluid from the selected tank to a tank in the selected inventory slot.""") + def fill(context: Context, args: Arguments): Array[AnyRef] = { + val amount = args.optionalFluidCount(0) + owner.getFluidTank(owner.selectedTank) match { + case Some(tank) => + owner.getStackInSlot(owner.selectedSlot) match { + case stack: ItemStack => + if (FluidContainerRegistry.isEmptyContainer(stack)) { + val drained = tank.drain(amount, false) + val filled = FluidContainerRegistry.fillFluidContainer(drained, stack) + if (filled == null) { + result(Unit, "tank is empty") + } + else { + tank.drain(FluidContainerRegistry.getFluidForFilledItem(filled).amount, true) + owner.decrStackSize(owner.selectedSlot, 1) + owner.player().inventory.addItemStackToInventory(filled) + result(true) + } + } + else stack.getItem match { + case container: IFluidContainerItem => + val drained = tank.drain(amount, false) + val transferred = container.fill(stack, drained, true) + if (transferred > 0) { + tank.drain(transferred, true) + result(true) + } + else result(Unit, "incompatible or no fluid") + case _ => result(Unit, "item is full or not a fluid container") + } + case _ => result(Unit, "nothing selected") + } + case _ => result(Unit, "no tank") + } + } + + private def checkSideForTank(args: Arguments, n: Int) = owner.toGlobal(args.checkSide(n, ForgeDirection.SOUTH, ForgeDirection.NORTH, ForgeDirection.UP, ForgeDirection.DOWN)) +} diff --git a/src/main/scala/li/cil/oc/server/component/robot/Robot.scala b/src/main/scala/li/cil/oc/server/component/robot/Robot.scala index 1176f600f..e35081c4d 100644 --- a/src/main/scala/li/cil/oc/server/component/robot/Robot.scala +++ b/src/main/scala/li/cil/oc/server/component/robot/Robot.scala @@ -18,7 +18,7 @@ import net.minecraft.util.{MovingObjectPosition, Vec3} import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.util.ForgeDirection import net.minecraftforge.event.world.BlockEvent -import net.minecraftforge.fluids.FluidRegistry +import net.minecraftforge.fluids._ import scala.collection.convert.WrapAsScala._ @@ -45,6 +45,8 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent { def selectedSlot = robot.selectedSlot + def selectedTank = robot.selectedTank + def player = robot.player() def canPlaceInAir = { @@ -493,6 +495,185 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent { // ----------------------------------------------------------------------- // + @Callback + def tankCount(context: Context, args: Arguments): Array[AnyRef] = result(robot.tankCount) + + @Callback + def selectTank(context: Context, args: Arguments): Array[AnyRef] = { + if (args.count > 0 && args.checkAny(0) != null) { + robot.selectedTank = checkTank(args, 0) + } + result(selectedTank + 1) + } + + @Callback(direct = true) + def tankLevel(context: Context, args: Arguments): Array[AnyRef] = { + val index = + if (args.count > 0 && args.checkAny(0) != null) checkTank(args, 0) + else selectedTank + result(fluidInTank(index) match { + case Some(fluid) => fluid.amount + case _ => 0 + }) + } + + @Callback(direct = true) + def tankSpace(context: Context, args: Arguments): Array[AnyRef] = { + val index = + if (args.count > 0 && args.checkAny(0) != null) checkTank(args, 0) + else selectedTank + result(getTank(index) match { + case Some(tank) => tank.getCapacity - tank.getFluidAmount + case _ => 0 + }) + } + + @Callback + def compareFluidTo(context: Context, args: Arguments): Array[AnyRef] = { + val index = checkTank(args, 0) + result((fluidInTank(selectedTank), fluidInTank(index)) match { + case (Some(stackA), Some(stackB)) => haveSameFluidType(stackA, stackB) + case (None, None) => true + case _ => false + }) + } + + @Callback + def transferFluidTo(context: Context, args: Arguments): Array[AnyRef] = { + val index = checkTank(args, 0) + val count = args.optionalFluidCount(1) + if (index == selectedTank || count == 0) { + result(true) + } + else (getTank(selectedTank), getTank(index)) match { + case (Some(from), Some(to)) => + val drained = from.drain(count, false) + val transferred = to.fill(drained, true) + if (transferred > 0) { + from.drain(transferred, true) + robot.markDirty() + result(true) + } + else if (count >= from.getFluidAmount && to.getCapacity >= from.getFluidAmount && from.getCapacity >= to.getFluidAmount) { + // Swap. + val tmp = to.drain(to.getFluidAmount, true) + to.fill(from.drain(from.getFluidAmount, true), true) + from.fill(tmp, true) + robot.markDirty() + result(true) + } + else result(Unit, "incompatible or no fluid") + case _ => result(Unit, "invalid index") + } + } + + @Callback + def compareFluid(context: Context, args: Arguments): Array[AnyRef] = { + val side = checkSideForAction(args, 0) + fluidInTank(selectedTank) match { + case Some(stack) => + world.getTileEntity(x + side.offsetX, y + side.offsetY, z + side.offsetZ) match { + case handler: IFluidHandler => + result(Option(handler.getTankInfo(side.getOpposite)).exists(_.exists(other => stack.isFluidEqual(other.fluid)))) + case _ => + val block = world.getBlock(x + side.offsetX, y + side.offsetY, z + side.offsetZ) + val fluid = FluidRegistry.lookupFluidForBlock(block) + result(stack.getFluid == fluid) + } + case _ => result(false) + } + } + + @Callback + def drain(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForAction(args, 0) + val count = args.optionalFluidCount(1) + 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 world.getTileEntity(x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) match { + case handler: IFluidHandler => + tank.getFluid match { + case stack: FluidStack => + val drained = handler.drain(facing.getOpposite, new FluidStack(stack, amount), true) + if ((drained != null && drained.amount > 0) || amount == 0) { + tank.fill(drained, true) + result(true) + } + else result(Unit, "incompatible or no fluid") + case _ => + tank.fill(handler.drain(facing.getOpposite, amount, true), true) + result(true) + } + case _ => + val block = world.getBlock(x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) + val fluid = FluidRegistry.lookupFluidForBlock(block) + if (tank.fill(new FluidStack(fluid, 1000), false) == 1000) { + tank.fill(new FluidStack(fluid, 1000), true) + world.setBlockToAir(x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) + result(true) + } + else result(Unit, "tank is full") + } + case _ => result(Unit, "no tank selected") + } + } + + @Callback + def fill(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForAction(args, 0) + val count = args.optionalFluidCount(1) + getTank(selectedTank) match { + case Some(tank) => + val amount = math.min(count, tank.getFluidAmount) + if (count > 0 && amount == 0) { + result(Unit, "tank is empty") + } + val (bx, by, bz) = (x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) + world.getTileEntity(bx, by, bz) 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) + } + else result(Unit, "incompatible or no fluid") + case _ => + result(Unit, "tank is empty") + } + case _ => + val block = world.getBlock(bx, by, bz) + if (block != null && !block.isAir(world, x, y, z) && !block.isReplaceable(world, x, y, z)) { + 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.func_147480_a(bx, by, bz, true) + world.setBlock(bx, by, bz, fluidBlock) + // This fake neighbor update is required to get stills to start flowing. + world.notifyBlockOfNeighborChange(bx, by, bz, robot.block) + result(true) + } + } + case _ => result(Unit, "no tank selected") + } + } + + // ----------------------------------------------------------------------- // + override def onConnect(node: Node) { super.onConnect(node) if (node == this.node) { @@ -619,12 +800,23 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent { stackA.getItem == stackB.getItem && (!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage) + private def haveSameFluidType(stackA: FluidStack, stackB: FluidStack) = stackA.isFluidEqual(stackB) + private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(slot)) + private def getTank(index: Int) = robot.getFluidTank(index) + + private def fluidInTank(index: Int) = getTank(index) match { + case Some(tank) => Option(tank.getFluid) + case _ => None + } + // ----------------------------------------------------------------------- // private def checkSlot(args: Arguments, n: Int) = args.checkSlot(robot, n) + private def checkTank(args: Arguments, n: Int) = args.checkTank(robot, n) + private def checkSideForAction(args: Arguments, n: Int) = robot.toGlobal(args.checkSideForAction(n)) private def checkSideForFace(args: Arguments, n: Int, facing: ForgeDirection) = robot.toGlobal(args.checkSideForFace(n, robot.toLocal(facing))) diff --git a/src/main/scala/li/cil/oc/server/driver/converter/FluidStack.scala b/src/main/scala/li/cil/oc/server/driver/converter/FluidStack.scala new file mode 100644 index 000000000..b27613bbc --- /dev/null +++ b/src/main/scala/li/cil/oc/server/driver/converter/FluidStack.scala @@ -0,0 +1,25 @@ +package li.cil.oc.server.driver.converter + +import java.util + +import li.cil.oc.{Settings, api} + +import scala.collection.convert.WrapAsScala._ + +object FluidStack extends api.driver.Converter { + override def convert(value: scala.Any, output: util.Map[AnyRef, AnyRef]) = + value match { + case stack: net.minecraftforge.fluids.FluidStack => + if (Settings.get.insertIdsInConverters) { + output += "id" -> Int.box(stack.fluidID) + } + output += "amount" -> Int.box(stack.amount) + output += "hasTag" -> Boolean.box(stack.tag != null) + val fluid = stack.getFluid + if (fluid != null) { + output += "name" -> fluid.getName + output += "label" -> fluid.getLocalizedName(stack) + } + case _ => + } +} diff --git a/src/main/scala/li/cil/oc/server/driver/converter/FluidTankInfo.scala b/src/main/scala/li/cil/oc/server/driver/converter/FluidTankInfo.scala index 373b1302f..6d008531c 100644 --- a/src/main/scala/li/cil/oc/server/driver/converter/FluidTankInfo.scala +++ b/src/main/scala/li/cil/oc/server/driver/converter/FluidTankInfo.scala @@ -13,13 +13,7 @@ object FluidTankInfo extends api.driver.Converter { case tankInfo: fluids.FluidTankInfo => output += "capacity" -> Int.box(tankInfo.capacity) if (tankInfo.fluid != null) { - output += "amount" -> Int.box(tankInfo.fluid.amount) - output += "id" -> Int.box(tankInfo.fluid.fluidID) - val fluid = tankInfo.fluid.getFluid - if (fluid != null) { - output += "name" -> fluid.getName - output += "label" -> fluid.getLocalizedName(tankInfo.fluid) - } + FluidStack.convert(tankInfo.fluid, output) } else output += "amount" -> Int.box(0) case _ => diff --git a/src/main/scala/li/cil/oc/server/driver/converter/ItemStack.scala b/src/main/scala/li/cil/oc/server/driver/converter/ItemStack.scala index 120a6d9b8..e3a272e5a 100644 --- a/src/main/scala/li/cil/oc/server/driver/converter/ItemStack.scala +++ b/src/main/scala/li/cil/oc/server/driver/converter/ItemStack.scala @@ -2,7 +2,7 @@ package li.cil.oc.server.driver.converter import java.util -import li.cil.oc.api +import li.cil.oc.{Settings, api} import net.minecraft.item import net.minecraft.item.Item @@ -12,7 +12,9 @@ object ItemStack extends api.driver.Converter { override def convert(value: AnyRef, output: util.Map[AnyRef, AnyRef]) = value match { case stack: item.ItemStack => - output += "id" -> Int.box(Item.getIdFromItem(stack.getItem)) + if (Settings.get.insertIdsInConverters) { + output += "id" -> Int.box(Item.getIdFromItem(stack.getItem)) + } output += "damage" -> Int.box(stack.getItemDamage) output += "maxDamage" -> Int.box(stack.getMaxDamage) output += "size" -> Int.box(stack.stackSize) diff --git a/src/main/scala/li/cil/oc/server/driver/item/UpgradeTank.scala b/src/main/scala/li/cil/oc/server/driver/item/UpgradeTank.scala new file mode 100644 index 000000000..4cac30dc7 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/driver/item/UpgradeTank.scala @@ -0,0 +1,14 @@ +package li.cil.oc.server.driver.item + +import li.cil.oc.api +import li.cil.oc.api.driver.{Container, Slot} +import li.cil.oc.server.component +import net.minecraft.item.ItemStack + +object UpgradeTank extends Item { + override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("tankUpgrade")) + + override def createEnvironment(stack: ItemStack, container: Container) = new component.UpgradeTank(container, 16000) + + override def slot(stack: ItemStack) = Slot.Upgrade +} diff --git a/src/main/scala/li/cil/oc/server/driver/item/UpgradeTankController.scala b/src/main/scala/li/cil/oc/server/driver/item/UpgradeTankController.scala new file mode 100644 index 000000000..3743c3953 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/driver/item/UpgradeTankController.scala @@ -0,0 +1,21 @@ +package li.cil.oc.server.driver.item + +import li.cil.oc.api +import li.cil.oc.api.driver.{Container, Slot} +import li.cil.oc.common.Tier +import li.cil.oc.common.tileentity.Robot +import li.cil.oc.server.component +import net.minecraft.item.ItemStack + +object UpgradeTankController extends Item { + override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("tankControllerUpgrade")) + + override def createEnvironment(stack: ItemStack, container: Container) = container match { + case robot: Container with Robot => new component.UpgradeTankController(robot) + case _ => null + } + + override def slot(stack: ItemStack) = Slot.Upgrade + + override def tier(stack: ItemStack) = Tier.Two +} diff --git a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala index cb41db5d9..a4d307948 100644 --- a/src/main/scala/li/cil/oc/util/ExtendedArguments.scala +++ b/src/main/scala/li/cil/oc/util/ExtendedArguments.scala @@ -1,7 +1,7 @@ package li.cil.oc.util -import li.cil.oc.api.machine.Robot import li.cil.oc.api.network.Arguments +import li.cil.oc.common.tileentity.Robot import net.minecraft.inventory.IInventory import net.minecraftforge.common.util.ForgeDirection @@ -18,6 +18,12 @@ object ExtendedArguments { } else 64 + def optionalFluidCount(n: Int) = + if (args.count > n && args.checkAny(n) != null) { + math.max(0, args.checkInteger(n)) + } + else 1000 + def checkSlot(inventory: IInventory, n: Int) = { val slot = args.checkInteger(n) - 1 if (slot < 0 || slot >= inventory.getSizeInventory) { @@ -34,6 +40,14 @@ object ExtendedArguments { slot + 1 + robot.containerCount } + def checkTank(robot: Robot, n: Int) = { + val tank = args.checkInteger(n) - 1 + if (tank < 0 || tank >= robot.tankCount) { + throw new IllegalArgumentException("invalid tank index") + } + tank + } + def checkSideForAction(n: Int) = checkSide(n, ForgeDirection.SOUTH, ForgeDirection.UP, ForgeDirection.DOWN) def checkSideForMovement(n: Int) = checkSide(n, ForgeDirection.SOUTH, ForgeDirection.NORTH, ForgeDirection.UP, ForgeDirection.DOWN)