PageRenderTime 28ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/pocketmine/level/format/mcregion/Chunk.php

https://gitlab.com/matthww/Elywing
PHP | 463 lines | 338 code | 78 blank | 47 comment | 31 complexity | c7cf391cae2d381424a92254a239ebf0 MD5 | raw file
  1. <?php
  2. /*
  3. *
  4. * ____ _ _ __ __ _ __ __ ____
  5. * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
  6. * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
  7. * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
  8. * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Lesser General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * @author PocketMine Team
  16. * @link http://www.pocketmine.net/
  17. *
  18. *
  19. */
  20. namespace pocketmine\level\format\mcregion;
  21. use pocketmine\level\format\generic\BaseFullChunk;
  22. use pocketmine\level\format\LevelProvider;
  23. use pocketmine\nbt\NBT;
  24. use pocketmine\nbt\tag\ByteTag;
  25. use pocketmine\nbt\tag\ByteArrayTag;
  26. use pocketmine\nbt\tag\CompoundTag;
  27. use pocketmine\nbt\tag\ListTag;
  28. use pocketmine\nbt\tag\IntTag;
  29. use pocketmine\nbt\tag\IntArrayTag;
  30. use pocketmine\nbt\tag\LongTag;
  31. use pocketmine\Player;
  32. use pocketmine\utils\Binary;
  33. use pocketmine\utils\BinaryStream;
  34. class Chunk extends BaseFullChunk{
  35. /** @var CompoundTag */
  36. protected $nbt;
  37. public function __construct($level, CompoundTag $nbt = null){
  38. if($nbt === null){
  39. $this->provider = $level;
  40. $this->nbt = new CompoundTag("Level", []);
  41. return;
  42. }
  43. $this->nbt = $nbt;
  44. if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof ListTag){
  45. $this->nbt->Entities->setTagType(NBT::TAG_Compound);
  46. }else{
  47. $this->nbt->Entities = new ListTag("Entities", []);
  48. $this->nbt->Entities->setTagType(NBT::TAG_Compound);
  49. }
  50. if(isset($this->nbt->TileEntities) and $this->nbt->TileEntities instanceof ListTag){
  51. $this->nbt->TileEntities->setTagType(NBT::TAG_Compound);
  52. }else{
  53. $this->nbt->TileEntities = new ListTag("TileEntities", []);
  54. $this->nbt->TileEntities->setTagType(NBT::TAG_Compound);
  55. }
  56. if(isset($this->nbt->TileTicks) and $this->nbt->TileTicks instanceof ListTag){
  57. $this->nbt->TileTicks->setTagType(NBT::TAG_Compound);
  58. }else{
  59. $this->nbt->TileTicks = new ListTag("TileTicks", []);
  60. $this->nbt->TileTicks->setTagType(NBT::TAG_Compound);
  61. }
  62. if(!isset($this->nbt->BiomeColors) or !($this->nbt->BiomeColors instanceof IntArrayTag)){
  63. $this->nbt->BiomeColors = new IntArrayTag("BiomeColors", array_fill(0, 256, 0));
  64. }
  65. if(!isset($this->nbt->HeightMap) or !($this->nbt->HeightMap instanceof IntArrayTag)){
  66. $this->nbt->HeightMap = new IntArrayTag("HeightMap", array_fill(0, 256, 0));
  67. }
  68. if(!isset($this->nbt->Blocks)){
  69. $this->nbt->Blocks = new ByteArrayTag("Blocks", str_repeat("\x00", 32768));
  70. }
  71. if(!isset($this->nbt->Data)){
  72. $this->nbt->Data = new ByteArrayTag("Data", $half = str_repeat("\x00", 16384));
  73. $this->nbt->SkyLight = new ByteArrayTag("SkyLight", $half);
  74. $this->nbt->BlockLight = new ByteArrayTag("BlockLight", $half);
  75. }
  76. $extraData = [];
  77. if(!isset($this->nbt->ExtraData) or !($this->nbt->ExtraData instanceof ByteArrayTag)){
  78. $this->nbt->ExtraData = new ByteArrayTag("ExtraData", Binary::writeInt(0));
  79. }else{
  80. $stream = new BinaryStream($this->nbt->ExtraData->getValue());
  81. $count = $stream->getInt();
  82. for($i = 0; $i < $count; ++$i){
  83. $key = $stream->getInt();
  84. $extraData[$key] = $stream->getShort();
  85. }
  86. }
  87. parent::__construct($level, $this->nbt["xPos"], $this->nbt["zPos"], $this->nbt->Blocks->getValue(), $this->nbt->Data->getValue(), $this->nbt->SkyLight->getValue(), $this->nbt->BlockLight->getValue(), $this->nbt->BiomeColors->getValue(), $this->nbt->HeightMap->getValue(), $this->nbt->Entities->getValue(), $this->nbt->TileEntities->getValue(), $extraData);
  88. if(isset($this->nbt->Biomes)){
  89. $this->checkOldBiomes($this->nbt->Biomes->getValue());
  90. unset($this->nbt->Biomes);
  91. }
  92. unset($this->nbt->Blocks);
  93. unset($this->nbt->Data);
  94. unset($this->nbt->SkyLight);
  95. unset($this->nbt->BlockLight);
  96. unset($this->nbt->BiomeColors);
  97. unset($this->nbt->HeightMap);
  98. unset($this->nbt->Biomes);
  99. }
  100. public function getBlockId($x, $y, $z){
  101. if(isset($this->blocks{($x << 11) | ($z << 7) | $y})) return ord($this->blocks{($x << 11) | ($z << 7) | $y});
  102. else return 0;
  103. }
  104. public function setBlockId($x, $y, $z, $id){
  105. $this->blocks{($x << 11) | ($z << 7) | $y} = chr($id);
  106. $this->hasChanged = true;
  107. }
  108. public function getBlockData($x, $y, $z){
  109. $m = ord($this->data{($x << 10) | ($z << 6) | ($y >> 1)});
  110. if(($y & 1) === 0){
  111. return $m & 0x0F;
  112. }else{
  113. return $m >> 4;
  114. }
  115. }
  116. public function setBlockData($x, $y, $z, $data){
  117. $i = ($x << 10) | ($z << 6) | ($y >> 1);
  118. $old_m = ord($this->data{$i});
  119. if(($y & 1) === 0){
  120. $this->data{$i} = chr(($old_m & 0xf0) | ($data & 0x0f));
  121. }else{
  122. $this->data{$i} = chr((($data & 0x0f) << 4) | ($old_m & 0x0f));
  123. }
  124. $this->hasChanged = true;
  125. }
  126. public function getFullBlock($x, $y, $z){
  127. $i = ($x << 11) | ($z << 7) | $y;
  128. if(($y & 1) === 0){
  129. return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0F);
  130. }else{
  131. return (ord($this->blocks{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4);
  132. }
  133. }
  134. public function setBlock($x, $y, $z, $blockId = null, $meta = null){
  135. $i = ($x << 11) | ($z << 7) | $y;
  136. $changed = false;
  137. if($blockId !== null){
  138. $blockId = chr($blockId);
  139. if($this->blocks{$i} !== $blockId){
  140. $this->blocks{$i} = $blockId;
  141. $changed = true;
  142. }
  143. }
  144. if($meta !== null){
  145. $i >>= 1;
  146. $old_m = ord($this->data{$i});
  147. if(($y & 1) === 0){
  148. $this->data{$i} = chr(($old_m & 0xf0) | ($meta & 0x0f));
  149. if(($old_m & 0x0f) !== $meta){
  150. $changed = true;
  151. }
  152. }else{
  153. $this->data{$i} = chr((($meta & 0x0f) << 4) | ($old_m & 0x0f));
  154. if((($old_m & 0xf0) >> 4) !== $meta){
  155. $changed = true;
  156. }
  157. }
  158. }
  159. if($changed){
  160. $this->hasChanged = true;
  161. }
  162. return $changed;
  163. }
  164. public function getBlockSkyLight($x, $y, $z){
  165. $sl = ord($this->skyLight{($x << 10) | ($z << 6) | ($y >> 1)});
  166. if(($y & 1) === 0){
  167. return $sl & 0x0F;
  168. }else{
  169. return $sl >> 4;
  170. }
  171. }
  172. public function setBlockSkyLight($x, $y, $z, $level){
  173. $i = ($x << 10) | ($z << 6) | ($y >> 1);
  174. $old_sl = ord($this->skyLight{$i});
  175. if(($y & 1) === 0){
  176. $this->skyLight{$i} = chr(($old_sl & 0xf0) | ($level & 0x0f));
  177. }else{
  178. $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($old_sl & 0x0f));
  179. }
  180. $this->hasChanged = true;
  181. }
  182. public function getBlockLight($x, $y, $z){
  183. $l = ord($this->blockLight{($x << 10) | ($z << 6) | ($y >> 1)});
  184. if(($y & 1) === 0){
  185. return $l & 0x0F;
  186. }else{
  187. return $l >> 4;
  188. }
  189. }
  190. public function setBlockLight($x, $y, $z, $level){
  191. $i = ($x << 10) | ($z << 6) | ($y >> 1);
  192. $old_l = ord($this->blockLight{$i});
  193. if(($y & 1) === 0){
  194. $this->blockLight{$i} = chr(($old_l & 0xf0) | ($level & 0x0f));
  195. }else{
  196. $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($old_l & 0x0f));
  197. }
  198. $this->hasChanged = true;
  199. }
  200. public function getBlockIdColumn($x, $z){
  201. return substr($this->blocks, ($x << 11) + ($z << 7), 128);
  202. }
  203. public function getBlockDataColumn($x, $z){
  204. return substr($this->data, ($x << 10) + ($z << 6), 64);
  205. }
  206. public function getBlockSkyLightColumn($x, $z){
  207. return substr($this->skyLight, ($x << 10) + ($z << 6), 64);
  208. }
  209. public function getBlockLightColumn($x, $z){
  210. return substr($this->blockLight, ($x << 10) + ($z << 6), 64);
  211. }
  212. public function isLightPopulated(){
  213. return $this->nbt["LightPopulated"] > 0;
  214. }
  215. public function setLightPopulated($value = 1){
  216. $this->nbt->LightPopulated = new ByteTag("LightPopulated", $value ? 1 : 0);
  217. $this->hasChanged = true;
  218. }
  219. /**
  220. * @return bool
  221. */
  222. public function isPopulated(){
  223. return isset($this->nbt->TerrainPopulated) and $this->nbt->TerrainPopulated->getValue() > 0;
  224. }
  225. /**
  226. * @param int $value
  227. */
  228. public function setPopulated($value = 1){
  229. $this->nbt->TerrainPopulated = new ByteTag("TerrainPopulated", $value ? 1 : 0);
  230. $this->hasChanged = true;
  231. }
  232. /**
  233. * @return bool
  234. */
  235. public function isGenerated(){
  236. if(isset($this->nbt->TerrainGenerated)){
  237. return $this->nbt->TerrainGenerated->getValue() > 0;
  238. }elseif(isset($this->nbt->TerrainPopulated)){
  239. return $this->nbt->TerrainPopulated->getValue() > 0;
  240. }
  241. return false;
  242. }
  243. /**
  244. * @param int $value
  245. */
  246. public function setGenerated($value = 1){
  247. $this->nbt->TerrainGenerated = new ByteTag("TerrainGenerated", (int) $value);
  248. $this->hasChanged = true;
  249. }
  250. /**
  251. * @param string $data
  252. * @param LevelProvider $provider
  253. *
  254. * @return Chunk
  255. */
  256. public static function fromBinary($data, LevelProvider $provider = null){
  257. $nbt = new NBT(NBT::BIG_ENDIAN);
  258. try{
  259. $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE);
  260. $chunk = $nbt->getData();
  261. if(!isset($chunk->Level) or !($chunk->Level instanceof CompoundTag)){
  262. return null;
  263. }
  264. return new Chunk($provider instanceof LevelProvider ? $provider : McRegion::class, $chunk->Level);
  265. }catch(\Throwable $e){
  266. return null;
  267. }
  268. }
  269. public static function fromFastBinary($data, LevelProvider $provider = null){
  270. try{
  271. $offset = 0;
  272. $chunk = new Chunk($provider instanceof LevelProvider ? $provider : McRegion::class, null);
  273. $chunk->provider = $provider;
  274. $chunk->x = Binary::readInt(substr($data, $offset, 4));
  275. $offset += 4;
  276. $chunk->z = Binary::readInt(substr($data, $offset, 4));
  277. $offset += 4;
  278. $chunk->blocks = substr($data, $offset, 32768);
  279. $offset += 32768;
  280. $chunk->data = substr($data, $offset, 16384);
  281. $offset += 16384;
  282. $chunk->skyLight = substr($data, $offset, 16384);
  283. $offset += 16384;
  284. $chunk->blockLight = substr($data, $offset, 16384);
  285. $offset += 16384;
  286. $chunk->heightMap = array_values(unpack("C*", substr($data, $offset, 256)));
  287. $offset += 256;
  288. $chunk->biomeColors = array_values(unpack("N*", substr($data, $offset, 1024)));
  289. $offset += 1024;
  290. $flags = ord($data{$offset++});
  291. $chunk->nbt->TerrainGenerated = new ByteTag("TerrainGenerated", $flags & 0b1);
  292. $chunk->nbt->TerrainPopulated = new ByteTag("TerrainPopulated", ($flags >> 1) & 0b1);
  293. $chunk->nbt->LightPopulated = new ByteTag("LightPopulated", ($flags >> 2) & 0b1);
  294. return $chunk;
  295. }catch(\Throwable $e){
  296. return null;
  297. }
  298. }
  299. public function toFastBinary(){
  300. return
  301. Binary::writeInt($this->x) .
  302. Binary::writeInt($this->z) .
  303. $this->getBlockIdArray() .
  304. $this->getBlockDataArray() .
  305. $this->getBlockSkyLightArray() .
  306. $this->getBlockLightArray() .
  307. pack("C*", ...$this->getHeightMapArray()) .
  308. pack("N*", ...$this->getBiomeColorArray()) .
  309. chr(($this->isLightPopulated() ? 1 << 2 : 0) + ($this->isPopulated() ? 1 << 1 : 0) + ($this->isGenerated() ? 1 : 0));
  310. }
  311. public function toBinary(){
  312. $nbt = clone $this->getNBT();
  313. $nbt->xPos = new IntTag("xPos", $this->x);
  314. $nbt->zPos = new IntTag("zPos", $this->z);
  315. if($this->isGenerated()){
  316. $nbt->Blocks = new ByteArrayTag("Blocks", $this->getBlockIdArray());
  317. $nbt->Data = new ByteArrayTag("Data", $this->getBlockDataArray());
  318. $nbt->SkyLight = new ByteArrayTag("SkyLight", $this->getBlockSkyLightArray());
  319. $nbt->BlockLight = new ByteArrayTag("BlockLight", $this->getBlockLightArray());
  320. $nbt->BiomeColors = new IntArrayTag("BiomeColors", $this->getBiomeColorArray());
  321. $nbt->HeightMap = new IntArrayTag("HeightMap", $this->getHeightMapArray());
  322. }
  323. $entities = [];
  324. foreach($this->getEntities() as $entity){
  325. if(!($entity instanceof Player) and !$entity->closed){
  326. $entity->saveNBT();
  327. $entities[] = $entity->namedtag;
  328. }
  329. }
  330. $nbt->Entities = new ListTag("Entities", $entities);
  331. $nbt->Entities->setTagType(NBT::TAG_Compound);
  332. $tiles = [];
  333. foreach($this->getTiles() as $tile){
  334. $tile->saveNBT();
  335. $tiles[] = $tile->namedtag;
  336. }
  337. $nbt->TileEntities = new ListTag("TileEntities", $tiles);
  338. $nbt->TileEntities->setTagType(NBT::TAG_Compound);
  339. $extraData = new BinaryStream();
  340. $extraData->putInt(count($this->getBlockExtraDataArray()));
  341. foreach($this->getBlockExtraDataArray() as $key => $value){
  342. $extraData->putInt($key);
  343. $extraData->putShort($value);
  344. }
  345. $nbt->ExtraData = new ByteArrayTag("ExtraData", $extraData->getBuffer());
  346. $writer = new NBT(NBT::BIG_ENDIAN);
  347. $nbt->setName("Level");
  348. $writer->setData(new CompoundTag("", ["Level" => $nbt]));
  349. return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL);
  350. }
  351. /**
  352. * @return CompoundTag
  353. */
  354. public function getNBT(){
  355. return $this->nbt;
  356. }
  357. /**
  358. * @param int $chunkX
  359. * @param int $chunkZ
  360. * @param LevelProvider $provider
  361. *
  362. * @return Chunk
  363. */
  364. public static function getEmptyChunk($chunkX, $chunkZ, LevelProvider $provider = null){
  365. try{
  366. $chunk = new Chunk($provider instanceof LevelProvider ? $provider : McRegion::class, null);
  367. $chunk->x = $chunkX;
  368. $chunk->z = $chunkZ;
  369. $chunk->data = str_repeat("\x00", 16384);
  370. $chunk->blocks = $chunk->data . $chunk->data;
  371. $chunk->skyLight = str_repeat("\xff", 16384);
  372. $chunk->blockLight = $chunk->data;
  373. $chunk->heightMap = array_fill(0, 256, 0);
  374. $chunk->biomeColors = array_fill(0, 256, 0);
  375. $chunk->nbt->V = new ByteTag("V", 1);
  376. $chunk->nbt->InhabitedTime = new LongTag("InhabitedTime", 0);
  377. $chunk->nbt->TerrainGenerated = new ByteTag("TerrainGenerated", 0);
  378. $chunk->nbt->TerrainPopulated = new ByteTag("TerrainPopulated", 0);
  379. $chunk->nbt->LightPopulated = new ByteTag("LightPopulated", 0);
  380. return $chunk;
  381. }catch(\Throwable $e){
  382. return null;
  383. }
  384. }
  385. }