Compare commits

...

115 Commits

Author SHA1 Message Date
1698eac6dc Release 4.22.2 2023-07-01 12:04:59 +01:00
321972b87b Composer: do not allow automatic minor dependency updates
manual intervention should always be used here, since we need to present a consistent API to plugins.
2023-07-01 11:57:13 +01:00
c86c9b3ead Update Composer dependencies 2023-07-01 11:43:36 +01:00
249ef9c534 ProcessLoginTask: remove old key expiry
since we don't have a hard date for this, and I've already made one wrong educated guess, I'd rather not have another massive outage.
A security update will have to be made to remove the old key as soon as the new one is rolled. This is not ideal, but it's the least disruptive option.
2023-07-01 11:41:04 +01:00
f4dab17a1b Added deprecation notices 2023-07-01 10:29:14 +01:00
e85605af7f changelog: fixed typo
[ci skip]
2023-06-09 01:44:45 +01:00
dfd70615ad 4.22.2 is next 2023-06-09 01:33:35 +01:00
ee903cad1f Release 4.22.1 2023-06-09 01:33:35 +01:00
9a04481bec Entity: broadcast teleports as regular movements
fixes #5810

probably fixes #4986

#5810 was caused by the workaround for #4394, which broke in 1.20 for reasons I'm still unclear on.

