Compare commits

..

135 Commits
3.9.4 ... 3.9.8

Author SHA1 Message Date
6399dacba7 Release 3.9.8 2019-12-06 20:33:51 +00:00
4d13c48e5c UPnP: COM constructor never returns false anymore 2019-12-05 16:07:06 +00:00
9159e8f002 MainLogger: mark static logger field as nullable 2019-12-05 15:48:58 +00:00
f5aa461945 GlobalConstants: assume we are on 64-bit when declaring INT32_MASK
fixes a FP reported by phpstan level 4
2019-12-05 15:48:11 +00:00
16817ff301 Player: remove superfluous conditions for RESPAWN action handling
spawned is always true here because of the condition at the top of the function.
isOnline() is always true here because the handler won't even be called if it isn't.
2019-12-05 14:52:12 +00:00
18863b1098 Player: remove superfluous check from processMovement()
if we reached this branch, revert is always false because it is not modified between this branch and the parent one.
detected by phpstan level 4
2019-12-05 14:47:15 +00:00
7b1ae2a822 phpstan: green on level 3 2019-12-04 22:12:14 +00:00
8ecf5e02b9 bad fix for WritableBook phpstan warning
master has this shit so much better
2019-12-04 22:00:19 +00:00
39c607cbd5 Position: mark level field as nullable
allowing the level to be null is, to be honest, a big design flaw, but one that can't be fixed without BC breaks.
2019-12-04 21:31:35 +00:00
d867ffc60d MetadataStore: fix some doc comments 2019-12-04 21:14:09 +00:00
c57eb26fd5 phpstan-bugs: add some extra patterns for level 3 false positives 2019-12-04 19:50:15 +00:00
c35d91a104 phpstan: allow blanket ignoreErrors in src to reduce merge work for master 2019-12-04 19:42:18 +00:00
9fc260fb1a keep phpstan.neon.dist ignoreErrors sorted by file 2019-12-04 19:38:54 +00:00
73d0f799c2 Update to PHPStan 0.12.0 2019-12-04 10:29:49 +00:00
ecb2e6e3af PluginManager: remove useless information from softDepend debug
we don't report the parameter type anywhere else, and since PHP doesn't support overloading, we don't need to.
This fixes PHPStan 0.12 complaints about ReflectionNamedType. I figured this was the best solution instead of adding an extra few lines of code.
2019-12-04 10:17:25 +00:00
7b75b6928d AvailableCommandsPacket: fixed foreach docs
these probably aren't necessary at all to be honest.
2019-12-04 10:15:50 +00:00
753a8a6937 Event: remove useless @var $this annotations
we are not in a trait here, the scope is always Event.
2019-12-04 10:15:04 +00:00
bc76b1cafe Server: remove several redundant @var annotations (copy pasta)
these are all useless because it is implied by the parameter type anyway.
2019-12-04 10:14:15 +00:00
00e415fc79 move phpstan.neon to phpstan.neon.dist to allow phpstan.neon to be locally modified for development 2019-12-03 19:57:19 +00:00
dbbe1f2d5c Revert "Entity: remove redundant check from spawnTo()"
This reverts commit 3028832cd3.

When I created this commit, I made the flawed assumption that spawnTo()
would not be used by plugins. In addition, I was not aware that there
are some usages of spawnTo() in the core which do not check for chunk
usage, such as in Player->showPlayer().

This caused a collection of problems including memory leaks and crashes
due to disconnecting players not removing their references from viewed
entities.

