├── .gitignore ├── .poggit.yml ├── README.md ├── src └── BlockHorizons │ └── libschematic │ └── Schematic.php └── virion.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/BlockHorizons/libschematic 2 | branches: 3 | - master 4 | projects: 5 | libschematic: 6 | path: "" 7 | model: virion 8 | type: library 9 | ... 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libschematic 2 | A library for creating and manipulating MCEdit Schematic files. 3 | 4 | ### Implementing into plugins 5 | Best way to implement this code is to use it as a git module or Poggit virion. 6 | 7 | ### Usage 8 | 9 | #### Loading schematic files 10 | 11 | ```php 12 | try { 13 | $schematic = new Schematic(); 14 | $schematic->parse("castle.schematic"); 15 | } catch (\Throwable $error) { 16 | // Handle error 17 | } 18 | ``` 19 | 20 | #### Filling schematics 21 | ```php 22 | $schematic = new Schematic(); 23 | $boundingBox = new AxisAlignedBB(); 24 | 25 | // For generator block providers, a bounding box is required as the size is unknown in advance. 26 | $schematic->setBlocks($boundingBox, $blockGenerator); 27 | 28 | $blocks = []; 29 | 30 | // For array block providers, the bounding box is calculated automatically. 31 | $schematic->setBlockArray($blocks); 32 | ``` 33 | 34 | #### Saving schematic files 35 | 36 | ```php 37 | try { 38 | $schematic = new Schematic(); 39 | $schematic->save("castle.schematic"); 40 | } catch (\Throwable $error) { 41 | // Handle error 42 | } 43 | ``` 44 | 45 | #### Pasting schematics 46 | 47 | ```php 48 | $target = $player->getPosition(); 49 | foreach($schematic->blocks() as $block) { 50 | $target->level->setBlock($target->add($block), $block); 51 | } 52 | ``` 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/BlockHorizons/libschematic/Schematic.php: -------------------------------------------------------------------------------- 1 | setByteArray("Blocks", $this->blocks) 74 | ->setByteArray("Data", $this->data) 75 | ->setShort("Length", $this->length) 76 | ->setShort("Width", $this->width) 77 | ->setShort("Height", $this->height) 78 | ->setString("Materials", self::MATERIALS_POCKET) 79 | ); 80 | //NOTE: Save after encoding with zlib_encode for backward compatibility. 81 | file_put_contents($file, zlib_encode((new BigEndianNbtSerializer())->write($nbt), ZLIB_ENCODING_GZIP)); 82 | } 83 | 84 | /** 85 | * parse parses a schematic from the file passed. 86 | * 87 | * @param string $file 88 | */ 89 | public function parse(string $file) : void{ 90 | $nbt = (new BigEndianNbtSerializer())->read(zlib_decode(file_get_contents($file))); 91 | $nbt = $nbt->getTag(); 92 | 93 | $this->materials = $nbt->getString("Materials"); 94 | 95 | $this->height = $nbt->getShort("Height"); 96 | $this->width = $nbt->getShort("Width"); 97 | $this->length = $nbt->getShort("Length"); 98 | 99 | $this->blocks = $nbt->getByteArray("Blocks"); 100 | $this->data = $nbt->getByteArray("Data"); 101 | } 102 | 103 | /** 104 | * blocks returns a generator of blocks found in the schematic opened. 105 | * 106 | * @return \Generator 107 | */ 108 | public function blocks() : \Generator{ 109 | for($x = 0; $x < $this->width; $x++){ 110 | for($z = 0; $z < $this->length; $z++){ 111 | for($y = 0; $y < $this->height; $y++){ 112 | $index = $this->blockIndex($x, $y, $z); 113 | $id = isset($this->blocks[$index]) ? ord($this->blocks[$index]) & 0xff : 0; 114 | $data = isset($this->data[$index]) ? ord($this->data[$index]) & 0x0f : 0; 115 | $block = BlockFactory::getInstance()->get($id, $data); 116 | [$block->getPos()->x, $block->getPos()->y, $block->getPos()->z] = [$x, $y, $z]; 117 | if($this->materials !== self::MATERIALS_POCKET){ 118 | $block = $this->fixBlock($block); 119 | } 120 | yield $block; 121 | } 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * setBlocks sets a generator of blocks to a schematic, using a bounding box to calculate the size. 128 | * 129 | * @param $bb AxisAlignedBB 130 | * @param \Generator $blocks 131 | */ 132 | public function setBlocks(AxisAlignedBB $bb, \Generator $blocks) : void{ 133 | /** @var Block $block */ 134 | $offset = new Vector3((int) $bb->minX, (int) $bb->minY, (int) $bb->minZ); 135 | $max = new Vector3((int) $bb->maxX, (int) $bb->maxY, (int) $bb->maxZ); 136 | 137 | $this->width = $max->x - $offset->x + 1; 138 | $this->length = $max->z - $offset->z + 1; 139 | $this->height = $max->y - $offset->y + 1; 140 | 141 | foreach($blocks as $block){ 142 | $pos = $block->getPos()->subtractVector($offset); 143 | $index = $this->blockIndex($pos->x, $pos->y, $pos->z); 144 | if(strlen($this->blocks) <= $index){ 145 | $this->blocks .= str_repeat(chr(0), $index - strlen($this->blocks) + 1); 146 | } 147 | $this->blocks[$index] = chr($block->getId()); 148 | $this->data[$index] = chr($block->getMeta()); 149 | } 150 | } 151 | 152 | /** 153 | * setBlockArray sets a block array to a schematic. The bounds of the schematic are calculated manually. 154 | * 155 | * @param Block[] $blocks 156 | */ 157 | public function setBlockArray(array $blocks) : void{ 158 | $min = new Vector3(0, 0, 0); 159 | $max = new Vector3(0, 0, 0); 160 | foreach($blocks as $block){ 161 | if($block->getPos()->x < $min->x){ 162 | $min->x = $block->getPos()->x; 163 | }elseif($block->getPos()->x > $max->x){ 164 | $max->x = $block->getPos()->x; 165 | } 166 | if($block->getPos()->y < $min->y){ 167 | $min->y = $block->getPos()->y; 168 | }elseif($block->getPos()->y > $max->y){ 169 | $max->y = $block->getPos()->y; 170 | } 171 | if($block->getPos()->z < $min->z){ 172 | $min->z = $block->getPos()->z; 173 | }elseif($block->getPos()->z > $max->z){ 174 | $max->z = $block->getPos()->z; 175 | } 176 | } 177 | $this->height = $max->y - $min->y + 1; 178 | $this->width = $max->x - $min->x + 1; 179 | $this->length = $max->z - $min->z + 1; 180 | 181 | foreach($blocks as $block){ 182 | $pos = $block->getPos()->subtractVector($min); 183 | $index = $this->blockIndex($pos->x, $pos->y, $pos->z); 184 | if(strlen($this->blocks) <= $index){ 185 | $this->blocks .= str_repeat(chr(0), $index - strlen($this->blocks) + 1); 186 | } 187 | $this->blocks[$index] = chr($block->getId()); 188 | $this->data[$index] = chr($block->getMeta()); 189 | } 190 | } 191 | 192 | /** 193 | * fixBlock replaces a block that has a different block ID in Pocket Edition than in PC Edition. 194 | * 195 | * @param $block Block 196 | * 197 | * @return Block 198 | */ 199 | protected function fixBlock(Block $block) : Block{ 200 | /** @var Block $new */ 201 | $new = null; 202 | switch($block->getId()){ 203 | case 95: 204 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::STAINED_GLASS, $block->getMeta()); 205 | break; 206 | case 126: 207 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::WOODEN_SLAB, $block->getMeta()); 208 | break; 209 | case 125: 210 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::DOUBLE_WOODEN_SLAB, $block->getMeta()); 211 | break; 212 | case 188: 213 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::FENCE, 1); 214 | break; 215 | case 189: 216 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::FENCE, 2); 217 | break; 218 | case 190: 219 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::FENCE, 3); 220 | break; 221 | case 191: 222 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::FENCE, 5); 223 | break; 224 | case 192: 225 | $new = BlockFactory::getInstance()->get(BlockLegacyIds::FENCE, 4); 226 | break; 227 | default: 228 | return $block; 229 | } 230 | //$new->setComponents($block->x, $block->y, $block->z); 231 | [$new->getPos()->x, $new->getPos()->y, $new->getPos()->z] = [$block->getPos()->x, $block->getPos()->y, $block->getPos()->z]; 232 | 233 | return $new; 234 | } 235 | 236 | /** 237 | * @param int $x 238 | * @param int $y 239 | * @param int $z 240 | * 241 | * @return int 242 | */ 243 | protected function blockIndex(int $x, int $y, int $z) : int{ 244 | return ($y * $this->length + $z) * $this->width + $x; 245 | } 246 | 247 | /** 248 | * @return string 249 | */ 250 | public function getMaterials() : string{ 251 | return $this->materials; 252 | } 253 | 254 | /** 255 | * @param string $materials 256 | * 257 | * @return $this 258 | */ 259 | public function setMaterials(string $materials){ 260 | $this->materials = $materials; 261 | 262 | return $this; 263 | } 264 | 265 | /** 266 | * @return int 267 | */ 268 | public function getLength() : int{ 269 | return $this->length; 270 | } 271 | 272 | /** 273 | * @return int 274 | */ 275 | public function getHeight() : int{ 276 | return $this->height; 277 | } 278 | 279 | /** 280 | * @return int 281 | */ 282 | public function getWidth() : int{ 283 | return $this->width; 284 | } 285 | 286 | /** 287 | * @param int $length 288 | * 289 | * @return $this 290 | */ 291 | public function setLength(int $length){ 292 | $this->length = $length; 293 | 294 | return $this; 295 | } 296 | 297 | /** 298 | * @param int $height 299 | * 300 | * @return $this 301 | */ 302 | public function setHeight(int $height){ 303 | $this->height = $height; 304 | 305 | return $this; 306 | } 307 | 308 | /** 309 | * @param int $width 310 | * 311 | * @return $this 312 | */ 313 | public function setWidth(int $width){ 314 | $this->width = $width; 315 | 316 | return $this; 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /virion.yml: -------------------------------------------------------------------------------- 1 | name: libschematic 2 | version: 2.0.1 3 | antigen: BlockHorizons\libschematic 4 | api: 4.0.0 5 | php: [7.2, 8.0] 6 | author: BlockHorizons 7 | --------------------------------------------------------------------------------