Compare commits

..

64 Commits

Author SHA1 Message Date
e7bdaa8579 Release 4.20.5 2023-05-30 14:35:17 +01:00
76749cbaa7 Use fork of JsonMapper to solve cweiske/jsonmapper#210 2023-05-30 14:30:28 +01:00
c3ceeeace7 Release 4.20.4 2023-05-06 18:20:18 +01:00
aac4f6c0e1 Fixed all game modes allowing flight
moral of the story: do not trust that mojang things do what they say they do - the spectator ability layer always applies, regardless of whether the player is actually in spectator mode or not ...
2023-05-06 18:18:05 +01:00
3b893961e4 4.20.4 is next 2023-05-06 17:01:49 +01:00
325ffec1be Release 4.20.3 2023-05-06 17:01:49 +01:00
fa715a074a Fixed TimingsHandler depth not getting reset when timings is disabled
When timings was disabled, internalStopTiming is not called, and timer depth is not decremented.
If timings is later reenabled, the next call to internalStartTiming will think the timer is already running, and won't generate any new records for the timer.
This has led to broken timings reports with missing Full Server Tick entries, amongst other things.
2023-05-06 16:56:39 +01:00
4caa2c7690 NetworkSession: send FLYING flag on spectator ability layer
fixes #5722

I'm not very clear why this works. PM doesn't use real spectator mode yet (we're still using the faux spectator mode PM has had for years, because I haven't yet assessed how real spectator mode will affect stuff like block interactions), so this ability layer shouldn't have any effect.

thank you @Alemiz112
2023-05-06 15:54:23 +01:00
d04da9b1d8 Reuse timings handlers for event handlers of the same events
due to direct repeated usage of registerEvent() with closures, we've seen some libraries like muqsit/SimplePacketHandler generate very large timings reports, because a new timings handler gets created every time a plugin registers or unregisters a new packet handler callback.

This change fixes the problem by ensuring that any handlers derived from the same function, handling the same event class, will share the same timer.
2023-05-06 15:42:52 +01:00
633e77a34c RuntimeBlockMapping: share states CompoundTags if they are the same
this allows saving about 4 MB of memory, because there are many blocks which have identical states, although they have different IDs.