The reverted commit may be the cause of #3178.
2019-12-03 10:41:13 +00:00
ce27c03774 PlayerChatEvent: fixed crash when non-CommandSender permissibles subscribe to broadcast permission
doing such a thing doesn't make any sense, but the system allows it, so it has to be accounted for.
2019-12-01 21:44:13 +00:00
c4a8781b5c Fixed doc type inconsistencies surrounding chat broadcast handling (several problems that are all related) 2019-12-01 21:40:11 +00:00
dbab8b5733 Level: fixed type doc of tickRateTime field 2019-12-01 21:21:42 +00:00
2b08bbc7b1 Server: fixed type doc comment of nextTick field 2019-12-01 21:18:20 +00:00
17037f5e9c Chunk: clean up nonsensical code in initChunk()
I have no idea why the extra check was there, or why the null assignment was used, because it doesn't make any sense.
2019-12-01 21:14:23 +00:00
fee3c17148 CraftingManager: fixed type doc of craftingDataCache field 2019-12-01 21:06:34 +00:00
25e6cb74b3 Squid: fixed type doc comment of swimDirection field 2019-12-01 21:06:03 +00:00
8d2e59222e Entity: fixed not calculating surrounding blocks on entity creation
it's unclear if this was actually causing any bugs, but if it was it would likely manifest in the form of, for example, not burning in lava.
2019-12-01 21:05:36 +00:00
cd778661c2 Entity: fixed type doc comment for blocksAround field 2019-12-01 21:03:57 +00:00
c2afc05e7c Entity: fix type doc comment for static knownEntities field 2019-12-01 21:03:29 +00:00
9be95bf263 Block: return [] instead of null in getMetadata() (return type is non-nullable) 2019-12-01 21:02:49 +00:00
4b65e1cbe1 Command: fix type doc comment for commandMap field 2019-12-01 21:01:57 +00:00
5caae37768 Server: fixed return type doc comment for getCommandAliases() 2019-12-01 21:00:49 +00:00
92e1811b06 DataPacket: fixed bad null assignment to buffer in clean()
this is never expected to be null, so implicitly relies on PHP magic behaviour to convert it to string when appended.
2019-12-01 20:36:56 +00:00
293c2710d0 PlayerCreationEvent: fixed doc comments for baseClass and playerClass fields 2019-12-01 20:19:59 +00:00
8a7017fd6b Enchantment: fixed doc comment for static enchantments field 2019-12-01 20:16:45 +00:00
15f8886958 phpstan: separate non-PM bugs from the main neon config
everything left in here is now a PM problem that needs to be looked into.
2019-12-01 20:12:41 +00:00
3226a9dc6a phpstan: ignore more optional-leveldb errors 2019-12-01 19:52:49 +00:00
1a1e3ff63b BaseInventory: fixed incorrect & redundant default value for slots field 2019-12-01 19:45:16 +00:00
ea413d0882 phpstan: analyze on level 2, close #3193 2019-12-01 19:32:03 +00:00
0890b5fc99 AsyncPool: assert() that the unstacked task is actually an AsyncTask
it's possible that it might not be if the workers were accessed directly, but that shouldn't be possible.
This also silences a PHPStan warning on level 2.
2019-12-01 19:28:05 +00:00
163ed225f2 NetworkBinaryStream: fixed crash when non-compound root tag is provided for itemstack 2019-12-01 18:41:02 +00:00
a4a6d3e094 PlayerCreationEvent: fixed illegal doc comment types 2019-12-01 18:08:25 +00:00
ecbf3e9722 GamemodeCommand: fix CommandSender assignment causing troubles for type inference on static analyzers
this would never crash, but in strongly typed code it would be a compile failure.
2019-12-01 09:33:12 +00:00
47a959dace block: fix some possible crashes when plugins overwrite tile classes in bad ways
phpstan was complaining on level 2, and it's not wrong to ...
2019-12-01 09:29:22 +00:00
3968f85c82 sync composer dependencies 2019-12-01 08:54:48 +00:00
6d109bfc6f CraftingDataPacket: fixed not retaining cleanRecipes during decode 2019-11-30 21:15:37 +00:00
42d8357821 AvailableCommandsPacket: fixed missing decoded overloads with 0 arguments
these should be listed even if they have 0 arguments
2019-11-30 11:58:29 +00:00
f2ac63d235 update build/php submodule to pmmp/php-build-scripts@8308571448 2019-11-29 11:54:42 +00:00
3e4366b30d readme: add XLM since Keybase made it so easy
... and also gave lots of keybase users free XLM. Well played, Keybase, you managed to make me adopt Stellar.
2019-11-20 10:51:07 +00:00
314ce1d012 build/make-release: push after 5 seconds instead of 10 2019-11-11 16:41:50 +00:00
8d2e3894ef DataPacket: fixed var_dump() not showing private & protected subclass properties 2019-11-11 15:41:08 +00:00
025cb73bf5 update travis pthreads 2019-11-09 17:34:04 +00:00
2a6ffb5aa9 updated build/php submodule 2019-11-09 17:33:25 +00:00
932418b951 fixing some PHPStan complaints about bootstrap
this isn't an ideal fix, but it'll do.
2019-11-07 18:22:37 +00:00
95812252d6 NetworkBinaryStream: fix a mistake in doc for putEntityRuntimeId() 2019-11-07 18:22:37 +00:00
cac3c356a5 Painting: fix dropping multiple items when multiple blockupdates occur in the same tick
test case: place sand on top of a line of signs, put a painting on the sand and break the supporting sign
fixes #2774 for stable
2019-11-06 14:29:43 -05:00
c5d3e9be76 phpstan: silence leveldb noise
maybe there's a better way to do this, but I don't know it yet.
2019-11-05 20:21:36 +00:00
714f4dc023 fortify CI with PHPStan 2019-11-05 19:42:37 +00:00
a86bcd5110 travis.sh: don't redownload phpunit if it already locally exists 2019-11-05 19:39:04 +00:00
7ffc477d76 introduced baseline PHPStan configuration 2019-11-05 11:16:49 +00:00
4cb0b319c0 load some non-class constants with composer
this makes PHPStan happy and also makes working with PM code externally less of a pain in the ass.
2019-11-05 11:16:22 +00:00
5edff79f5f 3.9.8 is next 2019-10-28 16:07:35 +00:00
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
83 changed files with 1183 additions and 434 deletions

1
.gitignore vendored
View File

