Merge branch 'stable' into next-minor

This commit is contained in:
Dylan K. Taylor 2019-10-22 18:49:22 +01:00
commit 0591458ef6
52 changed files with 660 additions and 299 deletions

View File

@ -11,4 +11,4 @@ We don't accept support requests on the issue tracker. Please try the following
Documentation: http://pmmp.rtfd.io
Forums: https://forums.pmmp.io
Discord: https://discord.gg/bge7dYQ
Discord: https://discord.gg/bmSAZBG

4
.github/support.yml vendored
View File

@ -5,10 +5,10 @@ supportLabel: "Support request"
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
supportComment: >
Thanks, but this issue tracker not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
Thanks, but this issue tracker is not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bge7dYQ) | [Forums](https://forums.pmmp.io)
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
# Whether to close issues marked as support requests
close: true

3
.gitmodules vendored
View File

@ -10,3 +10,6 @@
[submodule "src/pocketmine/resources/vanilla"]
path = src/pocketmine/resources/vanilla
url = https://github.com/pmmp/BedrockData.git
[submodule "build/php"]
path = build/php
url = https://github.com/pmmp/php-build-scripts.git

View File

@ -5,23 +5,24 @@
[![Build Status](https://travis-ci.org/pmmp/PocketMine-MP.svg?branch=master)](https://travis-ci.org/pmmp/PocketMine-MP)
### Getting started
## Getting started
- [Documentation](http://pmmp.readthedocs.org/)
- [Installation instructions](https://pmmp.readthedocs.io/en/rtfd/installation.html)
- [Docker image](https://hub.docker.com/r/pmmp/pocketmine-mp)
- [Plugin repository](https://poggit.pmmp.io/plugins)
### Discussion
## Discussion/Help
- [Forums](https://forums.pmmp.io/)
- [Community Discord](https://discord.gg/bge7dYQ)
- [Community Discord](https://discord.gg/bmSAZBG)
- [StackOverflow](https://stackoverflow.com/tags/pocketmine)
### For developers
## For developers
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
* [Contributing Guidelines](CONTRIBUTING.md)
### Donate
## Donate
- Bitcoin Cash (BCH): `qq3r46hn6ljnhnqnfwxt5pg3g447eq9jhvw5ddfear`
- Bitcoin (BTC): `171u8K9e4FtU6j3e5sqNoxKUgEw9qWQdRV`
- [Patreon](https://www.patreon.com/pocketminemp)

1
build/php Submodule

@ -0,0 +1 @@
Subproject commit 1b3fe3120cac93ef8d821a1c62a3722576fcfd81

View File

@ -57,3 +57,46 @@ Plugin developers should **only** update their required API to this version if y
- Fixed a memory leak on async task removal in error conditions.
- Fixed scheduled block updates (for example liquid) triggering chunk reloading. This could cause a significant performance issue in some conditions.
- Fixed some minor cosmetic issues in documentation.
# 3.9.4
- Fixed a memory leak when scheduled updates were pending on a chunk being unloaded.
- Fixed plugin detection in crashdumps. Previously `src/pocketmine` anywhere in the path would cause the error to be considered a core crash, regardless of the preceding path.
- Fixed entity metadata types for 1.12. The SLOT type was removed and a COMPOUND_TAG type added. This change involves changes to internal API which may break plugins. **See the warning at the top of this changelog about API versioning.**
- Fixed random and base populator amounts of trees and tallgrass never being initialized. This bug had no obvious effect, but may have become a problem in future PHP versions.
- The following internal methods have been marked as `@deprecated` and documentation warnings added:
- `Entity->getBlocksAround()`
- `Entity->despawnFrom()`
- `Entity->despawnFromAll()`
- Fixed plugin `softdepend` not influencing load order when a soft-depended plugin had an unresolved soft dependency of its own.
- Fixed endless falling of sand on top of fences.
# 3.9.5
- Fixed some issues with multiple consecutive commas inside quotes in form responses.
- Fixed server crash when the manifest json does not contain a json object in a resource pack.
- Ender pearls no longer collide with blocks that do not have any collision boxes.
# 3.9.6
- Updated Composer dependencies to their latest versions.
- Prevent clients repeating the resource pack sequence. This fixes error spam with bugged 1.12 clients.
- `Internet::simpleCurl()` now includes the PocketMine-MP version in the user-agent string.
- Spawn protection is now disabled by default in the setup wizard.
- Default difficulty is now NORMAL(2) instead of EASY(1).
- Fixed crashing on corrupted world manifest and unsupported world formats.
- Fixed `/transferserver` being usable without appropriate permissions.
- `RegionLoader->removeChunk()` now writes the region header as appropriate.
- Fixed performance issue when loading large regions (bug in header validation).
- Fixed skin geometry being removed when the JSON contained comments.
- Added new constants to `EventPacket`.
- Added encode/decode for `StructureTemplateDataExportRequestPacket` and `StructureTemplateDataExportResponsePacket`.
- Fixed broken type asserts in `LevelChunkPacket::withCache()` and `ClientCacheMissResponsePacket::create()`.
- `types\CommandParameter` field `byte1` has been renamed to `flags`.
- Cleaned up public interface of `AvailableCommandsPacket`, removing fields which exposed details of the encoding scheme.
- Improved documentation for the following API methods:
- `pocketmine\item\Item`:
- `addCreativeItem()`
- `removeCreativeItem()`
- `clearCreativeItems()`
- `pocketmine\level\Explosion`:
- `explodeA()`
- `explodeB()`
- Fixed various cosmetic documentation inconsistencies in the core and dependencies.

36
composer.lock generated
View File

@ -92,16 +92,16 @@
},
{
"name": "pocketmine/binaryutils",
"version": "0.1.9",
"version": "0.1.10",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "8b3b1160679398387cb896fd5d06018413437dfa"
"reference": "435f2ee265bce75ef1aa9563f9b60ff36d705e80"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/8b3b1160679398387cb896fd5d06018413437dfa",
"reference": "8b3b1160679398387cb896fd5d06018413437dfa",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/435f2ee265bce75ef1aa9563f9b60ff36d705e80",
"reference": "435f2ee265bce75ef1aa9563f9b60ff36d705e80",
"shasum": ""
},
"require": {
@ -119,23 +119,23 @@
],
"description": "Classes and methods for conveniently handling binary data",
"support": {
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.9",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.10",
"issues": "https://github.com/pmmp/BinaryUtils/issues"
},
"time": "2019-07-22T13:15:53+00:00"
"time": "2019-10-21T14:40:32+00:00"
},
{
"name": "pocketmine/math",
"version": "0.2.2",
"version": "0.2.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Math.git",
"reference": "b755d3fb7025c4ddb7d9d6df0ba7e0b65125e51c"
"reference": "68be8a79fd0169043ef514797c304517cb8a6071"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Math/zipball/b755d3fb7025c4ddb7d9d6df0ba7e0b65125e51c",
"reference": "b755d3fb7025c4ddb7d9d6df0ba7e0b65125e51c",
"url": "https://api.github.com/repos/pmmp/Math/zipball/68be8a79fd0169043ef514797c304517cb8a6071",
"reference": "68be8a79fd0169043ef514797c304517cb8a6071",
"shasum": ""
},
"require": {
@ -153,23 +153,23 @@
],
"description": "PHP library containing math related code used in PocketMine-MP",
"support": {
"source": "https://github.com/pmmp/Math/tree/0.2.2",
"source": "https://github.com/pmmp/Math/tree/0.2.3",
"issues": "https://github.com/pmmp/Math/issues"
},
"time": "2019-01-04T15:42:36+00:00"
"time": "2019-10-21T14:35:10+00:00"
},
{
"name": "pocketmine/nbt",
"version": "0.2.10",
"version": "0.2.11",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "2db27aebe7dc89772aaa8df53361eef801f60063"
"reference": "78784b93632c51f0fad0719b2d6ffe072529db6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/2db27aebe7dc89772aaa8df53361eef801f60063",
"reference": "2db27aebe7dc89772aaa8df53361eef801f60063",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/78784b93632c51f0fad0719b2d6ffe072529db6d",
"reference": "78784b93632c51f0fad0719b2d6ffe072529db6d",
"shasum": ""
},
"require": {
@ -194,10 +194,10 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.10",
"source": "https://github.com/pmmp/NBT/tree/0.2.11",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-07-22T15:22:23+00:00"
"time": "2019-10-21T14:50:43+00:00"
},
{
"name": "pocketmine/raklib",

View File

@ -261,6 +261,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var bool */
public $loggedIn = false;
/** @var bool */
private $resourcePacksDone = false;
/** @var bool */
public $spawned = false;
@ -2056,6 +2059,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{
if($this->resourcePacksDone){
return false;
}
switch($packet->status){
case ResourcePackClientResponsePacket::STATUS_REFUSED:
//TODO: add lang strings for this
@ -2097,6 +2103,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->dataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
$this->resourcePacksDone = true;
$this->completeLoginSequence();
break;
default:
@ -3049,6 +3056,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
if($this->resourcePacksDone){
return false;
}
$manager = $this->server->getResourcePackManager();
$pack = $manager->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){

View File

@ -594,7 +594,7 @@ class Server{
* @return int
*/
public function getDifficulty() : int{
return $this->getConfigInt("difficulty", 1);
return $this->getConfigInt("difficulty", Level::DIFFICULTY_NORMAL);
}
/**
@ -1102,11 +1102,17 @@ class Server{
return false;
}
/**
* @var LevelProvider $provider
* @see LevelProvider::__construct()
*/
$provider = new $providerClass($path);
try{
/**
* @var LevelProvider $provider
* @see LevelProvider::__construct()
*/
$provider = new $providerClass($path);
}catch(LevelException $e){
$this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()]));
return false;
}
try{
GeneratorManager::getGenerator($provider->getGenerator(), true);
}catch(\InvalidArgumentException $e){
@ -1530,7 +1536,7 @@ class Server{
"force-gamemode" => false,
"hardcore" => false,
"pvp" => true,
"difficulty" => 1,
"difficulty" => Level::DIFFICULTY_NORMAL,
"generator-settings" => "",
"level-name" => "world",
"level-seed" => "",
@ -2201,6 +2207,7 @@ class Server{
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.defaultGameMode", [self::getGamemodeString($this->getGamemode())]));
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.donate", [TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET]));
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.startFinished", [round(microtime(true) - \pocketmine\START_TIME, 3)]));
$this->tickProcessor();

View File

@ -22,6 +22,6 @@
namespace pocketmine;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.9.4";
const BASE_VERSION = "3.9.7";
const IS_DEVELOPMENT_BUILD = true;
const BUILD_NUMBER = 0;

View File

@ -69,7 +69,7 @@ class Block extends Position implements BlockIds, Metadatable{
/** @var int|null */
protected $itemId;
/** @var AxisAlignedBB */
/** @var AxisAlignedBB|null */
protected $boundingBox = null;

View File

@ -42,6 +42,10 @@ class TransferServerCommand extends VanillaCommand{
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
if(!$this->testPermission($sender)){
return true;
}
if(count($args) < 1){
throw new InvalidCommandSyntaxException();
}elseif(!($sender instanceof Player)){

View File

@ -23,8 +23,8 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use function assert;
use function is_float;
use function is_int;
@ -140,25 +140,14 @@ class DataPropertyManager{
$this->setPropertyValue($key, Entity::DATA_TYPE_STRING, $value, $force);
}
/**
* @param int $key
*
* @return null|Item
*/
public function getItem(int $key) : ?Item{
$value = $this->getPropertyValue($key, Entity::DATA_TYPE_SLOT);
assert($value instanceof Item or $value === null);
public function getCompoundTag(int $key) : ?CompoundTag{
$value = $this->getPropertyValue($key, Entity::DATA_TYPE_COMPOUND_TAG);
assert($value instanceof CompoundTag or $value === null);
return $value;
}
/**
* @param int $key
* @param Item $value
* @param bool $force
*/
public function setItem(int $key, Item $value, bool $force = false) : void{
$this->setPropertyValue($key, Entity::DATA_TYPE_SLOT, $value, $force);
public function setCompoundTag(int $key, CompoundTag $value, bool $force = false) : void{
$this->setPropertyValue($key, Entity::DATA_TYPE_COMPOUND_TAG, $value, $force);
}
/**

View File

@ -102,7 +102,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_TYPE_INT = 2;
public const DATA_TYPE_FLOAT = 3;
public const DATA_TYPE_STRING = 4;
public const DATA_TYPE_SLOT = 5;
public const DATA_TYPE_COMPOUND_TAG = 5;
public const DATA_TYPE_POS = 6;
public const DATA_TYPE_LONG = 7;
public const DATA_TYPE_VECTOR3F = 8;
@ -1782,6 +1782,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
/**
* @deprecated WARNING: Despite what its name implies, this function DOES NOT return all the blocks around the entity.
* Instead, it returns blocks which have reactions for an entity intersecting with them.
*
* @return Block[]
*/
public function getBlocksAround() : array{
@ -2071,6 +2074,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
/**
* @deprecated WARNING: This function DOES NOT permanently hide the entity from the player. As soon as the entity or
* player moves, the player will once again be able to see the entity.
*
* @param Player $player
* @param bool $send
*/
@ -2085,6 +2091,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
}
/**
* @deprecated WARNING: This function DOES NOT permanently hide the entity from viewers. As soon as the entity or
* player moves, viewers will once again be able to see the entity.
*/
public function despawnFromAll() : void{
foreach($this->hasSpawned as $player){
$this->despawnFrom($player);

View File

@ -587,6 +587,9 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
return (int) min(100, 7 * $this->getXpLevel());
}
/**
* @return PlayerInventory
*/
public function getInventory(){
return $this->inventory;
}

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\entity;
use Ahc\Json\Comment as CommentedJsonDecoder;
use function implode;
use function in_array;
use function json_decode;
use function json_encode;
use function strlen;
@ -129,7 +129,7 @@ class Skin{
*/
public function debloatGeometryData() : void{
if($this->geometryData !== ""){
$this->geometryData = (string) json_encode(json_decode($this->geometryData));
$this->geometryData = (string) json_encode((new CommentedJsonDecoder())->decode($this->geometryData));
}
}
}

View File

@ -33,6 +33,8 @@ use pocketmine\item\ItemFactory;
use pocketmine\level\Position;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\IntTag;
use function abs;
use function floor;
use function get_class;
class FallingBlock extends Entity{
@ -110,7 +112,7 @@ class FallingBlock extends Entity{
$this->flagForDespawn();
$block = $this->level->getBlock($pos);
if($block->getId() > 0 and $block->isTransparent() and !$block->canBeReplaced()){
if(($block->isTransparent() and !$block->canBeReplaced()) or ($this->onGround and abs($this->y - $this->getFloorY()) > 0.001)){
//FIXME: anvils are supposed to destroy torches
$this->getLevel()->dropItem($this, ItemFactory::get($this->getBlock(), $this->getDamage()));
}else{

View File

@ -23,36 +23,14 @@ declare(strict_types=1);
namespace pocketmine\entity\projectile;
use pocketmine\block\Block;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\level\sound\EndermanTeleportSound;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
class EnderPearl extends Throwable{
public const NETWORK_ID = self::ENDER_PEARL;
protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
if($block->getId() !== Block::AIR and empty($block->getCollisionBoxes())){
//TODO: remove this once block collision boxes are fixed properly
$bb = new AxisAlignedBB(
$block->x,
$block->y,
$block->z,
$block->x + 1,
$block->y + 1,
$block->z + 1
);
return $bb->calculateIntercept($start, $end);
}
return parent::calculateInterceptWithBlock($block, $start, $end);
}
protected function onHit(ProjectileHitEvent $event) : void{
$owner = $this->getOwningEntity();
if($owner !== null){

View File

@ -78,8 +78,6 @@ abstract class Event{
* Calls event handlers registered for this event.
*
* @throws \RuntimeException if event call recursion reaches the max depth limit
*
* @throws \ReflectionException
*/
public function call() : void{
if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){

View File

@ -65,6 +65,7 @@ class CraftingTransaction extends InventoryTransaction{
* @param int $iterations
*
* @return int
* @throws TransactionValidationException
*/
protected function matchRecipeItems(array $txItems, array $recipeItems, bool $wildcards, int $iterations = 0) : int{
if(empty($recipeItems)){

View File

@ -137,6 +137,10 @@ class Item implements ItemIds, \JsonSerializable{
}
}
/**
* Removes all previously added items from the creative menu.
* Note: Players who are already online when this is called will not see this change.
*/
public static function clearCreativeItems(){
Item::$creative = [];
}
@ -145,10 +149,22 @@ class Item implements ItemIds, \JsonSerializable{
return Item::$creative;
}
/**
* Adds an item to the creative menu.
* Note: Players who are already online when this is called will not see this change.
*
* @param Item $item
*/
public static function addCreativeItem(Item $item){
Item::$creative[] = clone $item;
}
/**
* Removes an item from the creative menu.
* Note: Players who are already online when this is called will not see this change.
*
* @param Item $item
*/
public static function removeCreativeItem(Item $item){
$index = self::getCreativeItemIndex($item);
if($index !== -1){

View File

@ -48,7 +48,7 @@ class Shears extends Tool{
}
public function onDestroyBlock(Block $block) : bool{
if($block->getHardness() === 0 or $block->isCompatibleWithTool($this)){
if($block->getHardness() === 0.0 or $block->isCompatibleWithTool($this)){
return $this->applyDamage(1);
}
return false;

@ -1 +1 @@
Subproject commit 73ed1ab3e1f2a1644fe908b439f8cf8ed6c12ab5
Subproject commit 85343cfb7f7892bcb42ae7b7f594199cebca7d03

View File

@ -89,6 +89,9 @@ class Explosion{
}
/**
* Calculates which blocks will be destroyed by this explosion. If explodeB() is called without calling this, no blocks
* will be destroyed.
*
* @return bool
*/
public function explodeA() : bool{
@ -148,6 +151,12 @@ class Explosion{
return true;
}
/**
* Executes the explosion's effects on the world. This includes destroying blocks (if any), harming and knocking back entities,
* and creating sounds and particles.
*
* @return bool
*/
public function explodeB() : bool{
$send = [];
$updateBlocks = [];

View File

@ -129,7 +129,7 @@ class LevelDB extends BaseLevelProvider{
$version = $this->levelData->getInt("StorageVersion", INT32_MAX, true);
if($version > self::CURRENT_STORAGE_VERSION){
throw new LevelException("Specified LevelDB world format version ($version) is not supported by " . \pocketmine\NAME);
throw new LevelException("Specified LevelDB world format version ($version) is not supported");
}
}

View File

@ -27,11 +27,10 @@ use pocketmine\level\format\ChunkException;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger;
use function array_fill;
use function ceil;
use function chr;
use function fclose;
use function fgetc;
use function feof;
use function file_exists;
use function filesize;
use function fopen;
@ -40,6 +39,7 @@ use function fseek;
use function ftruncate;
use function fwrite;
use function is_resource;
use function max;
use function ord;
use function pack;
use function str_pad;
@ -60,6 +60,8 @@ class RegionLoader{
public const MAX_SECTOR_LENGTH = 255 << 12; //255 sectors (~0.996 MiB)
public const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
private const FIRST_SECTOR = 2; //location table occupies 0 and 1
public static $COMPRESSION_LEVEL = 7;
/** @var int */
@ -71,8 +73,8 @@ class RegionLoader{
/** @var resource */
protected $filePointer;
/** @var int */
protected $lastSector;
/** @var int[][] [offset in sectors, chunk size in sectors, timestamp] */
protected $nextSector = self::FIRST_SECTOR;
/** @var RegionLocationTableEntry[] */
protected $locationTable = [];
/** @var int */
public $lastUsed = 0;
@ -83,6 +85,9 @@ class RegionLoader{
$this->filePath = $filePath;
}
/**
* @throws CorruptedRegionException
*/
public function open(){
$exists = file_exists($this->filePath);
if(!$exists){
@ -111,7 +116,7 @@ class RegionLoader{
}
protected function isChunkGenerated(int $index) : bool{
return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0);
return !$this->locationTable[$index]->isNull();
}
/**
@ -131,23 +136,25 @@ class RegionLoader{
return null;
}
fseek($this->filePointer, $this->locationTable[$index][0] << 12);
fseek($this->filePointer, $this->locationTable[$index]->getFirstSector() << 12);
$prefix = fread($this->filePointer, 4);
if($prefix === false or strlen($prefix) !== 4){
throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)");
}
$length = Binary::readInt($prefix);
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
if($length >= self::MAX_SECTOR_LENGTH){
throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")");
}
if($length <= 0){ //TODO: if we reached here, the locationTable probably needs updating
return null;
}
if($length > self::MAX_SECTOR_LENGTH){ //corrupted
throw new CorruptedChunkException("Length for chunk x=$x,z=$z ($length) is larger than maximum " . self::MAX_SECTOR_LENGTH);
}
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)");
$this->locationTable[$index][1] = $length >> 12;
if($length > ($this->locationTable[$index]->getSectorCount() << 12)){ //Invalid chunk, bigger than defined number of sectors
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index]->getSectorCount() << 12) . " sectors, got $length sectors)");
$old = $this->locationTable[$index];
$this->locationTable[$index] = new RegionLocationTableEntry($old->getFirstSector(), $length >> 12, time());
$this->writeLocationIndex($index);
}
@ -190,26 +197,22 @@ class RegionLoader{
if($length + 4 > self::MAX_SECTOR_LENGTH){
throw new ChunkException("Chunk is too big! " . ($length + 4) . " > " . self::MAX_SECTOR_LENGTH);
}
$sectors = (int) ceil(($length + 4) / 4096);
$newSize = (int) ceil(($length + 4) / 4096);
$index = self::getChunkOffset($x, $z);
$indexChanged = false;
if($this->locationTable[$index][1] < $sectors){
$this->locationTable[$index][0] = $this->lastSector + 1;
$this->lastSector += $sectors; //The GC will clean this shift "later"
$indexChanged = true;
}elseif($this->locationTable[$index][1] != $sectors){
$indexChanged = true;
$offset = $this->locationTable[$index]->getFirstSector();
if($this->locationTable[$index]->getSectorCount() < $newSize){
$offset = $this->nextSector;
}
$this->locationTable[$index][1] = $sectors;
$this->locationTable[$index][2] = time();
$this->locationTable[$index] = new RegionLocationTableEntry($offset, $newSize, time());
$this->bumpNextFreeSector($this->locationTable[$index]);
fseek($this->filePointer, $this->locationTable[$index][0] << 12);
fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT));
fseek($this->filePointer, $offset << 12);
fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT));
if($indexChanged){
$this->writeLocationIndex($index);
}
$this->writeLocationIndex($index);
}
/**
@ -220,8 +223,8 @@ class RegionLoader{
*/
public function removeChunk(int $x, int $z){
$index = self::getChunkOffset($x, $z);
$this->locationTable[$index][0] = 0;
$this->locationTable[$index][1] = 0;
$this->locationTable[$index] = new RegionLocationTableEntry(0, 0, 0);
$this->writeLocationIndex($index);
}
/**
@ -263,9 +266,11 @@ class RegionLoader{
}
}
/**
* @throws CorruptedRegionException
*/
protected function loadLocationTable(){
fseek($this->filePointer, 0);
$this->lastSector = 1;
$headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH);
if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){
@ -273,43 +278,64 @@ class RegionLoader{
}
$data = unpack("N*", $headerRaw);
/** @var int[] $usedOffsets */
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$index = $data[$i + 1];
$offset = $index >> 8;
if($offset !== 0){
self::getChunkCoords($i, $x, $z);
$fileOffset = $offset << 12;
$timestamp = $data[$i + 1025];
fseek($this->filePointer, $fileOffset);
if(fgetc($this->filePointer) === false){ //Try and read from the location
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
}elseif(isset($usedOffsets[$offset])){
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
}else{
$usedOffsets[$offset] = $i;
}
}
$this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]];
if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){
$this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1;
if($offset === 0){
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
}else{
$this->locationTable[$i] = new RegionLocationTableEntry($offset, $index & 0xff, $timestamp);
$this->bumpNextFreeSector($this->locationTable[$i]);
}
}
$this->checkLocationTableValidity();
fseek($this->filePointer, 0);
}
/**
* @throws CorruptedRegionException
*/
private function checkLocationTableValidity() : void{
/** @var int[] $usedOffsets */
$usedOffsets = [];
for($i = 0; $i < 1024; ++$i){
$entry = $this->locationTable[$i];
if($entry->isNull()){
continue;
}
self::getChunkCoords($i, $x, $z);
$offset = $entry->getFirstSector();
$fileOffset = $offset << 12;
//TODO: more validity checks
fseek($this->filePointer, $fileOffset);
if(feof($this->filePointer)){
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
}
if(isset($usedOffsets[$offset])){
self::getChunkCoords($usedOffsets[$offset], $existingX, $existingZ);
throw new CorruptedRegionException("Found two chunk offsets (chunk1: x=$existingX,z=$existingZ, chunk2: x=$x,z=$z) pointing to the file location $fileOffset");
}
$usedOffsets[$offset] = $i;
}
}
private function writeLocationTable(){
$write = [];
for($i = 0; $i < 1024; ++$i){
$write[] = (($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]);
$write[] = (($this->locationTable[$i]->getFirstSector() << 8) | $this->locationTable[$i]->getSectorCount());
}
for($i = 0; $i < 1024; ++$i){
$write[] = $this->locationTable[$i][2];
$write[] = $this->locationTable[$i]->getTimestamp();
}
fseek($this->filePointer, 0);
fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2);
@ -317,16 +343,21 @@ class RegionLoader{
protected function writeLocationIndex($index){
fseek($this->filePointer, $index << 2);
fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4);
fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index]->getFirstSector() << 8) | $this->locationTable[$index]->getSectorCount()), 4);
fseek($this->filePointer, 4096 + ($index << 2));
fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4);
fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index]->getTimestamp()), 4);
}
protected function createBlank(){
fseek($this->filePointer, 0);
ftruncate($this->filePointer, 8192); // this fills the file with the null byte
$this->lastSector = 1;
$this->locationTable = array_fill(0, 1024, [0, 0, 0]);
for($i = 0; $i < 1024; ++$i){
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
}
}
private function bumpNextFreeSector(RegionLocationTableEntry $entry) : void{
$this->nextSector = max($this->nextSector, $entry->getLastSector()) + 1;
}
public function getX() : int{

View File

@ -0,0 +1,98 @@
<?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\level\format\io\region;
use function range;
class RegionLocationTableEntry{
/** @var int */
private $firstSector;
/** @var int */
private $sectorCount;
/** @var int */
private $timestamp;
/**
* @param int $firstSector
* @param int $sectorCount
* @param int $timestamp
*
* @throws \InvalidArgumentException
*/
public function __construct(int $firstSector, int $sectorCount, int $timestamp){
if($firstSector < 0){
throw new \InvalidArgumentException("Start sector must be positive, got $firstSector");
}
$this->firstSector = $firstSector;
if($sectorCount < 0 or $sectorCount > 255){
throw new \InvalidArgumentException("Sector count must be in range 0...255, got $sectorCount");
}
$this->sectorCount = $sectorCount;
$this->timestamp = $timestamp;
}
/**
* @return int
*/
public function getFirstSector() : int{
return $this->firstSector;
}
/**
* @return int
*/
public function getLastSector() : int{
return $this->firstSector + $this->sectorCount - 1;
}
/**
* Returns an array of sector offsets reserved by this chunk.
* @return int[]
*/
public function getUsedSectors() : array{
return range($this->getFirstSector(), $this->getLastSector());
}
/**
* @return int
*/
public function getSectorCount() : int{
return $this->sectorCount;
}
/**
* @return int
*/
public function getTimestamp() : int{
return $this->timestamp;
}
/**
* @return bool
*/
public function isNull() : bool{
return $this->firstSector === 0 or $this->sectorCount === 0;
}
}

View File

@ -30,8 +30,8 @@ use pocketmine\utils\Random;
class TallGrass extends Populator{
/** @var ChunkManager */
private $level;
private $randomAmount;
private $baseAmount;
private $randomAmount = 1;
private $baseAmount = 0;
public function setRandomAmount($amount){
$this->randomAmount = $amount;
@ -43,7 +43,7 @@ class TallGrass extends Populator{
public function populate(ChunkManager $level, int $chunkX, int $chunkZ, Random $random){
$this->level = $level;
$amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount;
$amount = $random->nextRange(0, $this->randomAmount) + $this->baseAmount;
for($i = 0; $i < $amount; ++$i){
$x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15);
$z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15);

View File

@ -32,8 +32,8 @@ use pocketmine\utils\Random;
class Tree extends Populator{
/** @var ChunkManager */
private $level;
private $randomAmount;
private $baseAmount;
private $randomAmount = 1;
private $baseAmount = 0;
private $type;
@ -51,7 +51,7 @@ class Tree extends Populator{
public function populate(ChunkManager $level, int $chunkX, int $chunkZ, Random $random){
$this->level = $level;
$amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount;
$amount = $random->nextRange(0, $this->randomAmount) + $this->baseAmount;
for($i = 0; $i < $amount; ++$i){
$x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15);
$z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15);

View File

@ -34,7 +34,7 @@ abstract class LightUpdate{
/** @var ChunkManager */
protected $level;
/** @var int[] blockhash => new light level */
/** @var int[][] blockhash => [x, y, z, new light level] */
protected $updateNodes = [];
/** @var \SplQueue */

View File

@ -37,6 +37,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
use pocketmine\network\mcpe\protocol\types\EntityLink;
use pocketmine\network\mcpe\protocol\types\StructureSettings;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\UUID;
use function count;
@ -227,8 +228,8 @@ class NetworkBinaryStream extends BinaryStream{
case Entity::DATA_TYPE_STRING:
$value = $this->getString();
break;
case Entity::DATA_TYPE_SLOT:
$value = $this->getSlot();
case Entity::DATA_TYPE_COMPOUND_TAG:
$value = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
break;
case Entity::DATA_TYPE_POS:
$value = new Vector3();
@ -279,8 +280,8 @@ class NetworkBinaryStream extends BinaryStream{
case Entity::DATA_TYPE_STRING:
$this->putString($d[1]);
break;
case Entity::DATA_TYPE_SLOT:
$this->putSlot($d[1]);
case Entity::DATA_TYPE_COMPOUND_TAG:
$this->put((new NetworkLittleEndianNBTStream())->write($d[1]));
break;
case Entity::DATA_TYPE_POS:
$v = $d[1];
@ -592,4 +593,40 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarLong($data->varlong1);
}
}
protected function getStructureSettings() : StructureSettings{
$result = new StructureSettings();
$result->paletteName = $this->getString();
$result->ignoreEntities = $this->getBool();
$result->ignoreBlocks = $this->getBool();
$this->getBlockPosition($result->structureSizeX, $result->structureSizeY, $result->structureSizeZ);
$this->getBlockPosition($result->structureOffsetX, $result->structureOffsetY, $result->structureOffsetZ);
$result->lastTouchedByPlayerID = $this->getEntityUniqueId();
$result->rotation = $this->getByte();
$result->mirror = $this->getByte();
$result->integrityValue = $this->getFloat();
$result->integritySeed = $this->getInt();
return $result;
}
protected function putStructureSettings(StructureSettings $structureSettings) : void{
$this->putString($structureSettings->paletteName);
$this->putBool($structureSettings->ignoreEntities);
$this->putBool($structureSettings->ignoreBlocks);
$this->putBlockPosition($structureSettings->structureSizeX, $structureSettings->structureSizeY, $structureSettings->structureSizeZ);
$this->putBlockPosition($structureSettings->structureOffsetX, $structureSettings->structureOffsetY, $structureSettings->structureOffsetZ);
$this->putEntityUniqueId($structureSettings->lastTouchedByPlayerID);
$this->putByte($structureSettings->rotation);
$this->putByte($structureSettings->mirror);
$this->putFloat($structureSettings->integrityValue);
$this->putInt($structureSettings->integritySeed);
}
}

View File

@ -74,7 +74,6 @@ use function implode;
use function json_decode;
use function json_last_error_msg;
use function preg_match;
use function preg_split;
use function strlen;
use function substr;
use function trim;
@ -270,16 +269,31 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
*/
private static function stupid_json_decode(string $json, bool $assoc = false){
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
$parts = preg_split('/(?:"(?:\\"|[^"])*"|)\K(,)/', $matches[1]); //Splits on commas not inside quotes, ignoring escaped quotes
foreach($parts as $k => $part){
$part = trim($part);
if($part === ""){
$part = "\"\"";
$raw = $matches[1];
$lastComma = -1;
$newParts = [];
$quoteType = null;
for($i = 0, $len = strlen($raw); $i <= $len; ++$i){
if($i === $len or ($raw[$i] === "," and $quoteType === null)){
$part = substr($raw, $lastComma + 1, $i - ($lastComma + 1));
if(trim($part) === ""){ //regular parts will have quotes or something else that makes them non-empty
$part = '""';
}
$newParts[] = $part;
$lastComma = $i;
}elseif($raw[$i] === '"'){
if($quoteType === null){
$quoteType = $raw[$i];
}elseif($raw[$i] === $quoteType){
for($backslashes = 0; $backslashes < $i && $raw[$i - $backslashes - 1] === "\\"; ++$backslashes){}
if(($backslashes % 2) === 0){ //unescaped quote
$quoteType = null;
}
}
}
$parts[$k] = $part;
}
$fixed = "[" . implode(",", $parts) . "]";
$fixed = "[" . implode(",", $newParts) . "]";
if(($ret = json_decode($fixed, $assoc)) === null){
throw new \InvalidArgumentException("Failed to fix JSON: " . json_last_error_msg() . "(original: $json, modified: $fixed)");
}

View File

@ -29,11 +29,6 @@ use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\CommandData;
use pocketmine\network\mcpe\protocol\types\CommandEnum;
use pocketmine\network\mcpe\protocol\types\CommandParameter;
use function array_flip;
use function array_keys;
use function array_map;
use function array_search;
use function array_values;
use function count;
use function dechex;
@ -83,30 +78,6 @@ class AvailableCommandsPacket extends DataPacket{
*/
public const ARG_FLAG_POSTFIX = 0x1000000;
/**
* @var string[]
* A list of every single enum value for every single command in the packet, including alias names.
*/
public $enumValues = [];
/** @var int */
private $enumValuesCount = 0;
/**
* @var string[]
* A list of argument postfixes. Used for the /xp command's <int>L.
*/
public $postfixes = [];
/**
* @var CommandEnum[]
* List of command enums, from command aliases to argument enums.
*/
public $enums = [];
/**
* @var int[] string => int map of enum name to index
*/
private $enumMap = [];
/**
* @var CommandData[]
* List of command data, including name, description, alias indexes and parameters.
@ -121,20 +92,26 @@ class AvailableCommandsPacket extends DataPacket{
public $softEnums = [];
protected function decodePayload(){
for($i = 0, $this->enumValuesCount = $this->getUnsignedVarInt(); $i < $this->enumValuesCount; ++$i){
$this->enumValues[] = $this->getString();
/** @var string[] $enumValues */
$enumValues = [];
for($i = 0, $enumValuesCount = $this->getUnsignedVarInt(); $i < $enumValuesCount; ++$i){
$enumValues[] = $this->getString();
}
/** @var string[] $postfixes */
$postfixes = [];
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$postfixes[] = $this->getString();
}
/** @var CommandEnum[] $enums */
$enums = [];
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$enums[] = $this->getEnum($enumValues);
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->postfixes[] = $this->getString();
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->enums[] = $this->getEnum();
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$this->commandData[] = $this->getCommandData();
$this->commandData[] = $this->getCommandData($enums, $postfixes);
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
@ -142,17 +119,26 @@ class AvailableCommandsPacket extends DataPacket{
}
}
protected function getEnum() : CommandEnum{
/**
* @param string[] $enumValueList
*
* @return CommandEnum
* @throws \UnexpectedValueException
* @throws BinaryDataException
*/
protected function getEnum(array $enumValueList) : CommandEnum{
$retval = new CommandEnum();
$retval->enumName = $this->getString();
$listSize = count($enumValueList);
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$index = $this->getEnumValueIndex();
if(!isset($this->enumValues[$index])){
$index = $this->getEnumValueIndex($listSize);
if(!isset($enumValueList[$index])){
throw new \UnexpectedValueException("Invalid enum value index $index");
}
//Get the enum value from the initial pile of mess
$retval->enumValues[] = $this->enumValues[$index];
$retval->enumValues[] = $enumValueList[$index];
}
return $retval;
@ -170,17 +156,21 @@ class AvailableCommandsPacket extends DataPacket{
return $retval;
}
protected function putEnum(CommandEnum $enum){
/**
* @param CommandEnum $enum
* @param int[] $enumValueMap string enum name -> int index
*/
protected function putEnum(CommandEnum $enum, array $enumValueMap) : void{
$this->putString($enum->enumName);
$this->putUnsignedVarInt(count($enum->enumValues));
$listSize = count($enumValueMap);
foreach($enum->enumValues as $value){
//Dumb bruteforce search. I hate this packet.
$index = array_search($value, $this->enumValues, true);
if($index === false){
$index = $enumValueMap[$value] ?? -1;
if($index === -1){
throw new \InvalidStateException("Enum value '$value' not found");
}
$this->putEnumValueIndex($index);
$this->putEnumValueIndex($index, $listSize);
}
}
@ -193,33 +183,47 @@ class AvailableCommandsPacket extends DataPacket{
}
}
protected function getEnumValueIndex() : int{
if($this->enumValuesCount < 256){
/**
* @param int $valueCount
*
* @return int
* @throws BinaryDataException
*/
protected function getEnumValueIndex(int $valueCount) : int{
if($valueCount < 256){
return $this->getByte();
}elseif($this->enumValuesCount < 65536){
}elseif($valueCount < 65536){
return $this->getLShort();
}else{
return $this->getLInt();
}
}
protected function putEnumValueIndex(int $index){
if($this->enumValuesCount < 256){
protected function putEnumValueIndex(int $index, int $valueCount) : void{
if($valueCount < 256){
$this->putByte($index);
}elseif($this->enumValuesCount < 65536){
}elseif($valueCount < 65536){
$this->putLShort($index);
}else{
$this->putLInt($index);
}
}
protected function getCommandData() : CommandData{
/**
* @param CommandEnum[] $enums
* @param string[] $postfixes
*
* @return CommandData
* @throws \UnexpectedValueException
* @throws BinaryDataException
*/
protected function getCommandData(array $enums, array $postfixes) : CommandData{
$retval = new CommandData();
$retval->commandName = $this->getString();
$retval->commandDescription = $this->getString();
$retval->flags = $this->getByte();
$retval->permission = $this->getByte();
$retval->aliases = $this->enums[$this->getLInt()] ?? null;
$retval->aliases = $enums[$this->getLInt()] ?? null;
for($overloadIndex = 0, $overloadCount = $this->getUnsignedVarInt(); $overloadIndex < $overloadCount; ++$overloadIndex){
for($paramIndex = 0, $paramCount = $this->getUnsignedVarInt(); $paramIndex < $paramCount; ++$paramIndex){
@ -227,17 +231,17 @@ class AvailableCommandsPacket extends DataPacket{
$parameter->paramName = $this->getString();
$parameter->paramType = $this->getLInt();
$parameter->isOptional = $this->getBool();
$parameter->byte1 = $this->getByte();
$parameter->flags = $this->getByte();
if($parameter->paramType & self::ARG_FLAG_ENUM){
$index = ($parameter->paramType & 0xffff);
$parameter->enum = $this->enums[$index] ?? null;
$parameter->enum = $enums[$index] ?? null;
if($parameter->enum === null){
throw new \UnexpectedValueException("deserializing $retval->commandName parameter $parameter->paramName: expected enum at $index, but got none");
}
}elseif($parameter->paramType & self::ARG_FLAG_POSTFIX){
$index = ($parameter->paramType & 0xffff);
$parameter->postfix = $this->postfixes[$index] ?? null;
$parameter->postfix = $postfixes[$index] ?? null;
if($parameter->postfix === null){
throw new \UnexpectedValueException("deserializing $retval->commandName parameter $parameter->paramName: expected postfix at $index, but got none");
}
@ -252,14 +256,19 @@ class AvailableCommandsPacket extends DataPacket{
return $retval;
}
protected function putCommandData(CommandData $data){
/**
* @param CommandData $data
* @param int[] $enumIndexes string enum name -> int index
* @param int[] $postfixIndexes
*/
protected function putCommandData(CommandData $data, array $enumIndexes, array $postfixIndexes) : void{
$this->putString($data->commandName);
$this->putString($data->commandDescription);
$this->putByte($data->flags);
$this->putByte($data->permission);
if($data->aliases !== null){
$this->putLInt($this->enumMap[$data->aliases->enumName] ?? -1);
$this->putLInt($enumIndexes[$data->aliases->enumName] ?? -1);
}else{
$this->putLInt(-1);
}
@ -272,10 +281,10 @@ class AvailableCommandsPacket extends DataPacket{
$this->putString($parameter->paramName);
if($parameter->enum !== null){
$type = self::ARG_FLAG_ENUM | self::ARG_FLAG_VALID | ($this->enumMap[$parameter->enum->enumName] ?? -1);
$type = self::ARG_FLAG_ENUM | self::ARG_FLAG_VALID | ($enumIndexes[$parameter->enum->enumName] ?? -1);
}elseif($parameter->postfix !== null){
$key = array_search($parameter->postfix, $this->postfixes, true);
if($key === false){
$key = $postfixIndexes[$parameter->postfix] ?? -1;
if($key === -1){
throw new \InvalidStateException("Postfix '$parameter->postfix' not in postfixes array");
}
$type = self::ARG_FLAG_POSTFIX | $key;
@ -285,12 +294,12 @@ class AvailableCommandsPacket extends DataPacket{
$this->putLInt($type);
$this->putBool($parameter->isOptional);
$this->putByte($parameter->byte1);
$this->putByte($parameter->flags);
}
}
}
private function argTypeToString(int $argtype) : string{
private function argTypeToString(int $argtype, array $postfixes) : string{
if($argtype & self::ARG_FLAG_VALID){
if($argtype & self::ARG_FLAG_ENUM){
return "stringenum (" . ($argtype & 0xffff) . ")";
@ -319,7 +328,7 @@ class AvailableCommandsPacket extends DataPacket{
return "command";
}
}elseif($argtype & self::ARG_FLAG_POSTFIX){
$postfix = $this->postfixes[$argtype & 0xffff];
$postfix = $postfixes[$argtype & 0xffff];
return "int (postfix $postfix)";
}else{
@ -330,15 +339,22 @@ class AvailableCommandsPacket extends DataPacket{
}
protected function encodePayload(){
$enumValuesMap = [];
$postfixesMap = [];
$enumMap = [];
/** @var int[] $enumValueIndexes */
$enumValueIndexes = [];
/** @var int[] $postfixIndexes */
$postfixIndexes = [];
/** @var int[] $enumIndexes */
$enumIndexes = [];
/** @var CommandEnum[] $enums */
$enums = [];
foreach($this->commandData as $commandData){
if($commandData->aliases !== null){
$enumMap[$commandData->aliases->enumName] = $commandData->aliases;
if(!isset($enumIndexes[$commandData->aliases->enumName])){
$enums[$enumIndexes[$commandData->aliases->enumName] = count($enumIndexes)] = $commandData->aliases;
}
foreach($commandData->aliases->enumValues as $str){
$enumValuesMap[$str] = true;
$enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); //latest index
}
}
@ -349,41 +365,39 @@ class AvailableCommandsPacket extends DataPacket{
*/
foreach($overload as $parameter){
if($parameter->enum !== null){
$enumMap[$parameter->enum->enumName] = $parameter->enum;
if(!isset($enumIndexes[$parameter->enum->enumName])){
$enums[$enumIndexes[$parameter->enum->enumName] = count($enumIndexes)] = $parameter->enum;
}
foreach($parameter->enum->enumValues as $str){
$enumValuesMap[$str] = true;
$enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes);
}
}
if($parameter->postfix !== null){
$postfixesMap[$parameter->postfix] = true;
$postfixIndexes[$parameter->postfix] = $postfixIndexes[$parameter->postfix] ?? count($postfixIndexes);
}
}
}
}
$this->enumValues = array_map('\strval', array_keys($enumValuesMap)); //stupid PHP key casting D:
$this->putUnsignedVarInt($this->enumValuesCount = count($this->enumValues));
foreach($this->enumValues as $enumValue){
$this->putString($enumValue);
$this->putUnsignedVarInt(count($enumValueIndexes));
foreach($enumValueIndexes as $enumValue => $index){
$this->putString((string) $enumValue); //stupid PHP key casting D:
}
$this->postfixes = array_map('\strval', array_keys($postfixesMap));
$this->putUnsignedVarInt(count($this->postfixes));
foreach($this->postfixes as $postfix){
$this->putString($postfix);
$this->putUnsignedVarInt(count($postfixIndexes));
foreach($postfixIndexes as $postfix => $index){
$this->putString((string) $postfix); //stupid PHP key casting D:
}
$this->enums = array_values($enumMap);
$this->enumMap = array_flip(array_keys($enumMap));
$this->putUnsignedVarInt(count($this->enums));
foreach($this->enums as $enum){
$this->putEnum($enum);
$this->putUnsignedVarInt(count($enums));
foreach($enums as $enum){
$this->putEnum($enum, $enumValueIndexes);
}
$this->putUnsignedVarInt(count($this->commandData));
foreach($this->commandData as $data){
$this->putCommandData($data);
$this->putCommandData($data, $enumIndexes, $postfixIndexes);
}
$this->putUnsignedVarInt(count($this->softEnums));

View File

@ -39,9 +39,9 @@ class BossEventPacket extends DataPacket{
public const TYPE_HIDE = 2;
/* C2S: Unregisters a player from a boss fight. */
public const TYPE_UNREGISTER_PLAYER = 3;
/* S2C: Appears not to be implemented. Currently bar percentage only appears to change in response to the target entity's health. */
/* S2C: Sets the bar percentage. */
public const TYPE_HEALTH_PERCENT = 4;
/* S2C: Also appears to not be implemented. Title client-side sticks as the target entity's nametag, or their entity type name if not set. */
/* S2C: Sets title of the bar. */
public const TYPE_TITLE = 5;
/* S2C: Not sure on this. Includes color and overlay fields, plus an unknown short. TODO: check this */
public const TYPE_UNKNOWN_6 = 6;

View File

@ -42,7 +42,7 @@ class ClientCacheMissResponsePacket extends DataPacket/* implements ClientboundP
*/
public static function create(array $blobs) : self{
//type check
(static function(ChunkCacheBlob ...$blobs){})($blobs);
(static function(ChunkCacheBlob ...$blobs){})(...$blobs);
$result = new self;
$result->blobs = $blobs;

View File

@ -43,6 +43,11 @@ class EventPacket extends DataPacket{
public const TYPE_PATTERN_REMOVED = 10; //???
public const TYPE_COMMANED_EXECUTED = 11;
public const TYPE_FISH_BUCKETED = 12;
public const TYPE_MOB_BORN = 13;
public const TYPE_PET_DIED = 14;
public const TYPE_CAULDRON_BLOCK_USED = 15;
public const TYPE_COMPOSTER_BLOCK_USED = 16;
public const TYPE_BELL_BLOCK_USED = 17;
/** @var int */
public $playerRuntimeId;

View File

@ -57,7 +57,7 @@ class LevelChunkPacket extends DataPacket/* implements ClientboundPacket*/{
}
public static function withCache(int $chunkX, int $chunkZ, int $subChunkCount, array $usedBlobHashes, string $extraPayload) : self{
(static function(int ...$hashes){})($usedBlobHashes);
(static function(int ...$hashes){})(...$usedBlobHashes);
$result = new self;
$result->chunkX = $chunkX;
$result->chunkZ = $chunkZ;

View File

@ -26,16 +26,39 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\StructureSettings;
class StructureTemplateDataExportRequestPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_EXPORT_REQUEST_PACKET;
public const TYPE_ALWAYS_LOAD = 1;
public const TYPE_CREATE_AND_LOAD = 2;
/** @var string */
public $structureTemplateName;
/** @var int */
public $structureBlockX;
/** @var int */
public $structureBlockY;
/** @var int */
public $structureBlockZ;
/** @var StructureSettings */
public $structureSettings;
/** @var int */
public $structureTemplateResponseType;
protected function decodePayload() : void{
//TODO
$this->structureTemplateName = $this->getString();
$this->getBlockPosition($this->structureBlockX, $this->structureBlockY, $this->structureBlockZ);
$this->structureSettings = $this->getStructureSettings();
$this->structureTemplateResponseType = $this->getByte();
}
protected function encodePayload() : void{
//TODO
$this->putString($this->structureTemplateName);
$this->putBlockPosition($this->structureBlockX, $this->structureBlockY, $this->structureBlockZ);
$this->putStructureSettings($this->structureSettings);
$this->putByte($this->structureTemplateResponseType);
}
public function handle(NetworkSession $handler) : bool{

View File

@ -30,12 +30,24 @@ use pocketmine\network\mcpe\NetworkSession;
class StructureTemplateDataExportResponsePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::STRUCTURE_TEMPLATE_DATA_EXPORT_RESPONSE_PACKET;
/** @var string */
public $structureTemplateName;
/** @var string|null */
public $namedtag;
protected function decodePayload() : void{
//TODO
$this->structureTemplateName = $this->getString();
if($this->getBool()){
$this->namedtag = $this->getRemaining();
}
}
protected function encodePayload() : void{
//TODO
$this->putString($this->structureTemplateName);
$this->putBool($this->namedtag !== null);
if($this->namedtag !== null){
$this->put($this->namedtag);
}
}
public function handle(NetworkSession $handler) : bool{

View File

@ -31,7 +31,7 @@ class CommandParameter{
/** @var bool */
public $isOptional;
/** @var int */
public $byte1 = 0; //unknown, always zero except for in /gamerule command
public $flags = 0; //shows enum name if 1, always zero except for in /gamerule command
/** @var CommandEnum|null */
public $enum;
/** @var string|null */

View File

@ -0,0 +1,55 @@
<?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\network\mcpe\protocol\types;
class StructureSettings{
/** @var string */
public $paletteName;
/** @var bool */
public $ignoreEntities;
/** @var bool */
public $ignoreBlocks;
/** @var int */
public $structureSizeX;
/** @var int */
public $structureSizeY;
/** @var int */
public $structureSizeZ;
/** @var int */
public $structureOffsetX;
/** @var int */
public $structureOffsetY;
/** @var int */
public $structureOffsetZ;
/** @var int */
public $lastTouchedByPlayerID;
/** @var int */
public $rotation;
/** @var int */
public $mirror;
/** @var float */
public $integrityValue;
/** @var int */
public $integritySeed;
}

View File

@ -112,12 +112,7 @@ class Permission{
$desc = null;
$children = [];
if(isset($data["default"])){
$value = Permission::getByName($data["default"]);
if($value !== null){
$default = $value;
}else{
throw new \InvalidStateException("'default' key contained unknown value");
}
$default = Permission::getByName($data["default"]);
}
if(isset($data["children"])){

View File

@ -305,7 +305,7 @@ class PluginManager{
while(count($plugins) > 0){
$missingDependency = true;
$loadedThisLoop = 0;
foreach($plugins as $name => $file){
if(isset($dependencies[$name])){
foreach($dependencies[$name] as $key => $dependency){
@ -329,7 +329,14 @@ class PluginManager{
if(isset($softDependencies[$name])){
foreach($softDependencies[$name] as $key => $dependency){
if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){
$this->server->getLogger()->debug("Successfully resolved soft dependency \"$dependency\" for plugin \"$name\"");
unset($softDependencies[$name][$key]);
}elseif(!isset($plugins[$dependency])){
//this dependency is never going to be resolved, so don't bother trying
$this->server->getLogger()->debug("Skipping resolution of missing soft dependency \"$dependency\" for plugin \"$name\"");
unset($softDependencies[$name][$key]);
}else{
$this->server->getLogger()->debug("Deferring resolution of soft dependency \"$dependency\" for plugin \"$name\" (found but not loaded yet)");
}
}
@ -340,7 +347,7 @@ class PluginManager{
if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){
unset($plugins[$name]);
$missingDependency = false;
$loadedThisLoop++;
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}else{
@ -349,27 +356,12 @@ class PluginManager{
}
}
if($missingDependency){
foreach($plugins as $name => $file){
if(!isset($dependencies[$name])){
unset($softDependencies[$name]);
unset($plugins[$name]);
$missingDependency = false;
if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){
$loadedPlugins[$name] = $plugin;
}else{
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name]));
}
}
}
if($loadedThisLoop === 0){
//No plugins loaded :(
if($missingDependency){
foreach($plugins as $name => $file){
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
}
$plugins = [];
foreach($plugins as $name => $file){
$this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"]));
}
$plugins = [];
}
}

View File

@ -73,6 +73,7 @@ interface ResourcePack{
* @param int $length Maximum length of data to return.
*
* @return string byte-array
* @throws \InvalidArgumentException if the chunk does not exist
*/
public function getPackChunk(int $start, int $length) : string;
}

View File

@ -75,6 +75,7 @@ class ZippedResourcePack implements ResourcePack{
/**
* @param string $zipPath Path to the resource pack zip
* @throws ResourcePackException
*/
public function __construct(string $zipPath){
$this->path = $zipPath;
@ -104,7 +105,9 @@ class ZippedResourcePack implements ResourcePack{
}catch(\RuntimeException $e){
throw new ResourcePackException("Failed to parse manifest.json: " . $e->getMessage(), $e->getCode(), $e);
}
if(!($manifest instanceof \stdClass)){
throw new ResourcePackException("manifest.json should contain a JSON object, not " . gettype($manifest));
}
if(!self::verifyManifest($manifest)){
throw new ResourcePackException("manifest.json is missing required fields");
}
@ -148,7 +151,7 @@ class ZippedResourcePack implements ResourcePack{
public function getPackChunk(int $start, int $length) : string{
fseek($this->fileResource, $start);
if(feof($this->fileResource)){
throw new \RuntimeException("Requested a resource pack chunk with invalid start offset");
throw new \InvalidArgumentException("Requested a resource pack chunk with invalid start offset");
}
return fread($this->fileResource, $length);
}

View File

@ -27,7 +27,7 @@ use pocketmine\utils\Utils;
abstract class Task{
/** @var TaskHandler */
/** @var TaskHandler|null */
private $taskHandler = null;
/**

View File

@ -42,7 +42,7 @@ class Chest extends Spawnable implements InventoryHolder, Container, Nameable{
/** @var ChestInventory */
protected $inventory;
/** @var DoubleChestInventory */
/** @var DoubleChestInventory|null */
protected $doubleInventory = null;
/** @var int|null */

View File

@ -210,7 +210,7 @@ class Internet{
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME . "/" . \pocketmine\VERSION], $extraHeaders),
CURLOPT_HEADER => true
]);
try{

View File

@ -61,7 +61,7 @@ class MainLogger extends \AttachableThreadedLogger{
/** @var \Threaded */
protected $logStream;
/** @var bool */
protected $shutdown;
protected $shutdown = false;
/** @var bool */
protected $logDebug;
/** @var MainLogger */
@ -344,7 +344,6 @@ class MainLogger extends \AttachableThreadedLogger{
}
public function run(){
$this->shutdown = false;
$logResource = fopen($this->logFile, "ab");
if(!is_resource($logResource)){
throw new \RuntimeException("Couldn't open log file");

View File

@ -28,6 +28,7 @@ declare(strict_types=1);
namespace pocketmine\wizard;
use pocketmine\lang\BaseLang;
use pocketmine\Player;
use pocketmine\utils\Config;
use pocketmine\utils\Internet;
use pocketmine\utils\InternetException;
@ -45,7 +46,7 @@ class SetupWizard{
public const DEFAULT_NAME = \pocketmine\NAME . " Server";
public const DEFAULT_PORT = 19132;
public const DEFAULT_PLAYERS = 20;
public const DEFAULT_GAMEMODE = 0;
public const DEFAULT_GAMEMODE = Player::SURVIVAL;
/** @var BaseLang */
private $lang;
@ -162,7 +163,7 @@ LICENSE;
$this->message($this->lang->get("spawn_protection_info"));
if(strtolower($this->getInput($this->lang->get("spawn_protection"), "y", "Y/n")) === "n"){
if(strtolower($this->getInput($this->lang->get("spawn_protection"), "n", "y/N")) === "n"){
$config->set("spawn-protection", -1);
}else{
$config->set("spawn-protection", 16);

View File

@ -26,6 +26,12 @@ namespace pocketmine\network\mcpe;
use PHPUnit\Framework\TestCase;
class StupidJsonDecodeTest extends TestCase{
/** @var \Closure */
private $stupidJsonDecodeFunc;
public function setUp() : void{
$this->stupidJsonDecodeFunc = (new \ReflectionMethod(PlayerNetworkSessionAdapter::class, 'stupid_json_decode'))->getClosure();
}
public function stupidJsonDecodeProvider() : array{
return [
@ -34,7 +40,10 @@ class StupidJsonDecodeTest extends TestCase{
["false", false],
["NULL", null],
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']]
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']],
['["Hello,, PocketMine"]', ['Hello,, PocketMine']],
['[,]', ['', '']],
['[]', []]
];
}
@ -47,10 +56,7 @@ class StupidJsonDecodeTest extends TestCase{
* @throws \ReflectionException
*/
public function testStupidJsonDecode(string $brokenJson, $expect){
$func = new \ReflectionMethod(PlayerNetworkSessionAdapter::class, 'stupid_json_decode');
$func->setAccessible(true);
$decoded = $func->invoke(null, $brokenJson, true);
$decoded = ($this->stupidJsonDecodeFunc)($brokenJson, true);
self::assertEquals($expect, $decoded);
}
}

@ -1 +1 @@
Subproject commit c0f0f9383d27d0efb2c1d04ea3678beb6a14d3af
Subproject commit 3fadb2c3f45a528a715733ba41c273289ef8ffb1