diff --git a/assets/items.psd b/assets/items.psd index 5266573c4..084bc15bf 100644 Binary files a/assets/items.psd and b/assets/items.psd differ diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/index.md b/src/main/resources/assets/opencomputers/doc/en_US/item/index.md index 8462ff8d7..5531d50d8 100644 --- a/src/main/resources/assets/opencomputers/doc/en_US/item/index.md +++ b/src/main/resources/assets/opencomputers/doc/en_US/item/index.md @@ -50,6 +50,7 @@ Keep in mind that some of these may not be available, depending on the recipe se * [Tank Controller](tankControllerUpgrade.md) * [Tank Upgrade](tankUpgrade.md) * [Tractor Beam Upgrade](tractorBeamUpgrade.md) +* [Trading Upgrade](tradingUpgrade.md) * [Upgrade Container](upgradeContainer1.md) ### Other diff --git a/src/main/resources/assets/opencomputers/doc/en_US/item/tradingUpgrade.md b/src/main/resources/assets/opencomputers/doc/en_US/item/tradingUpgrade.md new file mode 100644 index 000000000..8a599707c --- /dev/null +++ b/src/main/resources/assets/opencomputers/doc/en_US/item/tradingUpgrade.md @@ -0,0 +1,5 @@ +# Trading Upgrade + + + +The trading upgrade enables automatic trading with merchants, such as villagers. It can be used in agents such as [robots](../block/robot.md) and [drones](drone.md) and provides capabilities to identify available trades from nearby merchants, retrieve information on individual trade offers, and finally for performing trades. diff --git a/src/main/resources/assets/opencomputers/recipes/default.recipes b/src/main/resources/assets/opencomputers/recipes/default.recipes index 660b269bd..883e28a75 100644 --- a/src/main/resources/assets/opencomputers/recipes/default.recipes +++ b/src/main/resources/assets/opencomputers/recipes/default.recipes @@ -335,6 +335,11 @@ tractorBeamUpgrade { [ingotIron, "oc:capacitor", ingotIron] [ingotGold, "oc:circuitChip3", ingotGold]] } +tradingUpgrade { + input: [[ingotGold, chest, ingotGold] + [emerald, "oc:circuitChip2", emerald] + [dropper, "oc:materialCircuitBoardPrinted", craftingPiston]] +} cardContainer1 { input: [[ingotIron, "oc:circuitChip1", ingotIron] @@ -367,12 +372,6 @@ upgradeContainer3 { [ingotGold, "oc:materialCircuitBoardPrinted", ingotGold]] } -upgradeTrading { - input: [[ingotGold, chest, ingotGold] - [emerald, "oc:circuitChip2", emerald] - [dropper, "oc:materialCircuitBoardPrinted", craftingPiston]] -} - # Note: iron ingot and nugget recipes are *only* registered if no other mod # already provides the same functionality. nuggetIron { diff --git a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes index 3623657f0..b56831948 100644 --- a/src/main/resources/assets/opencomputers/recipes/hardmode.recipes +++ b/src/main/resources/assets/opencomputers/recipes/hardmode.recipes @@ -181,8 +181,7 @@ solarGeneratorUpgrade { input: [[blockGlass, blockGlass, blockGlass] ["oc:circuitChip3", "oc:generatorUpgrade", "oc:circuitChip3"]] } - -upgradeTrading { +tradingUpgrade { input: [["oc:circuitChip2", chest, "oc:circuitChip2"] [emerald, "oc:circuitChip2", emerald] [dropper, "oc:materialCircuitBoardPrinted", craftingPiston]] diff --git a/src/main/resources/assets/opencomputers/textures/items/UpgradeTrading.png b/src/main/resources/assets/opencomputers/textures/items/UpgradeTrading.png index 9eae15eae..4a8db07df 100644 Binary files a/src/main/resources/assets/opencomputers/textures/items/UpgradeTrading.png and b/src/main/resources/assets/opencomputers/textures/items/UpgradeTrading.png differ 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) /**