Compare commits

...

229 Commits

Author SHA1 Message Date
66a4c4c88b Release 4.18.0-ALPHA2 2023-03-21 00:26:19 +00:00
1a9322c00a ItemStackRequestExecutor: added some missing @throws 2023-03-21 00:23:31 +00:00
c8d9477da1 ItemStackRequestExecutor: make non-final, and make some stuff protected
this allows for plugin extension, for example to implement anvils.
2023-03-21 00:22:21 +00:00
08e8ef275f remove comment 2023-03-21 00:17:24 +00:00
e57fbff28c ItemStackRequestExecutor: added a sanity check for recipe repetitions 2023-03-21 00:16:03 +00:00
f90315c4a2 ItemStackRequestExecutor: harden against invalid item counts
these cases should all be impossible, but that's assuming that the core code doesn't start using them for a different purpose in the future.
2023-03-21 00:13:21 +00:00
955f7944bb ItemStackRequestExecutor: fixed another possible crash condition 2023-03-21 00:06:33 +00:00
ccd288d7fa Avoid repeated calls to getItemInHand() in drop item handler 2023-03-21 00:04:29 +00:00
097632902a InGamePacketHandler: fixed crash condition in drop item handler 2023-03-21 00:02:32 +00:00
e7771d76f2 Cover buffered inventory sync in timings 2023-03-20 23:29:02 +00:00
ecc830a689 InventoryManager: avoid calling TypeConverter::getInstance() in a loop 2023-03-20 23:24:52 +00:00
ee72e80fbb ItemStackResponseBuilder: removed incorrect code
the client expects that all itemstacks must be acked by ItemStackResponse, regardless of whether the server changed them to some other item.
We'll overwrite the item to the correct thing at the end of the tick anyway.
2023-03-20 23:21:24 +00:00
63310cf764 Do not cache ItemStacks for every item
this is very memory inefficient, and only provides a performance advantage in cold code anyway.
2023-03-20 23:18:43 +00:00
1992d3b6db InventoryManager: avoid useless work in trackItemStack()
this attempts to accommodate slots being set to themselves, which is a rare enough occurrence (only plugins will cause it) that it doesn't make sense to penalize every inventory update this way.
attempting to avoid changing the itemstackID in this way is detrimental to performance, and it doesn't actually matter if we set a new itemstackID anyway.
2023-03-20 23:08:17 +00:00
035a0a4e9d InventoryManager: specialize trackItemStack() to avoid useless lookups 2023-03-20 22:57:58 +00:00
23ea721164 Reduce packets-per-batch limit to 100
this should be well in excess of requirements with the ItemStackRequest system in use.
2023-03-20 22:15:02 +00:00
8408da8534 Merge branch 'item-stack-request' into minor-next 2023-03-20 22:05:50 +00:00
c9601ae67d Fixed crash when opening crafting table and other 'UI' inventories 2023-03-20 22:00:38 +00:00
758b5ee500 InventoryManager: fixed armor slots hack
the correct condition for this should be an unsynced armor slot changed during a transaction, but conveying this information to syncSlot() is a bit of a hassle, so this will do for now.
2023-03-20 21:27:56 +00:00
ca6d51498f Buffer slot and content syncing until the end of the tick
we may receive multiple requests in one tick (e.g. crafting in a batch)
2023-03-20 19:16:00 +00:00
e8085e22a0 Fixed crash when opening main inventory
the InventoryManagerEntry was getting overwritten, since we don't expect to open the same inventory with two different window IDs.
2023-03-20 18:40:18 +00:00
a83fc85f1e InventoryManagerEntry: fixed missing default 2023-03-20 17:32:44 +00:00
3d70a169e1 Reduce chaos in InventoryManager
the information in these arrays is usually needed all at the same time, so it doesn't make sense to force multiple array lookups for it.

