From ac3b928a2f55d901c6e42559dec1aec446e3377b Mon Sep 17 00:00:00 2001 From: gamax92 Date: Sun, 26 Jul 2015 09:41:44 -0600 Subject: [PATCH 1/2] Add NBT editing functionality to Debug Card --- .../cil/oc/server/component/DebugCard.scala | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index 36cb97818..94b401aa2 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -25,8 +25,8 @@ import net.minecraft.block.Block import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.Item import net.minecraft.item.ItemStack -import net.minecraft.nbt.JsonToNBT -import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt._ +import net.minecraft.nbt.NBTBase.NBTPrimitive import net.minecraft.server.MinecraftServer import net.minecraft.server.management.UserListOpsEntry import net.minecraft.util.IChatComponent @@ -317,6 +317,76 @@ object DebugCard { // ----------------------------------------------------------------------- // + def getMap(tag: NBTTagCompound): java.util.Map[String, Any] = { + val keys = tag.func_150296_c().asInstanceOf[java.util.Set[String]] + val map = new java.util.HashMap[String, Any]() + for (key <- keys) { + val keyMap = new java.util.HashMap[String, Any]() + map.put(key, keyMap) + val nbt = tag.getTag(key) + keyMap.put("type", nbt.getId) + if (nbt.isInstanceOf[NBTTagCompound]) keyMap.put("value", getMap(nbt.asInstanceOf[NBTTagCompound])) + else if (nbt.isInstanceOf[NBTPrimitive]) keyMap.put("value", nbt.asInstanceOf[NBTPrimitive].func_150286_g()) + else if (nbt.isInstanceOf[NBTTagString]) keyMap.put("value", nbt.asInstanceOf[NBTTagString].func_150285_a_()) + else if (nbt.isInstanceOf[NBTTagList]) { + val array = new java.util.HashMap[Integer, Any]() + val tagList = nbt.asInstanceOf[NBTTagList] + for (i <- 0 until tagList.tagCount()) array.put(i + 1, getMap(tagList.getCompoundTagAt(i))) + keyMap.put("value", array) + } else if (nbt.isInstanceOf[NBTTagIntArray]) { + val array = new java.util.HashMap[Integer, Any]() + val tagIntList = nbt.asInstanceOf[NBTTagIntArray].func_150302_c() + for (i <- 0 until tagIntList.length) array.put(i + 1, tagIntList(i)) + keyMap.put("value", array) + } else if (nbt.isInstanceOf[NBTTagByteArray]) { + val array = new java.util.HashMap[Integer, Any]() + val tagByteList = nbt.asInstanceOf[NBTTagByteArray].func_150292_c() + for (i <- 0 until tagByteList.length) array.put(i + 1, tagByteList(i)) + keyMap.put("value", array) + } else throw new Exception("NBT tag " + NBTBase.NBTTypes(nbt.getId) + " is unsupported") + } + map + } + + def getTag(map: java.util.Map[String, Any]): NBTTagCompound = { + val tag = new NBTTagCompound() + for ((key, value) <- map) { + val keyObject = value.asInstanceOf[java.util.Map[String, Any]] + val `type` = keyObject.get("type").asInstanceOf[Double].intValue() + val element = keyObject.get("value") + `type` match { + case 1 => tag.setByte(key, element.asInstanceOf[Double].byteValue()) + case 2 => tag.setShort(key, element.asInstanceOf[Double].shortValue()) + case 3 => tag.setInteger(key, element.asInstanceOf[Double].intValue()) + case 4 => tag.setLong(key, element.asInstanceOf[Double].longValue()) + case 5 => tag.setFloat(key, element.asInstanceOf[Double].floatValue()) + case 6 => tag.setDouble(key, element.asInstanceOf[Double]) + case 7 => + var jbArray = element.asInstanceOf[java.util.Map[Integer, Any]] + var bArray = Array.ofDim[Byte](jbArray.size) + for (i <- 0 until jbArray.size) bArray(i) = jbArray.get(i + 1).asInstanceOf[Double].byteValue() + tag.setByteArray(key, bArray) + + case 8 => tag.setString(key, element.asInstanceOf[String]) + case 9 => + var array = element.asInstanceOf[java.util.Map[Integer, Any]] + var tagList = new NBTTagList() + for ((key, value) <- array) tagList.appendTag(getTag(value.asInstanceOf[java.util.Map[String, Any]])) + tag.setTag(key, tagList) + + case 10 => tag.setTag(key, getTag(element.asInstanceOf[java.util.Map[String, Any]])) + case 11 => + var ibArray = element.asInstanceOf[java.util.Map[Integer, Any]] + var iArray = Array.ofDim[Int](ibArray.size) + for (i <- 0 until ibArray.size) iArray(i) = ibArray.get(i + 1).asInstanceOf[Double].intValue() + tag.setIntArray(key, iArray) + + case _ => throw new Exception("NBT type #" + `type` + " is unsupported") + } + } + tag + } + @Callback(doc = """function():number -- Gets the numeric id of the current dimension.""") def getDimensionId(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() @@ -415,6 +485,33 @@ object DebugCard { result(block != null && block.hasTileEntity(world.getBlockMetadata(x, y, z))) } + @Callback(doc = """function(x:number, y:number, z:number):table -- Get the NBT of the block at the specified coordinates.""") + def getTileNBT(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() + val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) + val tileEntity = world.getTileEntity(x, y, z) + if (tileEntity != null) { + val tag = new NBTTagCompound() + tileEntity.writeToNBT(tag) + return result(getMap(tag)) + } + result(null) + } + + @Callback(doc = """function(x:number, y:number, z:number, nbt:table):boolean -- Set the NBT of the block at the specified coordinates.""") + def setTileNBT(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() + val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) + val nbt = args.checkTable(3).asInstanceOf[java.util.Map[String,Any]] + val tileEntity = world.getTileEntity(x, y, z) + if (tileEntity != null) { + val tag = getTag(nbt) + tileEntity.readFromNBT(tag) + return result(true) + } + result(null) + } + @Callback(doc = """function(x:number, y:number, z:number):number -- Get the light opacity of the block at the specified coordinates.""") def getLightOpacity(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() From 0509c804d86ef410b1cdf9753d0ecd5aea89d9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 26 Jul 2015 22:04:24 +0200 Subject: [PATCH 2/2] Moved nbt->map->nbt stuff to NBT helper class and made it a tad more generic. Also marking TEs dirty after loading and triggering a world update. Also scala-ified the whole stuff some more. --- .../cil/oc/server/component/DebugCard.scala | 100 +++------------ .../scala/li/cil/oc/util/ExtendedNBT.scala | 121 +++++++++++++++++- 2 files changed, 135 insertions(+), 86 deletions(-) diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index 94b401aa2..8795f4643 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -19,6 +19,7 @@ import li.cil.oc.api.prefab.AbstractValue import li.cil.oc.server.component.DebugCard.CommandSender import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedArguments._ +import li.cil.oc.util.ExtendedNBT._ import li.cil.oc.util.ExtendedWorld._ import li.cil.oc.util.InventoryUtils import net.minecraft.block.Block @@ -26,9 +27,9 @@ import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.Item import net.minecraft.item.ItemStack import net.minecraft.nbt._ -import net.minecraft.nbt.NBTBase.NBTPrimitive import net.minecraft.server.MinecraftServer import net.minecraft.server.management.UserListOpsEntry +import net.minecraft.tileentity.TileEntity import net.minecraft.util.IChatComponent import net.minecraft.world.World import net.minecraft.world.WorldServer @@ -317,76 +318,6 @@ object DebugCard { // ----------------------------------------------------------------------- // - def getMap(tag: NBTTagCompound): java.util.Map[String, Any] = { - val keys = tag.func_150296_c().asInstanceOf[java.util.Set[String]] - val map = new java.util.HashMap[String, Any]() - for (key <- keys) { - val keyMap = new java.util.HashMap[String, Any]() - map.put(key, keyMap) - val nbt = tag.getTag(key) - keyMap.put("type", nbt.getId) - if (nbt.isInstanceOf[NBTTagCompound]) keyMap.put("value", getMap(nbt.asInstanceOf[NBTTagCompound])) - else if (nbt.isInstanceOf[NBTPrimitive]) keyMap.put("value", nbt.asInstanceOf[NBTPrimitive].func_150286_g()) - else if (nbt.isInstanceOf[NBTTagString]) keyMap.put("value", nbt.asInstanceOf[NBTTagString].func_150285_a_()) - else if (nbt.isInstanceOf[NBTTagList]) { - val array = new java.util.HashMap[Integer, Any]() - val tagList = nbt.asInstanceOf[NBTTagList] - for (i <- 0 until tagList.tagCount()) array.put(i + 1, getMap(tagList.getCompoundTagAt(i))) - keyMap.put("value", array) - } else if (nbt.isInstanceOf[NBTTagIntArray]) { - val array = new java.util.HashMap[Integer, Any]() - val tagIntList = nbt.asInstanceOf[NBTTagIntArray].func_150302_c() - for (i <- 0 until tagIntList.length) array.put(i + 1, tagIntList(i)) - keyMap.put("value", array) - } else if (nbt.isInstanceOf[NBTTagByteArray]) { - val array = new java.util.HashMap[Integer, Any]() - val tagByteList = nbt.asInstanceOf[NBTTagByteArray].func_150292_c() - for (i <- 0 until tagByteList.length) array.put(i + 1, tagByteList(i)) - keyMap.put("value", array) - } else throw new Exception("NBT tag " + NBTBase.NBTTypes(nbt.getId) + " is unsupported") - } - map - } - - def getTag(map: java.util.Map[String, Any]): NBTTagCompound = { - val tag = new NBTTagCompound() - for ((key, value) <- map) { - val keyObject = value.asInstanceOf[java.util.Map[String, Any]] - val `type` = keyObject.get("type").asInstanceOf[Double].intValue() - val element = keyObject.get("value") - `type` match { - case 1 => tag.setByte(key, element.asInstanceOf[Double].byteValue()) - case 2 => tag.setShort(key, element.asInstanceOf[Double].shortValue()) - case 3 => tag.setInteger(key, element.asInstanceOf[Double].intValue()) - case 4 => tag.setLong(key, element.asInstanceOf[Double].longValue()) - case 5 => tag.setFloat(key, element.asInstanceOf[Double].floatValue()) - case 6 => tag.setDouble(key, element.asInstanceOf[Double]) - case 7 => - var jbArray = element.asInstanceOf[java.util.Map[Integer, Any]] - var bArray = Array.ofDim[Byte](jbArray.size) - for (i <- 0 until jbArray.size) bArray(i) = jbArray.get(i + 1).asInstanceOf[Double].byteValue() - tag.setByteArray(key, bArray) - - case 8 => tag.setString(key, element.asInstanceOf[String]) - case 9 => - var array = element.asInstanceOf[java.util.Map[Integer, Any]] - var tagList = new NBTTagList() - for ((key, value) <- array) tagList.appendTag(getTag(value.asInstanceOf[java.util.Map[String, Any]])) - tag.setTag(key, tagList) - - case 10 => tag.setTag(key, getTag(element.asInstanceOf[java.util.Map[String, Any]])) - case 11 => - var ibArray = element.asInstanceOf[java.util.Map[Integer, Any]] - var iArray = Array.ofDim[Int](ibArray.size) - for (i <- 0 until ibArray.size) iArray(i) = ibArray.get(i + 1).asInstanceOf[Double].intValue() - tag.setIntArray(key, iArray) - - case _ => throw new Exception("NBT type #" + `type` + " is unsupported") - } - } - tag - } - @Callback(doc = """function():number -- Gets the numeric id of the current dimension.""") def getDimensionId(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() @@ -489,27 +420,28 @@ object DebugCard { def getTileNBT(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) - val tileEntity = world.getTileEntity(x, y, z) - if (tileEntity != null) { - val tag = new NBTTagCompound() - tileEntity.writeToNBT(tag) - return result(getMap(tag)) + world.getTileEntity(x, y, z) match { + case tileEntity: TileEntity => result(toNbt(tileEntity).toTypedMap) + case _ => null } - result(null) } @Callback(doc = """function(x:number, y:number, z:number, nbt:table):boolean -- Set the NBT of the block at the specified coordinates.""") def setTileNBT(context: Context, args: Arguments): Array[AnyRef] = { checkEnabled() val (x, y, z) = (args.checkInteger(0), args.checkInteger(1), args.checkInteger(2)) - val nbt = args.checkTable(3).asInstanceOf[java.util.Map[String,Any]] - val tileEntity = world.getTileEntity(x, y, z) - if (tileEntity != null) { - val tag = getTag(nbt) - tileEntity.readFromNBT(tag) - return result(true) + world.getTileEntity(x, y, z) match { + case tileEntity: TileEntity => + typedMapToNbt(mapAsScalaMap(args.checkTable(3)).toMap) match { + case nbt: NBTTagCompound => + tileEntity.readFromNBT(nbt) + tileEntity.markDirty() + world.markBlockForUpdate(x, y, z) + result(true) + case nbt => result(null, s"nbt tag compound expected, got '${NBTBase.NBTTypes(nbt.getId)}'") + } + case _ => result(null, "no tile entity") } - result(null) } @Callback(doc = """function(x:number, y:number, z:number):number -- Get the light opacity of the block at the specified coordinates.""") diff --git a/src/main/scala/li/cil/oc/util/ExtendedNBT.scala b/src/main/scala/li/cil/oc/util/ExtendedNBT.scala index f1284a10d..0df8ac050 100644 --- a/src/main/scala/li/cil/oc/util/ExtendedNBT.scala +++ b/src/main/scala/li/cil/oc/util/ExtendedNBT.scala @@ -1,10 +1,13 @@ package li.cil.oc.util +import com.google.common.base.Charsets import net.minecraft.item.ItemStack import net.minecraft.nbt._ +import net.minecraftforge.common.util.Constants.NBT import net.minecraftforge.common.util.ForgeDirection -import scala.collection.mutable.ArrayBuffer +import scala.collection.convert.WrapAsScala._ +import scala.collection.mutable import scala.language.implicitConversions import scala.language.reflectiveCalls import scala.reflect.ClassTag @@ -66,6 +69,99 @@ object ExtendedNBT { 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) @@ -90,10 +186,31 @@ object ExtendedNBT { 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() @@ -158,7 +275,7 @@ object ExtendedNBT { def map[Tag <: NBTBase, Value](f: Tag => Value): IndexedSeq[Value] = { val iterable = nbt.copy.asInstanceOf[NBTTagList] - val buffer = ArrayBuffer.empty[Value] + val buffer = mutable.ArrayBuffer.empty[Value] while (iterable.tagCount > 0) { buffer += f(iterable.removeTag(0).asInstanceOf[Tag]) }