blob: 907beccd2068313b91064046de53ff09b37e581a [file] [log] [blame] [raw]
Florian Nücke9cd55182014-05-18 19:53:34 +02001package li.cil.oc.common.tileentity
2
3import cpw.mods.fml.relauncher.{SideOnly, Side}
Florian Nückeadb58e02014-06-10 00:02:53 +02004import org.apache.logging.log4j.Level
Florian Nücke02bdcce2014-05-19 11:25:25 +02005import li.cil.oc.{OpenComputers, api, Settings}
Florian Nücke9cd55182014-05-18 19:53:34 +02006import li.cil.oc.api.network.Visibility
Florian Nücke177edc92014-05-29 13:26:15 +02007import li.cil.oc.common.inventory.ServerInventory
Florian Nücke5f5a5532014-05-19 10:10:15 +02008import li.cil.oc.server.{PacketSender => ServerPacketSender}
Florian Nücke9cd55182014-05-18 19:53:34 +02009import li.cil.oc.util.ExtendedNBT._
10import li.cil.oc.util.{ItemUtils, InventoryUtils}
Florian Nücke177edc92014-05-29 13:26:15 +020011import net.minecraft.item.{ItemBucket, ItemStack}
Florian Nücke9cd55182014-05-18 19:53:34 +020012import net.minecraft.item.crafting.{ShapelessRecipes, ShapedRecipes, IRecipe, CraftingManager}
13import net.minecraft.nbt.NBTTagCompound
Florian Nücke177edc92014-05-29 13:26:15 +020014import net.minecraftforge.common.util.ForgeDirection
Florian Nücke9cd55182014-05-18 19:53:34 +020015import net.minecraftforge.oredict.{ShapelessOreRecipe, ShapedOreRecipe}
16import scala.collection.convert.WrapAsScala._
17import scala.collection.mutable
Florian Nücke177edc92014-05-29 13:26:15 +020018import net.minecraftforge.common.util.Constants.NBT
Florian Nücke9cd55182014-05-18 19:53:34 +020019
Florian Nücke3459ff12014-05-29 16:16:04 +020020class Disassembler extends traits.Environment with traits.PowerAcceptor with traits.Inventory {
Florian Nücke9cd55182014-05-18 19:53:34 +020021 val node = api.Network.newNode(this, Visibility.None).
Florian Nücke3459ff12014-05-29 16:16:04 +020022 withConnector(Settings.get.bufferConverter).
Florian Nücke9cd55182014-05-18 19:53:34 +020023 create()
24
Florian Nücke5f5a5532014-05-19 10:10:15 +020025 var isActive = false
Florian Nücke9cd55182014-05-18 19:53:34 +020026
27 val queue = mutable.ArrayBuffer.empty[ItemStack]
28
Florian Nücke3459ff12014-05-29 16:16:04 +020029 var totalRequiredEnergy = 0.0
30
Florian Nücke9cd55182014-05-18 19:53:34 +020031 var buffer = 0.0
Florian Nücke3459ff12014-05-29 16:16:04 +020032
33 def progress = if (queue.isEmpty) 0 else (1 - (queue.size * Settings.get.disassemblerItemCost - buffer) / totalRequiredEnergy) * 100
34
35 private def setActive(value: Boolean) = if (value != isActive) {
36 isActive = value
37 ServerPacketSender.sendDisassemblerActive(this, isActive)
38 }
39
40 // ----------------------------------------------------------------------- //
41
42 @SideOnly(Side.CLIENT)
43 override protected def hasConnector(side: ForgeDirection) = side != ForgeDirection.UP
44
45 override protected def connector(side: ForgeDirection) = Option(if (side != ForgeDirection.UP) node else null)
Florian Nücke9cd55182014-05-18 19:53:34 +020046
47 // ----------------------------------------------------------------------- //
48
49 override def canUpdate = isServer
50
51 override def updateEntity() {
52 super.updateEntity()
53 if (world.getWorldTime % Settings.get.tickFrequency == 0) {
54 if (queue.isEmpty) {
Florian Nücke3459ff12014-05-29 16:16:04 +020055 disassemble(decrStackSize(0, 1))
56 setActive(!queue.isEmpty)
Florian Nücke9cd55182014-05-18 19:53:34 +020057 }
58 else {
59 if (buffer < Settings.get.disassemblerItemCost) {
60 val want = Settings.get.disassemblerTickAmount
Florian Nücke3459ff12014-05-29 16:16:04 +020061 val success = node.tryChangeBuffer(-want)
62 setActive(success) // If energy is insufficient indicate it visually.
63 if (success) {
64 buffer += want
65 }
Florian Nücke9cd55182014-05-18 19:53:34 +020066 }
67 if (buffer >= Settings.get.disassemblerItemCost) {
68 buffer -= Settings.get.disassemblerItemCost
69 val stack = queue.remove(0)
Florian Nücke5f5a5532014-05-19 10:10:15 +020070 if (world.rand.nextDouble > Settings.get.disassemblerBreakChance) {
71 drop(stack)
Florian Nücke9cd55182014-05-18 19:53:34 +020072 }
73 }
74 }
75 }
76 }
77
78 def disassemble(stack: ItemStack) {
79 // Validate the item, never trust Minecraft / other Mods on anything!
Florian Nücke3459ff12014-05-29 16:16:04 +020080 if (stack != null && isItemValidForSlot(0, stack)) {
Florian Nücke5f5a5532014-05-19 10:10:15 +020081 if (api.Items.get(stack) == api.Items.get("robot")) enqueueRobot(stack)
82 else if (api.Items.get(stack) == api.Items.get("server1")) enqueueServer(stack, 0)
83 else if (api.Items.get(stack) == api.Items.get("server2")) enqueueServer(stack, 1)
84 else if (api.Items.get(stack) == api.Items.get("server3")) enqueueServer(stack, 2)
Florian Nücke9cd55182014-05-18 19:53:34 +020085 else if (api.Items.get(stack) == api.Items.get("navigationUpgrade")) {
Florian Nücke5f5a5532014-05-19 10:10:15 +020086 enqueueNavigationUpgrade(stack)
Florian Nücke9cd55182014-05-18 19:53:34 +020087 }
Florian Nücke5f5a5532014-05-19 10:10:15 +020088 else queue ++= getIngredients(stack)
Florian Nücke3459ff12014-05-29 16:16:04 +020089 totalRequiredEnergy = queue.size * Settings.get.disassemblerItemCost
Florian Nücke9cd55182014-05-18 19:53:34 +020090 }
91 }
92
Florian Nücke5f5a5532014-05-19 10:10:15 +020093 private def enqueueRobot(robot: ItemStack) {
94 val info = new ItemUtils.RobotData(robot)
95 queue += api.Items.get("case" + (info.tier + 1)).createItemStack(1)
96 queue ++= info.containers
97 queue ++= info.components
Florian Nückeddd8e182014-05-23 18:55:19 +020098 node.changeBuffer(info.robotEnergy)
Florian Nücke5f5a5532014-05-19 10:10:15 +020099 }
100
101 private def enqueueServer(server: ItemStack, serverTier: Int) {
102 val info = new ServerInventory {
103 override def tier = serverTier
104
105 override def container = server
106 }
107 for (slot <- 0 until info.getSizeInventory) {
108 val stack = info.getStackInSlot(slot)
109 drop(stack)
110 }
111 queue ++= getIngredients(server)
112 }
113
114 private def enqueueNavigationUpgrade(stack: ItemStack) {
Florian Nücke3c355c22014-05-19 10:54:31 +0200115 val info = new ItemUtils.NavigationUpgradeData(stack)
Florian Nücke5f5a5532014-05-19 10:10:15 +0200116 val parts = getIngredients(stack)
Florian Nücke3c355c22014-05-19 10:54:31 +0200117 queue ++= parts.map {
Florian Nücke177edc92014-05-29 13:26:15 +0200118 case part if part.getItem == net.minecraft.init.Items.filled_map => info.map
Florian Nücke3c355c22014-05-19 10:54:31 +0200119 case part => part
120 }
Florian Nücke5f5a5532014-05-19 10:10:15 +0200121 }
122
Florian Nücke02bdcce2014-05-19 11:25:25 +0200123 private def getIngredients(stack: ItemStack): Iterable[ItemStack] = try {
Florian Nücke5f5a5532014-05-19 10:10:15 +0200124 val recipes = CraftingManager.getInstance.getRecipeList.map(_.asInstanceOf[IRecipe])
125 val recipe = recipes.find(recipe => recipe.getRecipeOutput != null && recipe.getRecipeOutput.isItemEqual(stack))
126 val count = recipe.fold(0)(_.getRecipeOutput.stackSize)
Florian Nücke02bdcce2014-05-19 11:25:25 +0200127 val ingredients = (recipe match {
Florian Nücke5f5a5532014-05-19 10:10:15 +0200128 case Some(recipe: ShapedRecipes) => recipe.recipeItems.toIterable
129 case Some(recipe: ShapelessRecipes) => recipe.recipeItems.map(_.asInstanceOf[ItemStack])
Florian Nücke9cd55182014-05-18 19:53:34 +0200130 case Some(recipe: ShapedOreRecipe) => resolveOreDictEntries(recipe.getInput)
131 case Some(recipe: ShapelessOreRecipe) => resolveOreDictEntries(recipe.getInput)
132 case _ => Iterable.empty
Florian Nückec28cfa62014-05-20 00:46:14 +0200133 }).filter(ingredient => ingredient != null &&
134 // Strip out buckets, because those are returned when crafting, and
135 // we have no way of returning the fluid only (and I can't be arsed
136 // to make it output fluids into fluiducts or such, sorry).
137 !ingredient.getItem.isInstanceOf[ItemBucket]).toArray
Florian Nücke5f5a5532014-05-19 10:10:15 +0200138 // Avoid positive feedback loops.
Florian Nückec28cfa62014-05-20 00:46:14 +0200139 if (ingredients.exists(ingredient => ingredient.isItemEqual(stack))) {
Florian Nücke02bdcce2014-05-19 11:25:25 +0200140 return Iterable.empty
141 }
Florian Nücke5f5a5532014-05-19 10:10:15 +0200142 // Merge equal items for size division by output size.
143 val merged = mutable.ArrayBuffer.empty[ItemStack]
Florian Nückec28cfa62014-05-20 00:46:14 +0200144 for (ingredient <- ingredients) {
Florian Nücke5f5a5532014-05-19 10:10:15 +0200145 merged.find(_.isItemEqual(ingredient)) match {
146 case Some(entry) => entry.stackSize += ingredient.stackSize
147 case _ => merged += ingredient.copy()
148 }
149 }
150 merged.foreach(_.stackSize /= count)
151 // Split items up again to 'disassemble them individually'.
152 val distinct = mutable.ArrayBuffer.empty[ItemStack]
153 for (ingredient <- merged) {
154 val size = ingredient.stackSize
155 ingredient.stackSize = 1
156 for (i <- 0 until size) {
157 distinct += ingredient.copy()
158 }
159 }
160 distinct
Florian Nücke9cd55182014-05-18 19:53:34 +0200161 }
Florian Nücke02bdcce2014-05-19 11:25:25 +0200162 catch {
163 case t: Throwable =>
Florian Nückeadb58e02014-06-10 00:02:53 +0200164 OpenComputers.log.log(Level.WARN, "Whoops, something went wrong when trying to figure out an item's parts.", t)
Florian Nücke02bdcce2014-05-19 11:25:25 +0200165 Iterable.empty
166 }
Florian Nücke9cd55182014-05-18 19:53:34 +0200167
168 private def resolveOreDictEntries[T](entries: Iterable[T]) = entries.collect {
169 case stack: ItemStack => stack
170 case list: java.util.ArrayList[ItemStack]@unchecked if !list.isEmpty => list.get(world.rand.nextInt(list.size))
Florian Nücke5f5a5532014-05-19 10:10:15 +0200171 }
Florian Nücke3c355c22014-05-19 10:54:31 +0200172
Florian Nücke5f5a5532014-05-19 10:10:15 +0200173 private def drop(stack: ItemStack) {
174 if (stack != null) {
175 for (side <- ForgeDirection.VALID_DIRECTIONS if stack.stackSize > 0) {
Florian Nücked7143f52014-05-20 16:00:04 +0200176 InventoryUtils.insertIntoInventoryAt(stack, world, x + side.offsetX, y + side.offsetY, z + side.offsetZ, side.getOpposite)
Florian Nücke5f5a5532014-05-19 10:10:15 +0200177 }
178 if (stack.stackSize > 0) {
179 spawnStackInWorld(stack, ForgeDirection.UP)
180 }
181 }
182 }
Florian Nücke9cd55182014-05-18 19:53:34 +0200183
184 // ----------------------------------------------------------------------- //
185
186 override def readFromNBT(nbt: NBTTagCompound) {
187 super.readFromNBT(nbt)
188 queue.clear()
Florian Nücke177edc92014-05-29 13:26:15 +0200189 queue ++= nbt.getTagList(Settings.namespace + "queue", NBT.TAG_COMPOUND).map((list, index) => {
190 ItemStack.loadItemStackFromNBT(list.getCompoundTagAt(index))
191 })
Florian Nücke9cd55182014-05-18 19:53:34 +0200192 buffer = nbt.getDouble(Settings.namespace + "buffer")
Florian Nücke3459ff12014-05-29 16:16:04 +0200193 totalRequiredEnergy = nbt.getDouble(Settings.namespace + "total")
Florian Nücke5f5a5532014-05-19 10:10:15 +0200194 isActive = !queue.isEmpty
Florian Nücke9cd55182014-05-18 19:53:34 +0200195 }
196
197 override def writeToNBT(nbt: NBTTagCompound) {
198 super.writeToNBT(nbt)
199 nbt.setNewTagList(Settings.namespace + "queue", queue)
200 nbt.setDouble(Settings.namespace + "buffer", buffer)
Florian Nücke3459ff12014-05-29 16:16:04 +0200201 nbt.setDouble(Settings.namespace + "total", totalRequiredEnergy)
Florian Nücke9cd55182014-05-18 19:53:34 +0200202 }
203
204 @SideOnly(Side.CLIENT)
205 override def readFromNBTForClient(nbt: NBTTagCompound) {
206 super.readFromNBTForClient(nbt)
207 isActive = nbt.getBoolean("isActive")
208 }
209
210 override def writeToNBTForClient(nbt: NBTTagCompound) {
211 super.writeToNBTForClient(nbt)
Florian Nücke5f5a5532014-05-19 10:10:15 +0200212 nbt.setBoolean("isActive", isActive)
Florian Nücke9cd55182014-05-18 19:53:34 +0200213 }
214
215 // ----------------------------------------------------------------------- //
216
217 override def getSizeInventory = 1
218
Florian Nücke5f5a5532014-05-19 10:10:15 +0200219 override def getInventoryStackLimit = 64
Florian Nücke9cd55182014-05-18 19:53:34 +0200220
Florian Nücke177edc92014-05-29 13:26:15 +0200221 override def getInventoryName = Settings.namespace + "container.Disassembler"
Florian Nücke9cd55182014-05-18 19:53:34 +0200222
223 override def isItemValidForSlot(i: Int, stack: ItemStack) =
224 api.Items.get(stack) == api.Items.get("robot") ||
225 ((Settings.get.disassembleAllTheThings || api.Items.get(stack) != null) && !getIngredients(stack).isEmpty)
226}