Compare commits

...

178 Commits

Author SHA1 Message Date
d2f68836c6 Release 3.16.0 2020-11-21 01:25:06 +00:00
d19db5d2e4 fix phpstan warnings 2020-11-21 01:16:04 +00:00
98cdc80d37 Protocol changes for 1.16.100 2020-11-21 01:07:25 +00:00
8273f789ee Backport SingletonTrait to PM3 2020-11-20 21:00:47 +00:00
29eccba5f0 Updated composer dependencies 2020-11-17 23:25:15 +00:00
9984b15de6 fix build #3 2020-11-16 19:25:13 +00:00
6ea01e0dd4 fix build #2 2020-11-16 19:21:44 +00:00
46331df7db fix build 2020-11-16 19:06:56 +00:00
691c49fb32 I don't know how to pass custom arguments to these scripts :< 2020-11-16 19:05:04 +00:00
db815360d1 [ci skip] BUILDING.md: tell users to use 'composer make-server' instead of the server-phar script directly
this takes care of the dev dependencies automatically.
2020-11-16 19:03:39 +00:00
6e297168c2 travis.sh: use new composer commands 2020-11-16 19:01:07 +00:00
95dbb00d4c Added custom composer commands: 'composer make-devtools' and 'composer make-server' 2020-11-16 18:55:23 +00:00
50e29a5ed8 build/make-server: bail when composer dev dependencies are installed
these mess up the phar and make it extremely bloated. Almost everyone building for themselves unintentionally includes dev dependencies.
2020-11-16 18:54:07 +00:00
9f3fb935b5 Update composer dependencies 2020-11-12 22:03:32 +00:00
e30b1ee2c7 Clean up entity and tile saveID handling
we only ever need the first entry, so there's no point storing all of them. In addition, the field is private, which guarantees that nothing else needs the array either.
This also fixes phpstan/phpstan@c50650c5dd.
2020-11-12 21:49:12 +00:00
574b7f6343 3.15.5 is next 2020-11-10 16:48:18 +00:00
e8b6b56330 Release 3.15.4 2020-11-10 16:48:18 +00:00
c368ebb5e7 InventoryTransaction: beware of conflicting slot change actions with the same origin/target
these types of chains would never normally occur, but they've been seen in the wild. Attempting to resolve such chains has exponentially increasing complexity.
2020-11-10 16:45:20 +00:00
fa920aa868 Misplaced the changelog AGAIN 2020-11-08 19:25:55 +00:00
a421d32273 3.15.4 is next 2020-11-08 14:48:31 +00:00
6c21c23444 Release 3.15.3 2020-11-08 14:48:31 +00:00
55e0d9c520 Properly time chunk loading and chunk sending on timings reports, closes #3895 2020-11-08 14:30:12 +00:00
37ee3f2775 Player: fixed orderChunks performance issue on newly-generated maps
every time a chunk passed through Level->generateChunkCallback(), it fired onChunkChanged() for chunk listeners, which in turn caused players to rerun chunk orders even though the target chunk had not been sent yet anyway.
2020-11-05 16:17:39 +00:00
bfdcc12e81 phpstan 0.12.54 2020-11-05 14:49:00 +00:00
b2299e08e0 phpstan 0.12.53 2020-11-03 15:00:56 +00:00
d7741050c5 Updated composer dependencies 2020-11-03 14:54:50 +00:00
6cff08cd65 Chunk: fixed hasChanged being set on fastDeserialize() chunks (caused by 2bb497b716)
this caused some performance issues and silent bugs with the generator, notably that the generator would always think all chunks had been changed, causing them to be re-set back into the world 9 times.
2020-11-01 15:50:21 +00:00
fec42f16ba Level: properly define type of generator field 2020-11-01 14:36:05 +00:00
deb0cee8a0 BaseInventory::canAddItem(): consider item max stack size (#3881)
this fixes addItem() failing when canAddItem() reported that an item can be added.
2020-11-01 13:49:13 +00:00
c0dafe7872 Explosion: remove dead code
this was needed for the old ExplodePacket, which was removed a few versions back.
2020-10-29 13:32:56 +00:00
340881d590 remove superfluous newline 2020-10-26 15:59:57 +00:00
e2e960e43d tests: add missing function imports 2020-10-26 15:59:42 +00:00
500fd2d842 tests: strip useless phpdoc 2020-10-26 15:59:17 +00:00
0b550b346b imports cleanup 2020-10-26 15:43:25 +00:00
1424114cf2 Clean phpstan baselines
some of these are dead, others are FPs fixed by newer PHPStan versions.
2020-10-24 17:22:49 +01:00
a8980a0f67 phpstan 0.12.51 2020-10-24 17:10:31 +01:00
69aa7c5ac1 Support for Composer v2 (#3880) 2020-10-24 16:42:38 +01:00
11b74868ee CraftingTransaction: remove impossible condition
this is never hit thanks to the logic flow above - recipeItems is never empty.
2020-10-24 11:22:02 +01:00
9a53de0903 Utils: explode() never returns an empty array 2020-10-24 11:19:37 +01:00
0f8101d4a6 McRegion: Ignore files which don't have a valid file extension
previously a file with a 4-letter name ending in 'mca' in the region folder of a PMAnvil world would cause the world format to be unrecognized. This happens because strrpos() returns false when the substring isn't found, which gets coerced to 0 when used in addition.
2020-10-24 11:15:07 +01:00
55ecac4c80 Fixed always-true condition in world loading
this has a couple of side effects which need to be explored.
- first of all, this bug prevented generateLevel() from filling in the preset from server.properties. With this fix, worlds which don't have any extra generator settings will start to be generated using server.properties settings, which is almost certainly not expected behaviour.
- preset can now be specified separately from generator in pocketmine.yml, which is nicer for users.
2020-10-24 11:10:35 +01:00
2a1d1e90a2 php-cs-fixer nits 2020-10-21 16:44:57 +01:00
4444a79468 Bump phpunit/phpunit from 9.4.1 to 9.4.2 (#3875) 2020-10-20 09:06:46 +00:00
4cbeee3ab8 Bump phpstan/phpstan from 0.12.49 to 0.12.50 (#3874) 2020-10-17 13:44:43 +00:00
a251960c1c AsyncPool: expose workerUsage to the API
this allows plugins (and maybe later on the core) to detect async worker overload and warn the user about potential performance issues.
I planned to implement such detection in the core directly, but it turned out to be a bit more complex than I anticipated. At the least, this API might be useful to someone else.
2020-10-16 21:20:49 +01:00
52f734799e Human: do not modify totalXp unless setting XP succeeds 2020-10-16 20:43:03 +01:00
42171f6e06 Human: beware negative values in addXp() 2020-10-16 20:42:13 +01:00
1fe4fdc67c PluginDescription: fixed some very old refactoring errors 2020-10-15 13:55:40 +01:00
af4f30d1c8 Bump phpstan/phpstan from 0.12.48 to 0.12.49 (#3873) 2020-10-13 16:31:25 +00:00
3e2926441d PluginDescription: make sure that extensions constraints are actually strings 2020-10-13 17:21:10 +01:00
0b33762be0 PluginDescription: fixed type of extensions (reported by phpstan 0.12.49) 2020-10-13 17:21:10 +01:00
e6f89213dc Entity: properly account for upwards motion when calculating fall distance (#3867)
Previously, upwards movement wouldn't be considered, but downwards would, so if an entity bobbed up and down in the air for a while (e.g. while being comboed in PvP), the downwards distance would accumulate and deal a large amount of fall damage on touchdown.
This commit changes fall distance measurement to correctly account for upwards movement.

A better way of measuring fall distance would simply be to record the highest Y coordinate reached while in the air, and then measure the distance between that and the point of contact when landing. This would also remove the need to constantly update the fallDistance field. However, this would involve a BC break and will therefore have to wait until PM4.
2020-10-13 14:16:09 +01:00
f8d249b240 Isolate and always show IP details on install (#3870)
* Isolate and always show IP details on install

* camelCase
2020-10-13 14:11:28 +01:00
b39afa20d1 Bump phpunit/phpunit from 9.4.0 to 9.4.1 (#3872) 2020-10-13 13:09:56 +00:00
7027a9b972 camelCase 2020-10-12 08:25:34 -04:00
b02f3f4090 Isolate and always show IP details on install 2020-10-11 18:57:30 -04:00
8564912149 phpstan: define LEVELDB_ZLIB_RAW_COMPRESSION if it doesn't exist (for phpstan)
this improves the analysis quality by informing phpstan of the type of whatever should be there.
2020-10-11 23:01:21 +01:00
873535f719 Timezone: explicitly check result of getURL()
phpstan-strict-rules should report this, but it doesn't ...
2020-10-09 17:23:22 +01:00
78f4fcf6ab StatusCommand: removed "Maximum memory (system)"
it's not clear what this was actually supposed to represent, but it actually reports VmSize, which is already reported by "Total virtual memory". This line confuses users and is misleading.
2020-10-08 22:58:37 +01:00
90b749c260 net: reduce default compression level to 6
On larger packets, this worsens compression ratio by 1-2%, but reduces CPU load due to compression by 15-20%. Higher levels than 6 are far more expensive for diminishing returns.
Level 5 produces a further 25-30% CPU reduction, but increases bandwidth usage by 20-25%, so 6 is the sweet spot.
2020-10-08 16:51:10 +01:00
d5398b2781 3.15.3 is next 2020-10-06 13:33:42 +01:00
d7a66ad755 Release 3.15.2 2020-10-06 13:33:42 +01:00
b3f88e7b73 Updated language submodule to pmmp/Language@c85a7b79f3 2020-10-06 13:28:51 +01:00
55adc1ef63 Updated build/php submodule to pmmp/php-build-scripts@e45cfc1ece 2020-10-06 13:09:05 +01:00
868d236ddc Updated composer dependencies 2020-10-06 12:39:19 +01:00
59e9c84806 Merge branch 'stable' of https://github.com/pmmp/pocketmine-mp into stable 2020-10-06 12:35:51 +01:00
a110317d1b Bump phpstan/phpstan from 0.12.47 to 0.12.48 (#3851) 2020-10-05 13:23:19 +00:00
b169d89291 Added some documentation to FurnaceBurnEvent 2020-10-04 21:40:52 +01:00
74bef7f423 Bump phpunit/phpunit from 9.3.11 to 9.4.0 (#3850) 2020-10-02 11:02:17 +00:00
8db7867881 travis: notify about build results 2020-10-01 22:05:12 +01:00
d8f8afe531 Bump phpstan/phpstan from 0.12.46 to 0.12.47 (#3844) 2020-09-30 10:51:33 +00:00
7dabf305f8 Bump phpstan/phpstan from 0.12.44 to 0.12.46 (#3841) 2020-09-29 07:01:51 +00:00
ed0053d0ee Updated build/php submodule to pmmp/php-build-scripts@732ceba847 2020-09-26 13:20:45 +01:00
28255e35d1 Updated composer dependencies 2020-09-26 13:18:38 +01:00
0fc9170bbf Bump phpstan/phpstan from 0.12.43 to 0.12.44 (#3838) 2020-09-25 13:20:29 +00:00
7e2efae024 Bump phpstan/phpstan from 0.12.42 to 0.12.43 (#3831) 2020-09-24 11:23:32 +00:00
e9fa07b550 Bump phpunit/phpunit from 9.3.10 to 9.3.11 (#3835) 2020-09-24 09:57:58 +00:00
8ac32824a2 Bump phpunit/phpunit from 9.3.9 to 9.3.10 (#3828) 2020-09-15 17:01:03 +00:00
1322defead Bump phpunit/phpunit from 9.3.8 to 9.3.9 (#3825) 2020-09-12 12:32:33 +00:00
d084b7a34b RegionGarbageMap: add an extra overlap check
this shouldn't ever be triggered, but we want to know if it does.
2020-09-12 01:38:13 +01:00
c2d0605b1e Entity: avoid implicit float truncation in getDirection()
this didn't cause any bugs because of the way the function is written, but it might have in other circumstances.
2020-09-11 21:01:22 +01:00
0ff0b33047 StatusCommand: avoid modulo operator on float
this was detected by a custom PHPStan extension.
2020-09-11 20:56:18 +01:00
114df07622 RegionLoader: specify type of unpack() return
PHPStan has no idea what is going on in this code because unpack() returns mixed[].
Possibly it might be a good idea to implement a dynamic return type extension for this.
2020-09-11 20:48:37 +01:00
4a88db7f43 travis: update pthreads to pmmp/pthreads@b81ab29df5 2020-09-11 14:30:22 +01:00
d3ea29d527 Release memory to OS on garbage collection
ZMM often holds onto big chunks of memory after they aren't used anymore, which is fine in a webserver, but it's not OK for PM.
2020-09-09 01:40:18 +01:00
d2f1a3cf5b SubChunk: workaround opcache preloading constant issue
non-class constants aren't stored during preloading phase and for some reason they aren't pre-resolved in opcode arrays. However, they are resolved by value when referenced by class constants, and class constants stick, so we can use those instead.
2020-09-04 17:53:22 +01:00
f9c2ed6200 composer: do not install packages with min version higher than 7.3.0
running composer update on 7.4 will generate a lock file using the newest dependencies which work for the current PHP version, which usually isn't desirable for a project like this where developers might be using newer PHP versions than users.
2020-09-04 01:00:26 +01:00
e47a711494 Bump phpstan/phpstan from 0.12.40 to 0.12.42 (#3812) 2020-09-03 14:13:14 +00:00
2ea7a9e216 3.15.2 is next 2020-09-03 15:09:52 +01:00
9f60484212 Release 3.15.1 2020-09-03 15:09:52 +01:00
3031d89ec5 Potato: drop 1-5 potatoes per harvest, not 1-4 2020-09-03 14:54:58 +01:00
883e135bc0 Potato: drop poisonous potatoes when harvested, fixes #2830 2020-09-03 14:54:04 +01:00
4448f603a6 Updated build/php submodule to pmmp/php-build-scripts@3a3e3495c3 2020-09-03 14:53:23 +01:00
9365525efa ANTI CHEAT DOESN'T TRIP ON STAIRS ANYMORE 🎉
This commit fixes the 5+ years-old bug with the movement anti-cheat that everyone has complained about: sprinting on stairs causes rubberbanding.
This commit addresses this problem at long last, along with a handful of precursor commits that were necessary to fix this problem:
- dac76f0e0f
- 89fe8f7f10
- 2d77b1e364

Additionally, these changes now allow the anti-cheat to be accurate to at least 0.001 of a block, perhaps even better. I didn't commit a change to the threshold here, but it was instrumental to pinning down the exact nature of these bugs.

This closes #1475, at long last.
2020-09-03 14:27:11 +01:00
17bee5e349 Fixed crash when using strings for flatworld layers
I don't know why this didn't show up sooner.
2020-09-03 14:15:37 +01:00
c6e0753c3e clean up phpstan baselines 2020-09-02 15:04:37 +01:00
2ae7ba275b Updated Composer dependencies 2020-09-02 12:15:29 +01:00
6aa0a82341 Bump phpstan/phpstan-strict-rules from 0.12.4 to 0.12.5 (#3807) 2020-09-01 10:00:39 +00:00
0af08a7375 Call BlockGrowEvent when sugarcane grows (#3780) 2020-08-31 16:03:38 +01:00
81c1613e5d StupidJsonDecodeTest: added a callable prototype
for some reason this causes a new error to be reported which previously didn't show. I have no idea why.
2020-08-31 13:45:46 +01:00
9cf8f608d8 Provide a default for health (#3806) 2020-08-30 21:50:11 +01:00
dd4f26a9cf Switch "Auto: Spam" trap to direct links (#3550) 2020-08-30 18:03:38 +01:00
f976545f56 Delete security-dos-vulnerability.md 2020-08-29 23:41:37 +01:00
9929fb0abd Create SECURITY.md 2020-08-29 23:40:47 +01:00
37e453b875 Updated composer dependencies 2020-08-28 23:34:32 +01:00
b7578fef9c Fixup TesterPlugin to PHPStan standards 2020-08-28 21:17:21 +01:00
09eb904f6b fixed explicit-mixed errors exposed by upgrading pocketmine/nbt 2020-08-28 15:47:41 +01:00
b47d6bbc22 Bump phpstan/phpstan from 0.12.39 to 0.12.40 (#3800) 2020-08-27 11:45:20 +00:00
aa26ddf8b1 Bump phpunit/phpunit from 9.3.7 to 9.3.8 (#3799) 2020-08-27 11:44:54 +00:00
119c72980f Bump phpstan/phpstan from 0.12.37 to 0.12.39 (#3794) 2020-08-25 20:38:22 +00:00
eba888449d ExperienceUtils: handle an unhandled error condition explicitly
this would previously throw a TypeError on some negative numbers and crash. This should still crash, but this makes it explicit.
2020-08-25 21:28:29 +01:00
dac76f0e0f Player: reset ySize when syncing movement 2020-08-23 18:31:25 +01:00
89fe8f7f10 Entity: shift BB back down after trying to auto-step
this fixes bugs where the entity would jump in the air when walking on blocks like carpet. It also fixes a lot of the issues with stepping in the anti-cheat, allowing to reduce the error tolerance on movement processing.
2020-08-23 17:54:48 +01:00
2d77b1e364 Entity: fixed recalculateCollisionBox not taking ySize into account
this was causing the movement anti-cheat to shit itself after the first movement because it used setPosition() on the player if the position wasn't exactly perfect (which obviously it never is perfect, because of fp rounding errors).
2020-08-23 17:51:36 +01:00
e59a4296f8 LevelDB: fixed crash on corrupted level.dat 2020-08-22 19:36:22 +01:00
6856761946 Updated composer dependencies to fix some 7.4 issues 2020-08-21 22:09:26 +01:00
4fe3401182 travis: moved PHP-specific configuration to its own YAML file
this allows it to be imported by other repos using the same config (for example plugins needing PHPStan analysis) without them needing to copy paste big blocks of shit every time something little changes.
2020-08-21 17:42:47 +01:00
e80ad22702 Updated build/php submodule 2020-08-21 00:34:10 +01:00
c22ab37372 Player: pre-cancel PlayerInteractEvent when left-clicking a block in spectator mode
fixes #3778
2020-08-20 23:43:52 +01:00
1f9d672cfc Updated DevTools submodule to release 1.14.1 2020-08-20 16:49:49 +01:00
974cbae725 travis: added PHP 7.4 2020-08-20 16:21:58 +01:00
b53f88027e Explosion: fixed blocks getting updated too early
this bug became painfully obvious when testing bamboo. Detonating TNT near tall bamboo canes would result in bamboo canes not deleting themselves in some circumstances, because the non-destroyed parts of the cane would be updated before their supporting block was deleted, and subsequently would not be re-updated afterwards.
I think this bug should also reproduce with cacti, but I have not tested.
2020-08-16 00:09:17 +01:00
9a0f723dff Updated composer dependencies 2020-08-15 20:19:37 +01:00
ab2003a85d Merge branch 'stable' of https://github.com/pmmp/pocketmine-mp into stable 2020-08-15 20:17:04 +01:00
4befd9095a Updated build/php submodule to pmmp/php-build-scripts@f93a6f0e31 2020-08-15 20:16:29 +01:00
06623d788a Bump phpunit/phpunit from 9.3.5 to 9.3.7 (#3771) 2020-08-15 17:46:26 +00:00
730ee74a65 Use objects for internal structures created in TextFormat::toJSON() (#3747) 2020-08-15 18:30:26 +01:00
700e0afee0 Updated build/php submodule to pmmp/php-build-scripts@cfc425ad63 2020-08-11 21:39:15 +01:00
4b9712fdee fixed changelog typo 2020-08-11 21:37:15 +01:00
dbd015b866 3.15.1 is next 2020-08-11 21:26:12 +01:00
a498b0415a Release 3.15.0 2020-08-11 21:26:12 +01:00
5b01cf72dd Data sync and version bump for 1.16.20
this version doesn't change any packets as far as I know, but it does change some packet content (most notably, some blockstates changed, which are troublesome when not in sync).
2020-08-11 21:17:11 +01:00
ec21c2baa0 Merge branch 'stable' of https://github.com/pmmp/pocketmine-mp into stable 2020-08-11 21:07:06 +01:00
11a0d9b502 3.14.4 is next 2020-08-11 21:06:38 +01:00
a7fc245291 Release 3.14.3 2020-08-11 21:06:38 +01:00
6db51e2380 Updated CONTRIBUTING.md RFC label name 2020-08-11 12:35:12 +08:00
d6f35f2342 Bump phpunit/phpunit from 9.3.2 to 9.3.5 (#3764) 2020-08-10 19:15:28 +00:00
d1df72ec78 Bump phpstan/phpstan from 0.12.36 to 0.12.37 (#3765) 2020-08-10 19:10:19 +00:00
9bd6d5c67e Updated travis pthreads to pmmp/pthreads@45579e1e62 2020-08-10 17:45:06 +01:00
aaa23361d1 updated devtools links 2020-08-10 17:32:09 +01:00
691d92a959 moved tests/plugins/PocketMine-DevTools -> tests/plugins/DevTools 2020-08-10 17:29:44 +01:00
50101663f2 Use the up-to-date git submodule urls 2020-08-10 17:25:48 +01:00
e369966890 updated composer.lock 2020-08-07 20:00:12 +01:00
63f57841de PlayerAuthInputPacket: fixed yaw/pitch being the wrong way round, closes #3757 2020-08-07 19:50:49 +01:00
ac3bba0a11 Bump phpunit/phpunit from 9.2.6 to 9.3.2 (#3758) 2020-08-07 15:38:57 +00:00
1ff3df6ff0 Bump phpstan/phpstan-phpunit from 0.12.15 to 0.12.16 (#3753) 2020-08-05 19:49:04 +00:00
4e29b216bf Bump phpstan/phpstan from 0.12.35 to 0.12.36 (#3752) 2020-08-05 18:12:12 +00:00
809dad2ac8 Bump phpstan/phpstan-phpunit from 0.12.11 to 0.12.15
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 0.12.11 to 0.12.15.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/0.12.11...0.12.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-05 10:46:27 +00:00
e238d583b8 Bump phpstan/phpstan-strict-rules from 0.12.3 to 0.12.4 (#3748) 2020-08-05 10:38:00 +00:00
3f89bd7bff TaskHandler->getOwnerName() never returns NULL 2020-08-04 16:58:27 +01:00
8da7e789fd LoginPacket: protocol cannot be NULL 2020-08-04 16:55:47 +01:00
0766952f39 FINALLY, a usable new build of phpstan 2020-08-04 11:38:00 +01:00
eeee1fbe73 Updated composer dependencies 2020-08-04 11:32:25 +01:00
46c224da86 phpstan: remove an obsolete ignored error pattern from explicit-mixed baseline 2020-08-03 19:54:53 +01:00
3c001b310f fix phpstan analyze failure 2020-08-03 19:54:14 +01:00
198a106b9f Merge branch 'stable' of https://github.com/pmmp/pocketmine-mp into stable 2020-08-03 19:37:30 +01:00
1f5e0bc96d Updated BedrockData submodule, new recipes.json format 2020-08-03 19:36:32 +01:00
41f7c07703 Entity: report the class in getSaveId() unregistered entity exception (#3744) 2020-08-03 00:20:28 +01:00
f0a0c9a85f Player: remove useless var 2020-08-02 23:49:07 +01:00
5b620d964e Do not assume the presence of a crafting transaction closing marker
fixes #3655, fixes #3241
instead of guessing where the end of the transaction is, we attempt validation after every piece of the transaction, with the assumption being that a crafting transaction will not validate until it's complete.
2020-08-02 23:37:33 +01:00
756840f11d Fixed matchItems corrupting CraftingTransaction internal state on repeated validation
This bug became apparent while developing a more robust fix for 1.16 crafting.
2020-08-02 23:07:47 +01:00
df2c3136c9 VersionString: added missing start anchor to regex 2020-08-02 21:10:47 +01:00
a6b5cddd5a remove unused import 2020-07-21 19:26:24 +01:00
5b9453af43 WhitelistCommand: fixed incorrect implode() parameter order
PHP allows this to work either way for legacy reasons, but glue-first is the canonical way for a long time.
2020-07-21 11:46:11 +01:00
8bba25f4f5 Fix wrong hardness value for Podzol (#3709) 2020-07-16 22:07:41 +01:00
f9bd7016aa Bump phpstan/phpstan-strict-rules from 0.12.2 to 0.12.3 (#3705) 2020-07-16 15:24:42 +00:00
213406fc60 Bump phpunit/phpunit from 9.2.5 to 9.2.6 (#3701) 2020-07-14 16:44:35 +00:00
7ff6e5895e added missing 3.14.2 changelog 2020-07-13 11:55:08 +01:00
2e6b62fdec 3.14.3 is next 2020-07-13 10:46:58 +01:00
4fc5b9772a Release 3.14.2 2020-07-13 10:46:57 +01:00
5d4880b0a7 SendUsageTask: fixed json_encode() choking on player list keys 2020-07-11 20:14:04 +01:00
2b1a0e1e72 PlayerRespawnEvent: harden setRespawnPosition()
apparently plugins like to pass around positions which have null worlds, which aside from being quite stupid, also breaks a lot of stuff and makes it look like PM is to blame when it's just trying to make everything work the way it's supposed to ...
2020-07-10 20:37:45 +01:00
cd022f1592 EmotePacket: make FLAG_SERVER constant public 2020-07-10 20:02:32 +01:00
4ae3fd7734 Player: Reset spawn chunk send count if teleporting pre-spawn 2020-07-09 12:17:19 +01:00
b2249f93c0 TaskHandler: bail if given a task that already has a handler
This fixes undefined behaviour when scheduling the same task twice. This is usually accidental and almost never desirable.
Note that this still allows a task to be scheduled again after it has
been cancelled; it only disallows scheduling a task multiple times
concurrently.

This commit will probably break MyPlot and other plugins that have
self-scheduling tasks, but as far as I can tell those use-cases should
be replaced with self-cancelling repeating tasks anyway.
2020-07-08 11:02:33 +01:00
303344783a CheckTestCompletionTask: use TaskHandler->cancel() 2020-07-08 10:57:20 +01:00
75e0844ff5 MainLogger: log stack traces with CRITICAL level
maybe this will get people to send the whole thing instead of just the error message? ...
2020-07-08 10:45:15 +01:00
18fabf5466 3.14.2 is next 2020-07-08 10:32:07 +01:00
127 changed files with 2706 additions and 1142 deletions

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Help & support on Discord
url: https://discord.gg/bmSAZBG
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
- name: Help & support on forums
url: https://forums.pmmp.io
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
- name: Documentation
url: https://pmmp.rtfd.io
about: PocketMine-MP documentation

View File

@ -1,14 +0,0 @@
---
name: Help & support
about: We don't accept support requests here. Try the links on the README.
title: ''
labels: Support request
assignees: ''
---
We don't accept support requests on the issue tracker. Please try the following links instead:
Documentation: http://pmmp.rtfd.io
Forums: https://forums.pmmp.io
Discord: https://discord.gg/bmSAZBG

View File

@ -1,12 +0,0 @@
---
name: Security/DoS vulnerability
about: 'Bug or exploit that can be used to attack servers (hint: don''t report it
on a public issue tracker)'
title: ''
labels: 'Auto: Spam'
assignees: ''
---
Please DO NOT report security vulnerabilities here.
Instead, send an email to team@pmmp.io or contact a developer directly, IN PRIVATE.

8
.gitmodules vendored
View File

@ -1,12 +1,12 @@
[submodule "src/pocketmine/lang/locale"]
path = src/pocketmine/lang/locale
url = https://github.com/pmmp/PocketMine-Language.git
url = https://github.com/pmmp/Language.git
[submodule "tests/preprocessor"]
path = build/preprocessor
url = https://github.com/pmmp/preprocessor.git
[submodule "tests/plugins/PocketMine-DevTools"]
path = tests/plugins/PocketMine-DevTools
url = https://github.com/pmmp/PocketMine-DevTools.git
[submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git
[submodule "build/php"]
path = build/php
url = https://github.com/pmmp/php-build-scripts.git

View File

@ -1,20 +1,5 @@
language: php
php:
- 7.3
before_script:
- phpenv config-rm xdebug.ini
- echo | pecl install channel://pecl.php.net/yaml-2.1.0
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout 646dac62ae0d48c1ada7b007e15575fb84f7d71d
- phpize
- ./configure
- make
- make install
- cd ..
- echo "extension=pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
import:
source: ./tests/travis/setup-php.yml
script:
- composer install --prefer-dist
@ -29,4 +14,8 @@ cache:
- $HOME/.composer/cache/vcs
notifications:
email: false
email:
recipients:
- team@pmmp.io
on_success: change
on_failure: always

View File

@ -30,7 +30,7 @@ If you use a custom binary, you'll need to replace `composer` usages in this gui
Preprocessor requires that the `cpp` (c preprocessor) is available in your PATH.
## Building `PocketMine-MP.phar`
Run `build/server-phar.php` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
Run `composer make-server` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
You can also use the `--out` option to change the output filename.

View File

@ -116,7 +116,7 @@ class ExampleClass{
<!-- TODO: RFC and voting on the forums instead -->
### RFC and Voting
* These are big Pull Requests or contributions that change important behavior.
* RFCs will be tagged with the *PR: RFC* label
* RFCs will be tagged with the *Type: Request For Comments* label
* A vote will be held once the RFC is ready. All users can vote commenting on the Pull Request
* Comments MUST use "Yes" or "No" on the FIRST sentence to signify the vote, except when they don't want it to be counted.
* If your comment is a voting comment, specify the reason of your vote or it won't be counted.

View File

@ -19,7 +19,7 @@
## For developers
* [Building and running from source](BUILDING.md)
* [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
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
* [Contributing Guidelines](CONTRIBUTING.md)

33
SECURITY.md Normal file
View File

@ -0,0 +1,33 @@
# Security Policy
## Supported Versions
The following release lines are currently receiving active security updates and bug fixes:
| Version | Supported |
| -------- | ------------------ |
| 3.15.x | :white_check_mark: |
| < 3.15.0 | :x: |
## Reporting a Vulnerability
**DO NOT report vulnerabilities on the GitHub issue tracker.**
GitHub is public and anyone can see the issues you post on the issue tracker, including people who would exploit vulnerabilities for their own gain.
**WARNING: You may put live servers at risk by reporting a vulnerability on the GitHub issue tracker.**
**Contact us** by sending an email to [**team@pmmp.io**](mailto:team@pmmp.io?subject=Security%20Vulnerability%20in%20PocketMine-MP). Include the following information:
- Version of PocketMine-MP
- Detailed description of the vulnerability (e.g. how to exploit it, what the effects are)
Please note that we can't guarantee a reply to every email.
## FAQ
### Do you offer a bug bounty?
No.
### How soon can I expect a fix for a vulnerability I've reported?
This depends on the nature of the problem. We can't provide any general ETA (nor would it be wise to provide one).
In general, it depends on when developers have time to look into the problem, how complex the problem is to fix, and how many users it impacts.
When a fix for a severe vulnerability is pushed, a patch release for the target version will usually be released within 24 hours so that users can update.

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\build\make_release;
use pocketmine\utils\VersionString;
use function defined;
use function dirname;
use function fgets;
use function file_get_contents;
@ -37,7 +38,6 @@ use const STDIN;
require_once dirname(__DIR__) . '/vendor/autoload.php';
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev) : void{
$versionInfo = file_get_contents($versionInfoPath);
$versionInfo = preg_replace(

View File

@ -41,6 +41,7 @@ use function rtrim;
use function sprintf;
use function str_replace;
use function unlink;
use const PHP_EOL;
require dirname(__DIR__) . '/vendor/autoload.php';
@ -129,6 +130,10 @@ function main() : void{
echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL;
exit(1);
}
if(file_exists(dirname(__DIR__) . '/vendor/phpunit')){
echo "Remove Composer dev dependencies before building (composer install --no-dev)" . PHP_EOL;
exit(1);
}
$opts = getopt("", ["out:", "git:"]);
if(isset($opts["git"])){

View File

@ -23,3 +23,18 @@ Plugin developers should **only** update their required API to this version if y
- `/plugins`, `/whitelist list`, `/banlist` and `/list` now show output in alphabetical order.
- Some `pocketmine\event` APIs which accept arrays now have more robust type checking, fixing type errors caused by plugin input occurring in core code.
- `Attribute::getAttributeByName()` is now aware of the `minecraft:lava_movement` attribute.
# 3.14.2
- Exception stack traces are now logged as CRITICAL. It's hoped that users will recognize that they are just as important as the error message and not leave them out when asking for help with errors on Discord.
- `TaskScheduler` no longer accepts tasks that already have a handler. This fixes undefined behaviour which occurs when scheduling the same task instance twice, but it does break plugins such as **MyPlot** which unintentionally used this buggy behaviour.
- Players will now correctly receive the needed number of spawn chunks if they are teleported between `PlayerLoginEvent` and `PlayerJoinEvent`. This fixes a bug that could occur when teleporting players in delayed tasks between login and join.
- `PlayerRespawnEvent->setRespawnPosition()` now throws an exception if the provided `Position` has an invalid world associated with it (null or unloaded).
- Fixed a crash that occurred when stats reporting was enabled.
# 3.14.3
- Fixed deprecation error when running `/whitelist list` on PHP 7.4.
- Fixed podzol breaking animation being incorrect (incorrect hardness).
- `Entity::getSaveId()` now reports the class name in the message thrown for unregistered entities.
- Fixed `CraftingManager->validate()` producing different results when called multiple times for the same transaction.
- Fixed various issues with batch-crafting items using the recipe book and shift-clicking.
- `tests/plugins/PocketMine-DevTools` submodule has been renamed to `tests/plugins/DevTools`.

54
changelogs/3.15.md Normal file
View File

@ -0,0 +1,54 @@
**For Minecraft: Bedrock Edition 1.16.20**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.15.0
- Added support for Minecraft: Bedrock Edition 1.16.20.
- Removed compatibility with 1.16.0.
## Known issues (please don't open issues for these)
- Walls don't connect to each other
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
- New blocks, items & mobs aren't implemented
- Nether doesn't exist
# 3.15.1
- Fixed various PHP 7.4 compatibility issues in Composer dependencies (primarily callback-validator).
- Fixed LevelDB worlds with corrupted `level.dat` crashing the server instead of failing gracefully.
- Fixed error spam when using strings for layers in flatworld presets (`e.g. bedrock,3xdirt,grass`).
- Fixed blocks not getting updated properly on explosions.
- Fixed `BlockGrowEvent` not being called when sugarcane grows.
- Potato crops now drop poisonous potatoes when harvested.
- Fixed the wrong number of potatoes being dropped when harvesting potato crops.
- Players no longer get pullbacks when sprinting on slabs, stairs and various other blocks when `player.anti-cheat.allow-movement-cheats` is set to `false`. (This bug has been around for over 5 years, so many of you will be used to its existence.)
- Fixed entity collision box calculation not taking clip distance into account.
- Entities now step up the correct height of the target block, instead of jumping into the air 0.6 blocks and falling back down.
# 3.15.2
- Fixed issues with preloading `SubChunk`.
- `/gc` and automatic garbage collection will now release unused heap blocks back to the OS. Previously, the PHP process might hold onto these blocks indefinitely even when not used, causing elevated real memory usage.
- Added some documentation to `FurnaceBurnEvent`.
# 3.15.3
- Fixed fall damage accumulation over continuous knockbacks (e.g. combo attacks in PvP).
- Fixed a bug in `Human->addXp()` that would cause a crash when saving player data.
- `Human->addXp()` will no longer modify the target's total XP if `PlayerExperienceChangeEvent` was cancelled.
- `AsyncPool->getTaskQueueSizes()` has been added to allow external detection of async pool overload. This is planned to be implemented as a core feature in the future, but it hasn't been done yet.
- `BaseInventory->canAddItem()` behaviour now matches `addItem()` by considering the max stack size of the given item.
- Fixed a bug in generator options handling for worlds loaded via `pocketmine.yml`. This fix has the following side effects:
- It's now possible to provide generator options as an `options` key when loading a world via `pocketmine.yml`.
- If generator options are not provided, the options from `server.properties` will be used, instead of using an empty preset. (It's not clear whether this is desired behaviour, but it was clearly intended, since there is code to do this which was broken until this release. As such, this behaviour is subject to change in the future.)
- Fixed a bug in region-based world loading where some files without filename extensions and names containing a region filename extension (e.g a file named `amca` in a McRegion world) would cause the world not to load. These files are now ignored.
- Default network compression level has been lowered to 6, due to level 7 being 25% more expensive for only a marginal improvement in bandwidth.
- Fixed a performance issue with chunk requesting when players trigger chunk generation on first join.
- Setup wizard will now always show IP information, even if the user chose to skip the setup wizard when prompted. (This doesn't affect `--no-wizard` in any way.)
- `Maximum memory (system)` is no longer reported in `/status` due to having a misleading output (it was the same as the current memory usage).
- The `Player Chunk Send` timer on timings reports now actually reports measurements of chunk sending, not chunk loading.
- A new parent timer `World Load` has been added to timings reports, which aggregates timings from `syncChunkLoad` and subtimings from all worlds.
# 3.15.4
- Fixed a bug in the inventory transaction system that caused the server to freeze under some circumstances.

18
changelogs/3.16.md Normal file
View File

@ -0,0 +1,18 @@
**For Minecraft: Bedrock Edition 1.16.100**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.16.0
- Added support for Minecraft: Bedrock Edition 1.16.100.
- Removed compatibility with earlier versions.
- Added new custom composer commands `make-server` and `make-devtools` to ease setting up a development environment and building the server.
## Known issues (please don't open issues for these)
- Walls don't connect to each other
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
- New blocks, items & mobs aren't implemented
- Nether doesn't exist

View File

@ -33,12 +33,12 @@
"pocketmine/classloader": "^0.1.0",
"pocketmine/log": "^0.2.0",
"pocketmine/log-pthreads": "^0.1.0",
"pocketmine/callback-validator": "^1.0.1",
"pocketmine/callback-validator": "^1.0.2",
"adhocore/json-comment": "^0.1.0",
"ocramius/package-versions": "^1.5"
"composer-runtime-api": "^2.0"
},
"require-dev": {
"phpstan/phpstan": "0.12.29",
"phpstan/phpstan": "0.12.54",
"phpstan/phpstan-phpunit": "^0.12.6",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^9.2"
@ -57,5 +57,17 @@
"psr-4": {
"pocketmine\\": "tests/phpunit/"
}
},
"config": {
"platform": {
"php": "7.3.0"
}
},
"scripts": {
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
"make-server": [
"@composer install --no-dev --classmap-authoritative",
"@php -dphar.readonly=0 build/server-phar.php"
]
}
}

1226
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,6 @@ This site contains auto-generated API documentation for PocketMine-MP (and depen
This site can be accessed via https://apidoc.pmmp.io.
### Additional developer resources
- [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
- [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
- [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
- [DeveloperDocs](https://github.com/pmmp/DeveloperDocs/) - Reference, guides and specifications for the PocketMine-MP API

View File

@ -2,7 +2,6 @@ includes:
- tests/phpstan/configs/actual-problems.neon
- tests/phpstan/configs/check-explicit-mixed-baseline.neon
- tests/phpstan/configs/com-dotnet-magic.neon
- tests/phpstan/configs/custom-leveldb.neon
- tests/phpstan/configs/gc-hacks.neon
- tests/phpstan/configs/l7-baseline.neon
- tests/phpstan/configs/l8-baseline.neon
@ -20,6 +19,8 @@ parameters:
checkExplicitMixed: true
bootstrapFiles:
- tests/phpstan/bootstrap.php
scanDirectories:
- tests/plugins/TesterPlugin
scanFiles:
- src/pocketmine/PocketMine.php
- build/make-release.php
@ -29,6 +30,7 @@ parameters:
- build/make-release.php
- build/server-phar.php
- tests/phpunit
- tests/plugins/TesterPlugin
dynamicConstantNames:
- pocketmine\IS_DEVELOPMENT_BUILD
- pocketmine\DEBUG

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine;
use PackageVersions\Versions;
use Composer\InstalledVersions;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginLoadOrder;
@ -54,6 +54,7 @@ use function php_uname;
use function phpinfo;
use function phpversion;
use function preg_replace;
use function sprintf;
use function str_split;
use function strpos;
use function substr;
@ -338,6 +339,15 @@ class CrashDump{
private function generalData() : void{
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
$composerLibraries = [];
foreach(InstalledVersions::getInstalledPackages() as $package){
$composerLibraries[$package] = sprintf(
"%s@%s",
InstalledVersions::getPrettyVersion($package) ?? "unknown",
InstalledVersions::getReference($package) ?? "unknown"
);
}
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["base_version"] = \pocketmine\BASE_VERSION;
@ -350,7 +360,7 @@ class CrashDump{
$this->data["general"]["zend"] = zend_version();
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->data["general"]["composer_libraries"] = Versions::VERSIONS;
$this->data["general"]["composer_libraries"] = $composerLibraries;
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . \pocketmine\GIT_COMMIT);
$this->addLine("uname -a: " . php_uname("a"));
@ -358,7 +368,7 @@ class CrashDump{
$this->addLine("Zend version: " . zend_version());
$this->addLine("OS : " . PHP_OS . ", " . Utils::getOS());
$this->addLine("Composer libraries: ");
foreach(Versions::VERSIONS as $library => $libraryVersion){
foreach($composerLibraries as $library => $libraryVersion){
$this->addLine("- $library $libraryVersion");
}
}

View File

@ -40,6 +40,7 @@ use function fwrite;
use function gc_collect_cycles;
use function gc_disable;
use function gc_enable;
use function gc_mem_caches;
use function get_class;
use function get_declared_classes;
use function implode;
@ -273,6 +274,7 @@ class MemoryManager{
}
$cycles = gc_collect_cycles();
gc_mem_caches();
Timings::$garbageCollectorTimer->stopTiming();

View File

@ -100,6 +100,7 @@ use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\network\mcpe\convert\ItemTypeDictionary;
use pocketmine\network\mcpe\PlayerNetworkSessionAdapter;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
@ -148,7 +149,9 @@ use pocketmine\network\mcpe\protocol\types\CommandEnum;
use pocketmine\network\mcpe\protocol\types\CommandParameter;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\GameMode;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor;
use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece;
@ -1600,10 +1603,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$dy = $newPos->y - $this->y;
$dz = $newPos->z - $this->z;
//the client likes to clip into blocks like stairs, but we do full server-side prediction of that without
//help from the client's position changes, so we deduct the expected clip height from the moved distance.
$expectedClipDistance = $this->ySize * (1 - self::STEP_CLIP_MULTIPLIER);
$dy -= $expectedClipDistance;
$this->move($dx, $dy, $dz);
$diff = $this->distanceSquared($newPos);
//TODO: Explore lowering this threshold now that stairs work properly.
if($this->isSurvival() and $diff > 0.0625){
$ev = new PlayerIllegalMoveEvent($this, $newPos, new Vector3($this->lastX, $this->lastY, $this->lastZ));
$ev->setCancelled($this->allowMovementCheats);
@ -1613,7 +1621,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if(!$ev->isCancelled()){
$revert = true;
$this->server->getLogger()->debug($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidMove", [$this->getName()]));
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos);
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos . ", expected clip distance: $expectedClipDistance");
}
}
@ -1897,7 +1905,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol ?? "unknown"]), false);
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol]), false);
return true;
}
@ -1933,7 +1941,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$animation["ImageWidth"],
base64_decode($animation["Image"], true)),
$animation["Type"],
$animation["Frames"]
$animation["Frames"],
$animation["AnimationExpression"]
);
}
@ -2173,6 +2182,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
$pk->mustAccept = false;
$pk->experiments = new Experiments([], false);
$this->dataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
@ -2238,6 +2248,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$pk->commandsEnabled = true;
$pk->levelId = "";
$pk->worldName = $this->server->getMotd();
$pk->experiments = new Experiments([], false);
$pk->itemTable = ItemTypeDictionary::getInstance()->getEntries();
$this->dataPacket($pk);
$this->sendDataPacket(new AvailableActorIdentifiersPacket());
@ -2392,18 +2404,20 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var InventoryAction[] $actions */
$actions = [];
$isCraftingPart = false;
$isFinalCraftingPart = false;
foreach($packet->actions as $networkInventoryAction){
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and (
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
) or (
$this->craftingTransaction !== null &&
!$networkInventoryAction->oldItem->equalsExact($networkInventoryAction->newItem) &&
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
$networkInventoryAction->windowId === ContainerIds::UI &&
$networkInventoryAction->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT
)
){
$isCraftingPart = true;
if($networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT){
$isFinalCraftingPart = true;
}
}
try{
$action = $networkInventoryAction->createInventoryAction($this);
@ -2426,23 +2440,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
if($isFinalCraftingPart){
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
//trying to execute it
$ret = true;
try{
$this->craftingTransaction->execute();
}catch(TransactionValidationException $e){
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
$ret = false;
}
$this->craftingTransaction = null;
return $ret;
try{
$this->craftingTransaction->validate();
}catch(TransactionValidationException $e){
//transaction is incomplete - crafting transaction comes in lots of little bits, so we have to collect
//all of the parts before we can execute it
return true;
}
return true;
try{
$this->craftingTransaction->execute();
return true;
}catch(TransactionValidationException $e){
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
return false;
}finally{
$this->craftingTransaction = null;
}
}elseif($this->craftingTransaction !== null){
$this->server->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction from " . $this->getName() . ", refusing to execute crafting");
$this->craftingTransaction = null;
@ -2862,7 +2876,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE_REQUEST)){
if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN)){
return true;
}
@ -2878,7 +2892,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$target = $this->level->getBlock($pos);
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
if($this->level->checkSpawnProtection($this, $target)){
if($this->isSpectator() || $this->level->checkSpawnProtection($this, $target)){
$ev->setCancelled();
}
@ -2949,7 +2963,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
case PlayerActionPacket::ACTION_STOP_SWIMMING:
//TODO: handle this when it doesn't spam every damn tick (yet another spam bug!!)
break;
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //ignored (for now)
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //TODO: ignored (for now)
break;
case PlayerActionPacket::ACTION_CREATIVE_PLAYER_DESTROY_BLOCK:
//TODO: do we need to handle this?
break;
default:
$this->server->getLogger()->debug("Unhandled/unknown player action type " . $packet->action . " from " . $this->getName());
@ -3031,6 +3048,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
/** @var int|null */
private $closingWindowId = null;
/** @internal */
public function getClosingWindowId() : ?int{ return $this->closingWindowId; }
public function handleContainerClose(ContainerClosePacket $packet) : bool{
if(!$this->spawned){
return true;
@ -3042,12 +3065,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
unset($this->openHardcodedWindows[$packet->windowId]);
$pk = new ContainerClosePacket();
$pk->windowId = $packet->windowId;
$pk->server = false;
$this->sendDataPacket($pk);
return true;
}
if(isset($this->windowIndex[$packet->windowId])){
$this->closingWindowId = $packet->windowId;
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
$this->removeWindow($this->windowIndex[$packet->windowId]);
$this->closingWindowId = null;
//removeWindow handles sending the appropriate
return true;
}
@ -3900,10 +3926,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
if($targets !== null){
if(in_array($this, $targets, true)){
$this->forceMoveSync = $pos->asVector3();
$this->ySize = 0;
}
$this->server->broadcastPacket($targets, $pk);
}else{
$this->forceMoveSync = $pos->asVector3();
$this->ySize = 0;
$this->dataPacket($pk);
}
}
@ -3923,6 +3951,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->resetFallDistance();
$this->nextChunkOrderRun = 0;
if($this->spawnChunkLoadCount !== -1){
$this->spawnChunkLoadCount = 0;
}
$this->stopSleep();
//TODO: workaround for player last pos not getting updated
@ -4109,7 +4140,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function onChunkChanged(Chunk $chunk){
if(isset($this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())])){
$hasSent = $this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())] ?? false;
if($hasSent){
$this->usedChunks[$hash] = false;
$this->nextChunkOrderRun = 0;
}

View File

@ -1400,10 +1400,10 @@ class Server{
Network::$BATCH_THRESHOLD = -1;
}
$this->networkCompressionLevel = (int) $this->getProperty("network.compression-level", 7);
$this->networkCompressionLevel = (int) $this->getProperty("network.compression-level", 6);
if($this->networkCompressionLevel < 1 or $this->networkCompressionLevel > 9){
$this->logger->warning("Invalid network compression level $this->networkCompressionLevel set, setting to default 7");
$this->networkCompressionLevel = 7;
$this->logger->warning("Invalid network compression level $this->networkCompressionLevel set, setting to default 6");
$this->networkCompressionLevel = 6;
}
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
@ -1543,7 +1543,7 @@ class Server{
if(isset($options["generator"])){
$generatorOptions = explode(":", $options["generator"]);
$generator = GeneratorManager::getGenerator(array_shift($generatorOptions));
if(count($options) > 0){
if(count($generatorOptions) > 0){
$options["preset"] = implode(":", $generatorOptions);
}
}else{

View File

@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
const _VERSION_INFO_INCLUDED = true;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.14.1";
const BASE_VERSION = "3.16.0";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -40,6 +40,6 @@ class Podzol extends Solid{
}
public function getHardness() : float{
return 2.5;
return 0.5;
}
}

View File

@ -40,9 +40,13 @@ class Potato extends Crops{
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
ItemFactory::get(Item::POTATO, 0, $this->getDamage() >= 0x07 ? mt_rand(1, 4) : 1)
$result = [
ItemFactory::get(Item::POTATO, 0, $this->getDamage() >= 0x07 ? mt_rand(1, 5) : 1)
];
if($this->getDamage() >= 7 && mt_rand(0, 49) === 0){
$result[] = ItemFactory::get(Item::POISONOUS_POTATO);
}
return $result;
}
public function getPickedItem() : Item{

View File

@ -87,7 +87,12 @@ class Sugarcane extends Flowable{
for($y = 1; $y < 3; ++$y){
$b = $this->getLevelNonNull()->getBlockAt($this->x, $this->y + $y, $this->z);
if($b->getId() === self::AIR){
$this->getLevelNonNull()->setBlock($b, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
$ev->call();
if($ev->isCancelled()){
break;
}
$this->getLevelNonNull()->setBlock($b, $ev->getNewState(), true);
break;
}
}

View File

@ -54,9 +54,9 @@ class StatusCommand extends VanillaCommand{
$server = $sender->getServer();
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
$time = microtime(true) - \pocketmine\START_TIME;
$time = (int) (microtime(true) - \pocketmine\START_TIME);
$seconds = floor($time % 60);
$seconds = $time % 60;
$minutes = null;
$hours = null;
$days = null;
@ -100,7 +100,6 @@ class StatusCommand extends VanillaCommand{
$sender->sendMessage(TextFormat::GOLD . "Total memory: " . TextFormat::RED . number_format(round(($mUsage[1] / 1024) / 1024, 2), 2) . " MB.");
$sender->sendMessage(TextFormat::GOLD . "Total virtual memory: " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2), 2) . " MB.");
$sender->sendMessage(TextFormat::GOLD . "Heap memory: " . TextFormat::RED . number_format(round(($rUsage[0] / 1024) / 1024, 2), 2) . " MB.");
$sender->sendMessage(TextFormat::GOLD . "Maximum memory (system): " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2), 2) . " MB.");
if($server->getProperty("memory.global-limit") > 0){
$sender->sendMessage(TextFormat::GOLD . "Maximum memory (manager): " . TextFormat::RED . number_format(round($server->getProperty("memory.global-limit"), 2), 2) . " MB.");

View File

@ -74,7 +74,7 @@ class WhitelistCommand extends VanillaCommand{
case "list":
$entries = $sender->getServer()->getWhitelisted()->getAll(true);
sort($entries, SORT_STRING);
$result = implode($entries, ", ");
$result = implode(", ", $entries);
$count = count($entries);
$sender->sendMessage(new TranslationContainer("commands.whitelist.list", [$count, $count]));

View File

@ -77,9 +77,9 @@ use function abs;
use function assert;
use function cos;
use function count;
use function current;
use function deg2rad;
use function floor;
use function fmod;
use function get_class;
use function in_array;
use function is_a;
@ -94,6 +94,7 @@ use const M_PI_2;
abstract class Entity extends Location implements Metadatable, EntityIds{
public const MOTION_THRESHOLD = 0.00001;
protected const STEP_CLIP_MULTIPLIER = 0.4;
public const NETWORK_ID = -1;
@ -332,8 +333,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
*/
private static $knownEntities = [];
/**
* @var string[][]
* @phpstan-var array<class-string<Entity>, list<string>>
* @var string[]
* @phpstan-var array<class-string<Entity>, string>
*/
private static $saveNames = [];
@ -412,7 +413,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
self::$knownEntities[$name] = $className;
}
self::$saveNames[$className] = $saveNames;
self::$saveNames[$className] = reset($saveNames);
return true;
}
@ -707,10 +708,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->boundingBox->setBounds(
$this->x - $halfWidth,
$this->y,
$this->y + $this->ySize,
$this->z - $halfWidth,
$this->x + $halfWidth,
$this->y + $this->height,
$this->y + $this->height + $this->ySize,
$this->z + $halfWidth
);
}
@ -868,10 +869,9 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
*/
public function getSaveId() : string{
if(!isset(self::$saveNames[static::class])){
throw new \InvalidStateException("Entity is not registered");
throw new \InvalidStateException("Entity " . static::class . " is not registered");
}
reset(self::$saveNames[static::class]);
return current(self::$saveNames[static::class]);
return self::$saveNames[static::class];
}
public function saveNBT() : void{
@ -1311,7 +1311,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
}
public function getDirection() : ?int{
$rotation = ($this->yaw - 90) % 360;
$rotation = fmod($this->yaw - 90, 360);
if($rotation < 0){
$rotation += 360.0;
}
@ -1449,8 +1449,14 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->fall($this->fallDistance);
$this->resetFallDistance();
}
}elseif($distanceThisTick < 0){
}elseif($distanceThisTick < $this->fallDistance){
//we've fallen some distance (distanceThisTick is negative)
//or we ascended back towards where fall distance was measured from initially (distanceThisTick is positive but less than existing fallDistance)
$this->fallDistance -= $distanceThisTick;
}else{
//we ascended past the apex where fall distance was originally being measured from
//reset it so it will be measured starting from the new, higher position
$this->fallDistance = 0;
}
}
@ -1538,7 +1544,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
if($this->keepMovement){
$this->boundingBox->offset($dx, $dy, $dz);
}else{
$this->ySize *= 0.4;
$this->ySize *= self::STEP_CLIP_MULTIPLIER;
/*
if($this->isColliding){ //With cobweb?
@ -1605,7 +1611,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->boundingBox->offset(0, 0, $dz);
if($this->stepHeight > 0 and $fallingFlag and $this->ySize < 0.05 and ($movX != $dx or $movZ != $dz)){
if($this->stepHeight > 0 and $fallingFlag and ($movX != $dx or $movZ != $dz)){
$cx = $dx;
$cy = $dy;
$cz = $dz;
@ -1637,13 +1643,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
$this->boundingBox->offset(0, 0, $dz);
$reverseDY = -$dy;
foreach($list as $bb){
$reverseDY = $bb->calculateYOffset($this->boundingBox, $reverseDY);
}
$dy += $reverseDY;
$this->boundingBox->offset(0, $reverseDY, 0);
if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){
$dx = $cx;
$dy = $cy;
$dz = $cz;
$this->boundingBox->setBB($axisalignedbb1);
}else{
$this->ySize += 0.5; //FIXME: this should be the height of the block it walked up, not fixed 0.5
$this->ySize += $dy;
}
}
}

View File

@ -400,12 +400,14 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
* @param bool $playSound Whether to play level-up and XP gained sounds.
*/
public function addXp(int $amount, bool $playSound = true) : bool{
$this->totalXp += $amount;
$oldLevel = $this->getXpLevel();
$oldTotal = $this->getCurrentTotalXp();
if($this->setCurrentTotalXp($oldTotal + $amount)){
if($amount > 0){
$this->totalXp += $amount;
}
if($playSound){
$newLevel = $this->getXpLevel();
if((int) ($newLevel / 5) > (int) ($oldLevel / 5)){

View File

@ -45,6 +45,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
@ -97,17 +98,15 @@ abstract class Living extends Entity implements Damageable{
//TODO: load/save armor inventory contents
$this->armorInventory->setEventProcessor(new ArmorInventoryEventProcessor($this));
$health = $this->getMaxHealth();
if($this->namedtag->hasTag("HealF", FloatTag::class)){
$health = $this->namedtag->getFloat("HealF");
$this->namedtag->removeTag("HealF");
}elseif($this->namedtag->hasTag("Health")){
$healthTag = $this->namedtag->getTag("Health");
$health = (float) $healthTag->getValue(); //Older versions of PocketMine-MP incorrectly saved this as a short instead of a float
if(!($healthTag instanceof FloatTag)){
$this->namedtag->removeTag("Health");
}
}elseif($this->namedtag->hasTag("Health", ShortTag::class)){
//Older versions of PocketMine-MP incorrectly saved this as a short instead of a float
$health = $this->namedtag->getShort("Health");
$this->namedtag->removeTag("Health");
}else{
$health = $this->namedtag->getFloat("Health", $this->getMaxHealth());
}
$this->setHealth($health);

View File

@ -24,6 +24,8 @@ declare(strict_types=1);
namespace pocketmine\entity\utils;
use pocketmine\math\Math;
use pocketmine\utils\AssumptionFailedError;
use function count;
use function max;
abstract class ExperienceUtils{
@ -59,6 +61,9 @@ abstract class ExperienceUtils{
* This returns a floating-point number, the decimal part being the progress through the resulting level.
*/
public static function getLevelFromXp(int $xp) : float{
if($xp < 0){
throw new \InvalidArgumentException("XP must be at least 0");
}
if($xp <= self::getXpToReachLevel(16)){
$a = 1;
$b = 6;
@ -74,6 +79,9 @@ abstract class ExperienceUtils{
}
$x = Math::solveQuadratic($a, $b, $c - $xp);
if(count($x) === 0){
throw new AssumptionFailedError("Expected at least 1 solution");
}
return max($x); //we're only interested in the positive solution
}

View File

@ -28,6 +28,9 @@ use pocketmine\event\Cancellable;
use pocketmine\item\Item;
use pocketmine\tile\Furnace;
/**
* Called when a furnace is about to consume a new fuel item.
*/
class FurnaceBurnEvent extends BlockEvent implements Cancellable{
/** @var Furnace */
private $furnace;
@ -53,18 +56,31 @@ class FurnaceBurnEvent extends BlockEvent implements Cancellable{
return $this->fuel;
}
/**
* Returns the number of ticks that the furnace will be powered for.
*/
public function getBurnTime() : int{
return $this->burnTime;
}
/**
* Sets the number of ticks that the given fuel will power the furnace for.
*/
public function setBurnTime(int $burnTime) : void{
$this->burnTime = $burnTime;
}
/**
* Returns whether the fuel item will be consumed.
*/
public function isBurning() : bool{
return $this->burning;
}
/**
* Sets whether the fuel will be consumed. If false, the furnace will smelt as if it consumed fuel, but no fuel
* will be deducted.
*/
public function setBurning(bool $burning) : void{
$this->burning = $burning;
}

View File

@ -29,7 +29,6 @@ use pocketmine\permission\PermissionManager;
use pocketmine\Player;
use pocketmine\Server;
use pocketmine\utils\Utils;
use function array_values;
use function spl_object_id;
/**

View File

@ -43,6 +43,9 @@ class PlayerRespawnEvent extends PlayerEvent{
}
public function setRespawnPosition(Position $position) : void{
if(!$position->isValid()){
throw new \InvalidArgumentException("Spawn position must reference a valid and loaded World");
}
$this->position = $position;
}
}

View File

@ -252,11 +252,11 @@ abstract class BaseInventory implements Inventory{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->equals($slot)){
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){
$count -= $diff;
}
}elseif($slot->isNull()){
$count -= $this->getMaxStackSize();
$count -= min($this->getMaxStackSize(), $item->getMaxStackSize());
}
if($count <= 0){

View File

@ -64,6 +64,7 @@ abstract class ContainerInventory extends BaseInventory{
public function onClose(Player $who) : void{
$pk = new ContainerClosePacket();
$pk->windowId = $who->getWindowId($this);
$pk->server = $who->getClosingWindowId() !== $pk->windowId;
$who->dataPacket($pk);
parent::onClose($who);
}

View File

@ -28,8 +28,10 @@ use pocketmine\network\mcpe\protocol\BatchPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use function array_map;
use function file_get_contents;
use function is_array;
use function json_decode;
use function json_encode;
use function usort;
@ -52,41 +54,39 @@ class CraftingManager{
public function init() : void{
$recipes = json_decode(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla" . DIRECTORY_SEPARATOR . "recipes.json"), true);
if(!is_array($recipes)){
throw new AssumptionFailedError("recipes.json root should contain a map of recipe types");
}
$itemDeserializerFunc = \Closure::fromCallable([Item::class, 'jsonDeserialize']);
foreach($recipes as $recipe){
switch($recipe["type"]){
case "shapeless":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerShapelessRecipe(new ShapelessRecipe(
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "shaped":
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerShapedRecipe(new ShapedRecipe(
$recipe["shape"],
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
break;
case "smelting":
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
break;
}
$this->registerFurnaceRecipe(new FurnaceRecipe(
Item::jsonDeserialize($recipe["output"]),
Item::jsonDeserialize($recipe["input"]))
);
break;
default:
break;
foreach($recipes["shapeless"] as $recipe){
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerShapelessRecipe(new ShapelessRecipe(
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
}
foreach($recipes["shaped"] as $recipe){
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerShapedRecipe(new ShapedRecipe(
$recipe["shape"],
array_map($itemDeserializerFunc, $recipe["input"]),
array_map($itemDeserializerFunc, $recipe["output"])
));
}
foreach($recipes["smelting"] as $recipe){
if($recipe["block"] !== "furnace"){ //TODO: filter others out for now to avoid breaking economics
continue;
}
$this->registerFurnaceRecipe(new FurnaceRecipe(
Item::jsonDeserialize($recipe["output"]),
Item::jsonDeserialize($recipe["input"]))
);
}
$this->buildCraftingDataCache();

View File

@ -108,9 +108,6 @@ class CraftingTransaction extends InventoryTransaction{
}
}
if($iterations < 1){
throw new TransactionValidationException("Tried to craft zero times");
}
if(count($txItems) > 0){
//all items should be destroyed in this process
throw new TransactionValidationException("Expected 0 ingredients left over, have " . count($txItems));
@ -166,6 +163,7 @@ class CraftingTransaction extends InventoryTransaction{
*/
$pk = new ContainerClosePacket();
$pk->windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID;
$pk->server = true;
$this->source->dataPacket($pk);
}

View File

@ -136,6 +136,8 @@ class InventoryTransaction{
* @throws TransactionValidationException
*/
protected function matchItems(array &$needItems, array &$haveItems) : void{
$needItems = [];
$haveItems = [];
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();
@ -228,21 +230,34 @@ class InventoryTransaction{
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(count($possibleActions) > 0);
$candidate = null;
$newList = $possibleActions;
foreach($possibleActions as $i => $action){
if($action->getSourceItem()->equalsExact($needOrigin)){
$newList = $possibleActions;
if($candidate !== null){
/*
* we found multiple possible actions that match the origin action
* this means that there are multiple ways that this chain could play out
* if we cared so much about this, we could build all the possible chains in parallel and see which
* variation managed to complete the chain, but this has an extremely high complexity which is not
* worth the trouble for this scenario (we don't usually expect to see chains longer than a couple
* of actions in here anyway), and might still result in multiple possible results.
*/
return null;
}
$candidate = $action;
unset($newList[$i]);
if(count($newList) === 0){
return $action->getTargetItem();
}
$result = $this->findResultItem($action->getTargetItem(), $newList);
if($result !== null){
return $result;
}
}
}
if($candidate === null){
//chaining is not possible with this origin, none of the actions are valid
return null;
}
return null;
if(count($newList) === 0){
return $candidate->getTargetItem();
}
return $this->findResultItem($candidate->getTargetItem(), $newList);
}
/**

View File

@ -42,6 +42,7 @@ use pocketmine\nbt\tag\NamedTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use function array_map;
use function base64_decode;
@ -475,7 +476,12 @@ class Item implements ItemIds, \JsonSerializable{
public function getLore() : array{
$display = $this->getNamedTagEntry(self::TAG_DISPLAY);
if($display instanceof CompoundTag and ($lore = $display->getListTag(self::TAG_DISPLAY_LORE)) !== null){
return $lore->getAllValues();
return array_map(function(NamedTag $line) : string{
if(!($line instanceof StringTag)){
throw new AssumptionFailedError("Nobody bothered to handle this error case and we can't fix it until PM4, oops ... #blameshoghi");
}
return $line->getValue();
}, $lore->getValue());
}
return [];

View File

@ -154,7 +154,6 @@ class Explosion{
* and creating sounds and particles.
*/
public function explodeB() : bool{
$send = [];
$updateBlocks = [];
$source = (new Vector3($this->source->x, $this->source->y, $this->source->z))->floor();
@ -232,7 +231,9 @@ class Explosion{
$t->close();
}
}
foreach($this->affectedBlocks as $block){
$pos = new Vector3($block->x, $block->y, $block->z);
for($side = 0; $side <= 5; $side++){
@ -252,7 +253,6 @@ class Explosion{
$updateBlocks[$index] = true;
}
}
$send[] = new Vector3($block->x - $source->x, $block->y - $source->y, $block->z - $source->z);
}
$this->level->addParticle(new HugeExplodeSeedParticle($source));

View File

@ -53,7 +53,6 @@ use pocketmine\level\format\io\ChunkRequestTask;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
use pocketmine\level\format\io\LevelProvider;
use pocketmine\level\generator\Generator;
use pocketmine\level\generator\GeneratorManager;
use pocketmine\level\generator\GeneratorRegisterTask;
use pocketmine\level\generator\GeneratorUnregisterTask;
@ -289,7 +288,10 @@ class Level implements ChunkManager, Metadatable{
/** @var bool */
private $doingTick = false;
/** @var string|Generator */
/**
* @var string
* @phpstan-var class-string<\pocketmine\level\generator\Generator>
*/
private $generator;
/** @var bool */

View File

@ -80,15 +80,14 @@ class LevelTimings{
$this->entityTick = new TimingsHandler("** " . $name . "entityTick");
$this->tileEntityTick = new TimingsHandler("** " . $name . "tileEntityTick");
$this->syncChunkSendTimer = new TimingsHandler("** " . $name . "syncChunkSend");
$this->syncChunkSendPrepareTimer = new TimingsHandler("** " . $name . "syncChunkSendPrepare");
Timings::init(); //make sure the timers we want are available
$this->syncChunkSendTimer = new TimingsHandler("** " . $name . "syncChunkSend", Timings::$playerChunkSendTimer);
$this->syncChunkSendPrepareTimer = new TimingsHandler("** " . $name . "syncChunkSendPrepare", Timings::$playerChunkSendTimer);
$this->syncChunkLoadTimer = new TimingsHandler("** " . $name . "syncChunkLoad");
$this->syncChunkLoadTimer = new TimingsHandler("** " . $name . "syncChunkLoad", Timings::$worldLoadTimer);
$this->syncChunkLoadDataTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Data");
$this->syncChunkLoadEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Entities");
$this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileEntities");
Timings::init(); //make sure the timer we want is available
$this->syncChunkSaveTimer = new TimingsHandler("** " . $name . "syncChunkSave", Timings::$worldSaveTimer);
$this->doTick = new TimingsHandler($name . "doTick");

View File

@ -30,6 +30,7 @@ use pocketmine\block\BlockFactory;
use pocketmine\entity\Entity;
use pocketmine\level\Level;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\Player;
use pocketmine\tile\Spawnable;
@ -691,13 +692,14 @@ class Chunk{
$level->timings->syncChunkLoadEntitiesTimer->startTiming();
foreach($this->NBTentities as $nbt){
if(!$nbt->hasTag("id")){ //allow mixed types (because of leveldb)
$idTag = $nbt->getTag("id");
if(!($idTag instanceof IntTag) && !($idTag instanceof StringTag)){ //allow mixed types (because of leveldb)
$changed = true;
continue;
}
try{
$entity = Entity::createEntity($nbt->getTag("id")->getValue(), $level, $nbt);
$entity = Entity::createEntity($idTag->getValue(), $level, $nbt);
if(!($entity instanceof Entity)){
$changed = true;
continue;
@ -927,6 +929,7 @@ class Chunk{
$chunk->setGenerated($terrainGenerated);
$chunk->setPopulated($terrainPopulated);
$chunk->setLightPopulated($lightPopulated);
$chunk->setChanged(false);
return $chunk;
}

View File

@ -38,6 +38,8 @@ if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
}
class SubChunk implements SubChunkInterface{
private const ZERO_NIBBLE_ARRAY = ZERO_NIBBLE_ARRAY;
/** @var string */
protected $ids;
/** @var string */
@ -68,7 +70,7 @@ class SubChunk implements SubChunkInterface{
substr_count($this->ids, "\x00") === 4096 and
(!$checkLight or (
substr_count($this->skyLight, "\xff") === 2048 and
$this->blockLight === ZERO_NIBBLE_ARRAY
$this->blockLight === self::ZERO_NIBBLE_ARRAY
))
);
}
@ -230,14 +232,14 @@ class SubChunk implements SubChunkInterface{
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
* modified, which is perfect for this purpose.
*/
if($this->data === ZERO_NIBBLE_ARRAY){
$this->data = ZERO_NIBBLE_ARRAY;
if($this->data === self::ZERO_NIBBLE_ARRAY){
$this->data = self::ZERO_NIBBLE_ARRAY;
}
if($this->skyLight === ZERO_NIBBLE_ARRAY){
$this->skyLight = ZERO_NIBBLE_ARRAY;
if($this->skyLight === self::ZERO_NIBBLE_ARRAY){
$this->skyLight = self::ZERO_NIBBLE_ARRAY;
}
if($this->blockLight === ZERO_NIBBLE_ARRAY){
$this->blockLight = ZERO_NIBBLE_ARRAY;
if($this->blockLight === self::ZERO_NIBBLE_ARRAY){
$this->blockLight = self::ZERO_NIBBLE_ARRAY;
}
}
}

View File

@ -131,7 +131,11 @@ class LevelDB extends BaseLevelProvider{
throw new LevelException("Truncated level.dat");
}
$nbt = new LittleEndianNBTStream();
$levelData = $nbt->read(substr($rawLevelData, 8));
try{
$levelData = $nbt->read(substr($rawLevelData, 8));
}catch(\UnexpectedValueException $e){
throw new LevelException("Invalid level.dat (" . $e->getMessage() . ")", 0, $e);
}
if($levelData instanceof CompoundTag){
$this->levelData = $levelData;
}else{

View File

@ -245,11 +245,13 @@ class McRegion extends BaseLevelProvider{
if($isValid){
$files = array_filter(scandir($path . "/region/", SCANDIR_SORT_NONE), function(string $file) : bool{
return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file
$extPos = strrpos($file, ".");
return $extPos !== false && substr($file, $extPos + 1, 2) === "mc"; //region file
});
foreach($files as $f){
if(substr($f, strrpos($f, ".") + 1) !== static::REGION_FILE_EXTENSION){
$extPos = strrpos($f, ".");
if($extPos !== false && substr($f, $extPos + 1) !== static::REGION_FILE_EXTENSION){
$isValid = false;
break;
}

View File

@ -69,11 +69,14 @@ final class RegionGarbageMap{
/** @var RegionLocationTableEntry|null $prevEntry */
$prevEntry = null;
foreach($usedMap as $firstSector => $entry){
$expectedStart = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
$actualStart = $entry->getFirstSector();
if($expectedStart < $actualStart){
$prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
$currentStart = $entry->getFirstSector();
if($prevEndPlusOne < $currentStart){
//found a gap in the table
$garbageMap[$expectedStart] = new RegionLocationTableEntry($expectedStart, $actualStart - $expectedStart, 0);
$garbageMap[$prevEndPlusOne] = new RegionLocationTableEntry($prevEndPlusOne, $currentStart - $prevEndPlusOne, 0);
}elseif($prevEndPlusOne > $currentStart){
//current entry starts inside the previous. This would be a bug since RegionLoader should prevent this
throw new AssumptionFailedError("Overlapping entries detected");
}
$prevEntry = $entry;
}

View File

@ -303,6 +303,7 @@ class RegionLoader{
throw new CorruptedRegionException("Corrupted region header (unexpected end of file)");
}
/** @var int[] $data */
$data = unpack("N*", $headerRaw);
for($i = 0; $i < 1024; ++$i){

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\level\generator;
use pocketmine\block\BlockFactory;
use pocketmine\item\ItemFactory;
use pocketmine\level\biome\Biome;
use pocketmine\level\Level;
use pocketmine\level\SimpleChunkManager;
@ -34,7 +35,10 @@ use function unserialize;
class GeneratorRegisterTask extends AsyncTask{
/** @var string */
/**
* @var string
* @phpstan-var class-string<Generator>
*/
public $generatorClass;
/** @var string */
public $settings;
@ -47,6 +51,7 @@ class GeneratorRegisterTask extends AsyncTask{
/**
* @param mixed[] $generatorSettings
* @phpstan-param class-string<Generator> $generatorClass
* @phpstan-param array<string, mixed> $generatorSettings
*/
public function __construct(Level $level, string $generatorClass, array $generatorSettings = []){
@ -59,6 +64,7 @@ class GeneratorRegisterTask extends AsyncTask{
public function onRun(){
BlockFactory::init();
ItemFactory::init();
Biome::init();
$manager = new SimpleChunkManager($this->seed, $this->worldHeight);
$this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager);

View File

@ -35,6 +35,9 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\NamedTag;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\convert\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
use pocketmine\network\mcpe\protocol\types\EntityLink;
use pocketmine\network\mcpe\protocol\types\GameRuleType;
@ -47,6 +50,7 @@ use pocketmine\network\mcpe\protocol\types\StructureEditorData;
use pocketmine\network\mcpe\protocol\types\StructureSettings;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\UUID;
use function assert;
use function count;
use function strlen;
@ -91,7 +95,8 @@ class NetworkBinaryStream extends BinaryStream{
$skinImage = $this->getSkinImage();
$animationType = $this->getLInt();
$animationFrames = $this->getLFloat();
$animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames);
$expressionType = $this->getLInt();
$animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames, $expressionType);
}
$capeData = $this->getSkinImage();
$geometryData = $this->getString();
@ -186,15 +191,17 @@ class NetworkBinaryStream extends BinaryStream{
}
public function getSlot() : Item{
$id = $this->getVarInt();
if($id === 0){
$netId = $this->getVarInt();
if($netId === 0){
return ItemFactory::get(0, 0, 0);
}
$auxValue = $this->getVarInt();
$data = $auxValue >> 8;
$netData = $auxValue >> 8;
$cnt = $auxValue & 0xff;
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($netId, $netData);
$nbtLen = $this->getLShort();
/** @var CompoundTag|null $nbt */
@ -223,12 +230,12 @@ class NetworkBinaryStream extends BinaryStream{
$this->getString();
}
if($id === ItemIds::SHIELD){
if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){
$this->getVarLong(); //"blocking tick" (ffs mojang)
}
if($nbt !== null){
if($nbt->hasTag(self::DAMAGE_TAG, IntTag::class)){
$data = $nbt->getInt(self::DAMAGE_TAG);
$meta = $nbt->getInt(self::DAMAGE_TAG);
$nbt->removeTag(self::DAMAGE_TAG);
if($nbt->count() === 0){
$nbt = null;
@ -242,7 +249,7 @@ class NetworkBinaryStream extends BinaryStream{
}
}
end:
return ItemFactory::get($id, $data, $cnt, $nbt);
return ItemFactory::get($id, $meta, $cnt, $nbt);
}
public function putSlot(Item $item) : void{
@ -252,8 +259,10 @@ class NetworkBinaryStream extends BinaryStream{
return;
}
$this->putVarInt($item->getId());
$auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount();
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage());
$this->putVarInt($netId);
$auxValue = (($netData & 0x7fff) << 8) | $item->getCount();
$this->putVarInt($auxValue);
$nbt = null;
@ -284,20 +293,18 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
$this->putVarInt(0); //CanDestroy entry count (TODO)
if($item->getId() === ItemIds::SHIELD){
if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){
$this->putVarLong(0); //"blocking tick" (ffs mojang)
}
}
public function getRecipeIngredient() : Item{
$id = $this->getVarInt();
if($id === 0){
$netId = $this->getVarInt();
if($netId === 0){
return ItemFactory::get(ItemIds::AIR, 0, 0);
}
$meta = $this->getVarInt();
if($meta === 0x7fff){
$meta = -1;
}
$netData = $this->getVarInt();
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($netId, $netData);
$count = $this->getVarInt();
return ItemFactory::get($id, $meta, $count);
}
@ -306,8 +313,14 @@ class NetworkBinaryStream extends BinaryStream{
if($item->isNull()){
$this->putVarInt(0);
}else{
$this->putVarInt($item->getId());
$this->putVarInt($item->getDamage() & 0x7fff);
if($item->hasAnyDamageValue()){
[$netId, ] = ItemTranslator::getInstance()->toNetworkId($item->getId(), 0);
$netData = 0x7fff;
}else{
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage());
}
$this->putVarInt($netId);
$this->putVarInt($netData);
$this->putVarInt($item->getCount());
}
}
@ -750,6 +763,25 @@ class NetworkBinaryStream extends BinaryStream{
$this->putVarInt($structureEditorData->structureRedstoneSaveMove);
}
public function getNbtRoot() : NamedTag{
$offset = $this->getOffset();
try{
$result = (new NetworkLittleEndianNBTStream())->read($this->getBuffer(), false, $offset, 512);
assert($result instanceof NamedTag, "doMultiple is false so we should definitely have a NamedTag here");
return $result;
}finally{
$this->setOffset($offset);
}
}
public function getNbtCompoundRoot() : CompoundTag{
$root = $this->getNbtRoot();
if(!($root instanceof CompoundTag)){
throw new \UnexpectedValueException("Expected TAG_Compound root");
}
return $root;
}
public function readGenericTypeNetworkId() : int{
return $this->getVarInt();
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\ActorFallPacket;
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
use pocketmine\network\mcpe\protocol\AddActorPacket;
use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
@ -33,6 +32,7 @@ use pocketmine\network\mcpe\protocol\AddItemActorPacket;
use pocketmine\network\mcpe\protocol\AddPaintingPacket;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimateEntityPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\AnvilDamagePacket;
use pocketmine\network\mcpe\protocol\AutomationClientConnectPacket;
@ -45,6 +45,7 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
use pocketmine\network\mcpe\protocol\BookEditPacket;
use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\CameraPacket;
use pocketmine\network\mcpe\protocol\CameraShakePacket;
use pocketmine\network\mcpe\protocol\ChangeDimensionPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundMapItemDataPacket;
@ -60,6 +61,7 @@ use pocketmine\network\mcpe\protocol\CompletedUsingItemPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\network\mcpe\protocol\CorrectPlayerMovePredictionPacket;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
@ -77,6 +79,7 @@ use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemComponentPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
@ -96,6 +99,7 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
use pocketmine\network\mcpe\protocol\MotionPredictionHintsPacket;
use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket;
use pocketmine\network\mcpe\protocol\MoveActorDeltaPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
@ -111,6 +115,7 @@ use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerArmorDamagePacket;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
use pocketmine\network\mcpe\protocol\PlayerFogPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
@ -171,7 +176,6 @@ use pocketmine\network\mcpe\protocol\TickSyncPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPropertiesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
use pocketmine\network\mcpe\protocol\UpdatePlayerGameTypePacket;
@ -325,10 +329,6 @@ abstract class NetworkSession{
return false;
}
public function handleActorFall(ActorFallPacket $packet) : bool{
return false;
}
public function handleHurtArmor(HurtArmorPacket $packet) : bool{
return false;
}
@ -705,10 +705,6 @@ abstract class NetworkSession{
return false;
}
public function handleUpdateBlockProperties(UpdateBlockPropertiesPacket $packet) : bool{
return false;
}
public function handleClientCacheBlobStatus(ClientCacheBlobStatusPacket $packet) : bool{
return false;
}
@ -796,4 +792,28 @@ abstract class NetworkSession{
public function handlePacketViolationWarning(PacketViolationWarningPacket $packet) : bool{
return false;
}
public function handleMotionPredictionHints(MotionPredictionHintsPacket $packet) : bool{
return false;
}
public function handleAnimateEntity(AnimateEntityPacket $packet) : bool{
return false;
}
public function handleCameraShake(CameraShakePacket $packet) : bool{
return false;
}
public function handlePlayerFog(PlayerFogPacket $packet) : bool{
return false;
}
public function handleCorrectPlayerMovePrediction(CorrectPlayerMovePredictionPacket $packet) : bool{
return false;
}
public function handleItemComponent(ItemComponentPacket $packet) : bool{
return false;
}
}

View File

@ -25,7 +25,6 @@ namespace pocketmine\network\mcpe;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
use pocketmine\network\mcpe\protocol\ActorFallPacket;
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\AnimatePacket;
@ -174,10 +173,6 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
return $this->player->handlePlayerAction($packet);
}
public function handleActorFall(ActorFallPacket $packet) : bool{
return true; //Not used
}
public function handleAnimate(AnimatePacket $packet) : bool{
return $this->player->handleAnimate($packet);
}

View File

@ -0,0 +1,183 @@
<?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\convert;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use function array_key_exists;
use function file_get_contents;
use function is_array;
use function is_numeric;
use function is_string;
use function json_decode;
/**
* This class handles translation between network item ID+metadata to PocketMine-MP internal ID+metadata and vice versa.
*/
final class ItemTranslator{
use SingletonTrait;
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private $simpleCoreToNetMapping = [];
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private $simpleNetToCoreMapping = [];
/**
* runtimeId = array[internalId][metadata]
* @var int[][]
* @phpstan-var array<int, array<int, int>>
*/
private $complexCoreToNetMapping = [];
/**
* [internalId, metadata] = array[runtimeId]
* @var int[][]
* @phpstan-var array<int, array{int, int}>
*/
private $complexNetToCoreMapping = [];
private static function make() : self{
$data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/r16_to_current_item_map.json');
if($data === false) throw new AssumptionFailedError("Missing required resource file");
$json = json_decode($data, true);
if(!is_array($json) or !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){
throw new AssumptionFailedError("Invalid item table format");
}
$legacyStringToIntMapRaw = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/item_id_map.json');
if($legacyStringToIntMapRaw === false){
throw new AssumptionFailedError("Missing required resource file");
}
$legacyStringToIntMap = json_decode($legacyStringToIntMapRaw, true);
if(!is_array($legacyStringToIntMap)){
throw new AssumptionFailedError("Invalid mapping table format");
}
/** @phpstan-var array<string, int> $simpleMappings */
$simpleMappings = [];
foreach($json["simple"] as $oldId => $newId){
if(!is_string($oldId) || !is_string($newId)){
throw new AssumptionFailedError("Invalid item table format");
}
$simpleMappings[$newId] = $legacyStringToIntMap[$oldId];
}
foreach($legacyStringToIntMap as $stringId => $intId){
if(isset($simpleMappings[$stringId])){
throw new \UnexpectedValueException("Old ID $stringId collides with new ID");
}
$simpleMappings[$stringId] = $intId;
}
/** @phpstan-var array<string, array{int, int}> $complexMappings */
$complexMappings = [];
foreach($json["complex"] as $oldId => $map){
if(!is_string($oldId) || !is_array($map)){
throw new AssumptionFailedError("Invalid item table format");
}
foreach($map as $meta => $newId){
if(!is_numeric($meta) || !is_string($newId)){
throw new AssumptionFailedError("Invalid item table format");
}
$complexMappings[$newId] = [$legacyStringToIntMap[$oldId], (int) $meta];
}
}
return new self(ItemTypeDictionary::getInstance(), $simpleMappings, $complexMappings);
}
/**
* @param int[] $simpleMappings
* @param int[][] $complexMappings
* @phpstan-param array<string, int> $simpleMappings
* @phpstan-param array<string, array<int, int>> $complexMappings
*/
public function __construct(ItemTypeDictionary $dictionary, array $simpleMappings, array $complexMappings){
foreach($dictionary->getEntries() as $entry){
$stringId = $entry->getStringId();
$netId = $entry->getNumericId();
if(isset($complexMappings[$stringId])){
[$id, $meta] = $complexMappings[$stringId];
$this->complexCoreToNetMapping[$id][$meta] = $netId;
$this->complexNetToCoreMapping[$netId] = [$id, $meta];
}elseif(isset($simpleMappings[$stringId])){
$this->simpleCoreToNetMapping[$simpleMappings[$stringId]] = $netId;
$this->simpleNetToCoreMapping[$netId] = $simpleMappings[$stringId];
}elseif($stringId !== "minecraft:unknown"){
throw new \InvalidArgumentException("Unmapped entry " . $stringId);
}
}
}
/**
* @return int[]
* @phpstan-return array{int, int}
*/
public function toNetworkId(int $internalId, int $internalMeta) : array{
if(isset($this->complexCoreToNetMapping[$internalId][$internalMeta])){
return [$this->complexCoreToNetMapping[$internalId][$internalMeta], 0];
}
if(array_key_exists($internalId, $this->simpleCoreToNetMapping)){
return [$this->simpleCoreToNetMapping[$internalId], $internalMeta];
}
throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta");
}
/**
* @return int[]
* @phpstan-return array{int, int}
*/
public function fromNetworkId(int $networkId, int $networkMeta, ?bool &$isComplexMapping = null) : array{
if(isset($this->complexNetToCoreMapping[$networkId])){
if($networkMeta !== 0){
throw new \UnexpectedValueException("Unexpected non-zero network meta on complex item mapping");
}
$isComplexMapping = true;
return $this->complexNetToCoreMapping[$networkId];
}
$isComplexMapping = false;
if(isset($this->simpleNetToCoreMapping[$networkId])){
return [$this->simpleNetToCoreMapping[$networkId], $networkMeta];
}
throw new \UnexpectedValueException("Unmapped network ID/metadata combination $networkId:$networkMeta");
}
/**
* @return int[]
* @phpstan-return array{int, int}
*/
public function fromNetworkIdWithWildcardHandling(int $networkId, int $networkMeta) : array{
$isComplexMapping = false;
if($networkMeta !== 0x7fff){
return $this->fromNetworkId($networkId, $networkMeta);
}
[$id, $meta] = $this->fromNetworkId($networkId, 0, $isComplexMapping);
return [$id, $isComplexMapping ? $meta : -1];
}
}

View File

@ -0,0 +1,106 @@
<?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\convert;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use function array_key_exists;
use function file_get_contents;
use function is_array;
use function is_bool;
use function is_int;
use function is_string;
use function json_decode;
final class ItemTypeDictionary{
use SingletonTrait;
/**
* @var ItemTypeEntry[]
* @phpstan-var list<ItemTypeEntry>
*/
private $itemTypes;
/**
* @var string[]
* @phpstan-var array<int, string>
*/
private $intToStringIdMap = [];
/**
* @var int[]
* @phpstan-var array<string, int>
*/
private $stringToIntMap = [];
private static function make() : self{
$data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/required_item_list.json');
if($data === false) throw new AssumptionFailedError("Missing required resource file");
$table = json_decode($data, true);
if(!is_array($table)){
throw new AssumptionFailedError("Invalid item list format");
}
$params = [];
foreach($table as $name => $entry){
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
throw new AssumptionFailedError("Invalid item list format");
}
$params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]);
}
return new self($params);
}
/**
* @param ItemTypeEntry[] $itemTypes
*/
public function __construct(array $itemTypes){
$this->itemTypes = $itemTypes;
foreach($this->itemTypes as $type){
$this->stringToIntMap[$type->getStringId()] = $type->getNumericId();
$this->intToStringIdMap[$type->getNumericId()] = $type->getStringId();
}
}
/**
* @return ItemTypeEntry[]
* @phpstan-return list<ItemTypeEntry>
*/
public function getEntries() : array{
return $this->itemTypes;
}
public function fromStringId(string $stringId) : int{
if(!array_key_exists($stringId, $this->stringToIntMap)){
throw new \InvalidArgumentException("Unmapped string ID \"$stringId\"");
}
return $this->stringToIntMap[$stringId];
}
public function fromIntId(int $intId) : string{
if(!array_key_exists($intId, $this->intToStringIdMap)){
throw new \InvalidArgumentException("Unmapped int ID $intId");
}
return $this->intToStringIdMap[$intId];
}
}

View File

@ -27,14 +27,10 @@ use pocketmine\block\BlockIds;
use pocketmine\nbt\NBT;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\utils\AssumptionFailedError;
use function file_get_contents;
use function getmypid;
use function json_decode;
use function mt_rand;
use function mt_srand;
use function shuffle;
/**
* @internal
@ -53,14 +49,16 @@ final class RuntimeBlockMapping{
}
public static function init() : void{
$tag = (new NetworkLittleEndianNBTStream())->read(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/required_block_states.nbt"));
if(!($tag instanceof ListTag) or $tag->getTagType() !== NBT::TAG_Compound){ //this is a little redundant currently, but good for auto complete and makes phpstan happy
throw new \RuntimeException("Invalid blockstates table, expected TAG_List<TAG_Compound> root");
$canonicalBlockStatesFile = file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/canonical_block_states.nbt");
if($canonicalBlockStatesFile === false){
throw new AssumptionFailedError("Missing required resource file");
}
/** @var CompoundTag[] $list */
$list = $tag->getValue();
self::$bedrockKnownStates = self::randomizeTable($list);
$stream = new NetworkBinaryStream($canonicalBlockStatesFile);
$list = [];
while(!$stream->feof()){
$list[] = $stream->getNbtCompoundRoot();
}
self::$bedrockKnownStates = $list;
self::setupLegacyMappings();
}
@ -90,7 +88,7 @@ final class RuntimeBlockMapping{
*/
$idToStatesMap = [];
foreach(self::$bedrockKnownStates as $k => $state){
$idToStatesMap[$state->getCompoundTag("block")->getString("name")][] = $k;
$idToStatesMap[$state->getString("name")][] = $k;
}
foreach($legacyStateMap as $pair){
$id = $legacyIdMap[$pair->getId()] ?? null;
@ -105,14 +103,14 @@ final class RuntimeBlockMapping{
$mappedState = $pair->getBlockState();
//TODO HACK: idiotic NBT compare behaviour on 3.x compares keys which are stored by values
$mappedState->setName("block");
$mappedState->setName("");
$mappedName = $mappedState->getString("name");
if(!isset($idToStatesMap[$mappedName])){
throw new \RuntimeException("Mapped new state does not appear in network table");
}
foreach($idToStatesMap[$mappedName] as $k){
$networkState = self::$bedrockKnownStates[$k];
if($mappedState->equals($networkState->getCompoundTag("block"))){
if($mappedState->equals($networkState)){
self::registerMapping($k, $id, $data);
continue 2;
}
@ -127,23 +125,6 @@ final class RuntimeBlockMapping{
}
}
/**
* 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
* aren't supposed to do, so we have to deliberately break it to make them stop.
*
* @param CompoundTag[] $table
*
* @return CompoundTag[]
*/
private static function randomizeTable(array $table) : array{
$postSeed = mt_rand(); //save a seed to set afterwards, to avoid poor quality randoms
mt_srand(getmypid()); //Use a seed which is the same on all threads. This isn't a secure seed, but we don't care.
shuffle($table);
mt_srand($postSeed); //restore a good quality seed that isn't dependent on PID
return $table;
}
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
self::lazyInit();
/*

View File

@ -0,0 +1,108 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use function count;
class AnimateEntityPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::ANIMATE_ENTITY_PACKET;
/** @var string */
private $animation;
/** @var string */
private $nextState;
/** @var string */
private $stopExpression;
/** @var string */
private $controller;
/** @var float */
private $blendOutTime;
/**
* @var int[]
* @phpstan-var list<int>
*/
private $actorRuntimeIds;
/**
* @param int[] $actorRuntimeIds
* @phpstan-param list<int> $actorRuntimeIds
*/
public static function create(string $animation, string $nextState, string $stopExpression, string $controller, float $blendOutTime, array $actorRuntimeIds) : self{
$result = new self;
$result->animation = $animation;
$result->nextState = $nextState;
$result->stopExpression = $stopExpression;
$result->controller = $controller;
$result->blendOutTime = $blendOutTime;
$result->actorRuntimeIds = $actorRuntimeIds;
return $result;
}
public function getAnimation() : string{ return $this->animation; }
public function getNextState() : string{ return $this->nextState; }
public function getStopExpression() : string{ return $this->stopExpression; }
public function getController() : string{ return $this->controller; }
public function getBlendOutTime() : float{ return $this->blendOutTime; }
/**
* @return int[]
* @phpstan-return list<int>
*/
public function getActorRuntimeIds() : array{ return $this->actorRuntimeIds; }
protected function decodePayload() : void{
$this->animation = $this->getString();
$this->nextState = $this->getString();
$this->stopExpression = $this->getString();
$this->controller = $this->getString();
$this->blendOutTime = $this->getLFloat();
$this->actorRuntimeIds = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->actorRuntimeIds[] = $this->getEntityRuntimeId();
}
}
protected function encodePayload() : void{
$this->putString($this->animation);
$this->putString($this->nextState);
$this->putString($this->stopExpression);
$this->putString($this->controller);
$this->putLFloat($this->blendOutTime);
$this->putUnsignedVarInt(count($this->actorRuntimeIds));
foreach($this->actorRuntimeIds as $id){
$this->putEntityRuntimeId($id);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleAnimateEntity($this);
}
}

View File

@ -0,0 +1,72 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class CameraShakePacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::CAMERA_SHAKE_PACKET;
public const TYPE_POSITIONAL = 0;
public const TYPE_ROTATIONAL = 1;
/** @var float */
private $intensity;
/** @var float */
private $duration;
/** @var int */
private $shakeType;
public static function create(float $intensity, float $duration, int $shakeType) : self{
$result = new self;
$result->intensity = $intensity;
$result->duration = $duration;
$result->shakeType = $shakeType;
return $result;
}
public function getIntensity() : float{ return $this->intensity; }
public function getDuration() : float{ return $this->duration; }
public function getShakeType() : int{ return $this->shakeType; }
protected function decodePayload() : void{
$this->intensity = $this->getLFloat();
$this->duration = $this->getLFloat();
$this->shakeType = $this->getByte();
}
protected function encodePayload() : void{
$this->putLFloat($this->intensity);
$this->putLFloat($this->duration);
$this->putByte($this->shakeType);
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleCameraShake($this);
}
}

View File

@ -32,13 +32,17 @@ class ContainerClosePacket extends DataPacket{
/** @var int */
public $windowId;
/** @var bool */
public $server = false;
protected function decodePayload(){
$this->windowId = $this->getByte();
$this->server = $this->getBool();
}
protected function encodePayload(){
$this->putByte($this->windowId);
$this->putBool($this->server);
}
public function handle(NetworkSession $session) : bool{

View File

@ -0,0 +1,77 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
class CorrectPlayerMovePredictionPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::CORRECT_PLAYER_MOVE_PREDICTION_PACKET;
/** @var Vector3 */
private $position;
/** @var Vector3 */
private $delta;
/** @var bool */
private $onGround;
/** @var int */
private $tick;
public static function create(Vector3 $position, Vector3 $delta, bool $onGround, int $tick) : self{
$result = new self;
$result->position = $position;
$result->delta = $delta;
$result->onGround = $onGround;
$result->tick = $tick;
return $result;
}
public function getPosition() : Vector3{ return $this->position; }
public function getDelta() : Vector3{ return $this->delta; }
public function isOnGround() : bool{ return $this->onGround; }
public function getTick() : int{ return $this->tick; }
protected function decodePayload() : void{
$this->position = $this->getVector3();
$this->delta = $this->getVector3();
$this->onGround = $this->getBool();
$this->tick = $this->getUnsignedVarLong();
}
protected function encodePayload() : void{
$this->putVector3($this->position);
$this->putVector3($this->delta);
$this->putBool($this->onGround);
$this->putUnsignedVarLong($this->tick);
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleCorrectPlayerMovePrediction($this);
}
}

View File

@ -30,6 +30,7 @@ use pocketmine\inventory\ShapedRecipe;
use pocketmine\inventory\ShapelessRecipe;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe;
@ -124,13 +125,12 @@ class CraftingDataPacket extends DataPacket{
break;
case self::ENTRY_FURNACE:
case self::ENTRY_FURNACE_DATA:
$inputId = $this->getVarInt();
$inputData = -1;
if($recipeType === self::ENTRY_FURNACE_DATA){
$inputData = $this->getVarInt();
if($inputData === 0x7fff){
$inputData = -1;
}
$inputIdNet = $this->getVarInt();
if($recipeType === self::ENTRY_FURNACE){
[$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, 0x7fff);
}else{
$inputMetaNet = $this->getVarInt();
[$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, $inputMetaNet);
}
$entry["input"] = ItemFactory::get($inputId, $inputData);
$entry["output"] = $out = $this->getSlot();
@ -150,18 +150,25 @@ class CraftingDataPacket extends DataPacket{
$this->decodedEntries[] = $entry;
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$input = $this->getVarInt();
$inputMeta = $this->getVarInt();
$ingredient = $this->getVarInt();
$ingredientMeta = $this->getVarInt();
$output = $this->getVarInt();
$outputMeta = $this->getVarInt();
$inputIdNet = $this->getVarInt();
$inputMetaNet = $this->getVarInt();
[$input, $inputMeta] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, $inputMetaNet);
$ingredientIdNet = $this->getVarInt();
$ingredientMetaNet = $this->getVarInt();
[$ingredient, $ingredientMeta] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, $ingredientMetaNet);
$outputIdNet = $this->getVarInt();
$outputMetaNet = $this->getVarInt();
[$output, $outputMeta] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, $outputMetaNet);
$this->potionTypeRecipes[] = new PotionTypeRecipe($input, $inputMeta, $ingredient, $ingredientMeta, $output, $outputMeta);
}
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$input = $this->getVarInt();
$ingredient = $this->getVarInt();
$output = $this->getVarInt();
//TODO: we discard inbound ID here, not safe because netID on its own might map to internalID+internalMeta for us
$inputIdNet = $this->getVarInt();
[$input, ] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, 0);
$ingredientIdNet = $this->getVarInt();
[$ingredient, ] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, 0);
$outputIdNet = $this->getVarInt();
[$output, ] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, 0);
$this->potionContainerRecipes[] = new PotionContainerChangeRecipe($input, $ingredient, $output);
}
$this->cleanRecipes = $this->getBool();
@ -230,15 +237,18 @@ class CraftingDataPacket extends DataPacket{
}
private static function writeFurnaceRecipe(FurnaceRecipe $recipe, NetworkBinaryStream $stream) : int{
$stream->putVarInt($recipe->getInput()->getId());
$result = CraftingDataPacket::ENTRY_FURNACE;
if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe
$stream->putVarInt($recipe->getInput()->getDamage());
$result = CraftingDataPacket::ENTRY_FURNACE_DATA;
$input = $recipe->getInput();
if($input->hasAnyDamageValue()){
[$netId, ] = ItemTranslator::getInstance()->toNetworkId($input->getId(), 0);
$netData = 0x7fff;
}else{
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($input->getId(), $input->getDamage());
}
$stream->putVarInt($netId);
$stream->putVarInt($netData);
$stream->putSlot($recipe->getResult());
$stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks)
return $result;
return CraftingDataPacket::ENTRY_FURNACE_DATA;
}
/**

View File

@ -30,7 +30,7 @@ use pocketmine\network\mcpe\NetworkSession;
class EmotePacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{
public const NETWORK_ID = ProtocolInfo::EMOTE_PACKET;
private const FLAG_SERVER = 1 << 0;
public const FLAG_SERVER = 1 << 0;
/** @var int */
private $entityRuntimeId;

View File

@ -0,0 +1,78 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\ItemComponentPacketEntry;
use function count;
class ItemComponentPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::ITEM_COMPONENT_PACKET;
/**
* @var ItemComponentPacketEntry[]
* @phpstan-var list<ItemComponentPacketEntry>
*/
private $entries;
/**
* @param ItemComponentPacketEntry[] $entries
* @phpstan-param list<ItemComponentPacketEntry> $entries
*/
public static function create(array $entries) : self{
$result = new self;
$result->entries = $entries;
return $result;
}
/**
* @return ItemComponentPacketEntry[]
* @phpstan-return list<ItemComponentPacketEntry>
*/
public function getEntries() : array{ return $this->entries; }
protected function decodePayload() : void{
$this->entries = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$name = $this->getString();
$nbt = $this->getNbtCompoundRoot();
$this->entries[] = new ItemComponentPacketEntry($name, $nbt);
}
}
protected function encodePayload() : void{
$this->putUnsignedVarInt(count($this->entries));
foreach($this->entries as $entry){
$this->putString($entry->getName());
$this->put((new NetworkLittleEndianNBTStream())->write($entry->getComponentNbt()));
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleItemComponent($this);
}
}

View File

@ -78,7 +78,7 @@ class LoginPacket extends DataPacket{
}
public function mayHaveUnreadBytes() : bool{
return $this->protocol !== null and $this->protocol !== ProtocolInfo::CURRENT_PROTOCOL;
return $this->protocol !== ProtocolInfo::CURRENT_PROTOCOL;
}
protected function decodePayload(){
@ -92,7 +92,7 @@ class LoginPacket extends DataPacket{
}
$logger = MainLogger::getLogger();
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version " . ($this->protocol ?? "unknown") . "): " . $e->getMessage());
$logger->debug(get_class($e) . " was thrown while decoding connection request in login (protocol version $this->protocol): " . $e->getMessage());
foreach(Utils::printableTrace($e->getTrace()) as $line){
$logger->debug($line);
}

View File

@ -0,0 +1,70 @@
<?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;
#include <rules/DataPacket.h>
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\NetworkSession;
class MotionPredictionHintsPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::MOTION_PREDICTION_HINTS_PACKET;
/** @var int */
private $entityRuntimeId;
/** @var Vector3 */
private $motion;
/** @var bool */
private $onGround;
public static function create(int $entityRuntimeId, Vector3 $motion, bool $onGround) : self{
$result = new self;
$result->entityRuntimeId = $entityRuntimeId;
$result->motion = $motion;
$result->onGround = $onGround;
return $result;
}
public function getEntityRuntimeIdField() : int{ return $this->entityRuntimeId; } //TODO: rename this on PM4 (crap architecture, thanks shoghi)
public function getMotion() : Vector3{ return $this->motion; }
public function isOnGround() : bool{ return $this->onGround; }
protected function decodePayload() : void{
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->motion = $this->getVector3();
$this->onGround = $this->getBool();
}
protected function encodePayload() : void{
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putVector3($this->motion);
$this->putBool($this->onGround);
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleMotionPredictionHints($this);
}
}

View File

@ -44,12 +44,12 @@ class MoveActorDeltaPacket extends DataPacket{
public $entityRuntimeId;
/** @var int */
public $flags;
/** @var int */
public $xDiff = 0;
/** @var int */
public $yDiff = 0;
/** @var int */
public $zDiff = 0;
/** @var float */
public $xPos = 0;
/** @var float */
public $yPos = 0;
/** @var float */
public $zPos = 0;
/** @var float */
public $xRot = 0.0;
/** @var float */
@ -57,9 +57,9 @@ class MoveActorDeltaPacket extends DataPacket{
/** @var float */
public $zRot = 0.0;
private function maybeReadCoord(int $flag) : int{
private function maybeReadCoord(int $flag) : float{
if(($this->flags & $flag) !== 0){
return $this->getVarInt();
return $this->getLFloat();
}
return 0;
}
@ -74,17 +74,17 @@ class MoveActorDeltaPacket extends DataPacket{
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->flags = $this->getLShort();
$this->xDiff = $this->maybeReadCoord(self::FLAG_HAS_X);
$this->yDiff = $this->maybeReadCoord(self::FLAG_HAS_Y);
$this->zDiff = $this->maybeReadCoord(self::FLAG_HAS_Z);
$this->xPos = $this->maybeReadCoord(self::FLAG_HAS_X);
$this->yPos = $this->maybeReadCoord(self::FLAG_HAS_Y);
$this->zPos = $this->maybeReadCoord(self::FLAG_HAS_Z);
$this->xRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_X);
$this->yRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Y);
$this->zRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Z);
}
private function maybeWriteCoord(int $flag, int $val) : void{
private function maybeWriteCoord(int $flag, float $val) : void{
if(($this->flags & $flag) !== 0){
$this->putVarInt($val);
$this->putLFloat($val);
}
}
@ -97,9 +97,9 @@ class MoveActorDeltaPacket extends DataPacket{
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putLShort($this->flags);
$this->maybeWriteCoord(self::FLAG_HAS_X, $this->xDiff);
$this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yDiff);
$this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zDiff);
$this->maybeWriteCoord(self::FLAG_HAS_X, $this->xPos);
$this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yPos);
$this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zPos);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_X, $this->xRot);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Y, $this->yRot);
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Z, $this->zRot);

View File

@ -56,6 +56,8 @@ class MovePlayerPacket extends DataPacket{
public $teleportCause = 0;
/** @var int */
public $teleportItem = 0;
/** @var int */
public $tick = 0;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
@ -70,6 +72,7 @@ class MovePlayerPacket extends DataPacket{
$this->teleportCause = $this->getLInt();
$this->teleportItem = $this->getLInt();
}
$this->tick = $this->getUnsignedVarLong();
}
protected function encodePayload(){
@ -85,6 +88,7 @@ class MovePlayerPacket extends DataPacket{
$this->putLInt($this->teleportCause);
$this->putLInt($this->teleportItem);
}
$this->putUnsignedVarLong($this->tick);
}
public function handle(NetworkSession $session) : bool{

View File

@ -71,7 +71,6 @@ class PacketPool{
static::registerPacket(new BlockPickRequestPacket());
static::registerPacket(new ActorPickRequestPacket());
static::registerPacket(new PlayerActionPacket());
static::registerPacket(new ActorFallPacket());
static::registerPacket(new HurtArmorPacket());
static::registerPacket(new SetActorDataPacket());
static::registerPacket(new SetActorMotionPacket());
@ -166,7 +165,6 @@ class PacketPool{
static::registerPacket(new MapCreateLockedCopyPacket());
static::registerPacket(new StructureTemplateDataRequestPacket());
static::registerPacket(new StructureTemplateDataResponsePacket());
static::registerPacket(new UpdateBlockPropertiesPacket());
static::registerPacket(new ClientCacheBlobStatusPacket());
static::registerPacket(new ClientCacheMissResponsePacket());
static::registerPacket(new EducationSettingsPacket());
@ -189,6 +187,12 @@ class PacketPool{
static::registerPacket(new PositionTrackingDBClientRequestPacket());
static::registerPacket(new DebugInfoPacket());
static::registerPacket(new PacketViolationWarningPacket());
static::registerPacket(new MotionPredictionHintsPacket());
static::registerPacket(new AnimateEntityPacket());
static::registerPacket(new CameraShakePacket());
static::registerPacket(new PlayerFogPacket());
static::registerPacket(new CorrectPlayerMovePredictionPacket());
static::registerPacket(new ItemComponentPacket());
}
/**

View File

@ -43,7 +43,7 @@ class PlayerActionPacket extends DataPacket{
public const ACTION_STOP_SPRINT = 10;
public const ACTION_START_SNEAK = 11;
public const ACTION_STOP_SNEAK = 12;
public const ACTION_DIMENSION_CHANGE_REQUEST = 13; //sent when dying in different dimension
public const ACTION_CREATIVE_PLAYER_DESTROY_BLOCK = 13;
public const ACTION_DIMENSION_CHANGE_ACK = 14; //sent when spawning in a different dimension to tell the server we spawned
public const ACTION_START_GLIDE = 15;
public const ACTION_STOP_GLIDE = 16;

View File

@ -54,13 +54,17 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
private $playMode;
/** @var Vector3|null */
private $vrGazeDirection = null;
/** @var int */
private $tick;
/** @var Vector3 */
private $delta;
/**
* @param int $inputMode @see InputMode
* @param int $playMode @see PlayMode
* @param Vector3|null $vrGazeDirection only used when PlayMode::VR
*/
public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection = null) : self{
public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection, int $tick, Vector3 $delta) : self{
if($playMode === PlayMode::VR and $vrGazeDirection === null){
//yuck, can we get a properly written packet just once? ...
throw new \InvalidArgumentException("Gaze direction must be provided for VR play mode");
@ -78,6 +82,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
if($vrGazeDirection !== null){
$result->vrGazeDirection = $vrGazeDirection->asVector3();
}
$result->tick = $tick;
$result->delta = $delta;
return $result;
}
@ -127,9 +133,13 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
return $this->vrGazeDirection;
}
public function getTick() : int{ return $this->tick; }
public function getDelta() : Vector3{ return $this->delta; }
protected function decodePayload() : void{
$this->yaw = $this->getLFloat();
$this->pitch = $this->getLFloat();
$this->yaw = $this->getLFloat();
$this->position = $this->getVector3();
$this->moveVecX = $this->getLFloat();
$this->moveVecZ = $this->getLFloat();
@ -140,11 +150,13 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
if($this->playMode === PlayMode::VR){
$this->vrGazeDirection = $this->getVector3();
}
$this->tick = $this->getUnsignedVarLong();
$this->delta = $this->getVector3();
}
protected function encodePayload() : void{
$this->putLFloat($this->yaw);
$this->putLFloat($this->pitch);
$this->putLFloat($this->yaw);
$this->putVector3($this->position);
$this->putLFloat($this->moveVecX);
$this->putLFloat($this->moveVecZ);
@ -156,6 +168,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
assert($this->vrGazeDirection !== null);
$this->putVector3($this->vrGazeDirection);
}
$this->putUnsignedVarLong($this->tick);
$this->putVector3($this->delta);
}
public function handle(NetworkSession $handler) : bool{

View File

@ -25,31 +25,49 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\NetworkSession;
use function count;
class UpdateBlockPropertiesPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::UPDATE_BLOCK_PROPERTIES_PACKET;
class PlayerFogPacket extends DataPacket/* implements ClientboundPacket*/{
public const NETWORK_ID = ProtocolInfo::PLAYER_FOG_PACKET;
/** @var string */
private $nbt;
/**
* @var string[]
* @phpstan-var list<string>
*/
private $fogLayers;
public static function create(CompoundTag $data) : self{
/**
* @param string[] $fogLayers
* @phpstan-param list<string> $fogLayers
*/
public static function create(array $fogLayers) : self{
$result = new self;
$result->nbt = (new NetworkLittleEndianNBTStream())->write($data);
$result->fogLayers = $fogLayers;
return $result;
}
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getFogLayers() : array{ return $this->fogLayers; }
protected function decodePayload() : void{
$this->nbt = $this->getRemaining();
$this->fogLayers = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$this->fogLayers[] = $this->getString();
}
}
protected function encodePayload() : void{
$this->put($this->nbt);
$this->putUnsignedVarInt(count($this->fogLayers));
foreach($this->fogLayers as $fogLayer){
$this->putString($fogLayer);
}
}
public function handle(NetworkSession $handler) : bool{
return $handler->handleUpdateBlockProperties($this);
return $handler->handlePlayerFog($this);
}
}

View File

@ -37,11 +37,11 @@ interface ProtocolInfo{
*/
/** Actual Minecraft: PE protocol version */
public const CURRENT_PROTOCOL = 407;
public const CURRENT_PROTOCOL = 419;
/** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */
public const MINECRAFT_VERSION = 'v1.16.0';
public const MINECRAFT_VERSION = 'v1.16.100';
/** Version number sent to clients in ping responses. */
public const MINECRAFT_VERSION_NETWORK = '1.16.0';
public const MINECRAFT_VERSION_NETWORK = '1.16.100';
public const LOGIN_PACKET = 0x01;
public const PLAY_STATUS_PACKET = 0x02;
@ -79,7 +79,7 @@ interface ProtocolInfo{
public const BLOCK_PICK_REQUEST_PACKET = 0x22;
public const ACTOR_PICK_REQUEST_PACKET = 0x23;
public const PLAYER_ACTION_PACKET = 0x24;
public const ACTOR_FALL_PACKET = 0x25;
public const HURT_ARMOR_PACKET = 0x26;
public const SET_ACTOR_DATA_PACKET = 0x27;
public const SET_ACTOR_MOTION_PACKET = 0x28;
@ -176,7 +176,7 @@ interface ProtocolInfo{
public const MAP_CREATE_LOCKED_COPY_PACKET = 0x83;
public const STRUCTURE_TEMPLATE_DATA_REQUEST_PACKET = 0x84;
public const STRUCTURE_TEMPLATE_DATA_RESPONSE_PACKET = 0x85;
public const UPDATE_BLOCK_PROPERTIES_PACKET = 0x86;
public const CLIENT_CACHE_BLOB_STATUS_PACKET = 0x87;
public const CLIENT_CACHE_MISS_RESPONSE_PACKET = 0x88;
public const EDUCATION_SETTINGS_PACKET = 0x89;
@ -199,5 +199,11 @@ interface ProtocolInfo{
public const POSITION_TRACKING_D_B_CLIENT_REQUEST_PACKET = 0x9a;
public const DEBUG_INFO_PACKET = 0x9b;
public const PACKET_VIOLATION_WARNING_PACKET = 0x9c;
public const MOTION_PREDICTION_HINTS_PACKET = 0x9d;
public const ANIMATE_ENTITY_PACKET = 0x9e;
public const CAMERA_SHAKE_PACKET = 0x9f;
public const PLAYER_FOG_PACKET = 0xa0;
public const CORRECT_PLAYER_MOVE_PREDICTION_PACKET = 0xa1;
public const ITEM_COMPONENT_PACKET = 0xa2;
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\resourcepacks\ResourcePack;
use function count;
@ -40,11 +41,12 @@ class ResourcePackStackPacket extends DataPacket{
/** @var ResourcePack[] */
public $resourcePackStack = [];
/** @var bool */
public $isExperimental = false;
/** @var string */
public $baseGameVersion = ProtocolInfo::MINECRAFT_VERSION_NETWORK;
/** @var Experiments */
public $experiments;
protected function decodePayload(){
$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getUnsignedVarInt();
@ -61,8 +63,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->getString();
}
$this->isExperimental = $this->getBool();
$this->baseGameVersion = $this->getString();
$this->experiments = Experiments::read($this);
}
protected function encodePayload(){
@ -82,8 +84,8 @@ class ResourcePackStackPacket extends DataPacket{
$this->putString(""); //TODO: subpack name
}
$this->putBool($this->isExperimental);
$this->putString($this->baseGameVersion);
$this->experiments->write($this);
}
public function handle(NetworkSession $session) : bool{

View File

@ -38,14 +38,19 @@ class SetActorDataPacket extends DataPacket{
*/
public $metadata;
/** @var int */
public $tick = 0;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->metadata = $this->getEntityMetadata();
$this->tick = $this->getUnsignedVarLong();
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putEntityMetadata($this->metadata);
$this->putUnsignedVarLong($this->tick);
}
public function handle(NetworkSession $session) : bool{

View File

@ -27,29 +27,22 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\math\Vector3;
use pocketmine\nbt\NetworkLittleEndianNBTStream;
use pocketmine\nbt\tag\ListTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\NetworkBinaryStream;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\types\BlockPaletteEntry;
use pocketmine\network\mcpe\protocol\types\EducationEditionOffer;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\GameRuleType;
use pocketmine\network\mcpe\protocol\types\GeneratorType;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\network\mcpe\protocol\types\MultiplayerGameVisibility;
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
use function count;
use function file_get_contents;
use function json_decode;
use const pocketmine\RESOURCE_PATH;
class StartGamePacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::START_GAME_PACKET;
/** @var string|null */
private static $blockTableCache = null;
/** @var string|null */
private static $itemTableCache = null;
/** @var int */
public $entityUniqueId;
/** @var int */
@ -116,6 +109,8 @@ class StartGamePacket extends DataPacket{
public $gameRules = [ //TODO: implement this
"naturalregeneration" => [GameRuleType::BOOL, false] //Hack for client side regeneration
];
/** @var Experiments */
public $experiments;
/** @var bool */
public $hasBonusChestEnabled = false;
/** @var bool */
@ -159,8 +154,8 @@ class StartGamePacket extends DataPacket{
public $premiumWorldTemplateId = "";
/** @var bool */
public $isTrial = false;
/** @var bool */
public $isMovementServerAuthoritative = false;
/** @var int */
public $playerMovementType = PlayerMovementType::LEGACY;
/** @var int */
public $currentTick = 0; //only used if isTrial is true
/** @var int */
@ -168,13 +163,17 @@ class StartGamePacket extends DataPacket{
/** @var string */
public $multiplayerCorrelationId = ""; //TODO: this should be filled with a UUID of some sort
/** @var ListTag|null */
public $blockTable = null;
/**
* @var int[]|null string (name) => int16 (legacyID)
* @phpstan-var array<string, int>|null
* @var BlockPaletteEntry[]
* @phpstan-var list<BlockPaletteEntry>
*/
public $itemTable = null;
public $blockPalette = [];
/**
* @var ItemTypeEntry[]
* @phpstan-var list<ItemTypeEntry>
*/
public $itemTable;
/** @var bool */
public $enableNewInventorySystem = false; //TODO
@ -210,6 +209,7 @@ class StartGamePacket extends DataPacket{
$this->commandsEnabled = $this->getBool();
$this->isTexturePacksRequired = $this->getBool();
$this->gameRules = $this->getGameRules();
$this->experiments = Experiments::read($this);
$this->hasBonusChestEnabled = $this->getBool();
$this->hasStartWithMapEnabled = $this->getBool();
$this->defaultPlayerPermission = $this->getVarInt();
@ -235,23 +235,25 @@ class StartGamePacket extends DataPacket{
$this->worldName = $this->getString();
$this->premiumWorldTemplateId = $this->getString();
$this->isTrial = $this->getBool();
$this->isMovementServerAuthoritative = $this->getBool();
$this->playerMovementType = $this->getVarInt();
$this->currentTick = $this->getLLong();
$this->enchantmentSeed = $this->getVarInt();
$blockTable = (new NetworkLittleEndianNBTStream())->read($this->buffer, false, $this->offset, 512);
if(!($blockTable instanceof ListTag)){
throw new \UnexpectedValueException("Wrong block table root NBT tag type");
$this->blockPalette = [];
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
$blockName = $this->getString();
$state = $this->getNbtCompoundRoot();
$this->blockPalette[] = new BlockPaletteEntry($blockName, $state);
}
$this->blockTable = $blockTable;
$this->itemTable = [];
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
$id = $this->getString();
$legacyId = $this->getSignedLShort();
$stringId = $this->getString();
$numericId = $this->getSignedLShort();
$isComponentBased = $this->getBool();
$this->itemTable[$id] = $legacyId;
$this->itemTable[] = new ItemTypeEntry($stringId, $numericId, $isComponentBased);
}
$this->multiplayerCorrelationId = $this->getString();
@ -290,6 +292,7 @@ class StartGamePacket extends DataPacket{
$this->putBool($this->commandsEnabled);
$this->putBool($this->isTexturePacksRequired);
$this->putGameRules($this->gameRules);
$this->experiments->write($this);
$this->putBool($this->hasBonusChestEnabled);
$this->putBool($this->hasStartWithMapEnabled);
$this->putVarInt($this->defaultPlayerPermission);
@ -314,47 +317,28 @@ class StartGamePacket extends DataPacket{
$this->putString($this->worldName);
$this->putString($this->premiumWorldTemplateId);
$this->putBool($this->isTrial);
$this->putBool($this->isMovementServerAuthoritative);
$this->putVarInt($this->playerMovementType);
$this->putLLong($this->currentTick);
$this->putVarInt($this->enchantmentSeed);
if($this->blockTable === null){
if(self::$blockTableCache === null){
//this is a really nasty hack, but it'll do for now
self::$blockTableCache = (new NetworkLittleEndianNBTStream())->write(new ListTag("", RuntimeBlockMapping::getBedrockKnownStates()));
}
$this->put(self::$blockTableCache);
}else{
$this->put((new NetworkLittleEndianNBTStream())->write($this->blockTable));
$this->putUnsignedVarInt(count($this->blockPalette));
$nbtWriter = new NetworkLittleEndianNBTStream();
foreach($this->blockPalette as $entry){
$this->putString($entry->getName());
$this->put($nbtWriter->write($entry->getStates()));
}
if($this->itemTable === null){
if(self::$itemTableCache === null){
self::$itemTableCache = self::serializeItemTable(json_decode(file_get_contents(RESOURCE_PATH . '/vanilla/item_id_map.json'), true));
}
$this->put(self::$itemTableCache);
}else{
$this->put(self::serializeItemTable($this->itemTable));
$this->putUnsignedVarInt(count($this->itemTable));
foreach($this->itemTable as $entry){
$this->putString($entry->getStringId());
$this->putLShort($entry->getNumericId());
$this->putBool($entry->isComponentBased());
}
$this->putString($this->multiplayerCorrelationId);
$this->putBool($this->enableNewInventorySystem);
}
/**
* @param int[] $table
* @phpstan-param array<string, int> $table
*/
private static function serializeItemTable(array $table) : string{
$stream = new NetworkBinaryStream();
$stream->putUnsignedVarInt(count($table));
foreach($table as $name => $legacyId){
$stream->putString($name);
$stream->putLShort($legacyId);
}
return $stream->getBuffer();
}
public function handle(NetworkSession $session) : bool{
return $session->handleStartGame($this);
}

View File

@ -35,15 +35,19 @@ class UpdateAttributesPacket extends DataPacket{
public $entityRuntimeId;
/** @var Attribute[] */
public $entries = [];
/** @var int */
public $tick = 0;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->entries = $this->getAttributeList();
$this->tick = $this->getUnsignedVarLong();
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putAttributeList(...$this->entries);
$this->putUnsignedVarLong($this->tick);
}
public function handle(NetworkSession $session) : bool{

View File

@ -0,0 +1,43 @@
<?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;
use pocketmine\nbt\tag\CompoundTag;
final class BlockPaletteEntry{
/** @var string */
private $name;
/** @var CompoundTag */
private $states;
public function __construct(string $name, CompoundTag $states){
$this->name = $name;
$this->states = $states;
}
public function getName() : string{ return $this->name; }
public function getStates() : CompoundTag{ return $this->states; }
}

View File

@ -0,0 +1,72 @@
<?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;
use pocketmine\network\mcpe\NetworkBinaryStream;
use function count;
final class Experiments{
/**
* @var bool[]
* @phpstan-var array<string, bool>
*/
private $experiments;
/** @var bool */
private $hasPreviouslyUsedExperiments;
/**
* @param bool[] $experiments
* @phpstan-param array<string, bool> $experiments
*/
public function __construct(array $experiments, bool $hasPreviouslyUsedExperiments){
$this->experiments = $experiments;
$this->hasPreviouslyUsedExperiments = $hasPreviouslyUsedExperiments;
}
/** @return bool[] */
public function getExperiments() : array{ return $this->experiments; }
public function hasPreviouslyUsedExperiments() : bool{ return $this->hasPreviouslyUsedExperiments; }
public static function read(NetworkBinaryStream $in) : self{
$experiments = [];
for($i = 0, $len = $in->getLInt(); $i < $len; ++$i){
$experimentName = $in->getString();
$enabled = $in->getBool();
$experiments[$experimentName] = $enabled;
}
$hasPreviouslyUsedExperiments = $in->getBool();
return new self($experiments, $hasPreviouslyUsedExperiments);
}
public function write(NetworkBinaryStream $out) : void{
$out->putLInt(count($this->experiments));
foreach($this->experiments as $experimentName => $enabled){
$out->putString($experimentName);
$out->putBool($enabled);
}
$out->putBool($this->hasPreviouslyUsedExperiments);
}
}

View File

@ -0,0 +1,43 @@
<?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;
use pocketmine\nbt\tag\CompoundTag;
final class ItemComponentPacketEntry{
/** @var string */
private $name;
/** @var CompoundTag */
private $componentNbt;
public function __construct(string $name, CompoundTag $componentNbt){
$this->name = $name;
$this->componentNbt = $componentNbt;
}
public function getName() : string{ return $this->name; }
public function getComponentNbt() : CompoundTag{ return $this->componentNbt; }
}

View File

@ -21,35 +21,26 @@
declare(strict_types=1);
namespace pocketmine\network\mcpe\protocol;
namespace pocketmine\network\mcpe\protocol\types;
#include <rules/DataPacket.h>
use pocketmine\network\mcpe\NetworkSession;
class ActorFallPacket extends DataPacket{
public const NETWORK_ID = ProtocolInfo::ACTOR_FALL_PACKET;
final class ItemTypeEntry{
/** @var string */
private $stringId;
/** @var int */
public $entityRuntimeId;
/** @var float */
public $fallDistance;
private $numericId;
/** @var bool */
public $isInVoid;
private $componentBased;
protected function decodePayload(){
$this->entityRuntimeId = $this->getEntityRuntimeId();
$this->fallDistance = $this->getLFloat();
$this->isInVoid = $this->getBool();
public function __construct(string $stringId, int $numericId, bool $componentBased){
$this->stringId = $stringId;
$this->numericId = $numericId;
$this->componentBased = $componentBased;
}
protected function encodePayload(){
$this->putEntityRuntimeId($this->entityRuntimeId);
$this->putLFloat($this->fallDistance);
$this->putBool($this->isInVoid);
}
public function getStringId() : string{ return $this->stringId; }
public function handle(NetworkSession $session) : bool{
return $session->handleActorFall($this);
}
public function getNumericId() : int{ return $this->numericId; }
public function isComponentBased() : bool{ return $this->componentBased; }
}

View File

@ -0,0 +1,31 @@
<?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;
final class PlayerMovementType{
public const LEGACY = 0; //MovePlayerPacket
public const SERVER_AUTHORITATIVE_V1 = 1; //PlayerAuthInputPacket
public const SERVER_AUTHORITATIVE_V2_REWIND = 2; //PlayerAuthInputPacket + a bunch of junk that solves a nonexisting problem
}

View File

@ -29,17 +29,23 @@ class SkinAnimation{
public const TYPE_BODY_32 = 2;
public const TYPE_BODY_64 = 3;
public const EXPRESSION_LINEAR = 0; //???
public const EXPRESSION_BLINKING = 1;
/** @var SkinImage */
private $image;
/** @var int */
private $type;
/** @var float */
private $frames;
/** @var int */
private $expressionType;
public function __construct(SkinImage $image, int $type, float $frames){
public function __construct(SkinImage $image, int $type, float $frames, int $expressionType){
$this->image = $image;
$this->type = $type;
$this->frames = $frames;
$this->expressionType = $expressionType;
}
/**
@ -62,4 +68,6 @@ class SkinAnimation{
public function getFrames() : float{
return $this->frames;
}
public function getExpressionType() : int{ return $this->expressionType; }
}

View File

@ -28,8 +28,13 @@ use function count;
final class ItemStackResponse{
/** @var bool */
private $ok;
public const RESULT_OK = 0;
public const RESULT_ERROR = 1;
//TODO: there are a ton more possible result types but we don't need them yet and they are wayyyyyy too many for me
//to waste my time on right now...
/** @var int */
private $result;
/** @var int */
private $requestId;
/** @var ItemStackResponseContainerInfo[] */
@ -38,13 +43,13 @@ final class ItemStackResponse{
/**
* @param ItemStackResponseContainerInfo[] $containerInfos
*/
public function __construct(bool $ok, int $requestId, array $containerInfos){
$this->ok = $ok;
public function __construct(int $result, int $requestId, array $containerInfos){
$this->result = $result;
$this->requestId = $requestId;
$this->containerInfos = $containerInfos;
}
public function isOk() : bool{ return $this->ok; }
public function getResult() : int{ return $this->result; }
public function getRequestId() : int{ return $this->requestId; }
@ -52,17 +57,17 @@ final class ItemStackResponse{
public function getContainerInfos() : array{ return $this->containerInfos; }
public static function read(NetworkBinaryStream $in) : self{
$ok = $in->getBool();
$result = $in->getByte();
$requestId = $in->readGenericTypeNetworkId();
$containerInfos = [];
for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){
$containerInfos[] = ItemStackResponseContainerInfo::read($in);
}
return new self($ok, $requestId, $containerInfos);
return new self($result, $requestId, $containerInfos);
}
public function write(NetworkBinaryStream $out) : void{
$out->putBool($this->ok);
$out->putByte($this->result);
$out->writeGenericTypeNetworkId($this->requestId);
$out->putUnsignedVarInt(count($this->containerInfos));
foreach($this->containerInfos as $containerInfo){

View File

@ -58,7 +58,7 @@ class PluginDescription{
private $compatibleOperatingSystems = [];
/**
* @var string[][]
* @phpstan-var array<string, list<mixed>>
* @phpstan-var array<string, list<string>>
*/
private $extensions = [];
/** @var string[] */
@ -104,13 +104,13 @@ class PluginDescription{
$this->name = $plugin["name"];
if(preg_match('/^[A-Za-z0-9 _.-]+$/', $this->name) === 0){
throw new PluginException("Invalid PluginDescription name");
throw new PluginException("Invalid Plugin name");
}
$this->name = str_replace(" ", "_", $this->name);
$this->version = (string) $plugin["version"];
$this->main = $plugin["main"];
if(stripos($this->main, "pocketmine\\") === 0){
throw new PluginException("Invalid PluginDescription main, cannot start within the PocketMine namespace");
throw new PluginException("Invalid Plugin main, cannot start within the PocketMine namespace");
}
$this->api = array_map("\strval", (array) ($plugin["api"] ?? []));
@ -132,7 +132,7 @@ class PluginDescription{
$k = $v;
$v = "*";
}
$this->extensions[$k] = is_array($v) ? $v : [$v];
$this->extensions[$k] = array_map('strval', is_array($v) ? $v : [$v]);
}
}
@ -149,7 +149,7 @@ class PluginDescription{
if(isset($plugin["load"])){
$order = mb_strtoupper($plugin["load"]);
if(!defined(PluginLoadOrder::class . "::" . $order)){
throw new PluginException("Invalid PluginDescription load");
throw new PluginException("Invalid Plugin load");
}else{
$this->order = constant(PluginLoadOrder::class . "::" . $order);
}

View File

@ -84,7 +84,7 @@ network:
#Set to 0 to compress everything, -1 to disable.
batch-threshold: 256
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
compression-level: 7
compression-level: 6
#Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread
async-compression: false
#Experimental, only for Windows. Tries to use UPnP to automatically port forward

View File

@ -329,6 +329,16 @@ class AsyncPool{
$this->collectWorkers();
}
/**
* Returns an array of worker ID => task queue size
*
* @return int[]
* @phpstan-return array<int, int>
*/
public function getTaskQueueSizes() : array{
return $this->workerUsage;
}
public function shutdownUnusedWorkers() : int{
$ret = 0;
$time = time();

View File

@ -25,11 +25,13 @@ namespace pocketmine\scheduler;
use function gc_collect_cycles;
use function gc_enable;
use function gc_mem_caches;
class GarbageCollectionTask extends AsyncTask{
public function onRun(){
gc_enable();
gc_collect_cycles();
gc_mem_caches();
}
}

View File

@ -134,7 +134,7 @@ class SendUsageTask extends AsyncTask{
$data["players"] = [
"count" => count($players),
"limit" => $server->getMaxPlayers(),
"currentList" => $players,
"currentList" => array_values($players),
"historyList" => array_values($playerList)
];

View File

@ -55,6 +55,9 @@ class TaskHandler{
private $ownerName;
public function __construct(Task $task, int $taskId, int $delay = -1, int $period = -1, ?string $ownerName = null){
if($task->getHandler() !== null){
throw new \InvalidArgumentException("Cannot assign multiple handlers to the same task");
}
$this->task = $task;
$this->taskId = $taskId;
$this->delay = $delay;

View File

@ -39,7 +39,6 @@ use pocketmine\Player;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
use function current;
use function get_class;
use function in_array;
use function is_a;
@ -74,8 +73,8 @@ abstract class Tile extends Position{
*/
private static $knownTiles = [];
/**
* @var string[][]
* @phpstan-var array<class-string<Tile>, list<string>>
* @var string[]
* @phpstan-var array<class-string<Tile>, string>
*/
private static $saveNames = [];
@ -138,7 +137,7 @@ abstract class Tile extends Position{
self::$knownTiles[$name] = $className;
}
self::$saveNames[$className] = $saveNames;
self::$saveNames[$className] = reset($saveNames);
return true;
}
@ -154,8 +153,7 @@ abstract class Tile extends Position{
throw new \InvalidStateException("Tile is not registered");
}
reset(self::$saveNames[static::class]);
return current(self::$saveNames[static::class]);
return self::$saveNames[static::class];
}
public function __construct(Level $level, CompoundTag $nbt){

View File

@ -59,6 +59,8 @@ abstract class Timings{
/** @var TimingsHandler */
public static $serverCommandTimer;
/** @var TimingsHandler */
public static $worldLoadTimer;
/** @var TimingsHandler */
public static $worldSaveTimer;
/** @var TimingsHandler */
public static $populationTimer;
@ -126,6 +128,7 @@ abstract class Timings{
self::$connectionTimer = new TimingsHandler("Connection Handler");
self::$schedulerTimer = new TimingsHandler("Scheduler");
self::$serverCommandTimer = new TimingsHandler("Server Command");
self::$worldLoadTimer = new TimingsHandler("World Load");
self::$worldSaveTimer = new TimingsHandler("World Save");
self::$populationTimer = new TimingsHandler("World Population");
self::$generationCallbackTimer = new TimingsHandler("World Generation Callback");
@ -149,7 +152,7 @@ abstract class Timings{
}
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
$name = "Task: " . ($task->getOwnerName() ?? "Unknown") . " Runnable: " . $task->getTaskName();
$name = "Task: " . $task->getOwnerName() . " Runnable: " . $task->getTaskName();
if($period > 0){
$name .= "(interval:" . $period . ")";

View File

@ -201,12 +201,12 @@ class MainLogger extends \AttachableThreadedLogger{
$this->synchronized(function() use ($e, $trace) : void{
$this->critical(self::printExceptionMessage($e));
foreach(Utils::printableTrace($trace) as $line){
$this->debug($line, true);
$this->critical($line);
}
for($prev = $e->getPrevious(); $prev !== null; $prev = $prev->getPrevious()){
$this->debug("Previous: " . self::printExceptionMessage($prev), true);
$this->critical("Previous: " . self::printExceptionMessage($prev));
foreach(Utils::printableTrace($prev->getTrace()) as $line){
$this->debug(" " . $line, true);
$this->critical(" " . $line);
}
}
});

View File

@ -0,0 +1,48 @@
<?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\utils;
trait SingletonTrait{
/** @var self|null */
private static $instance = null;
private static function make() : self{
return new self;
}
public static function getInstance() : self{
if(self::$instance === null){
self::$instance = self::make();
}
return self::$instance;
}
public static function setInstance(self $instance) : void{
self::$instance = $instance;
}
public static function reset() : void{
self::$instance = null;
}
}

View File

@ -141,8 +141,8 @@ abstract class TextFormat{
if(!is_array($string)){
$string = self::tokenize($string);
}
$newString = [];
$pointer =& $newString;
$newString = new TextFormatJsonObject();
$pointer = $newString;
$color = "white";
$bold = false;
$italic = false;
@ -152,165 +152,164 @@ abstract class TextFormat{
$index = 0;
foreach($string as $token){
if(isset($pointer["text"])){
if(!isset($newString["extra"])){
$newString["extra"] = [];
if($pointer->text !== null){
if($newString->extra === null){
$newString->extra = [];
}
$newString["extra"][$index] = [];
$pointer =& $newString["extra"][$index];
$newString->extra[$index] = $pointer = new TextFormatJsonObject();
if($color !== "white"){
$pointer["color"] = $color;
$pointer->color = $color;
}
if($bold){
$pointer["bold"] = true;
$pointer->bold = true;
}
if($italic){
$pointer["italic"] = true;
$pointer->italic = true;
}
if($underlined){
$pointer["underlined"] = true;
$pointer->underlined = true;
}
if($strikethrough){
$pointer["strikethrough"] = true;
$pointer->strikethrough = true;
}
if($obfuscated){
$pointer["obfuscated"] = true;
$pointer->obfuscated = true;
}
++$index;
}
switch($token){
case TextFormat::BOLD:
if(!$bold){
$pointer["bold"] = true;
$pointer->bold = true;
$bold = true;
}
break;
case TextFormat::OBFUSCATED:
if(!$obfuscated){
$pointer["obfuscated"] = true;
$pointer->obfuscated = true;
$obfuscated = true;
}
break;
case TextFormat::ITALIC:
if(!$italic){
$pointer["italic"] = true;
$pointer->italic = true;
$italic = true;
}
break;
case TextFormat::UNDERLINE:
if(!$underlined){
$pointer["underlined"] = true;
$pointer->underlined = true;
$underlined = true;
}
break;
case TextFormat::STRIKETHROUGH:
if(!$strikethrough){
$pointer["strikethrough"] = true;
$pointer->strikethrough = true;
$strikethrough = true;
}
break;
case TextFormat::RESET:
if($color !== "white"){
$pointer["color"] = "white";
$pointer->color = "white";
$color = "white";
}
if($bold){
$pointer["bold"] = false;
$pointer->bold = false;
$bold = false;
}
if($italic){
$pointer["italic"] = false;
$pointer->italic = false;
$italic = false;
}
if($underlined){
$pointer["underlined"] = false;
$pointer->underlined = false;
$underlined = false;
}
if($strikethrough){
$pointer["strikethrough"] = false;
$pointer->strikethrough = false;
$strikethrough = false;
}
if($obfuscated){
$pointer["obfuscated"] = false;
$pointer->obfuscated = false;
$obfuscated = false;
}
break;
//Colors
case TextFormat::BLACK:
$pointer["color"] = "black";
$pointer->color = "black";
$color = "black";
break;
case TextFormat::DARK_BLUE:
$pointer["color"] = "dark_blue";
$pointer->color = "dark_blue";
$color = "dark_blue";
break;
case TextFormat::DARK_GREEN:
$pointer["color"] = "dark_green";
$pointer->color = "dark_green";
$color = "dark_green";
break;
case TextFormat::DARK_AQUA:
$pointer["color"] = "dark_aqua";
$pointer->color = "dark_aqua";
$color = "dark_aqua";
break;
case TextFormat::DARK_RED:
$pointer["color"] = "dark_red";
$pointer->color = "dark_red";
$color = "dark_red";
break;
case TextFormat::DARK_PURPLE:
$pointer["color"] = "dark_purple";
$pointer->color = "dark_purple";
$color = "dark_purple";
break;
case TextFormat::GOLD:
$pointer["color"] = "gold";
$pointer->color = "gold";
$color = "gold";
break;
case TextFormat::GRAY:
$pointer["color"] = "gray";
$pointer->color = "gray";
$color = "gray";
break;
case TextFormat::DARK_GRAY:
$pointer["color"] = "dark_gray";
$pointer->color = "dark_gray";
$color = "dark_gray";
break;
case TextFormat::BLUE:
$pointer["color"] = "blue";
$pointer->color = "blue";
$color = "blue";
break;
case TextFormat::GREEN:
$pointer["color"] = "green";
$pointer->color = "green";
$color = "green";
break;
case TextFormat::AQUA:
$pointer["color"] = "aqua";
$pointer->color = "aqua";
$color = "aqua";
break;
case TextFormat::RED:
$pointer["color"] = "red";
$pointer->color = "red";
$color = "red";
break;
case TextFormat::LIGHT_PURPLE:
$pointer["color"] = "light_purple";
$pointer->color = "light_purple";
$color = "light_purple";
break;
case TextFormat::YELLOW:
$pointer["color"] = "yellow";
$pointer->color = "yellow";
$color = "yellow";
break;
case TextFormat::WHITE:
$pointer["color"] = "white";
$pointer->color = "white";
$color = "white";
break;
default:
$pointer["text"] = $token;
$pointer->text = $token;
break;
}
}
if(isset($newString["extra"])){
foreach($newString["extra"] as $k => $d){
if(!isset($d["text"])){
unset($newString["extra"][$k]);
if($newString->extra !== null){
foreach($newString->extra as $k => $d){
if($d->text === null){
unset($newString->extra[$k]);
}
}
}

View File

@ -0,0 +1,60 @@
<?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\utils;
/**
* @internal
* @see TextFormat::toJSON()
*/
final class TextFormatJsonObject implements \JsonSerializable{
/** @var string|null */
public $text = null;
/** @var string|null */
public $color = null;
/** @var bool|null */
public $bold = null;
/** @var bool|null */
public $italic = null;
/** @var bool|null */
public $underlined = null;
/** @var bool|null */
public $strikethrough = null;
/** @var bool|null */
public $obfuscated = null;
/**
* @var TextFormatJsonObject[]|null
* @phpstan-var array<int, TextFormatJsonObject>|null
*/
public $extra = null;
public function jsonSerialize(){
$result = (array) $this;
foreach($result as $k => $v){
if($v === null){
unset($result[$k]);
}
}
return $result;
}
}

View File

@ -88,7 +88,7 @@ abstract class Timezone{
break;
}
if($response = Internet::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
if(($response = Internet::getURL("http://ip-api.com/json")) !== false //If system timezone detection fails or timezone is an invalid value.
and $ip_geolocation_data = json_decode($response, true)
and $ip_geolocation_data['status'] !== 'fail'
and date_default_timezone_set($ip_geolocation_data['timezone'])

Some files were not shown because too many files have changed in this diff Show More