mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-05 11:27:07 +00:00
Merge branch 'stable' into next-minor
This commit is contained in:
commit
0591458ef6
2
.github/ISSUE_TEMPLATE/help---support.md
vendored
2
.github/ISSUE_TEMPLATE/help---support.md
vendored
@ -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
4
.github/support.yml
vendored
@ -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
3
.gitmodules
vendored
@ -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
|
||||
|
11
README.md
11
README.md
@ -5,23 +5,24 @@
|
||||
|
||||
[](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
1
build/php
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1b3fe3120cac93ef8d821a1c62a3722576fcfd81
|
@ -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
36
composer.lock
generated
@ -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",
|
||||
|
@ -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)){
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -69,7 +69,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
/** @var int|null */
|
||||
protected $itemId;
|
||||
|
||||
/** @var AxisAlignedBB */
|
||||
/** @var AxisAlignedBB|null */
|
||||
protected $boundingBox = null;
|
||||
|
||||
|
||||
|
@ -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)){
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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){
|
||||
|
@ -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){
|
||||
|
@ -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)){
|
||||
|
@ -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){
|
||||
|
@ -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
|
@ -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 = [];
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)");
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
@ -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"])){
|
||||
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ use pocketmine\utils\Utils;
|
||||
|
||||
abstract class Task{
|
||||
|
||||
/** @var TaskHandler */
|
||||
/** @var TaskHandler|null */
|
||||
private $taskHandler = null;
|
||||
|
||||
/**
|
||||
|
@ -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 */
|
||||
|
@ -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{
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user