Merge branch 'next-minor' into modern-world-support

This commit is contained in:
Dylan K. Taylor 2022-02-07 02:22:47 +00:00
commit 4e6fb4b12c
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
37 changed files with 429 additions and 172 deletions

View File

@ -6,3 +6,8 @@ updates:
interval: daily
time: "10:00"
open-pull-requests-limit: 10
- package-ecosystem: gitsubmodule
directory: "/"
schedule:
interval: daily

@ -1 +1 @@
Subproject commit bd329dba08242b6ecb99ef7fbf9a0031dcbbb3ff
Subproject commit 30eed13faaa8995814b1ada426b7e947f891ee3c

View File

@ -1627,3 +1627,21 @@ Released 25th January 2022.
## Fixes
- Fixed ender chest not dropping itself when mined with a Silk Touch pickaxe.
- The correct amount of fall damage is now taken when falling from a height onto hay bales.
# 4.0.9
Released 5th February 2022.
## Fixes
### Core
- The spawn chunk of the default world is no longer loaded during shutdown. Previously, it would attempt to find a safe spawn to teleport players to, only to unload the target world of that safe spawn and not use it.
- The spawn chunk of the default world is no longer loaded when unloading a non-default world containing zero players.
- Fixed chunk version `8` in Bedrock worlds being treated as corrupted. These appeared in worlds between 1.2.13 and 1.8.0.
### API
- Added missing bounds check to `Liquid->setDecay()`.
- Fixed `StringToItemParser` returning concrete instead of concrete powder when given `<color>_concrete_powder`.
### Gameplay
- Cobwebs now drop themselves when broken using shears.
- Fixed spectator players being able to drop items.
- Fixed collision shapes of Bell in different orientations.

14
composer.lock generated
View File