As FLAG_TELEPORT does not work at all for non-player entities, and causes bugs with player entities, sending the teleport movement without the flag is the least buggy way to solve all of these issues. Having the client interpolate teleport movements is not ideal, but there doesn't seem to be a way to reliably prevent it without causing even more bugs, so this will have to do.
2023-06-09 01:24:57 +01:00
5d514a274f Merge branch 'legacy/pm4' of github.com:pmmp/PocketMine-MP into legacy/pm4 2023-06-07 21:24:13 +01:00
2220dc557e 4.22.1 is next 2023-06-07 21:23:57 +01:00
b5fc31a781 Release 4.22.0 2023-06-07 21:23:54 +01:00
179eec9754 PHP-CS-Fixer 3.17 2023-06-07 21:04:11 +01:00
441f1f534f Random change PHP-CS-Fixer wanted to make 2023-06-07 20:59:32 +01:00
e747478afd and one more 2023-06-07 20:58:15 +01:00
92c45dd7e1 Fixed PHPUnit deprecation warnings 2023-06-07 20:57:43 +01:00
2538880408 1.20.0 2023-06-07 20:56:59 +01:00
c715efb18e Jukebox: fix music not stopping when destroyed by explosion
closes #5794
2023-06-03 21:22:26 +01:00
6678360c00 Make changelogs less infuriating in PhpStorm 2023-06-02 13:34:16 +01:00
3db45b6a68 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-06-01 14:51:14 +01:00
3e87ad281f Use tagged fork of netresearch/jsonmapper
we need this for it to not be a huge pain in the ass to install PM as a composer dependency, which plugin CIs may do.
2023-06-01 14:51:04 +01:00
b72da777eb Bump tests/plugins/DevTools from a67f9af to a2f36e8 (#5785)
Bumps [tests/plugins/DevTools](https://github.com/pmmp/DevTools) from `a67f9af` to `a2f36e8`.
- [Release notes](https://github.com/pmmp/DevTools/releases)
- [Commits](a67f9af8d6...a2f36e8dbf)

---
updated-dependencies:
- dependency-name: tests/plugins/DevTools
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 13:45:20 +01:00
1101f35c17 Update setup-php-action to 2.0.0
this version vastly improves build time by using optimized prebuilts
instead of building the binaries on the runner.
2023-05-31 22:09:33 +01:00
3948dc4f75 Remove calls to ReflectionProperty::setAccessible() (#5783)
This is a no-op in PHP 8.1 and up.
2023-05-31 14:03:14 +01:00
20942b37eb Update composer dependencies 2023-05-30 16:07:01 +01:00
d343db8750 4.21.2 is next 2023-05-30 14:42:59 +01:00
f2df702c67 Release 4.21.1 2023-05-30 14:42:59 +01:00
481270e6aa Merge tag '4.20.5' into stable 2023-05-30 14:42:11 +01:00
e7bdaa8579 Release 4.20.5 2023-05-30 14:35:17 +01:00
76749cbaa7 Use fork of JsonMapper to solve cweiske/jsonmapper#210 2023-05-30 14:30:28 +01:00
a897bdfaa0 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-05-30 14:17:21 +01:00
09668a37d6 Use fork of JsonMapper to solve cweiske/JsonMapper#210 2023-05-30 14:17:09 +01:00
ea92a23d0d Bump build/php from b1d5c0d to f2ece7b (#5765)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `b1d5c0d` to `f2ece7b`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](b1d5c0d737...f2ece7b30d)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:37:18 +01:00
691e67018d Bump phpstan/phpstan-phpunit from 1.3.11 to 1.3.13 (#5772)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.3.11 to 1.3.13.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.3.11...1.3.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:36:58 +01:00
fe2140a716 Bump shivammathur/setup-php from 2.25.1 to 2.25.2 (#5766)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.25.1 to 2.25.2.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.25.1...2.25.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:36:46 +01:00
57330a7186 Bump build/php from f860ade to b1d5c0d (#5760)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `f860ade` to `b1d5c0d`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](f860ade30a...b1d5c0d737)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 12:27:12 +01:00
9ddac21de0 Bump shivammathur/setup-php from 2.24.0 to 2.25.1 (#5711)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.24.0 to 2.25.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.24.0...2.25.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-21 00:13:22 +01:00
c91aa24daa Bump phpunit/phpunit from 9.6.8 to 10.1.3 (#5753)
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.8 to 10.1.3.
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.1.3/ChangeLog-10.1.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.8...10.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 15:09:24 +01:00
6186fc0bfe 4.21.1 is next 2023-05-17 16:45:35 +01:00
ef40934d24 Release 4.21.0 2023-05-17 16:45:32 +01:00
69b668355f Merge branch 'minor-next' into stable 2023-05-17 16:12:24 +01:00
0547383296 Update build/php submodule to pmmp/PHP-Binaries@f860ade30a 2023-05-17 15:08:05 +01:00
c7dff9ea40 bootstrap: remove ext-parallel bootstrapping code
I have no intention of using parallel, so this code is not necessary.
2023-05-17 14:11:43 +01:00
043350753b Drop PHP 8.0, 8.1 is now minimum version 2023-05-17 13:53:57 +01:00
5ad8016b99 Merge branch 'stable' into minor-next 2023-05-17 13:44:45 +01:00
2e5b2eed6e Update composer dependencies 2023-05-17 13:43:28 +01:00
5a0cde49cc AsyncPool: do not double-check progress updates on finished tasks
checkProgressUpdates is called directly before onCompletion, so we only need to call it again if the task isn't finished yet.
2023-05-16 23:37:58 +01:00
008a022ec1 Players now have finite resources in spectator mode
this seems like the logical solution for the block picking issues.
2023-05-16 23:02:33 +01:00
5c85a7c306 Merge remote-tracking branch 'origin/stable' into minor-next 2023-05-16 22:54:53 +01:00
599c4284f5 Introduce 10 KB threshold for async compression
due to the extremely large performance cost of instantiating AsyncTasks, it's usually not worth bothering with async compression except for very large packets.
While this large overhead can be significantly reduced by using specialized threads, it's early days in the testing stages for such improvements, and for now, we still have this to deal with.

Since async compression is always used prior to player spawn, this change may slightly improve the performance of the pre-join stage of the game.
2023-05-16 22:54:06 +01:00
9499e2e595 always the CS... 2023-05-16 14:22:03 +01:00
a4fea1444a Remove validateCallableSignature() calls from network hot paths
we rely on phpstan for validation of this internally, and plugins shouldn't be calling these methods anyway.
this significantly reduces the overhead of CompressBatchPromise.
2023-05-16 14:21:32 +01:00
1ba47802a8 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-05-15 14:59:02 +01:00
9d111e13f1 CONTRIBUTING: added table of in-house dependencies and which classes, functions or namespaces they contain 2023-05-15 14:58:31 +01:00
44bc4d8c7c Bump phpstan/phpstan from 1.10.14 to 1.10.15 (#5741)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.14 to 1.10.15.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.14...1.10.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 15:02:59 +01:00
d317347a9b WorldTimings: remove TODO
I tried this, and it didn't really provide any information that the tree table didn't already show.
2023-05-08 16:35:30 +01:00
077fac84bf Added aggregate timers for all world timings
this allows timings list view to display totals for these sections. It does make the tree view a bit more annoying in some cases though.
2023-05-08 16:27:46 +01:00
fdb3a5b121 Fixed incorrect implementation of peak timings 2023-05-07 18:29:37 +01:00
e3bc36ab5b Merge branch 'stable' into minor-next 2023-05-06 18:26:47 +01:00
283ff28aa9 4.20.5 is next 2023-05-06 18:20:19 +01:00
c3ceeeace7 Release 4.20.4 2023-05-06 18:20:18 +01:00
aac4f6c0e1 Fixed all game modes allowing flight
moral of the story: do not trust that mojang things do what they say they do - the spectator ability layer always applies, regardless of whether the player is actually in spectator mode or not ...
2023-05-06 18:18:05 +01:00
bb60a9057f Merge branch 'stable' into minor-next 2023-05-06 17:08:29 +01:00
3b893961e4 4.20.4 is next 2023-05-06 17:01:49 +01:00
325ffec1be Release 4.20.3 2023-05-06 17:01:49 +01:00
fa715a074a Fixed TimingsHandler depth not getting reset when timings is disabled
When timings was disabled, internalStopTiming is not called, and timer depth is not decremented.
If timings is later reenabled, the next call to internalStartTiming will think the timer is already running, and won't generate any new records for the timer.
This has led to broken timings reports with missing Full Server Tick entries, amongst other things.
2023-05-06 16:56:39 +01:00
4caa2c7690 NetworkSession: send FLYING flag on spectator ability layer
fixes #5722

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

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

This change fixes the problem by ensuring that any handlers derived from the same function, handling the same event class, will share the same timer.
2023-05-06 15:42:52 +01:00
02cf5ed388 RuntimeBlockMapping: lazy-load NBT blockstates
this saves a considerable amount of memory.

we don't actually need this state array in PM4 anyway, since we don't support the client-side chunk cache yet.
when the time comes to support it, it'll be much more practical to cache binary states and copy bytes anyway, instead of doing it the current way, which is both slow and memory-intensive.

Measured footprint change: 9 MB -> 400 KB.
2023-05-05 16:18:03 +01:00
6cad559dbe Merge branch 'stable' into minor-next 2023-05-05 16:08:30 +01:00
84a943bcec BaseInventory: slap a TODO on isSlotEmpty() 2023-05-05 16:06:37 +01:00
633e77a34c RuntimeBlockMapping: share states CompoundTags if they are the same
this allows saving about 4 MB of memory, because there are many blocks which have identical states, although they have different IDs.

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-01 13:30:02 +01:00
b70ff32548 ItemTranslator: Fixed log items not displaying correctly on the client
closes #5724

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

In the future it would probably make more sense to bypass ItemTranslator entirely, and rely solely on the blockstate returned by RuntimeBlockMapping to fetch the correct ID. This is similar to how we serialize items for saving on disk in PM5.
2023-04-28 13:54:40 +01:00
73bf5d4b29 DoubleChestInventory: specialize isSlotEmpty() and getMatchingItemCount() 2023-04-27 21:17:55 +01:00
eb136e60c8 BaseInventory: added getMatchingItemCount() helper
this eliminates the performance issues described by #5719.
closes #5719

we may want to consider exposing a public API for this in the future, since it might be useful for plugins.
2023-04-27 21:08:35 +01:00
4228880509 BaseInventory: change dumb variable names in internalAddItem() 2023-04-27 20:29:02 +01:00
709d874204 BaseInventory: clean up max stack size handling
we can safely assume that:
- the inventory's max stack size won't change during the operation
- two items which stack together have the same max stack size
- the item's max stack size won't change during the operation
2023-04-27 20:27:05 +01:00
07dc10d6e6 World: improve performance of tickChunks() selection process (#5721)
Since light population is required to make a chunk tickable, a chunk may not be tickable for some time if the async workers get backlogged.
The previous version of this system only cached the eligibility result if the result was a "yes", but we can also track it when it's a "no", rather than rechecking it every tick.

This change should improve performance in factions and similar gamemodes, which involve large maps with sparsely distributed players, where each player likely has an independent, non-overlapping ticking chunk circle.

We also ditch TickingChunkEntry in favour of multiple arrays to track the eligibility states. This allows us to avoid rechecking the (even cached) readiness of potentially thousands of chunks. If there are no ticking chunks to recheck, this reduces the cost of the selection process to zero.
2023-04-27 16:59:29 +01:00
7f6269c432 Introduce and use optimised versions of Inventory->isSlotEmpty()
this avoids useless cloning, improving the performance of several functions.
2023-04-27 16:52:52 +01:00
194714b448 Merge branch 'stable' into minor-next 2023-04-27 15:37:54 +01:00
023460db2c BaseInventory: fixed internalAddItem() doing useless canStackWith() checks on null items
if the item is null, it's never going to stack with anything given to this function, because addItem() already discards null items.
2023-04-27 14:53:54 +01:00
2910ffebf4 4.20.2 is next 2023-04-27 13:31:02 +01:00
fea820a99e Release 4.20.1 2023-04-27 13:31:02 +01:00
7c19f14cf5 Fixed up offhand handling for ItemStackRequest, fixes #5723 2023-04-27 13:25:08 +01:00
5a54d09869 InventoryManager: verify slot existence in locateWindowAndSlot()
previously, this would happily return invalid slot IDs, potentially leading to a crash.
2023-04-27 13:18:28 +01:00
4def4d52d9 Merge branch 'stable' into minor-next 2023-04-26 23:22:00 +01:00
1d10107024 4.20.1 is next 2023-04-26 23:15:38 +01:00
54ae4d0ea2 Release 4.20.0 2023-04-26 23:15:34 +01:00
0d21e591d1 Support sign editing UI in 1.19.80, with APIs to allow plugins to use it
this doesn't support editing the rear side of a sign, since the 1.12 format doesn't allow us to represent the rear text, and it would necessitate API breaks to support anyway.

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

This is, however, a byproduct of the fact that APIs needed to be added in order to facilitate the use of OpenSignPacket in 1.19.80.
2023-04-26 22:55:05 +01:00
408616723c Changes for 1.19.80 2023-04-26 22:52:02 +01:00
9bfcd39f2a World: improve type info for getTickingChunks() 2023-04-26 17:06:52 +01:00
8102616ff4 Added ticking chunk count to /status
closes #5716
2023-04-26 17:05:31 +01:00
b162d688a3 CI: use php-cs-fixer 3.16 2023-04-26 16:05:06 +01:00
eb130f2906 Move primary version to PHP 8.1
8.0 is still supported for now, but won't be updated any longer.
2023-04-26 16:03:33 +01:00
3b09c3a48a actions: updated setup-php-action to pmmp/setup-php-action@c7fb29d835 2023-04-26 14:40:39 +01:00
87781cff4d Update GitHub Actions PHP versions 2023-04-26 14:38:40 +01:00
db0cf4bb5a Update composer dependencies 2023-04-26 14:35:05 +01:00
eb4679fefd Merge remote-tracking branch 'origin/stable' into minor-next 2023-04-26 14:28:29 +01:00
a4f2b99ed5 InGamePacketHandler: queue slots for syncing if they appear in requestChangedSlots
this is essentially a prediction without the actual predicted item. We have to sync these regardless of what happens.

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

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

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

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

fixes #5713
2023-04-21 16:19:15 +01:00
d06d3bc871 4.19.4 is next 2023-04-21 15:53:05 +01:00
71b78b02d3 Merge remote-tracking branch 'origin/stable' into minor-next 2023-04-19 23:57:26 +01:00
84cb070d56 Merge branch 'stable' into minor-next 2023-04-14 20:12:33 +01:00
8102586ee0 Merge branch 'stable' into minor-next 2023-04-12 21:04:03 +01:00
0336394098 Human: remove useless NameTag tag
this is not written anywhere, so this code never does anything.
2023-04-12 16:44:10 +01:00
1569bed37a Merge branch 'stable' into minor-next 2023-04-11 23:56:22 +01:00
a6e79c5004 TimingsHandler: remove useless paste metadata
these fields are not used by any version of timings, so this code is redundant.
2023-04-11 23:45:01 +01:00
67 changed files with 1184 additions and 1030 deletions

View File

@ -13,9 +13,9 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
php-version: 8.1
- name: Restore Composer package cache
uses: actions/cache@v3

View File

@ -18,9 +18,9 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
php-version: 8.1
- name: Restore Composer package cache
uses: actions/cache@v3
@ -84,3 +84,5 @@ jobs:
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
:warning: **4.x is now deprecated. Please read https://github.com/pmmp/PocketMine-MP/issues/5784 for details, and prepare to upgrade to 5.x.**

View File

@ -6,47 +6,26 @@ on:
workflow_dispatch:
jobs:
build-php:
name: Prepare PHP
runs-on: ${{ matrix.image }}
strategy:
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
steps:
- name: Build and prepare PHP cache
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
phpstan:
name: PHPStan analysis
needs: build-php
runs-on: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@2.0.0
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
- name: Restore Composer package cache
uses: actions/cache@v3
with:
@ -58,34 +37,30 @@ jobs:
composer-v2-cache-
- name: Install Composer dependencies
run: php composer.phar install --prefer-dist --no-interaction
run: composer install --prefer-dist --no-interaction
- name: Run PHPStan
run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
phpunit:
name: PHPUnit tests
needs: build-php
runs-on: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@2.0.0
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
- name: Restore Composer package cache
uses: actions/cache@v3
with:
@ -97,20 +72,19 @@ jobs:
composer-v2-cache-
- name: Install Composer dependencies
run: php composer.phar install --prefer-dist --no-interaction
run: composer install --prefer-dist --no-interaction
- name: Run PHPUnit tests
run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
integration:
name: Integration tests
needs: build-php
runs-on: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v3
@ -118,15 +92,12 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@2.0.0
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
- name: Restore Composer package cache
uses: actions/cache@v3
with:
@ -138,34 +109,30 @@ jobs:
composer-v2-cache-
- name: Install Composer dependencies
run: php composer.phar install --no-dev --prefer-dist --no-interaction
run: composer install --no-dev --prefer-dist --no-interaction
- name: Run integration tests
run: ./tests/travis.sh -t4
codegen:
name: Generated Code consistency checks
needs: build-php
runs-on: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.16, 8.2.3]
php: ["8.1", "8.2"]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
uses: pmmp/setup-php-action@2.0.0
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
- name: Restore Composer package cache
uses: actions/cache@v3
with:
@ -177,7 +144,7 @@ jobs:
composer-v2-cache-
- name: Install Composer dependencies
run: php composer.phar install --no-dev --prefer-dist --no-interaction
run: composer install --no-dev --prefer-dist --no-interaction
- name: Regenerate registry annotations
run: php build/generate-registry-annotations.php src
@ -203,10 +170,10 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
tools: php-cs-fixer:3.11
php-version: 8.1
tools: php-cs-fixer:3.17
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,51 +0,0 @@
<?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);
const VERSIONS = [
"8.0",
"8.1",
"8.2"
];
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
$newWorkflowFile = $workflowFile;
foreach(VERSIONS as $v){
$releaseInfo = file_get_contents("https://secure.php.net/releases?json&version=$v");
if($releaseInfo === false){
throw new \RuntimeException("Failed to contact php.net API");
}
$data = json_decode($releaseInfo, true);
if(!is_array($data) || !isset($data["version"]) || !is_string($data["version"]) || preg_match('/^\d+\.\d+\.\d+(-[A-Za-z\d]+)?$/', $data["version"]) === 0){
throw new \RuntimeException("Invalid data returned by API");
}
$updated = preg_replace("/$v\.\d+/", $data["version"], $newWorkflowFile);
if($updated !== $newWorkflowFile){
echo "Updated $v revision to " . $data["version"] . "\n";
}
$newWorkflowFile = $updated;
}
if($workflowFile !== $newWorkflowFile){
echo "Writing modified workflow file\n";
file_put_contents(__DIR__ . '/main.yml', $newWorkflowFile);
}

View File

@ -30,6 +30,13 @@
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Markdown">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="CLASS_BRACE_STYLE" value="1" />
<option name="METHOD_BRACE_STYLE" value="1" />

View File

@ -2,13 +2,13 @@
## Pre-requisites
- A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell
- PHP 8.0 or newer available in your shell
- PHP 8.1 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate)
- [Prebuilt binaries](https://github.com/pmmp/PHP-Binaries/releases)
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
@ -29,11 +29,5 @@ Run `composer make-server` using your preferred PHP binary. It'll drop a `Pocket
You can also use the `--out` option to change the output filename.
There is a bug in PHP that might cause an error which looks like this:
```
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
```
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3.
## Running PocketMine-MP from source code
Run `src/PocketMine.php` using your preferred PHP binary.

View File

@ -18,6 +18,30 @@ Larger contributions like feature additions should be preceded by a [Change Prop
## Other things you'll need
- [git](https://git-scm.com/)
## List of `pocketmine` namespaces which are in other repos
PocketMine-MP has several dependencies which are independent from the main server code. Most of them use the `pocketmine` namespace.
Some of these add extra classes to packages which already exist in PocketMine-MP.
Take a look at the table below if you can't find the class or function you're looking for.
| Source URL | Namespace, class or function |
|:----------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|
| [pmmp/BedrockProtocol](https://github.com/pmmp/BedrockProtocol) | `pocketmine\network\mcpe\protocol` |
| [pmmp/BinaryUtils](https://github.com/pmmp/BinaryUtils) | `pocketmine\utils\BinaryDataException`</br>`pocketmine\utils\BinaryStream`</br>`pocketmine\utils\Binary` |
| [pmmp/ClassLoader](https://github.com/pmmp/`ClassLoader`) | `BaseClassLoader`</br>`ClassLoader`</br>`DynamicClassLoader` |
| [pmmp/Color](https://github.com/pmmp/Color) | `pocketmine\color` |
| [pmmp/ErrorHandler](https://github.com/pmmp/ErrorHandler) | `pocketmine\errorhandler` |
| [pmmp/LogPthreads](https://github.com/pmmp/LogPthreads) | `ThreadedLoggerAttachment`</br>`ThreadedLogger`</br>`AttachableThreadedLogger` |
| [pmmp/Log](https://github.com/pmmp/Log) | `AttachableLogger`</br>`BufferedLogger`</br>`GlobalLogger`</br>`LogLevel`</br>`Logger`</br>`PrefixedLogger`</br>`SimpleLogger` |
| [pmmp/Math](https://github.com/pmmp/Math) | `pocketmine\math` |
| [pmmp/NBT](https://github.com/pmmp/NBT) | `pocketmine\nbt` |
| [pmmp/RakLibIpc](https://github.com/pmmp/RakLibIpc) | `raklib\server\ipc` |
| [pmmp/RakLib](https://github.com/pmmp/RakLib) | `raklib` |
| [pmmp/Snooze](https://github.com/pmmp/Snooze) | `pocketmine\snooze` |
| [pmmp/ext-chunkutils2](https://github.com/pmmp/ext-chunkutils2) | `pocketmine\world\format\LightArray`</br>`pocketmine\world\format\PalettedBlockArray`</br>`pocketmine\world\format\io\SubChunkConverter` |
| [pmmp/ext-morton](https://github.com/pmmp/ext-morton) | `morton2d_decode`</br>`morton2d_encode`</br>`morton3d_decode`</br>`morton3d_encode` |
| [pmmp/ext-libdeflate](https://github.com/pmmp/ext-libdeflate) | `libdeflate_deflate_compress`</br>`libdeflate_gzip_compress`</br>`libdeflate_zlib_compress` |
## Choosing a target branch
PocketMine-MP has three primary branches of development.

View File

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

79
changelogs/4.20.md Normal file
View File

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

77
changelogs/4.21.md Normal file
View File

@ -0,0 +1,77 @@
**For Minecraft: Bedrock Edition 1.19.80**
### Note about API versions
Plugins which don't touch the `pocketmine\network\mcpe` namespace, and don't use reflection or any internal methods,
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.
# 4.21.0
Released 17th May 2023.
## General
- PHP 8.1 is now required. Most plugins should run without changes, but some might need to be updated due to language-level deprecations.
- Ticking chunk count is now shown separately from loaded chunk count in the `/status` command, providing useful performance information.
- Further improved performance of ticking chunk selection.
- Improved performance of some inventory functions.
- Reduced server memory footprint in most cases by ~9 MB per thread.
- Due to large overhead, async network compression is now only used for packets larger than 10 KB by default.
## Configuration
- Added the following new `pocketmine.yml` configuration options:
- `network.async-compression-threshold` - minimum size of packet which will be compressed using `AsyncTask`
- Default is 10 KB, which means that very few packets will use async compression in practice. This is because the overhead of compressing async is currently so high that it's not worth it for smaller packets.
## Timings
- Timings reports no longer include the unused metadata fields `Entities` and `LivingEntities`.
- Timings reports now correctly calculate the peak time of a timer.
- Previously it was incorrectly recorded as the longest time spent in a single tick, rather than the longest time spent in a single activation.
- Timings report version has been bumped to `2` to reflect this change.
- All world-specific timers now have generic aggregate timings, making it much easier to locate performance patterns across all worlds.
## Gameplay
- Players in spectator mode are no longer able to pick blocks, and now have finite resources similar to survival mode.
## API
### `pocketmine\world`
- The following API methods have been added:
- `public World->getTickingChunks() : list<int>` - returns a list of chunk position hashes (a la `World::chunkHash()`) which are currently valid for random ticking
### `pocketmine\inventory`
- The following API methods have been added:
- `protected BaseInventory->getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int` - returns the number of items in the given stack if the content of the slot matches the test item, or zero otherwise
- This should be overridden if directly extending `BaseInventory` to provide a performance-optimised version. A slow default implementation is provided, but it will be removed in the future.
## Internals
### Entity
- Unused `NameTag` tag is no longer saved for `Human` entities.
### Inventory
- `BaseInventory` now uses a new internal method `getMatchingItemCount()` to locate items in the inventory without useless cloning. This improves performance of various API methods, such as `addItem()`, `contains()`, and more.
- Specialization of `Inventory->isSlotEmpty()` in `BaseInventory` subclasses has been added to improve performance of some API methods.
### Network
- `RuntimeBlockMapping` no longer keeps all block palette NBT data in memory.
- This significantly reduces server idle memory footprint.
- For multi-version implementations, this will have a significant impact on memory usage, since a different block palette is often required to support each version.
- NBT will be lazy-loaded into memory and cached if `getBedrockKnownStates()` is called. However, this is not used by PocketMine-MP under normal circumstances.
- Removed unnecessary usage of `Utils::validateCallableSignature()` from some internal network pathways, improving performance.
### Scheduler
- `AsyncPool` no longer double-checks progress updates on completed tasks.
### World
- Ticking chunks are now tracked in `World->validTickingChunks` and `World->recheckTickingChunks`.
- This allows avoiding rechecking every ticking chunk for validity during ticking chunk selection, improving performance.
- In some cases, this allows bypassing chunk selection entirely, reducing selection cost to zero.
- Registered but ineligible ticking chunks are no longer rechecked every tick.
- This was causing wasted cycles during async worker backlog.
- The internal system must call `markTickingChunkForRecheck()` whenever a ticking chunk's eligibility for ticking has potentially changed, rather than just when it has changed from a yes to a no.
# 4.21.1
Released 30th May 2023.
## Fixes
- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).

48
changelogs/4.22.md Normal file
View File

@ -0,0 +1,48 @@
# 4.22.0
Released 7th June 2023.
**For Minecraft: Bedrock Edition 1.20.0**
This is a support release for Minecraft: Bedrock Edition 1.20.0.
**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**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.20.x directly to 4.22.x, please also read the following changelogs, as the interim releases contain important changes:
- [4.21.0](https://github.com/pmmp/PocketMine-MP/blob/4.22.0/changelogs/4.21.md#4210) - PHP 8.1 minimum version, minor performance improvements
## General
- Added support for Minecraft: Bedrock Edition 1.20.0.
- Removed support for older versions.
## Fixes
- Removed deprecated `ReflectionProperty::setAccessible()` calls.
- Fixed jukebox music not stopping when destroyed by an explosion.
# 4.22.1
Released 9th June 2023.
## Fixes
- Replaced workaround for an old teleporting client bug:
- This workaround broke due to an additional client bug introduced by 1.20, causing players to become frozen to observers when teleported.
- The original client bug has still not been fixed, meaning a new workaround was needed, but no perfect solution could be found.
- The new workaround involves broadcasting teleport movements as regular movements, which causes unwanted interpolation between the old and new positions, but otherwise works correctly. This solution is not ideal, but it is the best we can do for now.
- See issues [#4394](https://github.com/pmmp/PocketMine-MP/issues/4394) and [#5810](https://github.com/pmmp/PocketMine-MP/issues/5810) for more details.
# 4.22.2
Released 1st July 2023.
## Changes
- Added obsoletion warnings to the server log at the end of the startup sequence.
## Fixes
- Fixed players being disconnected en masse with "Not authenticated" messages.
- This occurred due to a check intended to disable the old authentication key after July 1st.
- We expected that the new key would have been deployed by Mojang by now, but it seems like that has not yet happened.
- Due to the lack of a hard date for the key changeover, we guessed that July 1st would be a safe bet, but this appears to have backfired.
- This version will accept both old and new keys indefinitely.
- A security release will be published to remove the old key after the transition occurs.

View File

@ -5,7 +5,7 @@
"homepage": "https://pmmp.io",
"license": "LGPL-3.0",
"require": {
"php": "^8.0",
"php": "^8.1",
"php-64bit": "*",
"ext-chunkutils2": "^0.3.1",
"ext-crypto": "^0.3.1",
@ -31,13 +31,13 @@
"ext-zip": "*",
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-block-upgrade-schema": "~1.1.1+bedrock-1.19.70",
"pocketmine/bedrock-data": "~2.1.1+bedrock-1.19.70",
"pocketmine/bedrock-item-upgrade-schema": "~1.1.0+bedrock-1.19.70",
"pocketmine/bedrock-protocol": "~20.1.1+bedrock-1.19.70",
"adhocore/json-comment": "~1.2.0",
"fgrosse/phpasn1": "~2.5.0",
"pocketmine/netresearch-jsonmapper": "~v4.2.999",
"pocketmine/bedrock-block-upgrade-schema": "~2.2.0+bedrock-1.20.0",
"pocketmine/bedrock-data": "~2.3.0+bedrock-1.20.0",
"pocketmine/bedrock-item-upgrade-schema": "~1.3.0+bedrock-1.20.0",
"pocketmine/bedrock-protocol": "~22.0.0+bedrock-1.20.0",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
@ -51,15 +51,15 @@
"pocketmine/raklib": "^0.14.2",
"pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0",
"ramsey/uuid": "^4.1",
"symfony/filesystem": "^5.4",
"webmozart/path-util": "^2.3"
"ramsey/uuid": "~4.7.0",
"symfony/filesystem": "~5.4.0",
"webmozart/path-util": "~2.3.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.13",
"phpstan/phpstan": "1.10.15",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"
"phpunit/phpunit": "^10.1"
},
"autoload": {
"psr-4": {
@ -77,7 +77,7 @@
},
"config": {
"platform": {
"php": "8.0.0"
"php": "8.1.0"
},
"sort-packages": true
},

912
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -85,8 +85,11 @@ network:
batch-threshold: 256
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
compression-level: 6
#Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread
#Use AsyncTasks for compression during the main game session. Increases latency, but may reduce main thread load
async-compression: false
#Threshold for async compression, in bytes. Only packets larger than this will be compressed asynchronously
#Due to large overhead of AsyncTask, async compression isn't worth it except for large packets
async-compression-threshold: 10000
#Experimental. Use UPnP to automatically port forward
upnp-forwarding: false
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be

View File

@ -316,9 +316,6 @@ class MemoryManager{
continue;
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
if(!$property->isInitialized()){
continue;
}
@ -442,9 +439,6 @@ class MemoryManager{
continue;
}
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
if(!$property->isInitialized($object)){
continue;
}

View File

@ -50,7 +50,7 @@ namespace pocketmine {
require_once __DIR__ . '/VersionInfo.php';
const MIN_PHP_VERSION = "8.0.0";
const MIN_PHP_VERSION = "8.1.0";
/**
* @param string $message
@ -265,9 +265,6 @@ JIT_WARNING
exit(1);
}
}
if(extension_loaded('parallel')){
\parallel\bootstrap(\pocketmine\COMPOSER_AUTOLOADER_PATH);
}
ErrorToExceptionHandler::set();

View File

@ -208,6 +208,8 @@ class Server{
private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000;
private static ?Server $instance = null;
private TimeTrackingSleeperHandler $tickSleeper;
@ -266,6 +268,7 @@ class Server{
private Network $network;
private bool $networkCompressionAsync = true;
private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD;
private Language $language;
private bool $forceLanguage = false;
@ -908,6 +911,10 @@ class Server{
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
$this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true);
$this->networkCompressionAsyncThreshold = max(
$this->configGroup->getPropertyInt("network.async-compression-threshold", self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
$netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
);
EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true);
@ -1047,6 +1054,11 @@ class Server{
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning1("4.x", "5.0")));
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning2("4.x", "2023-09-01")));
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning3("https://github.com/pmmp/PocketMine-MP/issues/5784")));
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
@ -1375,7 +1387,7 @@ class Server{
}
$promise = new CompressBatchPromise();
if(!$sync){
if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
$task = new CompressBatchTask($buffer, $promise, $compressor);
$this->asyncPool->submitTask($task);
}else{

View File

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

View File

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

View File

@ -85,6 +85,20 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve
$this->right->setContents($rightContents);
}
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
$leftSize = $this->left->getSize();
return $slot < $leftSize ?
$this->left->getMatchingItemCount($slot, $test, $checkDamage, $checkTags) :
$this->right->getMatchingItemCount($slot - $leftSize, $test, $checkDamage, $checkTags);
}
public function isSlotEmpty(int $index) : bool{
$leftSize = $this->left->getSize();
return $index < $leftSize ?
$this->left->isSlotEmpty($index) :
$this->right->isSlotEmpty($index - $leftSize);
}
protected function getOpenSound() : Sound{ return new ChestOpenSound(); }
protected function getCloseSound() : Sound{ return new ChestCloseSound(); }

View File

@ -26,6 +26,7 @@ namespace pocketmine\block\tile;
use pocketmine\item\Item;
use pocketmine\item\Record;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\world\sound\RecordStopSound;
class Jukebox extends Spawnable{
private const TAG_RECORD = "RecordItem"; //Item CompoundTag
@ -61,4 +62,8 @@ class Jukebox extends Spawnable{
$nbt->setTag(self::TAG_RECORD, $this->record->nbtSerialize());
}
}
protected function onBlockDestroyedHook() : void{
$this->position->getWorld()->addSound($this->position, new RecordStopSound());
}
}

View File

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

View File

@ -110,7 +110,8 @@ class StatusCommand extends VanillaCommand{
$worldName = $world->getFolderName() !== $world->getDisplayName() ? " (" . $world->getDisplayName() . ")" : "";
$timeColor = $world->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
$sender->sendMessage(TextFormat::GOLD . "World \"{$world->getFolderName()}\"$worldName: " .
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " chunks, " .
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " loaded chunks, " .
TextFormat::RED . number_format(count($world->getTickingChunks())) . TextFormat::GREEN . " ticking chunks, " .
TextFormat::RED . number_format(count($world->getEntities())) . TextFormat::GREEN . " entities. " .
"Time $timeColor" . round($world->getTickRateTime(), 2) . "ms"
);

View File

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

View File

@ -248,7 +248,6 @@ class CrashDump{
if(file_exists($filePath)){
$reflection = new \ReflectionClass(PluginBase::class);
$file = $reflection->getProperty("file");
$file->setAccessible(true);
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin));
if(str_starts_with($frameCleanPath, $filePath)){

View File

@ -781,29 +781,21 @@ abstract class Entity{
}
protected function broadcastMovement(bool $teleport = false) : void{
if($teleport){
//TODO: HACK! workaround for https://github.com/pmmp/PocketMine-MP/issues/4394
//this happens because MoveActor*Packet doesn't clear interpolation targets on the client, so the entity
//snaps to the teleport position, but then lerps back to the original position if a normal movement for the
//entity was recently broadcasted. This can be seen with players throwing ender pearls.
//TODO: remove this if the bug ever gets fixed (lol)
foreach($this->hasSpawned as $player){
$this->despawnFrom($player);
$this->spawnTo($player);
}
}else{
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
$this->id,
$this->getOffsetPosition($this->location),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw,
(
//TODO: if the above hack for #4394 gets removed, we should be setting FLAG_TELEPORT here
($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
)
)]);
}
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
$this->id,
$this->getOffsetPosition($this->location),
$this->location->pitch,
$this->location->yaw,
$this->location->yaw,
(
//TODO: We should be setting FLAG_TELEPORT here to disable client-side movement interpolation, but it
//breaks player teleporting (observers see the player rubberband back to the pre-teleport position while
//the teleported player sees themselves at the correct position), and does nothing whatsoever for
//non-player entities (movement is still interpolated). Both of these are client bugs.
//See https://github.com/pmmp/PocketMine-MP/issues/4394
($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
)
)]);
}
protected function broadcastMotion() : void{

View File

@ -92,7 +92,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
private const TAG_XP_PROGRESS = "XpP"; //TAG_Float
private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int
private const TAG_XP_SEED = "XpSeed"; //TAG_Int
private const TAG_NAME_TAG = "NameTag"; //TAG_String
private const TAG_SKIN = "Skin"; //TAG_Compound
private const TAG_SKIN_NAME = "Name"; //TAG_String
private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray
@ -245,10 +244,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
*/
protected function initHumanData(CompoundTag $nbt) : void{
if(($nameTagTag = $nbt->getTag(self::TAG_NAME_TAG)) instanceof StringTag){
$this->setNameTag($nameTagTag->getValue());
}
//TODO: use of NIL UUID for namespace is a hack; we should provide a proper UUID for the namespace
$this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
}

View File

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

View File

@ -108,13 +108,23 @@ abstract class BaseInventory implements Inventory{
$this->onContentChange($oldContents);
}
/**
* Helper for utility functions which search the inventory.
* TODO: make this abstract instead of providing a slow default implementation (BC break)
*/
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
$item = $this->getItem($slot);
return $item->equals($test, $checkDamage, $checkTags) ? $item->getCount() : 0;
}
public function contains(Item $item) : bool{
$count = max(1, $item->getCount());
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $i){
if($item->equals($i, $checkDamage, $checkTags)){
$count -= $i->getCount();
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
if($slotCount > 0){
$count -= $slotCount;
if($count <= 0){
return true;
}
@ -128,9 +138,9 @@ abstract class BaseInventory implements Inventory{
$slots = [];
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$slots[$index] = $i;
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
$slots[$i] = $this->getItem($i);
}
}
@ -142,18 +152,9 @@ abstract class BaseInventory implements Inventory{
$checkDamage = $exact || !$item->hasAnyDamageValue();
$checkTags = $exact || $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){
return $index;
}
}
return -1;
}
public function firstEmpty() : int{
foreach($this->getContents(true) as $i => $slot){
if($slot->isNull()){
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
if($slotCount > 0 && ($slotCount === $count || (!$exact && $slotCount > $count))){
return $i;
}
}
@ -161,6 +162,20 @@ abstract class BaseInventory implements Inventory{
return -1;
}
public function firstEmpty() : int{
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
if($this->isSlotEmpty($i)){
return $i;
}
}
return -1;
}
/**
* TODO: make this abstract and force implementations to implement it properly (BC break)
* This default implementation works, but is slow.
*/
public function isSlotEmpty(int $index) : bool{
return $this->getItem($index)->isNull();
}
@ -171,14 +186,16 @@ abstract class BaseInventory implements Inventory{
public function getAddableItemQuantity(Item $item) : int{
$count = $item->getCount();
$maxStackSize = min($this->getMaxStackSize(), $item->getMaxStackSize());
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->canStackWith($slot)){
if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){
if($this->isSlotEmpty($i)){
$count -= $maxStackSize;
}else{
$slotCount = $this->getMatchingItemCount($i, $item, true, true);
if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
$count -= $diff;
}
}elseif($slot->isNull()){
$count -= min($this->getMaxStackSize(), $item->getMaxStackSize());
}
if($count <= 0){
@ -212,22 +229,29 @@ abstract class BaseInventory implements Inventory{
return $returnSlots;
}
private function internalAddItem(Item $slot) : Item{
private function internalAddItem(Item $newItem) : Item{
$emptySlots = [];
$maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
if($this->isSlotEmpty($i)){
$emptySlots[] = $i;
continue;
}
$slotCount = $this->getMatchingItemCount($i, $newItem, true, true);
if($slotCount === 0){
continue;
}
if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){
$amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize());
if($slotCount < $maxStackSize){
$amount = min($maxStackSize - $slotCount, $newItem->getCount());
if($amount > 0){
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() + $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
$newItem->setCount($newItem->getCount() - $amount);
$slotItem = $this->getItem($i);
$slotItem->setCount($slotItem->getCount() + $amount);
$this->setItem($i, $slotItem);
if($newItem->getCount() <= 0){
break;
}
}
@ -236,65 +260,67 @@ abstract class BaseInventory implements Inventory{
if(count($emptySlots) > 0){
foreach($emptySlots as $slotIndex){
$amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize());
$slot->setCount($slot->getCount() - $amount);
$item = clone $slot;
$item->setCount($amount);
$this->setItem($slotIndex, $item);
if($slot->getCount() <= 0){
$amount = min($maxStackSize, $newItem->getCount());
$newItem->setCount($newItem->getCount() - $amount);
$slotItem = clone $newItem;
$slotItem->setCount($amount);
$this->setItem($slotIndex, $slotItem);
if($newItem->getCount() <= 0){
break;
}
}
}
return $slot;
return $newItem;
}
public function remove(Item $item) : void{
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$this->clear($index);
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
$this->clear($i);
}
}
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $itemSlots */
/** @var Item[] $searchItems */
/** @var Item[] $slots */
$itemSlots = [];
$searchItems = [];
foreach($slots as $slot){
if(!$slot->isNull()){
$itemSlots[] = clone $slot;
$searchItems[] = clone $slot;
}
}
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
if($this->isSlotEmpty($i)){
continue;
}
foreach($itemSlots as $index => $slot){
if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){
$amount = min($item->getCount(), $slot->getCount());
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() - $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
unset($itemSlots[$index]);
foreach($searchItems as $index => $search){
$slotCount = $this->getMatchingItemCount($i, $search, !$search->hasAnyDamageValue(), $search->hasNamedTag());
if($slotCount > 0){
$amount = min($slotCount, $search->getCount());
$search->setCount($search->getCount() - $amount);
$slotItem = $this->getItem($i);
$slotItem->setCount($slotItem->getCount() - $amount);
$this->setItem($i, $slotItem);
if($search->getCount() <= 0){
unset($searchItems[$index]);
}
}
}
if(count($itemSlots) === 0){
if(count($searchItems) === 0){
break;
}
}
return $itemSlots;
return $searchItems;
}
public function clear(int $index) : void{

View File

@ -85,6 +85,10 @@ class DelegateInventory extends BaseInventory{
$this->backingInventory->setContents($items);
}
public function isSlotEmpty(int $index) : bool{
return $this->backingInventory->isSlotEmpty($index);
}
protected function onSlotChange(int $index, Item $before) : void{
if($this->backingInventoryChanging){
parent::onSlotChange($index, $before);

View File

@ -83,4 +83,13 @@ class SimpleInventory extends BaseInventory{
}
}
}
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
$slotItem = $this->slots[$slot];
return $slotItem !== null && $slotItem->equals($test, $checkDamage, $checkTags) ? $slotItem->getCount() : 0;
}
public function isSlotEmpty(int $index) : bool{
return $this->slots[$index] === null || $this->slots[$index]->isNull();
}
}

View File

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

View File

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

View File

@ -60,6 +60,7 @@ use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
use pocketmine\network\mcpe\protocol\OpenSignPacket;
use pocketmine\network\mcpe\protocol\Packet;
use pocketmine\network\mcpe\protocol\PacketDecodeException;
use pocketmine\network\mcpe\protocol\PacketPool;
@ -106,7 +107,6 @@ use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use pocketmine\world\Position;
use function array_map;
use function array_values;
@ -886,14 +886,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
)));
}
@ -987,8 +999,6 @@ class NetworkSession{
* @phpstan-param \Closure() : void $onCompletion
*/
public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{
Utils::validateCallableSignature(function() : void{}, $onCompletion);
$world = $this->player->getLocation()->getWorld();
ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve(
@ -1093,6 +1103,10 @@ class NetworkSession{
$this->sendDataPacket(ToastRequestPacket::create($title, $body));
}
public function onOpenSignEditor(Vector3 $signPosition, bool $frontSide) : void{
$this->sendDataPacket(OpenSignPacket::create(BlockPosition::fromVector3($signPosition), $frontSide));
}
public function tick() : void{
if(!$this->isConnected()){
$this->dispose();

View File

@ -138,6 +138,6 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
}
public function onEmote(array $recipients, Human $from, string $emoteId) : void{
$this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
$this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, "", "", EmotePacket::FLAG_SERVER));
}
}

View File

@ -38,7 +38,22 @@ use function time;
class ProcessLoginTask extends AsyncTask{
private const TLS_KEY_ON_COMPLETION = "completion";
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
/**
* Old Mojang root auth key. This was used since the introduction of Xbox Live authentication in 0.15.0.
* This key is expected to be replaced by the key below in the future, but this has not yet happened as of
* 2023-07-01.
* Ideally we would place a time expiry on this key, but since Mojang have not given a hard date for the key change,
* and one bad guess has already caused a major outage, we can't do this.
* TODO: This needs to be removed as soon as the new key is deployed by Mojang's authentication servers.
*/
public const MOJANG_OLD_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
/**
* New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0.
* Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01,
* it has not yet been deployed.
*/
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
private const CLOCK_DRIFT_MAX = 60;
@ -151,7 +166,7 @@ class ProcessLoginTask extends AsyncTask{
throw new VerifyLoginException($e->getMessage(), 0, $e);
}
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY){
$this->authenticated = true; //we're signed into xbox live
}

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
use pocketmine\utils\Utils;
use function array_push;
class CompressBatchPromise{
@ -42,9 +41,6 @@ class CompressBatchPromise{
*/
public function onResolve(\Closure ...$callbacks) : void{
$this->checkCancelled();
foreach($callbacks as $callback){
Utils::validateCallableSignature(function(CompressBatchPromise $promise) : void{}, $callback);
}
if($this->result !== null){
foreach($callbacks as $callback){
$callback($this);

View File

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

View File

@ -27,7 +27,12 @@ use pocketmine\block\Block;
use pocketmine\block\BlockLegacyIds;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\Filesystem;
@ -43,8 +48,8 @@ final class RuntimeBlockMapping{
private array $legacyToRuntimeMap = [];
/** @var int[] */
private array $runtimeToLegacyMap = [];
/** @var CompoundTag[] */
private array $bedrockKnownStates;
/** @var CompoundTag[]|null */
private ?array $bedrockKnownStates = null;
private static function make() : self{
return new self(
@ -53,22 +58,40 @@ final class RuntimeBlockMapping{
);
}
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = $blockState;
/**
* @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;
}
$this->bedrockKnownStates = $list;
$this->setupLegacyMappings($r12ToCurrentBlockMapFile);
$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;
}
private function setupLegacyMappings(string $r12ToCurrentBlockMapFile) : void{
public function __construct(
private string $canonicalBlockStatesFile,
string $r12ToCurrentBlockMapFile
){
//do not cache this - we only need it to set up mappings under normal circumstances
$bedrockKnownStates = $this->loadBedrockKnownStates();
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = [];
@ -88,7 +111,7 @@ final class RuntimeBlockMapping{
* @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
*/
$idToStatesMap = [];
foreach($this->bedrockKnownStates as $k => $state){
foreach($bedrockKnownStates as $k => $state){
$idToStatesMap[$state->getString("name")][] = $k;
}
foreach($legacyStateMap as $pair){
@ -107,7 +130,7 @@ final class RuntimeBlockMapping{
throw new \RuntimeException("Mapped new state does not appear in network table");
}
foreach($idToStatesMap[$mappedName] as $k){
$networkState = $this->bedrockKnownStates[$k];
$networkState = $bedrockKnownStates[$k];
if($mappedState->equals($networkState)){
$this->registerMapping($k, $id, $data);
continue 2;
@ -117,6 +140,25 @@ final class RuntimeBlockMapping{
}
}
/**
* @return CompoundTag[]
*/
private function loadBedrockKnownStates() : array{
$stream = new BinaryStream(Filesystem::fileGetContents($this->canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
return $list;
}
public function toRuntimeId(int $internalStateId) : int{
return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS];
}
@ -131,9 +173,14 @@ final class RuntimeBlockMapping{
}
/**
* WARNING: This method may load the palette from disk, which is a slow operation.
* Afterwards, it will cache the palette in memory, which requires (in some cases) tens of MB of memory.
* Avoid using this where possible.
*
* @deprecated
* @return CompoundTag[]
*/
public function getBedrockKnownStates() : array{
return $this->bedrockKnownStates;
return $this->bedrockKnownStates ??= $this->loadBedrockKnownStates();
}
}

View File

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

View File

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

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\CreativeInventory;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
@ -112,12 +111,7 @@ class ItemStackRequestExecutor{
* @throws ItemStackRequestProcessException
*/
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
$slotId = $info->getSlotId();
if($info->getContainerId() === ContainerUIIds::OFFHAND && $slotId === 1){
//TODO: HACK! The client sends an incorrect slot ID for the offhand as of 1.19.70
$slotId = 0;
}
[$windowId, $slotId] = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId(), $info->getSlotId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $slotId);
if($windowAndSlot === null){
throw new ItemStackRequestProcessException("No open inventory matches container UI ID: " . $info->getContainerId() . ", slot ID: " . $info->getSlotId());
@ -246,13 +240,11 @@ class ItemStackRequestExecutor{
$this->specialTransaction = new CraftingTransaction($this->player, $craftingManager, [], $recipe, $repetitions);
$currentWindow = $this->player->getCurrentWindow();
if($currentWindow !== null && !($currentWindow instanceof CraftingGrid)){
throw new ItemStackRequestProcessException("Player's current window is not a crafting grid");
}
$craftingGrid = $currentWindow ?? $this->player->getCraftingGrid();
$craftingResults = $recipe->getResultsFor($craftingGrid);
//TODO: Since the system assumes that crafting can only be done in the crafting grid, we have to give it a
//crafting grid to make the API happy. No implementation of getResultsFor() actually uses the crafting grid
//right now, so this will work, but this will become a problem in the future for things like shulker boxes and
//custom crafting recipes.
$craftingResults = $recipe->getResultsFor($this->player->getCraftingGrid());
foreach($craftingResults as $k => $craftingResult){
$craftingResult->setCount($craftingResult->getCount() * $repetitions);
$this->craftingResults[$k] = $craftingResult;

View File

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

View File

@ -39,6 +39,7 @@ use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\LevelSettings;
use pocketmine\network\mcpe\protocol\types\NetworkPermissions;
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
@ -105,6 +106,8 @@ class PreSpawnPacketHandler extends PacketHandler{
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
Uuid::fromString(Uuid::NIL),
false,
false,
new NetworkPermissions(disableClientSounds: true),
[],
0,
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\block\BaseSign;
use pocketmine\block\Bed;
use pocketmine\block\BlockLegacyIds;
use pocketmine\block\UnknownBlock;
@ -1190,7 +1191,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* TODO: make this a dynamic ability instead of being hardcoded
*/
public function hasFiniteResources() : bool{
return $this->gamemode->equals(GameMode::SURVIVAL()) || $this->gamemode->equals(GameMode::ADVENTURE());
return !$this->gamemode->equals(GameMode::CREATIVE());
}
public function isFireProof() : bool{
@ -1656,7 +1657,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$ev = new PlayerBlockPickEvent($this, $block, $item);
$existingSlot = $this->inventory->first($item);
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
if($existingSlot === -1 && $this->hasFiniteResources()){
$ev->cancel();
}
$ev->call();
@ -2626,6 +2627,20 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->permanentWindows = [];
}
/**
* Opens the player's sign editor GUI for the sign at the given position.
* TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations)
*/
public function openSignEditor(Vector3 $position) : void{
$block = $this->getWorld()->getBlock($position);
if($block instanceof BaseSign){
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
$this->getNetworkSession()->onOpenSignEditor($position, true);
}else{
throw new \InvalidArgumentException("Block at this position is not a sign");
}
}
use ChunkListenerNoOpTrait {
onChunkChanged as private;
onChunkUnloaded as private;

View File

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

View File

@ -247,7 +247,6 @@ class AsyncPool{
while(!$queue->isEmpty()){
/** @var AsyncTask $task */
$task = $queue->bottom();
$task->checkProgressUpdates();
if($task->isFinished()){ //make sure the task actually executed before trying to collect
$queue->dequeue();
@ -268,6 +267,7 @@ class AsyncPool{
$task->onCompletion();
}
}else{
$task->checkProgressUpdates();
$more = true;
break; //current task is still running, skip to next worker
}

View File

@ -87,14 +87,6 @@ abstract class Timings{
/** @var TimingsHandler */
public static $serverCommand;
/** @var TimingsHandler */
public static $worldLoad;
/** @var TimingsHandler */
public static $worldSave;
/** @var TimingsHandler */
public static $population;
/** @var TimingsHandler */
public static $generationCallback;
/** @var TimingsHandler */
public static $permissibleCalculation;
/** @var TimingsHandler */
public static $permissibleCalculationDiff;
@ -111,10 +103,6 @@ abstract class Timings{
/** @var TimingsHandler */
public static $playerCheckNearEntities;
/** @var TimingsHandler */
public static $tickEntity;
/** @var TimingsHandler */
public static $tickTileEntity;
/** @var TimingsHandler */
public static $entityBaseTick;
@ -166,6 +154,8 @@ abstract class Timings{
/** @var TimingsHandler[] */
private static array $events = [];
/** @var TimingsHandler[][] */
private static array $eventHandlers = [];
public static function init() : void{
if(self::$initialized){
@ -201,10 +191,6 @@ abstract class Timings{
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");
self::$worldSave = new TimingsHandler("World Save");
self::$population = new TimingsHandler("World Population");
self::$generationCallback = new TimingsHandler("World Generation Callback");
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
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);
@ -219,9 +205,6 @@ abstract class Timings{
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
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("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);
@ -270,7 +253,7 @@ abstract class Timings{
}else{
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
}
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN);
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN);
}
return self::$entityTypeTimingMap[$entity::class];
@ -280,7 +263,6 @@ abstract class Timings{
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
);
}
@ -336,4 +318,16 @@ abstract class Timings{
return self::$events[$eventClass];
}
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
*/
public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{
if(!isset(self::$eventHandlers[$event][$handlerName])){
self::$eventHandlers[$event][$handlerName] = new TimingsHandler($handlerName . "(" . self::shortenCoreClassName($event, "pocketmine\\event\\") . ")", group: $group);
}
return self::$eventHandlers[$event][$handlerName];
}
}

View File

@ -23,16 +23,14 @@ declare(strict_types=1);
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 const FORMAT_VERSION = 2; //peak timings fix
private static bool $enabled = false;
private static int $timingStart = 0;
@ -77,19 +75,6 @@ class TimingsHandler{
$result[] = "# Version " . Server::getInstance()->getVersion();
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
$entities = 0;
$livingEntities = 0;
foreach(Server::getInstance()->getWorldManager()->getWorlds() as $world){
$entities += count($world->getEntities());
foreach($world->getEntities() as $e){
if($e instanceof Living){
++$livingEntities;
}
}
}
$result[] = "# Entities " . $entities;
$result[] = "# LivingEntities " . $livingEntities;
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
$sampleTime = hrtime(true) - self::$timingStart;
@ -111,7 +96,7 @@ class TimingsHandler{
}
public static function reload() : void{
TimingsRecord::clearRecords();
TimingsRecord::reset();
if(self::$enabled){
self::$timingStart = hrtime(true);
}
@ -219,8 +204,9 @@ class TimingsHandler{
/**
* @internal
*/
public function destroyCycles() : void{
public function reset() : void{
$this->rootRecord = null;
$this->recordsByParent = [];
$this->timingDepth = 0;
}
}

View File

@ -42,9 +42,12 @@ final class TimingsRecord{
private static ?self $currentRecord = null;
public static function clearRecords() : void{
/**
* @internal
*/
public static function reset() : void{
foreach(self::$records as $record){
$record->handler->destroyCycles();
$record->handler->reset();
}
self::$records = [];
self::$currentRecord = null;
@ -63,9 +66,6 @@ final class TimingsRecord{
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++;
@ -123,7 +123,7 @@ final class TimingsRecord{
public function getTicksActive() : int{ return $this->ticksActive; }
public function getPeakTime() : float{ return $this->peakTime; }
public function getPeakTime() : int{ return $this->peakTime; }
public function startTiming(int $now) : void{
$this->start = $now;
@ -149,6 +149,9 @@ final class TimingsRecord{
++$this->curCount;
++$this->count;
$this->start = 0;
if($diff > $this->peakTime){
$this->peakTime = $diff;
}
}
public static function getCurrentRecord() : ?self{

View File

@ -1,37 +0,0 @@
<?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;
}

View File

@ -77,7 +77,6 @@ use pocketmine\promise\PromiseResolver;
use pocketmine\scheduler\AsyncPool;
use pocketmine\Server;
use pocketmine\ServerConfigGroup;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\ReversePriorityQueue;
@ -104,6 +103,7 @@ use pocketmine\world\utils\SubChunkExplorer;
use function abs;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_sum;
@ -223,10 +223,25 @@ class World implements ChunkManager{
private array $tickingLoaderCounter = [];
/**
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
* @var ChunkTicker[][] chunkHash => [spl_object_id => ChunkTicker]
* @phpstan-var array<ChunkPosHash, array<int, ChunkTicker>>
*/
private array $tickingChunks = [];
private array $registeredTickingChunks = [];
/**
* Set of chunks which are definitely ready for ticking.
*
* @var int[]
* @phpstan-var array<ChunkPosHash, ChunkPosHash>
*/
private array $validTickingChunks = [];
/**
* Set of chunks which might be ready for ticking. These will be checked at the next tick.
* @var int[]
* @phpstan-var array<ChunkPosHash, ChunkPosHash>
*/
private array $recheckTickingChunks = [];
/**
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
@ -983,7 +998,6 @@ class World implements ChunkManager{
$this->timings->entityTick->startTiming();
//Update entities that need update
Timings::$tickEntity->startTiming();
foreach($this->updateEntities as $id => $entity){
if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity->onUpdate($currentTick)){
unset($this->updateEntities[$id]);
@ -992,7 +1006,6 @@ class World implements ChunkManager{
$entity->close();
}
}
Timings::$tickEntity->stopTiming();
$this->timings->entityTick->stopTiming();
$this->timings->randomChunkUpdates->startTiming();
@ -1155,17 +1168,25 @@ class World implements ChunkManager{
$this->chunkTickRadius = $radius;
}
/**
* Returns a list of chunk position hashes (as returned by World::chunkHash()) which are currently valid for
* ticking.
*
* @return int[]
* @phpstan-return list<ChunkPosHash>
*/
public function getTickingChunks() : array{
return array_keys($this->validTickingChunks);
}
/**
* 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->registeredTickingChunks[$chunkPosHash][spl_object_id($ticker)] = $ticker;
$this->recheckTickingChunks[$chunkPosHash] = $chunkPosHash;
}
/**
@ -1175,10 +1196,14 @@ class World implements ChunkManager{
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]);
if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){
unset($this->registeredTickingChunks[$chunkHash][$tickerId]);
if(count($this->registeredTickingChunks[$chunkHash]) === 0){
unset(
$this->registeredTickingChunks[$chunkHash],
$this->recheckTickingChunks[$chunkHash],
$this->validTickingChunks[$chunkHash]
);
}
}
}
@ -1222,37 +1247,37 @@ class World implements ChunkManager{
}
private function tickChunks() : void{
if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){
if($this->chunkTickRadius <= 0 || (count($this->registeredTickingChunks) === 0 && count($this->tickingLoaders) === 0)){
return;
}
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
if(count($this->recheckTickingChunks) > 0 || count($this->tickingLoaders) > 0){
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
/** @var bool[] $chunkTickList chunkhash => dummy */
$chunkTickList = [];
$chunkTickableCache = [];
$chunkTickableCache = [];
foreach($this->tickingChunks as $hash => $entry){
if(!$entry->ready){
foreach($this->recheckTickingChunks as $hash => $_){
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;
$this->validTickingChunks[$hash] = $hash;
}
}
$chunkTickList[$hash] = true;
}
$this->recheckTickingChunks = [];
//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);
}
//TODO: REMOVE THIS - we need a local var to add extra chunks to if we have legacy ticking loaders
//this is copy-on-write, so it won't have any performance impact if there are no legacy ticking loaders
$chunkTickList = $this->validTickingChunks;
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
//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();
}else{
$chunkTickList = $this->validTickingChunks;
}
foreach($chunkTickList as $index => $_){
World::getXZ($index, $chunkX, $chunkZ);
@ -1303,16 +1328,23 @@ class World implements ChunkManager{
}
/**
* 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.
* Marks the 3x3 square of chunks centered on the specified chunk for chunk ticking eligibility recheck.
*
* This should be used whenever the chunk's eligibility to be ticked is changed. This includes:
* - Loading/unloading the chunk (the chunk may be registered for ticking before it is loaded)
* - Locking/unlocking the chunk (e.g. world population)
* - Light populated state change (i.e. scheduled for light population, or light population completed)
* - Arbitrary chunk replacement (i.e. setChunk() or similar)
*/
private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{
private function markTickingChunkForRecheck(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;
unset($this->validTickingChunks[$chunkHash]);
if(isset($this->registeredTickingChunks[$chunkHash])){
$this->recheckTickingChunks[$chunkHash] = $chunkHash;
}else{
unset($this->recheckTickingChunks[$chunkHash]);
}
}
}
@ -1323,7 +1355,7 @@ class World implements ChunkManager{
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
if($lightPopulatedState === false){
$this->chunks[$chunkHash]->setLightPopulated(null);
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
$this->workerPool->submitTask(new LightPopulationTask(
$this->chunks[$chunkHash],
@ -1347,6 +1379,7 @@ class World implements ChunkManager{
$chunk->getSubChunk($y)->setBlockSkyLightArray($lightArray);
}
$chunk->setLightPopulated(true);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
}
));
}
@ -1403,10 +1436,15 @@ class World implements ChunkManager{
(new WorldSaveEvent($this))->call();
$timings = $this->timings->syncDataSave;
$timings->startTiming();
$this->provider->getWorldData()->setTime($this->time);
$this->saveChunks();
$this->provider->getWorldData()->save();
$timings->stopTiming();
return true;
}
@ -2391,7 +2429,7 @@ class World implements ChunkManager{
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
}
$this->chunkLock[$chunkHash] = $lockId;
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
}
/**
@ -2406,6 +2444,7 @@ class World implements ChunkManager{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
if(isset($this->chunkLock[$chunkHash]) && ($lockId === null || $this->chunkLock[$chunkHash] === $lockId)){
unset($this->chunkLock[$chunkHash]);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
return true;
}
return false;
@ -2457,7 +2496,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
$this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
if(!$this->isChunkInUse($chunkX, $chunkZ)){
$this->unloadChunkRequest($chunkX, $chunkZ);
@ -2739,6 +2778,7 @@ class World implements ChunkManager{
foreach($this->getChunkListeners($x, $z) as $listener){
$listener->onChunkLoaded($x, $z, $this->chunks[$chunkHash]);
}
$this->markTickingChunkForRecheck($x, $z); //tickers may have been registered before the chunk was loaded
$this->timings->syncChunkLoad->stopTiming();
@ -2900,8 +2940,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);
unset($this->registeredTickingChunks[$chunkHash]);
$this->markTickingChunkForRecheck($x, $z);
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
$this->logger->debug("Rejecting population promise for chunk $x $z");
@ -3210,7 +3250,8 @@ class World implements ChunkManager{
private function internalOrderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader, ?PromiseResolver $resolver) : Promise{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
Timings::$population->startTiming();
$timings = $this->timings->chunkPopulationOrder;
$timings->startTiming();
try{
for($xx = -1; $xx <= 1; ++$xx){
@ -3267,7 +3308,7 @@ class World implements ChunkManager{
return $resolver->getPromise();
}finally{
Timings::$population->stopTiming();
$timings->stopTiming();
}
}
@ -3276,7 +3317,8 @@ class World implements ChunkManager{
* @phpstan-param array<int, Chunk> $adjacentChunks
*/
private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z, Chunk $chunk, array $adjacentChunks, ChunkLoader $temporaryChunkLoader) : void{
Timings::$generationCallback->startTiming();
$timings = $this->timings->chunkPopulationCompletion;
$timings->startTiming();
$dirtyChunks = 0;
for($xx = -1; $xx <= 1; ++$xx){
@ -3345,7 +3387,7 @@ class World implements ChunkManager{
$this->drainPopulationRequestQueue();
}
Timings::$generationCallback->stopTiming();
$timings->stopTiming();
}
public function doChunkGarbageCollection() : void{

View File

@ -30,7 +30,6 @@ use pocketmine\event\world\WorldUnloadEvent;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\player\ChunkSelector;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\exception\CorruptedWorldException;
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
@ -391,7 +390,6 @@ class WorldManager{
}
private function doAutoSave() : void{
Timings::$worldSave->startTiming();
foreach($this->worlds as $world){
foreach($world->getPlayers() as $player){
if($player->spawned){
@ -400,6 +398,5 @@ class WorldManager{
}
$world->save(false);
}
Timings::$worldSave->stopTiming();
}
}

View File

@ -38,6 +38,7 @@ class WorldTimings{
public TimingsHandler $randomChunkUpdatesChunkSelection;
public TimingsHandler $doChunkGC;
public TimingsHandler $entityTick;
public TimingsHandler $tileTick;
public TimingsHandler $doTick;
public TimingsHandler $syncChunkSend;
@ -48,33 +49,54 @@ class WorldTimings{
public TimingsHandler $syncChunkLoadFixInvalidBlocks;
public TimingsHandler $syncChunkLoadEntities;
public TimingsHandler $syncChunkLoadTileEntities;
public TimingsHandler $syncDataSave;
public TimingsHandler $syncChunkSave;
public TimingsHandler $chunkPopulationOrder;
public TimingsHandler $chunkPopulationCompletion;
/**
* @var TimingsHandler[]
* @phpstan-var array<string, TimingsHandler>
*/
private static array $aggregators = [];
private static function newTimer(string $worldName, string $timerName) : TimingsHandler{
$aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table
return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN);
}
public function __construct(World $world){
$name = $world->getFolderName() . " - ";
$name = $world->getFolderName();
$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->setBlock = self::newTimer($name, "Set Blocks");
$this->doBlockLightUpdates = self::newTimer($name, "Block Light Updates");
$this->doBlockSkyLightUpdates = self::newTimer($name, "Sky Light Updates");
$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);
$this->doChunkUnload = self::newTimer($name, "Unload Chunks");
$this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates");
$this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates");
$this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection");
$this->doChunkGC = self::newTimer($name, "Garbage Collection");
$this->entityTick = self::newTimer($name, "Entity Tick");
$this->tileTick = self::newTimer($name, "Block Entity Tick");
$this->doTick = self::newTimer($name, "World Tick");
Timings::init(); //make sure the timers we want are available
$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->syncChunkSend = self::newTimer($name, "Player Send Chunks");
$this->syncChunkSendPrepare = self::newTimer($name, "Player Send Chunk Prepare");
$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->syncChunkLoad = self::newTimer($name, "Chunk Load");
$this->syncChunkLoadData = self::newTimer($name, "Chunk Load - Data");
$this->syncChunkLoadFixInvalidBlocks = self::newTimer($name, "Chunk Load - Fix Invalid Blocks");
$this->syncChunkLoadEntities = self::newTimer($name, "Chunk Load - Entities");
$this->syncChunkLoadTileEntities = self::newTimer($name, "Chunk Load - Block Entities");
$this->doTick = new TimingsHandler($name . "World Tick");
$this->syncDataSave = self::newTimer($name, "Data Save");
$this->syncChunkSave = self::newTimer($name, "Chunk Save");
$this->chunkPopulationOrder = self::newTimer($name, "Chunk Population - Order");
$this->chunkPopulationCompletion = self::newTimer($name, "Chunk Population - Completion");
}
}

View File

@ -104,7 +104,7 @@ class BlockTest extends TestCase{
* @return int[][]
* @phpstan-return list<array{int,int}>
*/
public function blockGetProvider() : array{
public static function blockGetProvider() : array{
return [
[BlockLegacyIds::STONE, 5],
[BlockLegacyIds::GOLD_BLOCK, 0],

View File

@ -33,7 +33,7 @@ class BrewingStandTest extends TestCase{
/**
* @phpstan-return \Generator<int, array{list<BrewingStandSlot>}, void, void>
*/
public function slotsProvider() : \Generator{
public static function slotsProvider() : \Generator{
yield [array_values(BrewingStandSlot::getAll())];
yield [[BrewingStandSlot::EAST()]];
yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]];

View File

@ -27,7 +27,7 @@ use PHPUnit\Framework\TestCase;
class CommandStringHelperTest extends TestCase{
public function parseQuoteAwareProvider() : \Generator{
public static function parseQuoteAwareProvider() : \Generator{
yield [
'give "steve jobs" apple',
['give', 'steve jobs', 'apple']

View File

@ -49,7 +49,7 @@ class HandlerListManagerTest extends TestCase{
* @return \Generator|mixed[][]
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, bool, string}, void, void>
*/
public function isValidClassProvider() : \Generator{
public static function isValidClassProvider() : \Generator{
yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"];
yield [new \ReflectionClass(TestConcreteEvent::class), true, ""];
yield [new \ReflectionClass(TestAbstractEvent::class), false, "abstract event cannot be handled"];
@ -69,7 +69,7 @@ class HandlerListManagerTest extends TestCase{
* @return \Generator|\ReflectionClass[][]
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, \ReflectionClass<Event>|null}, void, void>
*/
public function resolveParentClassProvider() : \Generator{
public static function resolveParentClassProvider() : \Generator{
yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)];
yield [new \ReflectionClass(TestConcreteEvent::class), null];
yield [new \ReflectionClass(TestConcreteExtendsAbstractEvent::class), null];

View File

@ -31,7 +31,7 @@ class LegacyStringToItemParserTest extends TestCase{
* @return mixed[][]
* @phpstan-return list<array{string,int,int}>
*/
public function itemFromStringProvider() : array{
public static function itemFromStringProvider() : array{
return [
["dye:4", ItemIds::DYE, 4],
["351", ItemIds::DYE, 0],

View File

@ -32,7 +32,7 @@ class ApiVersionTest extends TestCase{
* @return \Generator|mixed[][]
* @phpstan-return \Generator<int, array{string, string, bool}, void, void>
*/
public function compatibleApiProvider() : \Generator{
public static function compatibleApiProvider() : \Generator{
yield ["3.0.0", "3.0.0", true];
yield ["3.1.0", "3.0.0", true];
yield ["3.0.0", "3.1.0", false];
@ -58,7 +58,7 @@ class ApiVersionTest extends TestCase{
* @return mixed[][][]
* @phpstan-return \Generator<int, array{list<string>, list<string>}, void, void>
*/
public function ambiguousVersionsProvider() : \Generator{
public static function ambiguousVersionsProvider() : \Generator{
yield [["3.0.0"], []];
yield [["3.0.0", "3.0.1"], ["3.0.0", "3.0.1"]];
yield [["3.0.0", "3.1.0", "4.0.0"], ["3.0.0", "3.1.0"]];

View File

@ -30,7 +30,7 @@ final class CloningRegistryTraitTest extends TestCase{
/**
* @phpstan-return \Generator<int, array{\Closure() : \stdClass}, void, void>
*/
public function cloningRegistryMembersProvider() : \Generator{
public static function cloningRegistryMembersProvider() : \Generator{
yield [function() : \stdClass{ return TestCloningRegistry::TEST1(); }];
yield [function() : \stdClass{ return TestCloningRegistry::TEST2(); }];
yield [function() : \stdClass{ return TestCloningRegistry::TEST3(); }];

View File

@ -32,7 +32,7 @@ class ConfigTest extends TestCase{
* @return \Generator|mixed[][]
* @phpstan-return \Generator<int, array{string, mixed[]}, void, void>
*/
public function fixYamlIndexesProvider() : \Generator{
public static function fixYamlIndexesProvider() : \Generator{
yield ["x: 1\ny: 2\nz: 3\n", [
"x" => 1,
"y" => 2,

View File

@ -44,7 +44,7 @@ class UtilsTest extends TestCase{
* @return string[][]
* @phpstan-return list<array{string}>
*/
public function parseDocCommentNewlineProvider() : array{
public static function parseDocCommentNewlineProvider() : array{
return [
["\t/**\r\n\t * @param PlayerJoinEvent \$event\r\n\t * @priority HIGHEST\r\n\t * @notHandler\r\n\t */"],
["\t/**\n\t * @param PlayerJoinEvent \$event\n\t * @priority HIGHEST\n\t * @notHandler\n\t */"],
@ -68,7 +68,7 @@ class UtilsTest extends TestCase{
* @return string[][]
* @phpstan-return list<array{string}>
*/
public function parseDocCommentOneLineProvider() : array{
public static function parseDocCommentOneLineProvider() : array{
return [
["/** @ignoreCancelled true dummy */"],
["/**@ignoreCancelled true dummy*/"],
@ -105,7 +105,7 @@ class UtilsTest extends TestCase{
* @return string[][]
* @return list<array{class-string, class-string}>
*/
public function validInstanceProvider() : array{
public static function validInstanceProvider() : array{
return [
//direct instance / implement / extend
[TestInstantiableClass::class, TestInstantiableClass::class],
@ -133,7 +133,7 @@ class UtilsTest extends TestCase{
* @return string[][]
* @return list<array{string, string}>
*/
public function validInstanceInvalidCombinationsProvider() : array{
public static function validInstanceInvalidCombinationsProvider() : array{
return [
["iDontExist abc", TestInstantiableClass::class],
[TestInstantiableClass::class, "iDon'tExist abc"],

View File

@ -74,7 +74,7 @@ class RegionLoaderTest extends TestCase{
* @return \Generator|int[][]
* @phpstan-return \Generator<int, array{int,int}, void, void>
*/
public function outOfBoundsCoordsProvider() : \Generator{
public static function outOfBoundsCoordsProvider() : \Generator{
yield [-1, -1];
yield [32, 32];
yield [-1, 32];

View File

@ -31,7 +31,7 @@ class RegionLocationTableEntryTest extends TestCase{
/**
* @phpstan-return \Generator<int, array{RegionLocationTableEntry, RegionLocationTableEntry, bool}, void, void>
*/
public function overlapDataProvider() : \Generator{
public static function overlapDataProvider() : \Generator{
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(2, 1, 0), true];
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(3, 1, 0), false];
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(3, 2, 0), true];