Compare commits

..

89 Commits
3.9.3 ... 3.9.7

Author SHA1 Message Date
0c91d568b4 Release 3.9.7 2019-10-28 16:07:35 +00:00
35fabc7765 updated DevTools submodule to 1.13.5 2019-10-28 16:03:26 +00:00
b5a98a993f lazy-init RuntimeBlockMapping 2019-10-27 20:58:43 +00:00
0840ba8067 PocketMine.php: reduce unnecessary pocketmine\NAME dependencies 2019-10-26 21:37:15 +01:00
274cf58ccf PocketMine.php: remove useless ini_set() (this is a PHP_INI_SYSTEM directive which can't be changed at runtime) 2019-10-25 21:38:47 +01:00
1bce5d0bc2 PocketMine.php: move BaseClassLoader creation to where it's actually used 2019-10-25 21:26:22 +01:00
0d5d5e21a8 CommandReader: define $w and $e to make PHPStan happy 2019-10-25 16:11:02 +01:00
a145e18c1e CrashDump: use fully qualified reference for GIT_COMMIT constant
this makes it easier to filter out PHPStan noise.
2019-10-25 16:08:37 +01:00
d1ca779c1a fix PHPStan @throws incompatible warning 2019-10-25 15:49:47 +01:00
abbb8bbf55 travis.sh: allow latest phpunit v7 now that we have XML
not ready to move to v8 yet because of BC breaks
2019-10-24 13:37:24 +01:00
86c7870427 update build/php submodule 2019-10-24 13:28:27 +01:00
48080b7f90 PocketMine.php: define INT32_MASK earlier
this is non-dependent on any of the PM crap.
2019-10-24 09:23:37 +01:00
b216fb8910 PocketMine.php: set INI entries as early as possible 2019-10-24 09:18:50 +01:00
d3171d6a8e backport a53f698d38: PocketMine.php: remove useless set_time_limit() call
this is hardcoded to zero in the PHP core anyway.
2019-10-24 09:14:18 +01:00
c063a4da29 backport 5c1f1f00cb: move assert.exception to PocketMine.php with the other stuff 2019-10-24 09:12:05 +01:00
cc79dfa64c backport 8b40a8f217: PocketMine.php: move INI entry setting to a separate function 2019-10-24 09:09:53 +01:00
d6b9950901 backport fdfbaf4e95: make startup performance warnings a little more coherent 2019-10-24 09:06:28 +01:00
1815fe5b46 backport 647f86a5b8: PocketMine.php: remove redundant ini_set()
this is overridden later on by MemoryManager.
2019-10-24 09:02:37 +01:00
3e993250d8 backport 0c31b8731f: PocketMine.php: use main logger to emit force-kill debug 2019-10-24 08:57:31 +01:00
1163ac1d7a backport 0c31b8731f: PocketMine.php: get rid of redundant LOCK_FILE_PATH constant 2019-10-24 08:55:28 +01:00
9a51ba697a PocketMine.php: move some mission-critical stuff earlier in boot sequence
fixes #3161
2019-10-24 08:50:13 +01:00
32ad9d0c1a Squid: fix spammy rotation in enclosed spaces
this bug was caused by 2f3c77c68a. It looks unrelated to the commit title, so it may have been committed by mistake.
2019-10-22 22:50:35 +01:00
3d840e969d 3.9.7 is next 2019-10-22 14:38:02 +01:00
d1b70bd400 Release 3.9.6 2019-10-22 14:38:02 +01:00
29f002b32c LightUpdate: fixed type doc for updateNodes field 2019-10-22 12:23:42 +01:00
da17ade575 AvailableCommandsPacket: fixed wrong parameter type doc for putEnum() 2019-10-22 12:23:01 +01:00
f0c36f3413 ClientCacheMissResponsePacket: fix broken type assert in create()
ouch! PhpStorm never saw these...
2019-10-22 11:57:10 +01:00
77d8f133f1 LevelChunkPacket: fixed broken type assert in withCache() 2019-10-22 11:54:37 +01:00
43ebb23085 Permission: remove dead code from loadPermission() 2019-10-22 10:42:22 +01:00
e198c8fa8b Task: mark taskHandler field as nullable 2019-10-22 10:26:49 +01:00
cc3285c8fe Chest: fixed type doc of doubleChestInventory field 2019-10-22 10:25:24 +01:00
305c63ba4d MainLogger: initialize shutdown field in the conventional manner
this avoids uninitialized uses
2019-10-22 10:21:17 +01:00
acaa0e33b0 update DevTools submodule to pmmp/PocketMine-DevTools@3fadb2c3f4 2019-10-22 10:16:44 +01:00
f63857deed update build/php submodule to pmmp/php-build-scripts@1b3fe3120c 2019-10-22 10:15:29 +01:00
eda3d9b5e4 sync composer dependencies 2019-10-22 10:13:47 +01:00
c3872619cd Block: mark boundingBox as nullable 2019-10-22 10:07:27 +01:00
39cc590829 Skin: accommodate JSON geometry containing comments, closes #3121 2019-10-21 22:26:48 +01:00
f347345bb3 Human::getInventory(): explicitly declare return type
the lack of this causes type inference bugs and documentation problems.
thanks PHPStan
2019-10-21 15:25:57 +01:00
bb05cfb36c Shears: fixed always-false hardness check
thanks PHPStan
2019-10-21 15:21:22 +01:00
7d5c3c9b46 backport 4364d2a94: AvailableCommandsPacket: Clean up internals
this is still disgusting, but it's a little more bearable now.
2019-10-21 14:51:36 +01:00
cff2d37add backport ec0558597b: CommandParameter: change byte1 field to "flags" (#3115) 2019-10-21 10:03:59 +01:00
447477c5fb RegionLoader: Write location table changes when deleting chunks 2019-10-20 20:54:46 +01:00
abdbb2bf0e backport 3f6660027: RegionLoader: Extract location table validation to separate function 2019-10-20 20:53:47 +01:00
783c13926f backport f2404804d: RegionLoader: clean up lastSector handling 2019-10-20 20:48:29 +01:00
45329ddf67 backport 07a9c35ee: RegionLoader: Use objects instead of arrays 2019-10-20 20:42:55 +01:00
20af789963 backport 3e58708130: Add some missing @throws annotations 2019-10-20 20:23:51 +01:00
93cb9390e0 RegionLoader: backport 62185d476b 2019-10-20 19:59:02 +01:00
0aed7f86f5 Updated BossEventPacket comments (#3155)
* Updated BossBar comments

* Fixed comments
2019-10-20 19:23:07 +01:00
bf44bd016d TransferServerCommand: add missing boilerplate permission check 2019-10-20 14:28:53 +01:00
04e4a36653 LevelDB: remove unnecessary \pocketmine\NAME dependency
this is non-obvious and makes it a pain the ass to use outside of PM.
2019-10-17 18:56:51 +01:00
7439e1971d Server: stop spamming crashdumps on unsupported / corrupted worlds
really we should look into making the server stop if any world fails to load, but flooding the place with crashdumps isn't the way to do it.
This is a simplified version of cf73c7f5c1
2019-10-17 11:27:00 +01:00
b961b4e003 SetupWizard: use constant for default gamemode 2019-10-16 11:48:25 +01:00
d9eac8fc0a Server: fixed default difficulty being EASY instead of NORMAL 2019-10-16 11:45:30 +01:00
aeeee5eb53 Added encode/decode for StructureTemplateDataExport(Request|Response)Packet (#3145) 2019-10-14 11:14:42 +01:00
13994055d9 SetupWizard: disable spawn protection by default
we can't change anything else wrt. this on a patch version, disabling it in the main core by default involves possible behavioural breaks
2019-10-09 11:26:58 +01:00
247875e3d5 Explosion: add documentation for explodeA() and explodeB()
these functions really ought to be renamed, or possibly redesigned entirely. However, that's no task for a patch version.

[ci skip]
2019-10-07 09:44:17 +01:00
ee60a7bc36 Item: add documentation for addCreativeItem(), removeCreativeItem() and clearCreativeItems()
[ci skip]
2019-10-07 09:37:59 +01:00
348c2a599b Internet: report PM version in user agent
this is useful for statistics
2019-10-04 10:59:32 +01:00
562b47a1e5 Player: guard against repeated resource pack sequence
this can happen because of the client being super broken in 1.12
close #3036
2019-10-03 11:20:44 +01:00
4e060bc13f EventPacket: Added some new constants (#3132) 2019-10-02 13:37:05 +01:00
2807f14fcd 3.9.6 is next 2019-10-01 15:02:34 +01:00
f0539f4898 Release 3.9.5 2019-10-01 15:02:33 +01:00
63d7e7b811 Fixed decoding form responses with double commas inside quotes (#3131)
closes #3113
2019-10-01 14:28:55 +01:00
4da06078ed Server: promote Patreon on startup 2019-10-01 14:22:07 +01:00
8a6381c3fa StupidJsonDecodeTest: add some extra test vectors 2019-10-01 13:25:20 +01:00
d0d61597c7 StupidJsonDecodeTest: use getClosure() instead of traditional mess
this is faster and requires less code.
2019-09-26 11:28:40 +01:00
7a2a4e2aa3 change some heading sizes [ci skip] 2019-09-25 12:59:04 +01:00
e41a2c0792 Link to StackOverflow in README (#3084) 2019-09-23 11:56:37 +01:00
11a6e04a28 EnderPearl: remove collision box hack (this isn't needed for MCPE anyway)
This was intended to address the problem that ender pearls would not stop on grass, saplings, and other similar objects. However, they don't stop on such objects in MCPE anyway, only PC.
2019-09-18 10:02:52 +01:00
70b1ac856d ZippedResourcePack: fix mishandling of wrong root JSON type (crashdump #2840512) 2019-09-17 11:01:33 +01:00
d724374d1a StupidJsonDecodeTest: add failing test case for #3113 2019-09-16 15:53:00 +01:00
a19143cae7 3.9.5 is next 2019-08-16 17:58:02 +01:00
1be6783c34 Release 3.9.4 2019-08-16 17:58:01 +01:00
092edc9d43 avoid breaking concrete powder 2019-08-16 17:41:50 +01:00
2ba8eac27f FallingBlock: fix endless falling on top of fences
this is a shitty fix, but I don't think there's a better way to do it on 3.x. This also fixes dropping on cactus.
close #2449, close #2895
2019-08-16 17:27:41 +01:00
25ff90b2c6 PluginManager: fix chained softdepend plugins load order
Test case:
- plugin2 depends on nonexistent plugin1
- plugin3 depends on plugin2

At random occasions, plugin3 would be loaded before plugin2, because plugin2 load would be deferred in the expectation of plugin1 being loaded. This would result in the assumption that plugin3's softdepend plugins would not be loaded, so they were ignored.
We fix this problem by removing missing plugins from softdepend if they were not present on a scan of the directory. This way, we don't ignore any unresolved deferred dependency resolutions.
2019-08-16 16:46:59 +01:00
b912ae78bc Merge branch 'stable' of https://github.com/pmmp/PocketMine-MP into stable 2019-08-14 18:21:24 +01:00
677d43028a add php-build-scripts as a submodule 2019-08-14 18:08:26 +01:00
7bfb55ec9a Fixed some errors in support.yml (#3095)
[ci skip]
2019-08-13 14:09:03 +01:00
2f61d42518 backport d23eeff832: FallingBlock: remove useless check 2019-08-11 19:38:12 +01:00
dbb669b156 Entity: add some deprecation warnings to despawnFrom() and despawnFromAll() 2019-08-11 19:34:57 +01:00
4d0e8741fe Added a deprecation notice to Entity->getBlocksAround() 2019-08-11 19:32:21 +01:00
53dc6e2050 fix TallGrass and Tree random/base amounts never being initialized, closes #2996 2019-08-11 19:06:20 +01:00
807b860cfe protocol: fixup data type changes, closes #3072 2019-08-11 19:02:16 +01:00
d756500928 Also updated Discord link in suppor template and README 2019-08-08 00:03:10 +08:00
7ef27a1a21 support.yml Discord link should point to #rules 2019-08-07 14:54:01 +08:00
6b4d8b91c6 fix crashdump plugin detection
some things which were copy pasting PM classes into plugins were causing incorrect detection here. The path cleaning guarantees that all plugin paths will start with "plugins/", so we can use that to check instead.
2019-07-30 18:58:31 +01:00
8f5eb7ef37 Level: fix memory leak on scheduled blockupdate in unloaded chunk 2019-07-30 15:37:24 +01:00
0ea9a08963 3.9.4 is next 2019-07-29 17:27:21 +01:00
59 changed files with 739 additions and 370 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

Submodule build/php added at 8666ae5add

View File

@ -57,3 +57,51 @@ 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.
# 3.9.7
- Fixed a crash that could occur during timezone detection.
- Squid no longer spin around constantly in enclosed spaces. Their performance impact is reduced.
- Cleaned up the bootstrap file.

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

@ -291,7 +291,7 @@ class CrashDump{
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
$frameCleanPath = Utils::cleanPath($filePath); //this will be empty in phar stub
if($frameCleanPath !== "" and strpos($frameCleanPath, "src/pocketmine/") === false and strpos($frameCleanPath, "vendor/pocketmine/") === false and file_exists($filePath)){
if(strpos($frameCleanPath, "plugins") === 0 and file_exists($filePath)){
$this->addLine();
if($crashFrame){
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
@ -333,7 +333,7 @@ class CrashDump{
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . GIT_COMMIT);
$this->addLine("Git commit: " . \pocketmine\GIT_COMMIT);
$this->addLine("uname -a: " . php_uname("a"));
$this->addLine("PHP Version: " . phpversion());
$this->addLine("Zend version: " . zend_version());

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

@ -57,18 +57,18 @@ namespace pocketmine {
if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
//If PHP version isn't high enough, anything below might break, so don't bother checking it.
return [
\pocketmine\NAME . " requires PHP >= " . MIN_PHP_VERSION . ", but you have PHP " . PHP_VERSION . "."
"PHP >= " . MIN_PHP_VERSION . " is required, but you have PHP " . PHP_VERSION . "."
];
}
$messages = [];
if(PHP_INT_SIZE < 8){
$messages[] = "Running " . \pocketmine\NAME . " with 32-bit systems/PHP is no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
$messages[] = "32-bit systems/PHP are no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
}
if(php_sapi_name() !== "cli"){
$messages[] = "You must run " . \pocketmine\NAME . " using the CLI.";
$messages[] = "Only PHP CLI is supported.";
}
$extensions = [
@ -121,6 +121,29 @@ namespace pocketmine {
return $messages;
}
function emit_performance_warnings(\Logger $logger){
if(extension_loaded("xdebug")){
$logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
}
if(!extension_loaded("pocketmine_chunkutils")){
$logger->warning("ChunkUtils extension is missing. Anvil-format worlds will experience degraded performance.");
}
if(((int) ini_get('zend.assertions')) !== -1){
$logger->warning("Debugging assertions are enabled. This may degrade performance. To disable them, set `zend.assertions = -1` in php.ini.");
}
if(\Phar::running(true) === ""){
$logger->warning("Non-packaged installation detected. This will degrade autoloading speed and make startup times longer.");
}
}
function set_ini_entries(){
ini_set("allow_url_fopen", '1');
ini_set("display_errors", '1');
ini_set("display_startup_errors", '1');
ini_set("default_charset", "utf-8");
ini_set('assert.exception', '1');
}
function server(){
if(!empty($messages = check_platform_dependencies())){
echo PHP_EOL;
@ -136,6 +159,8 @@ namespace pocketmine {
unset($messages);
error_reporting(-1);
set_ini_entries();
@define("INT32_MASK", is_int(0xffffffff) ? 0xffffffff : -1);
if(\Phar::running(true) !== ""){
define('pocketmine\PATH', \Phar::running(true) . "/");
@ -161,20 +186,27 @@ namespace pocketmine {
set_error_handler([Utils::class, 'errorExceptionHandler']);
/*
* We now use the Composer autoloader, but this autoloader is still for loading plugins.
*/
$autoloader = new \BaseClassLoader();
$autoloader->register(false);
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
define('pocketmine\VERSION', $version->getFullVersion(true));
set_time_limit(0); //Who set it to 30 seconds?!?!
$gitHash = str_repeat("00", 20);
ini_set("allow_url_fopen", '1');
ini_set("display_errors", '1');
ini_set("display_startup_errors", '1');
ini_set("default_charset", "utf-8");
if(\Phar::running(true) === ""){
if(Utils::execute("git rev-parse HEAD", $out) === 0 and $out !== false and strlen($out = trim($out)) === 40){
$gitHash = trim($out);
if(Utils::execute("git diff --quiet") === 1 or Utils::execute("git diff --cached --quiet") === 1){ //Locally-modified
$gitHash .= "-dirty";
}
}
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
}
}
ini_set("memory_limit", '-1');
define('pocketmine\GIT_COMMIT', $gitHash);
define('pocketmine\RESOURCE_PATH', \pocketmine\PATH . 'src' . DIRECTORY_SEPARATOR . 'pocketmine' . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR);
@ -187,8 +219,7 @@ namespace pocketmine {
mkdir(\pocketmine\DATA, 0777, true);
}
define('pocketmine\LOCK_FILE_PATH', \pocketmine\DATA . 'server.lock');
define('pocketmine\LOCK_FILE', fopen(\pocketmine\LOCK_FILE_PATH, "a+b"));
define('pocketmine\LOCK_FILE', fopen(\pocketmine\DATA . 'server.lock', "a+b"));
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
//other server wrote its PID and released exclusive lock before we get our lock
@ -222,42 +253,7 @@ namespace pocketmine {
}
unset($tzError);
if(extension_loaded("xdebug")){
$logger->warning(PHP_EOL . PHP_EOL . PHP_EOL . "\tYou are running " . \pocketmine\NAME . " with xdebug enabled. This has a major impact on performance." . PHP_EOL . PHP_EOL);
}
if(!extension_loaded("pocketmine_chunkutils")){
$logger->warning("ChunkUtils extension is missing. Anvil-format worlds will experience degraded performance.");
}
if(\Phar::running(true) === ""){
$logger->warning("Non-packaged " . \pocketmine\NAME . " installation detected. Consider using a phar in production for better performance.");
}
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
define('pocketmine\VERSION', $version->getFullVersion(true));
$gitHash = str_repeat("00", 20);
if(\Phar::running(true) === ""){
if(Utils::execute("git rev-parse HEAD", $out) === 0 and $out !== false and strlen($out = trim($out)) === 40){
$gitHash = trim($out);
if(Utils::execute("git diff --quiet") === 1 or Utils::execute("git diff --cached --quiet") === 1){ //Locally-modified
$gitHash .= "-dirty";
}
}
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
}
}
define('pocketmine\GIT_COMMIT', $gitHash);
@define("INT32_MASK", is_int(0xffffffff) ? 0xffffffff : -1);
@ini_set("opcache.mmap_base", bin2hex(random_bytes(8))); //Fix OPCache address errors
emit_performance_warnings($logger);
$exitCode = 0;
do{
@ -272,6 +268,13 @@ namespace pocketmine {
//TODO: move this to a Server field
define('pocketmine\START_TIME', microtime(true));
ThreadManager::init();
/*
* We now use the Composer autoloader, but this autoloader is still for loading plugins.
*/
$autoloader = new \BaseClassLoader();
$autoloader->register(false);
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);
$logger->info("Stopping other threads");
@ -281,9 +284,7 @@ namespace pocketmine {
usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
if(ThreadManager::getInstance()->stopAll() > 0){
if(\pocketmine\DEBUG > 1){
echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL;
}
$logger->debug("Some threads could not be stopped, performing a force-kill");
Utils::kill(getmypid());
}
}while(false);

View File

@ -593,7 +593,7 @@ class Server{
* @return int
*/
public function getDifficulty() : int{
return $this->getConfigInt("difficulty", 1);
return $this->getConfigInt("difficulty", Level::DIFFICULTY_NORMAL);
}
/**
@ -1101,11 +1101,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){
@ -1529,7 +1535,7 @@ class Server{
"force-gamemode" => false,
"hardcore" => false,
"pvp" => true,
"difficulty" => 1,
"difficulty" => Level::DIFFICULTY_NORMAL,
"generator-settings" => "",
"level-name" => "world",
"level-seed" => "",
@ -1559,12 +1565,6 @@ class Server{
return;
}
if(((int) ini_get('zend.assertions')) !== -1){
$this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini.");
}
ini_set('assert.exception', '1');
if($this->logger instanceof MainLogger){
$this->logger->setLogDebug(\pocketmine\DEBUG > 1);
}
@ -2200,6 +2200,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.3";
const BASE_VERSION = "3.9.7";
const IS_DEVELOPMENT_BUILD = false;
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

@ -141,6 +141,7 @@ class CommandReader extends Thread{
case self::TYPE_STREAM:
//stream_select doesn't work on piped streams for some reason
$r = [self::$stdin];
$w = $e = null;
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
return true;
}elseif($count === false){ //stream error

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

@ -82,7 +82,7 @@ class Squid extends WaterAnimal{
return false;
}
if(++$this->switchDirectionTicker === 100 or $this->isCollided){
if(++$this->switchDirectionTicker === 100){
$this->switchDirectionTicker = 0;
if(mt_rand(0, 100) < 50){
$this->swimDirection = null;

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;

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

@ -822,11 +822,11 @@ class Level implements ChunkManager, Metadatable{
while($this->scheduledBlockUpdateQueue->count() > 0 and $this->scheduledBlockUpdateQueue->current()["priority"] <= $currentTick){
/** @var Vector3 $vec */
$vec = $this->scheduledBlockUpdateQueue->extract()["data"];
unset($this->scheduledBlockUpdateQueueIndex[Level::blockHash($vec->x, $vec->y, $vec->z)]);
if(!$this->isInLoadedTerrain($vec)){
continue;
}
$block = $this->getBlock($vec);
unset($this->scheduledBlockUpdateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]);
$block->onScheduledUpdate();
}

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,7 @@ 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 pocketmine\utils\BinaryDataException;
use function count;
use function dechex;
@ -83,30 +79,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 +93,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 +120,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 +157,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 +184,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 +232,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 +257,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 +282,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 +295,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 +329,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 +340,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 +366,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

@ -40,8 +40,8 @@ final class RuntimeBlockMapping{
private static $legacyToRuntimeMap = [];
/** @var int[] */
private static $runtimeToLegacyMap = [];
/** @var mixed[] */
private static $bedrockKnownStates;
/** @var mixed[]|null */
private static $bedrockKnownStates = null;
private function __construct(){
//NOOP
@ -77,6 +77,12 @@ final class RuntimeBlockMapping{
}
}
private static function lazyInit() : void{
if(self::$bedrockKnownStates === null){
self::init();
}
}
/**
* Randomizes the order of the runtimeID table to prevent plugins relying on them.
* Plugins shouldn't use this stuff anyway, but plugin devs have an irritating habit of ignoring what they
@ -101,6 +107,7 @@ final class RuntimeBlockMapping{
* @return int
*/
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
self::lazyInit();
/*
* try id+meta first
* if not found, try id+0 (strip meta)
@ -115,6 +122,7 @@ final class RuntimeBlockMapping{
* @return int[] [id, meta]
*/
public static function fromStaticRuntimeId(int $runtimeId) : array{
self::lazyInit();
$v = self::$runtimeToLegacyMap[$runtimeId];
return [$v >> 4, $v & 0xf];
}
@ -128,7 +136,7 @@ final class RuntimeBlockMapping{
* @return array
*/
public static function getBedrockKnownStates() : array{
self::lazyInit();
return self::$bedrockKnownStates;
}
}
RuntimeBlockMapping::init();

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);
}
}

View File

@ -21,9 +21,7 @@ if [ $? -ne 0 ]; then
exit 1
fi
#Run PHPUnit tests
#7.5.12 introduces changes that set the build on fire because we don't ship libxml - TODO FIX
curl https://phar.phpunit.de/phpunit-7.5.11.phar --silent --location -o phpunit.phar
curl https://phar.phpunit.de/phpunit-7.phar --silent --location -o phpunit.phar
"$PHP_BINARY" phpunit.phar --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit || exit 1
#Run-the-server tests