this relies on a potentially risky assumption that the tags in knownStates won't be modified. If they are modified, the changes will influence all blockstates which share the tag.
However, I don't expect this to happen, and the 4 MB memory saving is substantial enough to be worth the risk.
2023-05-04 23:21:54 +01:00
092d130c96 RuntimeBlockMapping: borrow a hack from PM5 to reduce memory footprint
we can't change the internals of this on a patch release, but this hack provides a 12 MB memory usage reduction, which is very significant.
2023-05-04 23:01:10 +01:00
c09390d20f 4.20.3 is next 2023-05-04 21:06:30 +01:00
22f8623e17 Release 4.20.2 2023-05-04 21:06:27 +01:00
f04151dbe6 README: next-major branch was renamed (#5731)
[ci skip]
2023-05-01 14:08:20 +01:00
5dcd8bf289 Bump symfony/filesystem from 5.4.21 to 5.4.23 (#5730)
Bumps [symfony/filesystem](https://github.com/symfony/filesystem) from 5.4.21 to 5.4.23.
- [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.21...v5.4.23)

---
updated-dependencies:
- dependency-name: symfony/filesystem
  dependency-type: direct:production
  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-05-01 13:30:02 +01:00
b70ff32548 ItemTranslator: Fixed log items not displaying correctly on the client
closes #5724

this uses a (potentially bogus) assumption that the lowest mapped meta value associated with an ID is valid. I don't want to break this during a patch release, and this works for now.

In the future it would probably make more sense to bypass ItemTranslator entirely, and rely solely on the blockstate returned by RuntimeBlockMapping to fetch the correct ID. This is similar to how we serialize items for saving on disk in PM5.
2023-04-28 13:54:40 +01:00
023460db2c BaseInventory: fixed internalAddItem() doing useless canStackWith() checks on null items
if the item is null, it's never going to stack with anything given to this function, because addItem() already discards null items.
2023-04-27 14:53:54 +01:00
2910ffebf4 4.20.2 is next 2023-04-27 13:31:02 +01:00
fea820a99e Release 4.20.1 2023-04-27 13:31:02 +01:00
7c19f14cf5 Fixed up offhand handling for ItemStackRequest, fixes #5723 2023-04-27 13:25:08 +01:00
5a54d09869 InventoryManager: verify slot existence in locateWindowAndSlot()
previously, this would happily return invalid slot IDs, potentially leading to a crash.
2023-04-27 13:18:28 +01:00
1d10107024 4.20.1 is next 2023-04-26 23:15:38 +01:00
54ae4d0ea2 Release 4.20.0 2023-04-26 23:15:34 +01:00
0d21e591d1 Support sign editing UI in 1.19.80, with APIs to allow plugins to use it
this doesn't support editing the rear side of a sign, since the 1.12 format doesn't allow us to represent the rear text, and it would necessitate API breaks to support anyway.

However, we can quite trivially support APIs for the sign GUI, which plugins can use to enable editing signs. PocketMine-MP doesn't currently permit this, since it's currently an experimental feature in 1.20, but plugins can simply use Player->openSignEditor() to mimic it.

This is, however, a byproduct of the fact that APIs needed to be added in order to facilitate the use of OpenSignPacket in 1.19.80.
2023-04-26 22:55:05 +01:00
408616723c Changes for 1.19.80 2023-04-26 22:52:02 +01:00
b162d688a3 CI: use php-cs-fixer 3.16 2023-04-26 16:05:06 +01:00
3b09c3a48a actions: updated setup-php-action to pmmp/setup-php-action@c7fb29d835 2023-04-26 14:40:39 +01:00
87781cff4d Update GitHub Actions PHP versions 2023-04-26 14:38:40 +01:00
db0cf4bb5a Update composer dependencies 2023-04-26 14:35:05 +01:00
a4f2b99ed5 InGamePacketHandler: queue slots for syncing if they appear in requestChangedSlots
this is essentially a prediction without the actual predicted item. We have to sync these regardless of what happens.

fixes #5708
2023-04-24 14:06:29 +01:00
3ecc980bc4 ÂInventoryManagerEntry: fixed incorrect PHPDoc type 2023-04-24 13:42:11 +01:00
107b56154b ItemStackRequestExecutor: fixed stonecutter recipes
this uses the same dodgy hack used by CraftingTransaction, which assumes that getResultsFor() does not care about the crafting inputs.

While this is currently OK, since none of the currently-implemented recipes care about inputs anyway, it will become a problem when we implement shulker box recipes, so this needs to be addressed.

However, it can't be addressed without BC breaks, so this will have to be dealt with in PM5.

closes #5715
2023-04-24 13:35:00 +01:00
f86fde064d CraftingManager: fixed uninitialized field
I'm having deja vu about this ...
2023-04-24 12:34:34 +01:00
84a16ce69a HandlerList: fixed crash on getting unused priority
these sub-arrays are no longer allocated if no handlers are registered.

fixes #5713
2023-04-21 16:19:15 +01:00
d06d3bc871 4.19.4 is next 2023-04-21 15:53:05 +01:00
11e34b3e5c Release 4.19.3 2023-04-21 15:53:02 +01:00
f9318bf286 TimingsHandler: stop throwing exceptions when timers aren't stopped in the right order
this is usually because of an uncaught exception interacting with a try...finally block.
This will normally result in a crash anyway, and we don't want to obscure the real error.
2023-04-21 15:38:11 +01:00
674b65f789 Item: optimise serializeCompoundTag() a little 2023-04-18 16:18:34 +01:00
a77fc8109f TypeConverter: avoid repeated calls to getId() and getMeta() 2023-04-18 15:02:52 +01:00
6102740ee3 TypeConverter: use a less slow hack to restore meta values on items sent by the client
this isn't even really needed anymore, since we don't decode items from the client since 4.18.

However, this may still be useful for tools.
2023-04-18 15:00:34 +01:00
40168a457e TypeConverter: fixed coreItemStackToNet() causing item NBT to be prepared twice
hasNamedTag() calls getNamedTag(), which calls serializeCompoundTag(), which writes the item's properties into the given NBT tag.
2023-04-18 14:43:25 +01:00
d07acd0013 RakLibInterface: split error ID into 4-character chunks
this makes it easier to read, since the error ID can't be copy-pasted from the disconnection screen on the client.
2023-04-17 14:05:46 +01:00
9561ae5af7 Entity: micro optimisation for checkBlockIntersections() 2023-04-16 17:57:51 +01:00
56fbd45dd5 Entity: avoid double-checking block intersections for moving entities
fixes #1824
2023-04-16 17:38:26 +01:00
b5dc72b0ee tools/simulate-chunk-selector: fixed the script being completely broken
getopt() behaviour is really, really dumb
2023-04-16 16:47:17 +01:00
4ba57f2b03 RegisteredListener: use try...finally to stop timings
While event handlers should not throw exceptions, we need to make sure the timings get stopped in the correct order, because the parent Event timer will be stopped due to using a finally block.
If this happens while the handler timing is still running, a second exception will occur, obscuring the real error.
2023-04-16 00:40:43 +01:00
df0d72bf61 4.19.3 is next 2023-04-14 18:43:44 +01:00
a534ac759a Release 4.19.2 2023-04-14 18:43:41 +01:00
5ab954b7a0 Update composer dependencies 2023-04-14 18:31:57 +01:00
6c6f686f8e Timings: be more intelligent about shortening timer names
non-pocketmine classes may reuse the names of pocketmine core classes. We don't want timers to get erroneously reused in this case.
2023-04-14 18:24:53 +01:00
bf7975da57 Timings: use class to index packet timings, not IDs
on multi version servers, the same packet may have different IDs, or different packets might use the same ID. In these cases, we don't want the timings to get split up or erroneously reused.
2023-04-14 18:22:35 +01:00
7aeedd8220 Timings: fixed every player getting its own timings
we need a more consistent way to deal with this
2023-04-14 17:32:56 +01:00
3dd1ce2d02 4.19.2 is next 2023-04-14 16:47:46 +01:00
5a29d07021 Release 4.19.1 2023-04-14 16:47:43 +01:00
692e1253c6 Fixed AABB height for farmland and grass paths (fixed in 1.19.50) 2023-04-14 16:02:28 +01:00
ab0c444823 Player: fixed ticking chunks not being re-registered if they never left the view distance
closes #5699
2023-04-14 16:02:28 +01:00
e48a4aaa55 World: fixed chunk ticking not being disabled by setting chunk ticking radius to 0
I can't believe I missed this ...
2023-04-14 16:02:28 +01:00
6fc4ce0f86 ÂTimingsCommand: include HTTP response code in debug message
we should probably show this in the regular response message, but we need new translations for that.
2023-04-14 16:02:27 +01:00
10243c7b2c Bump phpstan/phpstan from 1.10.11 to 1.10.13 (#5697)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.11 to 1.10.13.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.11...1.10.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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 13:19:24 +01:00
18b528f72d ItemStackRequestExecutor: fixed borked taking of created items
closes #5695
2023-04-12 21:01:46 +01:00
7e92da126d DelegateInventory: fixed slots being synced twice and breaking ItemStackRequests
the second time the slot is synced, there is no prediction, so the slot update isn't associated with a request anymore. This causes subsequent requests in the same packet to fail, since the dependency request ID isn't associated with the slot anymore.

This change fixes the problem by only allowing the backing inventory to trigger a call to DelegateInventory->on*Change(). While we could have removed and re-added the listener instead, this way is safer since it doesn't assume that the backing inventory won't modify the given item in setItem().

closes #5692
2023-04-12 15:43:51 +01:00
ba62e0f9cb WorldManager: fixed borked pre-generation for new worlds' spawn terrain
perhaps directly altering the behaviour of selectChunks() wasn't a good
idea? ...
2023-04-11 23:55:53 +01:00
858d4a2ed2 changelog: fixed indentation
I have no idea what happened here...
2023-04-11 23:35:14 +01:00
87d8c1ea11 4.19.1 is next 2023-04-11 22:48:05 +01:00
39 changed files with 579 additions and 247 deletions

View File

@ -13,11 +13,11 @@ jobs:
strategy:
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: [8.0.28, 8.1.18, 8.2.5]
steps:
- name: Build and prepare PHP cache
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
@ -32,13 +32,13 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: [8.0.28, 8.1.18, 8.2.5]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
@ -71,13 +71,13 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: [8.0.28, 8.1.18, 8.2.5]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
@ -110,7 +110,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: [8.0.28, 8.1.18, 8.2.5]
steps:
- uses: actions/checkout@v3
@ -118,7 +118,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
@ -151,13 +151,13 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: [8.0.28, 8.1.18, 8.2.5]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
@ -206,7 +206,7 @@ jobs:
uses: shivammathur/setup-php@2.24.0
with:
php-version: 8.0
tools: php-cs-fixer:3.11
tools: php-cs-fixer:3.16
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -35,7 +35,7 @@
* [Building and running from source](BUILDING.md)
* [Developer documentation](https://devdoc.pmmp.io) - General documentation for PocketMine-MP plugin developers
* [Latest release API documentation](https://apidoc.pmmp.io) - Doxygen API documentation generated for each release
* [Latest bleeding-edge API documentation](https://apidoc-dev.pmmp.io) - Doxygen API documentation generated weekly from `next-major` branch
* [Latest bleeding-edge API documentation](https://apidoc-dev.pmmp.io) - Doxygen API documentation generated weekly from `major-next` branch
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
* [Contributing Guidelines](CONTRIBUTING.md)

View File

@ -54,13 +54,13 @@ Released 11th April 2023.
### `pocketmine\timings`
- The following API constants have been deprecated:
- `Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX` - this is superseded by timings group support (see `Timings::GROUP_BREAKDOWN`)
- `Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX` - this is superseded by timings group support (see `Timings::GROUP_BREAKDOWN`)
- The following API constants have been added:
- `Timings::GROUP_BREAKDOWN` - this group makes a timer appear in the `Minecraft - Breakdown` section of a timings report
- `Timings::GROUP_BREAKDOWN` - this group makes a timer appear in the `Minecraft - Breakdown` section of a timings report
- The following API methods have been added:
- `public TimingsHandler->getGroup() : string` - returns the name of the table in which this timer will appear in a timings report
- `public TimingsHandler->getGroup() : string` - returns the name of the table in which this timer will appear in a timings report
- The following API methods have changed signatures:
- `TimingsHandler->__construct()` now accepts an additional, optional `string $group` parameter, which defaults to `Minecraft`.
- `TimingsHandler->__construct()` now accepts an additional, optional `string $group` parameter, which defaults to `Minecraft`.
### `pocketmine\world`
#### Highlights
@ -75,3 +75,37 @@ It works similarly to the `ChunkLoader` system, in that chunks will be ticked as
- The following API methods have been added:
- `public World->registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void` - registers a chunk to be ticked by the given `ChunkTicker`
- `public World->unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void` - unregisters a chunk from being ticked by the given `ChunkTicker`
# 4.19.1
Released 14th April 2023.
## Fixes
- Fixed inventory rollbacks when spreading items in ender chests.
- Fixed inventory rollbacks when shift-clicking to craft and the outputs would have been split across multiple inventory slots.
- Fixed incorrect spawn terrain generation for newly created worlds.
- Fixed `chunk-ticking.tick-radius` not disabling chunk ticking when set to `0`.
- Fixed chunks not being ticked if they previously left a player's simulation distance without leaving their view distance.
- Fixed height of collision boxes for Grass Path and Farmland blocks.
# 4.19.2
Released 14th April 2023.
## Fixes
- Fixed player timings duplication leading to extremely large timings reports when timings runs for a long time with many players.
- Packet timings are now indexed by class FQN instead of packet ID. This prevents erroneous timer reuse on packet ID reuse (e.g. multi version servers).
- Fixed entity timings being shared by different classes with the same short name. This led to incorrect timings being reported for some entities when custom entities were used.
# 4.19.3
Released 21st April 2023.
## General
- Error IDs for `Packet processing error` disconnects are now split into 4-character chunks to make them easier to type (since they can't be copied from the disconnection screen of a client).
## Fixes
- Fixed entity-block intersections being checked twice per tick. Besides wasting CPU time, this may have caused unexpected behaviour during entity-block interactions with blocks like water or cacti.
- Fixed performance issue in network inventory synchronization due item NBT being prepared twice.
- Fixed `tools/simulate-chunk-selector.php` argument parsing being completely broken (weird behaviour of PHP `getopt()`).
## Internals
- `TimingsHandler->stopTiming()` now logs an error message if a subtimer wasn't stopped, rather than throwing an exception.
- Due to interactions between `try...finally` and unexpected errors, throwing exceptions made it difficult for plugin developers to debug errors in their plugins, since it obscured the original error.

79
changelogs/4.20.md Normal file
View File

@ -0,0 +1,79 @@
**For Minecraft: Bedrock Edition 1.19.80**
### 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.
### Interim releases
If you're upgrading from 4.17.x directly to 4.20.x, please also read the following changelogs, as the interim releases contain important changes:
- [4.18.0](https://github.com/pmmp/PocketMine-MP/blob/4.20.0/changelogs/4.18.md#4180) - major performance improvements, internal network changes, minor API additions
- [4.19.0](https://github.com/pmmp/PocketMine-MP/blob/4.20.0/changelogs/4.19.md#4190) - minor performance improvements, improved timings system, minor API additions
# 4.20.0
Released 26th April 2023.
## General
- Added support for Minecraft: Bedrock Edition 1.19.80.
- Removed support for older versions.
## Fixes
- Fixed packet processing error when attempting to use a stonecutter.
- Fixed armor slots containing ghost items when cancelling right-click to equip armor.
- Fixed crash in `HandlerList->getListenersByPriority()` when no listeners are registered at the given priority.
## API
### `pocketmine\block`
- The following API methods have been added:
- `public BaseSign->getEditorEntityRuntimeId() : int` - returns the entity runtime ID of the player currently editing this sign, or `null` if none
- `public BaseSign->setEditorEntityRuntimeId(?int $editorEntityRuntimeId) : $this` - sets the entity runtime ID of the player currently editing this sign
### `pocketmine\player`
- The following API methods have been added:
- `public Player->openSignEditor(Vector3 $position) : void` - opens the client-side sign editor GUI for the given position
# 4.20.1
Released 27th April 2023.
## Fixes
- Fixed server crash when firing a bow while holding arrows in the offhand slot.
## Internals
- `ItemStackContainerIdTranslator::translate()` now requires an additional `int $slotId` parameter and returns `array{int, int}` (translated window ID, translated slot ID) to be used with `InventoryManager->locateWindowAndSlot()`.
- `InventoryManager->locateWindowAndSlot()` now checks if the translated slot actually exists in the requested inventory, and returns `null` if not. Previously, it would return potentially invalid slot IDs without checking them, potentially leading to crashes.
# 4.20.2
Released 4th May 2023.
## Fixes
- Fixed all types of wooden logs appearing as oak in the inventory.
- Fixed a performance issue in `BaseInventory->canAddItem()` (missing `continue` causing useless logic to run).
# 4.20.3
Released 6th May 2023.
## Improvements
- Reduced memory usage of `RuntimeBlockMapping` from 25 MB to 9 MB. Since every thread has its own copy of the block map, this saves a substantial amount of memory.
## Fixes
- Fixed players falling through blocks in spectator mode.
- Fixed timings reports getting bloated by prolific usage of `PluginManager->registerEvent()`.
- This was caused by creating a new timings handler for each call, regardless of whether a timer already existed for the given event and callback.
- Fixed `Full Server Tick` and other records being missing from timings reports.
- This was caused by timings handler depth not getting reset when timings was disabled and later re-enabled.
# 4.20.4
Released 6th May 2023.
## Fixes
- Fixed players being forced into flight mode in every game mode.
- Moral of the story: do not assume anything in Mojang internals does what its name suggests...
# 4.20.5
Released 30th May 2023.
## Fixes
- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).

View File

@ -33,11 +33,11 @@
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"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.1+bedrock-1.19.70",
"netresearch/jsonmapper": "dev-array-in-string-property-error as 4.2.0",
"pocketmine/bedrock-block-upgrade-schema": "~2.1.0+bedrock-1.19.80",
"pocketmine/bedrock-data": "~2.2.0+bedrock-1.19.80",
"pocketmine/bedrock-item-upgrade-schema": "~1.2.0+bedrock-1.19.80",
"pocketmine/bedrock-protocol": "~21.0.0+bedrock-1.19.80",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
@ -56,7 +56,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.10.11",
"phpstan/phpstan": "1.10.14",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"
@ -93,5 +93,11 @@
"update-translation-apis": [
"@php build/generate-known-translation-apis.php"
]
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/dktapps/JsonMapper.git"
}
]
}

143
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": "6cd5185a409af08d842a5e41ba3b877b",
"content-hash": "80cc5ebf379cf4f425bf98e10611713e",
"packages": [
{
"name": "adhocore/json-comment",
@ -67,26 +67,25 @@
},
{
"name": "brick/math",
"version": "0.10.2",
"version": "0.11.0",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "459f2781e1a08d52ee56b0b1444086e038561e3f"
"reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f",
"reference": "459f2781e1a08d52ee56b0b1444086e038561e3f",
"url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478",
"reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.4 || ^8.0"
"php": "^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^9.0",
"vimeo/psalm": "4.25.0"
"vimeo/psalm": "5.0.0"
},
"type": "library",
"autoload": {
@ -111,7 +110,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.10.2"
"source": "https://github.com/brick/math/tree/0.11.0"
},
"funding": [
{
@ -119,7 +118,7 @@
"type": "github"
}
],
"time": "2022-08-10T22:54:19+00:00"
"time": "2023-01-15T23:15:59+00:00"
},
{
"name": "fgrosse/phpasn1",
@ -199,16 +198,16 @@
},
{
"name": "netresearch/jsonmapper",
"version": "v4.2.0",
"version": "dev-array-in-string-property-error",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "f60565f8c0566a31acf06884cdaa591867ecc956"
"url": "https://github.com/dktapps/jsonmapper.git",
"reference": "4a82d1b98b99d682b660d6caa9b3816b2abc794c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956",
"reference": "f60565f8c0566a31acf06884cdaa591867ecc956",
"url": "https://api.github.com/repos/dktapps/jsonmapper/zipball/4a82d1b98b99d682b660d6caa9b3816b2abc794c",
"reference": "4a82d1b98b99d682b660d6caa9b3816b2abc794c",
"shasum": ""
},
"require": {
@ -228,7 +227,6 @@
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
@ -244,22 +242,22 @@
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0"
"source": "https://github.com/dktapps/jsonmapper/tree/array-in-string-property-error"
},
"time": "2023-04-09T17:37:40+00:00"
"time": "2023-05-30T13:10:31+00:00"
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "1.1.1",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "e0540343e649a92126a1d4071ec401a811416c76"
"reference": "1c07ced86be7d185551082441b5a2b9b7fbd6b21"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/e0540343e649a92126a1d4071ec401a811416c76",
"reference": "e0540343e649a92126a1d4071ec401a811416c76",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/1c07ced86be7d185551082441b5a2b9b7fbd6b21",
"reference": "1c07ced86be7d185551082441b5a2b9b7fbd6b21",
"shasum": ""
},
"type": "library",
@ -270,22 +268,22 @@
"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"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/2.1.0"
},
"time": "2023-03-08T23:45:59+00:00"
"time": "2023-04-19T17:58:49+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "2.1.1+bedrock-1.19.70",
"version": "2.2.0+bedrock-1.19.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "cba0567bcb25f987f2712092f8d662056719e82d"
"reference": "33dd83601442b377af42ac91473278243cafd576"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/cba0567bcb25f987f2712092f8d662056719e82d",
"reference": "cba0567bcb25f987f2712092f8d662056719e82d",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/33dd83601442b377af42ac91473278243cafd576",
"reference": "33dd83601442b377af42ac91473278243cafd576",
"shasum": ""
},
"type": "library",
@ -296,22 +294,22 @@
"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/2.1.1+bedrock-1.19.70"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.80"
},
"time": "2023-03-14T18:03:19+00:00"
"time": "2023-04-26T20:00:35+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "aab89a1f121a0c127557a4a0cf981330301c9c45"
"reference": "dd804c3f2b1e8990434812627e62eb5bde9670a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/aab89a1f121a0c127557a4a0cf981330301c9c45",
"reference": "aab89a1f121a0c127557a4a0cf981330301c9c45",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/dd804c3f2b1e8990434812627e62eb5bde9670a5",
"reference": "dd804c3f2b1e8990434812627e62eb5bde9670a5",
"shasum": ""
},
"type": "library",
@ -322,22 +320,22 @@
"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"
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.2.0"
},
"time": "2023-03-08T22:27:13+00:00"
"time": "2023-04-19T18:16:14+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "20.1.2+bedrock-1.19.70",
"version": "21.0.1+bedrock-1.19.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "2787c531039b3d92fa3ec92f28b95158dc24b915"
"reference": "981ea2e76e207a25c1361df858c639feba5cf348"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2787c531039b3d92fa3ec92f28b95158dc24b915",
"reference": "2787c531039b3d92fa3ec92f28b95158dc24b915",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/981ea2e76e207a25c1361df858c639feba5cf348",
"reference": "981ea2e76e207a25c1361df858c639feba5cf348",
"shasum": ""
},
"require": {
@ -369,9 +367,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/20.1.2+bedrock-1.19.70"
"source": "https://github.com/pmmp/BedrockProtocol/tree/21.0.1+bedrock-1.19.80"
},
"time": "2023-04-10T11:40:32+00:00"
"time": "2023-04-26T21:00:01+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -994,20 +992,20 @@
},
{
"name": "ramsey/uuid",
"version": "4.7.3",
"version": "4.7.4",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "433b2014e3979047db08a17a205f410ba3869cf2"
"reference": "60a4c63ab724854332900504274f6150ff26d286"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2",
"reference": "433b2014e3979047db08a17a205f410ba3869cf2",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286",
"reference": "60a4c63ab724854332900504274f6150ff26d286",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10",
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
@ -1070,7 +1068,7 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.7.3"
"source": "https://github.com/ramsey/uuid/tree/4.7.4"
},
"funding": [
{
@ -1082,20 +1080,20 @@
"type": "tidelift"
}
],
"time": "2023-01-12T18:13:24+00:00"
"time": "2023-04-15T23:01:58+00:00"
},
{
"name": "symfony/filesystem",
"version": "v5.4.21",
"version": "v5.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f"
"reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5",
"reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5",
"shasum": ""
},
"require": {
@ -1130,7 +1128,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.4.21"
"source": "https://github.com/symfony/filesystem/tree/v5.4.23"
},
"funding": [
{
@ -1146,7 +1144,7 @@
"type": "tidelift"
}
],
"time": "2023-02-14T08:03:56+00:00"
"time": "2023-03-02T11:38:35+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1884,16 +1882,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.11",
"version": "1.10.14",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21"
"reference": "d232901b09e67538e5c86a724be841bea5768a7c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c",
"reference": "d232901b09e67538e5c86a724be841bea5768a7c",
"shasum": ""
},
"require": {
@ -1942,7 +1940,7 @@
"type": "tidelift"
}
],
"time": "2023-04-04T19:17:42+00:00"
"time": "2023-04-19T13:47:27+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -2365,16 +2363,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.6",
"version": "9.6.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115"
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115",
"reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"shasum": ""
},
"require": {
@ -2448,7 +2446,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
},
"funding": [
{
@ -2464,7 +2462,7 @@
"type": "tidelift"
}
],
"time": "2023-03-27T11:43:46+00:00"
"time": "2023-04-14T08:58:40+00:00"
},
{
"name": "sebastian/cli-parser",
@ -3481,9 +3479,18 @@
"time": "2021-07-28T10:34:58+00:00"
}
],
"aliases": [],
"aliases": [
{
"package": "netresearch/jsonmapper",
"version": "dev-array-in-string-property-error",
"alias": "4.2.0",
"alias_normalized": "4.2.0.0"
}
],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"netresearch/jsonmapper": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.19.0";
public const BASE_VERSION = "4.20.5";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";

