mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-08 10:53:05 +00:00
Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
e7bdaa8579 | |||
76749cbaa7 | |||
c3ceeeace7 | |||
aac4f6c0e1 | |||
3b893961e4 | |||
325ffec1be | |||
fa715a074a | |||
4caa2c7690 | |||
d04da9b1d8 | |||
633e77a34c | |||
092d130c96 | |||
c09390d20f | |||
22f8623e17 | |||
f04151dbe6 | |||
5dcd8bf289 | |||
b70ff32548 | |||
023460db2c | |||
2910ffebf4 | |||
fea820a99e | |||
7c19f14cf5 | |||
5a54d09869 | |||
1d10107024 | |||
54ae4d0ea2 | |||
0d21e591d1 | |||
408616723c | |||
b162d688a3 | |||
3b09c3a48a | |||
87781cff4d | |||
db0cf4bb5a | |||
a4f2b99ed5 | |||
3ecc980bc4 | |||
107b56154b | |||
f86fde064d | |||
84a16ce69a | |||
d06d3bc871 | |||
11e34b3e5c | |||
f9318bf286 | |||
674b65f789 | |||
a77fc8109f | |||
6102740ee3 | |||
40168a457e | |||
d07acd0013 | |||
9561ae5af7 | |||
56fbd45dd5 | |||
b5dc72b0ee | |||
4ba57f2b03 | |||
df0d72bf61 | |||
a534ac759a | |||
5ab954b7a0 | |||
6c6f686f8e | |||
bf7975da57 | |||
7aeedd8220 | |||
3dd1ce2d02 | |||
5a29d07021 | |||
692e1253c6 | |||
ab0c444823 | |||
e48a4aaa55 | |||
6fc4ce0f86 | |||
10243c7b2c | |||
18b528f72d | |||
7e92da126d | |||
ba62e0f9cb | |||
858d4a2ed2 | |||
87d8c1ea11 | |||
89deb0fe18 | |||
ad88490e84 | |||
3490e2b06a | |||
946c2fbacc | |||
1c0eed56f1 | |||
9e9b4db00f | |||
e667b5c7db | |||
f61f72180f | |||
3f82150837 | |||
017fcde6aa | |||
24374297e7 | |||
76ebedff6a | |||
3ea8d27a3b | |||
d6c923b525 | |||
7b55c984bf | |||
f5b4d64668 | |||
1683aa681d | |||
bf84caa02c | |||
734adec90d | |||
4724195791 | |||
f32a853bd4 | |||
61b0ad3e7f | |||
b2f755720d | |||
8ef2780dcd | |||
b19c7212ab | |||
14c1a9550d | |||
9037d5f16b | |||
8b64ea9e65 | |||
2936726bf8 | |||
9cd07f6721 | |||
4bb8daa1a5 | |||
6e8eda4ac1 | |||
73522d06ef | |||
a6a360d179 | |||
199ef7401f | |||
f63d349be4 | |||
02e11b5a60 | |||
4a770e5801 | |||
a2ff9649d5 | |||
a862cf5144 | |||
5ac0d7ae11 | |||
0c47455b24 | |||
821dd8885b | |||
a78ae73119 | |||
17a1266056 | |||
217d7ab4cf | |||
9e8c0a6bea | |||
dc1b5a9285 | |||
c3a16d9b1f | |||
bed218d1dd | |||
5e1f837a73 | |||
b49a9ae81d | |||
4c60e82110 | |||
beb0713a40 | |||
1959d6dc9b | |||
9db7e5f0ca | |||
c1cef19f84 | |||
cebdb95265 |
22
.github/workflows/main.yml
vendored
22
.github/workflows/main.yml
vendored
@ -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 }}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -31,3 +31,9 @@ Released 22nd March 2023.
|
||||
- Added version-specific channels to `update.pmmp.io`, such as `4`, `4.18-beta`, `4.17`, etc.
|
||||
- Replaced deprecated `::set-output` commands in GitHub Actions workflows.
|
||||
- `build/make-release.php` no longer automatically pushes changes, to avoid accidents when testing release workflows on forks.
|
||||
|
||||
# 4.17.2
|
||||
Released 29th March 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed players being unable to join due to the appearance of a new `x5t` field in the JWT header of Xbox Live authentication tokens.
|
||||
|
@ -89,4 +89,35 @@ Released 27th March 2023.
|
||||
## Fixes
|
||||
- Fixed server crash when attempting to drop more of an item from a stack than available in the inventory.
|
||||
- Fixed packet processing errors when editing writable books.
|
||||
- Fixed packet processing errors when shift-clicking on the recipe book to craft recipes which draw from a large number of inventory slots.
|
||||
- Fixed packet processing errors when shift-clicking on the recipe book to craft recipes which draw from a large number of inventory slots.
|
||||
|
||||
# 4.18.2
|
||||
Released 29th March 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed players being unable to join due to the appearance of a new `x5t` field in the JWT header of Xbox Live authentication tokens.
|
||||
- Fixed items' durability appearing to reset when moving them around in the inventory.
|
||||
|
||||
# 4.18.3
|
||||
Released 5th April 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed Average Players not being shown on timings reports when custom player classes are used.
|
||||
- Fixed incorrect tick violation calculation in timings reports.
|
||||
- Fixed not being able to add or remove items from the offhand slot.
|
||||
- Fixed creative inventory item count corruption when taking items (some players would see 64x items in the creative inventory after rejoining or changing gamemode).
|
||||
- Fixed not being able to drop items directly from the creative inventory on mobile.
|
||||
- Fixed `DataPacketReceiveEvent` not being called for packets sent by `EntityEventBroadcaster`.
|
||||
- `CreativeInventory::getItem()` and `CreativeInventory::getAll()` now return cloned itemstacks, to prevent accidental modification of the creative inventory.
|
||||
|
||||
# 4.18.4
|
||||
Released 10th April 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed movement becoming broken when the player moves at high speed (e.g. due to high levels of the Speed effect).
|
||||
- Updated dependencies to get fixes in `pocketmine/nbt` and `pocketmine/bedrock-protocol`.
|
||||
|
||||
## Internals
|
||||
### Network
|
||||
- Game packets are now rate-limited in a similar manner to packet batches. This helps to more effectively mitigate certain types of DoS attacks.
|
||||
- Added a new class `PacketRateLimiter`, implementing functionality previously baked directly into `NetworkSession` in a more generic way to allow reuse.
|
||||
|
111
changelogs/4.19.md
Normal file
111
changelogs/4.19.md
Normal file
@ -0,0 +1,111 @@
|
||||
**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.
|
||||
|
||||
### Highlights
|
||||
This version introduces support for a new, more advanced version of Timings.
|
||||
This improved system provides more detail than the old system, and supports being displayed in a tree view, making it much easier to see which timers contribute to which other timers.
|
||||
|
||||
In addition, some minor performance improvements have been made, along with a couple of minor API additions.
|
||||
|
||||
# 4.19.0
|
||||
Released 11th April 2023.
|
||||
|
||||
## General
|
||||
- Updated the Timings system.
|
||||
- Timings records now include parent information, allowing them to be displayed in a tree view (e.g. https://timings.pmmp.io/?id=303556).
|
||||
- Timings records now include additional information, such as Peak (max time spent on any single tick), and Ticks (number of ticks the timer was active on).
|
||||
- New timings have been added for every event.
|
||||
- A new timer `Player Network Send - Pre-Spawn Game Data` has been added, and covers most of the time spent handling `ResourcePackClientResponsePacket`, giving a clearer picture of what's happening.
|
||||
- Improved performance of the plugin event system.
|
||||
- By introducing some caching, the event system now has 90% less overhead than in previous versions.
|
||||
- Improved performance of the random chunk ticking system.
|
||||
- The selection of ticked random chunks, and their validation for ticking, is now cached. This significantly reduces the overhead of chunk selection.
|
||||
- Factions servers and other game modes with big maps and sparsely populated areas will see the most benefit from this change.
|
||||
- Real-world performance benefit of this change is anywhere from 0-20%, depending on server type and configuration.
|
||||
- The `timings paste` command now logs a debug message with the server response on failure to paste a timings report.
|
||||
|
||||
## API
|
||||
### `pocketmine\entity\object`
|
||||
- The following API constants have been added:
|
||||
- `ExperienceOrb::DEFAULT_DESPAWN_DELAY` - the default delay in ticks before an experience orb despawns
|
||||
- `ExperienceOrb::NEVER_DESPAWN` - magic value for `setDespawnDelay()` to make an experience orb never despawn
|
||||
- `ExperienceOrb::MAX_DESPAWN_DELAY` - the maximum delay in ticks before an experience orb despawns
|
||||
- The following API methods have been added:
|
||||
- `public ExperienceOrb->getDespawnDelay() : int` - returns the delay in ticks before this experience orb despawns
|
||||
- `public ExperienceOrb->setDespawnDelay(int $despawnDelay) : void` - sets the delay in ticks before this experience orb despawns
|
||||
- The following properties have been deprecated
|
||||
- `ExperienceOrb->age` - superseded by despawn delay methods
|
||||
|
||||
### `pocketmine\event`
|
||||
- The following API methods have been added:
|
||||
- `public HandlerList->getListenerList() : list<RegisteredListener>` - returns an ordered list of handlers to be called for the event
|
||||
|
||||
### `pocketmine\player`
|
||||
- The following API methods have behavioural changes:
|
||||
- `ChunkSelector->selectChunks()` now yields the distance in chunks from the center as the key, instead of an incrementing integer.
|
||||
- The following classes have been deprecated:
|
||||
- `PlayerChunkLoader` (this was technically internal, but never marked as such)
|
||||
|
||||
### `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`)
|
||||
- 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
|
||||
- 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
|
||||
- The following API methods have changed signatures:
|
||||
- `TimingsHandler->__construct()` now accepts an additional, optional `string $group` parameter, which defaults to `Minecraft`.
|
||||
|
||||
### `pocketmine\world`
|
||||
#### Highlights
|
||||
Ticking chunks is now done using the `ChunkTicker` system, which has a much more fine-grained API than the old `TickingChunkLoader` system, as well as better performance.
|
||||
It works similarly to the `ChunkLoader` system, in that chunks will be ticked as long as at least one `ChunkTicker` is registered for them.
|
||||
|
||||
#### API changes
|
||||
- The following classes have been deprecated:
|
||||
- `TickingChunkLoader` - this has been superseded by the more powerful and performant `ChunkTicker` APIs
|
||||
- The following classes have been added:
|
||||
- `ChunkTicker` - an opaque object used for `registerTickingChunk()` to instruct the `World` that we want a chunk to be ticked
|
||||
- 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
79
changelogs/4.20.md
Normal 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).
|
@ -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.0+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.7",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
184
composer.lock
generated
184
composer.lock
generated
@ -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": "956553d402f9c522cfcfe19260af2d2b",
|
||||
"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.1.0",
|
||||
"version": "dev-array-in-string-property-error",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cweiske/jsonmapper.git",
|
||||
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f"
|
||||
"url": "https://github.com/dktapps/jsonmapper.git",
|
||||
"reference": "4a82d1b98b99d682b660d6caa9b3816b2abc794c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
|
||||
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
|
||||
"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.1.0"
|
||||
"source": "https://github.com/dktapps/jsonmapper/tree/array-in-string-property-error"
|
||||
},
|
||||
"time": "2022-12-08T20:46:14+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.0+bedrock-1.19.70",
|
||||
"version": "21.0.1+bedrock-1.19.80",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "91d67c8b1bced3c82d0841b1041c0c1f4e93eb68"
|
||||
"reference": "981ea2e76e207a25c1361df858c639feba5cf348"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/91d67c8b1bced3c82d0841b1041c0c1f4e93eb68",
|
||||
"reference": "91d67c8b1bced3c82d0841b1041c0c1f4e93eb68",
|
||||
"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.0+bedrock-1.19.70"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/21.0.1+bedrock-1.19.80"
|
||||
},
|
||||
"time": "2023-03-20T01:17:00+00:00"
|
||||
"time": "2023-04-26T21:00:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -512,23 +510,23 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/color",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Color.git",
|
||||
"reference": "8cb346d0a21ad3287cc8d7175e4b643416607249"
|
||||
"reference": "a0421f1e9e0b0c619300fb92d593283378f6a5e1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Color/zipball/8cb346d0a21ad3287cc8d7175e4b643416607249",
|
||||
"reference": "8cb346d0a21ad3287cc8d7175e4b643416607249",
|
||||
"url": "https://api.github.com/repos/pmmp/Color/zipball/a0421f1e9e0b0c619300fb92d593283378f6a5e1",
|
||||
"reference": "a0421f1e9e0b0c619300fb92d593283378f6a5e1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.9.4",
|
||||
"phpstan/phpstan": "1.10.3",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0"
|
||||
},
|
||||
"type": "library",
|
||||
@ -544,9 +542,9 @@
|
||||
"description": "Color handling library used by PocketMine-MP and related projects",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Color/issues",
|
||||
"source": "https://github.com/pmmp/Color/tree/0.3.0"
|
||||
"source": "https://github.com/pmmp/Color/tree/0.3.1"
|
||||
},
|
||||
"time": "2022-12-18T19:49:21+00:00"
|
||||
"time": "2023-04-10T11:38:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/errorhandler",
|
||||
@ -738,16 +736,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "f4321be50df1a18b9f4e94d428a2e68a6e2ac2b4"
|
||||
"reference": "62c02464c6708b2467c1e1a2af01af09d5114eda"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/f4321be50df1a18b9f4e94d428a2e68a6e2ac2b4",
|
||||
"reference": "f4321be50df1a18b9f4e94d428a2e68a6e2ac2b4",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/62c02464c6708b2467c1e1a2af01af09d5114eda",
|
||||
"reference": "62c02464c6708b2467c1e1a2af01af09d5114eda",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -757,7 +755,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "1.7.7",
|
||||
"phpstan/phpstan": "1.10.3",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
@ -774,9 +772,9 @@
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/NBT/issues",
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.3.3"
|
||||
"source": "https://github.com/pmmp/NBT/tree/0.3.4"
|
||||
},
|
||||
"time": "2022-07-06T14:13:26+00:00"
|
||||
"time": "2023-04-10T11:31:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
@ -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.7",
|
||||
"version": "1.10.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975"
|
||||
"reference": "d232901b09e67538e5c86a724be841bea5768a7c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b10ceb526d9607903c5b2673f1fc8775dbe48975",
|
||||
"reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c",
|
||||
"reference": "d232901b09e67538e5c86a724be841bea5768a7c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1942,7 +1940,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-16T15:24:20+00:00"
|
||||
"time": "2023-04-19T13:47:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
@ -1998,16 +1996,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "b7dd96a5503919a43b3cd06a2dced9d4252492f2"
|
||||
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b7dd96a5503919a43b3cd06a2dced9d4252492f2",
|
||||
"reference": "b7dd96a5503919a43b3cd06a2dced9d4252492f2",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6",
|
||||
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2041,9 +2039,9 @@
|
||||
"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.5.0"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1"
|
||||
},
|
||||
"time": "2023-02-21T10:17:10+00:00"
|
||||
"time": "2023-03-29T14:47:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -2365,16 +2363,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.6.5",
|
||||
"version": "9.6.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5"
|
||||
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5",
|
||||
"reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
|
||||
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2447,7 +2445,8 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5"
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -2463,7 +2462,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-09T06:34:10+00:00"
|
||||
"time": "2023-04-14T08:58:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
@ -3480,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": {
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.18.1";
|
||||
public const BASE_VERSION = "4.20.5";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ abstract class Command{
|
||||
public function setLabel(string $name) : bool{
|
||||
$this->nextLabel = $name;
|
||||
if(!$this->isRegistered()){
|
||||
$this->timings = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Command: " . $name);
|
||||
$this->timings = new TimingsHandler("Command: " . $name, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->label = $name;
|
||||
|
||||
return true;
|
||||
|
@ -167,6 +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->getCode() . "): " . $result->getBody());
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class CraftingManager{
|
||||
* @var FurnaceRecipeManager[]
|
||||
* @phpstan-var array<int, FurnaceRecipeManager>
|
||||
*/
|
||||
protected $furnaceRecipeManagers;
|
||||
protected $furnaceRecipeManagers = [];
|
||||
|
||||
/**
|
||||
* @var PotionTypeRecipe[][]
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\player\Player;
|
||||
use function max;
|
||||
use function sqrt;
|
||||
|
||||
class ExperienceOrb extends Entity{
|
||||
@ -48,6 +49,10 @@ class ExperienceOrb extends Entity{
|
||||
/** Split sizes used for dropping experience orbs. */
|
||||
public const ORB_SPLIT_SIZES = [2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1]; //This is indexed biggest to smallest so that we can return as soon as we found the biggest value.
|
||||
|
||||
public const DEFAULT_DESPAWN_DELAY = 6000;
|
||||
public const NEVER_DESPAWN = -1;
|
||||
public const MAX_DESPAWN_DELAY = 32767 + self::DEFAULT_DESPAWN_DELAY; //max value storable by mojang NBT :(
|
||||
|
||||
/**
|
||||
* Returns the largest size of normal XP orb that will be spawned for the specified amount of XP. Used to split XP
|
||||
* up into multiple orbs when an amount of XP is dropped.
|
||||
@ -82,7 +87,10 @@ class ExperienceOrb extends Entity{
|
||||
public $gravity = 0.04;
|
||||
public $drag = 0.02;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int
|
||||
* @deprecated
|
||||
*/
|
||||
protected $age = 0;
|
||||
|
||||
/**
|
||||
@ -100,6 +108,8 @@ class ExperienceOrb extends Entity{
|
||||
/** @var int */
|
||||
protected $xpValue;
|
||||
|
||||
private int $despawnDelay = self::DEFAULT_DESPAWN_DELAY;
|
||||
|
||||
public function __construct(Location $location, int $xpValue, ?CompoundTag $nbt = null){
|
||||
$this->xpValue = $xpValue;
|
||||
parent::__construct($location, $nbt);
|
||||
@ -111,12 +121,22 @@ class ExperienceOrb extends Entity{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->age = $nbt->getShort(self::TAG_AGE, 0);
|
||||
if($this->age === -32768){
|
||||
$this->despawnDelay = self::NEVER_DESPAWN;
|
||||
}else{
|
||||
$this->despawnDelay = max(0, self::DEFAULT_DESPAWN_DELAY - $this->age);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setShort(self::TAG_AGE, $this->age);
|
||||
if($this->despawnDelay === self::NEVER_DESPAWN){
|
||||
$age = -32768;
|
||||
}else{
|
||||
$age = self::DEFAULT_DESPAWN_DELAY - $this->despawnDelay;
|
||||
}
|
||||
$nbt->setShort(self::TAG_AGE, $age);
|
||||
|
||||
$nbt->setShort(self::TAG_VALUE_PC, $this->getXpValue());
|
||||
$nbt->setInt(self::TAG_VALUE_PE, $this->getXpValue());
|
||||
@ -124,6 +144,15 @@ class ExperienceOrb extends Entity{
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
public function getDespawnDelay() : int{ return $this->despawnDelay; }
|
||||
|
||||
public function setDespawnDelay(int $despawnDelay) : void{
|
||||
if(($despawnDelay < 0 || $despawnDelay > self::MAX_DESPAWN_DELAY) && $despawnDelay !== self::NEVER_DESPAWN){
|
||||
throw new \InvalidArgumentException("Despawn ticker must be in range 0 ... " . self::MAX_DESPAWN_DELAY . " or " . self::NEVER_DESPAWN . ", got $despawnDelay");
|
||||
}
|
||||
$this->despawnDelay = $despawnDelay;
|
||||
}
|
||||
|
||||
public function getXpValue() : int{
|
||||
return $this->xpValue;
|
||||
}
|
||||
@ -161,7 +190,8 @@ class ExperienceOrb extends Entity{
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->age += $tickDiff;
|
||||
if($this->age > 6000){
|
||||
$this->despawnDelay -= $tickDiff;
|
||||
if($this->despawnDelay <= 0){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\timings\Timings;
|
||||
use function get_class;
|
||||
|
||||
abstract class Event{
|
||||
@ -51,22 +52,19 @@ abstract class Event{
|
||||
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
|
||||
}
|
||||
|
||||
$timings = Timings::getEventTimings($this);
|
||||
$timings->startTiming();
|
||||
|
||||
$handlerList = HandlerListManager::global()->getListFor(get_class($this));
|
||||
|
||||
++self::$eventCallDepth;
|
||||
try{
|
||||
foreach(EventPriority::ALL as $priority){
|
||||
$currentList = $handlerList;
|
||||
while($currentList !== null){
|
||||
foreach($currentList->getListenersByPriority($priority) as $registration){
|
||||
$registration->callEvent($this);
|
||||
}
|
||||
|
||||
$currentList = $currentList->getParent();
|
||||
}
|
||||
foreach($handlerList->getListenerList() as $registration){
|
||||
$registration->callEvent($this);
|
||||
}
|
||||
}finally{
|
||||
--self::$eventCallDepth;
|
||||
$timings->stopTiming();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ use function mb_strtoupper;
|
||||
* LOWEST -> LOW -> NORMAL -> HIGH -> HIGHEST -> MONITOR
|
||||
*
|
||||
* MONITOR events should not change the event outcome or contents
|
||||
*
|
||||
* WARNING: If these values are changed, handler sorting in HandlerList::getListenerList() may need to be updated.
|
||||
*/
|
||||
final class EventPriority{
|
||||
|
||||
|
@ -24,13 +24,20 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\plugin\Plugin;
|
||||
use function array_fill_keys;
|
||||
use function array_merge;
|
||||
use function krsort;
|
||||
use function spl_object_id;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
class HandlerList{
|
||||
/** @var RegisteredListener[][] */
|
||||
private array $handlerSlots = [];
|
||||
|
||||
private RegisteredListenerCache $handlerCache;
|
||||
|
||||
/** @var RegisteredListenerCache[] */
|
||||
private array $affectedHandlerCaches = [];
|
||||
|
||||
/**
|
||||
* @phpstan-template TEvent of Event
|
||||
* @phpstan-param class-string<TEvent> $class
|
||||
@ -39,7 +46,10 @@ class HandlerList{
|
||||
private string $class,
|
||||
private ?HandlerList $parentList
|
||||
){
|
||||
$this->handlerSlots = array_fill_keys(EventPriority::ALL, []);
|
||||
$this->handlerCache = new RegisteredListenerCache();
|
||||
for($list = $this; $list !== null; $list = $list->parentList){
|
||||
$list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +60,7 @@ class HandlerList{
|
||||
throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}");
|
||||
}
|
||||
$this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener;
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,6 +70,7 @@ class HandlerList{
|
||||
foreach($listeners as $listener){
|
||||
$this->register($listener);
|
||||
}
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,24 +88,60 @@ class HandlerList{
|
||||
}
|
||||
}
|
||||
}elseif($object instanceof RegisteredListener){
|
||||
if(isset($this->handlerSlots[$object->getPriority()][spl_object_id($object)])){
|
||||
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
||||
}
|
||||
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
||||
}
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
public function clear() : void{
|
||||
$this->handlerSlots = array_fill_keys(EventPriority::ALL, []);
|
||||
$this->handlerSlots = [];
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegisteredListener[]
|
||||
*/
|
||||
public function getListenersByPriority(int $priority) : array{
|
||||
return $this->handlerSlots[$priority];
|
||||
return $this->handlerSlots[$priority] ?? [];
|
||||
}
|
||||
|
||||
public function getParent() : ?HandlerList{
|
||||
return $this->parentList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all known caches which might be affected by this list's contents.
|
||||
*/
|
||||
private function invalidateAffectedCaches() : void{
|
||||
foreach($this->affectedHandlerCaches as $cache){
|
||||
$cache->list = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegisteredListener[]
|
||||
* @phpstan-return list<RegisteredListener>
|
||||
*/
|
||||
public function getListenerList() : array{
|
||||
if($this->handlerCache->list !== null){
|
||||
return $this->handlerCache->list;
|
||||
}
|
||||
|
||||
$handlerLists = [];
|
||||
for($currentList = $this; $currentList !== null; $currentList = $currentList->parentList){
|
||||
$handlerLists[] = $currentList;
|
||||
}
|
||||
|
||||
$listenersByPriority = [];
|
||||
foreach($handlerLists as $currentList){
|
||||
foreach($currentList->handlerSlots as $priority => $listeners){
|
||||
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $listeners);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: why on earth do the priorities have higher values for lower priority?
|
||||
krsort($listenersByPriority, SORT_NUMERIC);
|
||||
|
||||
return $this->handlerCache->list = array_merge(...$listenersByPriority);
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
38
src/event/RegisteredListenerCache.php
Normal file
38
src/event/RegisteredListenerCache.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RegisteredListenerCache{
|
||||
|
||||
/**
|
||||
* List of all handlers that will be called for a particular event, ordered by execution order.
|
||||
*
|
||||
* @var RegisteredListener[]
|
||||
* @phpstan-var list<RegisteredListener>
|
||||
*/
|
||||
public ?array $list = null;
|
||||
}
|
@ -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()){
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function json_decode;
|
||||
|
||||
@ -60,11 +61,11 @@ final class CreativeInventory{
|
||||
* @return Item[]
|
||||
*/
|
||||
public function getAll() : array{
|
||||
return $this->creative;
|
||||
return Utils::cloneObjectArray($this->creative);
|
||||
}
|
||||
|
||||
public function getItem(int $index) : ?Item{
|
||||
return $this->creative[$index] ?? null;
|
||||
return isset($this->creative[$index]) ? clone $this->creative[$index] : null;
|
||||
}
|
||||
|
||||
public function getItemIndex(Item $item) : int{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ final class InventoryManagerEntry{
|
||||
public array $itemStackInfos = [];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @var ItemStack[]
|
||||
* @phpstan-var array<int, ItemStack>
|
||||
*/
|
||||
public array $pendingSyncs = [];
|
||||
|
@ -23,7 +23,6 @@ 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;
|
||||
@ -57,13 +56,6 @@ final class NetworkBroadcastUtils{
|
||||
return false;
|
||||
}
|
||||
|
||||
$ev = new DataPacketSendEvent($sessions, $packets);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
$sessions = $ev->getTargets();
|
||||
|
||||
/** @var PacketBroadcaster[] $uniqueBroadcasters */
|
||||
$uniqueBroadcasters = [];
|
||||
/** @var NetworkSession[][] $broadcasterTargets */
|
||||
|
@ -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;
|
||||
@ -114,11 +115,8 @@ use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function hrtime;
|
||||
use function in_array;
|
||||
use function intdiv;
|
||||
use function json_encode;
|
||||
use function min;
|
||||
use function strcasecmp;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
@ -128,19 +126,14 @@ use function ucfirst;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
class NetworkSession{
|
||||
private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately
|
||||
private const INCOMING_PACKET_BATCH_MAX_BUDGET = 100 * self::INCOMING_PACKET_BATCH_PER_TICK; //enough to account for a 5-second lag spike
|
||||
private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions arrive separately
|
||||
private const INCOMING_PACKET_BATCH_BUFFER_TICKS = 100; //enough to account for a 5-second lag spike
|
||||
|
||||
/**
|
||||
* At most this many more packets can be received. If this reaches zero, any additional packets received will cause
|
||||
* the player to be kicked from the server.
|
||||
* This number is increased every tick up to a maximum limit.
|
||||
*
|
||||
* @see self::INCOMING_PACKET_BATCH_PER_TICK
|
||||
* @see self::INCOMING_PACKET_BATCH_MAX_BUDGET
|
||||
*/
|
||||
private int $incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
|
||||
private int $lastPacketBudgetUpdateTimeNs;
|
||||
private const INCOMING_GAME_PACKETS_PER_TICK = 2;
|
||||
private const INCOMING_GAME_PACKETS_BUFFER_TICKS = 100;
|
||||
|
||||
private PacketRateLimiter $packetBatchLimiter;
|
||||
private PacketRateLimiter $gamePacketLimiter;
|
||||
|
||||
private \PrefixedLogger $logger;
|
||||
private ?Player $player = null;
|
||||
@ -196,7 +189,8 @@ class NetworkSession{
|
||||
$this->disposeHooks = new ObjectSet();
|
||||
|
||||
$this->connectTime = time();
|
||||
$this->lastPacketBudgetUpdateTimeNs = hrtime(true);
|
||||
$this->packetBatchLimiter = new PacketRateLimiter("Packet Batches", self::INCOMING_PACKET_BATCH_PER_TICK, self::INCOMING_PACKET_BATCH_BUFFER_TICKS);
|
||||
$this->gamePacketLimiter = new PacketRateLimiter("Game Packets", self::INCOMING_GAME_PACKETS_PER_TICK, self::INCOMING_GAME_PACKETS_BUFFER_TICKS);
|
||||
|
||||
$this->setHandler(new SessionStartPacketHandler(
|
||||
$this->server,
|
||||
@ -339,13 +333,7 @@ class NetworkSession{
|
||||
|
||||
Timings::$playerNetworkReceive->startTiming();
|
||||
try{
|
||||
if($this->incomingPacketBatchBudget <= 0){
|
||||
$this->updatePacketBudget();
|
||||
if($this->incomingPacketBatchBudget <= 0){
|
||||
throw new PacketHandlingException("Receiving packets too fast");
|
||||
}
|
||||
}
|
||||
$this->incomingPacketBatchBudget--;
|
||||
$this->packetBatchLimiter->decrement();
|
||||
|
||||
if($this->cipher !== null){
|
||||
Timings::$playerNetworkReceiveDecrypt->startTiming();
|
||||
@ -377,6 +365,7 @@ class NetworkSession{
|
||||
$stream = new BinaryStream($decompressed);
|
||||
$count = 0;
|
||||
foreach(PacketBatch::decodeRaw($stream) as $buffer){
|
||||
$this->gamePacketLimiter->decrement();
|
||||
if(++$count > 100){
|
||||
throw new PacketHandlingException("Too many packets in batch");
|
||||
}
|
||||
@ -898,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
|
||||
)));
|
||||
}
|
||||
|
||||
@ -1105,21 +1106,8 @@ class NetworkSession{
|
||||
$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 onOpenSignEditor(Vector3 $signPosition, bool $frontSide) : void{
|
||||
$this->sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide));
|
||||
}
|
||||
|
||||
public function tick() : void{
|
||||
|
81
src/network/mcpe/PacketRateLimiter.php
Normal file
81
src/network/mcpe/PacketRateLimiter.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?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\network\PacketHandlingException;
|
||||
use function hrtime;
|
||||
use function intdiv;
|
||||
use function min;
|
||||
|
||||
final class PacketRateLimiter{
|
||||
/**
|
||||
* At most this many more packets can be received. If this reaches zero, any additional packets received will cause
|
||||
* the player to be kicked from the server.
|
||||
* This number is increased every tick up to a maximum limit, and decreased by one every time a packet is received.
|
||||
*/
|
||||
private int $budget;
|
||||
private int $lastUpdateTimeNs;
|
||||
private int $maxBudget;
|
||||
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private int $averagePerTick,
|
||||
int $maxBufferTicks,
|
||||
private int $updateFrequencyNs = 50_000_000,
|
||||
){
|
||||
$this->maxBudget = $this->averagePerTick * $maxBufferTicks;
|
||||
$this->budget = $this->maxBudget;
|
||||
$this->lastUpdateTimeNs = hrtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PacketHandlingException if the rate limit has been exceeded
|
||||
*/
|
||||
public function decrement(int $amount = 1) : void{
|
||||
if($this->budget <= 0){
|
||||
$this->update();
|
||||
if($this->budget <= 0){
|
||||
throw new PacketHandlingException("Exceeded rate limit for \"$this->name\"");
|
||||
}
|
||||
}
|
||||
$this->budget -= $amount;
|
||||
}
|
||||
|
||||
public function update() : void{
|
||||
$nowNs = hrtime(true);
|
||||
$timeSinceLastUpdateNs = $nowNs - $this->lastUpdateTimeNs;
|
||||
if($timeSinceLastUpdateNs > $this->updateFrequencyNs){
|
||||
$ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, $this->updateFrequencyNs);
|
||||
/*
|
||||
* 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->budget = min($this->budget, $this->maxBudget) + ($this->averagePerTick * 2 * $ticksSinceLastUpdate);
|
||||
$this->lastUpdateTimeNs = $nowNs;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\event\server\DataPacketSendEvent;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
@ -41,6 +42,15 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||
){}
|
||||
|
||||
public function broadcastPackets(array $recipients, array $packets) : void{
|
||||
//TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative
|
||||
//implementation that doesn't fire events
|
||||
$ev = new DataPacketSendEvent($recipients, $packets);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
$packets = $ev->getPackets();
|
||||
|
||||
$compressors = [];
|
||||
|
||||
/** @var NetworkSession[][] $targetsByCompressor */
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -214,7 +214,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
if($newPos->distanceSquared($curPos) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks
|
||||
$this->session->getLogger()->debug("Got outdated pre-teleport movement, received " . $newPos . ", expected " . $curPos);
|
||||
//Still getting movements from before teleport, ignore them
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock
|
||||
@ -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){
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,8 +111,8 @@ class ItemStackRequestExecutor{
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
|
||||
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
|
||||
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $info->getSlotId());
|
||||
[$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());
|
||||
}
|
||||
@ -142,6 +141,11 @@ class ItemStackRequestExecutor{
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
|
||||
if($slotInfo->getContainerId() === ContainerUIIds::CREATED_OUTPUT && $slotInfo->getSlotId() === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
|
||||
//special case for the "created item" output slot
|
||||
//TODO: do we need to send a response for this slot info?
|
||||
return $this->takeCreatedItem($count);
|
||||
}
|
||||
$this->requestSlotInfos[] = $slotInfo;
|
||||
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
|
||||
if($count < 1){
|
||||
@ -183,6 +187,13 @@ class ItemStackRequestExecutor{
|
||||
$inventory->setItem($slot, $newItem);
|
||||
}
|
||||
|
||||
protected function dropItem(Item $item, int $count) : void{
|
||||
if($count < 1){
|
||||
throw new ItemStackRequestProcessException("Cannot drop less than 1 of an item");
|
||||
}
|
||||
$this->builder->addAction(new DropItemAction((clone $item)->setCount($count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
@ -229,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;
|
||||
@ -249,7 +258,7 @@ class ItemStackRequestExecutor{
|
||||
/**
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
protected function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
|
||||
protected function takeCreatedItem(int $count) : Item{
|
||||
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");
|
||||
@ -267,10 +276,12 @@ class ItemStackRequestExecutor{
|
||||
}
|
||||
|
||||
$this->createdItemsTakenCount += $count;
|
||||
$this->addItemToSlot($destination, $createdItem, $count);
|
||||
$takenItem = clone $createdItem;
|
||||
$takenItem->setCount($count);
|
||||
if(!$this->createdItemFromCreativeInventory && $this->createdItemsTakenCount >= $createdItem->getCount()){
|
||||
$this->setNextCreatedItem(null);
|
||||
}
|
||||
return $takenItem;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -294,14 +305,7 @@ class ItemStackRequestExecutor{
|
||||
$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());
|
||||
}
|
||||
$this->transferItems($action->getSource(), $action->getDestination(), $action->getCount());
|
||||
}elseif($action instanceof SwapStackRequestAction){
|
||||
$this->requestSlotInfos[] = $action->getSlot1();
|
||||
$this->requestSlotInfos[] = $action->getSlot2();
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackresponse\ItemStackResponse;
|
||||
@ -52,7 +53,7 @@ final class ItemStackResponseBuilder{
|
||||
* @phpstan-return array{Inventory, int}
|
||||
*/
|
||||
private function getInventoryAndSlot(int $containerInterfaceId, int $slotId) : ?array{
|
||||
$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;
|
||||
@ -91,7 +92,7 @@ final class ItemStackResponseBuilder{
|
||||
$item->getCount(),
|
||||
$itemStackInfo->getStackId(),
|
||||
$item->getCustomName(),
|
||||
0
|
||||
$item instanceof Durable ? $item->getDamage() : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
|
||||
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\VersionInfo;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function sprintf;
|
||||
@ -60,89 +61,95 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
){}
|
||||
|
||||
public function setUp() : void{
|
||||
$location = $this->player->getLocation();
|
||||
$world = $location->getWorld();
|
||||
Timings::$playerNetworkSendPreSpawnGameData->startTiming();
|
||||
try{
|
||||
$location = $this->player->getLocation();
|
||||
$world = $location->getWorld();
|
||||
|
||||
$this->session->getLogger()->debug("Preparing StartGamePacket");
|
||||
$levelSettings = new LevelSettings();
|
||||
$levelSettings->seed = -1;
|
||||
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
|
||||
$levelSettings->difficulty = $world->getDifficulty();
|
||||
$levelSettings->spawnPosition = BlockPosition::fromVector3($world->getSpawnLocation());
|
||||
$levelSettings->hasAchievementsDisabled = true;
|
||||
$levelSettings->time = $world->getTime();
|
||||
$levelSettings->eduEditionOffer = 0;
|
||||
$levelSettings->rainLevel = 0; //TODO: implement these properly
|
||||
$levelSettings->lightningLevel = 0;
|
||||
$levelSettings->commandsEnabled = true;
|
||||
$levelSettings->gameRules = [
|
||||
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
|
||||
];
|
||||
$levelSettings->experiments = new Experiments([], false);
|
||||
$this->session->getLogger()->debug("Preparing StartGamePacket");
|
||||
$levelSettings = new LevelSettings();
|
||||
$levelSettings->seed = -1;
|
||||
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
|
||||
$levelSettings->difficulty = $world->getDifficulty();
|
||||
$levelSettings->spawnPosition = BlockPosition::fromVector3($world->getSpawnLocation());
|
||||
$levelSettings->hasAchievementsDisabled = true;
|
||||
$levelSettings->time = $world->getTime();
|
||||
$levelSettings->eduEditionOffer = 0;
|
||||
$levelSettings->rainLevel = 0; //TODO: implement these properly
|
||||
$levelSettings->lightningLevel = 0;
|
||||
$levelSettings->commandsEnabled = true;
|
||||
$levelSettings->gameRules = [
|
||||
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
|
||||
];
|
||||
$levelSettings->experiments = new Experiments([], false);
|
||||
|
||||
$this->session->sendDataPacket(StartGamePacket::create(
|
||||
$this->player->getId(),
|
||||
$this->player->getId(),
|
||||
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
|
||||
$this->player->getOffsetPosition($location),
|
||||
$location->pitch,
|
||||
$location->yaw,
|
||||
new CacheableNbt(CompoundTag::create()), //TODO: we don't care about this right now
|
||||
$levelSettings,
|
||||
"",
|
||||
$this->server->getMotd(),
|
||||
"",
|
||||
false,
|
||||
new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
true,
|
||||
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
|
||||
Uuid::fromString(Uuid::NIL),
|
||||
false,
|
||||
[],
|
||||
0,
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
$this->session->sendDataPacket(StartGamePacket::create(
|
||||
$this->player->getId(),
|
||||
$this->player->getId(),
|
||||
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
|
||||
$this->player->getOffsetPosition($location),
|
||||
$location->pitch,
|
||||
$location->yaw,
|
||||
new CacheableNbt(CompoundTag::create()), //TODO: we don't care about this right now
|
||||
$levelSettings,
|
||||
"",
|
||||
$this->server->getMotd(),
|
||||
"",
|
||||
false,
|
||||
new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
true,
|
||||
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
|
||||
Uuid::fromString(Uuid::NIL),
|
||||
false,
|
||||
false,
|
||||
[],
|
||||
0,
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
|
||||
$this->session->getLogger()->debug("Sending biome definitions");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
$this->session->getLogger()->debug("Sending biome definitions");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
|
||||
$this->session->getLogger()->debug("Sending attributes");
|
||||
$this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll());
|
||||
$this->session->getLogger()->debug("Sending attributes");
|
||||
$this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll());
|
||||
|
||||
$this->session->getLogger()->debug("Sending available commands");
|
||||
$this->session->syncAvailableCommands();
|
||||
$this->session->getLogger()->debug("Sending available commands");
|
||||
$this->session->syncAvailableCommands();
|
||||
|
||||
$this->session->getLogger()->debug("Sending abilities");
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
$this->session->getLogger()->debug("Sending abilities");
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
|
||||
$this->session->getLogger()->debug("Sending effects");
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false);
|
||||
$this->session->getLogger()->debug("Sending effects");
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false);
|
||||
}
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor metadata");
|
||||
$this->player->sendData([$this->player]);
|
||||
|
||||
$this->session->getLogger()->debug("Sending inventory");
|
||||
$this->inventoryManager->syncAll();
|
||||
$this->inventoryManager->syncSelectedHotbarSlot();
|
||||
|
||||
$this->session->getLogger()->debug("Sending creative inventory data");
|
||||
$this->inventoryManager->syncCreative();
|
||||
|
||||
$this->session->getLogger()->debug("Sending crafting data");
|
||||
$this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending player list");
|
||||
$this->session->syncPlayerList($this->server->getOnlinePlayers());
|
||||
}finally{
|
||||
Timings::$playerNetworkSendPreSpawnGameData->stopTiming();
|
||||
}
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor metadata");
|
||||
$this->player->sendData([$this->player]);
|
||||
|
||||
$this->session->getLogger()->debug("Sending inventory");
|
||||
$this->inventoryManager->syncAll();
|
||||
$this->inventoryManager->syncSelectedHotbarSlot();
|
||||
|
||||
$this->session->getLogger()->debug("Sending creative inventory data");
|
||||
$this->inventoryManager->syncCreative();
|
||||
|
||||
$this->session->getLogger()->debug("Sending crafting data");
|
||||
$this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending player list");
|
||||
$this->session->syncPlayerList($this->server->getOnlinePlayers());
|
||||
}
|
||||
|
||||
public function handleRequestChunkRadius(RequestChunkRadiusPacket $packet) : bool{
|
||||
|
@ -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());
|
||||
|
@ -54,23 +54,23 @@ final class ChunkSelector{
|
||||
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
|
||||
|
||||
/* Top right quadrant */
|
||||
yield World::chunkHash($centerX + $x, $centerZ + $z);
|
||||
yield $subRadius => World::chunkHash($centerX + $x, $centerZ + $z);
|
||||
/* Top left quadrant */
|
||||
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
|
||||
yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ + $z);
|
||||
/* Bottom right quadrant */
|
||||
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
|
||||
yield $subRadius => World::chunkHash($centerX + $x, $centerZ - $z - 1);
|
||||
/* Bottom left quadrant */
|
||||
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
|
||||
yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
|
||||
|
||||
if($x !== $z){
|
||||
/* Top right quadrant mirror */
|
||||
yield World::chunkHash($centerX + $z, $centerZ + $x);
|
||||
yield $subRadius => World::chunkHash($centerX + $z, $centerZ + $x);
|
||||
/* Top left quadrant mirror */
|
||||
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
|
||||
yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ + $x);
|
||||
/* Bottom right quadrant mirror */
|
||||
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
|
||||
yield $subRadius => World::chunkHash($centerX + $z, $centerZ - $x - 1);
|
||||
/* Bottom left quadrant mirror */
|
||||
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
|
||||
yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -121,6 +122,8 @@ use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\ChunkListener;
|
||||
use pocketmine\world\ChunkListenerNoOpTrait;
|
||||
use pocketmine\world\ChunkLoader;
|
||||
use pocketmine\world\ChunkTicker;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\sound\EntityAttackNoDamageSound;
|
||||
@ -237,12 +240,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
protected array $loadQueue = [];
|
||||
protected int $nextChunkOrderRun = 5;
|
||||
|
||||
/** @var true[] */
|
||||
private array $tickingChunks = [];
|
||||
|
||||
protected int $viewDistance = -1;
|
||||
protected int $spawnThreshold;
|
||||
protected int $spawnChunkLoadCount = 0;
|
||||
protected int $chunksPerTick;
|
||||
protected ChunkSelector $chunkSelector;
|
||||
protected PlayerChunkLoader $chunkLoader;
|
||||
protected ChunkLoader $chunkLoader;
|
||||
protected ChunkTicker $chunkTicker;
|
||||
|
||||
/** @var bool[] map: raw UUID (string) => bool */
|
||||
protected array $hiddenPlayers = [];
|
||||
@ -308,8 +315,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4) ** 2) * M_PI);
|
||||
$this->chunkSelector = new ChunkSelector();
|
||||
|
||||
$this->chunkLoader = new PlayerChunkLoader($spawnLocation);
|
||||
|
||||
$this->chunkLoader = new class implements ChunkLoader{};
|
||||
$this->chunkTicker = new ChunkTicker();
|
||||
$world = $spawnLocation->getWorld();
|
||||
//load the spawn chunk so we can see the terrain
|
||||
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
|
||||
@ -747,6 +754,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
|
||||
$world->unregisterChunkListener($this, $x, $z);
|
||||
unset($this->loadQueue[$index]);
|
||||
$world->unregisterTickingChunk($this->chunkTicker, $x, $z);
|
||||
unset($this->tickingChunks[$index]);
|
||||
}
|
||||
|
||||
protected function spawnEntitiesOnAllChunks() : void{
|
||||
@ -798,6 +807,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
unset($this->loadQueue[$index]);
|
||||
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
|
||||
$this->getWorld()->registerChunkListener($this, $X, $Z);
|
||||
if(isset($this->tickingChunks[$index])){
|
||||
$this->getWorld()->registerTickingChunk($this->chunkTicker, $X, $Z);
|
||||
}
|
||||
|
||||
$this->getWorld()->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion(
|
||||
function() use ($X, $Z, $index, $world) : void{
|
||||
@ -883,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.
|
||||
@ -895,16 +932,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
Timings::$playerChunkOrder->startTiming();
|
||||
|
||||
$newOrder = [];
|
||||
$tickingChunks = [];
|
||||
$unloadChunks = $this->usedChunks;
|
||||
|
||||
$world = $this->getWorld();
|
||||
$tickingChunkRadius = $world->getChunkTickRadius();
|
||||
|
||||
foreach($this->chunkSelector->selectChunks(
|
||||
$this->server->getAllowedViewDistance($this->viewDistance),
|
||||
$this->location->getFloorX() >> Chunk::COORD_BIT_SIZE,
|
||||
$this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE
|
||||
) as $hash){
|
||||
) as $radius => $hash){
|
||||
if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){
|
||||
$newOrder[$hash] = true;
|
||||
}
|
||||
if($radius < $tickingChunkRadius){
|
||||
$tickingChunks[$hash] = true;
|
||||
}
|
||||
unset($unloadChunks[$hash]);
|
||||
}
|
||||
|
||||
@ -914,8 +958,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
|
||||
$this->loadQueue = $newOrder;
|
||||
|
||||
$this->updateTickingChunkRegistrations($this->tickingChunks, $tickingChunks);
|
||||
$this->tickingChunks = $tickingChunks;
|
||||
|
||||
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
|
||||
$this->chunkLoader->setCurrentLocation($this->location);
|
||||
$this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance);
|
||||
}
|
||||
|
||||
@ -1231,7 +1278,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$revert = false;
|
||||
|
||||
if($distanceSquared > 100){
|
||||
if($distanceSquared > 225){ //15 blocks
|
||||
//TODO: this is probably too big if we process every movement
|
||||
/* !!! BEWARE YE WHO ENTER HERE !!!
|
||||
*
|
||||
@ -1243,7 +1290,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* If you must tamper with this code, be aware that this can cause very nasty results. Do not waste our time
|
||||
* asking for help if you suffer the consequences of messing with this.
|
||||
*/
|
||||
$this->logger->debug("Moved too fast, reverting movement");
|
||||
$this->logger->debug("Moved too fast (" . sqrt($distanceSquared) . " blocks in 1 movement), reverting movement");
|
||||
$this->logger->debug("Old position: " . $oldPos->asVector3() . ", new position: " . $newPos);
|
||||
$revert = true;
|
||||
}elseif(!$this->getWorld()->isInLoadedTerrain($newPos)){
|
||||
@ -2580,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;
|
||||
|
@ -26,6 +26,10 @@ namespace pocketmine\player;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\world\TickingChunkLoader;
|
||||
|
||||
/**
|
||||
* @deprecated This class was only needed to implement TickingChunkLoader, which is now deprecated.
|
||||
* ChunkTicker should be registered on ticking chunks to make them tick instead.
|
||||
*/
|
||||
final class PlayerChunkLoader implements TickingChunkLoader{
|
||||
public function __construct(private Vector3 $currentLocation){}
|
||||
|
||||
|
@ -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("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . $handlerName . "(" . (new \ReflectionClass($event))->getShortName() . ")");
|
||||
$timings = Timings::getEventHandlerTimings($event, $handlerName, $plugin->getDescription()->getFullName());
|
||||
|
||||
$registeredListener = new RegisteredListener($handler, $priority, $plugin, $handleCancelled, $timings);
|
||||
HandlerListManager::global()->getListFor($event)->register($registeredListener);
|
||||
|
@ -25,12 +25,22 @@ namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\block\tile\Tile;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerboundPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\scheduler\TaskHandler;
|
||||
use function get_class;
|
||||
use function str_starts_with;
|
||||
|
||||
abstract class Timings{
|
||||
/**
|
||||
* @deprecated This was used by the old timings viewer to make a timer appear in the Breakdown section of a timings
|
||||
* report. Provide a group to your timer's constructor instead.
|
||||
* @see Timings::GROUP_BREAKDOWN
|
||||
*/
|
||||
public const INCLUDED_BY_OTHER_TIMINGS_PREFIX = "** ";
|
||||
public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
|
||||
|
||||
private static bool $initialized = false;
|
||||
|
||||
@ -58,6 +68,7 @@ abstract class Timings{
|
||||
public static $playerNetworkSendEncrypt;
|
||||
|
||||
public static TimingsHandler $playerNetworkSendInventorySync;
|
||||
public static TimingsHandler $playerNetworkSendPreSpawnGameData;
|
||||
|
||||
/** @var TimingsHandler */
|
||||
public static $playerNetworkReceive;
|
||||
@ -153,6 +164,11 @@ abstract class Timings{
|
||||
|
||||
public static TimingsHandler $playerMove;
|
||||
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $events = [];
|
||||
/** @var TimingsHandler[][] */
|
||||
private static array $eventHandlers = [];
|
||||
|
||||
public static function init() : void{
|
||||
if(self::$initialized){
|
||||
return;
|
||||
@ -160,8 +176,8 @@ abstract class Timings{
|
||||
self::$initialized = true;
|
||||
|
||||
self::$fullTick = new TimingsHandler("Full Server Tick");
|
||||
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Tick Update Cycle", self::$fullTick);
|
||||
self::$serverInterrupts = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Mid-Tick Processing", self::$fullTick);
|
||||
self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick, group: self::GROUP_BREAKDOWN);
|
||||
self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick, group: self::GROUP_BREAKDOWN);
|
||||
self::$memoryManager = new TimingsHandler("Memory Manager");
|
||||
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
|
||||
self::$titleTick = new TimingsHandler("Console Title Tick");
|
||||
@ -169,21 +185,22 @@ abstract class Timings{
|
||||
self::$connection = new TimingsHandler("Connection Handler");
|
||||
|
||||
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::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$broadcastPackets = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Broadcast Packets", self::$playerNetworkSend);
|
||||
self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$playerMove = new TimingsHandler("Player Movement");
|
||||
self::$playerChunkOrder = new TimingsHandler("Player Order Chunks");
|
||||
self::$playerChunkSend = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Chunks", self::$playerNetworkSend);
|
||||
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$scheduler = new TimingsHandler("Scheduler");
|
||||
self::$serverCommand = new TimingsHandler("Server Command");
|
||||
self::$worldLoad = new TimingsHandler("World Load");
|
||||
@ -191,36 +208,36 @@ abstract class Timings{
|
||||
self::$population = new TimingsHandler("World Population");
|
||||
self::$generationCallback = new TimingsHandler("World Generation Callback");
|
||||
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
|
||||
self::$permissibleCalculationDiff = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Permissible Calculation - Diff", self::$permissibleCalculation);
|
||||
self::$permissibleCalculationCallback = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Permissible Calculation - Callbacks", self::$permissibleCalculation);
|
||||
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load");
|
||||
self::$syncPlayerDataSave = new TimingsHandler("Player Data Save");
|
||||
|
||||
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::$entityMove = new TimingsHandler("Entity Movement", group: self::GROUP_BREAKDOWN);
|
||||
self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove, group: self::GROUP_BREAKDOWN);
|
||||
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
|
||||
self::$tickEntity = new TimingsHandler("Entity Tick", group: self::GROUP_BREAKDOWN);
|
||||
self::$tickTileEntity = new TimingsHandler("Block Entity Tick", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
|
||||
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
|
||||
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN);
|
||||
self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
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");
|
||||
self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN);
|
||||
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
}
|
||||
|
||||
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
|
||||
$name = "Task: " . $task->getOwnerName() . " Runnable: " . $task->getTaskName();
|
||||
$name = "Task: " . $task->getTaskName();
|
||||
|
||||
if($period > 0){
|
||||
$name .= "(interval:" . $period . ")";
|
||||
@ -229,69 +246,108 @@ abstract class Timings{
|
||||
}
|
||||
|
||||
if(!isset(self::$pluginTaskTimingMap[$name])){
|
||||
self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSync);
|
||||
self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSync, $task->getOwnerName());
|
||||
}
|
||||
|
||||
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{
|
||||
$entityType = (new \ReflectionClass($entity))->getShortName();
|
||||
if(!isset(self::$entityTypeTimingMap[$entityType])){
|
||||
self::$entityTypeTimingMap[$entityType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Tick - " . $entityType, self::$tickEntity);
|
||||
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[$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(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Block Entity Tick - " . $tileType, self::$tickTileEntity);
|
||||
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(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Receive - " . $pk->getName(), self::$playerNetworkReceive);
|
||||
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(
|
||||
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Decode - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk)
|
||||
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Decode - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
self::getReceiveDataPacketTimings($pk)
|
||||
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Handler - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Encode - " . $pk->getName(),
|
||||
self::getSendDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
if(!isset(self::$packetSendTimingMap[$pid])){
|
||||
self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Send - " . $pk->getName(), self::$playerNetworkSend);
|
||||
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])){
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,21 @@ namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function hrtime;
|
||||
use function implode;
|
||||
use function spl_object_id;
|
||||
|
||||
class TimingsHandler{
|
||||
private const FORMAT_VERSION = 1;
|
||||
|
||||
private static bool $enabled = false;
|
||||
private static int $timingStart = 0;
|
||||
|
||||
/** @return string[] */
|
||||
public static function printTimings() : array{
|
||||
$result = ["Minecraft"];
|
||||
$groups = [];
|
||||
|
||||
foreach(TimingsRecord::getAll() as $timings){
|
||||
$time = $timings->getTotalTime();
|
||||
@ -46,7 +51,27 @@ class TimingsHandler{
|
||||
|
||||
$avg = $time / $count;
|
||||
|
||||
$result[] = " " . $timings->getName() . " Time: $time Count: " . $count . " Avg: $avg Violations: " . $timings->getViolations();
|
||||
$group = $timings->getGroup();
|
||||
$groups[$group][] = implode(" ", [
|
||||
$timings->getName(),
|
||||
"Time: $time",
|
||||
"Count: $count",
|
||||
"Avg: $avg",
|
||||
"Violations: " . $timings->getViolations(),
|
||||
"RecordId: " . $timings->getId(),
|
||||
"ParentRecordId: " . ($timings->getParentId() ?? "none"),
|
||||
"TimerId: " . $timings->getTimerId(),
|
||||
"Ticks: " . $timings->getTicksActive(),
|
||||
"Peak: " . $timings->getPeakTime(),
|
||||
]);
|
||||
}
|
||||
$result = [];
|
||||
|
||||
foreach(Utils::stringifyKeys($groups) as $groupName => $lines){
|
||||
$result[] = $groupName;
|
||||
foreach($lines as $line){
|
||||
$result[] = " $line";
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = "# Version " . Server::getInstance()->getVersion();
|
||||
@ -65,6 +90,7 @@ class TimingsHandler{
|
||||
|
||||
$result[] = "# Entities " . $entities;
|
||||
$result[] = "# LivingEntities " . $livingEntities;
|
||||
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
|
||||
|
||||
$sampleTime = hrtime(true) - self::$timingStart;
|
||||
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
|
||||
@ -85,7 +111,7 @@ class TimingsHandler{
|
||||
}
|
||||
|
||||
public static function reload() : void{
|
||||
TimingsRecord::clearRecords();
|
||||
TimingsRecord::reset();
|
||||
if(self::$enabled){
|
||||
self::$timingStart = hrtime(true);
|
||||
}
|
||||
@ -97,16 +123,25 @@ class TimingsHandler{
|
||||
}
|
||||
}
|
||||
|
||||
private ?TimingsRecord $record = null;
|
||||
private ?TimingsRecord $rootRecord = null;
|
||||
private int $timingDepth = 0;
|
||||
|
||||
/**
|
||||
* @var TimingsRecord[]
|
||||
* @phpstan-var array<int, TimingsRecord>
|
||||
*/
|
||||
private array $recordsByParent = [];
|
||||
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private ?TimingsHandler $parent = null
|
||||
private ?TimingsHandler $parent = null,
|
||||
private string $group = "Minecraft"
|
||||
){}
|
||||
|
||||
public function getName() : string{ return $this->name; }
|
||||
|
||||
public function getGroup() : string{ return $this->group; }
|
||||
|
||||
public function startTiming() : void{
|
||||
if(self::$enabled){
|
||||
$this->internalStartTiming(hrtime(true));
|
||||
@ -115,13 +150,24 @@ class TimingsHandler{
|
||||
|
||||
private function internalStartTiming(int $now) : void{
|
||||
if(++$this->timingDepth === 1){
|
||||
if($this->record === null){
|
||||
$this->record = new TimingsRecord($this);
|
||||
}
|
||||
$this->record->startTiming($now);
|
||||
if($this->parent !== null){
|
||||
$this->parent->internalStartTiming($now);
|
||||
}
|
||||
|
||||
$current = TimingsRecord::getCurrentRecord();
|
||||
if($current !== null){
|
||||
$record = $this->recordsByParent[spl_object_id($current)] ?? null;
|
||||
if($record === null){
|
||||
$record = new TimingsRecord($this, $current);
|
||||
$this->recordsByParent[spl_object_id($current)] = $record;
|
||||
}
|
||||
}else{
|
||||
if($this->rootRecord === null){
|
||||
$this->rootRecord = new TimingsRecord($this, null);
|
||||
}
|
||||
$record = $this->rootRecord;
|
||||
}
|
||||
$record->startTiming($now);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,10 +188,13 @@ class TimingsHandler{
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->record !== null){
|
||||
//this might be null if a timings reset occurred while the timer was running
|
||||
$this->record->stopTiming($now);
|
||||
$record = TimingsRecord::getCurrentRecord();
|
||||
$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);
|
||||
}
|
||||
@ -170,7 +219,9 @@ class TimingsHandler{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function destroyCycles() : void{
|
||||
$this->record = null;
|
||||
public function reset() : void{
|
||||
$this->rootRecord = null;
|
||||
$this->recordsByParent = [];
|
||||
$this->timingDepth = 0;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ declare(strict_types=1);
|
||||
namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\Server;
|
||||
use function round;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function floor;
|
||||
use function spl_object_id;
|
||||
|
||||
/**
|
||||
@ -39,11 +40,17 @@ final class TimingsRecord{
|
||||
*/
|
||||
private static array $records = [];
|
||||
|
||||
public static function clearRecords() : void{
|
||||
private static ?self $currentRecord = null;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function reset() : void{
|
||||
foreach(self::$records as $record){
|
||||
$record->handler->destroyCycles();
|
||||
$record->handler->reset();
|
||||
}
|
||||
self::$records = [];
|
||||
self::$currentRecord = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,11 +62,17 @@ final class TimingsRecord{
|
||||
public static function tick(bool $measure = true) : void{
|
||||
if($measure){
|
||||
foreach(self::$records as $record){
|
||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||
$record->violations += (int) round($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||
if($record->curCount > 0){
|
||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||
$record->violations += (int) floor($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||
}
|
||||
if($record->curTickTotal > $record->peakTime){
|
||||
$record->peakTime = $record->curTickTotal;
|
||||
}
|
||||
$record->curTickTotal = 0;
|
||||
$record->curCount = 0;
|
||||
$record->ticksActive++;
|
||||
}
|
||||
$record->curTickTotal = 0;
|
||||
$record->curCount = 0;
|
||||
}
|
||||
}else{
|
||||
foreach(self::$records as $record){
|
||||
@ -78,16 +91,27 @@ final class TimingsRecord{
|
||||
private int $totalTime = 0;
|
||||
private int $curTickTotal = 0;
|
||||
private int $violations = 0;
|
||||
private int $ticksActive = 0;
|
||||
private int $peakTime = 0;
|
||||
|
||||
public function __construct(
|
||||
//I'm not the biggest fan of this cycle, but it seems to be the most effective way to avoid leaking anything.
|
||||
private TimingsHandler $handler
|
||||
private TimingsHandler $handler,
|
||||
private ?TimingsRecord $parentRecord
|
||||
){
|
||||
self::$records[spl_object_id($this)] = $this;
|
||||
}
|
||||
|
||||
public function getId() : int{ return spl_object_id($this); }
|
||||
|
||||
public function getParentId() : ?int{ return $this->parentRecord?->getId(); }
|
||||
|
||||
public function getTimerId() : int{ return spl_object_id($this->handler); }
|
||||
|
||||
public function getName() : string{ return $this->handler->getName(); }
|
||||
|
||||
public function getGroup() : string{ return $this->handler->getGroup(); }
|
||||
|
||||
public function getCount() : int{ return $this->count; }
|
||||
|
||||
public function getCurCount() : int{ return $this->curCount; }
|
||||
@ -100,14 +124,28 @@ final class TimingsRecord{
|
||||
|
||||
public function getViolations() : int{ return $this->violations; }
|
||||
|
||||
public function getTicksActive() : int{ return $this->ticksActive; }
|
||||
|
||||
public function getPeakTime() : float{ return $this->peakTime; }
|
||||
|
||||
public function startTiming(int $now) : void{
|
||||
$this->start = $now;
|
||||
self::$currentRecord = $this;
|
||||
}
|
||||
|
||||
public function stopTiming(int $now) : void{
|
||||
if($this->start == 0){
|
||||
return;
|
||||
}
|
||||
if(self::$currentRecord !== $this){
|
||||
if(self::$currentRecord === null){
|
||||
//timings may have been stopped while this timer was running
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssumptionFailedError("stopTiming() called on a non-current timer");
|
||||
}
|
||||
self::$currentRecord = $this->parentRecord;
|
||||
$diff = $now - $this->start;
|
||||
$this->totalTime += $diff;
|
||||
$this->curTickTotal += $diff;
|
||||
@ -115,4 +153,8 @@ final class TimingsRecord{
|
||||
++$this->count;
|
||||
$this->start = 0;
|
||||
}
|
||||
|
||||
public static function getCurrentRecord() : ?self{
|
||||
return self::$currentRecord;
|
||||
}
|
||||
}
|
||||
|
34
src/world/ChunkTicker.php
Normal file
34
src/world/ChunkTicker.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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\world;
|
||||
|
||||
/**
|
||||
* Used to signal to the World that a chunk should be ticked.
|
||||
*
|
||||
* @see World::registerTickingChunk()
|
||||
* @see World::unregisterTickingChunk()
|
||||
*/
|
||||
final class ChunkTicker{
|
||||
|
||||
}
|
37
src/world/TickingChunkEntry.php
Normal file
37
src/world/TickingChunkEntry.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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\world;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class TickingChunkEntry{
|
||||
/**
|
||||
* @var ChunkTicker[] spl_object_id => ChunkTicker
|
||||
* @phpstan-var array<int, ChunkTicker>
|
||||
*/
|
||||
public array $tickers = [];
|
||||
|
||||
public bool $ready = false;
|
||||
}
|
@ -27,6 +27,10 @@ namespace pocketmine\world;
|
||||
* TickingChunkLoader includes all of the same functionality as ChunkLoader (it can be used in the same way).
|
||||
* However, using this version will also cause chunks around the loader's reported coordinates to get random block
|
||||
* updates.
|
||||
*
|
||||
* @deprecated
|
||||
* @see World::registerTickingChunk()
|
||||
* @see World::unregisterTickingChunk()
|
||||
*/
|
||||
interface TickingChunkLoader extends ChunkLoader{
|
||||
|
||||
|
@ -210,13 +210,24 @@ class World implements ChunkManager{
|
||||
/**
|
||||
* @var TickingChunkLoader[] spl_object_id => TickingChunkLoader
|
||||
* @phpstan-var array<int, TickingChunkLoader>
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
private array $tickingLoaders = [];
|
||||
/**
|
||||
* @var int[] spl_object_id => number of chunks
|
||||
* @phpstan-var array<int, int>
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
private array $tickingLoaderCounter = [];
|
||||
|
||||
/**
|
||||
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
|
||||
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
|
||||
*/
|
||||
private array $tickingChunks = [];
|
||||
|
||||
/**
|
||||
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
||||
* @phpstan-var array<ChunkPosHash, array<int, ChunkLoader>>
|
||||
@ -496,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.");
|
||||
@ -1129,32 +1140,61 @@ class World implements ChunkManager{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the radius of chunks to be ticked around each ticking chunk loader (usually players). This is referred to
|
||||
* as "simulation distance" in the Minecraft: Bedrock world options screen.
|
||||
* Returns the radius of chunks to be ticked around each player. This is referred to as "simulation distance" in the
|
||||
* Minecraft: Bedrock world options screen.
|
||||
*/
|
||||
public function getChunkTickRadius() : int{
|
||||
return $this->chunkTickRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of chunks ticked around each ticking chunk loader (usually players).
|
||||
* Sets the radius of chunks ticked around each player. This may not take effect immediately, since each player
|
||||
* needs to recalculate their tick radius.
|
||||
*/
|
||||
public function setChunkTickRadius(int $radius) : void{
|
||||
$this->chunkTickRadius = $radius;
|
||||
}
|
||||
|
||||
private function tickChunks() : void{
|
||||
if($this->chunkTickRadius <= 0 || count($this->tickingLoaders) === 0){
|
||||
return;
|
||||
/**
|
||||
* Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is
|
||||
* registered to it.
|
||||
*/
|
||||
public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
||||
$chunkPosHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$entry = $this->tickingChunks[$chunkPosHash] ?? null;
|
||||
if($entry === null){
|
||||
$entry = $this->tickingChunks[$chunkPosHash] = new TickingChunkEntry();
|
||||
}
|
||||
$entry->tickers[spl_object_id($ticker)] = $ticker;
|
||||
}
|
||||
|
||||
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
|
||||
|
||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
||||
$chunkTickList = [];
|
||||
|
||||
$chunkTickableCache = [];
|
||||
/**
|
||||
* Unregisters the given chunk ticker from the specified chunk. If there are other tickers still registered to the
|
||||
* chunk, it will continue to be ticked.
|
||||
*/
|
||||
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$tickerId = spl_object_id($ticker);
|
||||
if(isset($this->tickingChunks[$chunkHash]->tickers[$tickerId])){
|
||||
unset($this->tickingChunks[$chunkHash]->tickers[$tickerId]);
|
||||
if(count($this->tickingChunks[$chunkHash]->tickers) === 0){
|
||||
unset($this->tickingChunks[$chunkHash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @param true[] $chunkTickList
|
||||
* @param bool[] $chunkTickableCache
|
||||
*
|
||||
* @phpstan-param array<int, true> $chunkTickList
|
||||
* @phpstan-param array<int, bool> $chunkTickableCache
|
||||
* @phpstan-param-out array<int, true> $chunkTickList
|
||||
* @phpstan-param-out array<int, bool> $chunkTickableCache
|
||||
*/
|
||||
private function selectTickableChunksLegacy(array &$chunkTickList, array &$chunkTickableCache) : void{
|
||||
$centerChunks = [];
|
||||
|
||||
$selector = new ChunkSelector();
|
||||
@ -1179,6 +1219,38 @@ class World implements ChunkManager{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function tickChunks() : void{
|
||||
if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
|
||||
|
||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
||||
$chunkTickList = [];
|
||||
|
||||
$chunkTickableCache = [];
|
||||
|
||||
foreach($this->tickingChunks as $hash => $entry){
|
||||
if(!$entry->ready){
|
||||
World::getXZ($hash, $chunkX, $chunkZ);
|
||||
if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
|
||||
$entry->ready = true;
|
||||
}else{
|
||||
//the chunk has been flagged as temporarily not tickable, so we don't want to tick it this time
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$chunkTickList[$hash] = true;
|
||||
}
|
||||
|
||||
//TODO: REMOVE THIS
|
||||
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
|
||||
if(count($this->tickingLoaders) !== 0){
|
||||
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
|
||||
}
|
||||
|
||||
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
|
||||
|
||||
@ -1230,11 +1302,28 @@ class World implements ChunkManager{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the 3x3 chunks around the specified chunk as not ready to be ticked. This is used to prevent chunk ticking
|
||||
* while a chunk is being populated, light-populated, or unloaded.
|
||||
* Each chunk will be rechecked every tick until it is ready to be ticked again.
|
||||
*/
|
||||
private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{
|
||||
for($cx = -1; $cx <= 1; ++$cx){
|
||||
for($cz = -1; $cz <= 1; ++$cz){
|
||||
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
|
||||
if(isset($this->tickingChunks[$chunkHash])){
|
||||
$this->tickingChunks[$chunkHash]->ready = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function orderLightPopulation(int $chunkX, int $chunkZ) : void{
|
||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
|
||||
if($lightPopulatedState === false){
|
||||
$this->chunks[$chunkHash]->setLightPopulated(null);
|
||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
||||
|
||||
$this->workerPool->submitTask(new LightPopulationTask(
|
||||
$this->chunks[$chunkHash],
|
||||
@ -2302,6 +2391,7 @@ class World implements ChunkManager{
|
||||
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
|
||||
}
|
||||
$this->chunkLock[$chunkHash] = $lockId;
|
||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2367,6 +2457,7 @@ class World implements ChunkManager{
|
||||
unset($this->blockCache[$chunkHash]);
|
||||
unset($this->changedBlocks[$chunkHash]);
|
||||
$chunk->setTerrainDirty();
|
||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
|
||||
|
||||
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
||||
$this->unloadChunkRequest($chunkX, $chunkZ);
|
||||
@ -2809,6 +2900,8 @@ class World implements ChunkManager{
|
||||
unset($this->chunks[$chunkHash]);
|
||||
unset($this->blockCache[$chunkHash]);
|
||||
unset($this->changedBlocks[$chunkHash]);
|
||||
unset($this->tickingChunks[$chunkHash]);
|
||||
$this->markTickingChunkUnavailable($x, $z);
|
||||
|
||||
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
|
||||
$this->logger->debug("Rejecting population promise for chunk $x $z");
|
||||
|
@ -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){
|
||||
|
@ -53,27 +53,27 @@ class WorldTimings{
|
||||
public function __construct(World $world){
|
||||
$name = $world->getFolderName() . " - ";
|
||||
|
||||
$this->setBlock = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "setBlock");
|
||||
$this->doBlockLightUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Block Light Updates");
|
||||
$this->doBlockSkyLightUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Sky Light Updates");
|
||||
$this->setBlock = new TimingsHandler($name . "setBlock", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doBlockLightUpdates = new TimingsHandler($name . "Block Light Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doBlockSkyLightUpdates = new TimingsHandler($name . "Sky Light Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->doChunkUnload = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Unload Chunks");
|
||||
$this->scheduledBlockUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Scheduled Block Updates");
|
||||
$this->randomChunkUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Random Chunk Updates");
|
||||
$this->randomChunkUpdatesChunkSelection = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Random Chunk Updates - Chunk Selection");
|
||||
$this->doChunkGC = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Garbage Collection");
|
||||
$this->entityTick = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Tick Entities");
|
||||
$this->doChunkUnload = new TimingsHandler($name . "Unload Chunks", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->scheduledBlockUpdates = new TimingsHandler($name . "Scheduled Block Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->randomChunkUpdates = new TimingsHandler($name . "Random Chunk Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->randomChunkUpdatesChunkSelection = new TimingsHandler($name . "Random Chunk Updates - Chunk Selection", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doChunkGC = new TimingsHandler($name . "Garbage Collection", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->entityTick = new TimingsHandler($name . "Tick Entities", group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
Timings::init(); //make sure the timers we want are available
|
||||
$this->syncChunkSend = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunks", Timings::$playerChunkSend);
|
||||
$this->syncChunkSendPrepare = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunk Prepare", Timings::$playerChunkSend);
|
||||
$this->syncChunkSend = new TimingsHandler($name . "Player Send Chunks", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkSendPrepare = new TimingsHandler($name . "Player Send Chunk Prepare", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->syncChunkLoad = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load", Timings::$worldLoad);
|
||||
$this->syncChunkLoadData = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Data");
|
||||
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Fix Invalid Blocks");
|
||||
$this->syncChunkLoadEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Entities");
|
||||
$this->syncChunkLoadTileEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - TileEntities");
|
||||
$this->syncChunkSave = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Save", Timings::$worldSave);
|
||||
$this->syncChunkLoad = new TimingsHandler($name . "Chunk Load", Timings::$worldLoad, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadData = new TimingsHandler($name . "Chunk Load - Data", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler($name . "Chunk Load - Fix Invalid Blocks", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadEntities = new TimingsHandler($name . "Chunk Load - Entities", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadTileEntities = new TimingsHandler($name . "Chunk Load - TileEntities", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkSave = new TimingsHandler($name . "Chunk Save", Timings::$worldSave, group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->doTick = new TimingsHandler($name . "World Tick");
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
<?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 pmmp\TesterPlugin;
|
||||
|
||||
use pmmp\TesterPlugin\event\ChildEvent;
|
||||
use pmmp\TesterPlugin\event\GrandchildEvent;
|
||||
use pmmp\TesterPlugin\event\ParentEvent;
|
||||
use pocketmine\event\EventPriority;
|
||||
use function implode;
|
||||
|
||||
final class EventHandlerInheritanceTest extends Test{
|
||||
|
||||
private const EXPECTED_ORDER = [
|
||||
GrandchildEvent::class,
|
||||
ChildEvent::class,
|
||||
ParentEvent::class
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private array $callOrder = [];
|
||||
|
||||
public function getName() : string{
|
||||
return "Event Handler Inheritance Test";
|
||||
}
|
||||
|
||||
public function getDescription() : string{
|
||||
return "Tests that child events are correctly passed to parent event handlers";
|
||||
}
|
||||
|
||||
public function run() : void{
|
||||
$plugin = $this->getPlugin();
|
||||
$plugin->getServer()->getPluginManager()->registerEvent(
|
||||
ParentEvent::class,
|
||||
function(ParentEvent $event) : void{
|
||||
$this->callOrder[] = ParentEvent::class;
|
||||
},
|
||||
EventPriority::NORMAL,
|
||||
$plugin
|
||||
);
|
||||
$plugin->getServer()->getPluginManager()->registerEvent(
|
||||
ChildEvent::class,
|
||||
function(ChildEvent $event) : void{
|
||||
$this->callOrder[] = ChildEvent::class;
|
||||
},
|
||||
EventPriority::NORMAL,
|
||||
$plugin
|
||||
);
|
||||
$plugin->getServer()->getPluginManager()->registerEvent(
|
||||
GrandchildEvent::class,
|
||||
function(GrandchildEvent $event) : void{
|
||||
$this->callOrder[] = GrandchildEvent::class;
|
||||
},
|
||||
EventPriority::NORMAL,
|
||||
$plugin
|
||||
);
|
||||
|
||||
$event = new GrandchildEvent();
|
||||
$event->call();
|
||||
|
||||
if($this->callOrder === self::EXPECTED_ORDER){
|
||||
$this->setResult(Test::RESULT_OK);
|
||||
}else{
|
||||
$plugin->getLogger()->error("Expected order: " . implode(", ", self::EXPECTED_ORDER) . ", got: " . implode(", ", $this->callOrder));
|
||||
$this->setResult(Test::RESULT_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
@ -56,7 +56,9 @@ class Main extends PluginBase implements Listener{
|
||||
}
|
||||
}), 10);
|
||||
|
||||
$this->waitingTests = [];
|
||||
$this->waitingTests = [
|
||||
new EventHandlerInheritanceTest($this),
|
||||
];
|
||||
}
|
||||
|
||||
public function onServerCommand(CommandEvent $event) : void{
|
||||
|
28
tests/plugins/TesterPlugin/src/event/ChildEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/ChildEvent.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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 pmmp\TesterPlugin\event;
|
||||
|
||||
class ChildEvent extends ParentEvent{
|
||||
|
||||
}
|
28
tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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 pmmp\TesterPlugin\event;
|
||||
|
||||
class GrandchildEvent extends ChildEvent{
|
||||
|
||||
}
|
28
tests/plugins/TesterPlugin/src/event/ParentEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/ParentEvent.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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 pmmp\TesterPlugin\event;
|
||||
|
||||
class ParentEvent extends \pocketmine\event\Event{
|
||||
|
||||
}
|
@ -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)){
|
||||
|
Reference in New Issue
Block a user