1 module nbtd.NBTCompound; 2 3 import nbtd; 4 5 import std.zlib; 6 import std.bitmanip; 7 import std.format; 8 9 /// NBT Compound containing a array of named items. 10 class NBTCompound : INBTItem 11 { 12 private: 13 string _name; 14 INBTItem[] _value; 15 public: 16 this(INBTItem[] items = [], string name = "") 17 { 18 _name = name; 19 _value = items; 20 } 21 22 @property NBTType type() { return NBTType.Compound; } 23 @property int size() 24 { 25 int size = 1; // incl. EndTag 26 foreach(INBTItem item; _value) 27 size += item.size + 3 + item.name.length; 28 return size; 29 } 30 31 @property string name() { return _name; } 32 @property void name(string name) { assert(name.length < short.max, "Name is too long! (%s)".format(name.length)); _name = name; } 33 34 @property INBTItem[] value() { return _value; } 35 @property void value(INBTItem[] value) { _value = value; } 36 37 /// Returns: the item with the name `name`. 38 /// Params: 39 /// name = Name to search for. 40 /// throwOnError = When false, returns null instead of throwing an exception. 41 /// Throws: Exception when unable to cast to the target type. 42 /// Throws: Exception when unable to find `name` in the array. 43 T get(T : INBTItem = INBTItem)(string name, bool throwOnError = true) 44 { 45 foreach(INBTItem item; _value) 46 if(item.name == name) 47 { 48 if(cast(T)item) 49 return cast(T)item; 50 if(throwOnError) 51 throw new Exception("Can't cast item to " ~ T.stringof); 52 return null; 53 } 54 if(throwOnError) 55 throw new Exception("Item " ~ name ~ " not found in Compound!"); 56 return null; 57 } 58 59 ubyte[] encode(bool compressed = true, bool hasName = true) 60 { 61 ubyte[] data; 62 if(hasName) 63 data = new ubyte[size + 3 + _name.length]; 64 else 65 data = new ubyte[size]; 66 67 if(hasName) 68 { 69 data[0] = cast(ubyte)type; 70 data.write(cast(short)_name.length, 1); 71 data[3 .. 3 + _name.length] = cast(ubyte[])_name; 72 int index = 3 + name.length; 73 for(int i = 0; i < _value.length; i++) 74 { 75 ubyte[] buffer = _value[i].encode(false); 76 data[index .. index + buffer.length] = buffer; 77 index += buffer.length; 78 } 79 data[index] = cast(ubyte)0; 80 assert(index == data.length - 1); 81 } 82 else 83 { 84 int index = 0; 85 for(int i = 0; i < _value.length; i++) 86 { 87 ubyte[] buffer = _value[i].encode(false); 88 data[index .. index + buffer.length] = buffer; 89 index += buffer.length; 90 } 91 data[index] = cast(ubyte)0; 92 assert(index == data.length - 1); 93 } 94 95 if(compressed) 96 { 97 return compressGZip(data); 98 } 99 return data; 100 } 101 102 void decode(ubyte[] data, bool compressed = true, bool hasName = true) 103 { 104 if(compressed) 105 { 106 data = uncompressGZip(data); 107 } 108 109 read(data, hasName); 110 } 111 112 void read(ref ubyte[] stream, bool hasName = true) 113 { 114 _name = ""; 115 if(hasName) 116 { 117 assert(stream.read!ubyte == cast(ubyte)type); 118 short nameLength = stream.read!short; 119 _name = cast(string)stream[0 .. nameLength]; 120 stream = stream[nameLength .. $]; 121 } 122 INBTItem item; 123 _value.length = 0; 124 while((item = parseElement(stream)).type != NBTType.End) 125 _value ~= item; 126 } 127 128 @property INBTItem dup() 129 { 130 auto copy = new NBTCompound(); 131 copy.name = name; 132 copy.value = value; 133 return copy; 134 } 135 136 /// Returns: `"NBTCompound('name') = {[each ChildElement.toString()]}"` 137 override string toString() 138 { 139 return format("NBTCompound('%s') = {%s}", name, value); 140 } 141 142 /// See_Also: get 143 INBTItem opIndex(string index) 144 { 145 return get(index); 146 } 147 148 /// Sets the value at `index` to a duplicate of item and will rename the item name to `index`. 149 INBTItem opIndexAssign(INBTItem item, string index) 150 { 151 int found = -1; 152 item = item.dup; 153 item.name = index; 154 foreach(int i, INBTItem val; _value) 155 if(val.name == index) 156 found = i; 157 if(found == -1) 158 { 159 _value ~= item; 160 } 161 else 162 { 163 _value[found] = item; 164 } 165 return item; 166 } 167 }