in addition, this (obviously) cleans up the code quite a lot.
2023-03-20 17:31:54 +00:00
6ccb8f7373 git 2023-03-20 16:57:38 +00:00
59bae9b077 Give InventoryManager internals clearer names
and stop mixing 'window' and 'inventory' terminology...
2023-03-20 16:53:57 +00:00
2751e1ec02 replacing new Vector3(0, 0, 0) with Vector3::zero() (#5640) 2023-03-20 12:54:28 +00:00
c91168db66 ... 2023-03-20 01:35:15 +00:00
4e55433ed8 Fixed request rejecting 2023-03-20 01:35:03 +00:00
eece6c4433 InGamePacketHandler: remove dead code 2023-03-20 01:28:18 +00:00
67b7b60d18 .............. 2023-03-20 01:19:07 +00:00
804feedb67 Added some dumb limits 2023-03-20 00:54:33 +00:00
d57aca1367 CS 2023-03-20 00:53:00 +00:00
7b0816e42f Properly handle transaction building errors instead of kicking the player 2023-03-20 00:52:26 +00:00
4864444440 Added CraftingManager::getCraftingRecipeFromIndex() 2023-03-19 22:14:23 +00:00
f696a5881b Merge remote-tracking branch 'origin/stable' into minor-next 2023-03-19 16:23:09 +00:00
419962d3a2 Added timer for player-specific movement code
players use an entirely different pathway for movement processing, which could be costly.
2023-03-19 16:12:47 +00:00
054c06fab9 Add specialized entityBaseTick timer for item entities
since item merging is a potential hotspot, we want to know if this code section is a performance problem.
Current timers only tell us whether overall ticking of a particular entity is slow, but that includes movement and therefore isn't particularly helpful.
2023-03-19 15:59:06 +00:00
7bc5d8c824 Rename more timers 2023-03-19 15:57:36 +00:00
607bdfa42f Timings: added new timers for entity move collision checks and projectile move ray tracing
projectiles get their own distinct sub-timer, since the logic is completely different from regular entities.
2023-03-19 15:49:35 +00:00
eec53f9ae0 Timings: clean up timer names 2023-03-19 15:39:44 +00:00
3d56bd267c Timings: fixup network timer inheritance 2023-03-18 23:13:25 +00:00
9a969e21c7 ÂNetworkSession: ensure onResolve handler for CompressBatchPromise is covered by network send timings 2023-03-18 22:49:52 +00:00
195bc3b623 NetworkSession: prevent dev client asserts from missing ability flags 2023-03-18 21:53:17 +00:00
2177d8d352 Push Docker image tags to ghcr.io 2023-03-17 16:32:28 +00:00
471625e697 readme: remove docker hub shield
sadly there isn't any ghcr replacement right now.

[ci skip]
2023-03-17 16:24:13 +00:00
2135776c19 readme: goodbye docker hub, won't miss you
[ci skip]
2023-03-17 16:21:57 +00:00
765aef0810 4.18.0-ALPHA2 is next 2023-03-16 21:45:21 +00:00
bd21feffc4 Release 4.18.0-ALPHA1 2023-03-16 21:45:18 +00:00
5b324f695c Merge branch 'stable' into minor-next 2023-03-16 15:04:19 +00:00
9caed10488 update-updater-api: use github.repository_owner to make fork testing of this workflow less obnoxious 2023-03-16 15:03:14 +00:00
83945ff0a0 Do not update release channels if the new build has a lower version ID
this prevents stuff like 5.0.0 beta versions getting overwritten by 4.x beta versions.
2023-03-16 15:02:42 +00:00
ef45180b80 Rename DataPacketPreReceiveEvent -> DataPacketDecodeEvent
thank you @IvanCraft623 for the suggestion
2023-03-16 13:40:37 +00:00
941fd03998 Remove useless code 2023-03-15 22:58:10 +00:00
1af8da3c1f Merge branch 'minor-next' of github.com:pmmp/PocketMine-MP into minor-next 2023-03-15 22:54:05 +00:00
a5985dcf7d Merge branch 'stable' into minor-next 2023-03-15 22:53:44 +00:00
183d1f4038 Implement DataPacketPreReceiveEvent (#5559)
closes #5554

This is called just before the packet is decoded, allowing the event to be used to drop packets from clients without wasting CPU time decoding them. This can be particularly useful for mitigating denial-of-service attacks.
2023-03-15 22:47:19 +00:00
08ee825d91 StandardPacketBroadcaster: Include varint length prefix in length calculation
varints encode 7 bits per byte, so a log with base 128 will tell us how many bytes are required to encode the length of the packet.
2023-03-15 22:41:19 +00:00
337a254768 Use NetworkBroadcastUtils for broadcasting packets
this eradicates all but 4 usages of Server in Entity, which is extremely cool.
2023-03-15 22:28:51 +00:00
a31e3331fd Move Server::broadcastPackets() to NetworkBroadcastUtils::broadcastPackets()
this has no business being in Server, and it also doesn't need to be an instance method, since it never uses $this.
2023-03-15 22:25:23 +00:00
acebbeed16 Added version channels for update.pmmp.io 2023-03-15 20:59:36 +00:00
e0fdbe6eb1 make-release: don't automatically push
this is rather obnoxious when attempting to push test releases to a
fork.
2023-03-15 20:47:01 +00:00
cc8660629b First look at shared EntityEventBroadcaster,
this improves performance in PvP servers and other areas where lots of players or entities exist in one space.

fixes #5622
2023-03-15 18:22:56 +00:00
e7e19abe85 IPv4 and IPv6 RakLibInterface instances now both use the same broadcaster and context
fixes #5625
2023-03-15 17:17:56 +00:00
5f9e0081fd Fixed mushroom block silk-touch drops and block picking behaviour
fixes #5284
2023-03-15 16:36:35 +00:00
b266f45152 Bump build/php from 71b9f9d to a464454 (#5637)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `71b9f9d` to `a464454`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](71b9f9d2d7...a464454d1e)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 13:44:20 +00:00
34ced382db Eliminate final remaining usage of TypeConverter::netItemStackToCore()
instead, we can verify that the held items match by comparing the received ItemStack with the one cached in InventoryManager, which is more cost effective and closes off internal item deserializers to external attacks.
2023-03-14 22:56:11 +00:00
a573a279fa Merge branch 'minor-next' into item-stack-request 2023-03-14 22:25:49 +00:00
14f141fab2 NetworkSession: Stop counting DataPacketReceiveEvent in handler timings
we want it to be included in receive timings, but not handler timings. Handler timings should reflect the time spent in the actual session PacketHandler, not in the event.
2023-03-14 19:00:15 +00:00
daff955bc4 Merge remote-tracking branch 'origin/stable' into minor-next 2023-03-14 18:42:14 +00:00
0022d82779 Merge commit 'd376399b7f332384532a82eaf69b9b02dad5bd0c' into minor-next 2023-03-14 18:39:03 +00:00
7cad9be0d2 Bump build/php from b2207cf to 71b9f9d (#5634)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `b2207cf` to `71b9f9d`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](b2207cf70d...71b9f9d2d7)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-14 18:32:10 +00:00
2f862a552a actions: Replace deprecated ::set-output commands 2023-03-14 18:31:25 +00:00
590f6dad08 4.17.1 is next 2023-03-14 18:14:40 +00:00
9564c81582 Release 4.17.0 2023-03-14 18:14:40 +00:00
3de7a8c27f Updated for 1.19.70 2023-03-14 18:08:10 +00:00
d376399b7f Update composer dependencies
bedrock-item-upgrade-schema and bedrock-block-upgrade-schema are now minor-version-locked, to prevent introducing new upgrade schemas not intended for the currently in-use version
previously I'd intended to do this using max schema IDs, but this has proven to be error-prone, so it makes more sense to lock them in using package version constraints instead.
2023-03-14 17:29:25 +00:00
e2071e59c8 actions: update PHP versions 2023-03-13 17:44:10 +00:00
8e280ebb8b RuntimeBlockMapping: avoid unnecessary PacketSerializer usage 2023-03-11 22:16:24 +00:00
fa7c38276c Fixing gigantic clusterfuck with protocol contexts and broadcasting
fixes #5623
2023-03-11 21:54:14 +00:00
b13e97de3d Timings: fixed receivePacket timer showing 2x the actual number of received packets 2023-03-11 19:13:10 +00:00
328b87fc18 Bump phpstan/phpstan from 1.10.4 to 1.10.6 (#5617)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.4 to 1.10.6.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.4...1.10.6)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 11:49:34 +00:00
acaa1a9ce1 contributing: update table to reflect branch name changes
these branches were renamed to make them more auto-complete-friendly.
2023-03-07 17:28:31 +00:00
3aec0fa3df 4.16.1 is next 2023-03-07 16:19:51 +00:00
fa131dab12 Release 4.16.0 2023-03-07 16:19:50 +00:00
bb4a82b1e7 Merge branch 'next-minor' into stable 2023-03-07 16:15:57 +00:00
93d844a281 build/make-release: improve support for non-stable release channels 2023-03-07 16:12:27 +00:00
616844696e 4.15.4 is next 2023-03-07 15:33:03 +00:00
71e3e36522 Release 4.15.3 2023-03-07 15:33:03 +00:00
a1b42d419f Merge branch 'stable' into next-minor 2023-03-07 15:24:24 +00:00
ef942a627f actions: drop concurrency group
this is causing builds to get randomly cancelled when multiple branches are pushed at once.
2023-03-07 15:23:12 +00:00
fd8c276bd2 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-03-07 15:15:21 +00:00
9783380d1a Merge branch 'stable' into next-minor 2023-03-07 15:13:39 +00:00
a784d93bfd Update composer dependencies 2023-03-07 15:13:16 +00:00
a05e8b366f Bump phpstan/phpstan from 1.10.3 to 1.10.4 (#5610)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.3 to 1.10.4.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.3...1.10.4)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-07 14:40:08 +00:00
87a2e0460c Ping Discord news subscribers on new releases 2023-03-04 17:09:32 +00:00
4073c3fb39 Update composer dependencies 2023-03-04 16:56:08 +00:00
e227e6d8bf Merge branch 'stable' into next-minor 2023-03-04 16:55:56 +00:00
3aa40829ae Update composer dependencies 2023-03-04 16:55:31 +00:00
035d4b7263 MemoryManager: stringify floats, fixes #5598 2023-03-04 16:47:58 +00:00
3db1492c18 Fix CS again 2023-03-04 16:43:29 +00:00
a523189149 Added separate timings for broadcast and session buffer compression 2023-03-04 16:41:41 +00:00
f8893efb94 Don't bother with global batch compression if there is only 1 recipient
this allows the session to achieve better ratios, and also reduces worker pool spam.
2023-03-04 16:34:00 +00:00
70f1ee3e97 draft-release: set prerelease flag properly 2023-03-04 16:29:26 +00:00
eb2f0ed3d0 4.16.0-BETA3 is next 2023-03-04 16:19:34 +00:00
14e7d3e143 Release 4.16.0-BETA2 2023-03-04 16:19:34 +00:00
6d636fc2c7 4.16.0-BETA2 is next 2023-03-04 16:18:04 +00:00
a39f61a33d Release 4.16.0-BETA1 2023-03-04 16:18:01 +00:00
aaec21f544 Compressor: Use minCompressionThreshold exclusively
closes #5589
2023-03-04 15:07:50 +00:00
0edc5f8113 Bump phpunit/phpunit from 9.6.3 to 9.6.4 (#5597)
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.3 to 9.6.4.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/main/ChangeLog-9.6.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.3...9.6.4)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-03 14:06:18 +00:00
a382f0fd92 Bump phpstan/phpstan-phpunit from 1.3.8 to 1.3.10 (#5602)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.3.8 to 1.3.10.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.3.8...1.3.10)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-03 14:05:59 +00:00
0fcd2e7894 Merge branch 'stable' into next-minor 2023-02-28 19:23:25 +00:00
369e0855a7 Update composer dependencies 2023-02-28 19:17:46 +00:00
a6cf39b94e Update composer dependencies 2023-02-25 20:39:01 +00:00
17afd38274 Bump phpstan/phpstan from 1.10.1 to 1.10.3 (#5593)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.1 to 1.10.3.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.1...1.10.3)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-25 20:36:33 +00:00
8f024cb382 Bump docker/build-push-action from 3.3.0 to 4.0.0 (#5545)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.3.0 to 4.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.3.0...v4.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-25 20:04:34 +00:00
e7209679fb ... 2023-02-24 22:23:00 +00:00
da054736b1 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-02-24 22:19:38 +00:00
d92173cded 4.15.3 is next 2023-02-24 22:18:34 +00:00
308cdb6863 Release 4.15.2 2023-02-24 22:18:34 +00:00
ae50b952f1 Accept 1.19.63 (same protocol, different protocol version) 2023-02-24 22:15:58 +00:00
f44946cb49 ... 2023-02-23 22:00:24 +00:00
f704bfb63a Use BedrockData 2.0.0 2023-02-23 21:52:17 +00:00
9acb4d64db Added generated constants for available BedrockData files
this makes it easier to detect unused files, detect removed files, and also avoid typos in usages.
2023-02-23 21:45:12 +00:00
8234360c8d Avoid creating batch buffers just to determine whether a batch should be globally compressed
Instead, sum together the lengths of encoded packet buffers and use that to decide whether to build the buffer or not.
2023-02-22 22:43:10 +00:00
6a64486f55 StandardPacketBroadcaster: Improve performance when broadcasting small packets
In refactors during PM4, I stripped out packet buffer caching, as it was problematic when events alter packets in undetectable ways.
However, I never cleaned this part of the code up properly after enabling DataPacketSendEvent to include multiple packets and multiple targets, so we were still individually encoding the packet(s) for every single session if the sum total of the sizes was below 256 bytes.

This change encodes packets once in the StandardPacketBroadcaster and retains their buffers to post to the session's send buffer directly if the resulting batch is below compression threshold.
This code is still not optimal (see ##5589), but fixing this brings broadcasting performance back to PM3 levels, without any of PM3's problems.
2023-02-22 21:52:12 +00:00
6ec778d0af Bump phpstan/phpstan from 1.9.18 to 1.10.1 (#5588)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.18 to 1.10.1.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.18...1.10.1)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-22 13:47:54 +00:00
75bb4f8da6 Merge branch 'stable' into next-minor 2023-02-21 18:32:58 +00:00
efdd7a186d World: fixed population timer sometimes not being stopped 2023-02-21 18:31:33 +00:00
c4ecb3d128 Merge branch 'stable' into next-minor 2023-02-21 15:37:06 +00:00
b574d49d36 4.15.2 is next 2023-02-21 15:23:25 +00:00
47e9ecd257 Release 4.15.1 2023-02-21 15:23:25 +00:00
799739fe86 Updated build/php submodule to pmmp/PHP-Binaries@b2207cf70d 2023-02-21 15:22:38 +00:00
59a04c971f Getter and setter for gravity (#5584)
closes #5525
2023-02-21 15:01:22 +00:00
168af31fd7 Bump phpstan/phpstan from 1.9.17 to 1.9.18 (#5585)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.17 to 1.9.18.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.17...1.9.18)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 18:39:29 +00:00
871bd169a8 Timings: remove unnecessary code 2023-02-19 17:58:12 +00:00
4dbcd714bd NetworkSession: fixed some segments of recv/send logic not being covered by their respective network timings 2023-02-19 17:21:10 +00:00
d5e92b4ae6 ... 2023-02-19 16:53:04 +00:00
2a3288c4f9 Avoid useless throwaway PacketBatch objects 2023-02-19 16:50:03 +00:00
9cdb641936 Added encode packet timings
these changes required some new APIs in BedrockProtocol.
2023-02-19 16:47:20 +00:00
b56b35b10d ItemEntity: fixed a bunch of suspicious logic in entityBaseTick()
closes #5580
2023-02-17 20:14:38 +00:00
324bc27b5a Merge branch 'stable' into next-minor 2023-02-17 19:59:28 +00:00
71aad310c6 stfu 2023-02-17 16:39:46 +00:00
38828e2b42 4.15.1 is next 2023-02-17 16:37:34 +00:00
9a6d7b505c Release 4.15.0 2023-02-17 16:37:34 +00:00
1e3b025916 1.19.62 2023-02-17 16:36:32 +00:00
396d64c60b 4.14.2 is next 2023-02-15 15:19:39 +00:00
d7a0f5362e Release 4.14.1 2023-02-15 15:19:38 +00:00
c5dcd268ad CS 2023-02-15 15:04:41 +00:00
910c4c4b24 Updated BedrockProtocol 2023-02-15 15:02:00 +00:00
2fd6e769e6 NetworkSession: Improved packet budgeting
this fixes players getting kicked during server lag spikes.

closes #5532
2023-02-15 14:59:05 +00:00
69155015c9 Double quote array expansions to avoid re-splitting elements. (#5570)
See: https://github.com/koalaman/shellcheck/wiki/SC2068
2023-02-13 12:24:47 +00:00
6854830b6e start.sh: Use -n instead of ! -z (#5567)
See https://github.com/koalaman/shellcheck/wiki/SC2236
2023-02-13 12:21:35 +00:00
2c413768a5 Merge branch 'stable' into next-minor 2023-02-11 17:15:12 +00:00
fbaf8e3fc8 Update composer dependencies 2023-02-11 17:13:12 +00:00
c62845e92a 4.14.1 is next 2023-02-08 20:21:43 +00:00
c7930ce9ec Release 4.14.0 2023-02-08 20:21:42 +00:00
475888b031 InGamePacketHandler: do not process repeated skin change requests for the same full skin ID
this fixes a feedback loop with persona skins in 1.19.60.
2023-02-08 20:16:41 +00:00
40b90bb722 InGamePacketHandler: log a debug when processing skin change requests 2023-02-08 19:47:12 +00:00
5a4550a4fc CS 2023-02-08 18:55:49 +00:00
7bbc04e6de Silence PlayerSkinPacket debug messages during spawn response stage
the client sends its skin here in 1.19.60 for some reason, which makes no sense - I can only assume it's a bug
2023-02-08 18:55:38 +00:00
3ba662f64f 1.19.60 2023-02-08 18:46:37 +00:00
5d7b99daf4 Bump phpstan/phpstan from 1.9.15 to 1.9.16 (#5560)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.15 to 1.9.16.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.15...1.9.16)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-08 10:55:09 +00:00
e47627f565 Bump build/php from f51e954 to fb297eb (#5558)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `f51e954` to `fb297eb`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](f51e954743...fb297eb511)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-08 10:09:15 +00:00
39207c7992 Bump phpstan/phpstan from 1.9.14 to 1.9.15 (#5557)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.14 to 1.9.15.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.14...1.9.15)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-07 12:39:49 +00:00
41ab698f93 wrong filename 2023-02-06 12:29:54 +00:00
e45a6d8311 Merge branch 'stable' into next-minor 2023-02-06 12:27:09 +00:00
8912a97be7 Update Composer dependencies 2023-02-06 12:11:42 +00:00
8d2a9ce67c Clean PHPStan baselines 2023-02-06 12:09:19 +00:00
811352e2ef Bump build/php from c658506 to f51e954 (#5555)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `c658506` to `f51e954`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](c6585061ca...f51e954743)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 11:50:10 +00:00
981385cf4a GeneratorManager: Removed redundant Closure wrapping (#5551) 2023-02-04 14:04:31 +00:00
cfa1e7486a Move legacy recipes and creative items in-house
having them here allows BedrockData to represent latest versions freely, without being limited by technical limitations of PM4.
2023-02-02 15:25:03 +00:00
3c46bf01c6 Begin removing dependence on obsolete files from BedrockData
these files were only kept for backwards compatibility, and aren't actively maintained. They are only needed for legacy conversions in the modern day era.
2023-02-02 14:47:38 +00:00
4562cfb85b 4.13.1 is next 2023-01-30 21:55:26 +00:00
cb1aac3cd4 Release 4.13.0 2023-01-30 21:55:26 +00:00
3dd1a14fb7 Merge branch 'next-minor' into stable 2023-01-30 21:52:41 +00:00
63c3127248 Scrub PHPStan baselines 2023-01-30 21:52:31 +00:00
96c32d24ba Update composer dependencies 2023-01-30 13:23:40 +00:00
d64a9d8b52 Merge branch 'stable' into next-minor 2023-01-30 12:58:50 +00:00
92c29b8172 Update transitive composer dependencies 2023-01-30 12:53:06 +00:00
0ac9584bbb Bump shivammathur/setup-php from 2.23.0 to 2.24.0 (#5543)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.23.0 to 2.24.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.23.0...2.24.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 12:49:42 +00:00
fe12e8d944 Bump build/php from 1c44c61 to c658506 (#5542)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `1c44c61` to `c658506`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](1c44c615c3...c6585061ca)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 12:39:14 +00:00
7529953f0a Bump phpstan/phpstan from 1.9.13 to 1.9.14 (#5523)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.13 to 1.9.14.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.13...1.9.14)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-27 23:08:36 +00:00
00dbb6855a Bump build/php from 83bec42 to 1c44c61 (#5539)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `83bec42` to `1c44c61`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](83bec42c3c...1c44c615c3)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-27 23:08:08 +00:00
7eca3e8081 Fix typo
closes #5533
2023-01-26 14:52:50 +00:00
b58d7fc82a Bump build/php from af250d7 to 83bec42 (#5534)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `af250d7` to `83bec42`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](af250d7e06...83bec42c3c)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-26 13:00:07 +00:00
ceea8220a9 Bump symfony/filesystem from 5.4.13 to 5.4.19 (#5530)
Bumps [symfony/filesystem](https://github.com/symfony/filesystem) from 5.4.13 to 5.4.19.
- [Release notes](https://github.com/symfony/filesystem/releases)
- [Changelog](https://github.com/symfony/filesystem/blob/6.2/CHANGELOG.md)
- [Commits](https://github.com/symfony/filesystem/compare/v5.4.13...v5.4.19)

---
updated-dependencies:
- dependency-name: symfony/filesystem
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 13:23:48 +00:00
18013e9551 Bump build/php from b479ec4 to af250d7 (#5527)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `b479ec4` to `af250d7`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](b479ec438f...af250d7e06)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 13:51:50 +00:00
bd3e9e1cad Do not allow more than 1 run to compile PHP at a time
this causes cache collisions and build failures
2023-01-23 20:13:29 +00:00
6173471cca Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-01-23 19:40:14 +00:00
644881372d Merge branch 'stable' into next-minor 2023-01-23 19:37:02 +00:00
a12aac71fd Updated setup-php-action 2023-01-23 19:36:52 +00:00
608fcd6cd7 4.12.12 is next 2023-01-22 20:44:31 +00:00
ce9b25e97a Release 4.12.11 2023-01-22 20:44:26 +00:00
f948cb0086 PocketMine.php: refuse pthreads 5.0 2023-01-21 15:30:36 +00:00
6c52723d97 Merge branch 'stable' into next-minor 2023-01-20 15:40:32 +00:00
d5b7bf77b0 Bump build/php from 6b605ed to b479ec4 (#5521)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `6b605ed` to `b479ec4`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](6b605ed7c4...b479ec438f)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-20 09:44:19 +00:00
b1a5b02d3a Updated DevTools to 1.16.1 2023-01-20 01:55:52 +00:00
74e052de51 Terminal: fix deprecation error on PHP 8.2 2023-01-20 01:30:39 +00:00
a5397d55fe Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-01-20 01:13:27 +00:00
65ef929d22 Update Actions PHP versions 2023-01-20 01:13:15 +00:00
441919c5e3 Begin testing on PHP 8.2 2023-01-20 01:12:35 +00:00
448aeec780 Bump phpstan/phpstan from 1.9.12 to 1.9.13 (#5520)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.12 to 1.9.13.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.10.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.12...1.9.13)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-19 13:04:28 +00:00
78aea5c34c Merge branch 'stable' into next-minor 2023-01-18 20:46:10 +00:00
d7f40f75d2 PlayerPreLoginEvent: fixed documentation errors 2023-01-18 20:45:49 +00:00
b47035fbab Merge branch 'stable' into next-minor 2023-01-18 19:48:29 +00:00
f0925ff9dc draft-release: link to the correct channel-specific changelog
not having this made releasing alphas and betas error-prone, because I'd have to manually amend the changelog URL in the release.
2023-01-18 16:32:22 +00:00
d9324b9951 4.13.0-BETA2 is next 2023-01-18 16:15:47 +00:00
faaec12aaf Update BedrockProtocol 2023-01-06 22:16:29 +00:00
1123a5aa23 InventoryManager: Track predictions using ItemStack directly, instead of internal Item
this removes the need for deserializing network itemstacks to core items, thereby eliminating a whole bunch of potential security issues.
2023-01-06 20:45:08 +00:00
8633804f15 InventoryManager: disentangle slot tracking from slot syncing 2023-01-06 20:26:19 +00:00
d3cea2ca7c CS 2023-01-06 02:07:31 +00:00
5d6dba96af Merge branch 'stable' into item-stack-request 2023-01-06 01:47:27 +00:00
b24eb153f9 Constrain inventory transaction predictions
these are now only used for actions done with a closed inventory window. This means that they can only predict the slots of inventory, offhand and armor (total 41 slots) and perhaps include some DropItem actions.
2023-01-05 21:18:30 +00:00
36525d9055 Fixed multi-output recipe handling 2023-01-05 20:41:44 +00:00
3d6baa8a55 Working creative inventory, with a few more hacks than I'd like 2023-01-05 18:09:57 +00:00
30d3869eea Remove dead code 2023-01-05 17:26:58 +00:00
81697111b9 Merge branch 'item-stack-request' of github.com:pmmp/PocketMine-MP into item-stack-request 2023-01-05 17:24:15 +00:00
eedc943766 Confine legacy transaction handling to dropping items only 2023-01-05 17:23:50 +00:00
c6e11a8453 Remove unnecessary ternary operator (#5493) 2023-01-04 23:22:31 +00:00
2e9a3f9160 Working crafting :woohoo: 2023-01-04 22:29:29 +00:00
3d4ed5308e Merge branch 'stable' into item-stack-request 2023-01-04 01:28:42 +00:00
58dd4a44e3 Merge branch 'stable' into item-stack-request 2023-01-04 00:41:58 +00:00
5fdbb19852 Fixed a whole bunch of issues with legacy transactions 2023-01-04 00:13:51 +00:00
6b2156151f Merge branch 'stable' into item-stack-request 2023-01-03 23:51:37 +00:00
d8d236842f Fixed merge error 2023-01-03 19:54:41 +00:00
f51717323b Merge branch 'stable' into item-stack-request 2023-01-03 19:53:25 +00:00
0039af984d Merge branch 'next-minor' into item-stack-request 2022-10-16 16:56:26 +01:00
3235d128e5 Fixed handling of fake requests during block placement and other actions 2022-08-18 18:25:49 +01:00
2b7510945a First look at ItemStackRequest usage (very unstable) 2022-08-18 17:38:57 +01:00
87 changed files with 57922 additions and 1071 deletions

View File

@ -30,68 +30,68 @@ jobs:
id: tag-name
run: |
VERSION=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{')
echo ::set-output name=TAG_NAME::$VERSION
echo ::set-output name=MAJOR::$(echo $VERSION | cut -d. -f1)
echo ::set-output name=MINOR::$(echo $VERSION | cut -d. -f1-2)
echo TAG_NAME=$VERSION >> $GITHUB_OUTPUT
echo MAJOR=$(echo $VERSION | cut -d. -f1) >> $GITHUB_OUTPUT
echo MINOR=$(echo $VERSION | cut -d. -f1-2) >> $GITHUB_OUTPUT
- name: Download new release information
run: curl -f -L ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.tag-name.outputs.TAG_NAME }}/build_info.json -o new_build_info.json
- name: Detect channel
id: channel
run: echo ::set-output name=CHANNEL::$(jq -r '.channel' new_build_info.json)
run: echo CHANNEL=$(jq -r '.channel' new_build_info.json) >> $GITHUB_OUTPUT
- name: Get name of Docker repository name
id: docker-repo-name
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v3.3.0
uses: docker/build-push-action@v4.0.0
with:
push: true
context: ./pocketmine-mp
tags: |
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.TAG_NAME }}
# ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.TAG_NAME }}
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.TAG_NAME }}
build-args: |
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
PMMP_REPO=${{ github.repository }}
- name: Build image for major tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v3.3.0
uses: docker/build-push-action@v4.0.0
with:
push: true
context: ./pocketmine-mp
tags: |
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }}
# ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }}
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }}
build-args: |
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
PMMP_REPO=${{ github.repository }}
- name: Build image for minor tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v3.3.0
uses: docker/build-push-action@v4.0.0
with:
push: true
context: ./pocketmine-mp
tags: |
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }}
# ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }}
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }}
build-args: |
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
PMMP_REPO=${{ github.repository }}
- name: Build image for latest tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v3.3.0
uses: docker/build-push-action@v4.0.0
with:
push: true
context: ./pocketmine-mp
tags: |
${{ steps.docker-repo-name.outputs.NAME }}:latest
# ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:latest
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:latest
build-args: |
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
PMMP_REPO=${{ github.repository }}

View File

@ -18,8 +18,9 @@ require dirname(__DIR__, 2) . '/vendor/autoload.php';
/**
* @phpstan-return array<string, mixed>
*/
function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl) : array{
function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl, int $newsPingRoleId) : array{
return [
"content" => "<@&$newsPingRoleId> New PocketMine-MP release: $version ($channel)",
"embeds" => [
[
"title" => "New PocketMine-MP release: $version ($channel)",
@ -35,11 +36,11 @@ DESCRIPTION,
];
}
if(count($argv) !== 5){
fwrite(STDERR, "Required arguments: github repo, version, API token\n");
if(count($argv) !== 6){
fwrite(STDERR, "Required arguments: github repo, version, API token, webhook URL, ping role ID\n");
exit(1);
}
[, $repo, $tagName, $token, $hookURL] = $argv;
[, $repo, $tagName, $token, $hookURL, $newsPingRoleId] = $argv;
$result = Internet::getURL('https://api.github.com/repos/' . $repo . '/releases/tags/' . $tagName, extraHeaders: [
'Authorization: token ' . $token
@ -86,7 +87,7 @@ $buildLogUrl = $buildInfoJson["build_log_url"];
$description = $releaseInfoJson["body"];
$discordPayload = generateDiscordEmbed($buildInfoJson["base_version"], $buildInfoJson["channel"], $description, $detailsUrl, $sourceUrl, $pharDownloadUrl, $buildLogUrl);
$discordPayload = generateDiscordEmbed($buildInfoJson["base_version"], $buildInfoJson["channel"], $description, $detailsUrl, $sourceUrl, $pharDownloadUrl, $buildLogUrl, (int) $newsPingRoleId);
$response = Internet::postURL(
$hookURL,

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.23.0
uses: shivammathur/setup-php@2.24.0
with:
php-version: 8.0
@ -32,7 +32,7 @@ jobs:
- name: Get actual tag name
id: tag-name
run: echo ::set-output name=TAG_NAME::$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{')
run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT
- name: Run webhook post script
run: php .github/workflows/discord-release-embed.php ${{ github.repository }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
run: php .github/workflows/discord-release-embed.php ${{ github.repository }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }} ${{ secrets.DISCORD_NEWS_PING_ROLE_ID }}

View File

@ -18,7 +18,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.23.0
uses: shivammathur/setup-php@2.24.0
with:
php-version: 8.0
@ -40,7 +40,7 @@ jobs:
run: |
BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins
echo "Build number: $BUILD_NUMBER"
echo ::set-output name=BUILD_NUMBER::$BUILD_NUMBER
echo BUILD_NUMBER=$BUILD_NUMBER >> $GITHUB_OUTPUT
- name: Minify BedrockData JSON files
run: php vendor/pocketmine/bedrock-data/.minify_json.php
@ -51,10 +51,12 @@ jobs:
- name: Get PocketMine-MP release version
id: get-pm-version
run: |
echo ::set-output name=PM_VERSION::$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BASE_VERSION;')
echo ::set-output name=MCPE_VERSION::$(php -r 'require "vendor/autoload.php"; echo \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK;')
echo ::set-output name=PM_VERSION_SHORT::$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\VersionInfo::BASE_VERSION); array_pop($v); echo implode(".", $v);')
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
echo PM_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BASE_VERSION;') >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK;') >> $GITHUB_OUTPUT
echo PM_VERSION_SHORT=$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\VersionInfo::BASE_VERSION); array_pop($v); echo implode(".", $v);') >> $GITHUB_OUTPUT
echo PM_VERSION_MD=$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);') >> $GITHUB_OUTPUT
echo CHANGELOG_SUFFIX=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "" : "-" . \pocketmine\VersionInfo::BUILD_CHANNEL;') >> $GITHUB_OUTPUT
echo PRERELEASE=$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BUILD_CHANNEL === "stable" ? "false" : "true";') >> $GITHUB_OUTPUT
- name: Generate build info
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} ${{ github.run_id }} > build_info.json
@ -74,10 +76,11 @@ jobs:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
commit: ${{ github.sha }}
draft: true
prerelease: ${{ steps.get-pm-version.outputs.PRERELEASE }}
name: PocketMine-MP ${{ steps.get-pm-version.outputs.PM_VERSION }}
tag: ${{ steps.get-pm-version.outputs.PM_VERSION }}
token: ${{ secrets.GITHUB_TOKEN }}
body: |
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.

View File

@ -13,14 +13,15 @@ jobs:
strategy:
matrix:
image: [ubuntu-20.04]
php: [8.0.23, 8.1.10]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- name: Build and prepare PHP cache
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
phpstan:
name: PHPStan analysis
@ -31,16 +32,17 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.23, 8.1.10]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -69,16 +71,17 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.23, 8.1.10]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -107,7 +110,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.23, 8.1.10]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- uses: actions/checkout@v3
@ -115,10 +118,11 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -147,16 +151,17 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.23, 8.1.10]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -180,6 +185,9 @@ jobs:
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Regenerate BedrockData available files constants
run: php build/generate-bedrockdata-path-consts.php
- name: Verify code is unchanged
run: |
git diff
@ -195,7 +203,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.23.0
uses: shivammathur/setup-php@2.24.0
with:
php-version: 8.0
tools: php-cs-fixer:3.11

View File

@ -23,7 +23,8 @@ declare(strict_types=1);
const VERSIONS = [
"8.0",
"8.1"
"8.1",
"8.2"
];
$workflowFile = file_get_contents(__DIR__ . '/main.yml');

View File

@ -15,23 +15,68 @@ jobs:
- uses: actions/checkout@v3
with:
repository: pmmp/update.pmmp.io
repository: ${{ github.repository_owner }}/update.pmmp.io
ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }}
- name: Get actual tag name
id: tag-name
run: echo ::set-output name=TAG_NAME::$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{')
run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT
- name: Download new release information
run: curl -f -L ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.tag-name.outputs.TAG_NAME }}/build_info.json -o new_build_info.json
- name: Detect channel
- name: Detect channels
id: channel
run: echo ::set-output name=CHANNEL::$(jq -r '.channel' new_build_info.json)
- name: Copy release information
run: |
cp new_build_info.json channels/${{ steps.channel.outputs.CHANNEL }}.json
CHANNEL=$(jq -r '.channel' new_build_info.json)
VERSION=${{ steps.tag-name.outputs.TAG_NAME }}
echo CHANNEL=$CHANNEL >> $GITHUB_OUTPUT
if [ "$CHANNEL" == "stable" ]; then
echo MAJOR=$(echo $VERSION | cut -d. -f1) >> $GITHUB_OUTPUT
echo MINOR=$(echo $VERSION | cut -d. -f1-2) >> $GITHUB_OUTPUT
else
echo MAJOR=$(echo $VERSION | cut -d. -f1)-$CHANNEL >> $GITHUB_OUTPUT
echo MINOR=$(echo $VERSION | cut -d. -f1-2)-$CHANNEL >> $GITHUB_OUTPUT
fi
- name: Update channel info
run: |
function version_id() {
major=$(echo $1 | cut -d. -f1)
minor=$(echo $1 | cut -d. -f2)
patch=$(echo $1 | cut -d. -f3)
echo $(((major * 1000000) + (minor * 1000) + patch))
}
function update_channel() {
local target_file_name="$1"
local new_file_name="$2"
local old_version_id
local new_version_id
if [ ! -f "$target_file_name" ]; then
echo "Creating channel file: $target_file_name"
cp "$new_file_name" "$target_file_name"
else
old_version_id=$(version_id "$(jq -r '.base_version' "$target_file_name")")
new_version_id=$(version_id "$(jq -r '.base_version' "$new_file_name")")
echo "Old version ID: $old_version_id"
echo "New version ID: $new_version_id"
if [ $new_version_id -ge $old_version_id ]; then #suffixed versions will have the same version ID - assume they'll always be newer
echo "Updating channel file: $target_file_name ($old_version_id -> $new_version_id)"
cp "$new_file_name" "$target_file_name"
else
echo "Version $new_version_id is less than $old_version_id, not updating channel file: $target_file_name"
fi
fi
}
update_channel "channels/${{ steps.channel.outputs.CHANNEL }}.json" "new_build_info.json"
update_channel "channels/${{ steps.channel.outputs.MAJOR }}.json" "new_build_info.json"
update_channel "channels/${{ steps.channel.outputs.MINOR }}.json" "new_build_info.json"
rm new_build_info.json
- name: Commit changes

View File

@ -21,7 +21,7 @@ Larger contributions like feature additions should be preceded by a [Change Prop
## Choosing a target branch
PocketMine-MP has three primary branches of development.
| Type of change | `stable` | `next-minor` | `next-major` |
| Type of change | `stable` | `minor-next` | `major-next` |
|:---------------|:--------:|:------------:|:------------:|
| Bug fixes | ✔️ | ✔️ | ✔️ |
| Improvements to API docs | ✔️ | ✔️ | ✔️ |

View File

@ -14,7 +14,6 @@
<p align="center">
<a href="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" /></a>
<a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/pmmp/PocketMine-MP?label=release&sort=semver"></a>
<a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a>
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
<br>
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img alt="GitHub all releases" src="https://img.shields.io/github/downloads/pmmp/PocketMine-MP/total?label=downloads%40total"></a>
@ -24,7 +23,7 @@
## Getting started
- [Documentation](http://pmmp.readthedocs.org/)
- [Installation instructions](https://pmmp.readthedocs.io/en/rtfd/installation.html)
- [Docker image](https://hub.docker.com/r/pmmp/pocketmine-mp)
- [Docker image](https://github.com/pmmp/PocketMine-MP/pkgs/container/pocketmine-mp)
- [Plugin repository](https://poggit.pmmp.io/plugins)
## Discussion/Help

View File

@ -0,0 +1,128 @@
<?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\build\generate_bedrockdata_path_consts;
use Symfony\Component\Filesystem\Path;
use function dirname;
use function fclose;
use function fopen;
use function fwrite;
use function is_file;
use function scandir;
use function str_replace;
use function strtoupper;
use const PHP_EOL;
use const pocketmine\BEDROCK_DATA_PATH;
use const SCANDIR_SORT_ASCENDING;
use const STDERR;
require dirname(__DIR__) . '/vendor/autoload.php';
function constantify(string $permissionName) : string{
return strtoupper(str_replace([".", "-"], "_", $permissionName));
}
$files = scandir(BEDROCK_DATA_PATH, SCANDIR_SORT_ASCENDING);
if($files === false){
fwrite(STDERR, "Couldn't find any files in " . BEDROCK_DATA_PATH . PHP_EOL);
exit(1);
}
$consts = [];
foreach($files as $file){
if($file === '.' || $file === '..'){
continue;
}
if($file[0] === '.'){
continue;
}
$path = Path::join(BEDROCK_DATA_PATH, $file);
if(!is_file($path)){
continue;
}
foreach([
'README.md',
'LICENSE',
'composer.json',
] as $ignored){
if($file === $ignored){
continue 2;
}
}
$consts[] = $file;
}
$output = fopen(dirname(__DIR__) . '/src/data/bedrock/BedrockDataFiles.php', 'wb');
if($output === false){
fwrite(STDERR, "Couldn't open output file" . PHP_EOL);
exit(1);
}
fwrite($output, <<<'HEADER'
<?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\data\bedrock;
use const pocketmine\BEDROCK_DATA_PATH;
final class BedrockDataFiles{
private function __construct(){
//NOOP
}
HEADER
);
foreach($consts as $constName => $fileName){
fwrite($output, "\tpublic const " . constantify($fileName) . " = BEDROCK_DATA_PATH . '/$fileName';\n");
}
fwrite($output, "}\n");
fclose($output);
echo "Done. Don't forget to run CS fixup after generating code.\n";

View File

@ -36,11 +36,12 @@ use function fwrite;
use function getopt;
use function is_string;
use function max;
use function preg_match;
use function preg_replace;
use function sleep;
use function sprintf;
use function str_pad;
use function strlen;
use function strtolower;
use function system;
use const STDERR;
use const STDIN;
@ -102,22 +103,43 @@ function main() : void{
$filteredOpts[$optName] = $optValue;
}
$channel = $filteredOpts["channel"] ?? null;
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
if(isset($filteredOpts["next"])){
$nextVer = new VersionString($filteredOpts["next"]);
$nextVer = isset($filteredOpts["next"]) ? new VersionString($filteredOpts["next"]) : null;
$suffix = $currentVer->getSuffix();
if($suffix !== ""){
if($channel === "stable"){
fwrite(STDERR, "error: cannot release a suffixed build into the stable channel\n");
exit(1);
}
if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){
echo "error: invalid current version suffix \"$suffix\"; aborting\n";
exit(1);
}
$nextVer ??= new VersionString(sprintf(
"%u.%u.%u-%s%u",
$currentVer->getMajor(),
$currentVer->getMinor(),
$currentVer->getPatch(),
$matches[1],
((int) $matches[2]) + 1
));
$channel ??= strtolower($matches[1]);
}else{
$nextVer = new VersionString(sprintf(
$nextVer ??= new VersionString(sprintf(
"%u.%u.%u",
$currentVer->getMajor(),
$currentVer->getMinor(),
$currentVer->getPatch() + 1
));
$channel ??= "stable";
}
$channel = $filteredOpts["channel"] ?? VersionInfo::BUILD_CHANNEL;
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
@ -137,9 +159,6 @@ function main() : void{
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit");
systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit");
echo "pushing changes in 5 seconds\n";
sleep(5);
systemWrapper('git push origin HEAD ' . $currentVer->getBaseVersion(), "failed to push changes to remote");
}
main();

View File

@ -104,3 +104,16 @@ Released 18th January 2023.
## Note about server load & performance
This version will report higher apparent server load than previous versions. The actual performance of the server is unchanged; the previous reported load was inaccurate.
These bugs have been present for nearly 5 years (ever since the first introduction of Snooze in 3.0.0).
# 4.12.11
Released 22nd January 2023.
## General
- Code is now tested and analysed using PHP 8.2 in addition to 8.1 and 8.0.
## Fixes
- Fixed pthreads 5.0.0 incorrectly being treated as compatible.
- Fixed deprecation errors on PHP 8.2.
## Documentation
- Updated documentation in `PlayerPreLoginEvent`.

94
changelogs/4.13.md Normal file
View File

@ -0,0 +1,94 @@
**For Minecraft: Bedrock Edition 1.19.50**
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.13.0
Released 30th January 2023.
## Gameplay
- Death message is now shown on the death screen when a player dies.
- Armour damage is now only increased if the armour reduced the damage taken.
- Implemented Swift Sneak enchantment.
- Fixed incorrect collision box calculation of walls and glass/bars when connected. Note: Client-side, wall connections are still broken; this only fixes projectile flight server-side.
## Performance
- Improved performance of chunk selection for chunk random ticking using a cache. This improves performance of chunk random ticking by 10-20%.
## Localization
- Added localized description for the `/dumpmemory` command.
## Permissions
- Added the following new core permissions:
- `pocketmine.command.effect.other` - allows the player to use the `/effect` command on other players (default operator only)
- `pocketmine.command.effect.self` - allows the player to use the `/effect` command on themselves (default operator only)
- `pocketmine.command.enchant.other` - allows the player to use the `/enchant` command on other players (default operator only)
- `pocketmine.command.enchant.self` - allows the player to use the `/enchant` command on themselves (default operator only)
- `pocketmine.command.gamemode.other` - allows the player to use the `/gamemode` command on other players (default operator only)
- `pocketmine.command.gamemode.self` - allows the player to use the `/gamemode` command on themselves (default operator only)
- `pocketmine.command.give.other` - allows the player to use the `/give` command on other players (default operator only)
- `pocketmine.command.give.self` - allows the player to use the `/give` command on themselves (default operator only)
- `pocketmine.command.spawnpoint.other` - allows the player to use the `/spawnpoint` command on other players (default operator only)
- `pocketmine.command.spawnpoint.self` - allows the player to use the `/spawnpoint` command on themselves (default operator only)
- `pocketmine.command.teleport.other` - allows the player to use the `/teleport` command on other players (default operator only)
- `pocketmine.command.teleport.self` - allows the player to use the `/teleport` command on themselves (default operator only)
- `pocketmine.command.title.other` - allows the player to use the `/title` command on other players (default operator only)
- `pocketmine.command.title.self` - allows the player to use the `/title` command on themselves (default operator only)
## Internals
- Decoupled `Player->sendMessage()` and `Player->sendTranslation()`.
- Refactored resource pack loading in `ResourcePackManager` to make it easier to understand.
- Client-aware translation processing has been moved to `NetworkSession` due to being client-specific.
- Replaced hardcoded strings with constants in various places.
- `NetworkSession` destructive cleanup is now deferred to the next session tick. This fixes various `InventoryManager` crashes when kicking players during events.
- Updated code using `strpos()` to use `str_starts_with()`, `str_ends_with()` and `str_contains()` where appropriate.
- Added documentation for some internal methods.
## API
### `pocketmine\command`
- The following new API methods have been added:
- `protected VanillaCommand->fetchPermittedPlayerTarget(...) : ?Player` - fetches a player target according to the given sender permissions, or null if not found or not permitted
### `pocketmine\entity`
- The following new API methods have been added:
- `public Living->getDisplayName() : string` - the name of the entity to be shown in death messages, commands etc.
### `pocketmine\event\world`
- The following new classes have been added:
- `WorldSoundEvent` - called when a sound is played in a world
- `WorldParticleEvent` - called when a particle is spawned in a world
### `pocketmine\item`
- The following new API methods have been added:
- `public Item->onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool` - called when a player interacts with an entity with this item in hand
### `pocketmine\lang`
- `Language->translate()` and `Language->translateString()` no longer parse nested translation in the "base text". This was never intended behaviour, and didn't work beyond the first level anyway.
### `pocketmine\player`
- The following new interfaces have been added:
- `PlayerDataProvider` - implemented by classes which want to offer storage for player data
- The following new classes have been added:
- `DatFilePlayerDataProvider` - the default player data provider, which stores `.dat` files in the `players` folder
- `PlayerDataLoadException` - thrown when an error occurs while loading player data
- `PlayerDataSaveException` - thrown when an error occurs while saving player data
- The following API methods have been deprecated:
- `Player->sendTranslation()` - use `Player->sendMessage()` instead with a `Translatable` message
### `pocketmine\resourcepacks`
- The following new API methods have been added:
- `public ResourcePackManager->setResourceStack(list<ResourcePack> $resourceStack) : void` - sets the list of resource packs to be applied by players
- `public ResourcePackManager->setPackEncryptionKey(string $id, ?string $key) : void` - sets the encryption key to be used for a resource pack
### `pocketmine\utils`
- The following new API methods have been added:
- `public static Filesystem::fileGetContents(...) : string` - a wrapper around `file_get_contents()` which throws an exception on failure
### `pocketmine\world`
- The following new API methods have been added:
- `public World->requestSafeSpawn(?Vector3 $spawn = null) : Promise<Position>` - an async version of `getSafeSpawn()` which generates all the needed chunks before returning

21
changelogs/4.14.md Normal file
View File

@ -0,0 +1,21 @@
**For Minecraft: Bedrock Edition 1.19.60**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.14.0
Released 8th February 2023.
## General
- Added support for Minecraft: Bedrock Edition 1.19.60.
- Removed support for older versions.
# 4.14.1
Released 15th February 2023.
## Fixes
- Fixed all players getting kicked with `Receiving packets too fast` if a server tick takes longer than 5 seconds (e.g. because of autosave or GC).
- Fixed players getting kicked when linking with entities.

38
changelogs/4.15.md Normal file
View File

@ -0,0 +1,38 @@
**For Minecraft: Bedrock Edition 1.19.62**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.15.0
Released 17th February 2023.
## General
- Added support for Minecraft: Bedrock Edition 1.19.62.
- Removed support for older versions.
# 4.15.1
Released 21st February 2023.
## Fixes
- Fixed dropped items not despawning when in non-ticking chunks.
- Fixed dropped items not despawning if an infinite pickup delay is set.
- Fixed infinite despawn delay (never despawn) being ignored for dropped items.
# 4.15.2
Released 24th February 2023.
## General
- Accept Minecraft: Bedrock Edition 1.19.63 (identical protocol to 1.19.62, but different version due to Mojang mixup).
## Fixes
- Fixed `World Population` timer sometimes not being stopped, causing strange results in timings reports.
# 4.15.3
Released 7th March 2023.
## Fixes
- Fixed `/dumpmemory` crash when any object contained an `INF` or `NaN` float value.
- Updated RakLib for security fixes.

45
changelogs/4.16-beta.md Normal file
View File

@ -0,0 +1,45 @@
**For Minecraft: Bedrock Edition 1.19.62**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.16.0-BETA1
Released 4th March 2023.
## General
- Added granular timings for packet encode, similar to the existing timings for packet decode.
- Timings now covers several areas of the network system which were previously not counted by network timings, but were counted by total timings. This provides a better insight into the performance of the network system.
## Performance
- Improved performance of packet batch handling by avoiding unnecessary object allocations.
- Improved performance of packet broadcasting when the broadcast size is below the batch threshold. Previously, the packets would be encoded once by every recipient, but now they are encoded once and then added to the send buffer of each session in their raw form.
- This change mostly affects servers with larger maps, where players are more widely distributed.
## Build system
- Added a new script `build/generate-bedrockdata-path-consts.php`, which must be run whenever BedrockData is updated. This script generates a class of constants with the file paths of all BedrockData files.
## API
### `pocketmine\entity`
- The following new API methods have been added:
- `public Entity->getGravity() : float` - returns the entity's gravity acceleration in blocks/tick^2
- `public Entity->setGravity(float $gravity) : void` - sets the entity's gravity acceleration in blocks/tick^2
## Internals
- Now uses [`pocketmine/bedrock-data` 2.0.0](https://github.com/pmmp/BedrockData/releases/tag/2.0.0+bedrock-1.19.60).
- This version is now used by both PM4 and PM5, reducing maintenance burden.
- Now uses [`pocketmine/bedrock-protocol` 19.3.0](https://github.com/pmmp/BedrockProtocol/releases/tag/19.3.0+bedrock-1.19.62).
- This version provides new APIs for handling packet batches which enabled improving performance and adding new features, such as detailed packet encode timings.
- Crafting recipes and creative inventory data are now loaded from `recipes/legacy_recipes.json` and `recipes/legacy_creativeitems.json` respectively. Previously, these were loaded from BedrockData directly, but BedrockData 2.0 now uses a format which can't be supported in 4.x without BC breaks.
- Added dependencies on [`pocketmine/bedrock-block-upgrade-schema`](https://github.com/pmmp/BedrockBlockUpgradeSchema) and [`pocketmine/bedrock-item-upgrade-schema`](https://github.com/pmmp/BedrockItemUpgradeSchema). These provide mapping files no longer present in BedrockData 2.0.
- Reduced and/or eliminated most usages of `PacketBatch`, since it only appeared as a throwaway object and was therefore wasting performance.
- `Compressor` now exposes `getCompressionThreshold()` instead of `willCompress()`, which allows determining whether a batch will be compressed without allocating it.
- Added `pocketmine\data\bedrock\BedrockDataFiles`, an auto-generated class of constants with the file paths of all BedrockData files. This makes it easier to locate usages, detect unused files and avoid typos.
# 4.16.0-BETA2
Released 4th March 2023.
## General
- Fixed incorrect release channel for 4.16.0-BETA1.

41
changelogs/4.16.md Normal file
View File

@ -0,0 +1,41 @@
**For Minecraft: Bedrock Edition 1.19.62**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.16.0
Released 7th March 2023.
## General
- Added granular timings for packet encode, similar to the existing timings for packet decode.
- Split `Player Network Send - Compression` timings into two timers, one for `Session Buffer` compression and one for `Broadcast` compression.
- Timings now covers several areas of the network system which were previously not counted by network timings, but were counted by total timings. This provides a better insight into the performance of the network system.
## Performance
- Improved performance of packet batch handling by avoiding unnecessary object allocations.
- Improved performance of packet broadcasting when the broadcast size is below the batch threshold. Previously, the packets would be encoded once by every recipient, but now they are encoded once and then added to the send buffer of each session in their raw form.
- This change mostly affects servers with larger maps, where players are more widely distributed.
- Improved performance of packet broadcasting when the broadcast has only one recipient (allow the session to compress the packet with the rest of its buffer).
## Build system
- Added a new script `build/generate-bedrockdata-path-consts.php`, which must be run whenever BedrockData is updated. This script generates a class of constants with the file paths of all BedrockData files.
## API
### `pocketmine\entity`
- The following new API methods have been added:
- `public Entity->getGravity() : float` - returns the entity's gravity acceleration in blocks/tick^2
- `public Entity->setGravity(float $gravity) : void` - sets the entity's gravity acceleration in blocks/tick^2
## Internals
- Now uses [`pocketmine/bedrock-data` 2.0.0](https://github.com/pmmp/BedrockData/releases/tag/2.0.0+bedrock-1.19.60).
- This version is now used by both PM4 and PM5, reducing maintenance burden.
- Now uses [`pocketmine/bedrock-protocol` 19.3.0](https://github.com/pmmp/BedrockProtocol/releases/tag/19.3.0+bedrock-1.19.62).
- This version provides new APIs for handling packet batches which enabled improving performance and adding new features, such as detailed packet encode timings.
- Crafting recipes and creative inventory data are now loaded from `recipes/legacy_recipes.json` and `recipes/legacy_creativeitems.json` respectively. Previously, these were loaded from BedrockData directly, but BedrockData 2.0 now uses a format which can't be supported in 4.x without BC breaks.
- Added dependencies on [`pocketmine/bedrock-block-upgrade-schema`](https://github.com/pmmp/BedrockBlockUpgradeSchema) and [`pocketmine/bedrock-item-upgrade-schema`](https://github.com/pmmp/BedrockItemUpgradeSchema). These provide mapping files no longer present in BedrockData 2.0.
- Reduced and/or eliminated most usages of `PacketBatch`, since it only appeared as a throwaway object and was therefore wasting performance.
- `Compressor` now exposes `getCompressionThreshold()` instead of `willCompress()`, which allows determining whether a batch will be compressed without allocating it.
- Added `pocketmine\data\bedrock\BedrockDataFiles`, an auto-generated class of constants with the file paths of all BedrockData files. This makes it easier to locate usages, detect unused files and avoid typos.

14
changelogs/4.17.md Normal file
View File

@ -0,0 +1,14 @@
**For Minecraft: Bedrock Edition 1.19.70**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.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.
# 4.17.0
Released 14th March 2023.
## General
- Added support for Minecraft: Bedrock Edition 1.19.70.
- Removed support for older versions.

91
changelogs/4.18-alpha.md Normal file
View File

@ -0,0 +1,91 @@
**For Minecraft: Bedrock Edition 1.19.70**
### Note about API versions
Plugins which don't touch the `pocketmine\network\mcpe` namespace are compatible with any previous 4.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 `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
### Alpha release warning
Alpha releases are **experimental**. Features introduced in these releases are subject to change or removal.
APIs which existed **prior** to this version will continue to work as normal, so plugins which use them will continue to work.
### Highlights
This version makes changes to the internal network system to improve server performance and reduce memory usage.
While these changes don't affect non-internal API, they are still significant enough to warrant a new minor version, as they may break plugins which use the internal network API (not recommended).
# 4.18.0-ALPHA1
Released 16th March 2023.
## General
- Improved server performance in congested areas of the world (lots of players and/or entities in the same area).
## API
### `pocketmine\event\server`
- The following new classes have been added:
- `DataPacketDecodeEvent` - called before a packet is decoded by a `NetworkSession`; useful to mitigate DoS attacks if PocketMine-MP hasn't been patched against new bugs yet
## Internals
- Introduced new system for broadcasting entity events to network sessions.
- This change improves performance when lots of players and/or entities are in the same area.
- New interface `EntityEventBroadcaster` and class `StandardEntityEventBroadcaster` have been added to implement this.
- All entity-specific `on*()` and `sync*()` methods have been removed from `NetworkSession` (BC break).
- `NetworkSession` now accepts an `EntityEventBroadcaster` instance in its constructor.
- `NetworkBroadcastUtils::broadcastEntityEvent()` can be used to efficiently broadcast events to unique broadcasters shared by several network sessions.
- All network sessions now share the same `PacketSerializerContext` and `PacketBroadcaster` by default.
- Previously, every session had its own context, meaning that broadcast optimisations were not used, causing significant performance losses compared to 3.x.
- This change improves performance in congested areas by allowing reuse of previously encoded packet buffers for all sessions sharing the same context.
- Packet broadcasts are automatically encoded separately per unique `PacketSerializerContext` instance. This allows, for example, a multi-version fork to have a separate context for each protocol version, to ensure maximum broadcast efficiency while encoding different packets for different versions.
- `PacketSerializerContext` is now passed in `NetworkSession::__construct()`, instead of being created by the session.
- `StandardPacketBroadcaster` is now locked to a single `PacketSerializer` context, reducing complexity.
- Introduced `NetworkBroadcastUtils::broadcastPackets()`, replacing `Server->broadcastPackets()`.
- `Server->broadcastPackets()` has been deprecated. It will be removed in a future version.
# 4.18.0-ALPHA2
Released 21st March 2023.
## General
- Included more sections of the network system in Player Network Send timings.
- Changed the names of some timings to make them more user-friendly.
- Removed packet IDs from `receivePacket` and `sendPacket` timings, as they were not very useful.
- Added new specialized timers for the following:
- Item entity base ticking (merging)
- Player movement processing
- Entity movement processing (collision checking section)
- Projectile movement (all)
- Projectile movement processing (ray tracing section)
## API
### `pocketmine\crafting`
- The following new API methods have been added:
- `CraftingManager->getCraftingRecipeIndex() : array<int, CraftingRecipe>` - returns a list of all crafting recipes
- `CraftingManager->getCraftingRecipeFromIndex(int $index) : ?CraftingRecipe` - returns the crafting recipe at the given index, or null if it doesn't exist
### `pocketmine\inventory\transaction`
- The following API methods have changed signatures:
- `CraftingTransaction->__construct()` now accepts additional arguments `?CraftingRecipe $recipe = null, ?int $repetitions = null`
- The following new API methods have been added:
- `TransactionBuilderInventory->getActualInventory() : Inventory` - returns the actual inventory that this inventory is a proxy for
## Internals
### Network
- Introduced support for the `ItemStackRequest` Minecraft: Bedrock network protocol.
- This fixes a large number of inventory- and crafting-related bugs.
- This also improves server security by closing off many code pathways that might have been used for exploits. `TypeConverter->netItemStackToCore()` is no longer used in server code, and remains for tool usage only.
- This system is also significantly more bandwidth-efficient and has lower overhead than the legacy system.
- This now opens the gateway to easily implement lots of gameplay features which have been missing for a long time, such as enchanting, anvils, looms, and more.
- Significant changes have been made to `pocketmine\network\mcpe\InventoryManager` internals. These shouldn't affect plugins, but may affect plugins which use internal network API.
- **No changes have been made to the plugin `InventoryTransaction` API**.
- This system has been implemented as a shim for the existing PocketMine-MP transaction system to preserve plugin compatibility. Plugins using `InventoryTransactionEvent` should continue to work seamlessly.
- The `InventoryTransaction` API will be redesigned in a future major version to make use of the new information provided by the `ItemStackRequest` system.
- `InventoryTransactionPacket` is no longer sent by the client for "regular" inventory actions. However, it is still sent when dropping items, interacting with blocks, and using items.
- Inventory slot and content syncing is now buffered until the end of the tick. This reduces outbound network usage when the client performs multiple transactions in a single tick (e.g. crafting a stack of items).
- Renamed some `InventoryManager` internal properties to make them easier to understand.
- `TypeConverter->createInventoryAction()` has been removed.
- Packet batch limit has been lowered to `100` packets. With the introduction of `ItemStackRequest`, this is more than sufficient for normal gameplay.
### Other
- Use `Vector3::zero()` instead of `new Vector3()` in some places.

View File

@ -34,14 +34,16 @@
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
"pocketmine/bedrock-protocol": "~18.0.0+bedrock-1.19.50",
"pocketmine/bedrock-block-upgrade-schema": "~1.1.1+bedrock-1.19.70",
"pocketmine/bedrock-data": "~2.1.1+bedrock-1.19.70",
"pocketmine/bedrock-item-upgrade-schema": "~1.1.0+bedrock-1.19.70",
"pocketmine/bedrock-protocol": "~20.1.0+bedrock-1.19.70",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.6.0",
"pocketmine/locale-data": "~2.17.0",
"pocketmine/locale-data": "~2.18.0",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
@ -54,7 +56,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.9.12",
"phpstan/phpstan": "1.10.6",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"

265
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "480dceecbc8a6fac8c498dfcc0938a73",
"content-hash": "01afa65b40f95ad9378c8cd999e6098d",
"packages": [
{
"name": "adhocore/json-comment",
@ -249,17 +249,43 @@
"time": "2022-12-08T20:46:14+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "1.13.0+bedrock-1.19.50",
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "57337ddc9433a0e245a1ce48c51af05f0573d58d"
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "e0540343e649a92126a1d4071ec401a811416c76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/57337ddc9433a0e245a1ce48c51af05f0573d58d",
"reference": "57337ddc9433a0e245a1ce48c51af05f0573d58d",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/e0540343e649a92126a1d4071ec401a811416c76",
"reference": "e0540343e649a92126a1d4071ec401a811416c76",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"CC0-1.0"
],
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/1.1.1"
},
"time": "2023-03-08T23:45:59+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "2.1.1+bedrock-1.19.70",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "cba0567bcb25f987f2712092f8d662056719e82d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/cba0567bcb25f987f2712092f8d662056719e82d",
"reference": "cba0567bcb25f987f2712092f8d662056719e82d",
"shasum": ""
},
"type": "library",
@ -270,22 +296,48 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.50"
"source": "https://github.com/pmmp/BedrockData/tree/2.1.1+bedrock-1.19.70"
},
"time": "2022-11-30T16:19:59+00:00"
"time": "2023-03-14T18:03:19+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "18.0.0+bedrock-1.19.50",
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "b558ec883bd967dd3339f513cba62d2fbcd63349"
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "aab89a1f121a0c127557a4a0cf981330301c9c45"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/b558ec883bd967dd3339f513cba62d2fbcd63349",
"reference": "b558ec883bd967dd3339f513cba62d2fbcd63349",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/aab89a1f121a0c127557a4a0cf981330301c9c45",
"reference": "aab89a1f121a0c127557a4a0cf981330301c9c45",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"CC0-1.0"
],
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.1.0"
},
"time": "2023-03-08T22:27:13+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "20.1.0+bedrock-1.19.70",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "91d67c8b1bced3c82d0841b1041c0c1f4e93eb68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/91d67c8b1bced3c82d0841b1041c0c1f4e93eb68",
"reference": "91d67c8b1bced3c82d0841b1041c0c1f4e93eb68",
"shasum": ""
},
"require": {
@ -299,7 +351,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.9.4",
"phpstan/phpstan": "1.10.7",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
@ -317,9 +369,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/18.0.0+bedrock-1.19.50"
"source": "https://github.com/pmmp/BedrockProtocol/tree/20.1.0+bedrock-1.19.70"
},
"time": "2023-01-06T21:47:35+00:00"
"time": "2023-03-20T01:17:00+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -537,16 +589,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.17.0",
"version": "2.18.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "a2c7071117c98ccc0e333994271cab1072eb3c06"
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/a2c7071117c98ccc0e333994271cab1072eb3c06",
"reference": "a2c7071117c98ccc0e333994271cab1072eb3c06",
"url": "https://api.github.com/repos/pmmp/Language/zipball/da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
"shasum": ""
},
"type": "library",
@ -554,9 +606,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.17.0"
"source": "https://github.com/pmmp/Language/tree/2.18.3"
},
"time": "2023-01-13T17:22:45+00:00"
"time": "2023-01-17T21:43:36+00:00"
},
{
"name": "pocketmine/log",
@ -728,16 +780,16 @@
},
{
"name": "pocketmine/raklib",
"version": "0.14.5",
"version": "0.14.6",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "85b4e5cb7117d37e010eeadb3ff53b21276c6f48"
"reference": "aeca667d5ecc4cc18fded612f29e3511bbf62f42"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/85b4e5cb7117d37e010eeadb3ff53b21276c6f48",
"reference": "85b4e5cb7117d37e010eeadb3ff53b21276c6f48",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/aeca667d5ecc4cc18fded612f29e3511bbf62f42",
"reference": "aeca667d5ecc4cc18fded612f29e3511bbf62f42",
"shasum": ""
},
"require": {
@ -749,7 +801,7 @@
"pocketmine/log": "^0.3.0 || ^0.4.0"
},
"require-dev": {
"phpstan/phpstan": "1.7.7",
"phpstan/phpstan": "1.9.17",
"phpstan/phpstan-strict-rules": "^1.0"
},
"type": "library",
@ -765,9 +817,9 @@
"description": "A RakNet server implementation written in PHP",
"support": {
"issues": "https://github.com/pmmp/RakLib/issues",
"source": "https://github.com/pmmp/RakLib/tree/0.14.5"
"source": "https://github.com/pmmp/RakLib/tree/0.14.6"
},
"time": "2022-08-25T16:16:44+00:00"
"time": "2023-03-07T15:10:23+00:00"
},
{
"name": "pocketmine/raklib-ipc",
@ -1034,16 +1086,16 @@
},
{
"name": "symfony/filesystem",
"version": "v5.4.13",
"version": "v5.4.21",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"shasum": ""
},
"require": {
@ -1078,7 +1130,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.4.13"
"source": "https://github.com/symfony/filesystem/tree/v5.4.21"
},
"funding": [
{
@ -1094,7 +1146,7 @@
"type": "tidelift"
}
],
"time": "2022-09-21T19:53:16+00:00"
"time": "2023-02-14T08:03:56+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1606,16 +1658,16 @@
},
{
"name": "myclabs/deep-copy",
"version": "1.11.0",
"version": "1.11.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
"reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": ""
},
"require": {
@ -1653,7 +1705,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
},
"funding": [
{
@ -1661,20 +1713,20 @@
"type": "tidelift"
}
],
"time": "2022-03-03T13:19:32+00:00"
"time": "2023-03-08T13:26:56+00:00"
},
{
"name": "nikic/php-parser",
"version": "v4.15.2",
"version": "v4.15.4",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"shasum": ""
},
"require": {
@ -1715,9 +1767,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
},
"time": "2022-11-12T15:38:23+00:00"
"time": "2023-03-05T19:49:14+00:00"
},
{
"name": "phar-io/manifest",
@ -1832,16 +1884,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.9.12",
"version": "1.10.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8"
"reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/44a338ff0d5572c13fd77dfd91addb96e48c29f8",
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a",
"reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a",
"shasum": ""
},
"require": {
@ -1871,7 +1923,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.9.12"
"source": "https://github.com/phpstan/phpstan/tree/1.10.6"
},
"funding": [
{
@ -1887,25 +1939,25 @@
"type": "tidelift"
}
],
"time": "2023-01-17T10:44:04+00:00"
"time": "2023-03-09T16:55:12+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.3.3",
"version": "1.3.10",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7"
"reference": "4cc5c6cc38e56bce7ea47c4091814e516d172dc3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/54a24bd23e9e80ee918cdc24f909d376c2e273f7",
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/4cc5c6cc38e56bce7ea47c4091814e516d172dc3",
"reference": "4cc5c6cc38e56bce7ea47c4091814e516d172dc3",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.9.3"
"phpstan/phpstan": "^1.10"
},
"conflict": {
"phpunit/phpunit": "<7.0"
@ -1937,31 +1989,32 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.3"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.10"
},
"time": "2022-12-21T15:25:00+00:00"
"time": "2023-03-02T10:25:13+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.4.5",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d"
"reference": "b7dd96a5503919a43b3cd06a2dced9d4252492f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b7dd96a5503919a43b3cd06a2dced9d4252492f2",
"reference": "b7dd96a5503919a43b3cd06a2dced9d4252492f2",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.9.7"
"phpstan/phpstan": "^1.10"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5"
},
@ -1985,29 +2038,29 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.5"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.0"
},
"time": "2023-01-11T14:16:29+00:00"
"time": "2023-02-21T10:17:10+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.23",
"version": "9.2.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c"
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.14",
"nikic/php-parser": "^4.15",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -2022,8 +2075,8 @@
"phpunit/phpunit": "^9.3"
},
"suggest": {
"ext-pcov": "*",
"ext-xdebug": "*"
"ext-pcov": "PHP extension that provides line coverage",
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"type": "library",
"extra": {
@ -2056,7 +2109,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
},
"funding": [
{
@ -2064,7 +2117,7 @@
"type": "github"
}
],
"time": "2022-12-28T12:41:10+00:00"
"time": "2023-03-06T12:58:08+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -2309,16 +2362,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.28",
"version": "9.6.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "954ca3113a03bf780d22f07bf055d883ee04b65e"
"reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e",
"reference": "954ca3113a03bf780d22f07bf055d883ee04b65e",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"shasum": ""
},
"require": {
@ -2351,8 +2404,8 @@
"sebastian/version": "^3.0.2"
},
"suggest": {
"ext-soap": "*",
"ext-xdebug": "*"
"ext-soap": "To be able to generate mocks based on WSDL files",
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"bin": [
"phpunit"
@ -2360,7 +2413,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.5-dev"
"dev-master": "9.6-dev"
}
},
"autoload": {
@ -2391,7 +2444,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5"
},
"funding": [
{
@ -2407,7 +2460,7 @@
"type": "tidelift"
}
],
"time": "2023-01-14T12:32:24+00:00"
"time": "2023-03-09T06:34:10+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2775,16 +2828,16 @@
},
{
"name": "sebastian/environment",
"version": "5.1.4",
"version": "5.1.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
"shasum": ""
},
"require": {
@ -2826,7 +2879,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
},
"funding": [
{
@ -2834,7 +2887,7 @@
"type": "github"
}
],
"time": "2022-04-03T09:37:03+00:00"
"time": "2023-02-03T06:03:51+00:00"
},
{
"name": "sebastian/exporter",
@ -3148,16 +3201,16 @@
},
{
"name": "sebastian/recursion-context",
"version": "4.0.4",
"version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"shasum": ""
},
"require": {
@ -3196,10 +3249,10 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
},
"funding": [
{
@ -3207,7 +3260,7 @@
"type": "github"
}
],
"time": "2020-10-26T13:17:30+00:00"
"time": "2023-02-03T06:07:39+00:00"
},
{
"name": "sebastian/resource-operations",
@ -3266,16 +3319,16 @@
},
{
"name": "sebastian/type",
"version": "3.2.0",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e"
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"shasum": ""
},
"require": {
@ -3310,7 +3363,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/3.2.0"
"source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
},
"funding": [
{
@ -3318,7 +3371,7 @@
"type": "github"
}
],
"time": "2022-09-12T14:47:03+00:00"
"time": "2023-02-03T06:13:03+00:00"
},
{
"name": "sebastian/version",

File diff suppressed because it is too large Load Diff

49988
resources/legacy_recipes.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -37,4 +37,6 @@ define('pocketmine\PATH', dirname(__DIR__) . '/');
define('pocketmine\RESOURCE_PATH', dirname(__DIR__) . '/resources/');
define('pocketmine\BEDROCK_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-data/');
define('pocketmine\LOCALE_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/locale-data/');
define('pocketmine\BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-block-upgrade-schema/');
define('pocketmine\BEDROCK_ITEM_UPGRADE_SCHEMA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-item-upgrade-schema/');
define('pocketmine\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__) . '/vendor/autoload.php');

View File

@ -49,6 +49,7 @@ use function ini_get;
use function ini_set;
use function intdiv;
use function is_array;
use function is_float;
use function is_object;
use function is_resource;
use function is_string;
@ -71,7 +72,7 @@ use const SORT_NUMERIC;
class MemoryManager{
private const DEFAULT_CHECK_RATE = Server::TARGET_TICKS_PER_SECOND;
private const DEFAULT_CONTINUOUS_TRIGGER_RATE = Server::TARGET_TICKS_PER_SECOND * 2;
private const DEEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND;
private const DEFAULT_TICKS_PER_GC = 30 * 60 * Server::TARGET_TICKS_PER_SECOND;
private int $memoryLimit;
private int $globalMemoryLimit;
@ -139,7 +140,7 @@ class MemoryManager{
$this->continuousTrigger = $config->getPropertyBool("memory.continuous-trigger", true);
$this->continuousTriggerRate = $config->getPropertyInt("memory.continuous-trigger-rate", self::DEFAULT_CONTINUOUS_TRIGGER_RATE);
$this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", self::DEEFAULT_TICKS_PER_GC);
$this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", self::DEFAULT_TICKS_PER_GC);
$this->garbageCollectionTrigger = $config->getPropertyBool("memory.garbage-collection.low-memory-trigger", true);
$this->garbageCollectionAsync = $config->getPropertyBool("memory.garbage-collection.collect-async-worker", true);
@ -519,6 +520,8 @@ class MemoryManager{
$data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
}elseif(is_resource($from)){
$data = "(resource) " . print_r($from, true);
}elseif(is_float($from)){
$data = "(float) $from";
}else{
$data = $from;
}

View File

@ -122,7 +122,7 @@ namespace pocketmine {
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") > 0){
if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") >= 0){
$messages[] = "pthreads ^4.0.0 is required, while you have $pthreads_version.";
}
}

View File

@ -43,7 +43,6 @@ use pocketmine\event\player\PlayerCreationEvent;
use pocketmine\event\player\PlayerDataSaveEvent;
use pocketmine\event\player\PlayerLoginEvent;
use pocketmine\event\server\CommandEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\event\server\QueryRegenerateEvent;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Language;
@ -54,13 +53,19 @@ use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\CompressBatchTask;
use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
use pocketmine\network\mcpe\encryption\EncryptionContext;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use pocketmine\network\mcpe\StandardEntityEventBroadcaster;
use pocketmine\network\mcpe\StandardPacketBroadcaster;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\query\DedicatedQueryNetworkInterface;
@ -891,6 +896,9 @@ class Server{
if($this->configGroup->getPropertyInt("network.batch-threshold", 256) >= 0){
$netCompressionThreshold = $this->configGroup->getPropertyInt("network.batch-threshold", 256);
}
if($netCompressionThreshold < 0){
$netCompressionThreshold = null;
}
$netCompressionLevel = $this->configGroup->getPropertyInt("network.compression-level", 6);
if($netCompressionLevel < 1 || $netCompressionLevel > 9){
@ -959,7 +967,7 @@ class Server{
$this->commandMap = new SimpleCommandMap($this);
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes.json"));
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\RESOURCE_PATH, "legacy_recipes.json"));
$this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger);
@ -1166,10 +1174,18 @@ class Server{
return !$anyWorldFailedToLoad;
}
private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery) : bool{
private function startupPrepareConnectableNetworkInterfaces(
string $ip,
int $port,
bool $ipV6,
bool $useQuery,
PacketBroadcaster $packetBroadcaster,
EntityEventBroadcaster $entityEventBroadcaster,
PacketSerializerContext $packetSerializerContext
) : bool{
$prettyIp = $ipV6 ? "[$ip]" : $ip;
try{
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6));
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$ip,
@ -1195,11 +1211,15 @@ class Server{
private function startupPrepareNetworkInterfaces() : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
$packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
$packetBroadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext);
$entityEventBroadcaster = new StandardEntityEventBroadcaster($packetBroadcaster);
if(
!$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery) ||
!$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext) ||
(
$this->configGroup->getConfigBool("enable-ipv6", true) &&
!$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery)
!$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext)
)
){
return false;
@ -1331,46 +1351,10 @@ class Server{
/**
* @param Player[] $players
* @param ClientboundPacket[] $packets
* @deprecated
*/
public function broadcastPackets(array $players, array $packets) : bool{
if(count($packets) === 0){
throw new \InvalidArgumentException("Cannot broadcast empty list of packets");
}
return Timings::$broadcastPackets->time(function() use ($players, $packets) : bool{
/** @var NetworkSession[] $recipients */
$recipients = [];
foreach($players as $player){
if($player->isConnected()){
$recipients[] = $player->getNetworkSession();
}
}
if(count($recipients) === 0){
return false;
}
$ev = new DataPacketSendEvent($recipients, $packets);
$ev->call();
if($ev->isCancelled()){
return false;
}
$recipients = $ev->getTargets();
/** @var PacketBroadcaster[] $broadcasters */
$broadcasters = [];
/** @var NetworkSession[][] $broadcasterTargets */
$broadcasterTargets = [];
foreach($recipients as $recipient){
$broadcaster = $recipient->getBroadcaster();
$broadcasters[spl_object_id($broadcaster)] = $broadcaster;
$broadcasterTargets[spl_object_id($broadcaster)][] = $recipient;
}
foreach($broadcasters as $broadcaster){
$broadcaster->broadcastPackets($broadcasterTargets[spl_object_id($broadcaster)], $packets);
}
return true;
});
return NetworkBroadcastUtils::broadcastPackets($players, $packets);
}
/**
@ -1378,14 +1362,16 @@ class Server{
*
* @param bool|null $sync Compression on the main thread (true) or workers (false). Default is automatic (null).
*/
public function prepareBatch(PacketBatch $stream, Compressor $compressor, ?bool $sync = null) : CompressBatchPromise{
public function prepareBatch(PacketBatch $stream, Compressor $compressor, ?bool $sync = null, ?TimingsHandler $timings = null) : CompressBatchPromise{
$timings ??= Timings::$playerNetworkSendCompress;
try{
Timings::$playerNetworkSendCompress->startTiming();
$timings->startTiming();
$buffer = $stream->getBuffer();
if($sync === null){
$sync = !($this->networkCompressionAsync && $compressor->willCompress($buffer));
$threshold = $compressor->getCompressionThreshold();
$sync = !$this->networkCompressionAsync || $threshold === null || strlen($stream->getBuffer()) < $threshold;
}
$promise = new CompressBatchPromise();
@ -1398,7 +1384,7 @@ class Server{
return $promise;
}finally{
Timings::$playerNetworkSendCompress->stopTiming();
$timings->stopTiming();
}
}

View File

@ -31,9 +31,9 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.13.0-BETA1";
public const BASE_VERSION = "4.18.0-ALPHA2";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "beta";
public const BUILD_CHANNEL = "alpha";
private function __construct(){
//NOOP

View File

@ -34,7 +34,7 @@ class RedMushroomBlock extends Opaque{
protected MushroomBlockType $mushroomBlockType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->mushroomBlockType = MushroomBlockType::PORES();
$this->mushroomBlockType = MushroomBlockType::ALL_CAP();
parent::__construct($idInfo, $name, $breakInfo);
}
@ -42,6 +42,11 @@ class RedMushroomBlock extends Opaque{
return MushroomBlockTypeIdMap::getInstance()->toId($this->mushroomBlockType);
}
protected function writeStateToItemMeta() : int{
//these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative)
return BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_CAP;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$type = MushroomBlockTypeIdMap::getInstance()->fromId($stateMeta);
if($type === null){

View File

@ -45,6 +45,12 @@ class CraftingManager{
*/
protected $shapelessRecipes = [];
/**
* @var CraftingRecipe[]
* @phpstan-var array<int, CraftingRecipe>
*/
private array $craftingRecipeIndex = [];
/**
* @var FurnaceRecipeManager[]
* @phpstan-var array<int, FurnaceRecipeManager>
@ -153,6 +159,18 @@ class CraftingManager{
return $this->shapedRecipes;
}
/**
* @return CraftingRecipe[]
* @phpstan-return array<int, CraftingRecipe>
*/
public function getCraftingRecipeIndex() : array{
return $this->craftingRecipeIndex;
}
public function getCraftingRecipeFromIndex(int $index) : ?CraftingRecipe{
return $this->craftingRecipeIndex[$index] ?? null;
}
public function getFurnaceRecipeManager(FurnaceType $furnaceType) : FurnaceRecipeManager{
return $this->furnaceRecipeManagers[$furnaceType->id()];
}
@ -175,6 +193,7 @@ class CraftingManager{
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
$this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
$this->craftingRecipeIndex[] = $recipe;
foreach($this->recipeRegisteredCallbacks as $callback){
$callback();
@ -183,6 +202,7 @@ class CraftingManager{
public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{
$this->shapelessRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
$this->craftingRecipeIndex[] = $recipe;
foreach($this->recipeRegisteredCallbacks as $callback){
$callback();

View File

@ -0,0 +1,50 @@
<?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\data\bedrock;
use const pocketmine\BEDROCK_DATA_PATH;
final class BedrockDataFiles{
private function __construct(){
//NOOP
}
public const BANNER_PATTERNS_JSON = BEDROCK_DATA_PATH . '/banner_patterns.json';
public const BIOME_DEFINITIONS_NBT = BEDROCK_DATA_PATH . '/biome_definitions.nbt';
public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt';
public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json';
public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json';
public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json';
public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt';
public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json';
public const CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.json';
public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json';
public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt';
public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json';
public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json';
public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json';
public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin';
public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json';
public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';
}

View File

@ -24,12 +24,11 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Symfony\Component\Filesystem\Path;
final class LegacyBiomeIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'biome_id_map.json'));
parent::__construct(BedrockDataFiles::BIOME_ID_MAP_JSON);
}
}

View File

@ -30,6 +30,6 @@ final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'block_id_map.json'));
parent::__construct(Path::join(\pocketmine\BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH, 'block_legacy_id_map.json'));
}
}

View File

@ -24,12 +24,11 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\utils\SingletonTrait;
use Symfony\Component\Filesystem\Path;
final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'entity_id_map.json'));
parent::__construct(BedrockDataFiles::ENTITY_ID_MAP_JSON);
}
}

View File

@ -30,6 +30,6 @@ final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{
use SingletonTrait;
public function __construct(){
parent::__construct(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'item_id_map.json'));
parent::__construct(Path::join(\pocketmine\BEDROCK_ITEM_UPGRADE_SCHEMA_PATH, 'item_legacy_id_map.json'));
}
}

View File

@ -44,6 +44,8 @@ use pocketmine\nbt\tag\DoubleTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\AddActorPacket;
use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
@ -244,7 +246,7 @@ abstract class Entity{
if($nbt !== null){
$this->motion = EntityDataHelper::parseVec3($nbt, self::TAG_MOTION, true);
}else{
$this->motion = new Vector3(0, 0, 0);
$this->motion = Vector3::zero();
}
$this->resetLastMovements();
@ -785,7 +787,7 @@ abstract class Entity{
$this->spawnTo($player);
}
}else{
$this->server->broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
$this->id,
$this->getOffsetPosition($this->location),
$this->location->pitch,
@ -800,7 +802,16 @@ abstract class Entity{
}
protected function broadcastMotion() : void{
$this->server->broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]);
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]);
}
public function getGravity() : float{
return $this->gravity;
}
public function setGravity(float $gravity) : void{
Utils::checkFloatNotInfOrNaN("gravity", $gravity);
$this->gravity = $gravity;
}
public function hasGravity() : bool{
@ -1128,6 +1139,7 @@ abstract class Entity{
$this->blocksAround = null;
Timings::$entityMove->startTiming();
Timings::$entityMoveCollision->startTiming();
$wantedX = $dx;
$wantedY = $dy;
@ -1212,6 +1224,7 @@ abstract class Entity{
$this->boundingBox = $moveBB;
}
Timings::$entityMoveCollision->stopTiming();
$this->location = new Location(
($this->boundingBox->minX + $this->boundingBox->maxX) / 2,
@ -1528,7 +1541,7 @@ abstract class Entity{
$id = spl_object_id($player);
if(isset($this->hasSpawned[$id])){
if($send){
$player->getNetworkSession()->onEntityRemoved($this);
$player->getNetworkSession()->getEntityEventBroadcaster()->onEntityRemoved([$player->getNetworkSession()], $this);
}
unset($this->hasSpawned[$id]);
}
@ -1539,9 +1552,11 @@ abstract class Entity{
* player moves, viewers will once again be able to see the entity.
*/
public function despawnFromAll() : void{
foreach($this->hasSpawned as $player){
$this->despawnFrom($player);
}
NetworkBroadcastUtils::broadcastEntityEvent(
$this->hasSpawned,
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEntityRemoved($recipients, $this)
);
$this->hasSpawned = [];
}
/**
@ -1615,9 +1630,7 @@ abstract class Entity{
$targets = $targets ?? $this->hasSpawned;
$data = $data ?? $this->getAllNetworkData();
foreach($targets as $p){
$p->getNetworkSession()->syncActorData($this, $data);
}
NetworkBroadcastUtils::broadcastEntityEvent($targets, fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->syncActorData($recipients, $this, $data));
}
/**
@ -1671,7 +1684,7 @@ abstract class Entity{
* @param Player[]|null $targets
*/
public function broadcastAnimation(Animation $animation, ?array $targets = null) : void{
$this->server->broadcastPackets($targets ?? $this->getViewers(), $animation->encode());
NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $animation->encode());
}
/**
@ -1680,7 +1693,7 @@ abstract class Entity{
*/
public function broadcastSound(Sound $sound, ?array $targets = null) : void{
if(!$this->silent){
$this->server->broadcastPackets($targets ?? $this->getViewers(), $sound->encode($this->location));
NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $sound->encode($this->location));
}
}

View File

@ -80,7 +80,7 @@ final class EntityDataHelper{
public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{
$pos = $nbt->getTag($tagName);
if($pos === null && $optional){
return new Vector3(0, 0, 0);
return Vector3::zero();
}
if(!($pos instanceof ListTag) || ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){
throw new SavedDataLoadingException("'$tagName' should be a List<Double> or List<Float>");

View File

@ -47,9 +47,13 @@ use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\types\AbilitiesData;
use pocketmine\network\mcpe\protocol\types\AbilitiesLayer;
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
use pocketmine\network\mcpe\protocol\types\DeviceOS;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@ -60,7 +64,6 @@ use pocketmine\network\mcpe\protocol\types\GameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\types\UpdateAbilitiesPacketLayer;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player;
use pocketmine\utils\Limits;
@ -173,7 +176,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
* @param Player[]|null $targets
*/
public function sendSkin(?array $targets = null) : void{
$this->server->broadcastPackets($targets ?? $this->hasSpawned, [
NetworkBroadcastUtils::broadcastPackets($targets ?? $this->hasSpawned, [
PlayerSkinPacket::create($this->getUniqueId(), "", "", SkinAdapterSingleton::get()->toSkinData($this->skin))
]);
}
@ -188,9 +191,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
}
public function emote(string $emoteId) : void{
foreach($this->getViewers() as $player){
$player->getNetworkSession()->onEmote($this, $emoteId);
}
NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEmote($recipients, $this, $emoteId)
);
}
public function getHungerManager() : HungerManager{
@ -269,11 +273,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
$this->xpManager = new ExperienceManager($this);
$this->inventory = new PlayerInventory($this);
$syncHeldItem = function() : void{
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobMainHandItemChange($this);
}
};
$syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this)
);
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot, Item $unused2) use ($syncHeldItem) : void{
if($slot === $this->inventory->getHeldItemIndex()){
@ -314,11 +317,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if($offHand !== null){
$this->offHandInventory->setItem(0, Item::nbtDeserialize($offHand));
}
$this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(function() : void{
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobOffHandItemChange($this);
}
}));
$this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobOffHandItemChange($recipients, $this)
)));
$enderChestInventoryTag = $nbt->getListTag(self::TAG_ENDER_CHEST_INVENTORY);
if($enderChestInventoryTag !== null){
@ -332,11 +334,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
}
$this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0));
$this->inventory->getHeldItemIndexChangeListeners()->add(function(int $oldIndex) : void{
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobMainHandItemChange($this);
}
});
$this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this)
));
$this->hungerManager->setFood((float) $nbt->getInt(self::TAG_FOOD_LEVEL, (int) $this->hungerManager->getFood()));
$this->hungerManager->setExhaustion($nbt->getFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion()));
@ -489,11 +490,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
}
protected function sendSpawnPacket(Player $player) : void{
$networkSession = $player->getNetworkSession();
if(!($this instanceof Player)){
$player->getNetworkSession()->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))]));
$networkSession->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))]));
}
$player->getNetworkSession()->sendDataPacket(AddPlayerPacket::create(
$networkSession->sendDataPacket(AddPlayerPacket::create(
$this->getUniqueId(),
$this->getName(),
$this->getId(),
@ -507,14 +509,14 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
GameMode::SURVIVAL,
$this->getAllNetworkData(),
new PropertySyncData([], []),
UpdateAbilitiesPacket::create(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() /* TODO: this should be unique ID */, [
new UpdateAbilitiesPacketLayer(
UpdateAbilitiesPacketLayer::LAYER_BASE,
array_fill(0, UpdateAbilitiesPacketLayer::NUMBER_OF_ABILITIES, false),
UpdateAbilitiesPacket::create(new AbilitiesData(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() /* TODO: this should be unique ID */, [
new AbilitiesLayer(
AbilitiesLayer::LAYER_BASE,
array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, false),
0.0,
0.0
)
]),
])),
[], //TODO: entity links
"", //device ID (we intentionally don't send this - secvuln)
DeviceOS::UNKNOWN //we intentionally don't send this (secvuln)
@ -523,11 +525,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
//TODO: Hack for MCPE 1.2.13: DATA_NAMETAG is useless in AddPlayerPacket, so it has to be sent separately
$this->sendData([$player], [EntityMetadataProperties::NAMETAG => new StringMetadataProperty($this->getNameTag())]);
$player->getNetworkSession()->onMobArmorChange($this);
$player->getNetworkSession()->onMobOffHandItemChange($this);
$entityEventBroadcaster = $networkSession->getEntityEventBroadcaster();
$entityEventBroadcaster->onMobArmorChange([$networkSession], $this);
$entityEventBroadcaster->onMobOffHandItemChange([$networkSession], $this);
if(!($this instanceof Player)){
$player->getNetworkSession()->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($this->uuid)]));
$networkSession->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($this->uuid)]));
}
}