@ -275,16 +275,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "7.3.0+bedrock-1.18.0",
"version": "7.3.1+bedrock-1.18.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "418b4dbaa6720b6c6c4385a4d321d9c0b3dbf14b"
"reference": "c2667453b03ca08a8c54cd89a1fd45cb29196aeb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/418b4dbaa6720b6c6c4385a4d321d9c0b3dbf14b",
"reference": "418b4dbaa6720b6c6c4385a4d321d9c0b3dbf14b",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c2667453b03ca08a8c54cd89a1fd45cb29196aeb",
"reference": "c2667453b03ca08a8c54cd89a1fd45cb29196aeb",
"shasum": ""
},
"require": {
@ -298,7 +298,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.3.1",
"phpstan/phpstan": "1.4.2",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
@ -316,9 +316,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/7.3.0+bedrock-1.18.0"
"source": "https://github.com/pmmp/BedrockProtocol/tree/7.3.1+bedrock-1.18.0"
},
"time": "2022-01-06T20:44:27+00:00"
"time": "2022-01-26T21:14:23+00:00"
},
{
"name": "pocketmine/binaryutils",

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.1.0-BETA3";
public const BASE_VERSION = "4.2.0";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "beta";

View File

@ -39,7 +39,11 @@ class Anvil extends Transparent implements Fallable{
use FallableTrait;
use HorizontalFacingTrait;
private int $damage = 0;
public const UNDAMAGED = 0;
public const SLIGHTLY_DAMAGED = 1;
public const VERY_DAMAGED = 2;
private int $damage = self::UNDAMAGED;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) | ($this->damage << 2);
@ -47,7 +51,7 @@ class Anvil extends Transparent implements Fallable{
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x3);
$this->damage = BlockDataSerializer::readBoundedInt("damage", $stateMeta >> 2, 0, 2);
$this->damage = BlockDataSerializer::readBoundedInt("damage", $stateMeta >> 2, self::UNDAMAGED, self::VERY_DAMAGED);
}
public function getStateBitmask() : int{
@ -62,8 +66,8 @@ class Anvil extends Transparent implements Fallable{
/** @return $this */
public function setDamage(int $damage) : self{
if($damage < 0 || $damage > 2){
throw new \InvalidArgumentException("Damage must be in range 0-2");
if($damage < self::UNDAMAGED || $damage > self::VERY_DAMAGED){
throw new \InvalidArgumentException("Damage must be in range " . self::UNDAMAGED . " ... " . self::VERY_DAMAGED);
}
$this->damage = $damage;
return $this;

View File

@ -30,7 +30,7 @@ use function mt_rand;
class Beetroot extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
if($this->age >= 7){
if($this->age >= self::MAX_AGE){
return [
VanillaItems::BEETROOT(),
VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3))

View File

@ -29,6 +29,7 @@ use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
@ -78,6 +79,28 @@ final class Bell extends Transparent{
return 0b1111;
}
protected function recalculateCollisionBoxes() : array{
if($this->attachmentType->equals(BellAttachmentType::FLOOR())){
return [
AxisAlignedBB::one()->squash(Facing::axis($this->facing), 1 / 4)->trim(Facing::UP, 3 / 16)
];
}
if($this->attachmentType->equals(BellAttachmentType::CEILING())){
return [
AxisAlignedBB::one()->contract(1 / 4, 0, 1 / 4)->trim(Facing::DOWN, 1 / 4)
];
}
$box = AxisAlignedBB::one()
->squash(Facing::axis(Facing::rotateY($this->facing, true)), 1 / 4)
->trim(Facing::UP, 1 / 16)
->trim(Facing::DOWN, 1 / 4);
return [
$this->attachmentType->equals(BellAttachmentType::ONE_WALL()) ? $box->trim($this->facing, 3 / 16) : $box
];
}
public function getAttachmentType() : BellAttachmentType{ return $this->attachmentType; }
/** @return $this */

View File

@ -36,6 +36,7 @@ use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Cactus extends Transparent{
public const MAX_AGE = 15;
protected int $age = 0;
@ -44,7 +45,7 @@ class Cactus extends Transparent{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 15);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -55,8 +56,8 @@ class Cactus extends Transparent{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 15){
throw new \InvalidArgumentException("Age must be in range 0-15");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -101,7 +102,7 @@ class Cactus extends Transparent{
public function onRandomTick() : void{
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
if($this->age === 15){
if($this->age === self::MAX_AGE){
for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break;

View File

@ -35,6 +35,7 @@ use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Cake extends Transparent implements FoodSource{
public const MAX_BITES = 6;
protected int $bites = 0;
@ -43,7 +44,7 @@ class Cake extends Transparent implements FoodSource{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->bites = BlockDataSerializer::readBoundedInt("bites", $stateMeta, 0, 6);
$this->bites = BlockDataSerializer::readBoundedInt("bites", $stateMeta, 0, self::MAX_BITES);
}
public function getStateBitmask() : int{
@ -66,8 +67,8 @@ class Cake extends Transparent implements FoodSource{
/** @return $this */
public function setBites(int $bites) : self{
if($bites < 0 || $bites > 6){
throw new \InvalidArgumentException("Bites must be in range 0-6");
if($bites < 0 || $bites > self::MAX_BITES){
throw new \InvalidArgumentException("Bites must be in range 0 ... " . self::MAX_BITES);
}
$this->bites = $bites;
return $this;
@ -118,7 +119,7 @@ class Cake extends Transparent implements FoodSource{
public function getResidue(){
$clone = clone $this;
$clone->bites++;
if($clone->bites > 6){
if($clone->bites > self::MAX_BITES){
$clone = VanillaBlocks::AIR();
}
return $clone;

View File

@ -31,7 +31,7 @@ class Carrot extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::CARROT()->setCount($this->age >= 7 ? mt_rand(1, 4) : 1)
VanillaItems::CARROT()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 4) : 1)
];
}

View File

@ -41,6 +41,8 @@ use function mt_rand;
class CocoaBlock extends Transparent{
use HorizontalFacingTrait;
public const MAX_AGE = 2;
protected int $age = 0;
protected function writeStateToMeta() : int{
@ -49,7 +51,7 @@ class CocoaBlock extends Transparent{
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = Facing::opposite(BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03));
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta >> 2, 0, 2);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta >> 2, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -60,8 +62,8 @@ class CocoaBlock extends Transparent{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 2){
throw new \InvalidArgumentException("Age must be in range 0-2");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -121,7 +123,7 @@ class CocoaBlock extends Transparent{
}
private function grow() : bool{
if($this->age < 2){
if($this->age < self::MAX_AGE){
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block);
@ -136,7 +138,7 @@ class CocoaBlock extends Transparent{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::COCOA_BEANS()->setCount($this->age === 2 ? mt_rand(2, 3) : 1)
VanillaItems::COCOA_BEANS()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 3) : 1)
];
}

View File

@ -34,6 +34,7 @@ use pocketmine\world\BlockTransaction;
use function mt_rand;
abstract class Crops extends Flowable{
public const MAX_AGE = 7;
protected int $age = 0;
@ -42,7 +43,7 @@ abstract class Crops extends Flowable{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 7);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -53,8 +54,8 @@ abstract class Crops extends Flowable{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 7){
throw new \InvalidArgumentException("Age must be in range 0-7");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -69,11 +70,11 @@ abstract class Crops extends Flowable{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->age < 7 && $item instanceof Fertilizer){
if($this->age < self::MAX_AGE && $item instanceof Fertilizer){
$block = clone $this;
$block->age += mt_rand(2, 5);
if($block->age > 7){
$block->age = 7;
if($block->age > self::MAX_AGE){
$block->age = self::MAX_AGE;
}
$ev = new BlockGrowEvent($this, $block);
@ -100,7 +101,7 @@ abstract class Crops extends Flowable{
}
public function onRandomTick() : void{
if($this->age < 7 && mt_rand(0, 2) === 1){
if($this->age < self::MAX_AGE && mt_rand(0, 2) === 1){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);

View File

@ -33,6 +33,7 @@ use pocketmine\math\Facing;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
protected int $wetness = 0; //"moisture" blockstate property in PC
@ -41,7 +42,7 @@ class Farmland extends Transparent{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->wetness = BlockDataSerializer::readBoundedInt("wetness", $stateMeta, 0, 7);
$this->wetness = BlockDataSerializer::readBoundedInt("wetness", $stateMeta, 0, self::MAX_WETNESS);
}
public function getStateBitmask() : int{
@ -52,8 +53,8 @@ class Farmland extends Transparent{
/** @return $this */
public function setWetness(int $wetness) : self{
if($wetness < 0 || $wetness > 7){
throw new \InvalidArgumentException("Wetness must be in range 0-7");
if($wetness < 0 || $wetness > self::MAX_WETNESS){
throw new \InvalidArgumentException("Wetness must be in range 0 ... " . self::MAX_WETNESS);
}
$this->wetness = $wetness;
return $this;
@ -84,8 +85,8 @@ class Farmland extends Transparent{
}else{
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
}
}elseif($this->wetness < 7){
$this->wetness = 7;
}elseif($this->wetness < self::MAX_WETNESS){
$this->wetness = self::MAX_WETNESS;
$this->position->getWorld()->setBlock($this->position, $this, false);
}
}

View File

@ -41,6 +41,7 @@ use function min;
use function mt_rand;
class Fire extends Flowable{
public const MAX_AGE = 15;
protected int $age = 0;
@ -49,7 +50,7 @@ class Fire extends Flowable{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 15);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -60,8 +61,8 @@ class Fire extends Flowable{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 15){
throw new \InvalidArgumentException("Age must be in range 0-15");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -114,7 +115,7 @@ class Fire extends Flowable{
$down = $this->getSide(Facing::DOWN);
$result = null;
if($this->age < 15 && mt_rand(0, 2) === 0){
if($this->age < self::MAX_AGE && mt_rand(0, 2) === 0){
$this->age++;
$result = $this;
}
@ -122,7 +123,7 @@ class Fire extends Flowable{
if(!$down->burnsForever()){
//TODO: check rain
if($this->age === 15){
if($this->age === self::MAX_AGE){
if(!$down->isFlammable() && mt_rand(0, 3) === 3){ //1/4 chance to extinguish
$canSpread = false;
$result = VanillaBlocks::AIR();
@ -183,7 +184,7 @@ class Fire extends Flowable{
$spreadedFire = false;
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
$fire = clone $this;
$fire->age = min(15, $fire->age + (mt_rand(0, 4) >> 2));
$fire->age = min(self::MAX_AGE, $fire->age + (mt_rand(0, 4) >> 2));
$spreadedFire = $this->spreadBlock($block, $fire);
}
if(!$spreadedFire){
@ -243,7 +244,7 @@ class Fire extends Flowable{
if($maxChance > 0 && mt_rand(0, $randomBound - 1) <= $maxChance){
$new = clone $this;
$new->age = min(15, $this->age + (mt_rand(0, 4) >> 2));
$new->age = min(self::MAX_AGE, $this->age + (mt_rand(0, 4) >> 2));
$this->spreadBlock($block, $new);
}
}

View File

@ -28,11 +28,12 @@ use pocketmine\event\block\BlockMeltEvent;
use function mt_rand;
class FrostedIce extends Ice{
public const MAX_AGE = 3;
protected int $age = 0;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 3);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
protected function writeStateToMeta() : int{
@ -47,8 +48,8 @@ class FrostedIce extends Ice{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 3){
throw new \InvalidArgumentException("Age must be in range 0-3");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -105,7 +106,7 @@ class FrostedIce extends Ice{
* @return bool Whether the ice was destroyed.
*/
private function tryMelt() : bool{
if($this->age >= 3){
if($this->age >= self::MAX_AGE){
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
$ev->call();
if(!$ev->isCancelled()){

View File

@ -37,6 +37,7 @@ use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{
public const MAX_DECAY = 7;
protected BlockIdentifierFlattened $idInfoFlattened;
@ -62,7 +63,7 @@ abstract class Liquid extends Transparent{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->decay = BlockDataSerializer::readBoundedInt("decay", $stateMeta & 0x07, 0, 7);
$this->decay = BlockDataSerializer::readBoundedInt("decay", $stateMeta & 0x07, 0, self::MAX_DECAY);
$this->falling = ($stateMeta & BlockLegacyMetadata::LIQUID_FLAG_FALLING) !== 0;
$this->still = $id === $this->idInfoFlattened->getSecondId();
}
@ -83,6 +84,9 @@ abstract class Liquid extends Transparent{
/** @return $this */
public function setDecay(int $decay) : self{
if($decay < 0 || $decay > self::MAX_DECAY){
throw new \InvalidArgumentException("Decay must be in range 0 ... " . self::MAX_DECAY);
}
$this->decay = $decay;
return $this;
}
@ -279,7 +283,7 @@ abstract class Liquid extends Transparent{
$newDecay = $smallestFlowDecay + $multiplier;
$falling = false;
if($newDecay >= 8 || $smallestFlowDecay < 0){
if($newDecay > self::MAX_DECAY || $smallestFlowDecay < 0){
$newDecay = -1;
}
@ -319,7 +323,7 @@ abstract class Liquid extends Transparent{
$adjacentDecay = $this->decay + $multiplier;
}
if($adjacentDecay < 8){
if($adjacentDecay <= self::MAX_DECAY){
$calculator = new MinimumCostFlowCalculator($this->position->getWorld(), $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
$this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);

View File

@ -33,6 +33,7 @@ use pocketmine\world\BlockTransaction;
use function mt_rand;
class NetherWartPlant extends Flowable{
public const MAX_AGE = 3;
protected int $age = 0;
@ -41,7 +42,7 @@ class NetherWartPlant extends Flowable{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 3);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -52,8 +53,8 @@ class NetherWartPlant extends Flowable{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 3){
throw new \InvalidArgumentException("Age must be in range 0-3");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ..." . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -79,7 +80,7 @@ class NetherWartPlant extends Flowable{
}
public function onRandomTick() : void{
if($this->age < 3 && mt_rand(0, 10) === 0){ //Still growing
if($this->age < self::MAX_AGE && mt_rand(0, 10) === 0){ //Still growing
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block);
@ -92,7 +93,7 @@ class NetherWartPlant extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{
return [
$this->asItem()->setCount($this->age === 3 ? mt_rand(2, 4) : 1)
$this->asItem()->setCount($this->age === self::MAX_AGE ? mt_rand(2, 4) : 1)
];
}
}

View File

@ -31,9 +31,9 @@ class Potato extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
$result = [
VanillaItems::POTATO()->setCount($this->age >= 7 ? mt_rand(1, 5) : 1)
VanillaItems::POTATO()->setCount($this->age >= self::MAX_AGE ? mt_rand(1, 5) : 1)
];
if($this->age >= 7 && mt_rand(0, 49) === 0){
if($this->age >= self::MAX_AGE && mt_rand(0, 49) === 0){
$result[] = VanillaItems::POISONOUS_POTATO();
}
return $result;

View File

@ -37,9 +37,12 @@ class RedstoneRepeater extends Flowable{
use HorizontalFacingTrait;
use PoweredByRedstoneTrait;
public const MIN_DELAY = 1;
public const MAX_DELAY = 4;
protected BlockIdentifierFlattened $idInfoFlattened;
protected int $delay = 1;
protected int $delay = self::MIN_DELAY;
public function __construct(BlockIdentifierFlattened $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->idInfoFlattened = $idInfo;
@ -52,7 +55,7 @@ class RedstoneRepeater extends Flowable{
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->delay = BlockDataSerializer::readBoundedInt("delay", ($stateMeta >> 2) + 1, 1, 4);
$this->delay = BlockDataSerializer::readBoundedInt("delay", ($stateMeta >> 2) + 1, self::MIN_DELAY, self::MAX_DELAY);
$this->powered = $id === $this->idInfoFlattened->getSecondId();
}
@ -68,8 +71,8 @@ class RedstoneRepeater extends Flowable{
/** @return $this */
public function setDelay(int $delay) : self{
if($delay < 1 || $delay > 4){
throw new \InvalidArgumentException("Delay must be in range 1-4");
if($delay < self::MIN_DELAY || $delay > self::MAX_DELAY){
throw new \InvalidArgumentException("Delay must be in range " . self::MIN_DELAY . " ... " . self::MAX_DELAY);
}
$this->delay = $delay;
return $this;
@ -95,8 +98,8 @@ class RedstoneRepeater extends Flowable{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(++$this->delay > 4){
$this->delay = 1;
if(++$this->delay > self::MAX_DELAY){
$this->delay = self::MIN_DELAY;
}
$this->position->getWorld()->setBlock($this->position, $this);
return true;

View File

@ -30,8 +30,10 @@ use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class SeaPickle extends Transparent{
public const MIN_COUNT = 1;
public const MAX_COUNT = 4;
protected int $count = 1;
protected int $count = self::MIN_COUNT;
protected bool $underwater = false;
public function readStateFromData(int $id, int $stateMeta) : void{
@ -51,8 +53,8 @@ class SeaPickle extends Transparent{
/** @return $this */
public function setCount(int $count) : self{
if($count < 1 || $count > 4){
throw new \InvalidArgumentException("Count must be in range 1-4");
if($count < self::MIN_COUNT || $count > self::MAX_COUNT){
throw new \InvalidArgumentException("Count must be in range " . self::MIN_COUNT . " ... " . self::MAX_COUNT);
}
$this->count = $count;
return $this;
@ -83,12 +85,12 @@ class SeaPickle extends Transparent{
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
//TODO: proper placement logic (needs a supporting face below)
return ($blockReplace instanceof SeaPickle && $blockReplace->count < 4) || parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
return ($blockReplace instanceof SeaPickle && $blockReplace->count < self::MAX_COUNT) || parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->underwater = false; //TODO: implement this once we have new water logic in place
if($blockReplace instanceof SeaPickle && $blockReplace->count < 4){
if($blockReplace instanceof SeaPickle && $blockReplace->count < self::MAX_COUNT){
$this->count = $blockReplace->count + 1;
}

View File

@ -36,12 +36,14 @@ use function assert;
use function floor;
class Skull extends Flowable{
public const MIN_ROTATION = 0;
public const MAX_ROTATION = 15;
protected SkullType $skullType;
protected int $facing = Facing::NORTH;
protected bool $noDrops = false;
protected int $rotation = 0; //TODO: split this into floor skull and wall skull handling
protected int $rotation = self::MIN_ROTATION; //TODO: split this into floor skull and wall skull handling
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->skullType = SkullType::SKELETON(); //TODO: this should be a parameter
@ -105,8 +107,8 @@ class Skull extends Flowable{
/** @return $this */
public function setRotation(int $rotation) : self{
if($rotation < 0 || $rotation > 15){
throw new \InvalidArgumentException("Rotation must be a value between 0 and 15");
if($rotation < self::MIN_ROTATION || $rotation > self::MAX_ROTATION){
throw new \InvalidArgumentException("Rotation must be in range " . self::MIN_ROTATION . " ... " . self::MAX_ROTATION);
}
$this->rotation = $rotation;
return $this;

View File

@ -40,14 +40,17 @@ use function max;
class SnowLayer extends Flowable implements Fallable{
use FallableTrait;
protected int $layers = 1;
public const MIN_LAYERS = 1;
public const MAX_LAYERS = 8;
protected int $layers = self::MIN_LAYERS;
protected function writeStateToMeta() : int{
return $this->layers - 1;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->layers = BlockDataSerializer::readBoundedInt("layers", $stateMeta + 1, 1, 8);
$this->layers = BlockDataSerializer::readBoundedInt("layers", $stateMeta + 1, self::MIN_LAYERS, self::MAX_LAYERS);
}
public function getStateBitmask() : int{
@ -58,15 +61,15 @@ class SnowLayer extends Flowable implements Fallable{
/** @return $this */
public function setLayers(int $layers) : self{
if($layers < 1 || $layers > 8){
throw new \InvalidArgumentException("Layers must be in range 1-8");
if($layers < self::MIN_LAYERS || $layers > self::MAX_LAYERS){
throw new \InvalidArgumentException("Layers must be in range " . self::MIN_LAYERS . " ... " . self::MAX_LAYERS);
}
$this->layers = $layers;
return $this;
}
public function canBeReplaced() : bool{
return $this->layers < 8;
return $this->layers < self::MAX_LAYERS;
}
/**
@ -78,12 +81,12 @@ class SnowLayer extends Flowable implements Fallable{
}
private function canBeSupportedBy(Block $b) : bool{
return $b->isSolid() || ($b instanceof SnowLayer && $b->isSameType($this) && $b->layers === 8);
return $b->isSolid() || ($b instanceof SnowLayer && $b->isSameType($this) && $b->layers === self::MAX_LAYERS);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockReplace instanceof SnowLayer){
if($blockReplace->layers >= 8){
if($blockReplace->layers >= self::MAX_LAYERS){
return false;
}
$this->layers = $blockReplace->layers + 1;

View File

@ -35,7 +35,7 @@ abstract class Stem extends Crops{
public function onRandomTick() : void{
if(mt_rand(0, 2) === 1){
if($this->age < 7){
if($this->age < self::MAX_AGE){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);

View File

@ -33,6 +33,7 @@ use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Sugarcane extends Flowable{
public const MAX_AGE = 15;
protected int $age = 0;
@ -41,7 +42,7 @@ class Sugarcane extends Flowable{
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 15);
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, self::MAX_AGE);
}
public function getStateBitmask() : int{
@ -76,8 +77,8 @@ class Sugarcane extends Flowable{
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 15){
throw new \InvalidArgumentException("Age must be in range 0-15");
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
@ -108,7 +109,7 @@ class Sugarcane extends Flowable{
public function onRandomTick() : void{
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
if($this->age === 15){
if($this->age === self::MAX_AGE){
$this->grow();
}else{
++$this->age;

View File

@ -30,7 +30,7 @@ use function mt_rand;
class Wheat extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
if($this->age >= 7){
if($this->age >= self::MAX_AGE){
return [
VanillaItems::WHEAT(),
VanillaItems::WHEAT_SEEDS()->setCount(mt_rand(0, 3))

View File

@ -46,6 +46,9 @@ class DropItemAction extends InventoryAction{
public function onPreExecute(Player $source) : bool{
$ev = new PlayerDropItemEvent($source, $this->targetItem);
if($source->isSpectator()){
$ev->cancel();
}
$ev->call();
if($ev->isCancelled()){
return false;

View File

@ -49,7 +49,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock($prefix("bed"), fn() => VanillaBlocks::BED()->setColor($color));
$result->registerBlock($prefix("carpet"), fn() => VanillaBlocks::CARPET()->setColor($color));
$result->registerBlock($prefix("concrete"), fn() => VanillaBlocks::CONCRETE()->setColor($color));
$result->registerBlock($prefix("concrete_powder"), fn() => VanillaBlocks::CONCRETE()->setColor($color));
$result->registerBlock($prefix("concrete_powder"), fn() => VanillaBlocks::CONCRETE_POWDER()->setColor($color));
$result->registerBlock($prefix("stained_clay"), fn() => VanillaBlocks::STAINED_CLAY()->setColor($color));
$result->registerBlock($prefix("stained_glass"), fn() => VanillaBlocks::STAINED_GLASS()->setColor($color));
$result->registerBlock($prefix("stained_glass_pane"), fn() => VanillaBlocks::STAINED_GLASS_PANE()->setColor($color));

View File

@ -61,7 +61,7 @@ class EncryptionContext{
}
/**
* Returns an EncryptionContext suitable for decrypting Minecraft packets from 1.16.200 and up.
* Returns an EncryptionContext suitable for decrypting Minecraft packets from 1.16.220.50 (protocol version 429) and up.
*
* MCPE uses GCM, but without the auth tag, which defeats the whole purpose of using GCM.
* GCM is just a wrapper around CTR which adds the auth tag, so CTR can replace GCM for this case.

View File

@ -107,18 +107,13 @@ class BanEntry{
}
public function getString() : string{
$str = "";
$str .= $this->getName();
$str .= "|";
$str .= $this->getCreated()->format(self::$format);
$str .= "|";
$str .= $this->getSource();
$str .= "|";
$str .= $this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format);
$str .= "|";
$str .= $this->getReason();
return $str;
return implode("|", [
$this->getName(),
$this->getCreated()->format(self::$format),
$this->getSource(),
$this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format),
$this->getReason()
]);
}
/**

View File

@ -106,7 +106,6 @@ class ZippedResourcePack implements ResourcePack{
}
$mapper = new \JsonMapper();
$mapper->bExceptionOnUndefinedProperty = true;
$mapper->bExceptionOnMissingData = true;
try{

View File

@ -153,16 +153,18 @@ class WorldManager{
}
$this->server->getLogger()->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_unloading($world->getDisplayName())));
try{
$safeSpawn = $this->defaultWorld !== null ? $this->defaultWorld->getSafeSpawn() : null;
}catch(WorldException $e){
$safeSpawn = null;
}
foreach($world->getPlayers() as $player){
if($world === $this->defaultWorld || $safeSpawn === null){
$player->disconnect("Forced default world unload");
}else{
$player->teleport($safeSpawn);
if(count($world->getPlayers()) !== 0){
try{
$safeSpawn = $this->defaultWorld !== null && $this->defaultWorld !== $world ? $this->defaultWorld->getSafeSpawn() : null;
}catch(WorldException $e){
$safeSpawn = null;
}
foreach($world->getPlayers() as $player){
if($safeSpawn === null){
$player->disconnect("Forced default world unload");
}else{
$player->teleport($safeSpawn);
}
}
}

View File

@ -0,0 +1,53 @@
<?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/
*
*
*/
declare(strict_types=1);
namespace pocketmine\world\format\io\leveldb;
final class ChunkDataKey{
private function __construct(){
//NOOP
}
public const HEIGHTMAP_AND_3D_BIOMES = "\x2b";
public const NEW_VERSION = "\x2c"; //since 1.16.100?
public const HEIGHTMAP_AND_2D_BIOMES = "\x2d"; //obsolete since 1.18
public const HEIGHTMAP_AND_2D_BIOME_COLORS = "\x2e"; //obsolete since 1.0
public const SUBCHUNK = "\x2f";
public const LEGACY_TERRAIN = "\x30"; //obsolete since 1.0
public const BLOCK_ENTITIES = "\x31";
public const ENTITIES = "\x32";
public const PENDING_SCHEDULED_TICKS = "\x33";
public const LEGACY_BLOCK_EXTRA_DATA = "\x34"; //obsolete since 1.2.13
public const BIOME_STATES = "\x35"; //TODO: is this still applicable to 1.18.0?
public const FINALIZATION = "\x36";
public const CONVERTER_TAG = "\x37"; //???
public const BORDER_BLOCKS = "\x38";
public const HARDCODED_SPAWNERS = "\x39";
public const PENDING_RANDOM_TICKS = "\x3a";
public const XXHASH_CHECKSUMS = "\x3b"; //obsolete since 1.18
public const GENERATION_SEED = "\x3c";
public const GENERATED_BEFORE_CNC_BLENDING = "\x3d";
public const OLD_VERSION = "\x76";
}

View File

@ -0,0 +1,73 @@
<?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/
*
*
*/
declare(strict_types=1);
namespace pocketmine\world\format\io\leveldb;
final class ChunkVersion{
private function __construct(){
//NOOP
}
public const v0_9_0 = 0;
public const v0_9_2 = 1;
public const v0_9_5 = 2;
public const v1_0_0 = 3;
public const v1_1_0 = 4;
public const v1_1_0_converted_from_console = 5;
public const v1_2_0_2_beta = 6;
public const v1_2_0 = 7;
public const v1_2_13 = 8;
public const v1_8_0 = 9;
public const v1_9_0 = 10;
public const v1_11_0_1_beta = 11;
public const v1_11_0_3_beta = 12;
public const v1_11_0_4_beta = 13;
public const v1_11_1 = 14;
public const v1_12_0_4_beta = 15;
public const v1_12_0_unused1 = 16; //possibly some beta version?
public const v1_12_0_unused2 = 17; //possibly some beta version?
public const v1_16_0_51_beta = 18;
public const v1_16_0 = 19;
public const v1_16_100_52_beta = 20;
public const v1_16_100_57_beta = 21;
public const v1_16_210 = 22;
//Since this version they seem to skip every other version. Possibly the skipped ones are internal use.
public const v1_16_220_50_beta_experimental_caves_cliffs = 23;
public const v1_16_220_50_unused = 24;
public const v1_16_230_50_beta_experimental_caves_cliffs = 25;
public const v1_16_230_50_unused = 26;
public const v1_17_30_23_beta_experimental_caves_cliffs = 27;
public const v1_17_30_23_unused = 28;
public const v1_17_30_25_beta_experimental_caves_cliffs = 29;
public const v1_17_30_25_unused = 30;
public const v1_17_40_20_beta_experimental_caves_cliffs = 31;
public const v1_17_40_unused = 32;
public const v1_18_0_20_beta = 33;
public const v1_18_0_20_unused = 34;
public const v1_18_0_22_beta = 35;
public const v1_18_0_22_unused = 36;
public const v1_18_0_24_beta = 37;
public const v1_18_0_24_unused = 38;
public const v1_18_0_25_beta = 39;
}

View File

@ -71,30 +71,30 @@ use const LEVELDB_ZLIB_RAW_COMPRESSION;
class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
//According to Tomasso, these aren't supposed to be readable anymore. Thankfully he didn't change the readable ones...
protected const TAG_DATA_2D = "\x2d";
protected const TAG_DATA_2D_LEGACY = "\x2e";
protected const TAG_SUBCHUNK_PREFIX = "\x2f";
protected const TAG_LEGACY_TERRAIN = "0";
protected const TAG_BLOCK_ENTITY = "1";
protected const TAG_ENTITY = "2";
protected const TAG_PENDING_TICK = "3";
protected const TAG_BLOCK_EXTRA_DATA = "4";
protected const TAG_BIOME_STATE = "5";
protected const TAG_STATE_FINALISATION = "6";
protected const TAG_DATA_2D = ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES;
protected const TAG_DATA_2D_LEGACY = ChunkDataKey::HEIGHTMAP_AND_2D_BIOME_COLORS;
protected const TAG_SUBCHUNK_PREFIX = ChunkDataKey::SUBCHUNK;
protected const TAG_LEGACY_TERRAIN = ChunkDataKey::LEGACY_TERRAIN;
protected const TAG_BLOCK_ENTITY = ChunkDataKey::BLOCK_ENTITIES;
protected const TAG_ENTITY = ChunkDataKey::ENTITIES;
protected const TAG_PENDING_TICK = ChunkDataKey::PENDING_SCHEDULED_TICKS;
protected const TAG_BLOCK_EXTRA_DATA = ChunkDataKey::LEGACY_BLOCK_EXTRA_DATA;
protected const TAG_BIOME_STATE = ChunkDataKey::BIOME_STATES;
protected const TAG_STATE_FINALISATION = ChunkDataKey::FINALIZATION;
protected const TAG_BORDER_BLOCKS = "8";
protected const TAG_HARDCODED_SPAWNERS = "9";
protected const TAG_BORDER_BLOCKS = ChunkDataKey::BORDER_BLOCKS;
protected const TAG_HARDCODED_SPAWNERS = ChunkDataKey::HARDCODED_SPAWNERS;
protected const FINALISATION_NEEDS_INSTATICKING = 0;
protected const FINALISATION_NEEDS_POPULATION = 1;
protected const FINALISATION_DONE = 2;
protected const TAG_VERSION = "v";
protected const TAG_VERSION = ChunkDataKey::OLD_VERSION;
protected const ENTRY_FLAT_WORLD_LAYERS = "game_flatworldlayers";
protected const CURRENT_LEVEL_CHUNK_VERSION = 7;
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = 8;
protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_2_0; //yes, I know this is wrong, but it ensures vanilla auto-fixes stuff we currently don't
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = SubChunkVersion::PALETTED_MULTI;
/** @var \LevelDB */
protected $db;
@ -192,7 +192,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
protected static function deserializeExtraDataKey(int $chunkVersion, int $key, ?int &$x, ?int &$y, ?int &$z) : void{
if($chunkVersion >= 3){
if($chunkVersion >= ChunkVersion::v1_0_0){
$x = ($key >> 12) & 0xf;
$z = ($key >> 8) & 0xf;
$y = $key & 0xff;
@ -207,7 +207,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
* @return PalettedBlockArray[]
*/
protected function deserializeLegacyExtraData(string $index, int $chunkVersion) : array{
if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) === false || $extraRawData === ""){
if(($extraRawData = $this->db->get($index . ChunkDataKey::LEGACY_BLOCK_EXTRA_DATA)) === false || $extraRawData === ""){
return [];
}
@ -241,7 +241,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
public function loadChunk(int $chunkX, int $chunkZ) : ?ChunkData{
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
$chunkVersionRaw = $this->db->get($index . self::TAG_VERSION);
$chunkVersionRaw = $this->db->get($index . ChunkDataKey::OLD_VERSION);
if($chunkVersionRaw === false){
return null;
}
@ -256,22 +256,23 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
switch($chunkVersion){
case 15: //MCPE 1.12.0.4 beta (???)
case 14: //MCPE 1.11.1.2 (???)
case 13: //MCPE 1.11.0.4 beta (???)
case 12: //MCPE 1.11.0.3 beta (???)
case 11: //MCPE 1.11.0.1 beta (???)
case 10: //MCPE 1.9 (???)
case 9: //MCPE 1.8 (???)
case 7: //MCPE 1.2 (???)
case 6: //MCPE 1.2.0.2 beta (???)
case 4: //MCPE 1.1
case ChunkVersion::v1_12_0_4_beta:
case ChunkVersion::v1_11_1:
case ChunkVersion::v1_11_0_4_beta:
case ChunkVersion::v1_11_0_3_beta:
case ChunkVersion::v1_11_0_1_beta:
case ChunkVersion::v1_9_0:
case ChunkVersion::v1_8_0:
case ChunkVersion::v1_2_13:
case ChunkVersion::v1_2_0:
case ChunkVersion::v1_2_0_2_beta:
case ChunkVersion::v1_1_0:
//TODO: check beds
case 3: //MCPE 1.0
case ChunkVersion::v1_0_0:
$convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion);
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){
if(($data = $this->db->get($index . ChunkDataKey::SUBCHUNK . chr($y))) === false){
continue;
}
@ -285,18 +286,18 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
switch($subChunkVersion){
case 0:
case 2: //these are all identical to version 0, but vanilla respects these so we should also
case 3:
case 4:
case 5:
case 6:
case 7:
case SubChunkVersion::CLASSIC:
case SubChunkVersion::CLASSIC_BUG_2: //these are all identical to version 0, but vanilla respects these so we should also
case SubChunkVersion::CLASSIC_BUG_3:
case SubChunkVersion::CLASSIC_BUG_4:
case SubChunkVersion::CLASSIC_BUG_5:
case SubChunkVersion::CLASSIC_BUG_6:
case SubChunkVersion::CLASSIC_BUG_7:
try{
$blocks = $binaryStream->get(4096);
$blockData = $binaryStream->get(2048);
if($chunkVersion < 4){
if($chunkVersion < ChunkVersion::v1_1_0){
$binaryStream->get(4096); //legacy light info, discard it
$hasBeenUpgraded = true;
}
@ -311,14 +312,14 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, $storages);
break;
case 1: //paletted v1, has a single blockstorage
case SubChunkVersion::PALETTED_SINGLE:
$storages = [$this->deserializePaletted($binaryStream)];
if(isset($convertedLegacyExtraData[$y])){
$storages[] = $convertedLegacyExtraData[$y];
}
$subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, $storages);
break;
case 8:
case SubChunkVersion::PALETTED_MULTI:
//legacy extradata layers intentionally ignored because they aren't supposed to exist in v8
$storageCount = $binaryStream->getByte();
if($storageCount > 0){
@ -336,7 +337,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
}
if(($maps2d = $this->db->get($index . self::TAG_DATA_2D)) !== false){
if(($maps2d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES)) !== false){
$binaryStream = new BinaryStream($maps2d);
try{
@ -347,12 +348,12 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
}
break;
case 2: // < MCPE 1.0
case 1:
case 0: //MCPE 0.9.0.1 beta (first version)
case ChunkVersion::v0_9_5:
case ChunkVersion::v0_9_2:
case ChunkVersion::v0_9_0:
$convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion);
$legacyTerrain = $this->db->get($index . self::TAG_LEGACY_TERRAIN);
$legacyTerrain = $this->db->get($index . ChunkDataKey::LEGACY_TERRAIN);
if($legacyTerrain === false){
throw new CorruptedChunkException("Missing expected LEGACY_TERRAIN tag for format version $chunkVersion");
}
@ -391,7 +392,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/** @var CompoundTag[] $entities */
$entities = [];
if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false && $entityData !== ""){
if(($entityData = $this->db->get($index . ChunkDataKey::ENTITIES)) !== false && $entityData !== ""){
try{
$entities = array_map(fn(TreeRoot $root) => $root->mustGetCompoundTag(), $nbt->readMultiple($entityData));
}catch(NbtDataException $e){
@ -401,7 +402,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/** @var CompoundTag[] $tiles */
$tiles = [];
if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false && $tileData !== ""){
if(($tileData = $this->db->get($index . ChunkDataKey::BLOCK_ENTITIES)) !== false && $tileData !== ""){
try{
$tiles = array_map(fn(TreeRoot $root) => $root->mustGetCompoundTag(), $nbt->readMultiple($tileData));
}catch(NbtDataException $e){
@ -409,7 +410,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
}
$finalisationChr = $this->db->get($index . self::TAG_STATE_FINALISATION);
$finalisationChr = $this->db->get($index . ChunkDataKey::FINALIZATION);
if($finalisationChr !== false){
$finalisation = ord($finalisationChr);
$terrainPopulated = $finalisation === self::FINALISATION_DONE;
@ -437,13 +438,13 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$index = LevelDB::chunkIndex($chunkX, $chunkZ);
$write = new \LevelDBWriteBatch();
$write->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
$write->put($index . ChunkDataKey::OLD_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
$chunk = $chunkData->getChunk();
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)){
$subChunks = $chunk->getSubChunks();
foreach($subChunks as $y => $subChunk){
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
$key = $index . ChunkDataKey::SUBCHUNK . chr($y);
if($subChunk->isEmptyAuthoritative()){
$write->delete($key);
}else{
@ -482,17 +483,17 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){
$write->put($index . self::TAG_DATA_2D, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
$write->put($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
}
//TODO: use this properly
$write->put($index . self::TAG_STATE_FINALISATION, chr($chunk->isPopulated() ? self::FINALISATION_DONE : self::FINALISATION_NEEDS_POPULATION));
$write->put($index . ChunkDataKey::FINALIZATION, chr($chunk->isPopulated() ? self::FINALISATION_DONE : self::FINALISATION_NEEDS_POPULATION));
$this->writeTags($chunkData->getTileNBT(), $index . self::TAG_BLOCK_ENTITY, $write);
$this->writeTags($chunkData->getEntityNBT(), $index . self::TAG_ENTITY, $write);
$this->writeTags($chunkData->getTileNBT(), $index . ChunkDataKey::BLOCK_ENTITIES, $write);
$this->writeTags($chunkData->getEntityNBT(), $index . ChunkDataKey::ENTITIES, $write);
$write->delete($index . self::TAG_DATA_2D_LEGACY);
$write->delete($index . self::TAG_LEGACY_TERRAIN);
$write->delete($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOME_COLORS);
$write->delete($index . ChunkDataKey::LEGACY_TERRAIN);
$this->db->write($write);
}
@ -527,7 +528,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
public function getAllChunks(bool $skipCorrupted = false, ?\Logger $logger = null) : \Generator{
foreach($this->db->getIterator() as $key => $_){
if(strlen($key) === 9 && substr($key, -1) === self::TAG_VERSION){
if(strlen($key) === 9 && substr($key, -1) === ChunkDataKey::OLD_VERSION){
$chunkX = Binary::readLInt(substr($key, 0, 4));
$chunkZ = Binary::readLInt(substr($key, 4, 4));
try{
@ -549,7 +550,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
public function calculateChunkCount() : int{
$count = 0;
foreach($this->db->getIterator() as $key => $_){
if(strlen($key) === 9 && substr($key, -1) === self::TAG_VERSION){
if(strlen($key) === 9 && substr($key, -1) === ChunkDataKey::OLD_VERSION){
$count++;
}
}

View File

@ -0,0 +1,57 @@
<?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/
*
*
*/
declare(strict_types=1);
namespace pocketmine\world\format\io\leveldb;
final class SubChunkVersion{
private function __construct(){
//NOOP
}
/** Original subchunk format as of MCPE 1.0 */
public const CLASSIC = 0;
/** First paletted version, seen in 1.2.13 */
public const PALETTED_SINGLE = 1;
//the following are not used by vanilla, but treated the same as version 0 due to a legacy converter which
//erroneously used the version byte as subchunk height
public const CLASSIC_BUG_2 = 2;
public const CLASSIC_BUG_3 = 3;
public const CLASSIC_BUG_4 = 4;
public const CLASSIC_BUG_5 = 5;
public const CLASSIC_BUG_6 = 6;
public const CLASSIC_BUG_7 = 7;
/**
* Paletted with layers, almost identical to v1, but includes a length prefix and 0 or more storages.
* First seen in 1.4 Update Aquatic to support water inside other blocks.
*/
public const PALETTED_MULTI = 8;
/**
* Paletted with layers, identical to v8 except for a height byte after the layercount byte.
* This seems like a pointless change, but newest versions of the game use it.
* First seen in 1.18 for Caves and Cliffs update (and some experimental versions prior to 1.18).
*/
public const PALETTED_MULTI_WITH_OFFSET = 9;
}

@ -1 +1 @@
Subproject commit 39510af5bcf19eef3b3157c8c51d88a736811591
Subproject commit e884a4c234629126203e769df7c4dbbbc0dc2d49