PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
LevelFormat.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 
22 namespace pocketmine\level\conversor\pmf;
23 
30 
31 class LevelFormat extends PMF{
32  const VERSION = 2;
33  const ZLIB_LEVEL = 6;
34  const ZLIB_ENCODING = 15; //15 = zlib, -15 = raw deflate, 31 = gzip
35 
36  public $level;
37  public $levelData = [];
38  public $isLoaded = true;
39  private $chunks = [];
40  private $chunkChange = [];
41  private $chunkInfo = [];
42  public $isGenerating = 0;
43 
44  public function getData($index){
45  if(!isset($this->levelData[$index])){
46  return false;
47  }
48 
49  return ($this->levelData[$index]);
50  }
51 
52  public function setData($index, $data){
53  if(!isset($this->levelData[$index])){
54  return false;
55  }
56  $this->levelData[$index] = $data;
57 
58  return true;
59  }
60 
61  public function closeLevel(){
62  $this->chunks = null;
63  unset($this->chunks, $this->chunkChange, $this->chunkInfo, $this->level);
64  $this->close();
65  }
66 
71  public function __construct($file, $blank = false){
72  $this->chunks = [];
73  $this->chunkChange = [];
74  $this->chunkInfo = [];
75  if(is_array($blank)){
76  $this->create($file, 0);
77  $this->levelData = $blank;
78  $this->createBlank();
79  $this->isLoaded = true;
80  }else{
81  if($this->load($file) !== false){
82  $this->parseInfo();
83  if($this->parseLevel() === false){
84  $this->isLoaded = false;
85  }else{
86  $this->isLoaded = true;
87  }
88  }else{
89  $this->isLoaded = false;
90  }
91  }
92  }
93 
94  public function saveData(){
95  $this->levelData["version"] = self::VERSION;
96  @ftruncate($this->fp, 5);
97  $this->seek(5);
98  $this->write(chr($this->levelData["version"]));
99  $this->write(Binary::writeShort(strlen($this->levelData["name"])) . $this->levelData["name"]);
100  $this->write(Binary::writeInt($this->levelData["seed"]));
101  $this->write(Binary::writeInt($this->levelData["time"]));
102  $this->write(Binary::writeFloat($this->levelData["spawnX"]));
103  $this->write(Binary::writeFloat($this->levelData["spawnY"]));
104  $this->write(Binary::writeFloat($this->levelData["spawnZ"]));
105  $this->write(chr($this->levelData["height"]));
106  $this->write(Binary::writeShort(strlen($this->levelData["generator"])) . $this->levelData["generator"]);
107  $settings = serialize($this->levelData["generatorSettings"]);
108  $this->write(Binary::writeShort(strlen($settings)) . $settings);
109  $extra = zlib_encode($this->levelData["extra"], self::ZLIB_ENCODING, self::ZLIB_LEVEL);
110  $this->write(Binary::writeShort(strlen($extra)) . $extra);
111  }
112 
113  private function createBlank(){
114  $this->saveData();
115  @mkdir(dirname($this->file) . "/chunks/", 0755);
116  }
117 
118  protected function parseLevel(){
119  if($this->getType() !== 0x00){
120  return false;
121  }
122  $this->seek(5);
123  $this->levelData["version"] = ord($this->read(1));
124  if($this->levelData["version"] > self::VERSION){
125  MainLogger::getLogger()->error("New unsupported PMF Level format version #" . $this->levelData["version"] . ", current version is #" . self::VERSION);
126 
127  return false;
128  }
129  $this->levelData["name"] = $this->read(Binary::readShort($this->read(2)));
130  $this->levelData["seed"] = Binary::readInt($this->read(4));
131  $this->levelData["time"] = Binary::readInt($this->read(4));
132  $this->levelData["spawnX"] = Binary::readFloat($this->read(4));
133  $this->levelData["spawnY"] = Binary::readFloat($this->read(4));
134  $this->levelData["spawnZ"] = Binary::readFloat($this->read(4));
135  if($this->levelData["version"] === 0){
136  $this->read(1);
137  $this->levelData["height"] = ord($this->read(1));
138  }else{
139  $this->levelData["height"] = ord($this->read(1));
140  if($this->levelData["height"] !== 8){
141  return false;
142  }
143  $this->levelData["generator"] = $this->read(Binary::readShort($this->read(2)));
144  $this->levelData["generatorSettings"] = unserialize($this->read(Binary::readShort($this->read(2))));
145 
146  }
147  $this->levelData["extra"] = @zlib_decode($this->read(Binary::readShort($this->read(2))));
148 
149  $upgrade = false;
150  if($this->levelData["version"] === 0){
151  $this->upgrade_From0_To1();
152  $upgrade = true;
153  }
154  if($this->levelData["version"] === 1){
155  $this->upgrade_From1_To2();
156  $upgrade = true;
157  }
158 
159  if($upgrade === true){
160  $this->saveData();
161  }
162  }
163 
164  private function upgrade_From0_To1(){
165  MainLogger::getLogger()->notice("Old PMF Level format version #0 detected, upgrading to version #1");
166  for($index = 0; $index < 256; ++$index){
167  $X = $index & 0x0F;
168  $Z = $index >> 4;
169 
170  $bitflags = Binary::readShort($this->read(2));
171  $oldPath = dirname($this->file) . "/chunks/" . $Z . "." . $X . ".pmc";
172  $chunkOld = gzopen($oldPath, "rb");
173  $newPath = dirname($this->file) . "/chunks/" . (($X ^ $Z) & 0xff) . "/" . $Z . "." . $X . ".pmc";
174  @mkdir(dirname($newPath));
175  $chunkNew = gzopen($newPath, "wb1");
176  gzwrite($chunkNew, chr($bitflags) . "\x00\x00\x00\x01");
177  while(gzeof($chunkOld) === false){
178  gzwrite($chunkNew, gzread($chunkOld, 65535));
179  }
180  gzclose($chunkNew);
181  gzclose($chunkOld);
182  @unlink($oldPath);
183  }
184  $this->levelData["version"] = 1;
185  $this->levelData["generator"] = "default";
186  $this->levelData["generatorSettings"] = "";
187  }
188 
189  private function upgrade_From1_To2(){
190  MainLogger::getLogger()->notice("Old PMF Level format version #1 detected, upgrading to version #2");
191  $nbt = new Compound("", [
192  new Enum("Entities", []),
193  new Enum("TileEntities", [])
194  ]);
195  $nbt->Entities->setTagType(NBT::TAG_Compound);
196  $nbt->TileEntities->setTagType(NBT::TAG_Compound);
197  $nbtCodec = new NBT(NBT::BIG_ENDIAN);
198  $nbtCodec->setData($nbt);
199  $namedtag = $nbtCodec->write();
200  $namedtag = Binary::writeInt(strlen($namedtag)) . $namedtag;
201  foreach(glob(dirname($this->file) . "/chunks/*/*.*.pmc") as $chunkFile){
202  $oldChunk = zlib_decode(file_get_contents($chunkFile));
203  $newChunk = substr($oldChunk, 0, 5);
204  $newChunk .= $namedtag;
205  $newChunk .= str_repeat("\x01", 256); //Biome indexes (all Plains)
206  $newChunk .= substr($oldChunk, 5);
207  file_put_contents($chunkFile, zlib_encode($newChunk, self::ZLIB_ENCODING, self::ZLIB_LEVEL));
208  }
209  $this->levelData["version"] = 2;
210  }
211 
212  public static function getIndex($X, $Z){
213  return ($Z << 16) | ($X < 0 ? (~--$X & 0x7fff) | 0x8000 : $X & 0x7fff);
214  }
215 
216  public static function getXZ($index, &$X = null, &$Z = null){
217  $Z = $index >> 16;
218  $X = ($index & 0x8000) === 0x8000 ? -($index & 0x7fff) : $index & 0x7fff;
219 
220  return [$X, $Z];
221  }
222 
223  private function getChunkPath($X, $Z){
224  return dirname($this->file) . "/chunks/" . (((int) $X ^ (int) $Z) & 0xff) . "/" . $Z . "." . $X . ".pmc";
225  }
226 
227  public function generateChunk($X, $Z){
228  $path = $this->getChunkPath($X, $Z);
229  if(!file_exists(dirname($path))){
230  @mkdir(dirname($path), 0755);
231  }
232  $this->initCleanChunk($X, $Z);
233  if($this->level instanceof Level){
234  $ret = $this->level->generateChunk($X, $Z);
235  $this->saveChunk($X, $Z);
236  $this->populateChunk($X - 1, $Z);
237  $this->populateChunk($X + 1, $Z);
238  $this->populateChunk($X, $Z - 1);
239  $this->populateChunk($X, $Z + 1);
240  $this->populateChunk($X + 1, $Z + 1);
241  $this->populateChunk($X + 1, $Z - 1);
242  $this->populateChunk($X - 1, $Z - 1);
243  $this->populateChunk($X - 1, $Z + 1);
244 
245  return $ret;
246  }
247  }
248 
249  public function populateChunk($X, $Z){
250  if($this->level instanceof Level){
251  if($this->isGenerating === 0 and
252  $this->isChunkLoaded($X, $Z) and
253  !$this->isPopulated($X, $Z) and
254  $this->isGenerated($X - 1, $Z) and
255  $this->isGenerated($X, $Z - 1) and
256  $this->isGenerated($X + 1, $Z) and
257  $this->isGenerated($X, $Z + 1) and
258  $this->isGenerated($X + 1, $Z + 1) and
259  $this->isGenerated($X - 1, $Z - 1) and
260  $this->isGenerated($X + 1, $Z - 1) and
261  $this->isGenerated($X - 1, $Z + 1)
262  ){
263  $this->level->populateChunk($X, $Z);
264  $this->saveChunk($X, $Z);
265  }
266  }
267  }
268 
269  public function loadChunk($X, $Z){
270  if($this->isChunkLoaded($X, $Z)){
271  return true;
272  }
273  $index = self::getIndex($X, $Z);
274  $path = $this->getChunkPath($X, $Z);
275  if(!file_exists($path)){
276  if($this->generateChunk($X, $Z) === false){
277  return false;
278  }
279  if($this->isGenerating === 0){
280  $this->populateChunk($X, $Z);
281  }
282 
283  return true;
284  }
285 
286  $chunk = file_get_contents($path);
287  if($chunk === false){
288  return false;
289  }
290  $chunk = zlib_decode($chunk);
291  $offset = 0;
292 
293  $this->chunkInfo[$index] = [
294  0 => ord($chunk{0}),
295  1 => Binary::readInt(substr($chunk, 1, 4)),
296  ];
297  $offset += 5;
298  $len = Binary::readInt(substr($chunk, $offset, 4));
299  $offset += 4;
300  $nbt = new NBT(NBT::BIG_ENDIAN);
301  $nbt->read(substr($chunk, $offset, $len));
302  $this->chunkInfo[$index][2] = $nbt->getData();
303  $offset += $len;
304  $this->chunks[$index] = [];
305  $this->chunkChange[$index] = [-1 => false];
306  $this->chunkInfo[$index][3] = substr($chunk, $offset, 256); //Biome data
307  $offset += 256;
308  for($Y = 0; $Y < 8; ++$Y){
309  if(($this->chunkInfo[$index][0] & (1 << $Y)) !== 0){
310  // 4096 + 2048 + 2048, Block Data, Meta, Light
311  if(strlen($this->chunks[$index][$Y] = substr($chunk, $offset, 8192)) < 8192){
312  MainLogger::getLogger()->notice("Empty corrupt chunk detected [$X,$Z,:$Y], recovering contents");
313  $this->fillMiniChunk($X, $Z, $Y);
314  }
315  $offset += 8192;
316  }else{
317  $this->chunks[$index][$Y] = false;
318  }
319  }
320  if($this->isGenerating === 0 and !$this->isPopulated($X, $Z)){
321  $this->populateChunk($X, $Z);
322  }
323 
324  return true;
325  }
326 
327  public function unloadChunk($X, $Z, $save = true){
328  $X = (int) $X;
329  $Z = (int) $Z;
330  if(!$this->isChunkLoaded($X, $Z)){
331  return false;
332  }elseif($save !== false){
333  $this->saveChunk($X, $Z);
334  }
335  $index = self::getIndex($X, $Z);
336  $this->chunks[$index] = null;
337  $this->chunkChange[$index] = null;
338  $this->chunkInfo[$index] = null;
339  unset($this->chunks[$index], $this->chunkChange[$index], $this->chunkInfo[$index]);
340 
341  return true;
342  }
343 
344  public function isChunkLoaded($X, $Z){
345  $index = self::getIndex($X, $Z);
346  if(!isset($this->chunks[$index])){
347  return false;
348  }
349 
350  return true;
351  }
352 
353  protected function cleanChunk($X, $Z){
354  $index = self::getIndex($X, $Z);
355  if(isset($this->chunks[$index])){
356  for($Y = 0; $Y < 8; ++$Y){
357  if($this->chunks[$index][$Y] !== false and substr_count($this->chunks[$index][$Y], "\x00") === 8192){
358  $this->chunks[$index][$Y] = false;
359  $this->chunkInfo[$index][0] &= ~(1 << $Y);
360  }
361  }
362  }
363  }
364 
365  public function isMiniChunkEmpty($X, $Z, $Y){
366  $index = self::getIndex($X, $Z);
367  if(!isset($this->chunks[$index]) or $this->chunks[$index][$Y] === false){
368  return true;
369  }
370 
371  return false;
372  }
373 
374  protected function fillMiniChunk($X, $Z, $Y){
375  if($this->isChunkLoaded($X, $Z) === false){
376  return false;
377  }
378  $index = self::getIndex($X, $Z);
379  $this->chunks[$index][$Y] = str_repeat("\x00", 8192);
380  $this->chunkChange[$index][-1] = true;
381  $this->chunkChange[$index][$Y] = 8192;
382  $this->chunkInfo[$index][0] |= 1 << $Y;
383 
384  return true;
385  }
386 
387  public function getMiniChunk($X, $Z, $Y){
388  if($this->isChunkLoaded($X, $Z) === false and $this->loadChunk($X, $Z) === false){
389  return str_repeat("\x00", 8192);
390  }
391  $index = self::getIndex($X, $Z);
392  if(!isset($this->chunks[$index][$Y]) or $this->chunks[$index][$Y] === false){
393  return str_repeat("\x00", 8192);
394  }
395 
396  return $this->chunks[$index][$Y];
397  }
398 
399  public function initCleanChunk($X, $Z){
400  $index = self::getIndex($X, $Z);
401  if(!isset($this->chunks[$index])){
402  $this->chunks[$index] = [
403  0 => false,
404  1 => false,
405  2 => false,
406  3 => false,
407  4 => false,
408  5 => false,
409  6 => false,
410  7 => false,
411  ];
412  $this->chunkChange[$index] = [
413  -1 => true,
414  0 => 8192,
415  1 => 8192,
416  2 => 8192,
417  3 => 8192,
418  4 => 8192,
419  5 => 8192,
420  6 => 8192,
421  7 => 8192,
422  ];
423  $nbt = new Compound("", [
424  new Enum("Entities", []),
425  new Enum("TileEntities", [])
426  ]);
427  $nbt->Entities->setTagType(NBT::TAG_Compound);
428  $nbt->TileEntities->setTagType(NBT::TAG_Compound);
429  $this->chunkInfo[$index] = [
430  0 => 0,
431  1 => 0,
432  2 => $nbt,
433  3 => str_repeat("\x00", 256),
434  ];
435  }
436  }
437 
438  public function setMiniChunk($X, $Z, $Y, $data){
439  if($this->isGenerating > 0){
440  $this->initCleanChunk($X, $Z);
441  }elseif($this->isChunkLoaded($X, $Z) === false){
442  $this->loadChunk($X, $Z);
443  }
444  if(strlen($data) !== 8192){
445  return false;
446  }
447  $index = self::getIndex($X, $Z);
448  $this->chunks[$index][$Y] = (string) $data;
449  $this->chunkChange[$index][-1] = true;
450  $this->chunkChange[$index][$Y] = 8192;
451  $this->chunkInfo[$index][0] |= 1 << $Y;
452 
453  return true;
454  }
455 
456  public function getBlockID($x, $y, $z){
457  if($y > 127 or $y < 0){
458  return 0;
459  }
460  $X = $x >> 4;
461  $Z = $z >> 4;
462  $Y = $y >> 4;
463  $index = self::getIndex($X, $Z);
464  if(!isset($this->chunks[$index])){
465  return 0;
466  }
467  $aX = $x - ($X << 4);
468  $aZ = $z - ($Z << 4);
469  $aY = $y - ($Y << 4);
470  $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))});
471 
472  return $b;
473  }
474 
475 
476  public function getBiome($x, $z){
477  $X = $x >> 4;
478  $Z = $z >> 4;
479  $index = self::getIndex($X, $Z);
480  if(!isset($this->chunks[$index])){
481  return 0;
482  }
483  $aX = $x - ($X << 4);
484  $aZ = $z - ($Z << 4);
485 
486  return ord($this->chunkInfo[$index][3]{$aX + ($aZ << 4)});
487  }
488 
489  public function setBiome($x, $z, $biome){
490  $X = $x >> 4;
491  $Z = $z >> 4;
492  $index = self::getIndex($X, $Z);
493  if(!isset($this->chunks[$index])){
494  return false;
495  }
496  $aX = $x - ($X << 4);
497  $aZ = $z - ($Z << 4);
498  $this->chunkInfo[$index][3]{$aX + ($aZ << 4)} = chr((int) $biome);
499 
500  return true;
501  }
502 
503  public function setBlockID($x, $y, $z, $block){
504  if($y > 127 or $y < 0){
505  return false;
506  }
507  $X = $x >> 4;
508  $Z = $z >> 4;
509  $Y = $y >> 4;
510  $block &= 0xFF;
511  $index = self::getIndex($X, $Z);
512  if(!isset($this->chunks[$index])){
513  return false;
514  }
515  $aX = $x - ($X << 4);
516  $aZ = $z - ($Z << 4);
517  $aY = $y - ($Y << 4);
518  $this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))} = chr($block);
519  if(!isset($this->chunkChange[$index][$Y])){
520  $this->chunkChange[$index][$Y] = 1;
521  }else{
522  ++$this->chunkChange[$index][$Y];
523  }
524  $this->chunkChange[$index][-1] = true;
525 
526  return true;
527  }
528 
529  public function getBlockDamage($x, $y, $z){
530  if($y > 127 or $y < 0){
531  return 0;
532  }
533  $X = $x >> 4;
534  $Z = $z >> 4;
535  $Y = $y >> 4;
536  $index = self::getIndex($X, $Z);
537  if(!isset($this->chunks[$index])){
538  return 0;
539  }
540  $aX = $x - ($X << 4);
541  $aZ = $z - ($Z << 4);
542  $aY = $y - ($Y << 4);
543  $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))});
544  if(($y & 1) === 0){
545  $m = $m & 0x0F;
546  }else{
547  $m = $m >> 4;
548  }
549 
550  return $m;
551  }
552 
553  public function setBlockDamage($x, $y, $z, $damage){
554  if($y > 127 or $y < 0){
555  return false;
556  }
557  $X = $x >> 4;
558  $Z = $z >> 4;
559  $Y = $y >> 4;
560  $damage &= 0x0F;
561  $index = self::getIndex($X, $Z);
562  if(!isset($this->chunks[$index])){
563  return false;
564  }
565  $aX = $x - ($X << 4);
566  $aZ = $z - ($Z << 4);
567  $aY = $y - ($Y << 4);
568  $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9));
569  $old_m = ord($this->chunks[$index][$Y]{$mindex});
570  if(($y & 1) === 0){
571  $m = ($old_m & 0xF0) | $damage;
572  }else{
573  $m = ($damage << 4) | ($old_m & 0x0F);
574  }
575 
576  if($old_m != $m){
577  $this->chunks[$index][$Y]{$mindex} = chr($m);
578  if(!isset($this->chunkChange[$index][$Y])){
579  $this->chunkChange[$index][$Y] = 1;
580  }else{
581  ++$this->chunkChange[$index][$Y];
582  }
583  $this->chunkChange[$index][-1] = true;
584 
585  return true;
586  }
587 
588  return false;
589  }
590 
591  public function getBlock($x, $y, $z){
592  $X = $x >> 4;
593  $Z = $z >> 4;
594  $Y = $y >> 4;
595  if($y < 0 or $y > 127){
596  return [0, 0];
597  }
598  $index = self::getIndex($X, $Z);
599  if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){
600  return [0, 0];
601  }elseif($this->chunks[$index][$Y] === false){
602  return [0, 0];
603  }
604  $aX = $x - ($X << 4);
605  $aZ = $z - ($Z << 4);
606  $aY = $y - ($Y << 4);
607  $b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))});
608  $m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))});
609  if(($y & 1) === 0){
610  $m = $m & 0x0F;
611  }else{
612  $m = $m >> 4;
613  }
614 
615  return [$b, $m];
616  }
617 
618  public function setBlock($x, $y, $z, $block, $meta = 0){
619  if($y > 127 or $y < 0){
620  return false;
621  }
622  $X = $x >> 4;
623  $Z = $z >> 4;
624  $Y = $y >> 4;
625  $block &= 0xFF;
626  $meta &= 0x0F;
627  $index = self::getIndex($X, $Z);
628  if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){
629  return false;
630  }elseif($this->chunks[$index][$Y] === false){
631  $this->fillMiniChunk($X, $Z, $Y);
632  }
633  $aX = $x - ($X << 4);
634  $aZ = $z - ($Z << 4);
635  $aY = $y - ($Y << 4);
636  $bindex = (int) ($aY + ($aX << 5) + ($aZ << 9));
637  $mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9));
638  $old_b = ord($this->chunks[$index][$Y]{$bindex});
639  $old_m = ord($this->chunks[$index][$Y]{$mindex});
640  if(($y & 1) === 0){
641  $m = ($old_m & 0xF0) | $meta;
642  }else{
643  $m = ($meta << 4) | ($old_m & 0x0F);
644  }
645 
646  if($old_b !== $block or $old_m !== $m){
647  $this->chunks[$index][$Y]{$bindex} = chr($block);
648  $this->chunks[$index][$Y]{$mindex} = chr($m);
649  if(!isset($this->chunkChange[$index][$Y])){
650  $this->chunkChange[$index][$Y] = 1;
651  }else{
652  ++$this->chunkChange[$index][$Y];
653  }
654  $this->chunkChange[$index][-1] = true;
655 
656  return true;
657  }
658 
659  return false;
660  }
661 
662  public function getChunkNBT($X, $Z){
663  if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){
664  return false;
665  }
666  $index = self::getIndex($X, $Z);
667 
668  return $this->chunkInfo[$index][2];
669  }
670 
671  public function setChunkNBT($X, $Z, Compound $nbt){
672  if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){
673  return false;
674  }
675  $index = self::getIndex($X, $Z);
676  $this->chunkChange[$index][-1] = true;
677  $this->chunkInfo[$index][2] = $nbt;
678  }
679 
680  public function saveChunk($X, $Z, $force = false){
681  $X = (int) $X;
682  $Z = (int) $Z;
683  if(!$this->isChunkLoaded($X, $Z)){
684  return false;
685  }
686  $index = self::getIndex($X, $Z);
687  if($force !== true and (!isset($this->chunkChange[$index]) or $this->chunkChange[$index][-1] === false)){ //No changes in chunk
688  return true;
689  }
690 
691  $path = $this->getChunkPath($X, $Z);
692  if(!file_exists(dirname($path))){
693  @mkdir(dirname($path), 0755);
694  }
695  $bitmap = 0;
696  $this->cleanChunk($X, $Z);
697  for($Y = 0; $Y < 8; ++$Y){
698  if($this->chunks[$index][$Y] !== false and ((isset($this->chunkChange[$index][$Y]) and $this->chunkChange[$index][$Y] === 0) or !$this->isMiniChunkEmpty($X, $Z, $Y))){
699  $bitmap |= 1 << $Y;
700  }else{
701  $this->chunks[$index][$Y] = false;
702  }
703  $this->chunkChange[$index][$Y] = 0;
704  }
705  $this->chunkInfo[$index][0] = $bitmap;
706  $this->chunkChange[$index][-1] = false;
707  $chunk = "";
708  $chunk .= chr($bitmap);
709  $chunk .= Binary::writeInt($this->chunkInfo[$index][1]);
710  $namedtag = new NBT(NBT::BIG_ENDIAN);
711  $namedtag->setData($this->chunkInfo[$index][2]);
712  $namedtag = $namedtag->write();
713  $chunk .= Binary::writeInt(strlen($namedtag)) . $namedtag;
714  $chunk .= $this->chunkInfo[$index][3]; //biomes
715  for($Y = 0; $Y < 8; ++$Y){
716  $t = 1 << $Y;
717  if(($bitmap & $t) === $t){
718  $chunk .= $this->chunks[$index][$Y];
719  }
720  }
721  file_put_contents($path, zlib_encode($chunk, self::ZLIB_ENCODING, self::ZLIB_LEVEL));
722 
723  return true;
724  }
725 
726  public function setPopulated($X, $Z){
727  if(!$this->isChunkLoaded($X, $Z)){
728  return false;
729  }
730  $index = self::getIndex($X, $Z);
731  $this->chunkInfo[$index][1] |= 0b00000000000000000000000000000001;
732  }
733 
734  public function unsetPopulated($X, $Z){
735  if(!$this->isChunkLoaded($X, $Z)){
736  return false;
737  }
738  $index = self::getIndex($X, $Z);
739  $this->chunkInfo[$index][1] &= ~0b00000000000000000000000000000001;
740  }
741 
742  public function isPopulated($X, $Z){
743  if(!$this->isChunkLoaded($X, $Z)){
744  return false;
745  }
746  $index = self::getIndex($X, $Z);
747 
748  return ($this->chunkInfo[$index][1] & 0b00000000000000000000000000000001) > 0;
749  }
750 
751  public function isGenerated($X, $Z){
752  return file_exists($this->getChunkPath($X, $Z));
753  }
754 
755  public function doSaveRound($force = false){
756  foreach($this->chunks as $index => $chunk){
757  self::getXZ($index, $X, $Z);
758  $this->saveChunk($X, $Z, $force);
759  }
760  }
761 
762 }
static writeShort($value)
Definition: Binary.php:285
static readShort($str)
Definition: Binary.php:259