Cleaned up inventory util class some.

Added upgrade to allow choosing slot robot drops into / sucks from and change its equipped tool.
Fixed formatting bug in tooltip code, realized most of the code was unneeded because the font renderer can do it -.-
This commit is contained in:
Florian Nücke 2014-05-20 16:00:04 +02:00
parent c28cfa69fc
commit d7143f5ff7
17 changed files with 457 additions and 172 deletions

View File

@ -30,4 +30,30 @@ public interface Rotatable {
* @return the current facing.
*/
ForgeDirection facing();
/**
* Converts a facing relative to the block's <em>local</em> coordinate
* system to a <tt>global orientation</tt>, using south as the standard
* orientation.
* <p/>
* For example, if the block is facing east, calling this with south will
* return east, calling it with west will return south and so on.
*
* @param value the value to translate.
* @return the translated orientation.
*/
ForgeDirection toGlobal(ForgeDirection value);
/**
* Converts a <tt>global</tt> orientation to a facing relative to the
* block's <em>local</em> coordinate system, using south as the standard
* orientation.
* <p/>
* For example, if the block is facing east, calling this with south will
* return east, calling it with west will return north and so on.
*
* @param value the value to translate.
* @return the translated orientation.
*/
ForgeDirection toLocal(ForgeDirection value);
}

View File