View File

@ -50,6 +50,8 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\FloatTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\ShortTag;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
@ -143,13 +145,10 @@ abstract class Living extends Entity{
$this->armorInventory = new ArmorInventory($this);
//TODO: load/save armor inventory contents
$this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(
function(Inventory $unused) : void{
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onMobArmorChange($this);
}
}
));
$this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
)));
$health = $this->getMaxHealth();
@ -850,7 +849,8 @@ abstract class Living extends Entity{
protected function sendSpawnPacket(Player $player) : void{
parent::sendSpawnPacket($player);
$player->getNetworkSession()->onMobArmorChange($this);
$networkSession = $player->getNetworkSession();
$networkSession->getEntityEventBroadcaster()->onMobArmorChange([$networkSession], $this);
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{

View File

@ -35,10 +35,13 @@ use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\AddItemActorPacket;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\player\Player;
use pocketmine\timings\Timings;
use function max;
class ItemEntity extends Entity{
@ -111,13 +114,23 @@ class ItemEntity extends Entity{
return false;
}
$hasUpdate = parent::entityBaseTick($tickDiff);
Timings::$itemEntityBaseTick->startTiming();
try{
if(!$this->isFlaggedForDespawn() && $this->pickupDelay !== self::NEVER_DESPAWN){ //Infinite delay
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
$hasUpdate = parent::entityBaseTick($tickDiff);
if($this->isFlaggedForDespawn()){
return $hasUpdate;
}
if($this->pickupDelay !== self::NEVER_DESPAWN && $this->pickupDelay > 0){ //Infinite delay
$hasUpdate = true;
$this->pickupDelay -= $tickDiff;
if($this->pickupDelay < 0){
$this->pickupDelay = 0;
}
}
if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){
$mergeable = [$this]; //in case the merge target ends up not being this
$mergeTarget = $this;
@ -140,20 +153,24 @@ class ItemEntity extends Entity{
}
}
$this->despawnDelay -= $tickDiff;
if($this->despawnDelay <= 0){
$ev = new ItemDespawnEvent($this);
$ev->call();
if($ev->isCancelled()){
$this->despawnDelay = self::DEFAULT_DESPAWN_DELAY;
}else{
$this->flagForDespawn();
$hasUpdate = true;
if(!$this->isFlaggedForDespawn() && $this->despawnDelay !== self::NEVER_DESPAWN){
$hasUpdate = true;
$this->despawnDelay -= $tickDiff;
if($this->despawnDelay <= 0){
$ev = new ItemDespawnEvent($this);
$ev->call();
if($ev->isCancelled()){
$this->despawnDelay = self::DEFAULT_DESPAWN_DELAY;
}else{
$this->flagForDespawn();
}
}
}
}
return $hasUpdate;
return $hasUpdate;
}finally{
Timings::$itemEntityBaseTick->stopTiming();
}
}
/**
@ -320,9 +337,10 @@ class ItemEntity extends Entity{
return;
}
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onPlayerPickUpItem($player, $this);
}
NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this)
);
$inventory = $ev->getInventory();
if($inventory !== null){

View File

@ -33,6 +33,8 @@ use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\VanillaItems;
use pocketmine\math\RayTraceResult;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
@ -196,9 +198,10 @@ class Arrow extends Projectile{
return;
}
foreach($this->getViewers() as $viewer){
$viewer->getNetworkSession()->onPlayerPickUpItem($player, $this);
}
NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this)
);
$ev->getInventory()?->addItem($ev->getItem());
$this->flagForDespawn();

View File

@ -175,7 +175,8 @@ abstract class Projectile extends Entity{
protected function move(float $dx, float $dy, float $dz) : void{
$this->blocksAround = null;
Timings::$entityMove->startTiming();
Timings::$projectileMove->startTiming();
Timings::$projectileMoveRayTrace->startTiming();
$start = $this->location->asVector3();
$end = $start->add($dx, $dy, $dz);
@ -221,6 +222,8 @@ abstract class Projectile extends Entity{
}
}
Timings::$projectileMoveRayTrace->stopTiming();
$this->location = Location::fromObject(
$end,
$this->location->world,
@ -252,7 +255,7 @@ abstract class Projectile extends Entity{
}
$this->isCollided = $this->onGround = true;
$this->motion = new Vector3(0, 0, 0);
$this->motion = Vector3::zero();
}else{
$this->isCollided = $this->onGround = false;
$this->blockHit = null;
@ -268,7 +271,7 @@ abstract class Projectile extends Entity{
$this->getWorld()->onEntityMoved($this);
$this->checkBlockIntersections();
Timings::$entityMove->stopTiming();
Timings::$projectileMove->stopTiming();
}
/**

View File

@ -59,7 +59,7 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
$this->player = $player;
$this->item = $item;
$this->blockTouched = $block;
$this->touchVector = $touchVector ?? new Vector3(0, 0, 0);
$this->touchVector = $touchVector ?? Vector3::zero();
$this->blockFace = $face;
$this->action = $action;
}

View File

@ -69,8 +69,8 @@ class PlayerPreLoginEvent extends Event implements Cancellable{
/**
* Returns an object containing self-proclaimed information about the connecting player.
* WARNING: THE PLAYER IS NOT VERIFIED DURING THIS EVENT. At this point, it's unknown if the player is real or a
* hacker.
* WARNING: THE PLAYER IS NOT VERIFIED DURING THIS EVENT. At this point, this could be a hacker posing as another
* player.
*/
public function getPlayerInfo() : PlayerInfo{
return $this->playerInfo;
@ -109,7 +109,7 @@ class PlayerPreLoginEvent extends Event implements Cancellable{
}
/**
* Sets a reason to disallow the player to continue continue authenticating, with a message.
* Sets a reason to disallow the player to continue authenticating, with a message.
* This can also be used to change kick messages for already-set flags.
*/
public function setKickReason(int $flag, string $message) : void{

View File

@ -0,0 +1,54 @@
<?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\event\server;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\network\mcpe\NetworkSession;
/**
* Called before a packet is decoded and handled by the network session.
* Cancelling this event will drop the packet without decoding it, minimizing wasted CPU time.
*/
class DataPacketDecodeEvent extends ServerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
private NetworkSession $origin,
private int $packetId,
private string $packetBuffer
){}
public function getOrigin() : NetworkSession{
return $this->origin;
}
public function getPacketId() : int{
return $this->packetId;
}
public function getPacketBuffer() : string{
return $this->packetBuffer;
}
}

View File

@ -348,7 +348,7 @@ abstract class BaseInventory implements Inventory{
if($invManager === null){
continue;
}
$invManager->syncSlot($this, $index);
$invManager->onSlotChange($this, $index);
}
}

View File

@ -37,7 +37,7 @@ final class CreativeInventory{
private array $creative = [];
private function __construct(){
$creativeItems = json_decode(Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, "creativeitems.json")), true);
$creativeItems = json_decode(Filesystem::fileGetContents(Path::join(\pocketmine\RESOURCE_PATH, "legacy_creativeitems.json")), true);
foreach($creativeItems as $data){
$item = Item::jsonDeserialize($data);

View File

@ -60,9 +60,11 @@ class CraftingTransaction extends InventoryTransaction{
private CraftingManager $craftingManager;
public function __construct(Player $source, CraftingManager $craftingManager, array $actions = []){
public function __construct(Player $source, CraftingManager $craftingManager, array $actions = [], ?CraftingRecipe $recipe = null, ?int $repetitions = null){
parent::__construct($source, $actions);
$this->craftingManager = $craftingManager;
$this->recipe = $recipe;
$this->repetitions = $repetitions;
}
/**
@ -123,6 +125,18 @@ class CraftingTransaction extends InventoryTransaction{
return $iterations;
}
private function validateRecipe(CraftingRecipe $recipe, ?int $expectedRepetitions) : int{
//compute number of times recipe was crafted
$repetitions = $this->matchRecipeItems($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()), false);
if($expectedRepetitions !== null && $repetitions !== $expectedRepetitions){
throw new TransactionValidationException("Expected $expectedRepetitions repetitions, got $repetitions");
}
//assert that $repetitions x recipe ingredients should be consumed
$this->matchRecipeItems($this->inputs, $recipe->getIngredientList(), true, $repetitions);
return $repetitions;
}
public function validate() : void{
$this->squashDuplicateSlotChanges();
if(count($this->actions) < 1){
@ -131,25 +145,24 @@ class CraftingTransaction extends InventoryTransaction{
$this->matchItems($this->outputs, $this->inputs);
$failed = 0;
foreach($this->craftingManager->matchRecipeByOutputs($this->outputs) as $recipe){
try{
//compute number of times recipe was crafted
$this->repetitions = $this->matchRecipeItems($this->outputs, $recipe->getResultsFor($this->source->getCraftingGrid()), false);
//assert that $repetitions x recipe ingredients should be consumed
$this->matchRecipeItems($this->inputs, $recipe->getIngredientList(), true, $this->repetitions);
//Success!
$this->recipe = $recipe;
break;
}catch(TransactionValidationException $e){
//failed
++$failed;
}
}
if($this->recipe === null){
throw new TransactionValidationException("Unable to match a recipe to transaction (tried to match against $failed recipes)");
$failed = 0;
foreach($this->craftingManager->matchRecipeByOutputs($this->outputs) as $recipe){
try{
$this->repetitions = $this->validateRecipe($recipe, $this->repetitions);
$this->recipe = $recipe;
break;
}catch(TransactionValidationException $e){
//failed
++$failed;
}
}
if($this->recipe === null){
throw new TransactionValidationException("Unable to match a recipe to transaction (tried to match against $failed recipes)");
}
}else{
$this->repetitions = $this->validateRecipe($this->recipe, $this->repetitions);
}
}

View File

@ -50,6 +50,10 @@ final class TransactionBuilderInventory extends BaseInventory{
$this->changedSlots = new \SplFixedArray($this->actualInventory->getSize());
}
public function getActualInventory() : Inventory{
return $this->actualInventory;
}
protected function internalSetContents(array $items) : void{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
if(!isset($items[$i])){

View File

@ -1629,6 +1629,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_LOGINTIMEOUT, []);
}
public static function pocketmine_disconnect_error_respawn() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_ERROR_RESPAWN, []);
}
public static function pocketmine_disconnect_incompatibleProtocol(Translatable|string $param0) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_DISCONNECT_INCOMPATIBLEPROTOCOL, [
0 => $param0,

View File

@ -354,6 +354,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_DISCONNECT_ERROR_BADPACKET = "pocketmine.disconnect.error.badPacket";
public const POCKETMINE_DISCONNECT_ERROR_INTERNAL = "pocketmine.disconnect.error.internal";
public const POCKETMINE_DISCONNECT_ERROR_LOGINTIMEOUT = "pocketmine.disconnect.error.loginTimeout";
public const POCKETMINE_DISCONNECT_ERROR_RESPAWN = "pocketmine.disconnect.error.respawn";
public const POCKETMINE_DISCONNECT_INCOMPATIBLEPROTOCOL = "pocketmine.disconnect.incompatibleProtocol";
public const POCKETMINE_DISCONNECT_INVALIDSESSION = "pocketmine.disconnect.invalidSession";
public const POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE = "pocketmine.disconnect.invalidSession.badSignature";

View File

@ -33,6 +33,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
use pocketmine\scheduler\AsyncTask;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\FastChunkSerializer;
@ -72,7 +73,10 @@ class ChunkRequestTask extends AsyncTask{
$subCount = ChunkSerializer::getSubChunkCount($chunk) + ChunkSerializer::LOWER_PADDING_SIZE;
$encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
$payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles);
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
$stream = new BinaryStream();
PacketBatch::encodePackets($stream, $encoderContext, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload)]);
$this->setResult($this->compressor->compress($stream->getBuffer()));
}
public function onError() : void{

View File

@ -25,7 +25,7 @@ namespace pocketmine\network\mcpe;
use pocketmine\inventory\Inventory;
final class ComplexWindowMapEntry{
final class ComplexInventoryMapEntry{
/**
* @var int[]

View File

@ -0,0 +1,93 @@
<?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;
use pocketmine\entity\Attribute;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
/**
* This class allows broadcasting entity events to many viewers on the server network.
*/
interface EntityEventBroadcaster{
/**
* @param NetworkSession[] $recipients
* @param Attribute[] $attributes
*/
public function syncAttributes(array $recipients, Living $entity, array $attributes) : void;
/**
* @param NetworkSession[] $recipients
* @param MetadataProperty[] $properties
*
* @phpstan-param array<int, MetadataProperty> $properties
*/
public function syncActorData(array $recipients, Entity $entity, array $properties) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onEntityEffectAdded(array $recipients, Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onEntityRemoved(array $recipients, Entity $entity) : void;
/**
* TODO: expand this to more than just humans
*
* @param NetworkSession[] $recipients
*/
public function onMobMainHandItemChange(array $recipients,Human $mob) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onMobOffHandItemChange(array $recipients, Human $mob) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onMobArmorChange(array $recipients, Living $mob) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onPickUpItem(array $recipients, Entity $collector, Entity $pickedUp) : void;
/**
* @param NetworkSession[] $recipients
*/
public function onEmote(array $recipients, Human $from, string $emoteId) : void;
}

View File

@ -38,7 +38,6 @@ use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\TypeConversionException;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
@ -51,6 +50,7 @@ use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
@ -59,9 +59,11 @@ use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
use function array_map;
use function array_keys;
use function array_search;
use function count;
use function get_class;
use function implode;
use function is_int;
use function max;
use function spl_object_id;
@ -70,26 +72,25 @@ use function spl_object_id;
* @phpstan-type ContainerOpenClosure \Closure(int $id, Inventory $inventory) : (list<ClientboundPacket>|null)
*/
class InventoryManager{
/** @var Inventory[] */
private array $windowMap = [];
/**
* @var ComplexWindowMapEntry[]
* @phpstan-var array<int, ComplexWindowMapEntry>
* @var InventoryManagerEntry[] spl_object_id(Inventory) => InventoryManagerEntry
* @phpstan-var array<int, InventoryManagerEntry>
*/
private array $complexWindows = [];
private array $inventories = [];
/**
* @var ComplexWindowMapEntry[]
* @phpstan-var array<int, ComplexWindowMapEntry>
* @var Inventory[] network window ID => Inventory
* @phpstan-var array<int, Inventory>
*/
private array $complexSlotToWindowMap = [];
private array $networkIdToInventoryMap = [];
/**
* @var ComplexInventoryMapEntry[] net slot ID => ComplexWindowMapEntry
* @phpstan-var array<int, ComplexInventoryMapEntry>
*/
private array $complexSlotToInventoryMap = [];
private int $lastInventoryNetworkId = ContainerIds::FIRST;
/**
* @var Item[][]
* @phpstan-var array<int, array<int, Item>>
*/
private array $initiatedSlotChanges = [];
private int $clientSelectedHotbarSlot = -1;
/** @phpstan-var ObjectSet<ContainerOpenClosure> */
@ -99,6 +100,11 @@ class InventoryManager{
/** @phpstan-var \Closure() : void */
private ?\Closure $pendingOpenWindowCallback = null;
private int $nextItemStackId = 1;
private ?int $currentItemStackRequestId = null;
private bool $fullSyncRequested = false;
public function __construct(
private Player $player,
private NetworkSession $session
@ -117,14 +123,27 @@ class InventoryManager{
});
}
private function associateIdWithInventory(int $id, Inventory $inventory) : void{
$this->networkIdToInventoryMap[$id] = $inventory;
}
private function getNewWindowId() : int{
$this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % ContainerIds::LAST);
return $this->lastInventoryNetworkId;
}
private function add(int $id, Inventory $inventory) : void{
$this->windowMap[$id] = $inventory;
if(isset($this->inventories[spl_object_id($inventory)])){
throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked");
}
$this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry($inventory);
$this->associateIdWithInventory($id, $inventory);
}
private function addDynamic(Inventory $inventory) : int{
$this->lastInventoryNetworkId = max(ContainerIds::FIRST, ($this->lastInventoryNetworkId + 1) % ContainerIds::LAST);
$this->add($this->lastInventoryNetworkId, $inventory);
return $this->lastInventoryNetworkId;
$id = $this->getNewWindowId();
$this->add($id, $inventory);
return $id;
}
/**
@ -132,26 +151,45 @@ class InventoryManager{
* @phpstan-param array<int, int>|int $slotMap
*/
private function addComplex(array|int $slotMap, Inventory $inventory) : void{
$entry = new ComplexWindowMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap);
$this->complexWindows[spl_object_id($inventory)] = $entry;
foreach($entry->getSlotMap() as $netSlot => $coreSlot){
$this->complexSlotToWindowMap[$netSlot] = $entry;
if(isset($this->inventories[spl_object_id($inventory)])){
throw new \InvalidArgumentException("Inventory " . get_class($inventory) . " is already tracked");
}
$complexSlotMap = new ComplexInventoryMapEntry($inventory, is_int($slotMap) ? [$slotMap => 0] : $slotMap);
$this->inventories[spl_object_id($inventory)] = new InventoryManagerEntry(
$inventory,
$complexSlotMap
);
foreach($complexSlotMap->getSlotMap() as $netSlot => $coreSlot){
$this->complexSlotToInventoryMap[$netSlot] = $complexSlotMap;
}
}
/**
* @param int[]|int $slotMap
* @phpstan-param array<int, int>|int $slotMap
*/
private function addComplexDynamic(array|int $slotMap, Inventory $inventory) : int{
$this->addComplex($slotMap, $inventory);
$id = $this->getNewWindowId();
$this->associateIdWithInventory($id, $inventory);
return $id;
}
private function remove(int $id) : void{
$inventory = $this->windowMap[$id];
$splObjectId = spl_object_id($inventory);
unset($this->windowMap[$id], $this->initiatedSlotChanges[$id], $this->complexWindows[$splObjectId]);
foreach($this->complexSlotToWindowMap as $netSlot => $entry){
if($entry->getInventory() === $inventory){
unset($this->complexSlotToWindowMap[$netSlot]);
$inventory = $this->networkIdToInventoryMap[$id];
unset($this->networkIdToInventoryMap[$id]);
if($this->getWindowId($inventory) === null){
unset($this->inventories[spl_object_id($inventory)]);
foreach($this->complexSlotToInventoryMap as $netSlot => $entry){
if($entry->getInventory() === $inventory){
unset($this->complexSlotToInventoryMap[$netSlot]);
}
}
}
}
public function getWindowId(Inventory $inventory) : ?int{
return ($id = array_search($inventory, $this->windowMap, true)) !== false ? $id : null;
return ($id = array_search($inventory, $this->networkIdToInventoryMap, true)) !== false ? $id : null;
}
public function getCurrentWindowId() : int{
@ -159,28 +197,33 @@ class InventoryManager{
}
/**
* @phpstan-return array{Inventory, int}
* @phpstan-return array{Inventory, int}|null
*/
public function locateWindowAndSlot(int $windowId, int $netSlotId) : ?array{
if($windowId === ContainerIds::UI){
$entry = $this->complexSlotToWindowMap[$netSlotId] ?? null;
$entry = $this->complexSlotToInventoryMap[$netSlotId] ?? null;
if($entry === null){
return null;
}
$coreSlotId = $entry->mapNetToCore($netSlotId);
return $coreSlotId !== null ? [$entry->getInventory(), $coreSlotId] : null;
}
if(isset($this->windowMap[$windowId])){
return [$this->windowMap[$windowId], $netSlotId];
if(isset($this->networkIdToInventoryMap[$windowId])){
return [$this->networkIdToInventoryMap[$windowId], $netSlotId];
}
return null;
}
public function onTransactionStart(InventoryTransaction $tx) : void{
private function addPredictedSlotChange(Inventory $inventory, int $slot, ItemStack $item) : void{
$this->inventories[spl_object_id($inventory)]->predictions[$slot] = $item;
}
public function addTransactionPredictedSlotChanges(InventoryTransaction $tx) : void{
foreach($tx->getActions() as $action){
if($action instanceof SlotChangeAction && ($windowId = $this->getWindowId($action->getInventory())) !== null){
//in some cases the inventory might not have a window ID, but still be referenced by a transaction (e.g. crafting grid changes), so we can't unconditionally record the change here or we might leak things
$this->initiatedSlotChanges[$windowId][$action->getSlot()] = $action->getTargetItem();
if($action instanceof SlotChangeAction){
//TODO: ItemStackRequestExecutor can probably build these predictions with much lower overhead
$itemStack = TypeConverter::getInstance()->coreItemStackToNet($action->getTargetItem());
$this->addPredictedSlotChange($action->getInventory(), $action->getSlot(), $itemStack);
}
}
}
@ -189,22 +232,34 @@ class InventoryManager{
* @param NetworkInventoryAction[] $networkInventoryActions
* @throws PacketHandlingException
*/
public function addPredictedSlotChanges(array $networkInventoryActions) : void{
public function addRawPredictedSlotChanges(array $networkInventoryActions) : void{
foreach($networkInventoryActions as $action){
if($action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && (
isset($this->windowMap[$action->windowId]) ||
($action->windowId === ContainerIds::UI && isset($this->complexSlotToWindowMap[$action->inventorySlot]))
)){
try{
$item = TypeConverter::getInstance()->netItemStackToCore($action->newItem->getItemStack());
}catch(TypeConversionException $e){
throw new PacketHandlingException($e->getMessage(), 0, $e);
}
$this->initiatedSlotChanges[$action->windowId][$action->inventorySlot] = $item;
if($action->sourceType !== NetworkInventoryAction::SOURCE_CONTAINER){
continue;
}
//legacy transactions should not modify or predict anything other than these inventories, since these are
//the only ones accessible when not in-game (ItemStackRequest is used for everything else)
if(match($action->windowId){
ContainerIds::INVENTORY, ContainerIds::OFFHAND, ContainerIds::ARMOR => false,
default => true
}){
throw new PacketHandlingException("Legacy transactions cannot predict changes to inventory with ID " . $action->windowId);
}
$info = $this->locateWindowAndSlot($action->windowId, $action->inventorySlot);
if($info === null){
continue;
}
[$inventory, $slot] = $info;
$this->addPredictedSlotChange($inventory, $slot, $action->newItem->getItemStack());
}
}
public function setCurrentItemStackRequestId(?int $id) : void{
$this->currentItemStackRequestId = $id;
}
/**
* When the server initiates a window close, it does so by sending a ContainerClose to the client, which causes the
* client to behave as if it initiated the close itself. It responds by sending a ContainerClose back to the server,
@ -248,9 +303,10 @@ class InventoryManager{
$this->onCurrentWindowRemove();
$this->openWindowDeferred(function() use ($inventory) : void{
$windowId = $this->addDynamic($inventory);
if(($slotMap = $this->createComplexSlotMapping($inventory)) !== null){
$this->addComplex($slotMap, $inventory);
$windowId = $this->addComplexDynamic($slotMap, $inventory);
}else{
$windowId = $this->addDynamic($inventory);
}
foreach($this->containerOpenCallbacks as $callback){
@ -304,7 +360,8 @@ class InventoryManager{
$this->onCurrentWindowRemove();
$this->openWindowDeferred(function() : void{
$windowId = $this->addDynamic($this->player->getInventory());
$windowId = $this->getNewWindowId();
$this->associateIdWithInventory($windowId, $this->player->getInventory());
$this->session->sendDataPacket(ContainerOpenPacket::entityInv(
$windowId,
@ -315,7 +372,7 @@ class InventoryManager{
}
public function onCurrentWindowRemove() : void{
if(isset($this->windowMap[$this->lastInventoryNetworkId])){
if(isset($this->networkIdToInventoryMap[$this->lastInventoryNetworkId])){
$this->remove($this->lastInventoryNetworkId);
$this->session->sendDataPacket(ContainerClosePacket::create($this->lastInventoryNetworkId, true));
if($this->pendingCloseWindowId !== null){
@ -327,7 +384,7 @@ class InventoryManager{
public function onClientRemoveWindow(int $id) : void{
if($id === $this->lastInventoryNetworkId){
if(isset($this->windowMap[$id]) && $id !== $this->pendingCloseWindowId){
if(isset($this->networkIdToInventoryMap[$id]) && $id !== $this->pendingCloseWindowId){
$this->remove($id);
$this->player->removeCurrentWindow();
}
@ -349,96 +406,149 @@ class InventoryManager{
}
}
public function syncSlot(Inventory $inventory, int $slot) : void{
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
if($slotMap !== null){
$windowId = ContainerIds::UI;
$netSlot = $slotMap->mapCoreToNet($slot) ?? null;
public function onSlotChange(Inventory $inventory, int $slot) : void{
$currentItem = TypeConverter::getInstance()->coreItemStackToNet($inventory->getItem($slot));
$inventoryEntry = $this->inventories[spl_object_id($inventory)];
$clientSideItem = $inventoryEntry->predictions[$slot] ?? null;
if($clientSideItem === null || !$clientSideItem->equals($currentItem)){
//no prediction or incorrect - do not associate this with the currently active itemstack request
$this->trackItemStack($inventoryEntry, $slot, $currentItem, null);
$inventoryEntry->pendingSyncs[$slot] = $currentItem;
}else{
$windowId = $this->getWindowId($inventory);
//correctly predicted - associate the change with the currently active itemstack request
$this->trackItemStack($inventoryEntry, $slot, $currentItem, $this->currentItemStackRequestId);
}
unset($inventoryEntry->predictions[$slot]);
}
public function syncSlot(Inventory $inventory, int $slot, ItemStack $itemStack) : void{
$entry = $this->inventories[spl_object_id($inventory)] ?? null;
if($entry === null){
throw new \LogicException("Cannot sync an untracked inventory");
}
$itemStackInfo = $entry->itemStackInfos[$slot];
if($itemStackInfo === null){
throw new \LogicException("Cannot sync an untracked inventory slot");
}
if($entry->complexSlotMap !== null){
$windowId = ContainerIds::UI;
$netSlot = $entry->complexSlotMap->mapCoreToNet($slot) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null");
}else{
$windowId = $this->getWindowId($inventory) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null");
$netSlot = $slot;
}
if($windowId !== null && $netSlot !== null){
$currentItem = $inventory->getItem($slot);
$clientSideItem = $this->initiatedSlotChanges[$windowId][$netSlot] ?? null;
if($clientSideItem === null || !$clientSideItem->equalsExact($currentItem)){
$itemStackWrapper = ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($currentItem));
if($windowId === ContainerIds::OFFHAND){
//TODO: HACK!
//The client may sometimes ignore the InventorySlotPacket for the offhand slot.
//This can cause a lot of problems (totems, arrows, and more...).
//The workaround is to send an InventoryContentPacket instead
//BDS (Bedrock Dedicated Server) also seems to work this way.
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
}else{
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
}
$itemStackWrapper = new ItemStackWrapper($itemStackInfo->getStackId(), $itemStack);
if($windowId === ContainerIds::OFFHAND){
//TODO: HACK!
//The client may sometimes ignore the InventorySlotPacket for the offhand slot.
//This can cause a lot of problems (totems, arrows, and more...).
//The workaround is to send an InventoryContentPacket instead
//BDS (Bedrock Dedicated Server) also seems to work this way.
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
}else{
if($windowId === ContainerIds::ARMOR){
//TODO: HACK!
//When right-clicking to equip armour, the client predicts the content of the armour slot, but
//doesn't report it in the transaction packet. The server then sends an InventorySlotPacket to
//the client, assuming the slot changed for some other reason, since there is no prediction for
//the slot.
//However, later requests involving that itemstack will refer to the request ID in which the
//armour was equipped, instead of the stack ID provided by the server in the outgoing
//InventorySlotPacket. (Perhaps because the item is already the same as the client actually
//predicted, but didn't tell us?)
//We work around this bug by setting the slot to air and then back to the correct item. In
//theory, setting a different count and then back again (or changing any other property) would
//also work, but this is simpler.
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, new ItemStackWrapper(0, ItemStack::null())));
}
unset($this->initiatedSlotChanges[$windowId][$netSlot]);
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
}
unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]);
}
public function syncContents(Inventory $inventory) : void{
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
if($slotMap !== null){
$entry = $this->inventories[spl_object_id($inventory)];
if($entry->complexSlotMap !== null){
$windowId = ContainerIds::UI;
}else{
$windowId = $this->getWindowId($inventory);
}
$typeConverter = TypeConverter::getInstance();
if($windowId !== null){
if($slotMap !== null){
foreach($inventory->getContents(true) as $slotId => $item){
$packetSlot = $slotMap->mapCoreToNet($slotId) ?? null;
$entry->predictions = [];
$entry->pendingSyncs = [];
$contents = [];
$typeConverter = TypeConverter::getInstance();
foreach($inventory->getContents(true) as $slot => $item){
$itemStack = $typeConverter->coreItemStackToNet($item);
$info = $this->trackItemStack($entry, $slot, $itemStack, null);
$contents[] = new ItemStackWrapper($info->getStackId(), $itemStack);
}
if($entry->complexSlotMap !== null){
foreach($contents as $slotId => $info){
$packetSlot = $entry->complexSlotMap->mapCoreToNet($slotId) ?? null;
if($packetSlot === null){
continue;
}
unset($this->initiatedSlotChanges[$windowId][$packetSlot]);
$this->session->sendDataPacket(InventorySlotPacket::create(
$windowId,
$packetSlot,
ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($inventory->getItem($slotId)))
$info
));
}
}else{
unset($this->initiatedSlotChanges[$windowId]);
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, array_map(function(Item $itemStack) use ($typeConverter) : ItemStackWrapper{
return ItemStackWrapper::legacy($typeConverter->coreItemStackToNet($itemStack));
}, $inventory->getContents(true))));
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, $contents));
}
}
}
public function syncAll() : void{
foreach($this->windowMap as $inventory){
$this->syncContents($inventory);
}
foreach($this->complexWindows as $entry){
$this->syncContents($entry->getInventory());
foreach($this->inventories as $entry){
$this->syncContents($entry->inventory);
}
}
public function syncMismatchedPredictedSlotChanges() : void{
foreach($this->initiatedSlotChanges as $windowId => $slots){
foreach($slots as $netSlot => $expectedItem){
$located = $this->locateWindowAndSlot($windowId, $netSlot);
if($located === null){
continue;
}
[$inventory, $slot] = $located;
public function requestSyncAll() : void{
$this->fullSyncRequested = true;
}
if(!$inventory->slotExists($slot)){
public function syncMismatchedPredictedSlotChanges() : void{
$typeConverter = TypeConverter::getInstance();
foreach($this->inventories as $entry){
$inventory = $entry->inventory;
foreach($entry->predictions as $slot => $expectedItem){
if(!$inventory->slotExists($slot) || $entry->itemStackInfos[$slot] === null){
continue; //TODO: size desync ???
}
$actualItem = $inventory->getItem($slot);
if(!$actualItem->equalsExact($expectedItem)){
$this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot");
$this->syncSlot($inventory, $slot);
//any prediction that still exists at this point is a slot that was predicted to change but didn't
$this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot");
$entry->pendingSyncs[$slot] = $typeConverter->coreItemStackToNet($inventory->getItem($slot));
}
$entry->predictions = [];
}
}
public function flushPendingUpdates() : void{
if($this->fullSyncRequested){
$this->fullSyncRequested = false;
$this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->inventories) . " inventories");
$this->syncAll();
}else{
foreach($this->inventories as $entry){
if(count($entry->pendingSyncs) === 0){
continue;
}
$inventory = $entry->inventory;
$this->session->getLogger()->debug("Syncing slots " . implode(", ", array_keys($entry->pendingSyncs)) . " in inventory " . get_class($inventory) . "#" . spl_object_id($inventory));
foreach($entry->pendingSyncs as $slot => $itemStack){
$this->syncSlot($inventory, $slot, $itemStack);
}
$entry->pendingSyncs = [];
}
}
$this->initiatedSlotChanges = [];
}
public function syncData(Inventory $inventory, int $propertyId, int $value) : void{
@ -453,11 +563,17 @@ class InventoryManager{
}
public function syncSelectedHotbarSlot() : void{
$selected = $this->player->getInventory()->getHeldItemIndex();
$playerInventory = $this->player->getInventory();
$selected = $playerInventory->getHeldItemIndex();
if($selected !== $this->clientSelectedHotbarSlot){
$itemStackInfo = $this->getItemStackInfo($playerInventory, $selected);
if($itemStackInfo === null){
throw new AssumptionFailedError("Player inventory slots should always be tracked");
}
$this->session->sendDataPacket(MobEquipmentPacket::create(
$this->player->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->player->getInventory()->getItemInHand())),
new ItemStackWrapper($itemStackInfo->getStackId(), TypeConverter::getInstance()->coreItemStackToNet($playerInventory->getItemInHand())),
$selected,
$selected,
ContainerIds::INVENTORY
@ -469,9 +585,28 @@ class InventoryManager{
public function syncCreative() : void{
$typeConverter = TypeConverter::getInstance();
$nextEntryId = 1;
$this->session->sendDataPacket(CreativeContentPacket::create(array_map(function(Item $item) use($typeConverter, &$nextEntryId) : CreativeContentEntry{
return new CreativeContentEntry($nextEntryId++, $typeConverter->coreItemStackToNet($item));
}, $this->player->isSpectator() ? [] : CreativeInventory::getInstance()->getAll())));
$entries = [];
if(!$this->player->isSpectator()){
//creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent
foreach(CreativeInventory::getInstance()->getAll() as $k => $item){
$entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item));
}
}
$this->session->sendDataPacket(CreativeContentPacket::create($entries));
}
private function newItemStackId() : int{
return $this->nextItemStackId++;
}
public function getItemStackInfo(Inventory $inventory, int $slot) : ?ItemStackInfo{
$entry = $this->inventories[spl_object_id($inventory)] ?? null;
return $entry?->itemStackInfos[$slot] ?? null;
}
private function trackItemStack(InventoryManagerEntry $entry, int $slotId, ItemStack $itemStack, ?int $itemStackRequestId) : ItemStackInfo{
//TODO: ItemStack->isNull() would be nice to have here
$info = new ItemStackInfo($itemStackRequestId, $itemStack->getId() === 0 ? 0 : $this->newItemStackId());
return $entry->itemStackInfos[$slotId] = $info;
}
}

View File

@ -0,0 +1,52 @@
<?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;
use pocketmine\inventory\Inventory;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
final class InventoryManagerEntry{
/**
* @var ItemStack[]
* @phpstan-var array<int, ItemStack>
*/
public array $predictions = [];
/**
* @var ItemStackInfo[]
* @phpstan-var array<int, ItemStackInfo>
*/
public array $itemStackInfos = [];
/**
* @var int[]
* @phpstan-var array<int, ItemStack>
*/
public array $pendingSyncs = [];
public function __construct(
public Inventory $inventory,
public ?ComplexInventoryMapEntry $complexSlotMap = null
){}
}

View File

@ -0,0 +1,36 @@
<?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;
final class ItemStackInfo{
public function __construct(
private ?int $requestId,
private int $stackId
){}
public function getRequestId() : ?int{ return $this->requestId; }
public function getStackId() : int{ return $this->stackId; }
}

View File

@ -0,0 +1,103 @@
<?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;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\player\Player;
use pocketmine\timings\Timings;
use function count;
use function spl_object_id;
final class NetworkBroadcastUtils{
private function __construct(){
//NOOP
}
/**
* @param Player[] $recipients
* @param ClientboundPacket[] $packets
*/
public static function broadcastPackets(array $recipients, array $packets) : bool{
if(count($packets) === 0){
throw new \InvalidArgumentException("Cannot broadcast empty list of packets");
}
return Timings::$broadcastPackets->time(function() use ($recipients, $packets) : bool{
/** @var NetworkSession[] $sessions */
$sessions = [];
foreach($recipients as $player){
if($player->isConnected()){
$sessions[] = $player->getNetworkSession();
}
}
if(count($sessions) === 0){
return false;
}
$ev = new DataPacketSendEvent($sessions, $packets);
$ev->call();
if($ev->isCancelled()){
return false;
}
$sessions = $ev->getTargets();
/** @var PacketBroadcaster[] $uniqueBroadcasters */
$uniqueBroadcasters = [];
/** @var NetworkSession[][] $broadcasterTargets */
$broadcasterTargets = [];
foreach($sessions as $recipient){
$broadcaster = $recipient->getBroadcaster();
$uniqueBroadcasters[spl_object_id($broadcaster)] = $broadcaster;
$broadcasterTargets[spl_object_id($broadcaster)][spl_object_id($recipient)] = $recipient;
}
foreach($uniqueBroadcasters as $broadcaster){
$broadcaster->broadcastPackets($broadcasterTargets[spl_object_id($broadcaster)], $packets);
}
return true;
});
}
/**
* @param Player[] $recipients
* @phpstan-param \Closure(EntityEventBroadcaster, array<int, NetworkSession>) : void $callback
*/
public static function broadcastEntityEvent(array $recipients, \Closure $callback) : void{
$uniqueBroadcasters = [];
$broadcasterTargets = [];
foreach($recipients as $recipient){
$session = $recipient->getNetworkSession();
$broadcaster = $session->getEntityEventBroadcaster();
$uniqueBroadcasters[spl_object_id($broadcaster)] = $broadcaster;
$broadcasterTargets[spl_object_id($broadcaster)][spl_object_id($session)] = $session;
}
foreach($uniqueBroadcasters as $k => $broadcaster){
$callback($broadcaster, $broadcasterTargets[$k]);
}
}
}

View File

@ -23,13 +23,9 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\Attribute;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\Living;
use pocketmine\event\player\PlayerDuplicateLoginEvent;
use pocketmine\event\server\DataPacketDecodeEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\form\Form;
@ -43,7 +39,6 @@ use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\compression\DecompressionException;
use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\encryption\DecryptionException;
@ -62,10 +57,6 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
@ -74,39 +65,31 @@ use pocketmine\network\mcpe\protocol\PacketDecodeException;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\PlayerListPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
use pocketmine\network\mcpe\protocol\SetActorDataPacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\ToastRequestPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
use pocketmine\network\mcpe\protocol\types\AbilitiesData;
use pocketmine\network\mcpe\protocol\types\AbilitiesLayer;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\command\CommandData;
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\types\UpdateAbilitiesPacketLayer;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\network\mcpe\protocol\UpdateAdventureSettingsPacket;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\NetworkSessionManager;
use pocketmine\network\PacketHandlingException;
use pocketmine\permission\DefaultPermissionNames;
@ -119,6 +102,7 @@ use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
@ -128,13 +112,11 @@ use function array_values;
use function base64_encode;
use function bin2hex;
use function count;
use function function_exists;
use function get_class;
use function hrtime;
use function in_array;
use function intdiv;
use function json_encode;
use function ksort;
use function min;
use function strcasecmp;
use function strlen;
@ -142,9 +124,7 @@ use function strtolower;
use function substr;
use function time;
use function ucfirst;
use function xdebug_is_debugger_active;
use const JSON_THROW_ON_ERROR;
use const SORT_NUMERIC;
class NetworkSession{
private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately
@ -177,7 +157,7 @@ class NetworkSession{
private ?EncryptionContext $cipher = null;
/** @var Packet[] */
/** @var string[] */
private array $sendBuffer = [];
/**
@ -188,8 +168,6 @@ class NetworkSession{
private bool $forceAsyncCompression = true;
private bool $enableCompression = false; //disabled until handshake completed
private PacketSerializerContext $packetSerializerContext;
private ?InventoryManager $invManager = null;
/**
@ -202,8 +180,10 @@ class NetworkSession{
private Server $server,
private NetworkSessionManager $manager,
private PacketPool $packetPool,
private PacketSerializerContext $packetSerializerContext,
private PacketSender $sender,
private PacketBroadcaster $broadcaster,
private EntityEventBroadcaster $entityEventBroadcaster,
private Compressor $compressor,
private string $ip,
private int $port
@ -212,9 +192,6 @@ class NetworkSession{
$this->compressedQueue = new \SplQueue();
//TODO: allow this to be injected
$this->packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
$this->disposeHooks = new ObjectSet();
$this->connectTime = time();
@ -278,10 +255,10 @@ class NetworkSession{
$effectManager = $this->player->getEffects();
$effectManager->getEffectAddHooks()->add($effectAddHook = function(EffectInstance $effect, bool $replacesOldEffect) : void{
$this->onEntityEffectAdded($this->player, $effect, $replacesOldEffect);
$this->entityEventBroadcaster->onEntityEffectAdded([$this], $this->player, $effect, $replacesOldEffect);
});
$effectManager->getEffectRemoveHooks()->add($effectRemoveHook = function(EffectInstance $effect) : void{
$this->onEntityEffectRemoved($this->player, $effect);
$this->entityEventBroadcaster->onEntityEffectRemoved([$this], $this->player, $effect);
});
$this->disposeHooks->add(static function() use ($effectManager, $effectAddHook, $effectRemoveHook) : void{
$effectManager->getEffectAddHooks()->remove($effectAddHook);
@ -359,59 +336,67 @@ class NetworkSession{
return;
}
if($this->incomingPacketBatchBudget <= 0){
if(!function_exists('xdebug_is_debugger_active') || !xdebug_is_debugger_active()){
throw new PacketHandlingException("Receiving packets too fast");
}else{
//when a debugging session is active, the server may halt at any point for an indefinite length of time,
//in which time the client will continue to send packets
$this->incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
}
}
$this->incomingPacketBatchBudget--;
if($this->cipher !== null){
Timings::$playerNetworkReceiveDecrypt->startTiming();
try{
$payload = $this->cipher->decrypt($payload);
}catch(DecryptionException $e){
$this->logger->debug("Encrypted packet: " . base64_encode($payload));
throw PacketHandlingException::wrap($e, "Packet decryption error");
}finally{
Timings::$playerNetworkReceiveDecrypt->stopTiming();
}
}
if($this->enableCompression){
Timings::$playerNetworkReceiveDecompress->startTiming();
try{
$decompressed = $this->compressor->decompress($payload);
}catch(DecompressionException $e){
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
}finally{
Timings::$playerNetworkReceiveDecompress->stopTiming();
}
}else{
$decompressed = $payload;
}
Timings::$playerNetworkReceive->startTiming();
try{
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received");
}
try{
$this->handleDataPacket($packet, $buffer);
}catch(PacketHandlingException $e){
$this->logger->debug($packet->getName() . ": " . base64_encode($buffer));
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
if($this->incomingPacketBatchBudget <= 0){
$this->updatePacketBudget();
if($this->incomingPacketBatchBudget <= 0){
throw new PacketHandlingException("Receiving packets too fast");
}
}
}catch(PacketDecodeException $e){
$this->logger->logException($e);
throw PacketHandlingException::wrap($e, "Packet batch decode error");
$this->incomingPacketBatchBudget--;
if($this->cipher !== null){
Timings::$playerNetworkReceiveDecrypt->startTiming();
try{
$payload = $this->cipher->decrypt($payload);
}catch(DecryptionException $e){
$this->logger->debug("Encrypted packet: " . base64_encode($payload));
throw PacketHandlingException::wrap($e, "Packet decryption error");
}finally{
Timings::$playerNetworkReceiveDecrypt->stopTiming();
}
}
if($this->enableCompression){
Timings::$playerNetworkReceiveDecompress->startTiming();
try{
$decompressed = $this->compressor->decompress($payload);
}catch(DecompressionException $e){
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
}finally{
Timings::$playerNetworkReceiveDecompress->stopTiming();
}
}else{
$decompressed = $payload;
}
try{
$stream = new BinaryStream($decompressed);
$count = 0;
foreach(PacketBatch::decodeRaw($stream) as $buffer){
if(++$count > 100){
throw new PacketHandlingException("Too many packets in batch");
}
$packet = $this->packetPool->getPacket($buffer);
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received");
}
try{
$this->handleDataPacket($packet, $buffer);
}catch(PacketHandlingException $e){
$this->logger->debug($packet->getName() . ": " . base64_encode($buffer));
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
}
}
}catch(PacketDecodeException $e){
$this->logger->logException($e);
throw PacketHandlingException::wrap($e, "Packet batch decode error");
}
}finally{
Timings::$playerNetworkReceive->stopTiming();
}
}
@ -423,32 +408,45 @@ class NetworkSession{
throw new PacketHandlingException("Unexpected non-serverbound packet");
}
$timings = Timings::getDecodeDataPacketTimings($packet);
$timings = Timings::getReceiveDataPacketTimings($packet);
$timings->startTiming();
try{
$stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext);
try{
$packet->decode($stream);
}catch(PacketDecodeException $e){
throw PacketHandlingException::wrap($e);
}
if(!$stream->feof()){
$remains = substr($stream->getBuffer(), $stream->getOffset());
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
}
}finally{
$timings->stopTiming();
}
$timings = Timings::getHandleDataPacketTimings($packet);
$timings->startTiming();
try{
//TODO: I'm not sure DataPacketReceiveEvent should be included in the handler timings, but it needs to be
//included for now to ensure the receivePacket timings are counted the way they were before
$ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
$ev->call();
if($ev->isCancelled()){
return;
}
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
$decodeTimings->startTiming();
try{
$stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext);
try{
$packet->decode($stream);
}catch(PacketDecodeException $e){
throw PacketHandlingException::wrap($e);
}
if(!$stream->feof()){
$remains = substr($stream->getBuffer(), $stream->getOffset());
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
}
}finally{
$decodeTimings->stopTiming();
}
$ev = new DataPacketReceiveEvent($this, $packet);
$ev->call();
if(!$ev->isCancelled() && ($this->handler === null || !$packet->handle($this->handler))){
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
if(!$ev->isCancelled()){
$handlerTimings = Timings::getHandleDataPacketTimings($packet);
$handlerTimings->startTiming();
try{
if($this->handler === null || !$packet->handle($this->handler)){
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
}
}finally{
$handlerTimings->stopTiming();
}
}
}finally{
$timings->stopTiming();
@ -473,7 +471,7 @@ class NetworkSession{
return false;
}
$this->addToSendBuffer($packet);
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $packet));
if($immediate){
$this->flushSendBuffer(true);
}
@ -487,34 +485,49 @@ class NetworkSession{
/**
* @internal
*/
public function addToSendBuffer(ClientboundPacket $packet) : void{
$timings = Timings::getSendDataPacketTimings($packet);
public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{
$timings = Timings::getEncodeDataPacketTimings($packet);
$timings->startTiming();
try{
$this->sendBuffer[] = $packet;
$packet->encode($serializer);
return $serializer->getBuffer();
}finally{
$timings->stopTiming();
}
}
/**
* @internal
*/
public function addToSendBuffer(string $buffer) : void{
$this->sendBuffer[] = $buffer;
}
private function flushSendBuffer(bool $immediate = false) : void{
if(count($this->sendBuffer) > 0){
$syncMode = null; //automatic
if($immediate){
$syncMode = true;
}elseif($this->forceAsyncCompression){
$syncMode = false;
}
Timings::$playerNetworkSend->startTiming();
try{
$syncMode = null; //automatic
if($immediate){
$syncMode = true;
}elseif($this->forceAsyncCompression){
$syncMode = false;
}
$batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer);
if($this->enableCompression){
$promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode);
}else{
$promise = new CompressBatchPromise();
$promise->resolve($batch->getBuffer());
$stream = new BinaryStream();
PacketBatch::encodeRaw($stream, $this->sendBuffer);
if($this->enableCompression){
$promise = $this->server->prepareBatch(new PacketBatch($stream->getBuffer()), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
}else{
$promise = new CompressBatchPromise();
$promise->resolve($stream->getBuffer());
}
$this->sendBuffer = [];
$this->queueCompressedNoBufferFlush($promise, $immediate);
}finally{
Timings::$playerNetworkSend->stopTiming();
}
$this->sendBuffer = [];
$this->queueCompressedNoBufferFlush($promise, $immediate);
}
}
@ -522,40 +535,57 @@ class NetworkSession{
public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; }
public function getCompressor() : Compressor{
return $this->compressor;
}
public function queueCompressed(CompressBatchPromise $payload, bool $immediate = false) : void{
$this->flushSendBuffer($immediate); //Maintain ordering if possible
$this->queueCompressedNoBufferFlush($payload, $immediate);
Timings::$playerNetworkSend->startTiming();
try{
$this->flushSendBuffer($immediate); //Maintain ordering if possible
$this->queueCompressedNoBufferFlush($payload, $immediate);
}finally{
Timings::$playerNetworkSend->stopTiming();
}
}
private function queueCompressedNoBufferFlush(CompressBatchPromise $payload, bool $immediate = false) : void{
if($immediate){
//Skips all queues
$this->sendEncoded($payload->getResult(), true);
}else{
$this->compressedQueue->enqueue($payload);
$payload->onResolve(function(CompressBatchPromise $payload) : void{
if($this->connected && $this->compressedQueue->bottom() === $payload){
$this->compressedQueue->dequeue(); //result unused
$this->sendEncoded($payload->getResult());
Timings::$playerNetworkSend->startTiming();
try{
if($immediate){
//Skips all queues
$this->sendEncoded($payload->getResult(), true);
}else{
$this->compressedQueue->enqueue($payload);
$payload->onResolve(function(CompressBatchPromise $payload) : void{
if($this->connected && $this->compressedQueue->bottom() === $payload){
Timings::$playerNetworkSend->startTiming();
try{
$this->compressedQueue->dequeue(); //result unused
$this->sendEncoded($payload->getResult());
while(!$this->compressedQueue->isEmpty()){
/** @var CompressBatchPromise $current */
$current = $this->compressedQueue->bottom();
if($current->hasResult()){
$this->compressedQueue->dequeue();
while(!$this->compressedQueue->isEmpty()){
/** @var CompressBatchPromise $current */
$current = $this->compressedQueue->bottom();
if($current->hasResult()){
$this->compressedQueue->dequeue();
$this->sendEncoded($current->getResult());
}else{
//can't send any more queued until this one is ready
break;
$this->sendEncoded($current->getResult());
}else{
//can't send any more queued until this one is ready
break;
}
}
}finally{
Timings::$playerNetworkSend->stopTiming();
}
}
}
});
});
}
}finally{
Timings::$playerNetworkSend->stopTiming();
}
}
@ -782,7 +812,7 @@ class NetworkSession{
}
public function onServerRespawn() : void{
$this->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
$this->entityEventBroadcaster->syncAttributes([$this], $this->player, $this->player->getAttributeMap()->getAll());
$this->player->sendData(null);
$this->syncAbilities($this->player);
@ -848,33 +878,34 @@ class NetworkSession{
//ALL of these need to be set for the base layer, otherwise the client will cry
$boolAbilities = [
UpdateAbilitiesPacketLayer::ABILITY_ALLOW_FLIGHT => $for->getAllowFlight(),
UpdateAbilitiesPacketLayer::ABILITY_FLYING => $for->isFlying(),
UpdateAbilitiesPacketLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
UpdateAbilitiesPacketLayer::ABILITY_OPERATOR => $isOp,
UpdateAbilitiesPacketLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
UpdateAbilitiesPacketLayer::ABILITY_INVULNERABLE => $for->isCreative(),
UpdateAbilitiesPacketLayer::ABILITY_MUTED => false,
UpdateAbilitiesPacketLayer::ABILITY_WORLD_BUILDER => false,
UpdateAbilitiesPacketLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(),
UpdateAbilitiesPacketLayer::ABILITY_LIGHTNING => false,
UpdateAbilitiesPacketLayer::ABILITY_BUILD => !$for->isSpectator(),
UpdateAbilitiesPacketLayer::ABILITY_MINE => !$for->isSpectator(),
UpdateAbilitiesPacketLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(),
UpdateAbilitiesPacketLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(),
UpdateAbilitiesPacketLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(),
UpdateAbilitiesPacketLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_ALLOW_FLIGHT => $for->getAllowFlight(),
AbilitiesLayer::ABILITY_FLYING => $for->isFlying(),
AbilitiesLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
AbilitiesLayer::ABILITY_OPERATOR => $isOp,
AbilitiesLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT_SELF),
AbilitiesLayer::ABILITY_INVULNERABLE => $for->isCreative(),
AbilitiesLayer::ABILITY_MUTED => false,
AbilitiesLayer::ABILITY_WORLD_BUILDER => false,
AbilitiesLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(),
AbilitiesLayer::ABILITY_LIGHTNING => false,
AbilitiesLayer::ABILITY_BUILD => !$for->isSpectator(),
AbilitiesLayer::ABILITY_MINE => !$for->isSpectator(),
AbilitiesLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(),
AbilitiesLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(),
AbilitiesLayer::ABILITY_PRIVILEGED_BUILDER => false,
];
$this->sendDataPacket(UpdateAbilitiesPacket::create(
$this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData(
$isOp ? CommandPermissions::OPERATOR : CommandPermissions::NORMAL,
$isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
$for->getId(),
[
//TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!!
new UpdateAbilitiesPacketLayer(UpdateAbilitiesPacketLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1),
new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1),
]
));
)));
}
public function syncAdventureSettings() : void{
@ -891,41 +922,6 @@ class NetworkSession{
));
}
/**
* @param Attribute[] $attributes
*/
public function syncAttributes(Living $entity, array $attributes) : void{
if(count($attributes) > 0){
$this->sendDataPacket(UpdateAttributesPacket::create($entity->getId(), array_map(function(Attribute $attr) : NetworkAttribute{
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
}, $attributes), 0));
}
}
/**
* @param MetadataProperty[] $properties
* @phpstan-param array<int, MetadataProperty> $properties
*/
public function syncActorData(Entity $entity, array $properties) : void{
//TODO: HACK! as of 1.18.10, the client responds differently to the same data ordered in different orders - for
//example, sending HEIGHT in the list before FLAGS when unsetting the SWIMMING flag results in a hitbox glitch
ksort($properties, SORT_NUMERIC);
$this->sendDataPacket(SetActorDataPacket::create($entity->getId(), $properties, new PropertySyncData([], []), 0));
}
public function onEntityEffectAdded(Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{
//TODO: we may need yet another effect <=> ID map in the future depending on protocol changes
$this->sendDataPacket(MobEffectPacket::add($entity->getId(), $replacesOldEffect, EffectIdMap::getInstance()->toId($effect->getType()), $effect->getAmplifier(), $effect->isVisible(), $effect->getDuration()));
}
public function onEntityEffectRemoved(Living $entity, EffectInstance $effect) : void{
$this->sendDataPacket(MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType())));
}
public function onEntityRemoved(Entity $entity) : void{
$this->sendDataPacket(RemoveActorPacket::create($entity->getId()));
}
public function syncAvailableCommands() : void{
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $name => $command){
@ -1061,36 +1057,6 @@ class NetworkSession{
return $this->invManager;
}
/**
* TODO: expand this to more than just humans
*/
public function onMobMainHandItemChange(Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY));
}
public function onMobOffHandItemChange(Human $mob) : void{
$inv = $mob->getOffHandInventory();
$this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, 0, ContainerIds::OFFHAND));
}
public function onMobArmorChange(Living $mob) : void{
$inv = $mob->getArmorInventory();
$converter = TypeConverter::getInstance();
$this->sendDataPacket(MobArmorEquipmentPacket::create(
$mob->getId(),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots()))
));
}
public function onPlayerPickUpItem(Player $collector, Entity $pickedUp) : void{
$this->sendDataPacket(TakeItemActorPacket::create($collector->getId(), $pickedUp->getId()));
}
/**
* @param Player[] $players
*/
@ -1134,14 +1100,27 @@ class NetworkSession{
$this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut));
}
public function onEmote(Human $from, string $emoteId) : void{
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
}
public function onToastNotification(string $title, string $body) : void{
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
}
private function updatePacketBudget() : void{
$nowNs = hrtime(true);
$timeSinceLastUpdateNs = $nowNs - $this->lastPacketBudgetUpdateTimeNs;
if($timeSinceLastUpdateNs > 50_000_000){
$ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, 50_000_000);
/*
* If the server takes an abnormally long time to process a tick, add the budget for time difference to
* compensate. This extra budget may be very large, but it will disappear the next time a normal update
* occurs. This ensures that backlogs during a large lag spike don't cause everyone to get kicked.
* As long as all the backlogged packets are processed before the next tick, everything should be OK for
* clients behaving normally.
*/
$this->incomingPacketBatchBudget = min($this->incomingPacketBatchBudget, self::INCOMING_PACKET_BATCH_MAX_BUDGET) + (self::INCOMING_PACKET_BATCH_PER_TICK * 2 * $ticksSinceLastUpdate);
$this->lastPacketBudgetUpdateTimeNs = $nowNs;
}
}
public function tick() : void{
if(!$this->isConnected()){
$this->dispose();
@ -1160,25 +1139,20 @@ class NetworkSession{
$this->player->doChunkRequests();
$dirtyAttributes = $this->player->getAttributeMap()->needSend();
$this->syncAttributes($this->player, $dirtyAttributes);
$this->entityEventBroadcaster->syncAttributes([$this], $this->player, $dirtyAttributes);
foreach($dirtyAttributes as $attribute){
//TODO: we might need to send these to other players in the future
//if that happens, this will need to become more complex than a flag on the attribute itself
$attribute->markSynchronized();
}
}
Timings::$playerNetworkSendInventorySync->startTiming();
try{
$this->invManager?->flushPendingUpdates();
}finally{
Timings::$playerNetworkSendInventorySync->stopTiming();
}
$this->flushSendBuffer();
$nowNs = hrtime(true);
$timeSinceLastUpdateNs = $nowNs - $this->lastPacketBudgetUpdateTimeNs;
if($timeSinceLastUpdateNs > 50_000_000){
$ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, 50_000_000);
$this->incomingPacketBatchBudget = min(
$this->incomingPacketBatchBudget + (self::INCOMING_PACKET_BATCH_PER_TICK * 2 * $ticksSinceLastUpdate),
self::INCOMING_PACKET_BATCH_MAX_BUDGET
);
$this->lastPacketBudgetUpdateTimeNs = $nowNs;
}
}
}

View File

@ -0,0 +1,143 @@
<?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;
use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\Attribute;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\Entity;
use pocketmine\entity\Human;
use pocketmine\entity\Living;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
use pocketmine\network\mcpe\protocol\SetActorDataPacket;
use pocketmine\network\mcpe\protocol\TakeItemActorPacket;
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use function array_map;
use function count;
use function ksort;
use const SORT_NUMERIC;
final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
public function __construct(
private StandardPacketBroadcaster $broadcaster
){}
/**
* @param NetworkSession[] $recipients
*/
private function sendDataPacket(array $recipients, ClientboundPacket $packet) : void{
$this->broadcaster->broadcastPackets($recipients, [$packet]);
}
public function syncAttributes(array $recipients, Living $entity, array $attributes) : void{
if(count($attributes) > 0){
$this->sendDataPacket($recipients, UpdateAttributesPacket::create(
$entity->getId(),
array_map(fn(Attribute $attr) => new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []), $attributes),
0
));
}
}
public function syncActorData(array $recipients, Entity $entity, array $properties) : void{
//TODO: HACK! as of 1.18.10, the client responds differently to the same data ordered in different orders - for
//example, sending HEIGHT in the list before FLAGS when unsetting the SWIMMING flag results in a hitbox glitch
ksort($properties, SORT_NUMERIC);
$this->sendDataPacket($recipients, SetActorDataPacket::create($entity->getId(), $properties, new PropertySyncData([], []), 0));
}
public function onEntityEffectAdded(array $recipients, Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{
//TODO: we may need yet another effect <=> ID map in the future depending on protocol changes
$this->sendDataPacket($recipients, MobEffectPacket::add(
$entity->getId(),
$replacesOldEffect,
EffectIdMap::getInstance()->toId($effect->getType()),
$effect->getAmplifier(),
$effect->isVisible(),
$effect->getDuration()
));
}
public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void{
$this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType())));
}
public function onEntityRemoved(array $recipients, Entity $entity) : void{
$this->sendDataPacket($recipients, RemoveActorPacket::create($entity->getId()));
}
public function onMobMainHandItemChange(array $recipients, Human $mob) : void{
//TODO: we could send zero for slot here because remote players don't need to know which slot was selected
$inv = $mob->getInventory();
$this->sendDataPacket($recipients, MobEquipmentPacket::create(
$mob->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())),
$inv->getHeldItemIndex(),
$inv->getHeldItemIndex(),
ContainerIds::INVENTORY
));
}
public function onMobOffHandItemChange(array $recipients, Human $mob) : void{
$inv = $mob->getOffHandInventory();
$this->sendDataPacket($recipients, MobEquipmentPacket::create(
$mob->getId(),
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))),
0,
0,
ContainerIds::OFFHAND
));
}
public function onMobArmorChange(array $recipients, Living $mob) : void{
$inv = $mob->getArmorInventory();
$converter = TypeConverter::getInstance();
$this->sendDataPacket($recipients, MobArmorEquipmentPacket::create(
$mob->getId(),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())),
ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots()))
));
}
public function onPickUpItem(array $recipients, Entity $collector, Entity $pickedUp) : void{
$this->sendDataPacket($recipients, TakeItemActorPacket::create($collector->getId(), $pickedUp->getId()));
}
public function onEmote(array $recipients, Human $from, string $emoteId) : void{
$this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
}
}

