mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-17 03:08:58 +00:00
710 lines
20 KiB
PHP
710 lines
20 KiB
PHP
<?php
|
|
|
|
/**
|
|
*
|
|
* ____ _ _ __ __ _ __ __ ____
|
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* @author PocketMine Team
|
|
* @link http://www.pocketmine.net/
|
|
*
|
|
*
|
|
*/
|
|
|
|
class PMFLevel extends PMF{
|
|
const VERSION = 2;
|
|
const ZLIB_LEVEL = 6;
|
|
const ZLIB_ENCODING = 15; //15 = zlib, -15 = raw deflate, 31 = gzip
|
|
|
|
public $level;
|
|
public $levelData = array();
|
|
public $isLoaded = true;
|
|
private $chunks = array();
|
|
private $chunkChange = array();
|
|
private $chunkInfo = array();
|
|
public $isGenerating = 0;
|
|
|
|
public function getData($index){
|
|
if(!isset($this->levelData[$index])){
|
|
return false;
|
|
}
|
|
return ($this->levelData[$index]);
|
|
}
|
|
|
|
public function setData($index, $data){
|
|
if(!isset($this->levelData[$index])){
|
|
return false;
|
|
}
|
|
$this->levelData[$index] = $data;
|
|
return true;
|
|
}
|
|
|
|
public function closeLevel(){
|
|
$this->chunks = null;
|
|
unset($this->chunks, $this->chunkChange, $this->chunkInfo, $this->level);
|
|
$this->close();
|
|
}
|
|
|
|
public function __construct($file, $blank = false){
|
|
$this->chunks = array();
|
|
$this->chunkChange = array();
|
|
$this->chunkInfo = array();
|
|
if(is_array($blank)){
|
|
$this->create($file, 0);
|
|
$this->levelData = $blank;
|
|
$this->createBlank();
|
|
$this->isLoaded = true;
|
|
}else{
|
|
if($this->load($file) !== false){
|
|
$this->parseInfo();
|
|
if($this->parseLevel() === false){
|
|
$this->isLoaded = false;
|
|
}else{
|
|
$this->isLoaded = true;
|
|
}
|
|
}else{
|
|
$this->isLoaded = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function saveData(){
|
|
$this->levelData["version"] = PMFLevel::VERSION;
|
|
@ftruncate($this->fp, 5);
|
|
$this->seek(5);
|
|
$this->write(chr($this->levelData["version"]));
|
|
$this->write(Utils::writeShort(strlen($this->levelData["name"])).$this->levelData["name"]);
|
|
$this->write(Utils::writeInt($this->levelData["seed"]));
|
|
$this->write(Utils::writeInt($this->levelData["time"]));
|
|
$this->write(Utils::writeFloat($this->levelData["spawnX"]));
|
|
$this->write(Utils::writeFloat($this->levelData["spawnY"]));
|
|
$this->write(Utils::writeFloat($this->levelData["spawnZ"]));
|
|
$this->write(chr($this->levelData["height"]));
|
|
$this->write(Utils::writeShort(strlen($this->levelData["generator"])).$this->levelData["generator"]);
|
|
$settings = serialize($this->levelData["generatorSettings"]);
|
|
$this->write(Utils::writeShort(strlen($settings)).$settings);
|
|
$extra = zlib_encode($this->levelData["extra"], PMFLevel::ZLIB_ENCODING, PMFLevel::ZLIB_LEVEL);
|
|
$this->write(Utils::writeShort(strlen($extra)).$extra);
|
|
}
|
|
|
|
private function createBlank(){
|
|
$this->saveData();
|
|
@mkdir(dirname($this->file)."/chunks/", 0755);
|
|
}
|
|
|
|
protected function parseLevel(){
|
|
if($this->getType() !== 0x00){
|
|
return false;
|
|
}
|
|
$this->seek(5);
|
|
$this->levelData["version"] = ord($this->read(1));
|
|
if($this->levelData["version"] > PMFLevel::VERSION){
|
|
console("[ERROR] New unsupported PMF Level format version #".$this->levelData["version"].", current version is #".PMFLevel::VERSION);
|
|
return false;
|
|
}
|
|
$this->levelData["name"] = $this->read(Utils::readShort($this->read(2), false));
|
|
$this->levelData["seed"] = Utils::readInt($this->read(4));
|
|
$this->levelData["time"] = Utils::readInt($this->read(4));
|
|
$this->levelData["spawnX"] = Utils::readFloat($this->read(4));
|
|
$this->levelData["spawnY"] = Utils::readFloat($this->read(4));
|
|
$this->levelData["spawnZ"] = Utils::readFloat($this->read(4));
|
|
if($this->levelData["version"] === 0){
|
|
$this->read(1);
|
|
$this->levelData["height"] = ord($this->read(1));
|
|
}else{
|
|
$this->levelData["height"] = ord($this->read(1));
|
|
if($this->levelData["height"] !== 8){
|
|
return false;
|
|
}
|
|
$this->levelData["generator"] = $this->read(Utils::readShort($this->read(2), false));
|
|
$this->levelData["generatorSettings"] = unserialize($this->read(Utils::readShort($this->read(2), false)));
|
|
|
|
}
|
|
$this->levelData["extra"] = @zlib_decode($this->read(Utils::readShort($this->read(2), false)));
|
|
|
|
$upgrade = false;
|
|
if($this->levelData["version"] === 0){
|
|
$this->upgrade_From0_To1();
|
|
$upgrade = true;
|
|
}
|
|
if($this->levelData["version"] === 1){
|
|
$this->upgrade_From1_To2();
|
|
$upgrade = true;
|
|
}
|
|
|
|
if($upgrade === true){
|
|
$this->saveData();
|
|
}
|
|
}
|
|
|
|
private function upgrade_From0_To1(){
|
|
console("[NOTICE] Old PMF Level format version #0 detected, upgrading to version #1");
|
|
for($index = 0; $index < 256; ++$index){
|
|
$X = $index & 0x0F;
|
|
$Z = $index >> 4;
|
|
|
|
$bitflags = Utils::readShort($this->read(2));
|
|
$oldPath = dirname($this->file)."/chunks/".$Z.".".$X.".pmc";
|
|
$chunkOld = gzopen($oldPath, "rb");
|
|
$newPath = dirname($this->file)."/chunks/".(($X ^ $Z) & 0xff)."/".$Z.".".$X.".pmc";
|
|
@mkdir(dirname($newPath));
|
|
$chunkNew = gzopen($newPath, "wb1");
|
|
gzwrite($chunkNew, chr($bitflags) . "\x00\x00\x00\x01");
|
|
while(gzeof($chunkOld) === false){
|
|
gzwrite($chunkNew, gzread($chunkOld, 65535));
|
|
}
|
|
gzclose($chunkNew);
|
|
gzclose($chunkOld);
|
|
@unlink($oldPath);
|
|
}
|
|
$this->levelData["version"] = 1;
|
|
$this->levelData["generator"] = "NormalGenerator";
|
|
$this->levelData["generatorSettings"] = "";
|
|
}
|
|
|
|
private function upgrade_From1_To2(){
|
|
console("[NOTICE] Old PMF Level format version #1 detected, upgrading to version #2");
|
|
$nbt = new NBT(NBT::BIG_ENDIAN);
|
|
$nbt->setData(new NBTTag_Compound("", array(
|
|
"Entities" => new NBTTag_List("Entities", array()),
|
|
"TileEntities" => new NBTTag_List("TileEntities", array())
|
|
)));
|
|
$nbt->Entities->setTagType(NBTTag::TAG_Compound);
|
|
$nbt->TileEntities->setTagType(NBTTag::TAG_Compound);
|
|
$namedtag = $nbt->write();
|
|
$namedtag = Utils::writeInt(strlen($namedtag)) . $namedtag;
|
|
foreach(glob(dirname($this->file)."/chunks/*/*.*.pmc") as $chunkFile){
|
|
$oldChunk = zlib_decode(file_get_contents($chunkFile));
|
|
$newChunk = substr($oldChunk, 0, 5);
|
|
$newChunk .= $namedtag;
|
|
$newChunk .= str_repeat("\x01", 256); //Biome indexes (all Plains)
|
|
$newChunk .= substr($oldChunk, 5);
|
|
file_put_contents($chunkFile, zlib_encode($newChunk, PMFLevel::ZLIB_ENCODING, PMFLevel::ZLIB_LEVEL));
|
|
}
|
|
$this->levelData["version"] = 2;
|
|
}
|
|
|
|
public static function getIndex($X, $Z){
|
|
return ($Z << 16) | ($X < 0 ? (~--$X & 0x7fff) | 0x8000 : $X & 0x7fff);
|
|
}
|
|
|
|
public static function getXZ($index, &$X = null, &$Z = null){
|
|
$Z = $index >> 16;
|
|
$X = ($index & 0x8000) === 0x8000 ? -($index & 0x7fff) : $index & 0x7fff;
|
|
return array($X, $Z);
|
|
}
|
|
|
|
private function getChunkPath($X, $Z){
|
|
return dirname($this->file)."/chunks/".(($X ^ $Z) & 0xff)."/".$Z.".".$X.".pmc";
|
|
}
|
|
|
|
public function generateChunk($X, $Z){
|
|
$path = $this->getChunkPath($X, $Z);
|
|
if(!file_exists(dirname($path))){
|
|
@mkdir(dirname($path), 0755);
|
|
}
|
|
$this->initCleanChunk($X, $Z);
|
|
if($this->level instanceof Level){
|
|
$ret = $this->level->generateChunk($X, $Z);
|
|
$this->saveChunk($X, $Z);
|
|
$this->populateChunk($X - 1, $Z);
|
|
$this->populateChunk($X + 1, $Z);
|
|
$this->populateChunk($X, $Z - 1);
|
|
$this->populateChunk($X, $Z + 1);
|
|
$this->populateChunk($X + 1, $Z + 1);
|
|
$this->populateChunk($X + 1, $Z - 1);
|
|
$this->populateChunk($X - 1, $Z - 1);
|
|
$this->populateChunk($X - 1, $Z + 1);
|
|
return $ret;
|
|
}
|
|
}
|
|
|
|
public function populateChunk($X, $Z){
|
|
if($this->level instanceof Level){
|
|
if($this->isGenerating === 0 and
|
|
$this->isChunkLoaded($X, $Z) and
|
|
!$this->isPopulated($X, $Z) and
|
|
$this->isGenerated($X - 1, $Z) and
|
|
$this->isGenerated($X, $Z - 1) and
|
|
$this->isGenerated($X + 1, $Z) and
|
|
$this->isGenerated($X, $Z + 1) and
|
|
$this->isGenerated($X + 1, $Z + 1) and
|
|
$this->isGenerated($X - 1, $Z - 1) and
|
|
$this->isGenerated($X + 1, $Z - 1) and
|
|
$this->isGenerated($X - 1, $Z + 1)){
|
|
$this->level->populateChunk($X, $Z);
|
|
$this->saveChunk($X, $Z);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function loadChunk($X, $Z){
|
|
if($this->isChunkLoaded($X, $Z)){
|
|
return true;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$path = $this->getChunkPath($X, $Z);
|
|
if(!file_exists($path)){
|
|
if($this->generateChunk($X, $Z) === false){
|
|
return false;
|
|
}
|
|
if($this->isGenerating === 0){
|
|
$this->populateChunk($X, $Z);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
$chunk = file_get_contents($path);
|
|
if($chunk === false){
|
|
return false;
|
|
}
|
|
$chunk = zlib_decode($chunk);
|
|
$offset = 0;
|
|
|
|
$this->chunkInfo[$index] = array(
|
|
0 => ord($chunk{0}),
|
|
1 => Utils::readInt(substr($chunk, 1, 4)),
|
|
);
|
|
$offset += 5;
|
|
$len = Utils::readInt(substr($chunk, $offset, 4));
|
|
$offset += 4;
|
|
$nbt = new NBT(NBT::BIG_ENDIAN);
|
|
$nbt->read(substr($chunk, $offset, $len));
|
|
$this->chunkInfo[$index][2] = $nbt;
|
|
$offset += $len;
|
|
$this->chunks[$index] = array();
|
|
$this->chunkChange[$index] = array(-1 => false);
|
|
$this->chunkInfo[$index][3] = substr($chunk, $offset, 256); //Biome data
|
|
$offset += 256;
|
|
for($Y = 0; $Y < 8; ++$Y){
|
|
if(($this->chunkInfo[$index][0] & (1 << $Y)) !== 0){
|
|
// 4096 + 2048 + 2048, Block Data, Meta, Light
|
|
if(strlen($this->chunks[$index][$Y] = substr($chunk, $offset, 8192)) < 8192){
|
|
console("[NOTICE] Empty corrupt chunk detected [$X,$Z,:$Y], recovering contents", true, true, 2);
|
|
$this->fillMiniChunk($X, $Z, $Y);
|
|
}
|
|
$offset += 8192;
|
|
}else{
|
|
$this->chunks[$index][$Y] = false;
|
|
}
|
|
}
|
|
if($this->isGenerating === 0 and !$this->isPopulated($X, $Z)){
|
|
$this->populateChunk($X, $Z);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function unloadChunk($X, $Z, $save = true){
|
|
$X = (int) $X;
|
|
$Z = (int) $Z;
|
|
if(!$this->isChunkLoaded($X, $Z)){
|
|
return false;
|
|
}elseif($save !== false){
|
|
$this->saveChunk($X, $Z);
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunks[$index] = null;
|
|
$this->chunkChange[$index] = null;
|
|
$this->chunkInfo[$index] = null;
|
|
unset($this->chunks[$index], $this->chunkChange[$index], $this->chunkInfo[$index]);
|
|
return true;
|
|
}
|
|
|
|
public function isChunkLoaded($X, $Z){
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function isMiniChunkEmpty($X, $Z, $Y){
|
|
$index = self::getIndex($X, $Z);
|
|
if(isset($this->chunks[$index]) and $this->chunks[$index][$Y] !== false){
|
|
if(substr_count($this->chunks[$index][$Y], "\x00") < 8192){
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected function fillMiniChunk($X, $Z, $Y){
|
|
if($this->isChunkLoaded($X, $Z) === false){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunks[$index][$Y] = str_repeat("\x00", 8192);
|
|
$this->chunkChange[$index][-1] = true;
|
|
$this->chunkChange[$index][$Y] = 8192;
|
|
$this->chunkInfo[$index][0] |= 1 << $Y;
|
|
return true;
|
|
}
|
|
|
|
public function getMiniChunk($X, $Z, $Y){
|
|
if($this->isChunkLoaded($X, $Z) === false and $this->loadChunk($X, $Z) === false){
|
|
return str_repeat("\x00", 8192);
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index][$Y]) or $this->chunks[$index][$Y] === false){
|
|
return str_repeat("\x00", 8192);
|
|
}
|
|
return $this->chunks[$index][$Y];
|
|
}
|
|
|
|
public function initCleanChunk($X, $Z){
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
$this->chunks[$index] = array(
|
|
0 => false,
|
|
1 => false,
|
|
2 => false,
|
|
3 => false,
|
|
4 => false,
|
|
5 => false,
|
|
6 => false,
|
|
7 => false,
|
|
);
|
|
$this->chunkChange[$index] = array(
|
|
-1 => true,
|
|
0 => 8192,
|
|
1 => 8192,
|
|
2 => 8192,
|
|
3 => 8192,
|
|
4 => 8192,
|
|
5 => 8192,
|
|
6 => 8192,
|
|
7 => 8192,
|
|
);
|
|
$nbt = new NBT(NBT::BIG_ENDIAN);
|
|
$nbt->setData(new NBTTag_Compound("", array(
|
|
"Entities" => new NBTTag_List("Entities", array()),
|
|
"TileEntities" => new NBTTag_List("TileEntities", array())
|
|
)));
|
|
$nbt->Entities->setTagType(NBTTag::TAG_Compound);
|
|
$nbt->TileEntities->setTagType(NBTTag::TAG_Compound);
|
|
$this->chunkInfo[$index] = array(
|
|
0 => 0,
|
|
1 => 0,
|
|
2 => $nbt,
|
|
3 => str_repeat("\x00", 256),
|
|
);
|
|
}
|
|
}
|
|
|
|
public function setMiniChunk($X, $Z, $Y, $data){
|
|
if($this->isGenerating > 0){
|
|
$this->initCleanChunk($X, $Z);
|
|
}elseif($this->isChunkLoaded($X, $Z) === false){
|
|
$this->loadChunk($X, $Z);
|
|
}
|
|
if(strlen($data) !== 8192){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunks[$index][$Y] = (string) $data;
|
|
$this->chunkChange[$index][-1] = true;
|
|
$this->chunkChange[$index][$Y] = 8192;
|
|
$this->chunkInfo[$index][0] |= 1 << $Y;
|
|
return true;
|
|
}
|
|
|
|
public function getBlockID($x, $y, $z){
|
|
if($y > 127 or $y < 0){
|
|
return 0;
|
|
}
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return 0;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))});
|
|
return $b;
|
|
}
|
|
|
|
|
|
public function getBiome($x, $z){
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return 0;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
return ord($this->chunkInfo[$index][3]{$aX + ($aZ << 4)});
|
|
}
|
|
|
|
public function setBiome($x, $z, $biome){
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return false;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$this->chunkInfo[$index][3]{$aX + ($aZ << 4)} = chr((int) $biome);
|
|
return true;
|
|
}
|
|
|
|
public function setBlockID($x, $y, $z, $block){
|
|
if($y > 127 or $y < 0){
|
|
return false;
|
|
}
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
$block &= 0xFF;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return false;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))} = chr($block);
|
|
if(!isset($this->chunkChange[$index][$Y])){
|
|
$this->chunkChange[$index][$Y] = 1;
|
|
}else{
|
|
++$this->chunkChange[$index][$Y];
|
|
}
|
|
$this->chunkChange[$index][-1] = true;
|
|
return true;
|
|
}
|
|
|
|
public function getBlockDamage($x, $y, $z){
|
|
if($y > 127 or $y < 0){
|
|
return 0;
|
|
}
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return 0;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))});
|
|
if(($y & 1) === 0){
|
|
$m = $m & 0x0F;
|
|
}else{
|
|
$m = $m >> 4;
|
|
}
|
|
return $m;
|
|
}
|
|
|
|
public function setBlockDamage($x, $y, $z, $damage){
|
|
if($y > 127 or $y < 0){
|
|
return false;
|
|
}
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
$damage &= 0x0F;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index])){
|
|
return false;
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9));
|
|
$old_m = ord($this->chunks[$index][$Y]{$mindex});
|
|
if(($y & 1) === 0){
|
|
$m = ($old_m & 0xF0) | $damage;
|
|
}else{
|
|
$m = ($damage << 4) | ($old_m & 0x0F);
|
|
}
|
|
|
|
if($old_m != $m){
|
|
$this->chunks[$index][$Y]{$mindex} = chr($m);
|
|
if(!isset($this->chunkChange[$index][$Y])){
|
|
$this->chunkChange[$index][$Y] = 1;
|
|
}else{
|
|
++$this->chunkChange[$index][$Y];
|
|
}
|
|
$this->chunkChange[$index][-1] = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getBlock($x, $y, $z){
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
if($y < 0 or $y > 127){
|
|
return array(AIR, 0);
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){
|
|
return array(AIR, 0);
|
|
}elseif($this->chunks[$index][$Y] === false){
|
|
return array(AIR, 0);
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$b = ord($this->chunks[$index][$Y]{(int) ($aY + ($aX << 5) + ($aZ << 9))});
|
|
$m = ord($this->chunks[$index][$Y]{(int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9))});
|
|
if(($y & 1) === 0){
|
|
$m = $m & 0x0F;
|
|
}else{
|
|
$m = $m >> 4;
|
|
}
|
|
return array($b, $m);
|
|
}
|
|
|
|
public function setBlock($x, $y, $z, $block, $meta = 0){
|
|
if($y > 127 or $y < 0){
|
|
return false;
|
|
}
|
|
$X = $x >> 4;
|
|
$Z = $z >> 4;
|
|
$Y = $y >> 4;
|
|
$block &= 0xFF;
|
|
$meta &= 0x0F;
|
|
$index = self::getIndex($X, $Z);
|
|
if(!isset($this->chunks[$index]) and $this->loadChunk($X, $Z) === false){
|
|
return false;
|
|
}elseif($this->chunks[$index][$Y] === false){
|
|
$this->fillMiniChunk($X, $Z, $Y);
|
|
}
|
|
$aX = $x - ($X << 4);
|
|
$aZ = $z - ($Z << 4);
|
|
$aY = $y - ($Y << 4);
|
|
$bindex = (int) ($aY + ($aX << 5) + ($aZ << 9));
|
|
$mindex = (int) (($aY >> 1) + 16 + ($aX << 5) + ($aZ << 9));
|
|
$old_b = ord($this->chunks[$index][$Y]{$bindex});
|
|
$old_m = ord($this->chunks[$index][$Y]{$mindex});
|
|
if(($y & 1) === 0){
|
|
$m = ($old_m & 0xF0) | $meta;
|
|
}else{
|
|
$m = ($meta << 4) | ($old_m & 0x0F);
|
|
}
|
|
|
|
if($old_b !== $block or $old_m !== $m){
|
|
$this->chunks[$index][$Y]{$bindex} = chr($block);
|
|
$this->chunks[$index][$Y]{$mindex} = chr($m);
|
|
if(!isset($this->chunkChange[$index][$Y])){
|
|
$this->chunkChange[$index][$Y] = 1;
|
|
}else{
|
|
++$this->chunkChange[$index][$Y];
|
|
}
|
|
$this->chunkChange[$index][-1] = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getChunkNBT($X, $Z){
|
|
if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
return $this->chunkInfo[$index][2];
|
|
}
|
|
|
|
public function setChunkNBT($X, $Z, NBT $nbt){
|
|
if(!$this->isChunkLoaded($X, $Z) and $this->loadChunk($X, $Z) === false){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunkChange[$index][-1] = true;
|
|
$this->chunkInfo[$index][2] = $nbt;
|
|
}
|
|
|
|
public function saveChunk($X, $Z, $force = false){
|
|
$X = (int) $X;
|
|
$Z = (int) $Z;
|
|
if(!$this->isChunkLoaded($X, $Z)){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
if($force !== true and (!isset($this->chunkChange[$index]) or $this->chunkChange[$index][-1] === false)){//No changes in chunk
|
|
return true;
|
|
}
|
|
|
|
$path = $this->getChunkPath($X, $Z);
|
|
if(!file_exists(dirname($path))){
|
|
@mkdir(dirname($path), 0755);
|
|
}
|
|
$bitmap = 0;
|
|
for($Y = 0; $Y < 8; ++$Y){
|
|
if($this->chunks[$index][$Y] !== false and ((isset($this->chunkChange[$index][$Y]) and $this->chunkChange[$index][$Y] === 0) or !$this->isMiniChunkEmpty($X, $Z, $Y))){
|
|
$bitmap |= 1 << $Y;
|
|
}else{
|
|
$this->chunks[$index][$Y] = false;
|
|
}
|
|
$this->chunkChange[$index][$Y] = 0;
|
|
}
|
|
$this->chunkInfo[$index][0] = $bitmap;
|
|
$this->chunkChange[$index][-1] = false;
|
|
$chunk = b"";
|
|
$chunk .= chr($bitmap);
|
|
$chunk .= Utils::writeInt($this->chunkInfo[$index][1]);
|
|
$namedtag = $this->chunkInfo[$index][2]->write();
|
|
$chunk .= Utils::writeInt(strlen($namedtag)).$namedtag;
|
|
$chunk .= $this->chunkInfo[$index][3]; //biomes
|
|
for($Y = 0; $Y < 8; ++$Y){
|
|
$t = 1 << $Y;
|
|
if(($bitmap & $t) === $t){
|
|
$chunk .= $this->chunks[$index][$Y];
|
|
}
|
|
}
|
|
file_put_contents($path, zlib_encode($chunk, PMFLevel::ZLIB_ENCODING, PMFLevel::ZLIB_LEVEL));
|
|
return true;
|
|
}
|
|
|
|
public function setPopulated($X, $Z){
|
|
if(!$this->isChunkLoaded($X, $Z)){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunkInfo[$index][1] |= 0b00000000000000000000000000000001;
|
|
}
|
|
|
|
public function unsetPopulated($X, $Z){
|
|
if(!$this->isChunkLoaded($X, $Z)){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
$this->chunkInfo[$index][1] &= ~0b00000000000000000000000000000001;
|
|
}
|
|
|
|
public function isPopulated($X, $Z){
|
|
if(!$this->isChunkLoaded($X, $Z)){
|
|
return false;
|
|
}
|
|
$index = self::getIndex($X, $Z);
|
|
return ($this->chunkInfo[$index][1] & 0b00000000000000000000000000000001) > 0;
|
|
}
|
|
|
|
public function isGenerated($X, $Z){
|
|
return file_exists($this->getChunkPath($X, $Z));
|
|
}
|
|
|
|
public function doSaveRound($force = false){
|
|
foreach($this->chunks as $index => $chunk){
|
|
self::getXZ($index, $X, $Z);
|
|
$this->saveChunk($X, $Z, $force);
|
|
}
|
|
}
|
|
|
|
}
|