From 58de9015c3e6fe1792b0adeb15c32f2df58ab4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 4 Jan 2016 18:41:40 +0100 Subject: [PATCH] Refactored a few things out of the trading upgrade into InventoryUtils, making things a little more consistent, added manual entry for trade upgrade, tweaked the texture for the trade upgrade a bit. --- assets/items.psd | Bin 619441 -> 621113 bytes .../opencomputers/doc/en_US/item/index.md | 1 + .../doc/en_US/item/tradingUpgrade.md | 5 + .../opencomputers/recipes/default.recipes | 11 +- .../opencomputers/recipes/hardmode.recipes | 3 +- .../textures/items/UpgradeTrading.png | Bin 18312 -> 739 bytes src/main/scala/li/cil/oc/Constants.scala | 2 +- .../scala/li/cil/oc/common/init/Items.scala | 3 +- .../opencomputers/DriverUpgradeTrading.scala | 18 +- .../li/cil/oc/server/component/Trade.scala | 375 ++++++++---------- .../oc/server/component/UpgradeTrading.scala | 41 +- .../traits/InventoryWorldControl.scala | 2 +- .../traits/ItemInventoryControl.scala | 4 +- .../scala/li/cil/oc/util/InventoryUtils.scala | 37 +- 14 files changed, 229 insertions(+), 273 deletions(-) create mode 100644 src/main/resources/assets/opencomputers/doc/en_US/item/tradingUpgrade.md diff --git a/assets/items.psd b/assets/items.psd index 5266573c48c1b0783d8f674b028eadbbf0cedcf6..084bc15bfee2cfe3bbd53457960b02afb8e0a802 100644 GIT binary patch delta 9770 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 CBo>DN delta 11264 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 literal 18312 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 diff --git a/src/main/scala/li/cil/oc/Constants.scala b/src/main/scala/li/cil/oc/Constants.scala index 8d096f31f..2af38dff2 100644 --- a/src/main/scala/li/cil/oc/Constants.scala +++ b/src/main/scala/li/cil/oc/Constants.scala @@ -154,7 +154,7 @@ object Constants { final val TerminalServer = "terminalServer" final val TexturePicker = "texturePicker" final val TractorBeamUpgrade = "tractorBeamUpgrade" - final val TradingUpgrade = "upgradeTrading" + final val TradingUpgrade = "tradingUpgrade" final val Transistor = "transistor" final val UpgradeContainerTier1 = "upgradeContainer1" final val UpgradeContainerTier2 = "upgradeContainer2" diff --git a/src/main/scala/li/cil/oc/common/init/Items.scala b/src/main/scala/li/cil/oc/common/init/Items.scala index 35bfe2c3a..c7ff6ceac 100644 --- a/src/main/scala/li/cil/oc/common/init/Items.scala +++ b/src/main/scala/li/cil/oc/common/init/Items.scala @@ -532,8 +532,7 @@ object Items extends ItemAPI { // 1.6.0 Recipes.addSubItem(new item.TerminalServer(multi), Constants.ItemName.TerminalServer, "oc:terminalServer") Recipes.addSubItem(new item.DiskDriveMountable(multi), Constants.ItemName.DiskDriveMountable, "oc:diskDriveMountable") - - Recipes.addSubItem(new item.UpgradeTrading(multi), Constants.ItemName.TradingUpgrade, "oc:upgradeTrading") + Recipes.addSubItem(new item.UpgradeTrading(multi), Constants.ItemName.TradingUpgrade, "oc:tradingUpgrade") // Register aliases. for ((k, v) <- aliases) { diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeTrading.scala b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeTrading.scala index 9809f5b60..db66594eb 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeTrading.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/DriverUpgradeTrading.scala @@ -1,16 +1,15 @@ package li.cil.oc.integration.opencomputers -import li.cil.oc.api import li.cil.oc.Constants +import li.cil.oc.api import li.cil.oc.api.driver.EnvironmentProvider -import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.driver.item.HostAware -import li.cil.oc.common.{Tier, Slot} +import li.cil.oc.api.network.EnvironmentHost +import li.cil.oc.common.Slot +import li.cil.oc.common.Tier +import li.cil.oc.server.component import li.cil.oc.server.component.UpgradeTrading import net.minecraft.item.ItemStack -import li.cil.oc.common.entity.Drone -import li.cil.oc.common.tileentity.Robot -import li.cil.oc.server.component object DriverUpgradeTrading extends Item with HostAware { override def worksWith(stack: ItemStack) = isOneOf(stack, @@ -18,11 +17,7 @@ object DriverUpgradeTrading extends Item with HostAware { override def createEnvironment(stack: ItemStack, host: EnvironmentHost) = if (host.world.isRemote) null - else host match { - case host: EnvironmentHost with Robot => new UpgradeTrading(host) - case host: EnvironmentHost with Drone => new UpgradeTrading(host) - case _ => null - } + else new UpgradeTrading(host) override def slot(stack: ItemStack) = Slot.Upgrade @@ -34,4 +29,5 @@ object DriverUpgradeTrading extends Item with HostAware { classOf[component.UpgradeTrading] else null } + } diff --git a/src/main/scala/li/cil/oc/server/component/Trade.scala b/src/main/scala/li/cil/oc/server/component/Trade.scala index 917de78f3..8dfc38876 100644 --- a/src/main/scala/li/cil/oc/server/component/Trade.scala +++ b/src/main/scala/li/cil/oc/server/component/Trade.scala @@ -4,253 +4,192 @@ import java.util.UUID import li.cil.oc.Settings import li.cil.oc.api.machine._ +import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.prefab.AbstractValue import li.cil.oc.common.EventHandler import li.cil.oc.util.InventoryUtils -import net.minecraft.entity.passive.EntityVillager +import net.minecraft.entity.Entity +import net.minecraft.entity.IMerchant import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound +import net.minecraft.tileentity.TileEntity import net.minecraft.village.MerchantRecipe import net.minecraftforge.common.DimensionManager import net.minecraftforge.common.util.ForgeDirection -import ref.WeakReference -import li.cil.oc.api.network.EnvironmentHost -import scala.collection.JavaConverters._ -class Trade(info: TradeInfo) extends AbstractValue { +import scala.collection.convert.WrapAsScala._ +import scala.ref.WeakReference +class Trade(val info: TradeInfo) extends AbstractValue { def this() = this(new TradeInfo()) - def this(upgr: UpgradeTrading, villager: EntityVillager, recipeID: Int) = { - this(new TradeInfo(upgr.host, villager, recipeID)) - } + def this(upgrade: UpgradeTrading, merchant: IMerchant, recipeID: Int) = + this(new TradeInfo(upgrade.host, merchant, recipeID)) - override def load(nbt: NBTTagCompound) = { - EventHandler.scheduleServer(() => { - //Tell the info to load from NBT, behind EventHandler because when load is called we can't access the world yet - //and we need to access it to get the Robot/Drone TileEntity/Entity - info.load(nbt) - }) - } + def maxRange = Settings.get.tradingRange - override def save(nbt: NBTTagCompound) = { - //Tell info to save to nbt - info.save(nbt) - } - - def inventory = info.inventory - - @Callback(doc="function():table, table -- returns the items the villager wants for this trade") - def getInput(context: Context, arguments: Arguments): Array[AnyRef] = { - Array(info.recipe.getItemToBuy.copy(), - info.recipe.hasSecondItemToBuy match { - case true => info.recipe.getSecondItemToBuy.copy() - case false => null - }) - } - - @Callback(doc = "function():table -- returns the item the villager offers for this trade") - def getOutput(context: Context, arguments: Arguments): Array[AnyRef] = { - Array(info.recipe.getItemToSell.copy()) - } - - @Callback(doc="function():boolean -- returns whether the villager currently wants to trade this") - def isEnabled(context: Context, arguments: Arguments): Array[AnyRef] = { - Array(info.villager.exists((villager: EntityVillager) => // Make sure villager is neither dead/gone nor the recipe - !info.recipe.isRecipeDisabled // has been disabled - ).asInstanceOf[AnyRef]) - } - - val maxRange = Settings.get.tradingRange - def inRange = info.villager.isDefined && distance.exists((distance: Double) => distance < maxRange) - def distance = info.villager match { - case Some(villager: EntityVillager) => - info.host match { - case Some(h: EnvironmentHost) => Some(Math.sqrt(Math.pow(villager.posX - h.xPosition, 2) + Math.pow(villager.posY - h.yPosition, 2) + Math.pow(villager.posZ - h.zPosition, 2))) - case _ => None - } - case None => None - } - - @Callback(doc="function():boolean, string -- returns true when trade succeeds and nil, error when not") - def trade(context: Context, arguments: Arguments): Array[AnyRef] = { - //Make sure we can access an inventory - val inventory = info.inventory match { - case Some(i) => i - case None => return result(false, "trading requires an inventory upgrade to be installed") - } - - //Make sure villager hasn't died, it somehow gone or moved out of range - if (info.villager.isEmpty) - return result(false, "trade has become invalid") - else if (!info.villager.get.isEntityAlive) - return result(false, "trader died") - if (!inRange) { - return result(false, "out of range") - } - - //Make sure villager wants to trade this - if (info.recipe.isRecipeDisabled) - return result(false, "recipe is disabled") - - //Now we'll check if we have enough items to perform the trade, caching first - val firstItem = info.recipe.getItemToBuy - val secondItem = info.recipe.hasSecondItemToBuy match { - case true => Some(info.recipe.getSecondItemToBuy) - case false => None - } - - //Check if we have enough of the first item - var extracting: Int = firstItem.stackSize - for (slot <- 0 until inventory.getSizeInventory) { - val stack = inventory.getStackInSlot(slot) - if (stack != null && stack.isItemEqual(firstItem) && extracting > 0) - //Takes the stack in the slot, extracts up to limit and calls the function in the first argument - //We don't actually consume anything, we just count that we have extracted as much as we need - InventoryUtils.extractFromInventorySlot((stack: ItemStack) => extracting -= stack.stackSize, inventory, ForgeDirection.UNKNOWN, slot, extracting) - } - //If we had enough, the left-over amount will be 0 - if (extracting != 0) - return result(false, "not enough items to trade") - - //Do the same with the second item if there is one - if (secondItem.isDefined) { - extracting = secondItem.orNull.stackSize - for (slot <- 0 until inventory.getSizeInventory) { - val stack = inventory.getStackInSlot(slot) - if (stack != null && stack.isItemEqual(secondItem.orNull) && extracting > 0) - InventoryUtils.extractFromInventorySlot((stack: ItemStack) => extracting -= stack.stackSize, inventory, ForgeDirection.UNKNOWN, slot, extracting) - } - if (extracting != 0) - return result(false, "not enough items to trade") - } - - //Now we need to check if we have enough inventory space to accept the item we get for the trade - val outputItemSim = info.recipe.getItemToSell.copy() - InventoryUtils.insertIntoInventory(outputItemSim, inventory, None, 64, simulate = true) - if (outputItemSim.stackSize != 0) - return result(false, "not enough inventory space to trade") - - //We established that out inventory allows to perform the trade, now actaully do the trade - extracting = firstItem.stackSize - for (slot <- 0 until inventory.getSizeInventory) { - if (extracting != 0) { - val stack = inventory.getStackInSlot(slot) - if (stack != null && stack.isItemEqual(firstItem)) - //Pretty much the same as earlier (but counting down, and not up now) - //but this time we actually consume the stack we get - InventoryUtils.extractFromInventorySlot((stack: ItemStack) => { - extracting -= stack.stackSize - stack.stackSize = 0 - }, inventory, ForgeDirection.UNKNOWN, slot, extracting) - } - } - - //Do the same for the second item - if (secondItem.isDefined) { - extracting = secondItem.orNull.stackSize - for (slot <- 0 until inventory.getSizeInventory) { - if (extracting != 0) { - val stack = inventory.getStackInSlot(slot) - if (stack != null && stack.isItemEqual(secondItem.orNull)) - InventoryUtils.extractFromInventorySlot((stack: ItemStack) => { - extracting -= stack.stackSize - stack.stackSize = 0 - }, inventory, ForgeDirection.UNKNOWN, slot, extracting) - } - } - } - - //Now put our output item into the inventory - val outputItem = info.recipe.getItemToSell.copy() - while (outputItem.stackSize != 0) - InventoryUtils.insertIntoInventory(outputItem, inventory, None, outputItem.stackSize) - - //Tell the villager we used the recipe, so MC can disable it and/or enable more recipes - info.villager.orNull.useRecipe(info.recipe) - result(true) - } -} - -class TradeInfo() { - def this(host: EnvironmentHost, villager: EntityVillager, recipeID: Int) = { - this() - _vilRef = new WeakReference[EntityVillager](villager) - _recipeID = recipeID - this.host = host - } - - def getEntityByUUID(dimID: Int, uuid: UUID) = DimensionManager.getProvider(dimID).worldObj.getLoadedEntityList.asScala.find { - case entAny: net.minecraft.entity.Entity if entAny.getPersistentID == uuid => true + def isInRange = (info.merchant.get, info.host) match { + case (Some(merchant: Entity), Some(host)) => merchant.getDistanceSq(host.xPosition, host.yPosition, host.zPosition) < maxRange * maxRange case _ => false } - def getTileEntity(dimID: Int, posX: Int, posY: Int, posZ: Int) = Option(DimensionManager.getProvider(dimID).worldObj.getTileEntity(posX, posY, posZ) match { - case robot : li.cil.oc.common.tileentity.Robot => robot - case robotProxy : li.cil.oc.common.tileentity.RobotProxy => robotProxy.robot - case null => None - }) + // Queue the load because when load is called we can't access the world yet + // and we need to access it to get the Robot's TileEntity / Drone's Entity. + override def load(nbt: NBTTagCompound) = EventHandler.scheduleServer(() => info.load(nbt)) + + override def save(nbt: NBTTagCompound) = info.save(nbt) + + @Callback(doc = "function():table, table -- Returns the items the merchant wants for this trade.") + def getInput(context: Context, arguments: Arguments): Array[AnyRef] = + result(info.recipe.map(_.getItemToBuy.copy()).orNull, + if (info.recipe.exists(_.hasSecondItemToBuy)) info.recipe.map(_.getSecondItemToBuy.copy()).orNull else null) + + @Callback(doc = "function():table -- Returns the item the merchant offers for this trade.") + def getOutput(context: Context, arguments: Arguments): Array[AnyRef] = + result(info.recipe.map(_.getItemToSell.copy()).orNull) + + @Callback(doc = "function():boolean -- Returns whether the merchant currently wants to trade this.") + def isEnabled(context: Context, arguments: Arguments): Array[AnyRef] = + result(info.merchant.get.exists(merchant => !info.recipe.exists(_.isRecipeDisabled))) // Make sure merchant is neither dead/gone nor the recipe has been disabled. + + @Callback(doc = "function():boolean, string -- Returns true when trade succeeds and nil, error when not.") + def trade(context: Context, arguments: Arguments): Array[AnyRef] = { + // Make sure we can access an inventory. + info.inventory match { + case Some(inventory) => + // Make sure merchant hasn't died, it somehow gone or moved out of range and still wants to trade this. + info.merchant.get match { + case Some(merchant: Entity) if merchant.isEntityAlive && isInRange => + if (!merchant.isEntityAlive) { + result(false, "trader died") + } else if (!isInRange) { + result(false, "out of range") + } else { + info.recipe match { + case Some(recipe) => + if (recipe.isRecipeDisabled) { + result(false, "trade is disabled") + } else { + // Now we'll check if we have enough items to perform the trade, caching first + val firstInputStack = recipe.getItemToBuy + val secondInputStack = if (recipe.hasSecondItemToBuy) Option(recipe.getSecondItemToBuy) else None + + def containsAccumulativeItemStack(stack: ItemStack) = + InventoryUtils.extractFromInventory(stack, inventory, ForgeDirection.UNKNOWN, simulate = true).stackSize == 0 + def hasRoomForItemStack(stack: ItemStack) = { + val remainder = stack.copy() + InventoryUtils.insertIntoInventory(remainder, inventory, None, remainder.stackSize, simulate = true) + remainder.stackSize == 0 + } + + // Check if we have enough to perform the trade. + if (containsAccumulativeItemStack(firstInputStack) && secondInputStack.forall(containsAccumulativeItemStack)) { + // Now we need to check if we have enough inventory space to accept the item we get for the trade. + val outputStack = recipe.getItemToSell.copy() + if (hasRoomForItemStack(outputStack)) { + // We established that out inventory allows to perform the trade, now actually do the trade. + InventoryUtils.extractFromInventory(firstInputStack, inventory, ForgeDirection.UNKNOWN) + secondInputStack.map(InventoryUtils.extractFromInventory(_, inventory, ForgeDirection.UNKNOWN)) + InventoryUtils.insertIntoInventory(outputStack, inventory, None, outputStack.stackSize) + + // Tell the merchant we used the recipe, so MC can disable it and/or enable more recipes. + info.merchant.get.orNull.useRecipe(recipe) + + result(true) + } else { + result(false, "not enough inventory space to trade") + } + } else { + result(false, "not enough items to trade") + } + } + case _ => result(false, "trade has become invalid") + } + } + case _ => result(false, "trade has become invalid") + } + case _ => result(false, "trading requires an inventory upgrade to be installed") + } + } +} + +class TradeInfo(var host: Option[EnvironmentHost], var merchant: WeakReference[IMerchant], var recipeID: Int) { + def this() = this(None, new WeakReference[IMerchant](null), -1) + + def this(host: EnvironmentHost, merchant: IMerchant, recipeID: Int) = + this(Option(host), new WeakReference[IMerchant](merchant), recipeID) + + def recipe = merchant.get.map(_.getRecipes(null).get(recipeID).asInstanceOf[MerchantRecipe]) + + def inventory = host match { + case Some(agent: li.cil.oc.api.internal.Agent) => Option(agent.mainInventory()) + case _ => None + } def load(nbt: NBTTagCompound): Unit = { - val dimID = nbt.getInteger("dimensionID") - val _hostIsDrone = nbt.getBoolean("hostIsDrone") - //If drone we find it again by its UUID, if Robot we know the X/Y/Z of the TileEntity - host = Option(_hostIsDrone match { - case true => getEntityByUUID(dimID, UUID.fromString(nbt.getString("hostUUID"))).orNull.asInstanceOf[EnvironmentHost] - case false => getTileEntity( - dimID, - nbt.getInteger("hostX"), - nbt.getInteger("hostY"), - nbt.getInteger("hostZ") - ).orNull.asInstanceOf[EnvironmentHost] + val isEntity = nbt.getBoolean("hostIsEntity") + // If drone we find it again by its UUID, if Robot we know the X/Y/Z of the TileEntity. + host = if (isEntity) loadHostEntity(nbt) else loadHostTileEntity(nbt) + merchant = new WeakReference[IMerchant](loadEntity(nbt, new UUID(nbt.getLong("merchantUUIDMost"), nbt.getLong("merchantUUIDLeast"))) match { + case Some(merchant: IMerchant) => merchant + case _ => null }) - _recipeID = nbt.getInteger("recipeID") - _vilRef = new WeakReference[EntityVillager](getEntityByUUID(dimID, UUID.fromString(nbt.getString("villagerUUID"))).orNull.asInstanceOf[EntityVillager]) + recipeID = nbt.getInteger("recipeID") } def save(nbt: NBTTagCompound): Unit = { host match { - case Some(h) => - nbt.setInteger("dimensionID", h.world.provider.dimensionId) - hostIsDrone match { - case true => nbt.setString("hostUUID", h.asInstanceOf[li.cil.oc.common.entity.Drone].getPersistentID.toString) - case false => - nbt.setInteger("hostX", h.xPosition.floor.toInt) - nbt.setInteger("hostY", h.yPosition.floor.toInt) - nbt.setInteger("hostZ", h.zPosition.floor.toInt) - } - case None => + case Some(entity: Entity) => + nbt.setBoolean("hostIsEntity", true) + nbt.setInteger("dimensionID", entity.world.provider.dimensionId) + nbt.setLong("hostUUIDLeast", entity.getPersistentID.getLeastSignificantBits) + nbt.setLong("hostUUIDMost", entity.getPersistentID.getMostSignificantBits) + case Some(tileEntity: TileEntity) => + nbt.setBoolean("hostIsEntity", false) + nbt.setInteger("dimensionID", tileEntity.getWorldObj.provider.dimensionId) + nbt.setInteger("hostX", tileEntity.xCoord) + nbt.setInteger("hostY", tileEntity.yCoord) + nbt.setInteger("hostZ", tileEntity.zCoord) + case _ => // Welp! } - villager match { - case Some(v) => nbt.setString("villagerUUID", v.getPersistentID.toString) - case None => + merchant.get match { + case Some(entity: Entity) => + nbt.setLong("merchantUUIDLeast", entity.getPersistentID.getLeastSignificantBits) + nbt.setLong("merchantUUIDMost", entity.getPersistentID.getMostSignificantBits) + case _ => } - nbt.setBoolean("hostIsDrone", hostIsDrone) - nbt.setInteger("recipeID", _recipeID) + nbt.setInteger("recipeID", recipeID) } - def hostIsDrone = host.orNull match { - case h : li.cil.oc.common.tileentity.Robot => false - case h : li.cil.oc.common.entity.Drone => true - case _ => false + private def loadEntity(nbt: NBTTagCompound, uuid: UUID): Option[Entity] = { + val dimension = nbt.getInteger("dimensionID") + val world = DimensionManager.getProvider(dimension).worldObj + + world.loadedEntityList.find { + case entity: Entity if entity.getPersistentID == uuid => true + case _ => false + }.map(_.asInstanceOf[Entity]) } - private var _host: Option[EnvironmentHost] = None - private var _recipeID: Int = _ - private var _vilRef: WeakReference[EntityVillager] = new WeakReference[EntityVillager](null) - def host = _host - private def host_= (value: EnvironmentHost): Unit = _host = Option(value) - private def host_= (value: Option[EnvironmentHost]): Unit = _host = value - - def villager = _vilRef.get - def recipeID = _recipeID - def recipe : MerchantRecipe = villager.orNull.getRecipes(null).get(recipeID).asInstanceOf[MerchantRecipe] - - def inventory = host match { - case Some(h) => hostIsDrone match { - case true => Some(h.asInstanceOf[li.cil.oc.common.entity.Drone].mainInventory) - case false => Some(h.asInstanceOf[li.cil.oc.common.tileentity.Robot].mainInventory) + private def loadHostEntity(nbt: NBTTagCompound): Option[EnvironmentHost] = { + loadEntity(nbt, new UUID(nbt.getLong("hostUUIDMost"), nbt.getLong("hostUUIDLeast"))) match { + case Some(entity: Entity with li.cil.oc.api.internal.Agent) => Option(entity: EnvironmentHost) + case _ => None } - case None => None } -} \ No newline at end of file + + private def loadHostTileEntity(nbt: NBTTagCompound): Option[EnvironmentHost] = { + val dimension = nbt.getInteger("dimensionID") + val world = DimensionManager.getProvider(dimension).worldObj + + val x = nbt.getInteger("hostX") + val y = nbt.getInteger("hostY") + val z = nbt.getInteger("hostZ") + + world.getTileEntity(x, y, z) match { + case robotProxy: li.cil.oc.common.tileentity.RobotProxy => Option(robotProxy.robot) + case agent: li.cil.oc.api.internal.Agent => Option(agent) + case null => None + } + } +} diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeTrading.scala b/src/main/scala/li/cil/oc/server/component/UpgradeTrading.scala index 3aaadecb4..589204214 100644 --- a/src/main/scala/li/cil/oc/server/component/UpgradeTrading.scala +++ b/src/main/scala/li/cil/oc/server/component/UpgradeTrading.scala @@ -1,16 +1,19 @@ package li.cil.oc.server.component import li.cil.oc.Settings -import li.cil.oc.api.network.EnvironmentHost -import li.cil.oc.api.prefab import li.cil.oc.api.Network +import li.cil.oc.api.machine.Arguments +import li.cil.oc.api.machine.Callback +import li.cil.oc.api.machine.Context +import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.network.Visibility +import li.cil.oc.api.prefab import li.cil.oc.util.BlockPosition -import li.cil.oc.api.machine.{Callback, Arguments, Context} -import net.minecraft.entity.passive.EntityVillager +import net.minecraft.entity.Entity +import net.minecraft.entity.IMerchant import net.minecraft.util.Vec3 -import scala.collection.mutable.ArrayBuffer +import scala.collection.convert.WrapAsScala._ class UpgradeTrading(val host: EnvironmentHost) extends prefab.ManagedEnvironment with traits.WorldAware { override val node = Network.newNode(this, Visibility.Network). @@ -19,29 +22,15 @@ class UpgradeTrading(val host: EnvironmentHost) extends prefab.ManagedEnvironmen override def position = BlockPosition(host) - var maxRange = Settings.get.tradingRange + def maxRange = Settings.get.tradingRange - def inRange(vil: EntityVillager): Boolean = { - Vec3.createVectorHelper(vil.posX, vil.posY, vil.posZ).distanceTo(position.toVec3) <= maxRange - } + def isInRange(entity: Entity) = Vec3.createVectorHelper(entity.posX, entity.posY, entity.posZ).distanceTo(position.toVec3) <= maxRange - @Callback(doc = "function():table -- Returns a table of trades in range as userdata objects") + @Callback(doc = "function():table -- Returns a table of trades in range as userdata objects.") def getTrades(context: Context, args: Arguments): Array[AnyRef] = { - - val boundsLow = position.bounds.offset(-maxRange, -maxRange, -maxRange) - val boundsHigh = position.bounds.offset(maxRange, maxRange, maxRange) - val bounds = boundsLow.func_111270_a(boundsHigh) - - val trades = ArrayBuffer[Trade]() - entitiesInBounds[EntityVillager](bounds).foreach((vil: EntityVillager) => { - if (inRange(vil)) { - val merchantRecipes = vil.getRecipes(null) - for (i <- 0 to merchantRecipes.size() - 1) { - val trade = new Trade(this, vil, i) - trades += trade - } - } - }) - trades.toArray[Object] + result(entitiesInBounds[Entity](position.bounds.expand(maxRange, maxRange, maxRange)). + filter(isInRange). + collect { case merchant: IMerchant => merchant }. + flatMap(merchant => merchant.getRecipes(null).indices.map(new Trade(this, merchant, _)))) } } diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala index 643979f5b..6055f8f8d 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryWorldControl.scala @@ -73,7 +73,7 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest val blockPos = position.offset(facing) if (InventoryUtils.inventoryAt(blockPos).exists(inventory => { - inventory.isUseableByPlayer(fakePlayer) && mayInteract(blockPos, facing.getOpposite) && InventoryUtils.extractFromInventory(InventoryUtils.insertIntoInventory(_, this.inventory, slots = Option(insertionSlots)), inventory, facing.getOpposite, count) + inventory.isUseableByPlayer(fakePlayer) && mayInteract(blockPos, facing.getOpposite) && InventoryUtils.extractAnyFromInventory(InventoryUtils.insertIntoInventory(_, this.inventory, slots = Option(insertionSlots)), inventory, facing.getOpposite, count) })) { context.pause(Settings.get.suckDelay) result(true) diff --git a/src/main/scala/li/cil/oc/server/component/traits/ItemInventoryControl.scala b/src/main/scala/li/cil/oc/server/component/traits/ItemInventoryControl.scala index 8d3c2c433..03846a579 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/ItemInventoryControl.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/ItemInventoryControl.scala @@ -21,7 +21,7 @@ trait ItemInventoryControl extends InventoryAware { def dropIntoItemInventory(context: Context, args: Arguments): Array[AnyRef] = { withItemInventory(args.checkSlot(inventory, 0), itemInventory => { val count = args.optItemCount(1) - result(InventoryUtils.extractFromInventory(InventoryUtils.insertIntoInventory(_, itemInventory), inventory, ForgeDirection.UNKNOWN, count)) + result(InventoryUtils.extractAnyFromInventory(InventoryUtils.insertIntoInventory(_, itemInventory), inventory, ForgeDirection.UNKNOWN, count)) }) } @@ -29,7 +29,7 @@ trait ItemInventoryControl extends InventoryAware { def suckFromItemInventory(context: Context, args: Arguments): Array[AnyRef] = { withItemInventory(args.checkSlot(inventory, 0), itemInventory => { val count = args.optItemCount(1) - result(InventoryUtils.extractFromInventory(InventoryUtils.insertIntoInventory(_, inventory, slots = Option(insertionSlots)), itemInventory, ForgeDirection.UNKNOWN, count)) + result(InventoryUtils.extractAnyFromInventory(InventoryUtils.insertIntoInventory(_, inventory, slots = Option(insertionSlots)), itemInventory, ForgeDirection.UNKNOWN, count)) }) } diff --git a/src/main/scala/li/cil/oc/util/InventoryUtils.scala b/src/main/scala/li/cil/oc/util/InventoryUtils.scala index 3a570e131..757d19aa9 100644 --- a/src/main/scala/li/cil/oc/util/InventoryUtils.scala +++ b/src/main/scala/li/cil/oc/util/InventoryUtils.scala @@ -209,7 +209,7 @@ object InventoryUtils { /** * Extracts a slot from an inventory. - *

+ *

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

@@ -218,7 +218,7 @@ object InventoryUtils { *

* This returns true if at least one item was extracted. */ - def extractFromInventory(consumer: (ItemStack) => Unit, inventory: IInventory, side: ForgeDirection, limit: Int = 64) = { + def extractAnyFromInventory(consumer: (ItemStack) => Unit, inventory: IInventory, side: ForgeDirection, limit: Int = 64) = { val range = inventory match { case sided: ISidedInventory => sided.getAccessibleSlotsFromSide(side.ordinal).toIterable case _ => 0 until inventory.getSizeInventory @@ -226,6 +226,35 @@ object InventoryUtils { range.exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit)) } + /** + * Extracts an item stack from an inventory. + *

+ * This will try to remove items of the same type as the specified item stack + * up to the number of the stack's size for all slots in the specified inventory. + *

+ * This uses the extractFromInventorySlot method, and therefore + * handles special cases such as sided inventories and stack size limits. + */ + def extractFromInventory(stack: ItemStack, inventory: IInventory, side: ForgeDirection, simulate: Boolean = false) = { + val range = inventory match { + case sided: ISidedInventory => sided.getAccessibleSlotsFromSide(side.ordinal).toIterable + case _ => 0 until inventory.getSizeInventory + } + val remaining = stack.copy() + for (slot <- range if remaining.stackSize > 0) { + extractFromInventorySlot(stack => { + if (haveSameItemType(remaining, stack)) { + val transferred = stack.stackSize min remaining.stackSize + remaining.stackSize -= transferred + if (!simulate) { + stack.stackSize -= transferred + } + } + }, inventory, side, slot, remaining.stackSize) + } + remaining + } + /** * Utility method for calling insertIntoInventory on an inventory * in the world. @@ -238,7 +267,7 @@ object InventoryUtils { * in the world. */ def extractFromInventoryAt(consumer: (ItemStack) => Unit, position: BlockPosition, side: ForgeDirection, limit: Int = 64) = - inventoryAt(position).exists(extractFromInventory(consumer, _, side, limit)) + inventoryAt(position).exists(extractAnyFromInventory(consumer, _, side, limit)) /** * Transfers some items between two inventories. @@ -254,7 +283,7 @@ object InventoryUtils { * This returns true if at least one item was transferred. */ def transferBetweenInventories(source: IInventory, sourceSide: ForgeDirection, sink: IInventory, sinkSide: Option[ForgeDirection], limit: Int = 64) = - extractFromInventory( + extractAnyFromInventory( insertIntoInventory(_, sink, sinkSide, limit), source, sourceSide, limit) /**