View File

@ -24,44 +24,66 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\BinaryStream;
use function count;
use function log;
use function spl_object_id;
use function strlen;
final class StandardPacketBroadcaster implements PacketBroadcaster{
public function __construct(private Server $server){}
public function __construct(
private Server $server,
private PacketSerializerContext $protocolContext
){}
public function broadcastPackets(array $recipients, array $packets) : void{
$buffers = [];
$compressors = [];
$targetMap = [];
/** @var NetworkSession[][] $targetsByCompressor */
$targetsByCompressor = [];
foreach($recipients as $recipient){
$serializerContext = $recipient->getPacketSerializerContext();
$bufferId = spl_object_id($serializerContext);
if(!isset($buffers[$bufferId])){
$buffers[$bufferId] = PacketBatch::fromPackets($serializerContext, ...$packets);
if($recipient->getPacketSerializerContext() !== $this->protocolContext){
throw new \InvalidArgumentException("Only recipients with the same protocol context as the broadcaster can be broadcast to by this broadcaster");
}
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
$compressor = $recipient->getCompressor();
$compressors[spl_object_id($compressor)] = $compressor;
$targetMap[$bufferId][spl_object_id($compressor)][] = $recipient;
$targetsByCompressor[spl_object_id($compressor)][] = $recipient;
}
foreach($targetMap as $bufferId => $compressorMap){
$buffer = $buffers[$bufferId];
foreach($compressorMap as $compressorId => $compressorTargets){
$compressor = $compressors[$compressorId];
if(!$compressor->willCompress($buffer->getBuffer())){
foreach($compressorTargets as $target){
foreach($packets as $pk){
$target->addToSendBuffer($pk);
}
}
}else{
$promise = $this->server->prepareBatch($buffer, $compressor);
foreach($compressorTargets as $target){
$target->queueCompressed($promise);
$totalLength = 0;
$packetBuffers = [];
foreach($packets as $packet){
$buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder($this->protocolContext), $packet);
//varint length prefix + packet buffer
$totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer);
$packetBuffers[] = $buffer;
}
foreach($targetsByCompressor as $compressorId => $compressorTargets){
$compressor = $compressors[$compressorId];
$threshold = $compressor->getCompressionThreshold();
if(count($compressorTargets) > 1 && $threshold !== null && $totalLength >= $threshold){
//do not prepare shared batch unless we're sure it will be compressed
$stream = new BinaryStream();
PacketBatch::encodeRaw($stream, $packetBuffers);
$batchBuffer = $stream->getBuffer();
$promise = $this->server->prepareBatch(new PacketBatch($batchBuffer), $compressor, timings: Timings::$playerNetworkSendCompressBroadcast);
foreach($compressorTargets as $target){
$target->queueCompressed($promise);
}
}else{
foreach($compressorTargets as $target){
foreach($packetBuffers as $packetBuffer){
$target->addToSendBuffer($packetBuffer);
}
}
}

View File

@ -25,6 +25,8 @@ namespace pocketmine\network\mcpe\cache;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\FurnaceType;
use pocketmine\crafting\ShapedRecipe;
use pocketmine\crafting\ShapelessRecipe;
use pocketmine\crafting\ShapelessRecipeType;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\ItemTranslator;
@ -76,12 +78,12 @@ final class CraftingDataCache{
private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{
Timings::$craftingDataCacheRebuild->startTiming();
$counter = 0;
$nullUUID = Uuid::fromString(Uuid::NIL);
$converter = TypeConverter::getInstance();
$recipesWithTypeIds = [];
foreach($manager->getShapelessRecipes() as $list){
foreach($list as $recipe){
foreach($manager->getCraftingRecipeIndex() as $index => $recipe){
if($recipe instanceof ShapelessRecipe){
$typeTag = match($recipe->getType()->id()){
ShapelessRecipeType::CRAFTING()->id() => CraftingRecipeBlockName::CRAFTING_TABLE,
ShapelessRecipeType::STONECUTTER()->id() => CraftingRecipeBlockName::STONECUTTER,
@ -89,7 +91,7 @@ final class CraftingDataCache{
};
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
Binary::writeInt(++$counter),
Binary::writeInt($index),
array_map(function(Item $item) use ($converter) : RecipeIngredient{
return $converter->coreItemStackToRecipeIngredient($item);
}, $recipe->getIngredientList()),
@ -99,12 +101,9 @@ final class CraftingDataCache{
$nullUUID,
$typeTag,
50,
$counter
$index
);
}
}
foreach($manager->getShapedRecipes() as $list){
foreach($list as $recipe){
}elseif($recipe instanceof ShapedRecipe){
$inputs = [];
for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){
@ -114,7 +113,7 @@ final class CraftingDataCache{
}
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
CraftingDataPacket::ENTRY_SHAPED,
Binary::writeInt(++$counter),
Binary::writeInt($index),
$inputs,
array_map(function(Item $item) use ($converter) : ItemStack{
return $converter->coreItemStackToNet($item);
@ -122,8 +121,10 @@ final class CraftingDataCache{
$nullUUID,
CraftingRecipeBlockName::CRAFTING_TABLE,
50,
$counter
$index
);
}else{
//TODO: probably special recipe types
}
}

View File

@ -23,13 +23,13 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\cache;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use Symfony\Component\Filesystem\Path;
class StaticPacketCache{
use SingletonTrait;
@ -43,8 +43,8 @@ class StaticPacketCache{
private static function make() : self{
return new self(
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'biome_definitions.nbt'))),
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'entity_identifiers.nbt')))
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(BedrockDataFiles::BIOME_DEFINITIONS_NBT)),
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(BedrockDataFiles::ENTITY_IDENTIFIERS_NBT))
);
}

View File

@ -24,13 +24,20 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
interface Compressor{
public function willCompress(string $data) : bool;
/**
* @throws DecompressionException
*/
public function decompress(string $payload) : string;
public function compress(string $payload) : string;
/**
* Returns the minimum size of packet batch that the compressor will attempt to compress.
*
* The compressor's output **MUST** still be valid input for the decompressor even if the compressor input is
* below this threshold.
* However, it may choose to use a cheaper compression option (e.g. zlib level 0, which simply wraps the data and
* doesn't attempt to compress it) to avoid wasting CPU time.
*/
public function getCompressionThreshold() : ?int;
}

View File

@ -48,12 +48,12 @@ final class ZlibCompressor implements Compressor{
public function __construct(
private int $level,
private int $minCompressionSize,
private ?int $minCompressionSize,
private int $maxDecompressionSize
){}
public function willCompress(string $data) : bool{
return $this->minCompressionSize > -1 && strlen($data) >= $this->minCompressionSize;
public function getCompressionThreshold() : ?int{
return $this->minCompressionSize;
}
/**
@ -72,11 +72,12 @@ final class ZlibCompressor implements Compressor{
}
public function compress(string $payload) : string{
$compressible = $this->minCompressionSize !== null && strlen($payload) >= $this->minCompressionSize;
if(function_exists('libdeflate_deflate_compress')){
return $this->willCompress($payload) ?
return $compressible ?
libdeflate_deflate_compress($payload, $this->level) :
self::zlib_encode($payload, 0);
}
return self::zlib_encode($payload, $this->willCompress($payload) ? $this->level : 0);
return self::zlib_encode($payload, $compressible ? $this->level : 0);
}
}

View File

@ -23,12 +23,12 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use Symfony\Component\Filesystem\Path;
use function is_array;
use function is_bool;
use function is_int;
@ -39,7 +39,7 @@ final class GlobalItemTypeDictionary{
use SingletonTrait;
private static function make() : self{
$data = Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'required_item_list.json'));
$data = Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON);
$table = json_decode($data, true);
if(!is_array($table)){
throw new AssumptionFailedError("Invalid item list format");

View File

@ -23,13 +23,13 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\LegacyItemIdToStringIdMap;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function array_key_exists;
use function is_array;
use function is_numeric;
@ -67,7 +67,7 @@ final class ItemTranslator{
private array $complexNetToCoreMapping = [];
private static function make() : self{
$data = Filesystem::fileGetContents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'r16_to_current_item_map.json'));
$data = Filesystem::fileGetContents(BedrockDataFiles::R16_TO_CURRENT_ITEM_MAP_JSON);
$json = json_decode($data, true);
if(!is_array($json) || !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){
throw new AssumptionFailedError("Invalid item table format");

View File

@ -25,14 +25,13 @@ namespace pocketmine\network\mcpe\convert;
use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use Symfony\Component\Filesystem\Path;
/**
* @internal
@ -49,20 +48,20 @@ final class RuntimeBlockMapping{
private static function make() : self{
return new self(
Path::join(\pocketmine\BEDROCK_DATA_PATH, "canonical_block_states.nbt"),
Path::join(\pocketmine\BEDROCK_DATA_PATH, "r12_to_current_block_map.bin")
BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT,
BedrockDataFiles::R12_TO_CURRENT_BLOCK_MAP_BIN
);
}
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
$stream = PacketSerializer::decoder(
Filesystem::fileGetContents($canonicalBlockStatesFile),
0,
new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())
);
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
while(!$stream->feof()){
$list[] = $stream->getNbtCompoundRoot();
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = $blockState;
}
$this->bedrockKnownStates = $list;
@ -73,14 +72,10 @@ final class RuntimeBlockMapping{
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = [];
$legacyStateMapReader = PacketSerializer::decoder(
Filesystem::fileGetContents($r12ToCurrentBlockMapFile),
0,
new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())
);
$legacyStateMapReader = new BinaryStream(Filesystem::fileGetContents($r12ToCurrentBlockMapFile));
$nbtReader = new NetworkNbtSerializer();
while(!$legacyStateMapReader->feof()){
$id = $legacyStateMapReader->getString();
$id = $legacyStateMapReader->get($legacyStateMapReader->getUnsignedVarInt());
$meta = $legacyStateMapReader->getLShort();
$offset = $legacyStateMapReader->getOffset();

View File

@ -24,11 +24,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\block\BlockLegacyIds;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Durable;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
@ -37,17 +32,12 @@ use pocketmine\item\VanillaItems;
use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor;
use pocketmine\player\GameMode;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
@ -261,60 +251,4 @@ class TypeConverter{
throw TypeConversionException::wrap($e, "Bad itemstack NBT data");
}
}
/**
* @throws TypeConversionException
*/
public function createInventoryAction(NetworkInventoryAction $action, Player $player, InventoryManager $inventoryManager) : ?InventoryAction{
if($action->oldItem->getItemStack()->equals($action->newItem->getItemStack())){
//filter out useless noise in 1.13
return null;
}
try{
$old = $this->netItemStackToCore($action->oldItem->getItemStack());
}catch(TypeConversionException $e){
throw TypeConversionException::wrap($e, "Inventory action: oldItem");
}
try{
$new = $this->netItemStackToCore($action->newItem->getItemStack());
}catch(TypeConversionException $e){
throw TypeConversionException::wrap($e, "Inventory action: newItem");
}
switch($action->sourceType){
case NetworkInventoryAction::SOURCE_CONTAINER:
if($action->windowId === ContainerIds::UI && $action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
return null; //useless noise
}
$located = $inventoryManager->locateWindowAndSlot($action->windowId, $action->inventorySlot);
if($located !== null){
[$window, $slot] = $located;
return new SlotChangeAction($window, $slot, $old, $new);
}
throw new TypeConversionException("No open container with window ID $action->windowId");
case NetworkInventoryAction::SOURCE_WORLD:
if($action->inventorySlot !== NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){
throw new TypeConversionException("Only expecting drop-item world actions from the client!");
}
return new DropItemAction($new);
case NetworkInventoryAction::SOURCE_CREATIVE:
switch($action->inventorySlot){
case NetworkInventoryAction::ACTION_MAGIC_SLOT_CREATIVE_DELETE_ITEM:
return new DestroyItemAction($new);
case NetworkInventoryAction::ACTION_MAGIC_SLOT_CREATIVE_CREATE_ITEM:
return new CreateItemAction($old);
default:
throw new TypeConversionException("Unexpected creative action type $action->inventorySlot");
}
case NetworkInventoryAction::SOURCE_TODO:
//These are used to balance a transaction that involves special actions, like crafting, enchanting, etc.
//The vanilla server just accepted these without verifying them. We don't need to care about them since
//we verify crafting by checking for imbalances anyway.
return null;
default:
throw new TypeConversionException("Unknown inventory source type $action->sourceType");
}
}
}

View File

@ -32,10 +32,10 @@ use pocketmine\entity\animation\ConsumingItemAnimation;
use pocketmine\entity\Attribute;
use pocketmine\entity\InvalidSkinException;
use pocketmine\event\player\PlayerEditBookEvent;
use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionException;
use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionCancelledException;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\VanillaItems;
use pocketmine\item\WritableBook;
@ -46,7 +46,6 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConversionException;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession;
@ -65,6 +64,8 @@ use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LecternUpdatePacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
@ -96,7 +97,8 @@ use pocketmine\network\mcpe\protocol\types\inventory\MismatchTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
use pocketmine\network\mcpe\protocol\types\inventory\NormalTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\ReleaseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\PlayerAction;
@ -108,17 +110,18 @@ use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use pocketmine\world\format\Chunk;
use function array_push;
use function base64_encode;
use function count;
use function fmod;
use function implode;
use function in_array;
use function is_bool;
use function is_infinite;
use function is_nan;
use function json_decode;
use function json_encode;
use function max;
use function mb_strlen;
use function microtime;
@ -133,9 +136,6 @@ use const JSON_THROW_ON_ERROR;
class InGamePacketHandler extends PacketHandler{
private const MAX_FORM_RESPONSE_DEPTH = 2; //modal/simple will be 1, custom forms 2 - they will never contain anything other than string|int|float|bool|null
/** @var CraftingTransaction|null */
protected $craftingTransaction = null;
/** @var float */
protected $lastRightClickTime = 0.0;
/** @var UseItemTransactionData|null */
@ -149,6 +149,8 @@ class InGamePacketHandler extends PacketHandler{
/** @var bool */
public $forceMoveSync = false;
protected ?string $lastRequestedFullSkinId = null;
public function __construct(
private Player $player,
private NetworkSession $session,
@ -274,13 +276,22 @@ class InGamePacketHandler extends PacketHandler{
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
throw new PacketHandlingException("Too many actions in item use transaction");
}
$this->inventoryManager->addPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
$this->inventoryManager->setCurrentItemStackRequestId($useItemTransaction->getRequestId());
$this->inventoryManager->addRawPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
$packetHandled = false;
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
}else{
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
}
$this->inventoryManager->setCurrentItemStackRequestId(null);
}
$itemStackRequest = $packet->getItemStackRequest();
if($itemStackRequest !== null){
$result = $this->handleSingleItemStackRequest($itemStackRequest);
$this->session->sendDataPacket(ItemStackResponsePacket::create([$result]));
}
return $packetHandled;
@ -314,17 +325,18 @@ class InGamePacketHandler extends PacketHandler{
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
$result = true;
if(count($packet->trData->getActions()) > 100){
if(count($packet->trData->getActions()) > 50){
throw new PacketHandlingException("Too many actions in inventory transaction");
}
$this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions());
$this->inventoryManager->setCurrentItemStackRequestId($packet->requestId);
$this->inventoryManager->addRawPredictedSlotChanges($packet->trData->getActions());
if($packet->trData instanceof NormalTransactionData){
$result = $this->handleNormalTransaction($packet->trData);
$result = $this->handleNormalTransaction($packet->trData, $packet->requestId);
}elseif($packet->trData instanceof MismatchTransactionData){
$this->session->getLogger()->debug("Mismatch transaction received");
$this->inventoryManager->syncAll();
$this->inventoryManager->requestSyncAll();
$result = true;
}elseif($packet->trData instanceof UseItemTransactionData){
$result = $this->handleUseItemTransaction($packet->trData);
@ -334,96 +346,81 @@ class InGamePacketHandler extends PacketHandler{
$result = $this->handleReleaseItemTransaction($packet->trData);
}
if($this->craftingTransaction === null){ //don't sync if we're waiting to complete a crafting transaction
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
}
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
$this->inventoryManager->setCurrentItemStackRequestId(null);
return $result;
}
private function handleNormalTransaction(NormalTransactionData $data) : bool{
/** @var InventoryAction[] $actions */
$actions = [];
private function executeInventoryTransaction(InventoryTransaction $transaction, int $requestId) : bool{
$this->player->setUsingItem(false);
$isCraftingPart = false;
$converter = TypeConverter::getInstance();
foreach($data->getActions() as $networkInventoryAction){
if(
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO || (
$this->craftingTransaction !== null &&
!$networkInventoryAction->oldItem->getItemStack()->equals($networkInventoryAction->newItem->getItemStack()) &&
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
$networkInventoryAction->windowId === ContainerIds::UI &&
$networkInventoryAction->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT
)
){
$isCraftingPart = true;
}
$this->inventoryManager->setCurrentItemStackRequestId($requestId);
$this->inventoryManager->addTransactionPredictedSlotChanges($transaction);
try{
$transaction->execute();
}catch(TransactionValidationException $e){
$this->inventoryManager->requestSyncAll();
$logger = $this->session->getLogger();
$logger->debug("Invalid inventory transaction $requestId: " . $e->getMessage());
try{
$action = $converter->createInventoryAction($networkInventoryAction, $this->player, $this->inventoryManager);
if($action !== null){
$actions[] = $action;
}
}catch(TypeConversionException $e){
$this->session->getLogger()->debug("Error unpacking inventory action: " . $e->getMessage());
return false;
}
}
return false;
}catch(TransactionCancelledException){
$this->session->getLogger()->debug("Inventory transaction $requestId cancelled by a plugin");
if($isCraftingPart){
if($this->craftingTransaction === null){
//TODO: this might not be crafting if there is a special inventory open (anvil, enchanting, loom etc)
$this->craftingTransaction = new CraftingTransaction($this->player, $this->player->getServer()->getCraftingManager(), $actions);
}else{
foreach($actions as $action){
$this->craftingTransaction->addAction($action);
}
}
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;
}
$this->player->setUsingItem(false);
try{
$this->craftingTransaction->execute();
}catch(TransactionException $e){
$this->session->getLogger()->debug("Failed to execute crafting transaction: " . $e->getMessage());
return false;
}finally{
$this->craftingTransaction = null;
}
}else{
//normal transaction fallthru
if($this->craftingTransaction !== null){
$this->session->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction, refusing to execute crafting");
$this->craftingTransaction = null;
return false;
}
if(count($actions) === 0){
//TODO: 1.13+ often sends transactions with nothing but useless crap in them, no need for the debug noise
return true;
}
$this->player->setUsingItem(false);
$transaction = new InventoryTransaction($this->player, $actions);
try{
$transaction->execute();
}catch(TransactionException $e){
$logger = $this->session->getLogger();
$logger->debug("Failed to execute inventory transaction: " . $e->getMessage());
$logger->debug("Actions: " . json_encode($data->getActions()));
return false;
}
return false;
}finally{
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
$this->inventoryManager->setCurrentItemStackRequestId(null);
}
return true;
}
private function handleNormalTransaction(NormalTransactionData $data, int $itemStackRequestId) : bool{
//When the ItemStackRequest system is used, this transaction type is only used for dropping items by pressing Q.
//I don't know why they don't just use ItemStackRequest for that too, which already supports dropping items by
//clicking them outside an open inventory menu, but for now it is what it is.
//Fortunately, this means we can be extremely strict about the validation criteria.
if(count($data->getActions()) > 2){
throw new PacketHandlingException("Expected exactly 2 actions for dropping an item");
}
foreach($data->getActions() as $networkInventoryAction){
if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot == NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){
//drop item - we don't need to validate this, we only care about the count
//if the resulting actions don't match the client for some reason, it will trigger an automatic
//prediction rollback anyway.
//it's technically possible to see this more than once, but a normal client should never do that.
$droppedItemStack = $networkInventoryAction->newItem->getItemStack();
if($droppedItemStack->getCount() <= 0){
throw new PacketHandlingException("Expected positive count for dropped item");
}
$inventory = $this->player->getInventory();
$heldItem = $inventory->getItemInHand();
$heldItemStack = TypeConverter::getInstance()->coreItemStackToNet($heldItem);
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
if($heldItemStack->getCount() < $droppedItemStack->getCount() || !$heldItemStack->equalsWithoutCount($droppedItemStack)){
return false;
}
//this modifies $heldItem
$droppedItem = $heldItem->pop($droppedItemStack->getCount());
$builder = new TransactionBuilder();
$builder->getInventory($inventory)->setItem($inventory->getHeldItemIndex(), $heldItem);
$builder->addAction(new DropItemAction($droppedItem));
$transaction = new InventoryTransaction($this->player, $builder->generateActions());
return $this->executeInventoryTransaction($transaction, $itemStackRequestId);
}
}
throw new PacketHandlingException("Legacy 'normal' transactions should only be used for dropping items");
}
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
$this->player->selectHotbarSlot($data->getHotbarSlot());
@ -535,6 +532,43 @@ class InGamePacketHandler extends PacketHandler{
return false;
}
private function handleSingleItemStackRequest(ItemStackRequest $request) : ItemStackResponse{
if(count($request->getActions()) > 20){
//TODO: we can probably lower this limit, but this will do for now
throw new PacketHandlingException("Too many actions in ItemStackRequest");
}
$executor = new ItemStackRequestExecutor($this->player, $this->inventoryManager, $request);
try{
$transaction = $executor->generateInventoryTransaction();
$result = $this->executeInventoryTransaction($transaction, $request->getRequestId());
}catch(ItemStackRequestProcessException $e){
$result = false;
$this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage());
$this->session->getLogger()->debug(implode("\n", Utils::printableExceptionInfo($e)));
$this->inventoryManager->requestSyncAll();
}
if(!$result){
return new ItemStackResponse(ItemStackResponse::RESULT_ERROR, $request->getRequestId());
}
return $executor->buildItemStackResponse();
}
public function handleItemStackRequest(ItemStackRequestPacket $packet) : bool{
$responses = [];
if(count($packet->getRequests()) > 80){
//TODO: we can probably lower this limit, but this will do for now
throw new PacketHandlingException("Too many requests in ItemStackRequestPacket");
}
foreach($packet->getRequests() as $request){
$responses[] = $this->handleSingleItemStackRequest($request);
}
$this->session->sendDataPacket(ItemStackResponsePacket::create($responses));
return true;
}
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
if($packet->windowId === ContainerIds::OFFHAND){
return true; //this happens when we put an item into the offhand
@ -745,6 +779,15 @@ class InGamePacketHandler extends PacketHandler{
}
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
if($packet->skin->getFullSkinId() === $this->lastRequestedFullSkinId){
//TODO: HACK! In 1.19.60, the client sends its skin back to us if we sent it a skin different from the one
//it's using. We need to prevent this from causing a feedback loop.
$this->session->getLogger()->debug("Refused duplicate skin change request");
return true;
}
$this->lastRequestedFullSkinId = $packet->skin->getFullSkinId();
$this->session->getLogger()->debug("Processing skin change request");
try{
$skin = SkinAdapterSingleton::get()->fromSkinData($packet->skin);
}catch(InvalidSkinException $e){

View File

@ -0,0 +1,90 @@
<?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\handler;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
use pocketmine\network\PacketHandlingException;
final class ItemStackContainerIdTranslator{
private function __construct(){
//NOOP
}
public static function translate(int $containerInterfaceId, int $currentWindowId) : int{
return match($containerInterfaceId){
ContainerUIIds::ARMOR => ContainerIds::ARMOR,
ContainerUIIds::HOTBAR,
ContainerUIIds::INVENTORY,
ContainerUIIds::COMBINED_HOTBAR_AND_INVENTORY => ContainerIds::INVENTORY,
ContainerUIIds::OFFHAND => ContainerIds::OFFHAND,
ContainerUIIds::ANVIL_INPUT,
ContainerUIIds::ANVIL_MATERIAL,
ContainerUIIds::BEACON_PAYMENT,
ContainerUIIds::CARTOGRAPHY_ADDITIONAL,
ContainerUIIds::CARTOGRAPHY_INPUT,
ContainerUIIds::COMPOUND_CREATOR_INPUT,
ContainerUIIds::CRAFTING_INPUT,
ContainerUIIds::CREATED_OUTPUT,
ContainerUIIds::CURSOR,
ContainerUIIds::ENCHANTING_INPUT,
ContainerUIIds::ENCHANTING_MATERIAL,
ContainerUIIds::GRINDSTONE_ADDITIONAL,
ContainerUIIds::GRINDSTONE_INPUT,
ContainerUIIds::LAB_TABLE_INPUT,
ContainerUIIds::LOOM_DYE,
ContainerUIIds::LOOM_INPUT,
ContainerUIIds::LOOM_MATERIAL,
ContainerUIIds::MATERIAL_REDUCER_INPUT,
ContainerUIIds::MATERIAL_REDUCER_OUTPUT,
ContainerUIIds::SMITHING_TABLE_INPUT,
ContainerUIIds::SMITHING_TABLE_MATERIAL,
ContainerUIIds::STONECUTTER_INPUT,
ContainerUIIds::TRADE2_INGREDIENT1,
ContainerUIIds::TRADE2_INGREDIENT2,
ContainerUIIds::TRADE_INGREDIENT1,
ContainerUIIds::TRADE_INGREDIENT2 => ContainerIds::UI,
ContainerUIIds::BARREL,
ContainerUIIds::BLAST_FURNACE_INGREDIENT,
ContainerUIIds::BREWING_STAND_FUEL,
ContainerUIIds::BREWING_STAND_INPUT,
ContainerUIIds::BREWING_STAND_RESULT,
ContainerUIIds::FURNACE_FUEL,
ContainerUIIds::FURNACE_INGREDIENT,
ContainerUIIds::FURNACE_RESULT,
ContainerUIIds::LEVEL_ENTITY, //chest
ContainerUIIds::SHULKER_BOX,
ContainerUIIds::SMOKER_INGREDIENT => $currentWindowId,
//all preview slots are ignored, since the client shouldn't be modifying those directly
default => throw new PacketHandlingException("Unexpected container UI ID $containerInterfaceId")
};
}
}

View File

@ -0,0 +1,385 @@
<?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\handler;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\CreativeInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\CraftingTransaction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionBuilderInventory;
use pocketmine\item\Item;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingConsumeInputStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingCreateSpecificResultStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeAutoStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CreativeCreateStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DeprecatedCraftingResultsStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DestroyStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DropStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequest;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\ItemStackRequestSlotInfo;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\PlaceStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\SwapStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\TakeStackRequestAction;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use function array_key_first;
use function count;
use function spl_object_id;
class ItemStackRequestExecutor{
private TransactionBuilder $builder;
/** @var ItemStackRequestSlotInfo[] */
private array $requestSlotInfos = [];
private ?InventoryTransaction $specialTransaction = null;
/** @var Item[] */
private array $craftingResults = [];
private ?Item $nextCreatedItem = null;
private bool $createdItemFromCreativeInventory = false;
private int $createdItemsTakenCount = 0;
public function __construct(
private Player $player,
private InventoryManager $inventoryManager,
private ItemStackRequest $request
){
$this->builder = new TransactionBuilder();
}
protected function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{
if($inventory instanceof TransactionBuilderInventory){
$inventory = $inventory->getActualInventory();
}
return (new \ReflectionClass($inventory))->getShortName() . "#" . spl_object_id($inventory) . ", slot: $slot";
}
/**
* @throws ItemStackRequestProcessException
*/
private function matchItemStack(Inventory $inventory, int $slotId, int $clientItemStackId) : void{
$info = $this->inventoryManager->getItemStackInfo($inventory, $slotId);
if($info === null){
throw new AssumptionFailedError("The inventory is tracked and the slot is valid, so this should not be null");
}
if(!($clientItemStackId < 0 ? $info->getRequestId() === $clientItemStackId : $info->getStackId() === $clientItemStackId)){
throw new ItemStackRequestProcessException(
$this->prettyInventoryAndSlot($inventory, $slotId) . ": " .
"Mismatched expected itemstack, " .
"client expected: $clientItemStackId, server actual: " . $info->getStackId() . ", last modified by request: " . ($info->getRequestId() ?? "none")
);
}
}
/**
* @phpstan-return array{TransactionBuilderInventory, int}
*
* @throws ItemStackRequestProcessException
*/
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $info->getSlotId());
if($windowAndSlot === null){
throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerId() . ", slot ID: " . $info->getSlotId());
}
[$inventory, $slot] = $windowAndSlot;
if(!$inventory->slotExists($slot)){
throw new ItemStackRequestProcessException("No such inventory slot :" . $this->prettyInventoryAndSlot($inventory, $slot));
}
if($info->getStackId() !== $this->request->getRequestId()){ //the itemstack may have been modified by the current request
$this->matchItemStack($inventory, $slot, $info->getStackId());
}
return [$this->builder->getInventory($inventory), $slot];
}
/**
* @throws ItemStackRequestProcessException
*/
protected function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequestSlotInfo $destination, int $count) : void{
$removed = $this->removeItemFromSlot($source, $count);
$this->addItemToSlot($destination, $removed, $count);
}
/**
* Deducts items from an inventory slot, returning a stack containing the removed items.
* @throws ItemStackRequestProcessException
*/
protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
$this->requestSlotInfos[] = $slotInfo;
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
}
$existingItem = $inventory->getItem($slot);
if($existingItem->getCount() < $count){
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take $count items from a stack of " . $existingItem->getCount());
}
$removed = $existingItem->pop($count);
$inventory->setItem($slot, $existingItem);
return $removed;
}
/**
* Adds items to the target slot, if they are stackable.
* @throws ItemStackRequestProcessException
*/
protected function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, int $count) : void{
$this->requestSlotInfos[] = $slotInfo;
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
}
$existingItem = $inventory->getItem($slot);
if(!$existingItem->isNull() && !$existingItem->canStackWith($item)){
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Can only add items to an empty slot, or a slot containing the same item");
}
//we can't use the existing item here; it may be an empty stack
$newItem = clone $item;
$newItem->setCount($existingItem->getCount() + $count);
$inventory->setItem($slot, $newItem);
}
/**
* @throws ItemStackRequestProcessException
*/
protected function setNextCreatedItem(?Item $item, bool $creative = false) : void{
if($item !== null && $item->isNull()){
$item = null;
}
if($this->nextCreatedItem !== null){
//while this is more complicated than simply adding the action when the item is taken, this ensures that
//plugins can tell the difference between 1 item that got split into 2 slots, vs 2 separate items.
if($this->createdItemFromCreativeInventory && $this->createdItemsTakenCount > 0){
$this->nextCreatedItem->setCount($this->createdItemsTakenCount);
$this->builder->addAction(new CreateItemAction($this->nextCreatedItem));
}elseif($this->createdItemsTakenCount < $this->nextCreatedItem->getCount()){
throw new ItemStackRequestProcessException("Not all of the previous created item was taken");
}
}
$this->nextCreatedItem = $item;
$this->createdItemFromCreativeInventory = $creative;
$this->createdItemsTakenCount = 0;
}
/**
* @throws ItemStackRequestProcessException
*/
protected function beginCrafting(int $recipeId, int $repetitions) : void{
if($this->specialTransaction !== null){
throw new ItemStackRequestProcessException("Another special transaction is already in progress");
}
if($repetitions < 1){
throw new ItemStackRequestProcessException("Cannot craft a recipe less than 1 time");
}
if($repetitions > 256){
//TODO: we can probably lower this limit to 64, but I'm unsure if there are cases where the client may
//request more than 64 repetitions of a recipe.
//It's already hard-limited to 256 repetitions in the protocol, so this is just a sanity check.
throw new ItemStackRequestProcessException("Cannot craft a recipe more than 256 times");
}
$craftingManager = $this->player->getServer()->getCraftingManager();
$recipe = $craftingManager->getCraftingRecipeFromIndex($recipeId);
if($recipe === null){
throw new ItemStackRequestProcessException("No such crafting recipe index: $recipeId");
}
$this->specialTransaction = new CraftingTransaction($this->player, $craftingManager, [], $recipe, $repetitions);
$currentWindow = $this->player->getCurrentWindow();
if($currentWindow !== null && !($currentWindow instanceof CraftingGrid)){
throw new ItemStackRequestProcessException("Player's current window is not a crafting grid");
}
$craftingGrid = $currentWindow ?? $this->player->getCraftingGrid();
$craftingResults = $recipe->getResultsFor($craftingGrid);
foreach($craftingResults as $k => $craftingResult){
$craftingResult->setCount($craftingResult->getCount() * $repetitions);
$this->craftingResults[$k] = $craftingResult;
}
if(count($this->craftingResults) === 1){
//for multi-output recipes, later actions will tell us which result to create and when
$this->setNextCreatedItem($this->craftingResults[array_key_first($this->craftingResults)]);
}
}
/**
* @throws ItemStackRequestProcessException
*/
protected function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException("Cannot take less than 1 created item");
}
$createdItem = $this->nextCreatedItem;
if($createdItem === null){
throw new ItemStackRequestProcessException("No created item is waiting to be taken");
}
if(!$this->createdItemFromCreativeInventory){
$availableCount = $createdItem->getCount() - $this->createdItemsTakenCount;
if($count > $availableCount){
throw new ItemStackRequestProcessException("Not enough created items available to be taken (have $availableCount, tried to take $count)");
}
}
$this->createdItemsTakenCount += $count;
$this->addItemToSlot($destination, $createdItem, $count);
if(!$this->createdItemFromCreativeInventory && $this->createdItemsTakenCount >= $createdItem->getCount()){
$this->setNextCreatedItem(null);
}
}
/**
* @throws ItemStackRequestProcessException
*/
private function assertDoingCrafting() : void{
if(!$this->specialTransaction instanceof CraftingTransaction){
if($this->specialTransaction === null){
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
}else{
throw new ItemStackRequestProcessException("A different special transaction is already in progress");
}
}
}
/**
* @throws ItemStackRequestProcessException
*/
protected function processItemStackRequestAction(ItemStackRequestAction $action) : void{
if(
$action instanceof TakeStackRequestAction ||
$action instanceof PlaceStackRequestAction
){
$source = $action->getSource();
$destination = $action->getDestination();
if($source->getContainerId() === ContainerUIIds::CREATED_OUTPUT && $source->getSlotId() === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
$this->takeCreatedItem($destination, $action->getCount());
}else{
$this->transferItems($source, $destination, $action->getCount());
}
}elseif($action instanceof SwapStackRequestAction){
$this->requestSlotInfos[] = $action->getSlot1();
$this->requestSlotInfos[] = $action->getSlot2();
[$inventory1, $slot1] = $this->getBuilderInventoryAndSlot($action->getSlot1());
[$inventory2, $slot2] = $this->getBuilderInventoryAndSlot($action->getSlot2());
$item1 = $inventory1->getItem($slot1);
$item2 = $inventory2->getItem($slot2);
$inventory1->setItem($slot1, $item2);
$inventory2->setItem($slot2, $item1);
}elseif($action instanceof DropStackRequestAction){
//TODO: this action has a "randomly" field, I have no idea what it's used for
$dropped = $this->removeItemFromSlot($action->getSource(), $action->getCount());
$this->builder->addAction(new DropItemAction($dropped));
}elseif($action instanceof DestroyStackRequestAction){
$destroyed = $this->removeItemFromSlot($action->getSource(), $action->getCount());
$this->builder->addAction(new DestroyItemAction($destroyed));
}elseif($action instanceof CreativeCreateStackRequestAction){
$item = CreativeInventory::getInstance()->getItem($action->getCreativeItemId());
if($item === null){
throw new ItemStackRequestProcessException("No such creative item index: " . $action->getCreativeItemId());
}
$this->setNextCreatedItem($item, true);
}elseif($action instanceof CraftRecipeStackRequestAction){
$this->beginCrafting($action->getRecipeId(), 1);
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
}elseif($action instanceof CraftingConsumeInputStackRequestAction){
$this->assertDoingCrafting();
$this->removeItemFromSlot($action->getSource(), $action->getCount()); //output discarded - we allow CraftingTransaction to verify the balance
}elseif($action instanceof CraftingCreateSpecificResultStackRequestAction){
$this->assertDoingCrafting();
$nextResultItem = $this->craftingResults[$action->getResultIndex()] ?? null;
if($nextResultItem === null){
throw new ItemStackRequestProcessException("No such crafting result index: " . $action->getResultIndex());
}
$this->setNextCreatedItem($nextResultItem);
}elseif($action instanceof DeprecatedCraftingResultsStackRequestAction){
//no obvious use
}else{
throw new ItemStackRequestProcessException("Unhandled item stack request action");
}
}
/**
* @throws ItemStackRequestProcessException
*/
public function generateInventoryTransaction() : InventoryTransaction{
foreach($this->request->getActions() as $k => $action){
try{
$this->processItemStackRequestAction($action);
}catch(ItemStackRequestProcessException $e){
throw new ItemStackRequestProcessException("Error processing action $k (" . (new \ReflectionClass($action))->getShortName() . "): " . $e->getMessage(), 0, $e);
}
}
$this->setNextCreatedItem(null);
$inventoryActions = $this->builder->generateActions();
$transaction = $this->specialTransaction ?? new InventoryTransaction($this->player);
foreach($inventoryActions as $action){
$transaction->addAction($action);
}
return $transaction;
}
public function buildItemStackResponse() : ItemStackResponse{
$builder = new ItemStackResponseBuilder($this->request->getRequestId(), $this->inventoryManager);
foreach($this->requestSlotInfos as $requestInfo){
$builder->addSlot($requestInfo->getContainerId(), $requestInfo->getSlotId());
}
return $builder->build();
}
}

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\handler;
/**
* Thrown when an error occurs during processing of an ItemStackRequest.
*/
final class ItemStackRequestProcessException extends \RuntimeException{
}

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\handler;
use pocketmine\inventory\Inventory;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponseContainerInfo;
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponseSlotInfo;
use pocketmine\utils\AssumptionFailedError;
final class ItemStackResponseBuilder{
/**
* @var int[][]
* @phpstan-var array<int, array<int, int>>
*/
private array $changedSlots = [];
public function __construct(
private int $requestId,
private InventoryManager $inventoryManager
){}
public function addSlot(int $containerInterfaceId, int $slotId) : void{
$this->changedSlots[$containerInterfaceId][$slotId] = $slotId;
}
/**
* @phpstan-return array{Inventory, int}
*/
private function getInventoryAndSlot(int $containerInterfaceId, int $slotId) : ?array{
$windowId = ItemStackContainerIdTranslator::translate($containerInterfaceId, $this->inventoryManager->getCurrentWindowId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slotId);
if($windowAndSlot === null){
return null;
}
[$inventory, $slot] = $windowAndSlot;
if(!$inventory->slotExists($slot)){
return null;
}
return [$inventory, $slot];
}
public function build() : ItemStackResponse{
$responseInfosByContainer = [];
foreach($this->changedSlots as $containerInterfaceId => $slotIds){
if($containerInterfaceId === ContainerUIIds::CREATED_OUTPUT){
continue;
}
foreach($slotIds as $slotId){
$inventoryAndSlot = $this->getInventoryAndSlot($containerInterfaceId, $slotId);
if($inventoryAndSlot === null){
//a plugin may have closed the inventory during an event, or the slot may have been invalid
continue;
}
[$inventory, $slot] = $inventoryAndSlot;
$itemStackInfo = $this->inventoryManager->getItemStackInfo($inventory, $slot);
if($itemStackInfo === null){
throw new AssumptionFailedError("ItemStackInfo should never be null for an open inventory");
}
$item = $inventory->getItem($slot);
$responseInfosByContainer[$containerInterfaceId][] = new ItemStackResponseSlotInfo(
$slotId,
$slotId,
$item->getCount(),
$itemStackInfo->getStackId(),
$item->getCustomName(),
0
);
}
}
$responseContainerInfos = [];
foreach($responseInfosByContainer as $containerInterfaceId => $responseInfos){
$responseContainerInfos[] = new ItemStackResponseContainerInfo($containerInterfaceId, $responseInfos);
}
return new ItemStackResponse(ItemStackResponse::RESULT_OK, $this->requestId, $responseContainerInfos);
}
}

View File

@ -25,7 +25,6 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\entity\InvalidSkinException;
use pocketmine\event\player\PlayerPreLoginEvent;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\KnownTranslationKeys;
use pocketmine\network\mcpe\auth\ProcessLoginTask;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
@ -33,8 +32,6 @@ use pocketmine\network\mcpe\JwtException;
use pocketmine\network\mcpe\JwtUtils;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
use pocketmine\network\mcpe\protocol\types\login\ClientData;
use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper;
@ -63,18 +60,6 @@ class LoginPacketHandler extends PacketHandler{
){}
public function handleLogin(LoginPacket $packet) : bool{
if(!$this->isCompatibleProtocol($packet->protocol)){
$this->session->sendDataPacket(PlayStatusPacket::create($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
$this->session->disconnect(
$this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $packet->protocol)),
false
);
return true;
}
$extraData = $this->fetchAuthData($packet->chainDataJwt);
if(!Player::isValidUserName($extraData->displayName)){
@ -84,6 +69,7 @@ class LoginPacketHandler extends PacketHandler{
}
$clientData = $this->parseClientData($packet->clientDataJwt);
try{
$skin = SkinAdapterSingleton::get()->fromSkinData(ClientDataToSkinDataHelper::fromClientData($clientData));
}catch(\InvalidArgumentException | InvalidSkinException $e){
@ -215,8 +201,4 @@ class LoginPacketHandler extends PacketHandler{
$this->server->getAsyncPool()->submitTask(new ProcessLoginTask($packet->chainDataJwt->chain, $packet->clientDataJwt, $authRequired, $this->authCallback));
$this->session->setHandler(null); //drop packets received during login verification
}
protected function isCompatibleProtocol(int $protocolVersion) : bool{
return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL;
}
}

View File

@ -98,7 +98,7 @@ class PreSpawnPacketHandler extends PacketHandler{
0,
0,
"",
false,
true,
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
Uuid::fromString(Uuid::NIL),
false,
@ -114,7 +114,7 @@ class PreSpawnPacketHandler extends PacketHandler{
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
$this->session->getLogger()->debug("Sending attributes");
$this->session->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
$this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll());
$this->session->getLogger()->debug("Sending available commands");
$this->session->syncAvailableCommands();
@ -125,7 +125,7 @@ class PreSpawnPacketHandler extends PacketHandler{
$this->session->getLogger()->debug("Sending effects");
foreach($this->player->getEffects()->all() as $effect){
$this->session->onEntityEffectAdded($this->player, $effect, false);
$this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false);
}
$this->session->getLogger()->debug("Sending actor metadata");

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
final class SpawnResponsePacketHandler extends PacketHandler{
@ -37,6 +38,13 @@ final class SpawnResponsePacketHandler extends PacketHandler{
return true;
}
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
//TODO: REMOVE THIS
//As of 1.19.60, we receive this packet during pre-spawn for no obvious reason. The skin is still sent in the
//login packet, so we can ignore this one. If unhandled, this packet makes a huge debug spam in the log.
return true;
}
public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
//the client will send this every tick once we start sending chunks, but we don't handle it in this stage
//this is very spammy so we filter it out

View File

@ -26,11 +26,12 @@ namespace pocketmine\network\mcpe\raklib;
use pocketmine\network\AdvancedNetworkInterface;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\StandardPacketBroadcaster;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\PacketHandlingException;
@ -78,10 +79,16 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
private SleeperNotifier $sleeper;
private PacketBroadcaster $broadcaster;
private PacketBroadcaster $packetBroadcaster;
private EntityEventBroadcaster $entityEventBroadcaster;
private PacketSerializerContext $packetSerializerContext;
public function __construct(Server $server, string $ip, int $port, bool $ipV6){
public function __construct(Server $server, string $ip, int $port, bool $ipV6, PacketBroadcaster $packetBroadcaster, EntityEventBroadcaster $entityEventBroadcaster, PacketSerializerContext $packetSerializerContext){
$this->server = $server;
$this->packetBroadcaster = $packetBroadcaster;
$this->packetSerializerContext = $packetSerializerContext;
$this->entityEventBroadcaster = $entityEventBroadcaster;
$this->rakServerId = mt_rand(0, PHP_INT_MAX);
$this->sleeper = new SleeperNotifier();
@ -105,8 +112,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->interface = new UserToRakLibThreadMessageSender(
new PthreadsChannelWriter($mainToThreadBuffer)
);
$this->broadcaster = new StandardPacketBroadcaster($this->server);
}
public function start() : void{
@ -166,8 +171,10 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->server,
$this->network->getSessionManager(),
PacketPool::getInstance(),
$this->packetSerializerContext,
new RakLibPacketSender($sessionId, $this),
$this->broadcaster,
$this->packetBroadcaster,
$this->entityEventBroadcaster,
ZlibCompressor::getInstance(), //TODO: this shouldn't be hardcoded, but we might need the RakNet protocol version to select it
$address,
$port

View File

@ -1212,6 +1212,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @param Vector3 $newPos Coordinates of the player's feet, centered horizontally at the base of their bounding box.
*/
public function handleMovement(Vector3 $newPos) : void{
Timings::$playerMove->startTiming();
try{
$this->actuallyHandleMovement($newPos);
}finally{
Timings::$playerMove->stopTiming();
}
}
private function actuallyHandleMovement(Vector3 $newPos) : void{
$this->moveRateLimit--;
if($this->moveRateLimit < 0){
return;
@ -1365,13 +1374,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->timings->startTiming();
if($this->spawned){
Timings::$playerMove->startTiming();
$this->processMostRecentMovements();
$this->motion = new Vector3(0, 0, 0); //TODO: HACK! (Fixes player knockback being messed up)
$this->motion = Vector3::zero(); //TODO: HACK! (Fixes player knockback being messed up)
if($this->onGround){
$this->inAirTicks = 0;
}else{
$this->inAirTicks += $tickDiff;
}
Timings::$playerMove->stopTiming();
Timings::$entityBaseTick->startTiming();
$this->entityBaseTick($tickDiff);

View File

@ -27,9 +27,7 @@ use pocketmine\block\tile\Tile;
use pocketmine\entity\Entity;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\player\Player;
use pocketmine\scheduler\TaskHandler;
use function dechex;
abstract class Timings{
public const INCLUDED_BY_OTHER_TIMINGS_PREFIX = "** ";
@ -52,8 +50,15 @@ abstract class Timings{
public static $playerNetworkSend;
/** @var TimingsHandler */
public static $playerNetworkSendCompress;
public static TimingsHandler $playerNetworkSendCompressBroadcast;
public static TimingsHandler $playerNetworkSendCompressSessionBuffer;
/** @var TimingsHandler */
public static $playerNetworkSendEncrypt;
public static TimingsHandler $playerNetworkSendInventorySync;
/** @var TimingsHandler */
public static $playerNetworkReceive;
/** @var TimingsHandler */
@ -87,6 +92,12 @@ abstract class Timings{
/** @var TimingsHandler */
public static $entityMove;
public static TimingsHandler $entityMoveCollision;
public static TimingsHandler $projectileMove;
public static TimingsHandler $projectileMoveRayTrace;
/** @var TimingsHandler */
public static $playerCheckNearEntities;
/** @var TimingsHandler */
@ -99,6 +110,8 @@ abstract class Timings{
/** @var TimingsHandler */
public static $livingEntityBaseTick;
public static TimingsHandler $itemEntityBaseTick;
/** @var TimingsHandler */
public static $schedulerSync;
/** @var TimingsHandler */
@ -127,6 +140,9 @@ abstract class Timings{
/** @var TimingsHandler[] */
private static array $packetHandleTimingMap = [];
/** @var TimingsHandler[] */
private static array $packetEncodeTimingMap = [];
/** @var TimingsHandler[] */
public static $packetSendTimingMap = [];
/** @var TimingsHandler[] */
@ -135,6 +151,8 @@ abstract class Timings{
/** @var TimingsHandler */
public static $broadcastPackets;
public static TimingsHandler $playerMove;
public static function init() : void{
if(self::$initialized){
return;
@ -148,19 +166,24 @@ abstract class Timings{
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
self::$titleTick = new TimingsHandler("Console Title Tick");
self::$playerNetworkSend = new TimingsHandler("Player Network Send");
self::$playerNetworkSendCompress = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression", self::$playerNetworkSend);
self::$playerNetworkSendEncrypt = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Encryption", self::$playerNetworkSend);
self::$connection = new TimingsHandler("Connection Handler");
self::$playerNetworkReceive = new TimingsHandler("Player Network Receive");
self::$playerNetworkSend = new TimingsHandler("Player Network Send", self::$connection);
self::$playerNetworkSendCompress = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression", self::$playerNetworkSend);
self::$playerNetworkSendCompressBroadcast = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress);
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress);
self::$playerNetworkSendEncrypt = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Encryption", self::$playerNetworkSend);
self::$playerNetworkSendInventorySync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Inventory Sync", self::$playerNetworkSend);
self::$playerNetworkReceive = new TimingsHandler("Player Network Receive", self::$connection);
self::$playerNetworkReceiveDecompress = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Receive - Decompression", self::$playerNetworkReceive);
self::$playerNetworkReceiveDecrypt = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Receive - Decryption", self::$playerNetworkReceive);
self::$broadcastPackets = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Broadcast Packets", self::$playerNetworkSend);
self::$playerMove = new TimingsHandler("Player Movement");
self::$playerChunkOrder = new TimingsHandler("Player Order Chunks");
self::$playerChunkSend = new TimingsHandler("Player Send Chunks");
self::$connection = new TimingsHandler("Connection Handler");
self::$playerChunkSend = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Chunks", self::$playerNetworkSend);
self::$scheduler = new TimingsHandler("Scheduler");
self::$serverCommand = new TimingsHandler("Server Command");
self::$worldLoad = new TimingsHandler("World Load");
@ -174,19 +197,25 @@ abstract class Timings{
self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load");
self::$syncPlayerDataSave = new TimingsHandler("Player Data Save");
self::$entityMove = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "entityMove");
self::$playerCheckNearEntities = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "checkNearEntities");
self::$tickEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "tickEntity");
self::$tickTileEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "tickTileEntity");
self::$entityMove = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Movement");
self::$entityMoveCollision = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Movement - Collision Checks", self::$entityMove);
self::$entityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "entityBaseTick");
self::$livingEntityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "livingEntityBaseTick");
self::$projectileMove = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Projectile Movement", self::$entityMove);
self::$projectileMoveRayTrace = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Projectile Movement - Ray Tracing", self::$projectileMove);
self::$playerCheckNearEntities = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "checkNearEntities");
self::$tickEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Tick");
self::$tickTileEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Block Entity Tick");
self::$entityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick");
self::$livingEntityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick - Living");
self::$itemEntityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick - ItemEntity");
self::$schedulerSync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Scheduler - Sync Tasks");
self::$schedulerAsync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Scheduler - Async Tasks");
self::$playerCommand = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "playerCommand");
self::$craftingDataCacheRebuild = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "craftingDataCacheRebuild");
self::$playerCommand = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Command");
self::$craftingDataCacheRebuild = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Build CraftingDataPacket Cache");
}
@ -209,11 +238,7 @@ abstract class Timings{
public static function getEntityTimings(Entity $entity) : TimingsHandler{
$entityType = (new \ReflectionClass($entity))->getShortName();
if(!isset(self::$entityTypeTimingMap[$entityType])){
if($entity instanceof Player){
self::$entityTypeTimingMap[$entityType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "tickEntity - EntityPlayer", self::$tickEntity);
}else{
self::$entityTypeTimingMap[$entityType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "tickEntity - " . $entityType, self::$tickEntity);
}
self::$entityTypeTimingMap[$entityType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Tick - " . $entityType, self::$tickEntity);
}
return self::$entityTypeTimingMap[$entityType];
@ -222,7 +247,7 @@ abstract class Timings{
public static function getTileEntityTimings(Tile $tile) : TimingsHandler{
$tileType = (new \ReflectionClass($tile))->getShortName();
if(!isset(self::$tileEntityTypeTimingMap[$tileType])){
self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "tickTileEntity - " . $tileType, self::$tickTileEntity);
self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Block Entity Tick - " . $tileType, self::$tickTileEntity);
}
return self::$tileEntityTypeTimingMap[$tileType];
@ -231,8 +256,7 @@ abstract class Timings{
public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
if(!isset(self::$packetReceiveTimingMap[$pid])){
$pkName = (new \ReflectionClass($pk))->getShortName();
self::$packetReceiveTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "receivePacket - " . $pkName . " [0x" . dechex($pid) . "]", self::$playerNetworkReceive);
self::$packetReceiveTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Receive - " . $pk->getName(), self::$playerNetworkReceive);
}
return self::$packetReceiveTimingMap[$pid];
@ -241,7 +265,7 @@ abstract class Timings{
public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetDecodeTimingMap[$pid] ??= new TimingsHandler(
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Decode - " . $pk->getName() . " [0x" . dechex($pid) . "]",
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Decode - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk)
);
}
@ -249,16 +273,23 @@ abstract class Timings{
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetHandleTimingMap[$pid] ??= new TimingsHandler(
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Handler - " . $pk->getName() . " [0x" . dechex($pid) . "]",
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Handler - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk)
);
}
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetEncodeTimingMap[$pid] ??= new TimingsHandler(
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Encode - " . $pk->getName(),
self::getSendDataPacketTimings($pk)
);
}
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
if(!isset(self::$packetSendTimingMap[$pid])){
$pkName = (new \ReflectionClass($pk))->getShortName();
self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "sendPacket - " . $pkName . " [0x" . dechex($pid) . "]", self::$playerNetworkSend);
self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Send - " . $pk->getName(), self::$playerNetworkSend);
}
return self::$packetSendTimingMap[$pid];

View File

@ -92,7 +92,7 @@ abstract class Terminal{
self::$FORMAT_RESET = "\x1b[m";
$color = fn(int $code) => "\x1b[38;5;${code}m";
$color = fn(int $code) => "\x1b[38;5;{$code}m";
self::$COLOR_BLACK = $color(16);
self::$COLOR_DARK_BLUE = $color(19);

View File

@ -65,6 +65,7 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\NetworkBroadcastUtils;
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
@ -685,7 +686,7 @@ class World implements ChunkManager{
$this->broadcastPacketToViewers($pos, $e);
}
}else{
$this->server->broadcastPackets($this->filterViewersForPosition($pos, $players), $pk);
NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $players), $pk);
}
}
}
@ -711,7 +712,7 @@ class World implements ChunkManager{
$this->broadcastPacketToViewers($pos, $e);
}
}else{
$this->server->broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk);
NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk);
}
}
}
@ -1021,7 +1022,7 @@ class World implements ChunkManager{
World::getXZ($index, $chunkX, $chunkZ);
$chunkPlayers = $this->getChunkPlayers($chunkX, $chunkZ);
if(count($chunkPlayers) > 0){
$this->server->broadcastPackets($chunkPlayers, $entries);
NetworkBroadcastUtils::broadcastPackets($chunkPlayers, $entries);
}
}
@ -3118,59 +3119,63 @@ class World implements ChunkManager{
Timings::$population->startTiming();
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
if($this->isChunkLocked($chunkX + $xx, $chunkZ + $zz)){
//chunk is already in use by another generation request; queue the request for later
return $resolver?->getPromise() ?? $this->enqueuePopulationRequest($chunkX, $chunkZ, $associatedChunkLoader);
try{
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
if($this->isChunkLocked($chunkX + $xx, $chunkZ + $zz)){
//chunk is already in use by another generation request; queue the request for later
return $resolver?->getPromise() ?? $this->enqueuePopulationRequest($chunkX, $chunkZ, $associatedChunkLoader);
}
}
}
}
$this->activeChunkPopulationTasks[$chunkHash] = true;
if($resolver === null){
$resolver = new PromiseResolver();
$this->chunkPopulationRequestMap[$chunkHash] = $resolver;
}
$chunkPopulationLockId = new ChunkLockId();
$temporaryChunkLoader = new class implements ChunkLoader{};
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->lockChunk($chunkX + $xx, $chunkZ + $zz, $chunkPopulationLockId);
$this->registerChunkLoader($temporaryChunkLoader, $chunkX + $xx, $chunkZ + $zz);
$this->activeChunkPopulationTasks[$chunkHash] = true;
if($resolver === null){
$resolver = new PromiseResolver();
$this->chunkPopulationRequestMap[$chunkHash] = $resolver;
}
}
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
$task = new PopulationTask(
$this->worldId,
$chunkX,
$chunkZ,
$centerChunk,
$adjacentChunks,
function(Chunk $centerChunk, array $adjacentChunks) use ($chunkPopulationLockId, $chunkX, $chunkZ, $temporaryChunkLoader) : void{
if(!$this->isLoaded()){
return;
$chunkPopulationLockId = new ChunkLockId();
$temporaryChunkLoader = new class implements ChunkLoader{
};
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->lockChunk($chunkX + $xx, $chunkZ + $zz, $chunkPopulationLockId);
$this->registerChunkLoader($temporaryChunkLoader, $chunkX + $xx, $chunkZ + $zz);
}
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
}
);
$workerId = $this->workerPool->selectWorker();
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
unset($this->generatorRegisteredWorkers[$workerId]);
}
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->workerPool->submitTaskToWorker($task, $workerId);
Timings::$population->stopTiming();
return $resolver->getPromise();
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
$task = new PopulationTask(
$this->worldId,
$chunkX,
$chunkZ,
$centerChunk,
$adjacentChunks,
function(Chunk $centerChunk, array $adjacentChunks) use ($chunkPopulationLockId, $chunkX, $chunkZ, $temporaryChunkLoader) : void{
if(!$this->isLoaded()){
return;
}
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
}
);
$workerId = $this->workerPool->selectWorker();
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
unset($this->generatorRegisteredWorkers[$workerId]);
}
if(!isset($this->generatorRegisteredWorkers[$workerId])){
$this->registerGeneratorToWorker($workerId);
}
$this->workerPool->submitTaskToWorker($task, $workerId);
return $resolver->getPromise();
}finally{
Timings::$population->stopTiming();
}
}
/**

View File

@ -40,7 +40,7 @@ final class GeneratorManager{
private array $list = [];
public function __construct(){
$this->addGenerator(Flat::class, "flat", \Closure::fromCallable(function(string $preset) : ?InvalidGeneratorOptionsException{
$this->addGenerator(Flat::class, "flat", function(string $preset) : ?InvalidGeneratorOptionsException{
if($preset === ""){
return null;
}
@ -50,7 +50,7 @@ final class GeneratorManager{
}catch(InvalidGeneratorOptionsException $e){
return $e;
}
}));
});
$this->addGenerator(Normal::class, "normal", fn() => null);
$this->addGenerator(Normal::class, "default", fn() => null);
$this->addGenerator(Nether::class, "hell", fn() => null);

View File

@ -23,7 +23,7 @@ if [ "$PHP_BINARY" == "" ]; then
if [ -f ./bin/php7/bin/php ]; then
export PHPRC=""
PHP_BINARY="./bin/php7/bin/php"
elif [[ ! -z $(type php 2> /dev/null) ]]; then
elif [[ -n $(type php 2> /dev/null) ]]; then
PHP_BINARY=$(type -p php)
else
echo "Couldn't find a PHP binary in system PATH or $PWD/bin/php7/bin"
@ -51,12 +51,12 @@ if [ "$DO_LOOP" == "yes" ]; then
if [ ${LOOPS} -gt 0 ]; then
echo "Restarted $LOOPS times"
fi
"$PHP_BINARY" "$POCKETMINE_FILE" $@
"$PHP_BINARY" "$POCKETMINE_FILE" "$@"
echo "To escape the loop, press CTRL+C now. Otherwise, wait 5 seconds for the server to restart."
echo ""
sleep 5
((LOOPS++))
done
else
exec "$PHP_BINARY" "$POCKETMINE_FILE" $@
exec "$PHP_BINARY" "$POCKETMINE_FILE" "$@"
fi

View File

@ -5,11 +5,6 @@ parameters:
count: 2
path: ../../../build/make-release.php
-
message: "#^Parameter \\#1 \\$pharPath of function pocketmine\\\\build\\\\server_phar\\\\buildPhar expects string, array\\<int, mixed\\>\\|string\\|false given\\.$#"
count: 1
path: ../../../build/server-phar.php
-
message: "#^Parameter \\#1 \\$strings of function pocketmine\\\\build\\\\server_phar\\\\preg_quote_array expects array\\<string\\>, array\\<int, string\\|false\\> given\\.$#"
count: 1
@ -25,16 +20,6 @@ parameters:
count: 1
path: ../../../src/Server.php
-
message: "#^Parameter \\#1 \\$input of function yaml_parse expects string, string\\|false given\\.$#"
count: 1
path: ../../../src/Server.php
-
message: "#^Cannot cast array\\<int, mixed\\>\\|string\\|false to string\\.$#"
count: 1
path: ../../../src/ServerConfigGroup.php
-
message: "#^Cannot cast mixed to int\\.$#"
count: 2
@ -505,11 +490,6 @@ parameters:
count: 1
path: ../../../src/command/defaults/TimingsCommand.php
-
message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#"
count: 2
path: ../../../src/console/ConsoleReader.php
-
message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#"
count: 1
@ -590,11 +570,6 @@ parameters:
count: 1
path: ../../../src/inventory/CreativeInventory.php
-
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
count: 1
path: ../../../src/inventory/CreativeInventory.php
-
message: "#^Parameter \\#2 \\$recipe of class pocketmine\\\\event\\\\inventory\\\\CraftItemEvent constructor expects pocketmine\\\\crafting\\\\CraftingRecipe, pocketmine\\\\crafting\\\\CraftingRecipe\\|null given\\.$#"
count: 1
@ -670,21 +645,6 @@ parameters:
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAbilities\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 2
@ -705,6 +665,21 @@ parameters:
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
path: ../../../src/network/mcpe/NetworkSession.php
-
message: "#^Parameter \\#2 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#"
count: 1
@ -1315,11 +1290,6 @@ parameters:
count: 3
path: ../../../src/world/light/SkyLightUpdate.php
-
message: "#^Only numeric types are allowed in \\+, int\\|false given on the left side\\.$#"
count: 1
path: ../../../src/world/light/SkyLightUpdate.php
-
message: "#^Only numeric types are allowed in \\-, int\\|null given on the right side\\.$#"
count: 1