blob: 68df2ad045da33bb098960ea0556607ce00edc8f [file] [log] [blame] [raw]
package li.cil.oc.util
import com.google.common.base.Charsets
import net.minecraft.item.ItemStack
import net.minecraft.nbt._
import net.minecraft.tileentity.TileEntity
import net.minecraftforge.common.util.Constants.NBT
import net.minecraftforge.common.util.ForgeDirection
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
import scala.language.implicitConversions
import scala.language.reflectiveCalls
import scala.reflect.ClassTag
object ExtendedNBT {
implicit def toNbt(value: Boolean): NBTTagByte = new NBTTagByte(if (value) 1 else 0)
implicit def toNbt(value: Byte): NBTTagByte = new NBTTagByte(value)
implicit def toNbt(value: Short): NBTTagShort = new NBTTagShort(value)
implicit def toNbt(value: Int): NBTTagInt = new NBTTagInt(value)
implicit def toNbt(value: Long): NBTTagLong = new NBTTagLong(value)
implicit def toNbt(value: Float): NBTTagFloat = new NBTTagFloat(value)
implicit def toNbt(value: Double): NBTTagDouble = new NBTTagDouble(value)
implicit def toNbt(value: Array[Byte]): NBTTagByteArray = new NBTTagByteArray(value)
implicit def toNbt(value: Array[Int]): NBTTagIntArray = new NBTTagIntArray(value)
implicit def toNbt(value: Array[Boolean]): NBTTagByteArray = new NBTTagByteArray(value.map(if (_) 1: Byte else 0: Byte))
implicit def toNbt(value: String): NBTTagString = new NBTTagString(value)
implicit def toNbt(value: ItemStack): NBTTagCompound = {
val nbt = new NBTTagCompound()
if (value != null) {
value.writeToNBT(nbt)
}
nbt
}
implicit def toNbt(value: NBTTagCompound => Unit): NBTTagCompound = {
val nbt = new NBTTagCompound()
value(nbt)
nbt
}
implicit def toNbt(value: Map[String, _]): NBTTagCompound = {
val nbt = new NBTTagCompound()
for ((key, value) <- value) value match {
case value: Boolean => nbt.setTag(key, value)
case value: Byte => nbt.setTag(key, value)
case value: Short => nbt.setTag(key, value)
case value: Int => nbt.setTag(key, value)
case value: Long => nbt.setTag(key, value)
case value: Float => nbt.setTag(key, value)
case value: Double => nbt.setTag(key, value)
case value: Array[Byte] => nbt.setTag(key, value)
case value: Array[Int] => nbt.setTag(key, value)
case value: String => nbt.setTag(key, value)
case value: ItemStack => nbt.setTag(key, value)
case _ =>
}
nbt
}
def typedMapToNbt(map: Map[_, _]): NBTBase = {
def mapToList(value: Array[(_, _)]) = value.collect {
// Ignore, can be stuff like the 'n' introduced by Lua's `pack`.
case (k: Number, v) => k -> v
}.sortBy(_._1.intValue()).map(_._2)
def asList(value: Option[Any]): IndexedSeq[_] = value match {
case Some(v: Array[_]) => v
case Some(v: Map[_, _]) => mapToList(v.toArray)
case Some(v: mutable.Map[_, _]) => mapToList(v.toArray)
case Some(v: java.util.Map[_, _]) => mapToList(mapAsScalaMap(v).toArray)
case Some(v: String) => v.getBytes(Charsets.UTF_8)
case _ => throw new IllegalArgumentException("Illegal or missing value.")
}
def asMap[K](value: Option[Any]): Map[K, _] = value match {
case Some(v: Map[K, _]@unchecked) => v
case Some(v: mutable.Map[K, _]@unchecked) => v.toMap
case Some(v: java.util.Map[K, _]@unchecked) => mapAsScalaMap(v).toMap
case _ => throw new IllegalArgumentException("Illegal value.")
}
val typeAndValue = asMap[String](Option(map))
val nbtType = typeAndValue.get("type")
val nbtValue = typeAndValue.get("value")
nbtType match {
case Some(n: Number) => n.intValue() match {
case NBT.TAG_BYTE => new NBTTagByte(nbtValue match {
case Some(v: Number) => v.byteValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_SHORT => new NBTTagShort(nbtValue match {
case Some(v: Number) => v.shortValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_INT => new NBTTagInt(nbtValue match {
case Some(v: Number) => v.intValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_LONG => new NBTTagLong(nbtValue match {
case Some(v: Number) => v.longValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_FLOAT => new NBTTagFloat(nbtValue match {
case Some(v: Number) => v.floatValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_DOUBLE => new NBTTagDouble(nbtValue match {
case Some(v: Number) => v.doubleValue()
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_BYTE_ARRAY => new NBTTagByteArray(asList(nbtValue).map {
case n: Number => n.byteValue()
case _ => throw new IllegalArgumentException("Illegal value.")
}.toArray)
case NBT.TAG_STRING => new NBTTagString(nbtValue match {
case Some(v: String) => v
case Some(v: Array[Byte]) => new String(v, Charsets.UTF_8)
case _ => throw new IllegalArgumentException("Illegal or missing value.")
})
case NBT.TAG_LIST =>
val list = new NBTTagList()
asList(nbtValue).map(v => asMap(Option(v))).foreach(v => list.appendTag(typedMapToNbt(v)))
list
case NBT.TAG_COMPOUND =>
val nbt = new NBTTagCompound()
val values = asMap[String](nbtValue)
for ((name, entry) <- values) {
try nbt.setTag(name, typedMapToNbt(asMap[Any](Option(entry)))) catch {
case t: Throwable => throw new IllegalArgumentException(s"Error converting entry '$name': ${t.getMessage}")
}
}
nbt
case NBT.TAG_INT_ARRAY =>
new NBTTagIntArray(asList(nbtValue).map {
case n: Number => n.intValue()
case _ => throw new IllegalArgumentException()
}.toArray)
case _ => throw new IllegalArgumentException(s"Unsupported NBT type '$n'.")
}
case Some(t) => throw new IllegalArgumentException(s"Illegal NBT type '$t'.")
case _ => throw new IllegalArgumentException(s"Missing NBT type.")
}
}
implicit def booleanIterableToNbt(value: Iterable[Boolean]): Iterable[NBTTagByte] = value.map(toNbt)
implicit def byteIterableToNbt(value: Iterable[Byte]): Iterable[NBTTagByte] = value.map(toNbt)
implicit def shortIterableToNbt(value: Iterable[Short]): Iterable[NBTTagShort] = value.map(toNbt)
implicit def intIterableToNbt(value: Iterable[Int]): Iterable[NBTTagInt] = value.map(toNbt)
implicit def intArrayIterableToNbt(value: Iterable[Array[Int]]): Iterable[NBTTagIntArray] = value.map(toNbt)
implicit def longIterableToNbt(value: Iterable[Long]): Iterable[NBTTagLong] = value.map(toNbt)
implicit def floatIterableToNbt(value: Iterable[Float]): Iterable[NBTTagFloat] = value.map(toNbt)
implicit def doubleIterableToNbt(value: Iterable[Double]): Iterable[NBTTagDouble] = value.map(toNbt)
implicit def byteArrayIterableToNbt(value: Iterable[Array[Byte]]): Iterable[NBTTagByteArray] = value.map(toNbt)
implicit def stringIterableToNbt(value: Iterable[String]): Iterable[NBTTagString] = value.map(toNbt)
implicit def writableIterableToNbt(value: Iterable[NBTTagCompound => Unit]): Iterable[NBTTagCompound] = value.map(toNbt)
implicit def itemStackIterableToNbt(value: Iterable[ItemStack]): Iterable[NBTTagCompound] = value.map(toNbt)
implicit def extendNBTBase(nbt: NBTBase): ExtendedNBTBase = new ExtendedNBTBase(nbt)
implicit def extendNBTTagCompound(nbt: NBTTagCompound): ExtendedNBTTagCompound = new ExtendedNBTTagCompound(nbt)
implicit def extendNBTTagList(nbt: NBTTagList): ExtendedNBTTagList = new ExtendedNBTTagList(nbt)
class ExtendedNBTBase(val nbt: NBTBase) {
def toTypedMap: Map[String, _] = Map("type" -> nbt.getId, "value" -> (nbt match {
case tag: NBTTagByte => tag.func_150290_f()
case tag: NBTTagShort => tag.func_150289_e()
case tag: NBTTagInt => tag.func_150287_d()
case tag: NBTTagLong => tag.func_150291_c()
case tag: NBTTagFloat => tag.func_150288_h()
case tag: NBTTagDouble => tag.func_150286_g()
case tag: NBTTagByteArray => tag.func_150292_c()
case tag: NBTTagString => tag.func_150285_a_()
case tag: NBTTagList => tag.map((entry: NBTBase) => entry.toTypedMap)
case tag: NBTTagCompound => tag.func_150296_c().collect {
case key: String => key -> tag.getTag(key).toTypedMap
}.toMap
case tag: NBTTagIntArray => tag.func_150302_c()
case _ => throw new IllegalArgumentException()
}))
}
class ExtendedNBTTagCompound(val nbt: NBTTagCompound) {
def setNewCompoundTag(name: String, f: (NBTTagCompound) => Any) = {
val t = new NBTTagCompound()
f(t)
nbt.setTag(name, t)
nbt
}
def setNewTagList(name: String, values: Iterable[NBTBase]) = {
val t = new NBTTagList()
t.append(values)
nbt.setTag(name, t)
nbt
}
def setNewTagList(name: String, values: NBTBase*): NBTTagCompound = setNewTagList(name, values)
def getDirection(name: String) = {
nbt.getByte(name) match {
case id if id < 0 => None
case id =>
val side = ForgeDirection.getOrientation(id)
// Backwards compatibility.
if (side == ForgeDirection.UNKNOWN) None
else Option(side)
}
}
def setDirection(name: String, d: Option[ForgeDirection]): Unit = {
d match {
case Some(side) => nbt.setByte(name, side.ordinal.toByte)
case _ => nbt.setByte(name, -1: Byte)
}
}
def getBooleanArray(name: String) = nbt.getByteArray(name).map(_ == 1)
def setBooleanArray(name: String, value: Array[Boolean]) = nbt.setTag(name, toNbt(value))
}
class ExtendedNBTTagList(val nbt: NBTTagList) {
def appendNewCompoundTag(f: (NBTTagCompound) => Unit) {
val t = new NBTTagCompound()
f(t)
nbt.appendTag(t)
}
def append(values: Iterable[NBTBase]) {
for (value <- values) {
nbt.appendTag(value)
}
}
def append(values: NBTBase*): Unit = append(values)
def foreach[Tag <: NBTBase](f: Tag => Unit) {
val iterable = nbt.copy.asInstanceOf[NBTTagList]
while (iterable.tagCount > 0) {
f(iterable.removeTag(0).asInstanceOf[Tag])
}
}
def map[Tag <: NBTBase, Value](f: Tag => Value): IndexedSeq[Value] = {
val iterable = nbt.copy.asInstanceOf[NBTTagList]
val buffer = mutable.ArrayBuffer.empty[Value]
while (iterable.tagCount > 0) {
buffer += f(iterable.removeTag(0).asInstanceOf[Tag])
}
buffer
}
def toArray[Tag: ClassTag] = map((t: Tag) => t).toArray
}
}