PocketMine-MP 5.19.1 git-f1b1a7022d7dc67d012d8891bc4c23c2652c825e
BlockStateReader.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\data\bedrock\block\convert;
25
26use pocketmine\block\utils\BellAttachmentType;
27use pocketmine\block\utils\SlabType;
28use pocketmine\block\utils\WallConnectionType;
40use function array_keys;
41use function count;
42use function get_class;
43use function implode;
44
45final class BlockStateReader{
46
51 private array $unusedStates;
52
53 public function __construct(
54 private BlockStateData $data
55 ){
56 $this->unusedStates = $this->data->getStates();
57 }
58
59 public function missingOrWrongTypeException(string $name, ?Tag $tag) : BlockStateDeserializeException{
60 return new BlockStateDeserializeException("Property \"$name\" " . ($tag !== null ? "has unexpected type " . get_class($tag) : "is missing"));
61 }
62
63 public function badValueException(string $name, string $stringifiedValue, ?string $reason = null) : BlockStateDeserializeException{
65 "Property \"$name\" has unexpected value \"$stringifiedValue\"" . (
66 $reason !== null ? " ($reason)" : ""
67 ));
68 }
69
71 public function readBool(string $name) : bool{
72 unset($this->unusedStates[$name]);
73 $tag = $this->data->getState($name);
74 if($tag instanceof ByteTag){
75 switch($tag->getValue()){
76 case 0: return false;
77 case 1: return true;
78 default: throw $this->badValueException($name, (string) $tag->getValue());
79 }
80 }
81 throw $this->missingOrWrongTypeException($name, $tag);
82 }
83
85 public function readInt(string $name) : int{
86 unset($this->unusedStates[$name]);
87 $tag = $this->data->getState($name);
88 if($tag instanceof IntTag){
89 return $tag->getValue();
90 }
91 throw $this->missingOrWrongTypeException($name, $tag);
92 }
93
95 public function readBoundedInt(string $name, int $min, int $max) : int{
96 $result = $this->readInt($name);
97 if($result < $min || $result > $max){
98 throw $this->badValueException($name, (string) $result, "Must be inside the range $min ... $max");
99 }
100 return $result;
101 }
102
104 public function readString(string $name) : string{
105 unset($this->unusedStates[$name]);
106 //TODO: only allow a specific set of values (strings are primarily used for enums)
107 $tag = $this->data->getState($name);
108 if($tag instanceof StringTag){
109 return $tag->getValue();
110 }
111 throw $this->missingOrWrongTypeException($name, $tag);
112 }
113
120 private function parseFacingValue(int $value, array $mapping) : int{
121 $result = $mapping[$value] ?? null;
122 if($result === null){
123 throw new BlockStateDeserializeException("Unmapped facing value " . $value);
124 }
125 return $result;
126 }
127
129 public function readFacingDirection() : int{
130 return $this->parseFacingValue($this->readInt(BlockStateNames::FACING_DIRECTION), [
131 0 => Facing::DOWN,
132 1 => Facing::UP,
133 2 => Facing::NORTH,
134 3 => Facing::SOUTH,
135 4 => Facing::WEST,
136 5 => Facing::EAST
137 ]);
138 }
139
141 public function readBlockFace() : int{
142 return match($raw = $this->readString(BlockStateNames::MC_BLOCK_FACE)){
143 StringValues::MC_BLOCK_FACE_DOWN => Facing::DOWN,
144 StringValues::MC_BLOCK_FACE_UP => Facing::UP,
145 StringValues::MC_BLOCK_FACE_NORTH => Facing::NORTH,
146 StringValues::MC_BLOCK_FACE_SOUTH => Facing::SOUTH,
147 StringValues::MC_BLOCK_FACE_WEST => Facing::WEST,
148 StringValues::MC_BLOCK_FACE_EAST => Facing::EAST,
149 default => throw $this->badValueException(BlockStateNames::MC_BLOCK_FACE, $raw)
150 };
151 }
152
157 public function readFacingFlags() : array{
158 $result = [];
159 $flags = $this->readBoundedInt(BlockStateNames::MULTI_FACE_DIRECTION_BITS, 0, 63);
160 foreach([
161 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN => Facing::DOWN,
162 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_UP => Facing::UP,
163 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_NORTH => Facing::NORTH,
164 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_SOUTH => Facing::SOUTH,
165 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST => Facing::WEST,
166 BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST => Facing::EAST
167 ] as $flag => $facing){
168 if(($flags & $flag) !== 0){
169 $result[$facing] = $facing;
170 }
171 }
172
173 return $result;
174 }
175
177 public function readEndRodFacingDirection() : int{
178 $result = $this->readFacingDirection();
179 return Facing::axis($result) !== Axis::Y ? Facing::opposite($result) : $result;
180 }
181
183 public function readHorizontalFacing() : int{
184 return $this->parseFacingValue($this->readInt(BlockStateNames::FACING_DIRECTION), [
185 0 => Facing::NORTH, //should be illegal, but 1.13 allows it
186 1 => Facing::NORTH, //also should be illegal
187 2 => Facing::NORTH,
188 3 => Facing::SOUTH,
189 4 => Facing::WEST,
190 5 => Facing::EAST
191 ]);
192 }
193
195 public function readWeirdoHorizontalFacing() : int{
196 return $this->parseFacingValue($this->readInt(BlockStateNames::WEIRDO_DIRECTION), [
197 0 => Facing::EAST,
198 1 => Facing::WEST,
199 2 => Facing::SOUTH,
200 3 => Facing::NORTH
201 ]);
202 }
203
205 public function readLegacyHorizontalFacing() : int{
206 return $this->parseFacingValue($this->readInt(BlockStateNames::DIRECTION), [
207 0 => Facing::SOUTH,
208 1 => Facing::WEST,
209 2 => Facing::NORTH,
210 3 => Facing::EAST
211 ]);
212 }
213
218 public function read5MinusHorizontalFacing() : int{
219 return $this->parseFacingValue($this->readInt(BlockStateNames::DIRECTION), [
220 0 => Facing::EAST,
221 1 => Facing::WEST,
222 2 => Facing::SOUTH,
223 3 => Facing::NORTH
224 ]);
225 }
226
231 public function readCardinalHorizontalFacing() : int{
232 return match($raw = $this->readString(BlockStateNames::MC_CARDINAL_DIRECTION)){
233 StringValues::MC_CARDINAL_DIRECTION_NORTH => Facing::NORTH,
234 StringValues::MC_CARDINAL_DIRECTION_SOUTH => Facing::SOUTH,
235 StringValues::MC_CARDINAL_DIRECTION_WEST => Facing::WEST,
236 StringValues::MC_CARDINAL_DIRECTION_EAST => Facing::EAST,
237 default => throw $this->badValueException(BlockStateNames::MC_CARDINAL_DIRECTION, $raw)
238 };
239 }
240
242 public function readCoralFacing() : int{
243 return $this->parseFacingValue($this->readInt(BlockStateNames::CORAL_DIRECTION), [
244 0 => Facing::WEST,
245 1 => Facing::EAST,
246 2 => Facing::NORTH,
247 3 => Facing::SOUTH
248 ]);
249 }
250
252 public function readFacingWithoutDown() : int{
253 $result = $this->readFacingDirection();
254 if($result === Facing::DOWN){ //shouldn't be legal, but 1.13 allows it
255 $result = Facing::UP;
256 }
257 return $result;
258 }
259
260 public function readFacingWithoutUp() : int{
261 $result = $this->readFacingDirection();
262 if($result === Facing::UP){
263 $result = Facing::DOWN; //shouldn't be legal, but 1.13 allows it
264 }
265 return $result;
266 }
267
272 public function readPillarAxis() : int{
273 $rawValue = $this->readString(BlockStateNames::PILLAR_AXIS);
274 $value = [
275 StringValues::PILLAR_AXIS_X => Axis::X,
276 StringValues::PILLAR_AXIS_Y => Axis::Y,
277 StringValues::PILLAR_AXIS_Z => Axis::Z
278 ][$rawValue] ?? null;
279 if($value === null){
280 throw $this->badValueException(BlockStateNames::PILLAR_AXIS, $rawValue, "Invalid axis value");
281 }
282 return $value;
283 }
284
286 public function readSlabPosition() : SlabType{
287 return match($rawValue = $this->readString(BlockStateNames::MC_VERTICAL_HALF)){
288 StringValues::MC_VERTICAL_HALF_BOTTOM => SlabType::BOTTOM,
289 StringValues::MC_VERTICAL_HALF_TOP => SlabType::TOP,
290 default => throw $this->badValueException(BlockStateNames::MC_VERTICAL_HALF, $rawValue, "Invalid slab position"),
291 };
292 }
293
298 public function readTorchFacing() : int{
299 //TODO: horizontal directions are flipped (MCPE bug: https://bugs.mojang.com/browse/MCPE-152036)
300 return match($rawValue = $this->readString(BlockStateNames::TORCH_FACING_DIRECTION)){
301 StringValues::TORCH_FACING_DIRECTION_EAST => Facing::WEST,
302 StringValues::TORCH_FACING_DIRECTION_NORTH => Facing::SOUTH,
303 StringValues::TORCH_FACING_DIRECTION_SOUTH => Facing::NORTH,
304 StringValues::TORCH_FACING_DIRECTION_TOP => Facing::UP,
305 StringValues::TORCH_FACING_DIRECTION_UNKNOWN => Facing::UP, //should be illegal, but 1.13 allows it
306 StringValues::TORCH_FACING_DIRECTION_WEST => Facing::EAST,
307 default => throw $this->badValueException(BlockStateNames::TORCH_FACING_DIRECTION, $rawValue, "Invalid torch facing"),
308 };
309 }
310
312 public function readBellAttachmentType() : BellAttachmentType{
313 return match($type = $this->readString(BlockStateNames::ATTACHMENT)){
314 StringValues::ATTACHMENT_HANGING => BellAttachmentType::CEILING,
315 StringValues::ATTACHMENT_STANDING => BellAttachmentType::FLOOR,
316 StringValues::ATTACHMENT_SIDE => BellAttachmentType::ONE_WALL,
317 StringValues::ATTACHMENT_MULTIPLE => BellAttachmentType::TWO_WALLS,
318 default => throw $this->badValueException(BlockStateNames::ATTACHMENT, $type),
319 };
320 }
321
323 public function readWallConnectionType(string $name) : ?WallConnectionType{
324 return match($type = $this->readString($name)){
325 //TODO: this looks a bit confusing due to use of EAST, but the values are the same for all connections
326 //we need to find a better way to auto-generate the constant names when they are reused
327 //for now, using these constants is better than nothing since it still gives static analysability
328 StringValues::WALL_CONNECTION_TYPE_EAST_NONE => null,
329 StringValues::WALL_CONNECTION_TYPE_EAST_SHORT => WallConnectionType::SHORT,
330 StringValues::WALL_CONNECTION_TYPE_EAST_TALL => WallConnectionType::TALL,
331 default => throw $this->badValueException($name, $type),
332 };
333 }
334
338 public function ignored(string $name) : void{
339 if($this->data->getState($name) !== null){
340 unset($this->unusedStates[$name]);
341 }else{
342 throw $this->missingOrWrongTypeException($name, null);
343 }
344 }
345
349 public function todo(string $name) : void{
350 $this->ignored($name);
351 }
352
356 public function checkUnreadProperties() : void{
357 if(count($this->unusedStates) > 0){
358 throw new BlockStateDeserializeException("Unread properties: " . implode(", ", array_keys($this->unusedStates)));
359 }
360 }
361}