View File

@ -97,6 +97,15 @@ abstract class BaseSign extends Transparent{
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onPostPlace() : void{
$player = $this->editorEntityRuntimeId !== null ?
$this->position->getWorld()->getEntity($this->editorEntityRuntimeId) :
null;
if($player instanceof Player){
$player->openSignEditor($this->position);
}
}
/**
* Returns an object containing information about the sign text.
*/
@ -110,6 +119,19 @@ abstract class BaseSign extends Transparent{
return $this;
}
/**
* Sets the runtime entity ID of the player editing this sign. Only this player will be able to edit the sign.
* This is used to prevent multiple players from editing the same sign at the same time, and to prevent players
* from editing signs they didn't place.
*/
public function getEditorEntityRuntimeId() : ?int{ return $this->editorEntityRuntimeId; }
/** @return $this */
public function setEditorEntityRuntimeId(?int $editorEntityRuntimeId) : self{
$this->editorEntityRuntimeId = $editorEntityRuntimeId;
return $this;
}
/**
* Called by the player controller (network session) to update the sign text, firing events as appropriate.
*
@ -133,6 +155,7 @@ abstract class BaseSign extends Transparent{
$ev->call();
if(!$ev->isCancelled()){
$this->setText($ev->getNewText());
$this->setEditorEntityRuntimeId(null);
$this->position->getWorld()->setBlock($this->position, $this);
return true;
}

View File

@ -64,7 +64,7 @@ class Farmland extends Transparent{
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()]; //TODO: this should be trimmed at the top by 1/16, but MCPE currently treats them as a full block (https://bugs.mojang.com/browse/MCPE-12109)
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
}
public function onNearbyBlockChange() : void{

View File

@ -33,7 +33,7 @@ class GrassPath extends Transparent{
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()]; //TODO: this should be trimmed at the top by 1/16, but MCPE currently treats them as a full block (https://bugs.mojang.com/browse/MCPE-12109)
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
}
public function onNearbyBlockChange() : void{

View File

@ -45,12 +45,18 @@ class Sign extends Spawnable{
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able
public const TAG_TEXT_COLOR = "SignTextColor";
public const TAG_GLOWING_TEXT = "IgnoreLighting";
public const TAG_PERSIST_FORMATTING = "PersistFormatting"; //TAG_Byte
/**
* This tag is set to indicate that MCPE-117835 has been addressed in whatever version this sign was created.
* @see https://bugs.mojang.com/browse/MCPE-117835
*/
public const TAG_LEGACY_BUG_RESOLVE = "TextIgnoreLegacyBugResolved";
public const TAG_FRONT_TEXT = "FrontText"; //TAG_Compound
public const TAG_BACK_TEXT = "BackText"; //TAG_Compound
public const TAG_WAXED = "IsWaxed"; //TAG_Byte
public const TAG_LOCKED_FOR_EDITING_BY = "LockedForEditingBy"; //TAG_Long
/**
* @return string[]
*/
@ -118,12 +124,22 @@ class Sign extends Spawnable{
}
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
//the following are not yet used by the server, but needed to roll back any changes to glowing state or colour
//if the client uses dye on the sign
$nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00));
$nbt->setByte(self::TAG_GLOWING_TEXT, 0);
$nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1);
$textPolyfill = function(CompoundTag $textTag) : CompoundTag{
//the following are not yet used by the server, but needed to roll back any changes to glowing state or colour
//if the client uses dye on the sign
return $textTag
->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00))
->setByte(self::TAG_GLOWING_TEXT, 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1); //TODO: not sure what this is used for
};
$nbt->setTag(self::TAG_FRONT_TEXT, $textPolyfill(CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()))
));
//TODO: this is not yet used by the server, but needed to rollback any client-side changes to the back text
$nbt->setTag(self::TAG_BACK_TEXT, $textPolyfill(CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, "")
));
$nbt->setByte(self::TAG_WAXED, 0);
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
}
}

