using System.Collections.Generic; namespace LibAC.NBT.API; using System; using System.Text; public abstract class Tag { private TagType _parentTagType = TagType.End; private Tag _parentTag; private string _key; public byte GetType() { return (byte)GetTagType(); } public abstract TagType GetTagType(); public bool HasParent => _parentTag != null; public void UpdateParent(Tag tag) { if (tag == null) { _parentTag = null; SetParentTagType(TagType.End); } else { _parentTag = tag; SetParentTagType(tag.GetTagType()); } } public Tag GetParent => _parentTag; public TagType ParentTagType => _parentTagType; public void SetParentTagType(TagType type) { _parentTagType = type; } public abstract void WriteValue(ByteLayer data); public abstract void ReadValue(ByteLayer data); public string GetKey() { return _key ?? string.Empty; } public void SetKey(string key) { _key = key; } public abstract dynamic GetValue(); public abstract void SetValue(dynamic val); public static Tag ReadNamedTag(ByteLayer data) { var type = data.ReadByte(); if (type == 0) { return new EndTag(); } else { Tag tag = MakeTagOfType(TagTypeExtensions.GetTagTypeFromByte(type)); tag._key = data.ReadString(); tag.ReadValue(data); return tag; } } public static void WriteNamedTag(Tag tag, ByteLayer data) { data.WriteByte(tag.GetType()); if (tag.GetType() != 0) { data.WriteString(tag.GetKey()); tag.WriteValue(data); } } public static void WriteStringifiedNamedTag(Tag tag, StringBuilder builder, int indents) { if (tag.GetType() != 0) { if (builder.Length > 0) { if (string.IsNullOrEmpty(tag.GetKey())) { builder.Append(new string('\t', indents)); } else { if (tag.ShouldQuoteName()) { builder.Append(new string('\t', indents) + $"\"{tag.GetKey()}\": "); } else { builder.Append(new string('\t', indents) + $"{tag.GetKey()}: "); } } } tag.WriteStringifiedValue(builder, indents + 1, false); } } public static Tag ReadStringifiedNamedTag(StringReader stringReader) { string name = stringReader.Peek() == '{' || stringReader.Peek() == '[' ? "" : stringReader.ReadString(); if (!string.IsNullOrEmpty(name)) stringReader.Expect(':'); TagType type = TagTypeExtensions.GetStringifiedTagType(stringReader); Tag tag = MakeTagOfType(type); tag._key = name; tag.ReadStringifiedValue(stringReader); return tag; } public bool ShouldQuoteName() { if (string.IsNullOrEmpty(GetKey())) { return false; } else { string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; foreach (char c in GetKey()) { if (!letters.Contains(c.ToString())) return true; } return false; } } public bool ShouldQuote(string value) { if (string.IsNullOrEmpty(value)) return true; string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; foreach (char c in value) { if (!letters.Contains(c.ToString())) return true; } return false; } public abstract void WriteStringifiedValue(StringBuilder builder, int indent, bool isList); public abstract void ReadStringifiedValue(StringReader reader); public bool Equals(object obj) { if (obj == null || !(obj is Tag)) return false; var tag = (Tag)obj; if (tag.GetType() != GetType()) return false; if (GetKey() != tag.GetKey()) return false; return true; } public static Tag MakeTagOfType(TagType type) { switch (type) { case TagType.Byte: return new ByteTag(); case TagType.ByteArray: return new ByteArrayTag(); case TagType.Compound: return new CompoundTag(); case TagType.Double: return new DoubleTag(); case TagType.End: return new EndTag(); case TagType.Short: return new ShortTag(); case TagType.Int: return new IntTag(); case TagType.Long: return new LongTag(); case TagType.Float: return new FloatTag(); case TagType.IntArray: return new IntArrayTag(); case TagType.LongArray: return new LongArrayTag(); case TagType.List: return new ListTag(); case TagType.String: return new StringTag(); default: throw new ArgumentOutOfRangeException(); } } public int AsByte() { if (this is ByteTag byteTag) return byteTag.Value; return 0; } public List AsByteArray() { if (this is ByteArrayTag byteArrayTag) return byteArrayTag.Value; return new List(); } public double AsDouble() { if (this is DoubleTag doubleTag) return doubleTag.Value; return 0.0; } public double AsFloat() { if (this is FloatTag floatTag) return floatTag.value; return 0.0; } public List AsIntArray() { if (this is IntArrayTag intArrayTag) return intArrayTag.Value; return new List(); } public int AsInt() { if (this is IntTag intTag) return intTag.Value; return 0; } public List AsLongArray() { if (this is LongArrayTag longArrayTag) return longArrayTag.value; return new List(); } public long AsLong() { if (this is LongTag longTag) return longTag.value; return 0; } public short AsShort() { if (this is ShortTag shortTag) return shortTag.value; return 0; } public string AsString() { if (this is StringTag stringTag) return stringTag.value; return string.Empty; } public CompoundTag AsCompoundTag() { return this is CompoundTag compoundTag ? compoundTag : new CompoundTag(); } public abstract void PrettyPrint(int indent, bool recurse); public static string GetCanonicalName(TagType type) { return type switch { TagType.String => "TAG_String", TagType.List => "TAG_List", TagType.LongArray => "TAG_LongArray", TagType.Long => "TAG_Long", TagType.IntArray => "TAG_IntArray", TagType.Int => "TAG_Int", TagType.Compound => "TAG_Compound", TagType.ByteArray => "TAG_ByteArray", TagType.Byte => "TAG_Byte", TagType.Double => "TAG_Double", TagType.Float => "TAG_Float", TagType.Short => "TAG_Short", TagType.End => "TAG_End", _ => throw new ArgumentOutOfRangeException() }; } }