mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-08-04 03:27:19 -04:00
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.
This commit is contained in:
parent
7af4b41b1e
commit
58de9015c3
BIN
assets/items.psd
BIN
assets/items.psd
Binary file not shown.
@ -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 Controller](tankControllerUpgrade.md)
|
||||||
* [Tank Upgrade](tankUpgrade.md)
|
* [Tank Upgrade](tankUpgrade.md)
|
||||||
* [Tractor Beam Upgrade](tractorBeamUpgrade.md)
|
* [Tractor Beam Upgrade](tractorBeamUpgrade.md)
|
||||||
|
* [Trading Upgrade](tradingUpgrade.md)
|
||||||
* [Upgrade Container](upgradeContainer1.md)
|
* [Upgrade Container](upgradeContainer1.md)
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
@ -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.
|
@ -335,6 +335,11 @@ tractorBeamUpgrade {
|
|||||||
[ingotIron, "oc:capacitor", ingotIron]
|
[ingotIron, "oc:capacitor", ingotIron]
|
||||||
[ingotGold, "oc:circuitChip3", ingotGold]]
|
[ingotGold, "oc:circuitChip3", ingotGold]]
|
||||||
}
|
}
|
||||||
|
tradingUpgrade {
|
||||||
|
input: [[ingotGold, chest, ingotGold]
|
||||||
|
[emerald, "oc:circuitChip2", emerald]
|
||||||
|
[dropper, "oc:materialCircuitBoardPrinted", craftingPiston]]
|
||||||
|
}
|
||||||
|
|
||||||
cardContainer1 {
|
cardContainer1 {
|
||||||
input: [[ingotIron, "oc:circuitChip1", ingotIron]
|
input: [[ingotIron, "oc:circuitChip1", ingotIron]
|
||||||
@ -367,12 +372,6 @@ upgradeContainer3 {
|
|||||||
[ingotGold, "oc:materialCircuitBoardPrinted", ingotGold]]
|
[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
|
# Note: iron ingot and nugget recipes are *only* registered if no other mod
|
||||||
# already provides the same functionality.
|
# already provides the same functionality.
|
||||||
nuggetIron {
|
nuggetIron {
|
||||||
|
@ -181,8 +181,7 @@ solarGeneratorUpgrade {
|
|||||||
input: [[blockGlass, blockGlass, blockGlass]
|
input: [[blockGlass, blockGlass, blockGlass]
|
||||||
["oc:circuitChip3", "oc:generatorUpgrade", "oc:circuitChip3"]]
|
["oc:circuitChip3", "oc:generatorUpgrade", "oc:circuitChip3"]]
|
||||||
}
|
}
|
||||||
|
tradingUpgrade {
|
||||||
upgradeTrading {
|
|
||||||
input: [["oc:circuitChip2", chest, "oc:circuitChip2"]
|
input: [["oc:circuitChip2", chest, "oc:circuitChip2"]
|
||||||
[emerald, "oc:circuitChip2", emerald]
|
[emerald, "oc:circuitChip2", emerald]
|
||||||
[dropper, "oc:materialCircuitBoardPrinted", craftingPiston]]
|
[dropper, "oc:materialCircuitBoardPrinted", craftingPiston]]
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 739 B |
@ -154,7 +154,7 @@ object Constants {
|
|||||||
final val TerminalServer = "terminalServer"
|
final val TerminalServer = "terminalServer"
|
||||||
final val TexturePicker = "texturePicker"
|
final val TexturePicker = "texturePicker"
|
||||||
final val TractorBeamUpgrade = "tractorBeamUpgrade"
|
final val TractorBeamUpgrade = "tractorBeamUpgrade"
|
||||||
final val TradingUpgrade = "upgradeTrading"
|
final val TradingUpgrade = "tradingUpgrade"
|
||||||
final val Transistor = "transistor"
|
final val Transistor = "transistor"
|
||||||
final val UpgradeContainerTier1 = "upgradeContainer1"
|
final val UpgradeContainerTier1 = "upgradeContainer1"
|
||||||
final val UpgradeContainerTier2 = "upgradeContainer2"
|
final val UpgradeContainerTier2 = "upgradeContainer2"
|
||||||
|
@ -532,8 +532,7 @@ object Items extends ItemAPI {
|
|||||||
// 1.6.0
|
// 1.6.0
|
||||||
Recipes.addSubItem(new item.TerminalServer(multi), Constants.ItemName.TerminalServer, "oc:terminalServer")
|
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.DiskDriveMountable(multi), Constants.ItemName.DiskDriveMountable, "oc:diskDriveMountable")
|
||||||
|
Recipes.addSubItem(new item.UpgradeTrading(multi), Constants.ItemName.TradingUpgrade, "oc:tradingUpgrade")
|
||||||
Recipes.addSubItem(new item.UpgradeTrading(multi), Constants.ItemName.TradingUpgrade, "oc:upgradeTrading")
|
|
||||||
|
|
||||||
// Register aliases.
|
// Register aliases.
|
||||||
for ((k, v) <- aliases) {
|
for ((k, v) <- aliases) {
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
package li.cil.oc.integration.opencomputers
|
package li.cil.oc.integration.opencomputers
|
||||||
|
|
||||||
import li.cil.oc.api
|
|
||||||
import li.cil.oc.Constants
|
import li.cil.oc.Constants
|
||||||
|
import li.cil.oc.api
|
||||||
import li.cil.oc.api.driver.EnvironmentProvider
|
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.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 li.cil.oc.server.component.UpgradeTrading
|
||||||
import net.minecraft.item.ItemStack
|
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 {
|
object DriverUpgradeTrading extends Item with HostAware {
|
||||||
override def worksWith(stack: ItemStack) = isOneOf(stack,
|
override def worksWith(stack: ItemStack) = isOneOf(stack,
|
||||||
@ -18,11 +17,7 @@ object DriverUpgradeTrading extends Item with HostAware {
|
|||||||
|
|
||||||
override def createEnvironment(stack: ItemStack, host: EnvironmentHost) =
|
override def createEnvironment(stack: ItemStack, host: EnvironmentHost) =
|
||||||
if (host.world.isRemote) null
|
if (host.world.isRemote) null
|
||||||
else host match {
|
else new UpgradeTrading(host)
|
||||||
case host: EnvironmentHost with Robot => new UpgradeTrading(host)
|
|
||||||
case host: EnvironmentHost with Drone => new UpgradeTrading(host)
|
|
||||||
case _ => null
|
|
||||||
}
|
|
||||||
|
|
||||||
override def slot(stack: ItemStack) = Slot.Upgrade
|
override def slot(stack: ItemStack) = Slot.Upgrade
|
||||||
|
|
||||||
@ -34,4 +29,5 @@ object DriverUpgradeTrading extends Item with HostAware {
|
|||||||
classOf[component.UpgradeTrading]
|
classOf[component.UpgradeTrading]
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,253 +4,192 @@ import java.util.UUID
|
|||||||
|
|
||||||
import li.cil.oc.Settings
|
import li.cil.oc.Settings
|
||||||
import li.cil.oc.api.machine._
|
import li.cil.oc.api.machine._
|
||||||
|
import li.cil.oc.api.network.EnvironmentHost
|
||||||
import li.cil.oc.api.prefab.AbstractValue
|
import li.cil.oc.api.prefab.AbstractValue
|
||||||
import li.cil.oc.common.EventHandler
|
import li.cil.oc.common.EventHandler
|
||||||
import li.cil.oc.util.InventoryUtils
|
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.item.ItemStack
|
||||||
import net.minecraft.nbt.NBTTagCompound
|
import net.minecraft.nbt.NBTTagCompound
|
||||||
|
import net.minecraft.tileentity.TileEntity
|
||||||
import net.minecraft.village.MerchantRecipe
|
import net.minecraft.village.MerchantRecipe
|
||||||
import net.minecraftforge.common.DimensionManager
|
import net.minecraftforge.common.DimensionManager
|
||||||
import net.minecraftforge.common.util.ForgeDirection
|
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() = 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) = {
|
def maxRange = Settings.get.tradingRange
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override def save(nbt: NBTTagCompound) = {
|
def isInRange = (info.merchant.get, info.host) match {
|
||||||
//Tell info to save to nbt
|
case (Some(merchant: Entity), Some(host)) => merchant.getDistanceSq(host.xPosition, host.yPosition, host.zPosition) < maxRange * maxRange
|
||||||
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
|
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTileEntity(dimID: Int, posX: Int, posY: Int, posZ: Int) = Option(DimensionManager.getProvider(dimID).worldObj.getTileEntity(posX, posY, posZ) match {
|
// Queue the load because when load is called we can't access the world yet
|
||||||
case robot : li.cil.oc.common.tileentity.Robot => robot
|
// and we need to access it to get the Robot's TileEntity / Drone's Entity.
|
||||||
case robotProxy : li.cil.oc.common.tileentity.RobotProxy => robotProxy.robot
|
override def load(nbt: NBTTagCompound) = EventHandler.scheduleServer(() => info.load(nbt))
|
||||||
case null => None
|
|
||||||
})
|
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 = {
|
def load(nbt: NBTTagCompound): Unit = {
|
||||||
val dimID = nbt.getInteger("dimensionID")
|
val isEntity = nbt.getBoolean("hostIsEntity")
|
||||||
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.
|
||||||
//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)
|
||||||
host = Option(_hostIsDrone match {
|
merchant = new WeakReference[IMerchant](loadEntity(nbt, new UUID(nbt.getLong("merchantUUIDMost"), nbt.getLong("merchantUUIDLeast"))) match {
|
||||||
case true => getEntityByUUID(dimID, UUID.fromString(nbt.getString("hostUUID"))).orNull.asInstanceOf[EnvironmentHost]
|
case Some(merchant: IMerchant) => merchant
|
||||||
case false => getTileEntity(
|
case _ => null
|
||||||
dimID,
|
|
||||||
nbt.getInteger("hostX"),
|
|
||||||
nbt.getInteger("hostY"),
|
|
||||||
nbt.getInteger("hostZ")
|
|
||||||
).orNull.asInstanceOf[EnvironmentHost]
|
|
||||||
})
|
})
|
||||||
_recipeID = nbt.getInteger("recipeID")
|
recipeID = nbt.getInteger("recipeID")
|
||||||
_vilRef = new WeakReference[EntityVillager](getEntityByUUID(dimID, UUID.fromString(nbt.getString("villagerUUID"))).orNull.asInstanceOf[EntityVillager])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def save(nbt: NBTTagCompound): Unit = {
|
def save(nbt: NBTTagCompound): Unit = {
|
||||||
host match {
|
host match {
|
||||||
case Some(h) =>
|
case Some(entity: Entity) =>
|
||||||
nbt.setInteger("dimensionID", h.world.provider.dimensionId)
|
nbt.setBoolean("hostIsEntity", true)
|
||||||
hostIsDrone match {
|
nbt.setInteger("dimensionID", entity.world.provider.dimensionId)
|
||||||
case true => nbt.setString("hostUUID", h.asInstanceOf[li.cil.oc.common.entity.Drone].getPersistentID.toString)
|
nbt.setLong("hostUUIDLeast", entity.getPersistentID.getLeastSignificantBits)
|
||||||
case false =>
|
nbt.setLong("hostUUIDMost", entity.getPersistentID.getMostSignificantBits)
|
||||||
nbt.setInteger("hostX", h.xPosition.floor.toInt)
|
case Some(tileEntity: TileEntity) =>
|
||||||
nbt.setInteger("hostY", h.yPosition.floor.toInt)
|
nbt.setBoolean("hostIsEntity", false)
|
||||||
nbt.setInteger("hostZ", h.zPosition.floor.toInt)
|
nbt.setInteger("dimensionID", tileEntity.getWorldObj.provider.dimensionId)
|
||||||
}
|
nbt.setInteger("hostX", tileEntity.xCoord)
|
||||||
case None =>
|
nbt.setInteger("hostY", tileEntity.yCoord)
|
||||||
|
nbt.setInteger("hostZ", tileEntity.zCoord)
|
||||||
|
case _ => // Welp!
|
||||||
}
|
}
|
||||||
villager match {
|
merchant.get match {
|
||||||
case Some(v) => nbt.setString("villagerUUID", v.getPersistentID.toString)
|
case Some(entity: Entity) =>
|
||||||
case None =>
|
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 {
|
private def loadEntity(nbt: NBTTagCompound, uuid: UUID): Option[Entity] = {
|
||||||
case h : li.cil.oc.common.tileentity.Robot => false
|
val dimension = nbt.getInteger("dimensionID")
|
||||||
case h : li.cil.oc.common.entity.Drone => true
|
val world = DimensionManager.getProvider(dimension).worldObj
|
||||||
case _ => false
|
|
||||||
|
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 loadHostEntity(nbt: NBTTagCompound): Option[EnvironmentHost] = {
|
||||||
private def host_= (value: EnvironmentHost): Unit = _host = Option(value)
|
loadEntity(nbt, new UUID(nbt.getLong("hostUUIDMost"), nbt.getLong("hostUUIDLeast"))) match {
|
||||||
private def host_= (value: Option[EnvironmentHost]): Unit = _host = value
|
case Some(entity: Entity with li.cil.oc.api.internal.Agent) => Option(entity: EnvironmentHost)
|
||||||
|
case _ => None
|
||||||
def villager = _vilRef.get
|
}
|
||||||
def recipeID = _recipeID
|
}
|
||||||
def recipe : MerchantRecipe = villager.orNull.getRecipes(null).get(recipeID).asInstanceOf[MerchantRecipe]
|
|
||||||
|
private def loadHostTileEntity(nbt: NBTTagCompound): Option[EnvironmentHost] = {
|
||||||
def inventory = host match {
|
val dimension = nbt.getInteger("dimensionID")
|
||||||
case Some(h) => hostIsDrone match {
|
val world = DimensionManager.getProvider(dimension).worldObj
|
||||||
case true => Some(h.asInstanceOf[li.cil.oc.common.entity.Drone].mainInventory)
|
|
||||||
case false => Some(h.asInstanceOf[li.cil.oc.common.tileentity.Robot].mainInventory)
|
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
|
||||||
}
|
}
|
||||||
case None => None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,16 +1,19 @@
|
|||||||
package li.cil.oc.server.component
|
package li.cil.oc.server.component
|
||||||
|
|
||||||
import li.cil.oc.Settings
|
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.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.network.Visibility
|
||||||
|
import li.cil.oc.api.prefab
|
||||||
import li.cil.oc.util.BlockPosition
|
import li.cil.oc.util.BlockPosition
|
||||||
import li.cil.oc.api.machine.{Callback, Arguments, Context}
|
import net.minecraft.entity.Entity
|
||||||
import net.minecraft.entity.passive.EntityVillager
|
import net.minecraft.entity.IMerchant
|
||||||
import net.minecraft.util.Vec3
|
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 {
|
class UpgradeTrading(val host: EnvironmentHost) extends prefab.ManagedEnvironment with traits.WorldAware {
|
||||||
override val node = Network.newNode(this, Visibility.Network).
|
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)
|
override def position = BlockPosition(host)
|
||||||
|
|
||||||
var maxRange = Settings.get.tradingRange
|
def maxRange = Settings.get.tradingRange
|
||||||
|
|
||||||
def inRange(vil: EntityVillager): Boolean = {
|
def isInRange(entity: Entity) = Vec3.createVectorHelper(entity.posX, entity.posY, entity.posZ).distanceTo(position.toVec3) <= maxRange
|
||||||
Vec3.createVectorHelper(vil.posX, vil.posY, vil.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] = {
|
def getTrades(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
|
result(entitiesInBounds[Entity](position.bounds.expand(maxRange, maxRange, maxRange)).
|
||||||
val boundsLow = position.bounds.offset(-maxRange, -maxRange, -maxRange)
|
filter(isInRange).
|
||||||
val boundsHigh = position.bounds.offset(maxRange, maxRange, maxRange)
|
collect { case merchant: IMerchant => merchant }.
|
||||||
val bounds = boundsLow.func_111270_a(boundsHigh)
|
flatMap(merchant => merchant.getRecipes(null).indices.map(new Trade(this, merchant, _))))
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ trait InventoryWorldControl extends InventoryAware with WorldAware with SideRest
|
|||||||
|
|
||||||
val blockPos = position.offset(facing)
|
val blockPos = position.offset(facing)
|
||||||
if (InventoryUtils.inventoryAt(blockPos).exists(inventory => {
|
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)
|
context.pause(Settings.get.suckDelay)
|
||||||
result(true)
|
result(true)
|
||||||
|
@ -21,7 +21,7 @@ trait ItemInventoryControl extends InventoryAware {
|
|||||||
def dropIntoItemInventory(context: Context, args: Arguments): Array[AnyRef] = {
|
def dropIntoItemInventory(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
withItemInventory(args.checkSlot(inventory, 0), itemInventory => {
|
withItemInventory(args.checkSlot(inventory, 0), itemInventory => {
|
||||||
val count = args.optItemCount(1)
|
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] = {
|
def suckFromItemInventory(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
withItemInventory(args.checkSlot(inventory, 0), itemInventory => {
|
withItemInventory(args.checkSlot(inventory, 0), itemInventory => {
|
||||||
val count = args.optItemCount(1)
|
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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ object InventoryUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a slot from an inventory.
|
* Extracts a slot from an inventory.
|
||||||
* </p>
|
* <p/>
|
||||||
* This will try to extract a stack from any inventory slot. It will iterate
|
* 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.
|
* all slots until an item can be extracted from a slot.
|
||||||
* <p/>
|
* <p/>
|
||||||
@ -218,7 +218,7 @@ object InventoryUtils {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This returns <tt>true</tt> if at least one item was extracted.
|
* This returns <tt>true</tt> 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 {
|
val range = inventory match {
|
||||||
case sided: ISidedInventory => sided.getAccessibleSlotsFromSide(side.ordinal).toIterable
|
case sided: ISidedInventory => sided.getAccessibleSlotsFromSide(side.ordinal).toIterable
|
||||||
case _ => 0 until inventory.getSizeInventory
|
case _ => 0 until inventory.getSizeInventory
|
||||||
@ -226,6 +226,35 @@ object InventoryUtils {
|
|||||||
range.exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit))
|
range.exists(slot => extractFromInventorySlot(consumer, inventory, side, slot, limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts an item stack from an inventory.
|
||||||
|
* <p/>
|
||||||
|
* 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.
|
||||||
|
* <p/>
|
||||||
|
* This uses the <tt>extractFromInventorySlot</tt> 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 <tt>insertIntoInventory</tt> on an inventory
|
* Utility method for calling <tt>insertIntoInventory</tt> on an inventory
|
||||||
* in the world.
|
* in the world.
|
||||||
@ -238,7 +267,7 @@ object InventoryUtils {
|
|||||||
* in the world.
|
* in the world.
|
||||||
*/
|
*/
|
||||||
def extractFromInventoryAt(consumer: (ItemStack) => Unit, position: BlockPosition, side: ForgeDirection, limit: Int = 64) =
|
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.
|
* Transfers some items between two inventories.
|
||||||
@ -254,7 +283,7 @@ object InventoryUtils {
|
|||||||
* This returns <tt>true</tt> if at least one item was transferred.
|
* This returns <tt>true</tt> if at least one item was transferred.
|
||||||
*/
|
*/
|
||||||
def transferBetweenInventories(source: IInventory, sourceSide: ForgeDirection, sink: IInventory, sinkSide: Option[ForgeDirection], limit: Int = 64) =
|
def transferBetweenInventories(source: IInventory, sourceSide: ForgeDirection, sink: IInventory, sinkSide: Option[ForgeDirection], limit: Int = 64) =
|
||||||
extractFromInventory(
|
extractAnyFromInventory(
|
||||||
insertIntoInventory(_, sink, sinkSide, limit), source, sourceSide, limit)
|
insertIntoInventory(_, sink, sinkSide, limit), source, sourceSide, limit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user