@ -89,6 +89,10 @@ public interface Robot extends ISidedInventory, Rotatable {
/**
* Gets the index of the currently selected slot in the robot's inventory.
* <p/>
* This is the index in the underlying, <em>real</em> inventory. To get
* the 'local' index, i.e. the way the robot itself addresses it, add
* one for the tool and <tt>containerCount</tt> to this value.
*
* @return the index of the currently selected slot.
*/

View File

@ -13,6 +13,7 @@ oc:tile.Case3.name=Computergehäuse (Kreativ)
oc:tile.Charger.name=Ladestation
oc:tile.Disassembler.name=Recycler
oc:tile.DiskDrive.name=Diskettenlaufwerk
oc:tile.Geolyzer.name=Geolyzer
oc:tile.Keyboard.name=Tastatur
oc:tile.Hologram0.name=Hologrammprojektor (Stufe 1)
oc:tile.Hologram1.name=Hologrammprojektor (Stufe 2)
@ -85,6 +86,7 @@ oc:item.UpgradeCrafting.name=Fertigungs-Upgrade
oc:item.UpgradeExperience.name=Erfahrungs-Upgrade
oc:item.UpgradeGenerator.name=Generator-Upgrade
oc:item.UpgradeInventory.name=Inventar-Upgrade
oc:item.UpgradeInventoryController.name=Inventarbedienungs-Upgrade
oc:item.UpgradeNavigation.name=Navigationsupgrade
oc:item.UpgradeSign.name=Schild-I/O-Upgrade
oc:item.UpgradeSolarGenerator.name=Solargenerator-Upgrade
@ -163,6 +165,7 @@ oc:tooltip.Disassembler=Zerlegt Gegenstände in ihre Einzelteile. §lWarnung§7:
oc:tooltip.Disk=Sehr einfaches Speichermedium, das verwendet werden kann, um Disketten und Festplatten bauen.
oc:tooltip.DiskDrive.CC=ComputerCraft-Disketten werden §aunterstützt§7.
oc:tooltip.DiskDrive=Erlaubt es, Disketten zu lesen und zu beschreiben.
oc:tooltip.Geolyzer=Erlaubt es die Härte der Blöcke in der Umgebung abzufragen. Hilfreich um Hologramme der Gegend zu erzeugen, oder um Erze zu entdecken.
oc:tooltip.GraphicsCard=Erlaubt es, den angezeigten Inhalt von Bildschirmen zu ändern.[nl] Höchstauflösung: §f%sx%s§7.[nl] Maximale Farbtiefe: §f%s§7.[nl] Operationen/Tick: §f%s§7.
oc:tooltip.InternetCard=Diese Karte erlaubt es, HTTP-Anfragen zu senden und echte TCP Sockets zu verwenden.
oc:tooltip.Interweb=Kann in einer Internetkarte verwendet werden, um Computer mit dem rechtsfreien Raum kommunizieren zu lassen.
@ -209,6 +212,7 @@ oc:tooltip.UpgradeCrafting=Ermöglicht Robotern, in dem oberen linken Bereich ih
oc:tooltip.UpgradeExperience=Dieses Upgrade erlaubt es Robotern durch verschiedene Aktionen Erfahrung zu sammeln. Je mehr Erfahrung ein Roboter hat, desto mehr Energie kann er speichern, desto schneller kann er Blöcke abbauen und desto effizienter kann er mit Werkzeugen umgehen.
oc:tooltip.UpgradeGenerator=Kann verwendet werden, um unterwegs Energie aus Brennstoffen zu erzeugen. Verbraucht Gegenstände über einen ihrem Brennwert gemäßen Zeitraum.[nl] §fEffizienz§7: §a%s%%§7
oc:tooltip.UpgradeInventory=Dieses Upgrade gibt Robotern ein internes Inventar. Ohne ein solches Upgrade können Roboter keine Gegenstände verwahren.
oc:tooltip.UpgradeInventoryController=Dieses Upgrade erlaubt es dem Roboter präziser mit externen Inventaren zu interagieren, und erlaubt es ihm das angelegte Werkzeug mit einem Gegenstand in seinem Inventar auszutauschen.
oc:tooltip.UpgradeNavigation=Erlaubt es Robotern, ihre Position und Ausrichtung zu bestimmen. Die Position ist relativ zur Mitte der Karte, die in diesem Upgrade verbaut wurde.
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.

View File

@ -13,6 +13,7 @@ oc:tile.Case3.name=Computer Case (Creative)
oc:tile.Charger.name=Charger
oc:tile.Disassembler.name=Disassembler
oc:tile.DiskDrive.name=Disk Drive
oc:tile.Geolyzer.name=Geolyzer
oc:tile.Keyboard.name=Keyboard
oc:tile.Hologram0.name=Hologram Projector (Tier 1)
oc:tile.Hologram1.name=Hologram Projector (Tier 2)
@ -85,6 +86,7 @@ oc:item.UpgradeCrafting.name=Crafting Upgrade
oc:item.UpgradeExperience.name=Experience Upgrade
oc:item.UpgradeGenerator.name=Generator Upgrade
oc:item.UpgradeInventory.name=Inventory Upgrade
oc:item.UpgradeInventoryController.name=Inventory Controller Upgrade
oc:item.UpgradeNavigation.name=Navigation Upgrade
oc:item.UpgradeSign.name=Sign I/O Upgrade
oc:item.UpgradeSolarGenerator.name=Solar Generator Upgrade
@ -163,6 +165,7 @@ oc:tooltip.Disassembler=Separates items into their original components. §lWarni
oc:tooltip.Disk=Primitive medium that can be used to build persistent storage devices.
oc:tooltip.DiskDrive.CC=ComputerCraft floppies are §asupported§7.
oc:tooltip.DiskDrive=Allows reading and writing floppies.
oc:tooltip.Geolyzer=Allows scanning the surrounding area's blocks' hardness. This information can be useful for generating holograms of the area or for detecting ores.
oc:tooltip.GraphicsCard=Used to change what's displayed on screens.[nl] Maximum resolution: §f%sx%s§7.[nl] Maximum color depth: §f%s§7.[nl] Operations/tick: §f%s§7.
oc:tooltip.InternetCard=This card allows making HTTP requests and using real TCP sockets.
oc:tooltip.Interweb=Congratulations, you win one (1) interweb. You can connect to it using an Internet Card. Beware: don't feed the trolls.
@ -209,6 +212,7 @@ oc:tooltip.UpgradeCrafting=Enables robots to use the top left area of their inve
oc:tooltip.UpgradeExperience=This upgrade allows robots to accumulate experience by performing various operations. The more experience they have, the more energy they can store, the faster they can harvest blocks and the more efficiently they can use tools.
oc:tooltip.UpgradeGenerator=Can be used to generate energy from fuel on the go. Burns items to generate energy over time, based on their fuel value.[nl] §fEfficiency§7: §a%s%%§7
oc:tooltip.UpgradeInventory=This upgrade provides inventory space to the robot. Without one of these, robots will not be able to store items internally.
oc:tooltip.UpgradeInventoryController=This upgrade allows the robot more control in how it interacts with external inventories, and allows it to swap its equipped tool with an item in its inventory.
oc:tooltip.UpgradeNavigation=Can be used to determine the position and orientation of the robot. The position is relative to the center of the map that was used to craft this upgrade.
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.

View File

@ -142,6 +142,11 @@ inventoryUpgrade {
[dispenser, chest, craftingPiston]
[plankWood, "oc:circuitChip1", plankWood]]
}
inventoryControllerUpgrade {
input: [[ingotGold, "oc:analyzer", ingotGold]
[dispenser, "oc:circuitChip2", craftingPiston]
[ingotGold, "oc:materialCircuitBoardPrinted", ingotGold]]
}
navigationUpgrade {
input: [[ingotGold, compass, ingotGold]
["oc:circuitChip3", {item=map, subID=any}, "oc:circuitChip3"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

View File

@ -212,5 +212,7 @@ object Items extends ItemAPI {
stack
}
}, "openOS")
Recipes.addItem(new item.UpgradeInventoryController(multi), "inventoryControllerUpgrade", "oc:inventoryControllerUpgrade")
}
}