@ -13,6 +13,7 @@ server.properties
memory_dumps/*
resource_packs/
server.lock
/phpstan.neon
# Common IDEs
.idea/

View File

@ -9,7 +9,7 @@ before_script:
- echo | pecl install channel://pecl.php.net/yaml-2.0.4
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout 6ca019c58b4fa09ee2ff490f2444e34bef0773d0
- git checkout 1b7da492b944146fa9680f6399bd9c6c6c6095e0
- phpize
- ./configure
- make

View File

@ -5,25 +5,27 @@
[![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/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`
- Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T`
- [Patreon](https://www.patreon.com/pocketminemp)
## Licensing information

View File

@ -76,6 +76,6 @@ system('git tag ' . $currentVer->getBaseVersion());
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true);
system('git add "' . $versionInfoPath . '"');
system('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"');
echo "pushing changes in 10 seconds\n";
sleep(10);
echo "pushing changes in 5 seconds\n";
sleep(5);
system('git push origin HEAD ' . $currentVer->getBaseVersion());

View File

@ -69,3 +69,61 @@ Plugin developers should **only** update their required API to this version if y
- `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.
# 3.9.8
- Added [PHPStan](https://github.com/phpstan/phpstan) configuration. PHPStan is now used on CI for automated QA, which should improve stability and quality going forward.
- The following constants are now autoloaded when loading the Composer autoloader:
- `pocketmine\NAME`
- `pocketmine\BASE_VERSION`
- `pocketmine\IS_DEVELOPMENT_BUILD`
- `pocketmine\BUILD_NUMBER`
- `INT32_MIN`
- `INT32_MAX`
- `INT32_MASK`
- Fixed memory leaks and crashes caused by plugin use of `Player->showPlayer()` and `Entity->spawnTo()`.
- Fixed crashes that could occur when tile classes were overridden with classes incompatible with the originals.
- Fixed improper handling of non-Compound root NBT tags on network itemstack decoding.
- Fixed paintings dropping multiple items when destroyed by block updates.
- Fixed `var_dump()` not showing private and protected properties of `DataPacket` subclasses.
- Fixed overloads with zero arguments being missing when decoding `AvailableCommandsPacket`.
- `CraftingDataPacket` now retains the `cleanRecipes` field when decoding.
- Fixed `Block->getMetadata()` returning null (non-iterable).
- `PlayerChatEvent` documentation has been updated to specify that `CommandSender` recipients are accepted. This behaviour was already present in previous versions, but incorrectly documented.
- Fixed various issues with PHPDoc comments reported by PHPStan.
- Fixed various minor code nits reported by PHPStan.

View File

@ -36,7 +36,11 @@
"autoload": {
"psr-4": {
"": ["src"]
}
},
"files": [
"src/pocketmine/GlobalConstants.php",
"src/pocketmine/VersionInfo.php"
]
},
"autoload-dev": {
"psr-4": {

52
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.12",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "2db27aebe7dc89772aaa8df53361eef801f60063"
"reference": "b5777265329753b74dd40bb105eedabeefb98724"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/2db27aebe7dc89772aaa8df53361eef801f60063",
"reference": "2db27aebe7dc89772aaa8df53361eef801f60063",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/b5777265329753b74dd40bb105eedabeefb98724",
"reference": "b5777265329753b74dd40bb105eedabeefb98724",
"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",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-07-22T15:22:23+00:00"
"time": "2019-12-01T08:20:26+00:00"
},
{
"name": "pocketmine/raklib",
@ -276,23 +276,20 @@
},
{
"name": "pocketmine/spl",
"version": "0.3.2",
"version": "0.3.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/SPL.git",
"reference": "7fd53857cd000491ba69e8db865792a024dd2c49"
"reference": "94d4df142fe837ba836e9348dd00209e4bdcc307"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/SPL/zipball/7fd53857cd000491ba69e8db865792a024dd2c49",
"reference": "7fd53857cd000491ba69e8db865792a024dd2c49",
"url": "https://api.github.com/repos/pmmp/SPL/zipball/94d4df142fe837ba836e9348dd00209e4bdcc307",
"reference": "94d4df142fe837ba836e9348dd00209e4bdcc307",
"shasum": ""
},
"type": "library",
"autoload": {
"exclude-from-classmap": [
"stubs"
],
"classmap": [
"./"
]
@ -302,9 +299,10 @@
],
"description": "Standard library files required by PocketMine-MP and related projects",
"support": {
"source": "https://github.com/pmmp/SPL/tree/master"
"source": "https://github.com/pmmp/SPL/tree/0.3.3",
"issues": "https://github.com/pmmp/SPL/issues"
},
"time": "2018-08-12T15:17:39+00:00"
"time": "2019-10-28T11:41:20+00:00"
}
],
"packages-dev": [],

124
phpstan.neon.dist Normal file
View File

@ -0,0 +1,124 @@
includes:
- tests/phpstan/configs/gc-hacks.neon
- tests/phpstan/configs/optional-com-dotnet.neon
- tests/phpstan/configs/optional-leveldb.neon
- tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/pthreads-bugs.neon
parameters:
level: 3
autoload_files:
- tests/phpstan/bootstrap.php
- src/pocketmine/PocketMine.php
paths:
- src
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
ignoreErrors:
-
message: "#^Cannot instantiate interface pocketmine\\\\level\\\\format\\\\io\\\\LevelProvider\\.$#"
count: 1
path: src/pocketmine/Server.php
-
message: "#^Call to an undefined method pocketmine\\\\command\\\\CommandSender\\:\\:teleport\\(\\)\\.$#"
count: 1
path: src/pocketmine/command/defaults/TeleportCommand.php
# comment: "not actually possible, but high cost to fix warning"
-
message: "#^Array \\(array\\<string\\>\\) does not accept pocketmine\\\\entity\\\\Entity\\.$#"
count: 2
path: src/pocketmine/entity/Entity.php
-
message: "#^Invalid array key type pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/entity/Entity.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\EntityDeathEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\Living but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/EntityDeathEvent.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\EntityShootBowEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\Living but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/EntityShootBowEvent.php
-
message: "#^Property pocketmine\\\\event\\\\entity\\\\EntityShootBowEvent\\:\\:\\$projectile \\(pocketmine\\\\entity\\\\projectile\\\\Projectile\\) does not accept pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/EntityShootBowEvent.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\ItemDespawnEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\object\\\\ItemEntity but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/ItemDespawnEvent.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\ItemSpawnEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\object\\\\ItemEntity but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/ItemSpawnEvent.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\ProjectileHitEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\projectile\\\\Projectile but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/ProjectileHitEvent.php
-
message: "#^Method pocketmine\\\\event\\\\entity\\\\ProjectileLaunchEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\projectile\\\\Projectile but returns pocketmine\\\\entity\\\\Entity\\.$#"
count: 1
path: src/pocketmine/event/entity/ProjectileLaunchEvent.php
-
message: "#^Constructor of class pocketmine\\\\level\\\\generator\\\\hell\\\\Nether has an unused parameter \\$options\\.$#"
count: 1
path: src/pocketmine/level/generator/hell/Nether.php
-
message: "#^Constructor of class pocketmine\\\\level\\\\generator\\\\normal\\\\Normal has an unused parameter \\$options\\.$#"
count: 1
path: src/pocketmine/level/generator/normal/Normal.php
-
message: "#^Used constant pocketmine\\\\RESOURCE_PATH not found\\.$#"
count: 1
path: src/pocketmine/network/mcpe/protocol/StartGamePacket.php
-
message: "#^Constructor of class pocketmine\\\\scheduler\\\\TaskScheduler has an unused parameter \\$logger\\.$#"
count: 1
path: src/pocketmine/scheduler/TaskScheduler.php
-
message: "#^Constant pocketmine\\\\COMPOSER_AUTOLOADER_PATH not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\DATA not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\GIT_COMMIT not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\PATH not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\PLUGIN_PATH not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\RESOURCE_PATH not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\START_TIME not found\\.$#"
path: src
-
message: "#^Constant pocketmine\\\\VERSION not found\\.$#"
path: src

View File

@ -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

@ -0,0 +1,30 @@
<?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/
*
*
*/
// composer autoload doesn't use require_once and also pthreads can inherit things
if(defined('pocketmine\_GLOBAL_CONSTANTS_INCLUDED')){
return;
}
define('pocketmine\_GLOBAL_CONSTANTS_INCLUDED', true);
const INT32_MIN = -0x80000000;
const INT32_MAX = 0x7fffffff;
const INT32_MASK = 0xffffffff;

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;
@ -1613,7 +1616,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$diff = $this->distanceSquared($newPos) / $tickDiff ** 2;
if($this->isSurvival() and !$revert and $diff > 0.0625){
if($this->isSurvival() and $diff > 0.0625){
$ev = new PlayerIllegalMoveEvent($this, $newPos, new Vector3($this->lastX, $this->lastY, $this->lastZ));
$ev->setCancelled($this->allowMovementCheats);
@ -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:
@ -2821,7 +2828,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->stopSleep();
break;
case PlayerActionPacket::ACTION_RESPAWN:
if(!$this->spawned or $this->isAlive() or !$this->isOnline()){
if($this->isAlive()){
break;
}
@ -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

@ -21,11 +21,6 @@
declare(strict_types=1);
namespace {
const INT32_MIN = -0x80000000;
const INT32_MAX = 0x7fffffff;
}
namespace pocketmine {
use pocketmine\utils\MainLogger;
@ -57,18 +52,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 +116,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 +154,7 @@ namespace pocketmine {
unset($messages);
error_reporting(-1);
set_ini_entries();
if(\Phar::running(true) !== ""){
define('pocketmine\PATH', \Phar::running(true) . "/");
@ -161,20 +180,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 +213,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 +247,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 +262,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 +278,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);
@ -296,5 +291,7 @@ namespace pocketmine {
exit($exitCode);
}
\pocketmine\server();
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){
\pocketmine\server();
}
}

View File

@ -227,7 +227,7 @@ class Server{
* @var int
*/
private $tickCounter = 0;
/** @var int */
/** @var float */
private $nextTick = 0;
/** @var float[] */
private $tickAverage = [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20];
@ -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){
@ -1438,7 +1444,7 @@ class Server{
}
/**
* @return string[]
* @return string[][]
*/
public function getCommandAliases() : array{
$section = $this->getProperty("aliases");
@ -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);
}
@ -1789,7 +1789,7 @@ class Server{
/**
* @param TextContainer|string $message
* @param Player[] $recipients
* @param CommandSender[] $recipients
*
* @return int
*/
@ -1798,7 +1798,6 @@ class Server{
return $this->broadcast($message, self::BROADCAST_CHANNEL_USERS);
}
/** @var Player[] $recipients */
foreach($recipients as $recipient){
$recipient->sendMessage($message);
}
@ -1823,7 +1822,6 @@ class Server{
}
}
/** @var Player[] $recipients */
foreach($recipients as $recipient){
$recipient->sendTip($tip);
}
@ -1849,7 +1847,6 @@ class Server{
}
}
/** @var Player[] $recipients */
foreach($recipients as $recipient){
$recipient->sendPopup($popup);
}
@ -1879,7 +1876,6 @@ class Server{
}
}
/** @var Player[] $recipients */
foreach($recipients as $recipient){
$recipient->addTitle($title, $subtitle, $fadeIn, $stay, $fadeOut);
}
@ -2200,6 +2196,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

@ -21,7 +21,15 @@
namespace pocketmine;
// composer autoload doesn't use require_once and also pthreads can inherit things
// TODO: drop this file and use a final class with constants
if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
return;
}
const _VERSION_INFO_INCLUDED = true;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.9.4";
const BASE_VERSION = "3.9.8";
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;
@ -763,7 +763,7 @@ class Block extends Position implements BlockIds, Metadatable{
return $this->level->getBlockMetadata()->getMetadata($this, $metadataKey);
}
return null;
return [];
}
public function hasMetadata(string $metadataKey) : bool{

View File

@ -80,6 +80,9 @@ class BurningFurnace extends Solid{
$furnace = $this->getLevel()->getTile($this);
if(!($furnace instanceof TileFurnace)){
$furnace = Tile::createTile(Tile::FURNACE, $this->getLevel(), TileFurnace::createNBT($this));
if(!($furnace instanceof TileFurnace)){
return true;
}
}
if(!$furnace->canOpenWith($item->getCustomName())){

View File

@ -109,6 +109,9 @@ class Chest extends Transparent{
$chest = $t;
}else{
$chest = Tile::createTile(Tile::CHEST, $this->getLevel(), TileChest::createNBT($this));
if(!($chest instanceof TileChest)){
return true;
}
}
if(

View File

@ -47,6 +47,9 @@ class ItemFrame extends Flowable{
$tile = $this->level->getTile($this);
if(!($tile instanceof TileItemFrame)){
$tile = Tile::createTile(Tile::ITEM_FRAME, $this->getLevel(), TileItemFrame::createNBT($this));
if(!($tile instanceof TileItemFrame)){
return true;
}
}
if($tile->hasItem()){

View File

@ -55,7 +55,7 @@ abstract class Command{
*/
private $activeAliases = [];
/** @var CommandMap */
/** @var CommandMap|null */
private $commandMap = null;
/** @var string */

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

@ -60,7 +60,6 @@ class GamemodeCommand extends VanillaCommand{
return true;
}
$target = $sender;
if(isset($args[1])){
$target = $sender->getServer()->getPlayer($args[1]);
if($target === null){
@ -68,7 +67,9 @@ class GamemodeCommand extends VanillaCommand{
return true;
}
}elseif(!($sender instanceof Player)){
}elseif($sender instanceof Player){
$target = $sender;
}else{
throw new InvalidCommandSyntaxException();
}

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

@ -318,7 +318,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public const DATA_PLAYER_FLAG_DEAD = 2; //TODO: CHECK
public static $entityCount = 1;
/** @var Entity[] */
/** @var string[] */
private static $knownEntities = [];
/** @var string[][] */
private static $saveNames = [];
@ -460,8 +460,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var EntityDamageEvent|null */
protected $lastDamageCause = null;
/** @var Block[] */
protected $blocksAround = [];
/** @var Block[]|null */
protected $blocksAround = null;
/** @var float|null */
public $lastX = null;
@ -2048,7 +2048,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
* @param Player $player
*/
public function spawnTo(Player $player) : void{
if(!isset($this->hasSpawned[$player->getLoaderId()])){
if(!isset($this->hasSpawned[$player->getLoaderId()]) and $this->chunk !== null and isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])){
$this->hasSpawned[$player->getLoaderId()] = $player;
$this->sendSpawnPacket($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

@ -40,7 +40,7 @@ class Squid extends WaterAnimal{
public $width = 0.95;
public $height = 0.95;
/** @var Vector3 */
/** @var Vector3|null */
public $swimDirection = null;
public $swimSpeed = 0.1;
@ -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

@ -90,6 +90,9 @@ class Painting extends Entity{
}
public function kill() : void{
if(!$this->isAlive()){
return;
}
parent::kill();
$drops = true;

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

@ -56,7 +56,6 @@ abstract class Event{
throw new \BadMethodCallException(get_class($this) . " is not Cancellable");
}
/** @var Event $this */
return $this->isCancelled;
}
@ -70,7 +69,6 @@ abstract class Event{
throw new \BadMethodCallException(get_class($this) . " is not Cancellable");
}
/** @var Event $this */
$this->isCancelled = $value;
}
@ -78,8 +76,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

@ -23,10 +23,12 @@ declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\command\CommandSender;
use pocketmine\event\Cancellable;
use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
use function spl_object_id;
/**
* Called when a player chats something
@ -39,15 +41,15 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
protected $format;
/**
* @var Player[]
* @var CommandSender[]
*/
protected $recipients = [];
/**
* @param Player $player
* @param string $message
* @param string $format
* @param Player[] $recipients
* @param Player $player
* @param string $message
* @param string $format
* @param CommandSender[] $recipients
*/
public function __construct(Player $player, string $message, string $format = "chat.type.text", array $recipients = null){
$this->player = $player;
@ -56,7 +58,11 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
$this->format = $format;
if($recipients === null){
$this->recipients = PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS);
foreach(PermissionManager::getInstance()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS) as $permissible){
if($permissible instanceof CommandSender){
$this->recipients[spl_object_id($permissible)] = $permissible;
}
}
}else{
$this->recipients = $recipients;
}
@ -100,14 +106,14 @@ class PlayerChatEvent extends PlayerEvent implements Cancellable{
}
/**
* @return Player[]
* @return CommandSender[]
*/
public function getRecipients() : array{
return $this->recipients;
}
/**
* @param Player[] $recipients
* @param CommandSender[] $recipients
*/
public function setRecipients(array $recipients) : void{
$this->recipients = $recipients;

View File

@ -39,15 +39,15 @@ class PlayerCreationEvent extends Event{
/** @var int */
private $port;
/** @var Player::class */
/** @var string */
private $baseClass;
/** @var Player::class */
/** @var string */
private $playerClass;
/**
* @param SourceInterface $interface
* @param Player::class $baseClass
* @param Player::class $playerClass
* @param string $baseClass
* @param string $playerClass
* @param string $address
* @param int $port
*/
@ -91,14 +91,14 @@ class PlayerCreationEvent extends Event{
}
/**
* @return Player::class
* @return string
*/
public function getBaseClass(){
return $this->baseClass;
}
/**
* @param Player::class $class
* @param string $class
*/
public function setBaseClass($class){
if(!is_a($class, $this->baseClass, true)){
@ -109,14 +109,14 @@ class PlayerCreationEvent extends Event{
}
/**
* @return Player::class
* @return string
*/
public function getPlayerClass(){
return $this->playerClass;
}
/**
* @param Player::class $class
* @param string $class
*/
public function setPlayerClass($class){
if(!is_a($class, $this->baseClass, true)){

View File

@ -47,7 +47,7 @@ abstract class BaseInventory implements Inventory{
/** @var string */
protected $title;
/** @var \SplFixedArray|Item[] */
protected $slots = [];
protected $slots;
/** @var Player[] */
protected $viewers = [];
/** @var InventoryEventProcessor */

View File

@ -43,7 +43,7 @@ class CraftingManager{
/** @var FurnaceRecipe[] */
protected $furnaceRecipes = [];
/** @var BatchPacket */
/** @var BatchPacket|null */
private $craftingDataCache;
public function __construct(){

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

@ -186,16 +186,18 @@ class WritableBook extends Item{
* @return CompoundTag[]
*/
public function getPages() : array{
$pages = $this->getNamedTag()->getListTag(self::TAG_PAGES);
if($pages === null){
return [];
}
/** @var CompoundTag[] $pages */
$pages = $this->getPagesTag()->getValue();
return $pages->getValue();
return $pages;
}
protected function getPagesTag() : ListTag{
return $this->getNamedTag()->getListTag(self::TAG_PAGES) ?? new ListTag(self::TAG_PAGES, [], NBT::TAG_Compound);
$pagesTag = $this->getNamedTag()->getListTag(self::TAG_PAGES);
if($pagesTag !== null and $pagesTag->getTagType() === NBT::TAG_Compound){
return $pagesTag;
}
return new ListTag(self::TAG_PAGES, [], NBT::TAG_Compound);
}
/**

View File

@ -94,7 +94,7 @@ class Enchantment{
public const SLOT_ELYTRA = 0x4000;
public const SLOT_TRIDENT = 0x8000;
/** @var Enchantment[] */
/** @var \SplFixedArray|Enchantment[] */
protected static $enchantments;
public static function init() : void{

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

@ -265,7 +265,7 @@ class Level implements ChunkManager, Metadatable{
/** @var LevelTimings */
public $timings;
/** @var int */
/** @var float */
public $tickRateTime = 0;
/**
* @deprecated

View File

@ -29,7 +29,7 @@ use function assert;
class Position extends Vector3{
/** @var Level */
/** @var Level|null */
public $level = null;
/**

View File

@ -694,50 +694,48 @@ class Chunk{
public function initChunk(Level $level){
if(!$this->isInit){
$changed = false;
if($this->NBTentities !== null){
$level->timings->syncChunkLoadEntitiesTimer->startTiming();
foreach($this->NBTentities as $nbt){
if($nbt instanceof CompoundTag){
if(!$nbt->hasTag("id")){ //allow mixed types (because of leveldb)
$changed = true;
continue;
}
try{
$entity = Entity::createEntity($nbt->getTag("id")->getValue(), $level, $nbt);
if(!($entity instanceof Entity)){
$changed = true;
continue;
}
}catch(\Throwable $t){
$level->getServer()->getLogger()->logException($t);
$level->timings->syncChunkLoadEntitiesTimer->startTiming();
foreach($this->NBTentities as $nbt){
if($nbt instanceof CompoundTag){
if(!$nbt->hasTag("id")){ //allow mixed types (because of leveldb)
$changed = true;
continue;
}
try{
$entity = Entity::createEntity($nbt->getTag("id")->getValue(), $level, $nbt);
if(!($entity instanceof Entity)){
$changed = true;
continue;
}
}catch(\Throwable $t){
$level->getServer()->getLogger()->logException($t);
$changed = true;
continue;
}
}
$level->timings->syncChunkLoadEntitiesTimer->stopTiming();
$level->timings->syncChunkLoadTileEntitiesTimer->startTiming();
foreach($this->NBTtiles as $nbt){
if($nbt instanceof CompoundTag){
if(!$nbt->hasTag(Tile::TAG_ID, StringTag::class)){
$changed = true;
continue;
}
if(Tile::createTile($nbt->getString(Tile::TAG_ID), $level, $nbt) === null){
$changed = true;
continue;
}
}
}
$level->timings->syncChunkLoadTileEntitiesTimer->stopTiming();
$this->NBTentities = null;
$this->NBTtiles = null;
}
$this->NBTentities = [];
$level->timings->syncChunkLoadEntitiesTimer->stopTiming();
$level->timings->syncChunkLoadTileEntitiesTimer->startTiming();
foreach($this->NBTtiles as $nbt){
if($nbt instanceof CompoundTag){
if(!$nbt->hasTag(Tile::TAG_ID, StringTag::class)){
$changed = true;
continue;
}
if(Tile::createTile($nbt->getString(Tile::TAG_ID), $level, $nbt) === null){
$changed = true;
continue;
}
}
}
$this->NBTtiles = [];
$level->timings->syncChunkLoadTileEntitiesTimer->stopTiming();
$this->hasChanged = $changed;

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

@ -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

@ -29,7 +29,7 @@ namespace pocketmine\metadata;
use pocketmine\plugin\Plugin;
abstract class MetadataStore{
/** @var \SplObjectStorage[] */
/** @var \SplObjectStorage[]|MetadataValue[][] */
private $metadataMap;
/**
@ -100,7 +100,7 @@ abstract class MetadataStore{
* @param Plugin $owningPlugin
*/
public function invalidateAll(Plugin $owningPlugin){
/** @var MetadataValue[] $values */
/** @var \SplObjectStorage|MetadataValue[] $values */
foreach($this->metadataMap as $values){
if(isset($values[$owningPlugin])){
$values[$owningPlugin]->invalidate();

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;
@ -92,7 +93,11 @@ class NetworkBinaryStream extends BinaryStream{
if($c !== 1){
throw new \UnexpectedValueException("Unexpected NBT count $c");
}
$nbt = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
$decodedNBT = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
if(!($decodedNBT instanceof CompoundTag)){
throw new \UnexpectedValueException("Unexpected root tag type for itemstack");
}
$nbt = $decodedNBT;
}elseif($nbtLen !== 0){
throw new \UnexpectedValueException("Unexpected fake NBT length $nbtLen");
}
@ -377,7 +382,7 @@ class NetworkBinaryStream extends BinaryStream{
}
/**
* Writes an EntityUniqueID
* Writes an EntityRuntimeID
*
* @param int $eid
*/
@ -592,4 +597,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,51 +184,66 @@ 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){
$retval->overloads[$overloadIndex] = [];
for($paramIndex = 0, $paramCount = $this->getUnsignedVarInt(); $paramIndex < $paramCount; ++$paramIndex){
$parameter = new CommandParameter();
$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 +258,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 +283,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 +296,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 +330,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,60 +341,62 @@ 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
}
}
/** @var CommandParameter[] $overload */
foreach($commandData->overloads as $overload){
/**
* @var CommandParameter[] $overload
* @var CommandParameter $parameter
*/
/** @var CommandParameter $parameter */
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

@ -140,7 +140,7 @@ class CraftingDataPacket extends DataPacket{
}
$this->decodedEntries[] = $entry;
}
$this->getBool(); //cleanRecipes
$this->cleanRecipes = $this->getBool();
}
private static function writeEntry($entry, NetworkBinaryStream $stream, int $pos){

View File

@ -135,7 +135,7 @@ abstract class DataPacket extends NetworkBinaryStream{
abstract public function handle(NetworkSession $session) : bool;
public function clean(){
$this->buffer = null;
$this->buffer = "";
$this->isEncoded = false;
$this->offset = 0;
return $this;
@ -143,7 +143,7 @@ abstract class DataPacket extends NetworkBinaryStream{
public function __debugInfo(){
$data = [];
foreach($this as $k => $v){
foreach((array) $this as $k => $v){
if($k === "buffer" and is_string($v)){
$data[$k] = bin2hex($v);
}elseif(is_string($v) or (is_object($v) and method_exists($v, "__toString"))){

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

@ -50,7 +50,7 @@ abstract class UPnP{
$com = new \COM("HNetCfg.NATUPnP");
/** @noinspection PhpUndefinedFieldInspection */
if($com === false or !is_object($com->StaticPortMappingCollection)){
if(!is_object($com->StaticPortMappingCollection)){
throw new \RuntimeException("Failed to portforward using UPnP. Ensure that network discovery is enabled in Control Panel.");
}
@ -70,7 +70,7 @@ abstract class UPnP{
/** @noinspection PhpUndefinedClassInspection */
$com = new \COM("HNetCfg.NATUPnP");
/** @noinspection PhpUndefinedFieldInspection */
if($com === false or !is_object($com->StaticPortMappingCollection)){
if(!is_object($com->StaticPortMappingCollection)){
return false;
}
/** @noinspection PhpUndefinedFieldInspection */

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

@ -733,7 +733,7 @@ class PluginManager{
$eventClass = $parameters[0]->getClass();
}catch(\ReflectionException $e){ //class doesn't exist
if(isset($tags["softDepend"]) && !isset($this->plugins[$tags["softDepend"]])){
$this->server->getLogger()->debug("Not registering @softDepend listener " . Utils::getNiceClosureName($handlerClosure) . "(" . $parameters[0]->getType()->getName() . ") because plugin \"" . $tags["softDepend"] . "\" not found");
$this->server->getLogger()->debug("Not registering @softDepend listener " . Utils::getNiceClosureName($handlerClosure) . "() because plugin \"" . $tags["softDepend"] . "\" not found");
continue;
}

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

@ -263,6 +263,7 @@ class AsyncPool{
while(($task = $worker->unstack()) !== null){
//cancelRun() is not strictly necessary here, but it might be used to inform plugins of the task state
//(i.e. it never executed).
assert($task instanceof AsyncTask);
$task->cancelRun();
$this->removeTask($task, true);
}

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,10 +61,10 @@ class MainLogger extends \AttachableThreadedLogger{
/** @var \Threaded */
protected $logStream;
/** @var bool */
protected $shutdown;
protected $shutdown = false;
/** @var bool */
protected $logDebug;
/** @var MainLogger */
/** @var MainLogger|null */
public static $logger = null;
/** @var bool */
private $syncFlush = false;
@ -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

@ -0,0 +1,24 @@
<?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);
define('pocketmine\_PHPSTAN_ANALYSIS', true);

View File

@ -0,0 +1,80 @@
#cyclic refs in lots of objects prevent GC, causing increased memory pressure
#the proper fix is to not have cycles, but in the meantime we have these hacks
parameters:
ignoreErrors:
-
message: "#^Property pocketmine\\\\Player\\:\\:\\$sessionAdapter \\(pocketmine\\\\network\\\\mcpe\\\\PlayerNetworkSessionAdapter\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/Player.php
-
message: "#^Property pocketmine\\\\Player\\:\\:\\$cursorInventory \\(pocketmine\\\\inventory\\\\PlayerCursorInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/Player.php
-
message: "#^Property pocketmine\\\\Player\\:\\:\\$craftingGrid \\(pocketmine\\\\inventory\\\\CraftingGrid\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/Player.php
-
message: "#^Property pocketmine\\\\Player\\:\\:\\$perm \\(pocketmine\\\\permission\\\\PermissibleBase\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/Player.php
-
message: "#^Property pocketmine\\\\entity\\\\Entity\\:\\:\\$namedtag \\(pocketmine\\\\nbt\\\\tag\\\\CompoundTag\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/entity/Entity.php
-
message: "#^Property pocketmine\\\\entity\\\\Human\\:\\:\\$inventory \\(pocketmine\\\\inventory\\\\PlayerInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/entity/Human.php
-
message: "#^Property pocketmine\\\\entity\\\\Human\\:\\:\\$enderChestInventory \\(pocketmine\\\\inventory\\\\EnderChestInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/entity/Human.php
-
message: "#^Property pocketmine\\\\entity\\\\Living\\:\\:\\$armorInventory \\(pocketmine\\\\inventory\\\\ArmorInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/entity/Living.php
-
message: "#^Property pocketmine\\\\inventory\\\\DoubleChestInventory\\:\\:\\$left \\(pocketmine\\\\inventory\\\\ChestInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/inventory/DoubleChestInventory.php
-
message: "#^Property pocketmine\\\\inventory\\\\DoubleChestInventory\\:\\:\\$right \\(pocketmine\\\\inventory\\\\ChestInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/inventory/DoubleChestInventory.php
-
message: "#^Property pocketmine\\\\level\\\\Level\\:\\:\\$provider \\(pocketmine\\\\level\\\\format\\\\io\\\\LevelProvider\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/level/Level.php
-
message: "#^Property pocketmine\\\\level\\\\Level\\:\\:\\$blockMetadata \\(pocketmine\\\\metadata\\\\BlockMetadataStore\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/level/Level.php
-
message: "#^Property pocketmine\\\\level\\\\Level\\:\\:\\$temporalPosition \\(pocketmine\\\\level\\\\Position\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/level/Level.php
-
message: "#^Property pocketmine\\\\tile\\\\Chest\\:\\:\\$inventory \\(pocketmine\\\\inventory\\\\ChestInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/tile/Chest.php
-
message: "#^Property pocketmine\\\\tile\\\\Furnace\\:\\:\\$inventory \\(pocketmine\\\\inventory\\\\FurnaceInventory\\) does not accept null\\.$#"
count: 1
path: ../../../src/pocketmine/tile/Furnace.php

View File

@ -0,0 +1,12 @@
parameters:
ignoreErrors:
-
message: "#^Instantiated class COM not found\\.$#"
count: 2
path: ../../../src/pocketmine/network/upnp/UPnP.php
-
message: "#^Access to property \\$StaticPortMappingCollection on an unknown class COM\\.$#"
count: 4
path: ../../../src/pocketmine/network/upnp/UPnP.php

View File

@ -0,0 +1,30 @@
parameters:
ignoreErrors:
-
message: "#^Used constant LEVELDB_ZLIB_RAW_COMPRESSION not found\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Constant LEVELDB_ZLIB_RAW_COMPRESSION not found\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Instantiated class LevelDB not found\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Return typehint of method pocketmine\\\\level\\\\format\\\\io\\\\leveldb\\\\LevelDB\\:\\:createDB\\(\\) has invalid type LevelDB\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Return typehint of method pocketmine\\\\level\\\\format\\\\io\\\\leveldb\\\\LevelDB\\:\\:getDatabase\\(\\) has invalid type LevelDB\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Property pocketmine\\\\level\\\\format\\\\io\\\\leveldb\\\\LevelDB\\:\\:\\$db has unknown class LevelDB as its type\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php
-
message: "#^Call to method (get|put|delete|close)\\(\\) on an unknown class LevelDB\\.$#"
path: ../../../src/pocketmine/level/format/io/leveldb/LevelDB.php

View File

@ -0,0 +1,33 @@
parameters:
ignoreErrors:
-
message: "#^PHPDoc tag @param has invalid value \\(.+\\)\\: Unexpected token \"&\", expected variable at offset \\d+$#"
path: ../../../src
-
message: "#^Default value of the parameter \\#\\d+ \\$[A-Za-z\\d_]+ \\(\\-?\\d+\\) of method .+\\(\\) is incompatible with type float\\.$#"
path: ../../../src
-
message: "#^Cannot access an offset on Threaded\\.$#"
path: ../../../src
-
message: "#^Cannot assign new offset to Threaded\\.$#"
path: ../../../src
-
message: "#^Offset string does not exist on array\\(\\)\\.$#"
count: 3
path: ../../../src/pocketmine/MemoryManager.php
-
message: "#^Offset \\(int\\|string\\) does not exist on array\\(\\)\\.$#"
count: 1
path: ../../../src/pocketmine/MemoryManager.php
-
message: "#^Array \\(array\\) does not accept key int\\.$#"
count: 1
path: ../../../src/pocketmine/plugin/PluginDescription.php

View File

@ -0,0 +1,6 @@
parameters:
ignoreErrors:
-
message: "#^Variable \\$GLOBALS in isset\\(\\) always exists and is not nullable\\.$#"
path: ../../../src/pocketmine/MemoryManager.php

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,11 @@ 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
[ ! -f phpstan.phar ] && echo "Downloading PHPStan..." && curl -sSLO https://github.com/phpstan/phpstan/releases/download/0.12.0/phpstan.phar
"$PHP_BINARY" phpstan.phar analyze --no-progress --memory-limit=2G || exit 1
echo "PHPStan scan succeeded"
[ ! -f phpunit.phar ] && echo "Downloading PHPUnit..." && 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