| package li.cil.oc.util |
| |
| import java.util.Random |
| |
| import com.google.common.base.Charsets |
| import com.google.common.base.Strings |
| import li.cil.oc.OpenComputers |
| import li.cil.oc.Settings |
| import li.cil.oc.api |
| import li.cil.oc.api.Persistable |
| import li.cil.oc.common.Tier |
| import li.cil.oc.common.block.DelegatorConverter |
| import li.cil.oc.common.init.Items |
| import li.cil.oc.integration.opencomputers.DriverScreen |
| import li.cil.oc.util.ExtendedNBT._ |
| import net.minecraft.item.ItemBucket |
| import net.minecraft.item.ItemMap |
| import net.minecraft.item.ItemStack |
| import net.minecraft.item.crafting.CraftingManager |
| import net.minecraft.item.crafting.IRecipe |
| import net.minecraft.item.crafting.ShapedRecipes |
| import net.minecraft.item.crafting.ShapelessRecipes |
| import net.minecraft.nbt.NBTTagCompound |
| import net.minecraft.world.World |
| import net.minecraftforge.common.util.Constants.NBT |
| import net.minecraftforge.oredict.ShapedOreRecipe |
| import net.minecraftforge.oredict.ShapelessOreRecipe |
| |
| import scala.collection.convert.WrapAsScala._ |
| import scala.collection.mutable |
| import scala.io.Source |
| |
| object ItemUtils { |
| def caseTier(stack: ItemStack) = { |
| val descriptor = api.Items.get(stack) |
| if (descriptor == api.Items.get("case1")) Tier.One |
| else if (descriptor == api.Items.get("case2")) Tier.Two |
| else if (descriptor == api.Items.get("case3")) Tier.Three |
| else if (descriptor == api.Items.get("caseCreative")) Tier.Four |
| else if (descriptor == api.Items.get("microcontrollerCase1")) Tier.One |
| else if (descriptor == api.Items.get("microcontrollerCase2")) Tier.Two |
| else if (descriptor == api.Items.get("droneCase1")) Tier.One |
| else if (descriptor == api.Items.get("droneCase2")) Tier.Two |
| else if (descriptor == api.Items.get("server1")) Tier.One |
| else if (descriptor == api.Items.get("server2")) Tier.Two |
| else if (descriptor == api.Items.get("server3")) Tier.Three |
| else Tier.None |
| } |
| |
| def loadStack(nbt: NBTTagCompound) = DelegatorConverter.convert(ItemStack.loadItemStackFromNBT(nbt)) |
| |
| def getIngredients(stack: ItemStack): Array[ItemStack] = try { |
| val recipes = CraftingManager.getInstance.getRecipeList.map(_.asInstanceOf[IRecipe]) |
| val recipe = recipes.find(recipe => recipe.getRecipeOutput != null && recipe.getRecipeOutput.isItemEqual(stack)) |
| val count = recipe.fold(0)(_.getRecipeOutput.stackSize) |
| val ingredients = (recipe match { |
| case Some(recipe: ShapedRecipes) => recipe.recipeItems.toIterable |
| case Some(recipe: ShapelessRecipes) => recipe.recipeItems.map(_.asInstanceOf[ItemStack]) |
| case Some(recipe: ShapedOreRecipe) => resolveOreDictEntries(recipe.getInput) |
| case Some(recipe: ShapelessOreRecipe) => resolveOreDictEntries(recipe.getInput) |
| case _ => Iterable.empty |
| }).filter(ingredient => ingredient != null && |
| // Strip out buckets, because those are returned when crafting, and |
| // we have no way of returning the fluid only (and I can't be arsed |
| // to make it output fluids into fluiducts or such, sorry). |
| !ingredient.getItem.isInstanceOf[ItemBucket]).toArray |
| // Avoid positive feedback loops. |
| if (ingredients.exists(ingredient => ingredient.isItemEqual(stack))) { |
| return Array.empty |
| } |
| // Merge equal items for size division by output size. |
| val merged = mutable.ArrayBuffer.empty[ItemStack] |
| for (ingredient <- ingredients) { |
| merged.find(_.isItemEqual(ingredient)) match { |
| case Some(entry) => entry.stackSize += ingredient.stackSize |
| case _ => merged += ingredient.copy() |
| } |
| } |
| merged.foreach(_.stackSize /= count) |
| // Split items up again to 'disassemble them individually'. |
| val distinct = mutable.ArrayBuffer.empty[ItemStack] |
| for (ingredient <- merged) { |
| val size = ingredient.stackSize |
| ingredient.stackSize = 1 |
| for (i <- 0 until size) { |
| distinct += ingredient.copy() |
| } |
| } |
| distinct.toArray |
| } |
| catch { |
| case t: Throwable => |
| OpenComputers.log.warn("Whoops, something went wrong when trying to figure out an item's parts.", t) |
| Array.empty |
| } |
| |
| private lazy val rng = new Random() |
| |
| private def resolveOreDictEntries[T](entries: Iterable[T]) = entries.collect { |
| case stack: ItemStack => stack |
| case list: java.util.ArrayList[ItemStack]@unchecked if !list.isEmpty => list.get(rng.nextInt(list.size)) |
| } |
| |
| abstract class ItemData extends Persistable { |
| def load(stack: ItemStack) { |
| if (stack.hasTagCompound) { |
| // Because ItemStack's load function doesn't copy the compound tag, |
| // but keeps it as is, leading to oh so fun bugs! |
| load(stack.getTagCompound.copy().asInstanceOf[NBTTagCompound]) |
| } |
| } |
| |
| def save(stack: ItemStack) { |
| if (!stack.hasTagCompound) { |
| stack.setTagCompound(new NBTTagCompound()) |
| } |
| save(stack.getTagCompound) |
| } |
| } |
| |
| class MicrocontrollerData extends ItemData { |
| def this(stack: ItemStack) { |
| this() |
| load(stack) |
| } |
| |
| var tier = Tier.One |
| |
| var components = Array.empty[ItemStack] |
| |
| var storedEnergy = 0 |
| |
| override def load(nbt: NBTTagCompound) { |
| tier = nbt.getByte(Settings.namespace + "tier") |
| components = nbt.getTagList(Settings.namespace + "components", NBT.TAG_COMPOUND). |
| toArray[NBTTagCompound].map(loadStack) |
| storedEnergy = nbt.getInteger(Settings.namespace + "storedEnergy") |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| nbt.setByte(Settings.namespace + "tier", tier.toByte) |
| nbt.setNewTagList(Settings.namespace + "components", components.toIterable) |
| nbt.setInteger(Settings.namespace + "storedEnergy", storedEnergy) |
| } |
| |
| def createItemStack() = { |
| val stack = api.Items.get("microcontroller").createItemStack(1) |
| save(stack) |
| stack |
| } |
| |
| def copyItemStack() = { |
| val stack = createItemStack() |
| // Forget all node addresses and so on. This is used when 'picking' a |
| // microcontroller in creative mode. |
| val newInfo = new MicrocontrollerData(stack) |
| newInfo.components.foreach(cs => Option(api.Driver.driverFor(cs)) match { |
| case Some(driver) if driver == DriverScreen => |
| val nbt = driver.dataTag(cs) |
| for (tagName <- nbt.func_150296_c().toArray) { |
| nbt.removeTag(tagName.asInstanceOf[String]) |
| } |
| case _ => |
| }) |
| newInfo.save(stack) |
| stack |
| } |
| } |
| |
| class NavigationUpgradeData extends ItemData { |
| def this(stack: ItemStack) { |
| this() |
| load(stack) |
| } |
| |
| var map = new ItemStack(net.minecraft.init.Items.filled_map) |
| |
| def mapData(world: World) = try map.getItem.asInstanceOf[ItemMap].getMapData(map, world) catch { |
| case _: Throwable => throw new Exception("invalid map") |
| } |
| |
| override def load(stack: ItemStack) { |
| if (stack.hasTagCompound) { |
| load(stack.getTagCompound.getCompoundTag(Settings.namespace + "data")) |
| } |
| } |
| |
| override def save(stack: ItemStack) { |
| if (!stack.hasTagCompound) { |
| stack.setTagCompound(new NBTTagCompound()) |
| } |
| save(stack.getCompoundTag(Settings.namespace + "data")) |
| } |
| |
| override def load(nbt: NBTTagCompound) { |
| if (nbt.hasKey(Settings.namespace + "map")) { |
| map = loadStack(nbt.getCompoundTag(Settings.namespace + "map")) |
| } |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| if (map != null) { |
| nbt.setNewCompoundTag(Settings.namespace + "map", map.writeToNBT) |
| } |
| } |
| } |
| |
| class RobotData extends ItemData { |
| def this(stack: ItemStack) { |
| this() |
| load(stack) |
| } |
| |
| var name = "" |
| |
| // Overall energy including components. |
| var totalEnergy = 0 |
| |
| // Energy purely stored in robot component - this is what we have to restore manually. |
| var robotEnergy = 0 |
| |
| var tier = 0 |
| |
| var components = Array.empty[ItemStack] |
| |
| var containers = Array.empty[ItemStack] |
| |
| override def load(nbt: NBTTagCompound) { |
| if (nbt.hasKey("display") && nbt.getCompoundTag("display").hasKey("Name")) { |
| name = nbt.getCompoundTag("display").getString("Name") |
| } |
| if (Strings.isNullOrEmpty(name)) { |
| name = RobotData.randomName |
| } |
| totalEnergy = nbt.getInteger(Settings.namespace + "storedEnergy") |
| robotEnergy = nbt.getInteger(Settings.namespace + "robotEnergy") |
| tier = nbt.getInteger(Settings.namespace + "tier") |
| components = nbt.getTagList(Settings.namespace + "components", NBT.TAG_COMPOUND). |
| toArray[NBTTagCompound].map(loadStack) |
| containers = nbt.getTagList(Settings.namespace + "containers", NBT.TAG_COMPOUND). |
| toArray[NBTTagCompound].map(loadStack) |
| |
| // Code for migrating from 1.4.1 -> 1.4.2, add EEPROM. |
| // TODO Remove in 1.5 |
| if (!nbt.hasKey(Settings.namespace + "biosFlag")) { |
| components :+= Items.createLuaBios() |
| } |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| if (name != null) { |
| if (!nbt.hasKey("display")) { |
| nbt.setTag("display", new NBTTagCompound()) |
| } |
| nbt.getCompoundTag("display").setString("Name", name) |
| } |
| nbt.setInteger(Settings.namespace + "storedEnergy", totalEnergy) |
| nbt.setInteger(Settings.namespace + "robotEnergy", robotEnergy) |
| nbt.setInteger(Settings.namespace + "tier", tier) |
| nbt.setNewTagList(Settings.namespace + "components", components.toIterable) |
| nbt.setNewTagList(Settings.namespace + "containers", containers.toIterable) |
| |
| // TODO Remove in 1.5 |
| nbt.setBoolean(Settings.namespace + "biosFlag", true) |
| } |
| |
| def createItemStack() = { |
| val stack = api.Items.get("robot").createItemStack(1) |
| save(stack) |
| stack |
| } |
| |
| def copyItemStack() = { |
| val stack = createItemStack() |
| // Forget all node addresses and so on. This is used when 'picking' a |
| // robot in creative mode. |
| val newInfo = new RobotData(stack) |
| newInfo.components.foreach(cs => Option(api.Driver.driverFor(cs)) match { |
| case Some(driver) if driver == DriverScreen => |
| val nbt = driver.dataTag(cs) |
| for (tagName <- nbt.func_150296_c().toArray) { |
| nbt.removeTag(tagName.asInstanceOf[String]) |
| } |
| case _ => |
| }) |
| // Don't show energy info (because it's unreliable) but fill up the |
| // internal buffer. This is for creative use only, anyway. |
| newInfo.totalEnergy = 0 |
| newInfo.robotEnergy = 50000 |
| newInfo.save(stack) |
| stack |
| } |
| } |
| |
| object RobotData { |
| val names = try { |
| Source.fromInputStream(getClass.getResourceAsStream( |
| "/assets/" + Settings.resourceDomain + "/robot.names"))(Charsets.UTF_8). |
| getLines().map(_.takeWhile(_ != '#').trim()).filter(_ != "").toArray |
| } |
| catch { |
| case t: Throwable => |
| OpenComputers.log.warn("Failed loading robot name list.", t) |
| Array.empty[String] |
| } |
| |
| def randomName = if (names.length > 0) names((math.random * names.length).toInt) else "Robot" |
| } |
| |
| class TabletData extends ItemData { |
| def this(stack: ItemStack) { |
| this() |
| load(stack) |
| } |
| |
| var items = Array.fill[Option[ItemStack]](32)(None) |
| var isRunning = false |
| var energy = 0.0 |
| var maxEnergy = 0.0 |
| |
| override def load(nbt: NBTTagCompound) { |
| nbt.getTagList(Settings.namespace + "items", NBT.TAG_COMPOUND).foreach((slotNbt: NBTTagCompound) => { |
| val slot = slotNbt.getByte("slot") |
| if (slot >= 0 && slot < items.length) { |
| items(slot) = Option(loadStack(slotNbt.getCompoundTag("item"))) |
| } |
| }) |
| isRunning = nbt.getBoolean(Settings.namespace + "isRunning") |
| energy = nbt.getDouble(Settings.namespace + "energy") |
| maxEnergy = nbt.getDouble(Settings.namespace + "maxEnergy") |
| |
| // Code for migrating from 1.4.1 -> 1.4.2, add EEPROM. |
| // TODO Remove in 1.5 |
| if (!nbt.hasKey(Settings.namespace + "biosFlag")) { |
| val firstEmpty = items.indexWhere(_.isEmpty) |
| items(firstEmpty) = Option(Items.createLuaBios()) |
| } |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| nbt.setNewTagList(Settings.namespace + "items", |
| items.zipWithIndex collect { |
| case (Some(stack), slot) => (stack, slot) |
| } map { |
| case (stack, slot) => |
| val slotNbt = new NBTTagCompound() |
| slotNbt.setByte("slot", slot.toByte) |
| slotNbt.setNewCompoundTag("item", stack.writeToNBT) |
| }) |
| nbt.setBoolean(Settings.namespace + "isRunning", isRunning) |
| nbt.setDouble(Settings.namespace + "energy", energy) |
| nbt.setDouble(Settings.namespace + "maxEnergy", maxEnergy) |
| |
| // TODO Remove in 1.5 |
| nbt.setBoolean(Settings.namespace + "biosFlag", true) |
| } |
| } |
| |
| } |