PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
Loading...
Searching...
No Matches
CompoundTag.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20*/
21
22declare(strict_types=1);
23
24namespace pocketmine\nbt\tag;
25
33use function count;
34use function func_num_args;
35use function get_class;
36use function is_int;
37use function sprintf;
38use function str_repeat;
39use function strlen;
40use function strval;
41
45final class CompoundTag extends Tag implements \Countable, \IteratorAggregate{
46 use NoDynamicFieldsTrait;
47
49 private $value = [];
50
51 public function __construct(){
52 self::restrictArgCount(__METHOD__, func_num_args(), 0);
53 }
54
58 public static function create() : self{
59 return new self;
60 }
61
62 public function count() : int{
63 return count($this->value);
64 }
65
69 public function getCount(){
70 return count($this->value);
71 }
72
76 public function getValue(){
77 return $this->value;
78 }
79
80 /*
81 * Here follows many functions of misery for the sake of type safety. We really needs generics in PHP :(
82 */
83
87 public function getTag(string $name) : ?Tag{
88 return $this->value[$name] ?? null;
89 }
90
101 public function getListTag(string $name, string $tagClass = Tag::class) : ?ListTag{
102 $tag = $this->getTag($name);
103 if($tag !== null){
104 if(!$tag instanceof ListTag){
105 throw new UnexpectedTagTypeException("Expected a tag of type " . ListTag::class . ", got " . get_class($tag));
106 }
107 $casted = $tag->cast($tagClass);
108 if($casted === null){
109 throw new UnexpectedTagTypeException("Unable to cast list to ListTag<$tagClass>");
110 }
111 return $casted;
112 }
113 return null;
114 }
115
120 public function getCompoundTag(string $name) : ?CompoundTag{
121 $tag = $this->getTag($name);
122 if($tag !== null && !($tag instanceof CompoundTag)){
123 throw new UnexpectedTagTypeException("Expected a tag of type " . CompoundTag::class . ", got " . get_class($tag));
124 }
125 return $tag;
126 }
127
133 public function setTag(string $name, Tag $tag) : self{
134 if(strlen($name) > Limits::INT16_MAX){
135 throw new \InvalidArgumentException(sprintf("Tag name must be at most %d bytes, but got %d bytes", Limits::INT16_MAX, strlen($name)));
136 }
137 $this->value[$name] = $tag;
138 return $this;
139 }
140
145 public function removeTag(string ...$names) : void{
146 foreach($names as $name){
147 unset($this->value[$name]);
148 }
149 }
150
165 private function getTagValue(string $name, string $expectedClass, $default = null){
166 $tag = $this->getTag($name);
167 if($tag instanceof $expectedClass){
168 return $tag->getValue();
169 }
170 if($tag !== null){
171 throw new UnexpectedTagTypeException("Expected a tag of type $expectedClass, got " . get_class($tag));
172 }
173
174 if($default === null){
175 throw new NoSuchTagException("Tag \"$name\" does not exist");
176 }
177
178 return $default;
179 }
180
181 /*
182 * The following methods are wrappers around getTagValue() with type safety.
183 */
184
185 public function getByte(string $name, ?int $default = null) : int{
186 return $this->getTagValue($name, ByteTag::class, $default);
187 }
188
189 public function getShort(string $name, ?int $default = null) : int{
190 return $this->getTagValue($name, ShortTag::class, $default);
191 }
192
193 public function getInt(string $name, ?int $default = null) : int{
194 return $this->getTagValue($name, IntTag::class, $default);
195 }
196
197 public function getLong(string $name, ?int $default = null) : int{
198 return $this->getTagValue($name, LongTag::class, $default);
199 }
200
201 public function getFloat(string $name, ?float $default = null) : float{
202 return $this->getTagValue($name, FloatTag::class, $default);
203 }
204
205 public function getDouble(string $name, ?float $default = null) : float{
206 return $this->getTagValue($name, DoubleTag::class, $default);
207 }
208
209 public function getByteArray(string $name, ?string $default = null) : string{
210 return $this->getTagValue($name, ByteArrayTag::class, $default);
211 }
212
213 public function getString(string $name, ?string $default = null) : string{
214 return $this->getTagValue($name, StringTag::class, $default);
215 }
216
222 public function getIntArray(string $name, ?array $default = null) : array{
223 return $this->getTagValue($name, IntArrayTag::class, $default);
224 }
225
226 /*
227 * The following methods are wrappers around setTag() which create appropriate tag objects on the fly.
228 */
229
233 public function setByte(string $name, int $value) : self{
234 return $this->setTag($name, new ByteTag($value));
235 }
236
240 public function setShort(string $name, int $value) : self{
241 return $this->setTag($name, new ShortTag($value));
242 }
243
247 public function setInt(string $name, int $value) : self{
248 return $this->setTag($name, new IntTag($value));
249 }
250
254 public function setLong(string $name, int $value) : self{
255 return $this->setTag($name, new LongTag($value));
256 }
257
261 public function setFloat(string $name, float $value) : self{
262 return $this->setTag($name, new FloatTag($value));
263 }
264
268 public function setDouble(string $name, float $value) : self{
269 return $this->setTag($name, new DoubleTag($value));
270 }
271
275 public function setByteArray(string $name, string $value) : self{
276 return $this->setTag($name, new ByteArrayTag($value));
277 }
278
282 public function setString(string $name, string $value) : self{
283 return $this->setTag($name, new StringTag($value));
284 }
285
292 public function setIntArray(string $name, array $value) : self{
293 return $this->setTag($name, new IntArrayTag($value));
294 }
295
296 protected function getTypeName() : string{
297 return "Compound";
298 }
299
300 public function getType() : int{
301 return NBT::TAG_Compound;
302 }
303
304 public static function read(NbtStreamReader $reader, ReaderTracker $tracker) : self{
305 $result = new self;
306 $tracker->protectDepth(static function() use($reader, $tracker, $result) : void{
307 for($type = $reader->readByte(); $type !== NBT::TAG_End; $type = $reader->readByte()){
308 $name = $reader->readString();
309 $tag = NBT::createTag($type, $reader, $tracker);
310 if($result->getTag($name) !== null){
311 //this is technically a corruption case, but it's very common on older PM worlds (pretty much every
312 //furnace in PM worlds prior to 2017 is affected), and since we can't extricate this borked data
313 //from the rest in Anvil/McRegion worlds, we can't barf on this - it would result in complete loss
314 //of the chunk.
315 //TODO: add a flag to enable throwing on this (strict mode)
316 continue;
317 }
318 $result->setTag($name, $tag);
319 }
320 });
321 return $result;
322 }
323
324 public function write(NbtStreamWriter $writer) : void{
325 foreach($this->value as $name => $tag){
326 if(is_int($name)){
327 //PHP sucks
328 //we only cast on seeing an int, because forcibly casting other types might conceal bugs.
329 $name = (string) $name;
330 }
331 $writer->writeByte($tag->getType());
332 $writer->writeString($name);
333 $tag->write($writer);
334 }
335 $writer->writeByte(NBT::TAG_End);
336 }
337
338 protected function stringifyValue(int $indentation) : string{
339 $str = "{\n";
340 foreach($this->value as $name => $tag){
341 $str .= str_repeat(" ", $indentation + 1) . "\"$name\" => " . $tag->toString($indentation + 1) . "\n";
342 }
343 return $str . str_repeat(" ", $indentation) . "}";
344 }
345
346 public function __clone(){
347 foreach($this->value as $key => $tag){
348 $this->value[$key] = $tag->safeClone();
349 }
350 }
351
352 protected function makeCopy(){
353 return clone $this;
354 }
355
360 public function getIterator() : \Generator{
361 foreach($this->value as $name => $tag){
362 // PHP arrays are idiotic and cast keys like "1" to int(1)
363 // this also stops us using "yield from". REEEEEEEEEE
364 yield strval($name) => $tag;
365 }
366 }
367
368 public function equals(Tag $that) : bool{
369 if(!($that instanceof $this) or count($this->value) !== count($that->value)){
370 return false;
371 }
372
373 foreach($this->value as $k => $v){
374 $other = $that->value[$k] ?? null;
375 if($other === null or !$v->equals($other)){
376 return false;
377 }
378 }
379
380 return true;
381 }
382
389 public function merge(CompoundTag $other) : CompoundTag{
390 $new = clone $this;
391
392 foreach($other as $k => $namedTag){
393 $new->setTag($k, clone $namedTag);
394 }
395
396 return $new;
397 }
398}
setString(string $name, string $value)
setByte(string $name, int $value)
setIntArray(string $name, array $value)
setInt(string $name, int $value)
getIntArray(string $name, ?array $default=null)
setLong(string $name, int $value)
setTag(string $name, Tag $tag)
getListTag(string $name, string $tagClass=Tag::class)
setDouble(string $name, float $value)
setFloat(string $name, float $value)
setShort(string $name, int $value)
setByteArray(string $name, string $value)