View File

@ -167,7 +167,7 @@ class TimingsCommand extends VanillaCommand{
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server: " . $result->getBody());
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}

View File

@ -55,7 +55,7 @@ class CraftingManager{
* @var FurnaceRecipeManager[]
* @phpstan-var array<int, FurnaceRecipeManager>
*/
protected $furnaceRecipeManagers;
protected $furnaceRecipeManagers = [];
/**
* @var PotionTypeRecipe[][]

View File

@ -115,6 +115,8 @@ abstract class Entity{
/** @var Block[]|null */
protected $blocksAround;
private bool $checkBlockIntersectionsNextTick = true;
/** @var Location */
protected $location;
/** @var Location */
@ -649,7 +651,10 @@ abstract class Entity{
$hasUpdate = false;
$this->checkBlockIntersections();
if($this->checkBlockIntersectionsNextTick){
$this->checkBlockIntersections();
}
$this->checkBlockIntersectionsNextTick = true;
if($this->location->y <= World::Y_MIN - 16 && $this->isAlive()){
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10);
@ -1308,6 +1313,7 @@ abstract class Entity{
}
protected function checkBlockIntersections() : void{
$this->checkBlockIntersectionsNextTick = false;
$vectors = [];
foreach($this->getBlocksAroundWithEntityInsideActions() as $block){
@ -1319,10 +1325,12 @@ abstract class Entity{
}
}
$vector = Vector3::sum(...$vectors);
if($vector->lengthSquared() > 0){
$d = 0.014;
$this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
if(count($vectors) > 0){
$vector = Vector3::sum(...$vectors);
if($vector->lengthSquared() > 0){
$d = 0.014;
$this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
}
}
}

View File

@ -102,7 +102,7 @@ class HandlerList{
* @return RegisteredListener[]
*/
public function getListenersByPriority(int $priority) : array{
return $this->handlerSlots[$priority];
return $this->handlerSlots[$priority] ?? [];
}
public function getParent() : ?HandlerList{

View File

@ -57,8 +57,11 @@ class RegisteredListener{
return;
}
$this->timings->startTiming();
($this->handler)($event);
$this->timings->stopTiming();
try{
($this->handler)($event);
}finally{
$this->timings->stopTiming();
}
}
public function isHandlingCancelled() : bool{

View File

@ -219,6 +219,7 @@ abstract class BaseInventory implements Inventory{
$item = $this->getItem($i);
if($item->isNull()){
$emptySlots[] = $i;
continue;
}
if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){

View File

@ -30,6 +30,7 @@ use pocketmine\item\Item;
*/
class DelegateInventory extends BaseInventory{
private InventoryListener $inventoryListener;
private bool $backingInventoryChanging = false;
public function __construct(
private Inventory $backingInventory
@ -39,12 +40,22 @@ class DelegateInventory extends BaseInventory{
$this->backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener(
static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{
if(($strongThis = $weakThis->get()) !== null){
$strongThis->onSlotChange($slot, $oldItem);
$strongThis->backingInventoryChanging = true;
try{
$strongThis->onSlotChange($slot, $oldItem);
}finally{
$strongThis->backingInventoryChanging = false;
}
}
},
static function(Inventory $unused, array $oldContents) use ($weakThis) : void{
if(($strongThis = $weakThis->get()) !== null){
$strongThis->onContentChange($oldContents);
$strongThis->backingInventoryChanging = true;
try{
$strongThis->onContentChange($oldContents);
}finally{
$strongThis->backingInventoryChanging = false;
}
}
}
));
@ -73,4 +84,16 @@ class DelegateInventory extends BaseInventory{
protected function internalSetContents(array $items) : void{
$this->backingInventory->setContents($items);
}
protected function onSlotChange(int $index, Item $before) : void{
if($this->backingInventoryChanging){
parent::onSlotChange($index, $before);
}
}
protected function onContentChange(array $itemsBefore) : void{
if($this->backingInventoryChanging){
parent::onContentChange($itemsBefore);
}
}
}

View File

@ -353,30 +353,35 @@ class Item implements \JsonSerializable{
}
protected function serializeCompoundTag(CompoundTag $tag) : void{
$display = $tag->getCompoundTag(self::TAG_DISPLAY) ?? new CompoundTag();
$display = $tag->getCompoundTag(self::TAG_DISPLAY);
$this->hasCustomName() ?
$display->setString(self::TAG_DISPLAY_NAME, $this->getCustomName()) :
$display->removeTag(self::TAG_DISPLAY_NAME);
if($this->customName !== ""){
$display ??= new CompoundTag();
$display->setString(self::TAG_DISPLAY_NAME, $this->customName);
}else{
$display?->removeTag(self::TAG_DISPLAY_NAME);
}
if(count($this->lore) > 0){
$loreTag = new ListTag();
foreach($this->lore as $line){
$loreTag->push(new StringTag($line));
}
$display ??= new CompoundTag();
$display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
}else{
$display->removeTag(self::TAG_DISPLAY_LORE);
$display?->removeTag(self::TAG_DISPLAY_LORE);
}
$display->count() > 0 ?
$display !== null && $display->count() > 0 ?
$tag->setTag(self::TAG_DISPLAY, $display) :
$tag->removeTag(self::TAG_DISPLAY);
if($this->hasEnchantments()){
if(count($this->enchantments) > 0){
$ench = new ListTag();
foreach($this->getEnchantments() as $enchantmentInstance){
$enchantmentIdMap = EnchantmentIdMap::getInstance();
foreach($this->enchantments as $enchantmentInstance){
$ench->push(CompoundTag::create()
->setShort(self::TAG_ENCH_ID, EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType()))
->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
);
}
@ -385,8 +390,8 @@ class Item implements \JsonSerializable{
$tag->removeTag(self::TAG_ENCH);
}
($blockData = $this->getCustomBlockData()) !== null ?
$tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $blockData) :
$this->blockEntityTag !== null ?
$tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
$tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
if(count($this->canPlaceOn) > 0){

View File

@ -205,11 +205,13 @@ class InventoryManager{
if($entry === null){
return null;
}
$inventory = $entry->getInventory();
$coreSlotId = $entry->mapNetToCore($netSlotId);
return $coreSlotId !== null ? [$entry->getInventory(), $coreSlotId] : null;
return $coreSlotId !== null && $inventory->slotExists($coreSlotId) ? [$inventory, $coreSlotId] : null;
}
if(isset($this->networkIdToInventoryMap[$windowId])){
return [$this->networkIdToInventoryMap[$windowId], $netSlotId];
$inventory = $this->networkIdToInventoryMap[$windowId] ?? null;
if($inventory !== null && $inventory->slotExists($netSlotId)){
return [$inventory, $netSlotId];
}
return null;
}

View File

@ -40,7 +40,7 @@ final class InventoryManagerEntry{
public array $itemStackInfos = [];
/**
* @var int[]
* @var ItemStack[]
* @phpstan-var array<int, ItemStack>
*/
public array $pendingSyncs = [];

View File

@ -60,6 +60,7 @@ use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\OpenSignPacket;
use pocketmine\network\mcpe\protocol\Packet;
use pocketmine\network\mcpe\protocol\PacketDecodeException;
use pocketmine\network\mcpe\protocol\PacketPool;
@ -886,14 +887,26 @@ class NetworkSession{
AbilitiesLayer::ABILITY_PRIVILEGED_BUILDER => false,
];
$layers = [
//TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!!
new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1),
];
if(!$for->hasBlockCollision()){
//TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a
//block. We can't seem to prevent this short of forcing the player to always fly when block collision is
//disabled. Also, for some reason the client always reads flight state from this layer if present, even
//though the player isn't in spectator mode.
$layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [
AbilitiesLayer::ABILITY_FLYING => true,
], null, null);
}
$this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData(
$isOp ? CommandPermissions::OPERATOR : CommandPermissions::NORMAL,
$isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
$for->getId(),
[
//TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!!
new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1),
]
$layers
)));
}
@ -1093,6 +1106,10 @@ class NetworkSession{
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
}
public function onOpenSignEditor(Vector3 $signPosition, bool $frontSide) : void{
$this->sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide));
}
public function tick() : void{
if(!$this->isConnected()){
$this->dispose();

View File

@ -110,6 +110,12 @@ final class ItemTranslator{
//new item without a fixed legacy ID - we can't handle this right now
continue;
}
if(isset($complexMappings[$newId]) && $complexMappings[$newId][0] === $intId && $complexMappings[$newId][1] <= $meta){
//TODO: HACK! Multiple legacy ID/meta pairs can be mapped to the same new ID (see minecraft:log)
//Assume that the first one is the most relevant for now
//However, this could catch fire in the future if this assumption is broken
continue;
}
$complexMappings[$newId] = [$intId, (int) $meta];
}
}

View File

@ -27,7 +27,12 @@ use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\Filesystem;
@ -53,15 +58,45 @@ final class RuntimeBlockMapping{
);
}
/**
* @param string[] $keyIndex
* @param (ByteTag|StringTag|IntTag)[][] $valueIndex
* @phpstan-param array<string, string> $keyIndex
* @phpstan-param array<int, array<int|string, ByteTag|IntTag|StringTag>> $valueIndex
*/
private static function deduplicateCompound(CompoundTag $tag, array &$keyIndex, array &$valueIndex) : CompoundTag{
if($tag->count() === 0){
return $tag;
}
$newTag = CompoundTag::create();
foreach($tag as $key => $value){
$key = $keyIndex[$key] ??= $key;
if($value instanceof CompoundTag){
$value = $valueIndex[$value->getType()][(new LittleEndianNbtSerializer())->write(new TreeRoot($value))] ??= self::deduplicateCompound($value, $keyIndex, $valueIndex);
}elseif($value instanceof ByteTag || $value instanceof IntTag || $value instanceof StringTag){
$value = $valueIndex[$value->getType()][$value->getValue()] ??= $value;
}
$newTag->setTag($key, $value);
}
return $newTag;
}
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = $blockState;
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
$this->bedrockKnownStates = $list;

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\item\Durable;
use pocketmine\item\Item;
@ -135,14 +136,16 @@ class TypeConverter{
if($itemStack->isNull()){
return ItemStack::null();
}
$nbt = null;
if($itemStack->hasNamedTag()){
$nbt = clone $itemStack->getNamedTag();
$nbt = $itemStack->getNamedTag();
if($nbt->count() === 0){
$nbt = null;
}else{
$nbt = clone $nbt;
}
$isBlockItem = $itemStack->getId() < 256;
$idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($itemStack->getId(), $itemStack->getMeta());
$internalId = $itemStack->getId();
$internalMeta = $itemStack->getMeta();
$idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($internalId, $internalMeta);
if($idMeta === null){
//Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with
//other unmapped items.
@ -150,8 +153,8 @@ class TypeConverter{
if($nbt === null){
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_ID_TAG, $itemStack->getId());
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
$nbt->setInt(self::PM_ID_TAG, $internalId);
$nbt->setInt(self::PM_META_TAG, $internalMeta);
}else{
[$id, $meta] = $idMeta;
@ -166,23 +169,15 @@ class TypeConverter{
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
$meta = 0;
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
if($nbt === null){
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
$meta = 0;
}
}
$blockRuntimeId = 0;
if($isBlockItem){
if($internalId < 256){
$block = $itemStack->getBlock();
if($block->getId() !== BlockLegacyIds::AIR){
$blockRuntimeId = RuntimeBlockMapping::getInstance()->toRuntimeId($block->getFullId());
$meta = 0;
}
}
@ -208,6 +203,11 @@ class TypeConverter{
$compound = $itemStack->getNbt();
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($itemStack->getId(), $itemStack->getMeta());
if($itemStack->getBlockRuntimeId() !== 0){
//blockitem meta is zeroed out by the client, so we have to infer it from the block runtime ID
$blockFullId = RuntimeBlockMapping::getInstance()->fromRuntimeId($itemStack->getBlockRuntimeId());
$meta = $blockFullId & Block::INTERNAL_METADATA_MASK;
}
if($compound !== null){
$compound = clone $compound;
@ -222,12 +222,6 @@ class TypeConverter{
$compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
$compound->setTag(self::DAMAGE_TAG, $conflicted);
}
}elseif(($metaTag = $compound->getTag(self::PM_META_TAG)) instanceof IntTag){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
$meta = $metaTag->getValue();
$compound->removeTag(self::PM_META_TAG);
}
if($compound->count() === 0){
$compound = null;

View File

@ -328,6 +328,9 @@ class InGamePacketHandler extends PacketHandler{
if(count($packet->trData->getActions()) > 50){
throw new PacketHandlingException("Too many actions in inventory transaction");
}
if(count($packet->requestChangedSlots) > 10){
throw new PacketHandlingException("Too many slot sync requests in inventory transaction");
}
$this->inventoryManager->setCurrentItemStackRequestId($packet->requestId);
$this->inventoryManager->addRawPredictedSlotChanges($packet->trData->getActions());
@ -347,6 +350,21 @@ class InGamePacketHandler extends PacketHandler{
}
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
//requestChangedSlots asks the server to always send out the contents of the specified slots, even if they
//haven't changed. Handling these is necessary to ensure the client inventory stays in sync if the server
//rejects the transaction. The most common example of this is equipping armor by right-click, which doesn't send
//a legacy prediction action for the destination armor slot.
foreach($packet->requestChangedSlots as $containerInfo){
foreach($containerInfo->getChangedSlotIndexes() as $netSlot){
[$windowId, $slot] = ItemStackContainerIdTranslator::translate($containerInfo->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $netSlot);
$inventoryAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slot);
if($inventoryAndSlot !== null){ //trigger the normal slot sync logic
$this->inventoryManager->onSlotChange($inventoryAndSlot[0], $inventoryAndSlot[1]);
}
}
}
$this->inventoryManager->setCurrentItemStackRequestId(null);
return $result;
}
@ -730,7 +748,7 @@ class InGamePacketHandler extends PacketHandler{
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){
if(($textBlobTag = $nbt->getTag(Sign::TAG_TEXT_BLOB)) instanceof StringTag){
if(($textBlobTag = $nbt->getCompoundTag(Sign::TAG_FRONT_TEXT)?->getTag(Sign::TAG_TEXT_BLOB)) instanceof StringTag){
try{
$text = SignText::fromBlob($textBlobTag->getValue());
}catch(\InvalidArgumentException $e){

View File

@ -33,15 +33,21 @@ final class ItemStackContainerIdTranslator{
//NOOP
}
public static function translate(int $containerInterfaceId, int $currentWindowId) : int{
/**
* @return int[]
* @phpstan-return array{int, int}
* @throws PacketHandlingException
*/
public static function translate(int $containerInterfaceId, int $currentWindowId, int $slotId) : array{
return match($containerInterfaceId){
ContainerUIIds::ARMOR => ContainerIds::ARMOR,
ContainerUIIds::ARMOR => [ContainerIds::ARMOR, $slotId],
ContainerUIIds::HOTBAR,
ContainerUIIds::INVENTORY,
ContainerUIIds::COMBINED_HOTBAR_AND_INVENTORY => ContainerIds::INVENTORY,
ContainerUIIds::COMBINED_HOTBAR_AND_INVENTORY => [ContainerIds::INVENTORY, $slotId],
ContainerUIIds::OFFHAND => ContainerIds::OFFHAND,
//TODO: HACK! The client sends an incorrect slot ID for the offhand as of 1.19.70 (though this doesn't really matter since the offhand has only 1 slot anyway)
ContainerUIIds::OFFHAND => [ContainerIds::OFFHAND, 0],
ContainerUIIds::ANVIL_INPUT,
ContainerUIIds::ANVIL_MATERIAL,
@ -68,7 +74,7 @@ final class ItemStackContainerIdTranslator{
ContainerUIIds::TRADE2_INGREDIENT1,
ContainerUIIds::TRADE2_INGREDIENT2,
ContainerUIIds::TRADE_INGREDIENT1,
ContainerUIIds::TRADE_INGREDIENT2 => ContainerIds::UI,
ContainerUIIds::TRADE_INGREDIENT2 => [ContainerIds::UI, $slotId],
ContainerUIIds::BARREL,
ContainerUIIds::BLAST_FURNACE_INGREDIENT,
@ -80,7 +86,7 @@ final class ItemStackContainerIdTranslator{
ContainerUIIds::FURNACE_RESULT,
ContainerUIIds::LEVEL_ENTITY, //chest
ContainerUIIds::SHULKER_BOX,
ContainerUIIds::SMOKER_INGREDIENT => $currentWindowId,
ContainerUIIds::SMOKER_INGREDIENT => [$currentWindowId, $slotId],
//all preview slots are ignored, since the client shouldn't be modifying those directly

View File

@ -23,7 +23,6 @@ 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;
@ -112,12 +111,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
$slotId = $info->getSlotId();
if($info->getContainerId() === ContainerUIIds::OFFHAND && $slotId === 1){
//TODO: HACK! The client sends an incorrect slot ID for the offhand as of 1.19.70
$slotId = 0;
}
[$windowId, $slotId] = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $info->getSlotId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slotId);
if($windowAndSlot === null){
throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerId() . ", slot ID: " . $info->getSlotId());
@ -246,13 +240,11 @@ class ItemStackRequestExecutor{
$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);
//TODO: Since the system assumes that crafting can only be done in the crafting grid, we have to give it a
//crafting grid to make the API happy. No implementation of getResultsFor() actually uses the crafting grid
//right now, so this will work, but this will become a problem in the future for things like shulker boxes and
//custom crafting recipes.
$craftingResults = $recipe->getResultsFor($this->player->getCraftingGrid());
foreach($craftingResults as $k => $craftingResult){
$craftingResult->setCount($craftingResult->getCount() * $repetitions);
$this->craftingResults[$k] = $craftingResult;
@ -284,12 +276,12 @@ class ItemStackRequestExecutor{
}
$this->createdItemsTakenCount += $count;
$createdItem = clone $createdItem;
$createdItem->setCount($count);
$takenItem = clone $createdItem;
$takenItem->setCount($count);
if(!$this->createdItemFromCreativeInventory && $this->createdItemsTakenCount >= $createdItem->getCount()){
$this->setNextCreatedItem(null);
}
return $createdItem;
return $takenItem;
}
/**

View File

@ -53,11 +53,7 @@ final class ItemStackResponseBuilder{
* @phpstan-return array{Inventory, int}
*/
private function getInventoryAndSlot(int $containerInterfaceId, int $slotId) : ?array{
if($containerInterfaceId === ContainerUIIds::OFFHAND && $slotId === 1){
//TODO: HACK! The client sends an incorrect slot ID for the offhand as of 1.19.70
$slotId = 0;
}
$windowId = ItemStackContainerIdTranslator::translate($containerInterfaceId, $this->inventoryManager->getCurrentWindowId());
[$windowId, $slotId] = ItemStackContainerIdTranslator::translate($containerInterfaceId, $this->inventoryManager->getCurrentWindowId(), $slotId);
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slotId);
if($windowAndSlot === null){
return null;

View File

@ -105,6 +105,7 @@ class PreSpawnPacketHandler extends PacketHandler{
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
Uuid::fromString(Uuid::NIL),
false,
false,
[],
0,
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),

View File

@ -53,6 +53,7 @@ use function implode;
use function mt_rand;
use function random_bytes;
use function rtrim;
use function str_split;
use function substr;
use const PHP_INT_MAX;
@ -196,7 +197,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
try{
$session->handleEncoded($buf);
}catch(PacketHandlingException $e){
$errorId = bin2hex(random_bytes(6));
$errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
$logger = $session->getLogger();
$logger->error("Bad packet (error ID $errorId): " . $e->getMessage());

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\block\BaseSign;
use pocketmine\block\Bed;
use pocketmine\block\BlockLegacyIds;
use pocketmine\block\UnknownBlock;
@ -894,6 +895,31 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
}
/**
* @param true[] $oldTickingChunks
* @param true[] $newTickingChunks
*
* @phpstan-param array<int, true> $oldTickingChunks
* @phpstan-param array<int, true> $newTickingChunks
*/
private function updateTickingChunkRegistrations(array $oldTickingChunks, array $newTickingChunks) : void{
$world = $this->getWorld();
foreach($oldTickingChunks as $hash => $_){
if(!isset($newTickingChunks[$hash]) && !isset($this->loadQueue[$hash])){
//we are (probably) still using this chunk, but it's no longer within ticking range
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
$world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
}
}
foreach($newTickingChunks as $hash => $_){
if(!isset($oldTickingChunks[$hash]) && !isset($this->loadQueue[$hash])){
//we were already using this chunk, but it is now within ticking range
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
$world->registerTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
}
}
}
/**
* Calculates which new chunks this player needs to use, and which currently-used chunks it needs to stop using.
* This is based on factors including the player's current render radius and current position.
@ -930,15 +956,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
World::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
foreach($this->tickingChunks as $hash => $_){
//any chunks we encounter here are still used by the player, but may no longer be within ticking range
if(!isset($tickingChunks[$hash]) && !isset($newOrder[$hash])){
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
$world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
}
}
$this->loadQueue = $newOrder;
$this->updateTickingChunkRegistrations($this->tickingChunks, $tickingChunks);
$this->tickingChunks = $tickingChunks;
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
@ -2606,6 +2627,20 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->permanentWindows = [];
}
/**
* Opens the player's sign editor GUI for the sign at the given position.
* TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations)
*/
public function openSignEditor(Vector3 $position) : void{
$block = $this->getWorld()->getBlock($position);
if($block instanceof BaseSign){
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
$this->getNetworkSession()->onOpenSignEditor($position, true);
}else{
throw new \InvalidArgumentException("Block at this position is not a sign");
}
}
use ChunkListenerNoOpTrait {
onChunkChanged as private;
onChunkUnloaded as private;

View File

@ -37,7 +37,7 @@ use pocketmine\permission\DefaultPermissions;
use pocketmine\permission\PermissionManager;
use pocketmine\permission\PermissionParser;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
@ -651,7 +651,7 @@ class PluginManager{
throw new PluginException("Plugin attempted to register event handler " . $handlerName . "() to event " . $event . " while not enabled");
}
$timings = new TimingsHandler($handlerName . "(" . (new \ReflectionClass($event))->getShortName() . ")", group: $plugin->getDescription()->getFullName());
$timings = Timings::getEventHandlerTimings($event, $handlerName, $plugin->getDescription()->getFullName());
$registeredListener = new RegisteredListener($handler, $priority, $plugin, $handleCancelled, $timings);
HandlerListManager::global()->getListFor($event)->register($registeredListener);

View File

@ -166,6 +166,8 @@ abstract class Timings{
/** @var TimingsHandler[] */
private static array $events = [];
/** @var TimingsHandler[][] */
private static array $eventHandlers = [];
public static function init() : void{
if(self::$initialized){
@ -250,42 +252,54 @@ abstract class Timings{
return self::$pluginTaskTimingMap[$name];
}
/**
* @phpstan-template T of object
* @phpstan-param class-string<T> $class
*/
private static function shortenCoreClassName(string $class, string $prefix) : string{
if(str_starts_with($class, $prefix)){
return (new \ReflectionClass($class))->getShortName();
}
return $class;
}
public static function getEntityTimings(Entity $entity) : TimingsHandler{
$reflect = new \ReflectionClass($entity);
$entityType = $reflect->getShortName();
if(!isset(self::$entityTypeTimingMap[$entityType])){
//the timings viewer calculates average player count by looking at this timer, so we need to ensure it has
//a name it can identify. However, we also want to make it obvious if this is a custom Player class.
if($entity instanceof Player && $reflect->getName() !== Player::class){
$entityType = "Player (" . $reflect->getName() . ")";
if(!isset(self::$entityTypeTimingMap[$entity::class])){
if($entity instanceof Player){
//the timings viewer calculates average player count by looking at this timer, so we need to ensure it has
//a name it can identify. However, we also want to make it obvious if this is a custom Player class.
$displayName = $entity::class !== Player::class ? "Player (" . $entity::class . ")" : "Player";
}else{
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
}
self::$entityTypeTimingMap[$entityType] = new TimingsHandler("Entity Tick - " . $entityType, self::$tickEntity, group: self::GROUP_BREAKDOWN);
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN);
}
return self::$entityTypeTimingMap[$entityType];
return self::$entityTypeTimingMap[$entity::class];
}
public static function getTileEntityTimings(Tile $tile) : TimingsHandler{
$tileType = (new \ReflectionClass($tile))->getShortName();
if(!isset(self::$tileEntityTypeTimingMap[$tileType])){
self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler("Block Entity Tick - " . $tileType, self::$tickTileEntity, group: self::GROUP_BREAKDOWN);
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
self::$tickTileEntity,
group: self::GROUP_BREAKDOWN
);
}
return self::$tileEntityTypeTimingMap[$tileType];
return self::$tileEntityTypeTimingMap[$tile::class];
}
public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
if(!isset(self::$packetReceiveTimingMap[$pid])){
self::$packetReceiveTimingMap[$pid] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
if(!isset(self::$packetReceiveTimingMap[$pk::class])){
self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
}
return self::$packetReceiveTimingMap[$pid];
return self::$packetReceiveTimingMap[$pk::class];
}
public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetDecodeTimingMap[$pid] ??= new TimingsHandler(
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
"Decode - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -293,8 +307,7 @@ abstract class Timings{
}
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetHandleTimingMap[$pid] ??= new TimingsHandler(
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
"Handler - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -302,8 +315,7 @@ abstract class Timings{
}
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetEncodeTimingMap[$pid] ??= new TimingsHandler(
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
"Encode - " . $pk->getName(),
self::getSendDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -311,25 +323,31 @@ abstract class Timings{
}
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
if(!isset(self::$packetSendTimingMap[$pid])){
self::$packetSendTimingMap[$pid] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
if(!isset(self::$packetSendTimingMap[$pk::class])){
self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
}
return self::$packetSendTimingMap[$pid];
return self::$packetSendTimingMap[$pk::class];
}
public static function getEventTimings(Event $event) : TimingsHandler{
$eventClass = get_class($event);
if(!isset(self::$events[$eventClass])){
if(str_starts_with($eventClass, "pocketmine\\event\\")){
$name = (new \ReflectionClass($event))->getShortName();
}else{
$name = $eventClass;
}
self::$events[$eventClass] = new TimingsHandler($name, group: "Events");
self::$events[$eventClass] = new TimingsHandler(self::shortenCoreClassName($eventClass, "pocketmine\\event\\"), group: "Events");
}
return self::$events[$eventClass];
}
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
*/
public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{
if(!isset(self::$eventHandlers[$event][$handlerName])){
self::$eventHandlers[$event][$handlerName] = new TimingsHandler($handlerName . "(" . self::shortenCoreClassName($event, "pocketmine\\event\\") . ")", group: $group);
}
return self::$eventHandlers[$event][$handlerName];
}
}

View File

@ -111,7 +111,7 @@ class TimingsHandler{
}
public static function reload() : void{
TimingsRecord::clearRecords();
TimingsRecord::reset();
if(self::$enabled){
self::$timingStart = hrtime(true);
}
@ -189,12 +189,12 @@ class TimingsHandler{
}
$record = TimingsRecord::getCurrentRecord();
if($record !== null){
if($record->getTimerId() !== spl_object_id($this)){
throw new \LogicException("Timer \"" . $record->getName() . "\" should have been stopped before stopping timer \"" . $this->name . "\"");
}
$timerId = spl_object_id($this);
for(; $record !== null && $record->getTimerId() !== $timerId; $record = TimingsRecord::getCurrentRecord()){
\GlobalLogger::get()->error("Timer \"" . $record->getName() . "\" should have been stopped before stopping timer \"" . $this->name . "\"");
$record->stopTiming($now);
}
$record?->stopTiming($now);
if($this->parent !== null){
$this->parent->internalStopTiming($now);
}
@ -219,8 +219,9 @@ class TimingsHandler{
/**
* @internal
*/
public function destroyCycles() : void{
public function reset() : void{
$this->rootRecord = null;
$this->recordsByParent = [];
$this->timingDepth = 0;
}
}

View File

@ -42,9 +42,12 @@ final class TimingsRecord{
private static ?self $currentRecord = null;
public static function clearRecords() : void{
/**
* @internal
*/
public static function reset() : void{
foreach(self::$records as $record){
$record->handler->destroyCycles();
$record->handler->reset();
}
self::$records = [];
self::$currentRecord = null;

View File

@ -507,7 +507,7 @@ class World implements ChunkManager{
$this->time = $this->provider->getWorldData()->getTime();
$cfg = $this->server->getConfigGroup();
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
$this->chunkTickRadius = min($this->server->getViewDistance(), max(0, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
if($cfg->getPropertyInt("chunk-ticking.per-tick", 40) <= 0){
//TODO: this needs l10n
$this->logger->warning("\"chunk-ticking.per-tick\" setting is deprecated, but you've used it to disable chunk ticking. Set \"chunk-ticking.tick-radius\" to 0 in \"pocketmine.yml\" instead.");

View File

@ -286,7 +286,7 @@ class WorldManager{
$centerX = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
$centerZ = $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE;
$selected = iterator_to_array((new ChunkSelector())->selectChunks(8, $centerX, $centerZ));
$selected = iterator_to_array((new ChunkSelector())->selectChunks(8, $centerX, $centerZ), preserve_keys: false);
$done = 0;
$total = count($selected);
foreach($selected as $index){

View File

@ -24,12 +24,10 @@ declare(strict_types=1);
namespace pocketmine\tools\simulate_chunk_selector;
use pocketmine\player\ChunkSelector;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use pocketmine\world\format\Chunk;
use pocketmine\world\World;
use Symfony\Component\Filesystem\Path;
use function assert;
use function count;
use function dirname;
use function fwrite;
@ -128,7 +126,12 @@ if(count(getopt("", ["help"])) !== 0){
exit(0);
}
foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:"])) as $name => $value){
$opts = getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:", "output:"]);
foreach(["radius", "baseX", "baseZ", "scale", "chunksPerStep"] as $name){
$value = $opts[$name] ?? null;
if($value === null){
continue;
}
if(!is_string($value) || (string) ((int) $value) !== $value){
fwrite(STDERR, "Value for --$name must be an integer\n");
exit(1);
@ -139,8 +142,7 @@ foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:"
"baseX" => ($baseX = $value),
"baseZ" => ($baseZ = $value),
"scale" => ($scale = $value),
"chunksPerStep" => ($nChunksPerStep = $value),
default => throw new AssumptionFailedError("getopt() returned unknown option")
"chunksPerStep" => ($nChunksPerStep = $value)
};
}
if($radius === null){
@ -149,10 +151,10 @@ if($radius === null){
}
$outputDirectory = null;
foreach(Utils::stringifyKeys(getopt("", ["output:"])) as $name => $value){
assert($name === "output");
if(isset($opts["output"])){
$value = $opts["output"];
if(!is_string($value)){
fwrite(STDERR, "Value for --$name must be a string\n");
fwrite(STDERR, "Value for --output be a string\n");
exit(1);
}
if(!@mkdir($value) && !is_dir($value)){