View File

@ -76,6 +76,7 @@ class Proxy {
api.Driver.add(driver.item.UpgradeExperience)
api.Driver.add(driver.item.UpgradeGenerator)
api.Driver.add(driver.item.UpgradeInventory)
api.Driver.add(driver.item.UpgradeInventoryController)
api.Driver.add(driver.item.UpgradeNavigation)
api.Driver.add(driver.item.UpgradeSign)
api.Driver.add(driver.item.UpgradeSolarGenerator)

View File

@ -0,0 +1,25 @@
package li.cil.oc.common.item
import java.util
import li.cil.oc.{Settings, server}
import li.cil.oc.util.{Tooltip, Rarity}
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.client.renderer.texture.IconRegister
class UpgradeInventoryController(val parent: Delegator) extends Delegate {
val unlocalizedName = "UpgradeInventoryController"
override def rarity = Rarity.byTier(server.driver.item.UpgradeInventoryController.tier(createItemStack()))
override def tooltipLines(stack: ItemStack, player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) {
tooltip.addAll(Tooltip.get(unlocalizedName))
super.tooltipLines(stack, player, tooltip, advanced)
}
override def registerIcons(iconRegister: IconRegister) = {
super.registerIcons(iconRegister)
icon = iconRegister.registerIcon(Settings.resourceDomain + ":upgrade_inventory_controller")
}
}

View File

@ -162,7 +162,7 @@ class Disassembler extends traits.Environment with traits.Inventory {
private def drop(stack: ItemStack) {
if (stack != null) {
for (side <- ForgeDirection.VALID_DIRECTIONS if stack.stackSize > 0) {
InventoryUtils.tryDropIntoInventoryAt(stack, world, x + side.offsetX, y + side.offsetY, z + side.offsetZ, side.getOpposite)
InventoryUtils.insertIntoInventoryAt(stack, world, x + side.offsetX, y + side.offsetY, z + side.offsetZ, side.getOpposite)
}
if (stack.stackSize > 0) {
spawnStackInWorld(stack, ForgeDirection.UP)

View File

@ -0,0 +1,94 @@
package li.cil.oc.server.component
import li.cil.oc.Settings
import li.cil.oc.api.machine.Robot
import li.cil.oc.api.Network
import li.cil.oc.api.network._
import li.cil.oc.common.component.ManagedComponent
import li.cil.oc.util.InventoryUtils
import li.cil.oc.util.ExtendedArguments._
import net.minecraft.tileentity.TileEntity
class UpgradeInventoryController(val owner: TileEntity with Robot) extends ManagedComponent {
val node = Network.newNode(this, Visibility.Network).
withComponent("inventory_controller", Visibility.Neighbors).
withConnector().
create()
// ----------------------------------------------------------------------- //
@Callback(doc = """function():number -- Get the number of slots in the inventory on the specified side of the robot.""")
def getInventorySize(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
InventoryUtils.inventoryAt(owner.getWorldObj, owner.xCoord + facing.offsetX, owner.yCoord + facing.offsetY, owner.zCoord + facing.offsetZ) match {
case Some(inventory) => result(inventory.getSizeInventory)
case _ => result(Unit, "no inventory")
}
}
@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] = {
val facing = checkSideForAction(args, 0)
val count = args.optionalItemCount(2)
val selectedSlot = owner.selectedSlot
val stack = owner.getStackInSlot(selectedSlot)
if (stack != null && stack.stackSize > 0) {
InventoryUtils.inventoryAt(owner.getWorldObj, owner.xCoord + facing.offsetX, owner.yCoord + facing.offsetY, owner.zCoord + facing.offsetZ) match {
case Some(inventory) =>
val slot = args.checkSlot(inventory, 1)
if (!InventoryUtils.insertIntoInventorySlot(stack, inventory, facing.getOpposite, slot, count)) {
// Cannot drop into that inventory.
return result(false, "inventory full")
}
else if (stack.stackSize == 0) {
// Dropped whole stack.
owner.setInventorySlotContents(selectedSlot, null)
}
else {
// Dropped partial stack.
owner.onInventoryChanged()
}
case _ => return result(false, "no inventory")
}
context.pause(Settings.get.dropDelay)
result(true)
}
else result(false)
}
@Callback(doc = """function(facing:number, slot:number[, count:number]):boolean -- Sucks items from the specified slot of an inventory.""")
def suckFromSlot(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
val count = args.optionalItemCount(2)
InventoryUtils.inventoryAt(owner.getWorldObj, owner.xCoord + facing.offsetX, owner.yCoord + facing.offsetY, owner.zCoord + facing.offsetZ) match {
case Some(inventory) =>
val slot = args.checkSlot(inventory, 1)
if (InventoryUtils.extractFromInventorySlot(owner.player.inventory.addItemStackToInventory, inventory, facing.getOpposite, slot, count)) {
context.pause(Settings.get.suckDelay)
result(true)
}
else result(false)
case _ => result(false, "no inventory")
}
}
@Callback(doc = """function():boolean -- Swaps the equipped tool with the content of the currently selected inventory slot.""")
def equip(context: Context, args: Arguments): Array[AnyRef] = {
if (owner.inventorySize > 0) {
val selectedSlot = owner.selectedSlot
val equipped = owner.getStackInSlot(0)
val selected = owner.getStackInSlot(selectedSlot)
owner.setInventorySlotContents(0, selected)
owner.setInventorySlotContents(selectedSlot, equipped)
result(true)
}
else result(false)
}
private def checkSlot(args: Arguments, n: Int) = args.checkSlot(owner, n)
private def checkSideForAction(args: Arguments, n: Int) = owner.toGlobal(args.checkSideForAction(n))
}

View File

@ -115,7 +115,7 @@ class Inventory(player: Player) extends InventoryPlayer(player) {
override def readFromNBT(nbt: NBTTagList) {}
override def getSizeInventory = robot.getSizeInventory
override def getSizeInventory = 1 + robot.containerCount + robot.inventorySize
override def getStackInSlot(slot: Int) = robot.getStackInSlot(slot)

View File

@ -6,12 +6,10 @@ import li.cil.oc.common.tileentity
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedNBT._
import net.minecraft.block.{BlockFluid, Block}
import net.minecraft.entity.item.{EntityMinecart, EntityMinecartContainer, EntityItem}
import net.minecraft.entity.item.{EntityMinecart, EntityItem}
import net.minecraft.entity.{EntityLivingBase, Entity}
import net.minecraft.inventory.{IInventory, ISidedInventory}
import net.minecraft.item.{ItemStack, ItemBlock}
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.tileentity.TileEntityChest
import net.minecraft.util.{MovingObjectPosition, EnumMovingObjectType}
import net.minecraftforge.common.{MinecraftForge, ForgeDirection}
import net.minecraftforge.event.world.BlockEvent
@ -20,6 +18,7 @@ import scala.collection.convert.WrapAsScala._
import li.cil.oc.common.component.ManagedComponent
import li.cil.oc.api.event.RobotPlaceInAirEvent
import li.cil.oc.util.InventoryUtils
import li.cil.oc.util.ExtendedArguments._
class Robot(val robot: tileentity.Robot) extends ManagedComponent {
val node = api.Network.newNode(this, Visibility.Neighbors).
@ -57,6 +56,9 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
// ----------------------------------------------------------------------- //
@Callback
def getInventorySize(context: Context, args: Arguments): Array[AnyRef] = result(robot.inventorySize)
@Callback
def select(context: Context, args: Arguments): Array[AnyRef] = {
if (args.count > 0 && args.checkAny(0) != null) {
@ -104,7 +106,7 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
@Callback
def transferTo(context: Context, args: Arguments): Array[AnyRef] = {
val slot = checkSlot(args, 0)
val count = checkOptionalItemCount(args, 1)
val count = args.optionalItemCount(1)
if (slot == selectedSlot || count == 0) {
result(true)
}
@ -158,16 +160,28 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
@Callback
def drop(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
val count = checkOptionalItemCount(args, 1)
val stack = robot.decrStackSize(selectedSlot, count)
val count = args.optionalItemCount(1)
val stack = robot.getStackInSlot(selectedSlot)
if (stack != null && stack.stackSize > 0) {
val player = robot.player(facing)
if (!InventoryUtils.tryDropIntoInventoryAt(stack, world, x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ, facing.getOpposite)) {
// No inventory to drop into, drop into the world.
player.dropPlayerItemWithRandomChoice(stack, inPlace = false)
InventoryUtils.inventoryAt(world, x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) match {
case Some(inventory) =>
if (!InventoryUtils.insertIntoInventory(stack, inventory, facing.getOpposite, count)) {
// Cannot drop into that inventory.
return result(false, "inventory full")
}
else if (stack.stackSize == 0) {
// Dropped whole stack.
robot.setInventorySlotContents(selectedSlot, null)
}
else {
// Dropped partial stack.
robot.onInventoryChanged()
}
case _ =>
// No inventory to drop into, drop into the world.
player.dropPlayerItemWithRandomChoice(robot.decrStackSize(selectedSlot, count), inPlace = false)
}
// Put back whatever remained of the stack.
player.inventory.addItemStackToInventory(stack)
context.pause(Settings.get.dropDelay)
@ -219,45 +233,13 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
@Callback
def suck(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
val count = checkOptionalItemCount(args, 1)
val count = args.optionalItemCount(1)
def trySuckFromInventory(inventory: IInventory, filter: (Int) => Boolean) = {
var success = false
for (slot <- 0 until inventory.getSizeInventory if !success && filter(slot)) {
val stack = inventory.getStackInSlot(slot)
if (stack != null) {
val maxStackSize = math.min(robot.getInventoryStackLimit, stack.getMaxStackSize)
val amount = math.min(maxStackSize, math.min(stack.stackSize, count))
val sucked = stack.splitStack(amount)
success = player.inventory.addItemStackToInventory(sucked)
stack.stackSize += sucked.stackSize
if (stack.stackSize == 0) {
inventory.setInventorySlotContents(slot, null)
}
}
}
if (success) {
inventory.onInventoryChanged()
}
success
}
world.getBlockTileEntity(x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) match {
case chest: TileEntityChest if chest.isUseableByPlayer(player) =>
val inventory = Block.chest.getInventory(world, chest.xCoord, chest.yCoord, chest.zCoord)
result(trySuckFromInventory(inventory, slot => true))
case inventory: ISidedInventory if inventory.isUseableByPlayer(player) =>
result(trySuckFromInventory(inventory,
slot => inventory.canExtractItem(slot, inventory.getStackInSlot(slot), facing.getOpposite.ordinal())))
case inventory: IInventory if inventory.isUseableByPlayer(player) =>
result(trySuckFromInventory(inventory, slot => true))
case _ =>
val player = robot.player(facing)
for (entity <- player.entitiesOnSide[EntityMinecartContainer](facing) if entity.isUseableByPlayer(player)) {
if (trySuckFromInventory(entity, slot => true)) {
return result(true)
}
if (InventoryUtils.extractFromInventoryAt(player.inventory.addItemStackToInventory, world, x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ, facing.getOpposite, count)) {
context.pause(Settings.get.suckDelay)
result(true)
}
else {
for (entity <- player.entitiesOnSide[EntityItem](facing) if !entity.isDead && entity.delayBeforeCanPickup <= 0) {
val stack = entity.getEntityItem
val size = stack.stackSize
@ -457,7 +439,7 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
@Callback
def move(context: Context, args: Arguments): Array[AnyRef] = {
val direction = checkSideForMovement(args, 0)
val direction = robot.toGlobal(args.checkSideForMovement(0))
if (robot.isAnimatingMove) {
// This shouldn't really happen due to delays being enforced, but just to
// be on the safe side...
@ -624,33 +606,9 @@ class Robot(val robot: tileentity.Robot) extends ManagedComponent {
// ----------------------------------------------------------------------- //
private def checkOptionalItemCount(args: Arguments, n: Int) =
if (args.count > n && args.checkAny(n) != null) {
math.max(args.checkInteger(n), math.min(0, robot.getInventoryStackLimit))
}
else robot.getInventoryStackLimit
private def checkSlot(args: Arguments, n: Int) = args.checkSlot(robot, n)
private def checkSlot(args: Arguments, n: Int) = {
val slot = args.checkInteger(n) - 1
if (slot < 0 || slot > 15) {
throw new IllegalArgumentException("invalid slot")
}
actualSlot(slot)
}
private def checkSideForAction(args: Arguments, n: Int) = robot.toGlobal(args.checkSideForAction(n))
private def checkSideForAction(args: Arguments, n: Int) = checkSide(args, n, ForgeDirection.SOUTH, ForgeDirection.UP, ForgeDirection.DOWN)
private def checkSideForMovement(args: Arguments, n: Int) = checkSide(args, n, ForgeDirection.SOUTH, ForgeDirection.NORTH, ForgeDirection.UP, ForgeDirection.DOWN)
private def checkSideForFace(args: Arguments, n: Int, facing: ForgeDirection) = checkSide(args, n, ForgeDirection.VALID_DIRECTIONS.filter(_ != robot.toLocal(facing).getOpposite): _*)
private def checkSide(args: Arguments, n: Int, allowed: ForgeDirection*) = {
val side = args.checkInteger(n)
if (side < 0 || side > 5) {
throw new IllegalArgumentException("invalid side")
}
val direction = ForgeDirection.getOrientation(side)
if (allowed.isEmpty || (allowed contains direction)) robot.toGlobal(direction)
else throw new IllegalArgumentException("unsupported side")
}
private def checkSideForFace(args: Arguments, n: Int, facing: ForgeDirection) = robot.toGlobal(args.checkSideForFace(n, robot.toLocal(facing)))
}

View File

@ -0,0 +1,21 @@
package li.cil.oc.server.driver.item
import li.cil.oc.api
import li.cil.oc.api.driver.Slot
import li.cil.oc.server.component
import net.minecraft.item.ItemStack
import net.minecraft.tileentity.TileEntity
import li.cil.oc.api.machine.Robot
object UpgradeInventoryController extends Item {
override def worksWith(stack: ItemStack) = isOneOf(stack, api.Items.get("inventoryControllerUpgrade"))
override def createEnvironment(stack: ItemStack, container: component.Container) = container.tileEntity match {
case Some(robot: TileEntity with Robot) => new component.UpgradeInventoryController(robot)
case _ => null
}
override def slot(stack: ItemStack) = Slot.Upgrade
override def tier(stack: ItemStack) = 1
}

View File

@ -0,0 +1,52 @@
package li.cil.oc.util
import li.cil.oc.api.machine.Robot
import li.cil.oc.api.network.Arguments
import net.minecraftforge.common.ForgeDirection
import net.minecraft.inventory.IInventory
object ExtendedArguments {
implicit def extendedArguments(args: Arguments) = new ExtendedArguments(args)
class ExtendedArguments(val args: Arguments) {
def optionalItemCount(n: Int) =
if (args.count > n && args.checkAny(n) != null) {
math.max(0, math.min(64, args.checkInteger(n)))
}
else 64
def checkSlot(inventory: IInventory, n: Int) = {
val slot = args.checkInteger(n) - 1
if (slot < 0 || slot >= inventory.getSizeInventory) {
throw new IllegalArgumentException("invalid slot")
}
slot
}
def checkSlot(robot: Robot, n: Int) = {
val slot = args.checkInteger(n) - 1
if (slot < 0 || slot >= robot.inventorySize) {
throw new IllegalArgumentException("invalid slot")
}
slot + 1 + robot.containerCount
}
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)
def checkSideForFace(n: Int, facing: ForgeDirection) = checkSide(n, ForgeDirection.VALID_DIRECTIONS.filter(_ != facing.getOpposite): _*)
private def checkSide(n: Int, allowed: ForgeDirection*) = {
val side = args.checkInteger(n)
if (side < 0 || side > 5) {
throw new IllegalArgumentException("invalid side")
}
val direction = ForgeDirection.getOrientation(side)
if (allowed.isEmpty || (allowed contains direction)) direction
else throw new IllegalArgumentException("unsupported side")
}
}
}

View File

@ -11,60 +11,188 @@ import scala.collection.convert.WrapAsScala._
import net.minecraftforge.common.ForgeDirection
object InventoryUtils {
def tryDropIntoInventoryAt(stack: ItemStack, world: World, x: Int, y: Int, z: Int, side: ForgeDirection): Boolean = {
/**
* Retrieves an actual inventory implementation for a specified world coordinate.
* <p/>
* This performs special handling for (double-)chests and also checks for
* mine carts with chests.
*/
def inventoryAt(world: World, x: Int, y: Int, z: Int) = {
world.getBlockTileEntity(x, y, z) match {
case chest: TileEntityChest =>
val inventory = Block.chest.getInventory(world, chest.xCoord, chest.yCoord, chest.zCoord)
tryDropIntoInventory(stack, inventory, side)
case inventory: ISidedInventory =>
tryDropIntoInventory(stack, inventory, side)
case inventory: IInventory =>
tryDropIntoInventory(stack, inventory, side)
case _ =>
val mineCarts = world.getEntitiesWithinAABB(classOf[EntityMinecartContainer],
case chest: TileEntityChest => Option(Block.chest.getInventory(world, chest.xCoord, chest.yCoord, chest.zCoord))
case inventory: IInventory => Some(inventory)
case _ => world.getEntitiesWithinAABB(classOf[EntityMinecartContainer],
AxisAlignedBB.getBoundingBox(x, y, z, x + 1, y + 1, z + 1)).
map(_.asInstanceOf[EntityMinecartContainer])
for (inventory <- mineCarts if !inventory.isDead) {
if (tryDropIntoInventory(stack, inventory, side)) {
return true
}
}
false
map(_.asInstanceOf[EntityMinecartContainer]).
find(!_.isDead)
}
}
def tryDropIntoInventory(stack: ItemStack, inventory: IInventory, side: ForgeDirection) = {
def isSideValidForSlot: (Int) => Boolean = inventory match {
case inventory: ISidedInventory => (slot) => inventory.canInsertItem(slot, stack, side.ordinal)
case _ => (slot) => true
/**
* Inserts a stack into an inventory.
* <p/>
* Only tries to insert into the specified slot. This <em>cannot</em> be
* used to empty a slot. It can only insert stacks into empty slots and
* merge additional items into an existing stack in the slot.
* <p/>
* The passed stack's size will be adjusted to reflect the number of items
* inserted into the inventory, i.e. if 10 more items could fit into the
* slot, the stack's size will be 10 smaller than before the call.
* <p/>
* This will return <tt>true</tt> if <em>at least</em> one item could be
* inserted into the slot. It will return <tt>false</tt> if the passed
* stack did not change.
* <p/>
* This takes care of handling special cases such as sided inventories,
* maximum inventory and item stack sizes.
* <p/>
* The number of items inserted can be limited, to avoid unnecessary
* changes to the inventory the stack may come from, for example.
*/
def insertIntoInventorySlot(stack: ItemStack, inventory: IInventory, side: ForgeDirection, slot: Int, limit: Int = 64) =
(stack != null && limit > 0) && {
val isSideValidForSlot = inventory match {
case inventory: ISidedInventory => inventory.canInsertItem(slot, stack, side.ordinal)
case _ => true
}
var success = false
(stack.stackSize > 0 && inventory.isItemValidForSlot(slot, stack) && isSideValidForSlot) && {
val maxStackSize = math.min(inventory.getInventoryStackLimit, stack.getMaxStackSize)
val shouldTryMerge = !stack.isItemStackDamageable && stack.getMaxStackSize > 1 && inventory.getInventoryStackLimit > 1
if (shouldTryMerge) {
for (slot <- 0 until inventory.getSizeInventory if stack.stackSize > 0 && inventory.isItemValidForSlot(slot, stack) && isSideValidForSlot(slot)) {
val existing = inventory.getStackInSlot(slot)
val shouldMerge = existing != null && existing.stackSize < maxStackSize &&
existing.isItemEqual(stack) && ItemStack.areItemStackTagsEqual(existing, stack)
if (shouldMerge) {
val space = maxStackSize - existing.stackSize
val amount = math.min(space, stack.stackSize)
assert(amount > 0)
success = true
val amount = math.min(space, math.min(stack.stackSize, limit))
existing.stackSize += amount
stack.stackSize -= amount
inventory.onInventoryChanged()
true
}
else (existing == null) && {
val amount = math.min(maxStackSize, math.min(stack.stackSize, limit))
inventory.setInventorySlotContents(slot, stack.splitStack(amount))
true
}
}
}
for (slot <- 0 until inventory.getSizeInventory if stack.stackSize > 0 && inventory.getStackInSlot(slot) == null && inventory.isItemValidForSlot(slot, stack) && isSideValidForSlot(slot)) {
val amount = math.min(maxStackSize, stack.stackSize)
inventory.setInventorySlotContents(slot, stack.splitStack(amount))
success = true
/**
* Extracts a stack from an inventory.
* <p/>
* Only tries to extract from the specified slot. This <em>can</em> be used
* to empty a slot. It will extract items using the specified consumer method
* which is called with the extracted stack before the stack in the inventory
* that we extract from is cleared from. This allows placing back excess
* items with as few inventory updates as possible.
* <p/>
* The consumer is the only way to retrieve the actually extracted stack. It
* is called with a separate stack instance, so it does not have to be copied
* again.
* <p/>
* This will return <tt>true</tt> if <em>at least</em> one item could be
* extracted from the slot. It will return <tt>false</tt> if the stack in
* the slot did not change.
* <p/>
* This takes care of handling special cases such as sided inventories and
* maximum stack sizes.
* <p/>
* The number of items extracted can be limited, to avoid unnecessary
* changes to the inventory the stack is extracted from. Note that this could
* also be achieved by a check in the consumer, but it saves some unnecessary
* code repetition this way.
*/
def extractFromInventorySlot(consumer: (ItemStack) => Unit, inventory: IInventory, side: ForgeDirection, slot: Int, limit: Int = 64) = {
val stack = inventory.getStackInSlot(slot)
(stack != null && limit > 0) && {
val isSideValidForSlot = inventory match {
case inventory: ISidedInventory => inventory.canExtractItem(slot, stack, side.ordinal)
case _ => true
}
if (success) {
(stack.stackSize > 0 && isSideValidForSlot) && {
val maxStackSize = math.min(inventory.getInventoryStackLimit, stack.getMaxStackSize)
val amount = math.min(maxStackSize, math.min(stack.stackSize, limit))
val extracted = stack.splitStack(amount)
consumer(extracted)
val success = extracted.stackSize < amount
stack.stackSize += extracted.stackSize
if (stack.stackSize == 0) {
inventory.setInventorySlotContents(slot, null)
}
else if (success) {
inventory.onInventoryChanged()
}
success
}
}
}
/**
* Inserts a stack into an inventory.
* <p/>
* This will try to fit the stack in any and as many as necessary slots in
* the inventory. It will first try to merge the stack in stacks already
* present in the inventory. After that it will try to fit the stack into
* empty slots in the inventory.
* <p/>
* This uses the <tt>insertIntoInventorySlot</tt> method, 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 inserted. The passed
* item stack will be adjusted to reflect the number items inserted, by
* having its size decremented accordingly.
*/
def insertIntoInventory(stack: ItemStack, inventory: IInventory, side: ForgeDirection, limit: Int = 64) =
(stack != null && limit > 0) && {
var success = false
var remaining = limit
val shouldTryMerge = !stack.isItemStackDamageable && stack.getMaxStackSize > 1 && inventory.getInventoryStackLimit > 1
if (shouldTryMerge) {
for (slot <- 0 until inventory.getSizeInventory) {
val stackSize = stack.stackSize
if ((inventory.getStackInSlot(slot) != null) && insertIntoInventorySlot(stack, inventory, side, slot, remaining)) {
remaining -= stackSize - stack.stackSize
success = true
}
}
}
for (slot <- 0 until inventory.getSizeInventory) {
val stackSize = stack.stackSize
if ((inventory.getStackInSlot(slot) == null) && insertIntoInventorySlot(stack, inventory, side, slot, remaining)) {
remaining -= stackSize - stack.stackSize
success = true
}
}
success
}
/**
* Extracts a slot from an inventory.
* </p>
* This will try to extract a stack from any inventory slot. It will iterate
* all slots until an item can be extracted from a slot.
* <p/>
* This uses the <tt>extractFromInventorySlot</tt> method, 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 extracted.
*/
def extractFromInventory(consumer: (ItemStack) => Unit, inventory: IInventory, side: ForgeDirection, limit: Int = 64) =
(0 until inventory.getSizeInventory).exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit))
/**
* Utility method for calling <tt>insertIntoInventory</tt> on an inventory
* in the world.
*/
def insertIntoInventoryAt(stack: ItemStack, world: World, x: Int, y: Int, z: Int, side: ForgeDirection, limit: Int = 64): Boolean =
inventoryAt(world, x, y, z).exists(insertIntoInventory(stack, _, side, limit))
/**
* Utility method for calling <tt>extractFromInventory</tt> on an inventory
* in the world.
*/
def extractFromInventoryAt(consumer: (ItemStack) => Unit, world: World, x: Int, y: Int, z: Int, side: ForgeDirection, limit: Int = 64) =
inventoryAt(world, x, y, z).exists(extractFromInventory(consumer, _, side, limit))
}

View File

@ -5,11 +5,11 @@ import li.cil.oc.Settings
import net.minecraft.client.Minecraft
import net.minecraft.util.StatCollector
import scala.collection.convert.WrapAsJava._
import scala.collection.mutable
import scala.collection.convert.WrapAsScala._
import org.lwjgl.input.Keyboard
object Tooltip {
val maxWidth = 200
val maxWidth = 220
def get(name: String, args: Any*): java.util.List[String] = {
val tooltip = StatCollector.translateToLocal(Settings.namespace + "tooltip." + name).format(args.map(_.toString): _*)
@ -22,50 +22,11 @@ object Tooltip {
}
else {
val nl = """\[nl\]"""
val lines = mutable.ArrayBuffer.empty[String]
tooltip.split(nl).foreach(line => {
val formatted = line.trim.stripLineEnd
var position = 0
var start = 0
var lineEnd = 0
var width = 0
var lineWidth = 0
val iterator = formatted.iterator
while (iterator.hasNext) {
val c = iterator.next()
if (c == '§') {
iterator.next()
position += 2
}
else {
if (c == ' ') {
lineEnd = position
lineWidth = width
}
else {
width += font.getCharWidth(c)
}
position += 1
if (width > maxWidth) {
if (lineEnd > start) {
lines += formatted.substring(start, lineEnd)
start = lineEnd + 1
width -= lineWidth
lineWidth = 0
}
else {
lines += formatted.substring(start, position)
start = position
width = 0
}
}
}
}
if (start < formatted.length) {
lines += formatted.substring(start)
}
})
lines
tooltip.
split(nl).
map(font.listFormattedStringToWidth(_, maxWidth).map(_.asInstanceOf[String].trim() + " ")).
flatten.
toList
}
}
}