mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-15 13:55:12 +00:00
Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f5e08067d | |||
a8556dff02 | |||
664089861a | |||
6c0254c1eb | |||
0bb9fb09cc | |||
ab21fcdd67 | |||
b03df4f1e6 | |||
0a2a6e2b3a | |||
0eb751c1c9 | |||
b59b1e491e | |||
95e8c68fde | |||
7e16f9be8f | |||
768650cee0 | |||
c2c529e2da | |||
ba18a81e88 | |||
39218017ca | |||
f4a1d69075 | |||
cbeae906e1 | |||
b25e8e26f0 | |||
a79be994de | |||
e26c8b9e9f | |||
4e9c3e101d | |||
d295e1be54 | |||
2f3fcef97c | |||
c5056e0a43 | |||
a47aa50477 | |||
f7930a3a0b | |||
bb7df60a4d | |||
992cb06da6 | |||
bb3f87f862 | |||
fc77b14760 | |||
52b6f1a492 | |||
0233e74f4f | |||
dd355c58d8 | |||
267032cff9 | |||
6cecd690b2 | |||
91e38d1f97 | |||
653178c1fd | |||
eb06535ed1 | |||
f9bcc8e862 | |||
f43ca405d4 | |||
d146175d27 | |||
3baa5ab712 | |||
c02bead12b | |||
e9ca25c1cb | |||
a513cca582 | |||
9a47c1d401 | |||
433f5451d7 | |||
64e505defb | |||
33d1755eae | |||
fc63c54116 | |||
db07976aab | |||
0e4b79ea77 | |||
588c9b114b | |||
b095f606c1 | |||
b312e93176 | |||
61933624d2 | |||
90beaeaeb4 | |||
466f7e98ed | |||
59be901efe | |||
5d2b0acfc8 | |||
7d1d62042c | |||
17dde140a5 | |||
1e5597f0d5 | |||
97ef209c5f | |||
1305fd5fb2 | |||
7a137f932f | |||
63e8b1cf3a | |||
65bce762ff | |||
529700bb8b | |||
437fa615b8 | |||
0ee6cdb058 | |||
97d6a79b25 | |||
8b5e4c1c16 | |||
214a5ddc15 | |||
9f7dfe3355 | |||
9b55d18393 | |||
31465525e3 | |||
74613b9b09 | |||
1cefe24414 | |||
8bf85d4a18 | |||
b5e6dec0c6 | |||
a3306914cc | |||
3b32ea1b0b | |||
7ec32f981e | |||
b0c87e9d06 | |||
99996b62d6 | |||
1cb6d9f5af | |||
0a9b52618d | |||
7ae6425d05 | |||
b5cfab497d | |||
774e23137e | |||
43bc3c7b25 | |||
eb62dc3294 | |||
279056fe2f |
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@ -6,6 +6,12 @@ updates:
|
|||||||
interval: daily
|
interval: daily
|
||||||
time: "10:00"
|
time: "10:00"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
ignore:
|
||||||
|
#only allow patch updates for locale-data - this has to be updated manually due to codegen
|
||||||
|
- dependency-name: pocketmine/locale-data
|
||||||
|
update-types:
|
||||||
|
- "version-update:semver-major"
|
||||||
|
- "version-update:semver-minor"
|
||||||
|
|
||||||
- package-ecosystem: gitsubmodule
|
- package-ecosystem: gitsubmodule
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
|
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
|
||||||
|
|
||||||
- name: Build image for tag
|
- name: Build image for tag
|
||||||
uses: docker/build-push-action@v3.2.0
|
uses: docker/build-push-action@v3.3.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for major tag
|
- name: Build image for major tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.2.0
|
uses: docker/build-push-action@v3.3.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for minor tag
|
- name: Build image for minor tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.2.0
|
uses: docker/build-push-action@v3.3.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -85,7 +85,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for latest tag
|
- name: Build image for latest tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.2.0
|
uses: docker/build-push-action@v3.3.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
|
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP and tools
|
- name: Setup PHP and tools
|
||||||
uses: shivammathur/setup-php@2.22.0
|
uses: shivammathur/setup-php@2.23.0
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.0
|
||||||
|
|
||||||
|
4
.github/workflows/draft-release.yml
vendored
4
.github/workflows/draft-release.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.22.0
|
uses: shivammathur/setup-php@2.23.0
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.0
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ jobs:
|
|||||||
${{ github.workspace }}/build_info.json
|
${{ github.workspace }}/build_info.json
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: ncipollo/release-action@v1.11.2
|
uses: ncipollo/release-action@v1.12.0
|
||||||
with:
|
with:
|
||||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
|
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
|
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -195,10 +195,12 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP and tools
|
- name: Setup PHP and tools
|
||||||
uses: shivammathur/setup-php@2.22.0
|
uses: shivammathur/setup-php@2.23.0
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.0
|
||||||
tools: php-cs-fixer:3.11
|
tools: php-cs-fixer:3.11
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Run PHP-CS-Fixer
|
- name: Run PHP-CS-Fixer
|
||||||
run: php-cs-fixer fix --dry-run --diff --ansi
|
run: php-cs-fixer fix --dry-run --diff --ansi
|
||||||
|
2
.github/workflows/support.yml
vendored
2
.github/workflows/support.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
support:
|
support:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/support-requests@v2
|
- uses: dessant/support-requests@v3
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
support-label: "Support request"
|
support-label: "Support request"
|
||||||
|
5
.idea/codeStyles/Project.xml
generated
5
.idea/codeStyles/Project.xml
generated
@ -62,6 +62,11 @@
|
|||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Shell Script">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="neon">
|
<codeStyleSettings language="neon">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
@ -66,6 +66,9 @@ BODY,
|
|||||||
],
|
],
|
||||||
'indentation_type' => true,
|
'indentation_type' => true,
|
||||||
'logical_operators' => true,
|
'logical_operators' => true,
|
||||||
|
'native_constant_invocation' => [
|
||||||
|
'scope' => 'namespaced'
|
||||||
|
],
|
||||||
'native_function_invocation' => [
|
'native_function_invocation' => [
|
||||||
'scope' => 'namespaced',
|
'scope' => 'namespaced',
|
||||||
'include' => ['@all'],
|
'include' => ['@all'],
|
||||||
@ -92,6 +95,12 @@ BODY,
|
|||||||
],
|
],
|
||||||
'sort_algorithm' => 'alpha'
|
'sort_algorithm' => 'alpha'
|
||||||
],
|
],
|
||||||
|
'phpdoc_align' => [
|
||||||
|
'align' => 'vertical',
|
||||||
|
'tags' => [
|
||||||
|
'param',
|
||||||
|
]
|
||||||
|
],
|
||||||
'phpdoc_line_span' => [
|
'phpdoc_line_span' => [
|
||||||
'property' => 'single',
|
'property' => 'single',
|
||||||
'method' => null,
|
'method' => null,
|
||||||
|
@ -39,6 +39,7 @@ use function sprintf;
|
|||||||
use function str_replace;
|
use function str_replace;
|
||||||
use function substr;
|
use function substr;
|
||||||
use const SORT_STRING;
|
use const SORT_STRING;
|
||||||
|
use const STDERR;
|
||||||
|
|
||||||
if(count($argv) !== 2){
|
if(count($argv) !== 2){
|
||||||
fwrite(STDERR, "Provide a path to process\n");
|
fwrite(STDERR, "Provide a path to process\n");
|
||||||
|
Submodule build/php updated: 27cc1fcdfa...6b605ed7c4
@ -40,6 +40,7 @@ use function rtrim;
|
|||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function str_replace;
|
use function str_replace;
|
||||||
use function unlink;
|
use function unlink;
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
@ -95,6 +95,10 @@ Released 25th November 2022.
|
|||||||
- Improved performance of `ContainerTrait` dropping items on block destroy. ([link](https://github.com/pmmp/PocketMine-MP/commits/24e72ec109c1442b09558df89b6833cf2f2e0ec7))
|
- Improved performance of `ContainerTrait` dropping items on block destroy. ([link](https://github.com/pmmp/PocketMine-MP/commits/24e72ec109c1442b09558df89b6833cf2f2e0ec7))
|
||||||
- Avoid repeated calls to `Position->getWorld()` (use local variables). ([link](https://github.com/pmmp/PocketMine-MP/commit/2940547026db40ce76deb46e992870de3ead79ad))
|
- Avoid repeated calls to `Position->getWorld()` (use local variables). ([link](https://github.com/pmmp/PocketMine-MP/commit/2940547026db40ce76deb46e992870de3ead79ad))
|
||||||
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. ([link](https://github.com/pmmp/PocketMine-MP/commit/e90abecf38d9c57635fa0497514bba7e546a2469))
|
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. ([link](https://github.com/pmmp/PocketMine-MP/commit/e90abecf38d9c57635fa0497514bba7e546a2469))
|
||||||
|
- Inventories are now mapped on a per-slot basis. This means that more than one inventory can be mapped to the same window ID, which is necessary for correctly handling "UI" inventories like crafting tables.
|
||||||
|
- `InventoryManager->getWindow(int $windowId) : ?Inventory` is replaced by `locateWindowAndSlot` (see below).
|
||||||
|
- Added `InventoryManager->locateWindowAndSlot(int $windowId, int $netSlotId) : array{Inventory, int}` - accepts a window ID and absolute slot ID, and returns the associated inventory and the slot relative to the inventory's own start (for use with `getItem()` etc.).
|
||||||
|
- Slot offset mapping for "UI" inventories is now handled in `InventoryManager->createComplexSlotMapping()` instead of in `TypeConverter`.
|
||||||
- Console polling is now done on the main thread (no longer a performance concern). ([link](https://github.com/pmmp/PocketMine-MP/commit/b3f03d7ae645de67a54b7300c09b94eeca16298e))
|
- Console polling is now done on the main thread (no longer a performance concern). ([link](https://github.com/pmmp/PocketMine-MP/commit/b3f03d7ae645de67a54b7300c09b94eeca16298e))
|
||||||
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie. ([link](https://github.com/pmmp/PocketMine-MP/commit/2585160ca2c4df5758b8b980331307402ff9f0fb))
|
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie. ([link](https://github.com/pmmp/PocketMine-MP/commit/2585160ca2c4df5758b8b980331307402ff9f0fb))
|
||||||
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged. ([link](https://github.com/pmmp/PocketMine-MP/commit/83e5b0adb6fa0dddec377182bb1c7945ac8f7820))
|
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged. ([link](https://github.com/pmmp/PocketMine-MP/commit/83e5b0adb6fa0dddec377182bb1c7945ac8f7820))
|
||||||
|
@ -12,3 +12,95 @@ Released 30th November 2022.
|
|||||||
## General
|
## General
|
||||||
- Added support for Minecraft: Bedrock Edition 1.19.50.
|
- Added support for Minecraft: Bedrock Edition 1.19.50.
|
||||||
- Removed support for older versions.
|
- Removed support for older versions.
|
||||||
|
|
||||||
|
# 4.12.1
|
||||||
|
Released 4th December 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed items glitching when dragging a stack of items across the crafting grid (desync issues).
|
||||||
|
|
||||||
|
# 4.12.2
|
||||||
|
Released 15th December 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Folder used for plugins (optionally specified by `--plugins`) is no longer required to be writable.
|
||||||
|
- Fixed broken writable check for server data folder (`is_writable()` broken on NFS and similar filesystems).
|
||||||
|
- `Filesystem::createLockFile()` exceptions now include more information about why the lock file could not be created.
|
||||||
|
- Fixed client-side item predictions not being rolled back when cancelling events such as `PlayerItemUseEvent`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Updated BedrockProtocol to [17.1.0](https://github.com/pmmp/BedrockProtocol/releases/tag/17.1.0+bedrock-1.19.50). This adds some missing `LevelSoundEvent` constants and fixes the values for `ContainerUIIds`.
|
||||||
|
|
||||||
|
# 4.12.3
|
||||||
|
Released 28th December 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed unauthenticated connections taking up player count slots, preventing players from joining.
|
||||||
|
- Fixed a possible crash in `World->tickChunk()` when plugins unload chunks during some events.
|
||||||
|
- `/gamemode` will now report a failure to change game mode if the player is already in the requested game mode.
|
||||||
|
|
||||||
|
# 4.12.4
|
||||||
|
Released 3rd January 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Added workarounds for an active exploit being used to deny service to servers.
|
||||||
|
|
||||||
|
# 4.12.5
|
||||||
|
Released 6th January 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Removed a workaround for an old client bug in custom form responses. The code contained a denial-of-service vulnerability.
|
||||||
|
|
||||||
|
# 4.12.6
|
||||||
|
Released 7th January 2023.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Added a new security measure to `NetworkSession` to detect and ban players who flood the server with packets.
|
||||||
|
|
||||||
|
# 4.12.7
|
||||||
|
Released 8th January 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed players getting kicked when the server lags for too long.
|
||||||
|
- Fixed players getting kicked when a debugging session is active and a breakpoint is hit.
|
||||||
|
|
||||||
|
# 4.12.8
|
||||||
|
Released 9th January 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed players getting kicked during PvP.
|
||||||
|
- Fixed players randomly getting kicked on Windows (improper rate limit handling wrt. 15ms timer resolution).
|
||||||
|
|
||||||
|
# 4.12.9
|
||||||
|
Released 16th January 2023.
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
### Timings
|
||||||
|
- Added new timers:
|
||||||
|
- `Server Mid-Tick Processing` - time spent processing Snooze interrupts between ticks (e.g. incoming network packets)
|
||||||
|
- `Server Tick Update Cycle` - time spent processing regular per-tick updates (e.g. entity movement, world updates, etc.) (`Server->tick()`)
|
||||||
|
- `Full Server Tick` timer now counts the total of `Server Mid-Tick Processing` and `Server Tick Update Cycle`, which generates more accurate performance metrics.
|
||||||
|
- Previously, this timer only counted the time spent during regular per-tick updates, and the time recorded by `Server Mid-Tick Processing` was not included in the report at all.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed blocks such as pressure plates being able to be placed without the correct supporting blocks if the clicked block was solid.
|
||||||
|
- Pressure plates now self-destruct when the block below them is removed.
|
||||||
|
- Fixed being unable to place blocks by clicking on the side of a bell (when the click doesn't result in ringing the bell).
|
||||||
|
- Fixed various rotation-aware blocks (e.g. stairs) behaving incorrectly when placed by clicking on the side of a replaceable block (e.g. tall grass).
|
||||||
|
- Fixed banners being able to be placed on top of blocks such as skulls.
|
||||||
|
- Fixed server-side collision boxes of walls and glass (which should connect, but didn't). Note that wall connections still don't show client side - this just fixes the collision boxes.
|
||||||
|
- Fixed `PlayerInteractEvent` with `LEFT_CLICK` sometimes firing before `BlockBreakEvent` when breaking blocks.
|
||||||
|
|
||||||
|
## Other changes
|
||||||
|
- Increased packet batch budget for player sessions.
|
||||||
|
|
||||||
|
# 4.12.10
|
||||||
|
Released 18th January 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed reported server load not including the time spent processing Snooze interrupts between ticks (e.g. incoming network packets).
|
||||||
|
- Fixed `Connection Handler` entry in timings report not including time spent receiving packets.
|
||||||
|
|
||||||
|
## Note about server load & performance
|
||||||
|
This version will report higher apparent server load than previous versions. The actual performance of the server is unchanged; the previous reported load was inaccurate.
|
||||||
|
These bugs have been present for nearly 5 years (ever since the first introduction of Snooze in 3.0.0).
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
"fgrosse/phpasn1": "^2.3",
|
"fgrosse/phpasn1": "^2.3",
|
||||||
"netresearch/jsonmapper": "^4.0",
|
"netresearch/jsonmapper": "^4.0",
|
||||||
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
|
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
|
||||||
"pocketmine/bedrock-protocol": "~17.0.0+bedrock-1.19.50",
|
"pocketmine/bedrock-protocol": "~17.1.0+bedrock-1.19.50",
|
||||||
"pocketmine/binaryutils": "^0.2.1",
|
"pocketmine/binaryutils": "^0.2.1",
|
||||||
"pocketmine/callback-validator": "^1.0.2",
|
"pocketmine/callback-validator": "^1.0.2",
|
||||||
"pocketmine/classloader": "^0.2.0",
|
"pocketmine/classloader": "^0.2.0",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"webmozart/path-util": "^2.3"
|
"webmozart/path-util": "^2.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "1.9.2",
|
"phpstan/phpstan": "1.9.12",
|
||||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||||
"phpunit/phpunit": "^9.2"
|
"phpunit/phpunit": "^9.2"
|
||||||
|
198
composer.lock
generated
198
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6818c4f3d55e45758df83a56d60653cf",
|
"content-hash": "1c5a3de092db3cda702d3f39d3da246c",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/json-comment",
|
"name": "adhocore/json-comment",
|
||||||
@ -123,24 +123,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fgrosse/phpasn1",
|
"name": "fgrosse/phpasn1",
|
||||||
"version": "v2.4.0",
|
"version": "v2.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fgrosse/PHPASN1.git",
|
"url": "https://github.com/fgrosse/PHPASN1.git",
|
||||||
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296"
|
"reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296",
|
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
|
||||||
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296",
|
"reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0"
|
"php": "^7.1 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"php-coveralls/php-coveralls": "~2.0",
|
"php-coveralls/php-coveralls": "~2.0",
|
||||||
"phpunit/phpunit": "^6.3 || ^7.0 || ^8.0"
|
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-bcmath": "BCmath is the fallback extension for big integer calculations",
|
"ext-bcmath": "BCmath is the fallback extension for big integer calculations",
|
||||||
@ -192,22 +192,23 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/fgrosse/PHPASN1/issues",
|
"issues": "https://github.com/fgrosse/PHPASN1/issues",
|
||||||
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0"
|
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
|
||||||
},
|
},
|
||||||
"time": "2021-12-11T12:41:06+00:00"
|
"abandoned": true,
|
||||||
|
"time": "2022-12-19T11:08:26+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "netresearch/jsonmapper",
|
"name": "netresearch/jsonmapper",
|
||||||
"version": "v4.0.0",
|
"version": "v4.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/cweiske/jsonmapper.git",
|
"url": "https://github.com/cweiske/jsonmapper.git",
|
||||||
"reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d"
|
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
|
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
|
||||||
"reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
|
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -243,9 +244,9 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"email": "cweiske@cweiske.de",
|
"email": "cweiske@cweiske.de",
|
||||||
"issues": "https://github.com/cweiske/jsonmapper/issues",
|
"issues": "https://github.com/cweiske/jsonmapper/issues",
|
||||||
"source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0"
|
"source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0"
|
||||||
},
|
},
|
||||||
"time": "2020-12-01T19:48:11+00:00"
|
"time": "2022-12-08T20:46:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pocketmine/bedrock-data",
|
"name": "pocketmine/bedrock-data",
|
||||||
@ -275,16 +276,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pocketmine/bedrock-protocol",
|
"name": "pocketmine/bedrock-protocol",
|
||||||
"version": "17.0.0+bedrock-1.19.50",
|
"version": "17.1.0+bedrock-1.19.50",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||||
"reference": "272e10197bb1603c0a81075bf5b9dae0d081a6e2"
|
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/272e10197bb1603c0a81075bf5b9dae0d081a6e2",
|
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c572706cf5e3202718dd35a35dd30fe08cd671de",
|
||||||
"reference": "272e10197bb1603c0a81075bf5b9dae0d081a6e2",
|
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -298,7 +299,7 @@
|
|||||||
"ramsey/uuid": "^4.1"
|
"ramsey/uuid": "^4.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "1.9.0",
|
"phpstan/phpstan": "1.9.3",
|
||||||
"phpstan/phpstan-phpunit": "^1.0.0",
|
"phpstan/phpstan-phpunit": "^1.0.0",
|
||||||
"phpstan/phpstan-strict-rules": "^1.0.0",
|
"phpstan/phpstan-strict-rules": "^1.0.0",
|
||||||
"phpunit/phpunit": "^9.5"
|
"phpunit/phpunit": "^9.5"
|
||||||
@ -316,9 +317,9 @@
|
|||||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/17.0.0+bedrock-1.19.50"
|
"source": "https://github.com/pmmp/BedrockProtocol/tree/17.1.0+bedrock-1.19.50"
|
||||||
},
|
},
|
||||||
"time": "2022-11-30T16:16:27+00:00"
|
"time": "2022-12-15T20:34:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pocketmine/binaryutils",
|
"name": "pocketmine/binaryutils",
|
||||||
@ -851,42 +852,53 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ramsey/collection",
|
"name": "ramsey/collection",
|
||||||
"version": "1.2.2",
|
"version": "1.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ramsey/collection.git",
|
"url": "https://github.com/ramsey/collection.git",
|
||||||
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a"
|
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a",
|
"url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4",
|
||||||
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a",
|
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.3 || ^8",
|
"php": "^7.4 || ^8.0",
|
||||||
"symfony/polyfill-php81": "^1.23"
|
"symfony/polyfill-php81": "^1.23"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"captainhook/captainhook": "^5.3",
|
"captainhook/plugin-composer": "^5.3",
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
"ergebnis/composer-normalize": "^2.28.3",
|
||||||
"ergebnis/composer-normalize": "^2.6",
|
"fakerphp/faker": "^1.21",
|
||||||
"fakerphp/faker": "^1.5",
|
"hamcrest/hamcrest-php": "^2.0",
|
||||||
"hamcrest/hamcrest-php": "^2",
|
"jangregor/phpstan-prophecy": "^1.0",
|
||||||
"jangregor/phpstan-prophecy": "^0.8",
|
"mockery/mockery": "^1.5",
|
||||||
"mockery/mockery": "^1.3",
|
"php-parallel-lint/php-console-highlighter": "^1.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||||
|
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
|
||||||
"phpspec/prophecy-phpunit": "^2.0",
|
"phpspec/prophecy-phpunit": "^2.0",
|
||||||
"phpstan/extension-installer": "^1",
|
"phpstan/extension-installer": "^1.2",
|
||||||
"phpstan/phpstan": "^0.12.32",
|
"phpstan/phpstan": "^1.9",
|
||||||
"phpstan/phpstan-mockery": "^0.12.5",
|
"phpstan/phpstan-mockery": "^1.1",
|
||||||
"phpstan/phpstan-phpunit": "^0.12.11",
|
"phpstan/phpstan-phpunit": "^1.3",
|
||||||
"phpunit/phpunit": "^8.5 || ^9",
|
"phpunit/phpunit": "^9.5",
|
||||||
"psy/psysh": "^0.10.4",
|
"psalm/plugin-mockery": "^1.1",
|
||||||
"slevomat/coding-standard": "^6.3",
|
"psalm/plugin-phpunit": "^0.18.4",
|
||||||
"squizlabs/php_codesniffer": "^3.5",
|
"ramsey/coding-standard": "^2.0.3",
|
||||||
"vimeo/psalm": "^4.4"
|
"ramsey/conventional-commits": "^1.3",
|
||||||
|
"vimeo/psalm": "^5.4"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"captainhook": {
|
||||||
|
"force-install": true
|
||||||
|
},
|
||||||
|
"ramsey/conventional-commits": {
|
||||||
|
"configFile": "conventional-commits.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Ramsey\\Collection\\": "src/"
|
"Ramsey\\Collection\\": "src/"
|
||||||
@ -914,7 +926,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/ramsey/collection/issues",
|
"issues": "https://github.com/ramsey/collection/issues",
|
||||||
"source": "https://github.com/ramsey/collection/tree/1.2.2"
|
"source": "https://github.com/ramsey/collection/tree/1.3.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -926,27 +938,27 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-10-10T03:01:02+00:00"
|
"time": "2022-12-27T19:12:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ramsey/uuid",
|
"name": "ramsey/uuid",
|
||||||
"version": "4.6.0",
|
"version": "4.7.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ramsey/uuid.git",
|
"url": "https://github.com/ramsey/uuid.git",
|
||||||
"reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f"
|
"reference": "433b2014e3979047db08a17a205f410ba3869cf2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f",
|
"url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2",
|
||||||
"reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f",
|
"reference": "433b2014e3979047db08a17a205f410ba3869cf2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"brick/math": "^0.8.8 || ^0.9 || ^0.10",
|
"brick/math": "^0.8.8 || ^0.9 || ^0.10",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"php": "^8.0",
|
"php": "^8.0",
|
||||||
"ramsey/collection": "^1.0"
|
"ramsey/collection": "^1.2 || ^2.0"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"rhumsaa/uuid": "self.version"
|
"rhumsaa/uuid": "self.version"
|
||||||
@ -1006,7 +1018,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/ramsey/uuid/issues",
|
"issues": "https://github.com/ramsey/uuid/issues",
|
||||||
"source": "https://github.com/ramsey/uuid/tree/4.6.0"
|
"source": "https://github.com/ramsey/uuid/tree/4.7.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1018,7 +1030,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-05T23:03:38+00:00"
|
"time": "2023-01-12T18:13:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/filesystem",
|
"name": "symfony/filesystem",
|
||||||
@ -1524,30 +1536,30 @@
|
|||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "doctrine/instantiator",
|
"name": "doctrine/instantiator",
|
||||||
"version": "1.4.1",
|
"version": "1.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/instantiator.git",
|
"url": "https://github.com/doctrine/instantiator.git",
|
||||||
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
|
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
|
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
|
||||||
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
|
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1 || ^8.0"
|
"php": "^7.1 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^9",
|
"doctrine/coding-standard": "^9 || ^11",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"ext-phar": "*",
|
"ext-phar": "*",
|
||||||
"phpbench/phpbench": "^0.16 || ^1",
|
"phpbench/phpbench": "^0.16 || ^1",
|
||||||
"phpstan/phpstan": "^1.4",
|
"phpstan/phpstan": "^1.4",
|
||||||
"phpstan/phpstan-phpunit": "^1",
|
"phpstan/phpstan-phpunit": "^1",
|
||||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||||
"vimeo/psalm": "^4.22"
|
"vimeo/psalm": "^4.30 || ^5.4"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@ -1574,7 +1586,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/instantiator/issues",
|
"issues": "https://github.com/doctrine/instantiator/issues",
|
||||||
"source": "https://github.com/doctrine/instantiator/tree/1.4.1"
|
"source": "https://github.com/doctrine/instantiator/tree/1.5.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1590,7 +1602,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-03-03T08:28:38+00:00"
|
"time": "2022-12-30T00:15:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "myclabs/deep-copy",
|
"name": "myclabs/deep-copy",
|
||||||
@ -1820,16 +1832,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.9.2",
|
"version": "1.9.12",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa"
|
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/44a338ff0d5572c13fd77dfd91addb96e48c29f8",
|
||||||
"reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa",
|
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -1859,7 +1871,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.2"
|
"source": "https://github.com/phpstan/phpstan/tree/1.9.12"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1875,25 +1887,25 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-10T09:56:11+00:00"
|
"time": "2023-01-17T10:44:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-phpunit",
|
"name": "phpstan/phpstan-phpunit",
|
||||||
"version": "1.2.2",
|
"version": "1.3.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
||||||
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0"
|
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/dea1f87344c6964c607d9076dee42d891f3923f0",
|
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/54a24bd23e9e80ee918cdc24f909d376c2e273f7",
|
||||||
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0",
|
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2 || ^8.0",
|
"php": "^7.2 || ^8.0",
|
||||||
"phpstan/phpstan": "^1.8.11"
|
"phpstan/phpstan": "^1.9.3"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpunit/phpunit": "<7.0"
|
"phpunit/phpunit": "<7.0"
|
||||||
@ -1925,27 +1937,27 @@
|
|||||||
"description": "PHPUnit extensions and rules for PHPStan",
|
"description": "PHPUnit extensions and rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.2.2"
|
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.3"
|
||||||
},
|
},
|
||||||
"time": "2022-10-28T10:23:07+00:00"
|
"time": "2022-12-21T15:25:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-strict-rules",
|
"name": "phpstan/phpstan-strict-rules",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||||
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6"
|
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
|
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
|
||||||
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
|
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2 || ^8.0",
|
"php": "^7.2 || ^8.0",
|
||||||
"phpstan/phpstan": "^1.8.6"
|
"phpstan/phpstan": "^1.9.7"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"nikic/php-parser": "^4.13.0",
|
"nikic/php-parser": "^4.13.0",
|
||||||
@ -1973,22 +1985,22 @@
|
|||||||
"description": "Extra strict and opinionated rules for PHPStan",
|
"description": "Extra strict and opinionated rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.4"
|
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.5"
|
||||||
},
|
},
|
||||||
"time": "2022-09-21T11:38:17+00:00"
|
"time": "2023-01-11T14:16:29+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "9.2.19",
|
"version": "9.2.23",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559"
|
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
|
||||||
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559",
|
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -2044,7 +2056,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -2052,7 +2064,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-11-18T07:47:47+00:00"
|
"time": "2022-12-28T12:41:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
@ -2297,20 +2309,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "9.5.26",
|
"version": "9.5.28",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
|
"reference": "954ca3113a03bf780d22f07bf055d883ee04b65e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e",
|
||||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
"reference": "954ca3113a03bf780d22f07bf055d883ee04b65e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"doctrine/instantiator": "^1.3.1",
|
"doctrine/instantiator": "^1.3.1 || ^2",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
@ -2379,7 +2391,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -2395,7 +2407,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-10-28T06:00:21+00:00"
|
"time": "2023-01-14T12:32:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
|
@ -39,11 +39,14 @@ namespace pocketmine {
|
|||||||
use function extension_loaded;
|
use function extension_loaded;
|
||||||
use function function_exists;
|
use function function_exists;
|
||||||
use function getcwd;
|
use function getcwd;
|
||||||
|
use function is_dir;
|
||||||
|
use function mkdir;
|
||||||
use function phpversion;
|
use function phpversion;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function preg_quote;
|
use function preg_quote;
|
||||||
use function realpath;
|
use function realpath;
|
||||||
use function version_compare;
|
use function version_compare;
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
require_once __DIR__ . '/VersionInfo.php';
|
require_once __DIR__ . '/VersionInfo.php';
|
||||||
|
|
||||||
@ -273,25 +276,33 @@ JIT_WARNING
|
|||||||
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
|
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
|
||||||
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
||||||
|
|
||||||
if(!@mkdir($dataPath, 0777, true) && (!is_dir($dataPath) || !is_writable($dataPath))){
|
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
|
||||||
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
|
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
//this has to be done after we're sure the data path exists
|
//this has to be done after we're sure the data path exists
|
||||||
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
|
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
|
||||||
if(!@mkdir($pluginPath, 0777, true) && (!is_dir($pluginPath) || !is_writable($pluginPath))){
|
|
||||||
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
$lockFilePath = Path::join($dataPath, 'server.lock');
|
$lockFilePath = Path::join($dataPath, 'server.lock');
|
||||||
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
|
try{
|
||||||
|
$pid = Filesystem::createLockFile($lockFilePath);
|
||||||
|
}catch(\InvalidArgumentException $e){
|
||||||
|
critical_error($e->getMessage());
|
||||||
|
critical_error("Please ensure that there is enough space on the disk and that the current user has read/write permissions to the selected data directory $dataPath.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if($pid !== null){
|
||||||
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
|
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
|
||||||
critical_error("Please stop the other server first before running a new one.");
|
critical_error("Please stop the other server first before running a new one.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
|
||||||
|
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
//Logger has a dependency on timezone
|
//Logger has a dependency on timezone
|
||||||
Timezone::init();
|
Timezone::init();
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ class Server{
|
|||||||
|
|
||||||
private static ?Server $instance = null;
|
private static ?Server $instance = null;
|
||||||
|
|
||||||
private SleeperHandler $tickSleeper;
|
private TimeTrackingSleeperHandler $tickSleeper;
|
||||||
|
|
||||||
private BanList $banByName;
|
private BanList $banByName;
|
||||||
|
|
||||||
@ -781,7 +781,8 @@ class Server{
|
|||||||
self::$instance = $this;
|
self::$instance = $this;
|
||||||
$this->startTime = microtime(true);
|
$this->startTime = microtime(true);
|
||||||
|
|
||||||
$this->tickSleeper = new SleeperHandler();
|
Timings::init();
|
||||||
|
$this->tickSleeper = new TimeTrackingSleeperHandler(Timings::$serverInterrupts);
|
||||||
|
|
||||||
$this->signalHandler = new SignalHandler(function() : void{
|
$this->signalHandler = new SignalHandler(function() : void{
|
||||||
$this->logger->info("Received signal interrupt, stopping the server");
|
$this->logger->info("Received signal interrupt, stopping the server");
|
||||||
@ -961,7 +962,6 @@ class Server{
|
|||||||
)));
|
)));
|
||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
|
||||||
|
|
||||||
Timings::init();
|
|
||||||
TimingsHandler::setEnabled($this->configGroup->getPropertyBool("settings.enable-profiling", false));
|
TimingsHandler::setEnabled($this->configGroup->getPropertyBool("settings.enable-profiling", false));
|
||||||
$this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", 20);
|
$this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", 20);
|
||||||
|
|
||||||
@ -1865,14 +1865,16 @@ class Server{
|
|||||||
Timings::$serverTick->stopTiming();
|
Timings::$serverTick->stopTiming();
|
||||||
|
|
||||||
$now = microtime(true);
|
$now = microtime(true);
|
||||||
$this->currentTPS = min(20, 1 / max(0.001, $now - $tickTime));
|
$totalTickTimeSeconds = $now - $tickTime + ($this->tickSleeper->getNotificationProcessingTime() / 1_000_000_000);
|
||||||
$this->currentUse = min(1, ($now - $tickTime) / 0.05);
|
$this->currentTPS = min(20, 1 / max(0.001, $totalTickTimeSeconds));
|
||||||
|
$this->currentUse = min(1, $totalTickTimeSeconds / 0.05);
|
||||||
|
|
||||||
TimingsHandler::tick($this->currentTPS <= $this->profilingTickRate);
|
TimingsHandler::tick($this->currentTPS <= $this->profilingTickRate);
|
||||||
|
|
||||||
$idx = $this->tickCounter % 20;
|
$idx = $this->tickCounter % 20;
|
||||||
$this->tickAverage[$idx] = $this->currentTPS;
|
$this->tickAverage[$idx] = $this->currentTPS;
|
||||||
$this->useAverage[$idx] = $this->currentUse;
|
$this->useAverage[$idx] = $this->currentUse;
|
||||||
|
$this->tickSleeper->resetNotificationProcessingTime();
|
||||||
|
|
||||||
if(($this->nextTick - $tickTime) < -1){
|
if(($this->nextTick - $tickTime) < -1){
|
||||||
$this->nextTick = $tickTime;
|
$this->nextTick = $tickTime;
|
||||||
|
64
src/TimeTrackingSleeperHandler.php
Normal file
64
src/TimeTrackingSleeperHandler.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use pocketmine\snooze\SleeperHandler;
|
||||||
|
use pocketmine\timings\TimingsHandler;
|
||||||
|
use function hrtime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Snooze sleeper handler which captures notification processing time.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TimeTrackingSleeperHandler extends SleeperHandler{
|
||||||
|
|
||||||
|
private int $notificationProcessingTimeNs = 0;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private TimingsHandler $timings
|
||||||
|
){
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time in nanoseconds spent processing notifications since the last reset.
|
||||||
|
*/
|
||||||
|
public function getNotificationProcessingTime() : int{ return $this->notificationProcessingTimeNs; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the notification processing time tracker to zero.
|
||||||
|
*/
|
||||||
|
public function resetNotificationProcessingTime() : void{ $this->notificationProcessingTimeNs = 0; }
|
||||||
|
|
||||||
|
public function processNotifications() : void{
|
||||||
|
$startTime = hrtime(true);
|
||||||
|
$this->timings->startTiming();
|
||||||
|
try{
|
||||||
|
parent::processNotifications();
|
||||||
|
}finally{
|
||||||
|
$this->notificationProcessingTimeNs += hrtime(true) - $startTime;
|
||||||
|
$this->timings->stopTiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ use function str_repeat;
|
|||||||
|
|
||||||
final class VersionInfo{
|
final class VersionInfo{
|
||||||
public const NAME = "PocketMine-MP";
|
public const NAME = "PocketMine-MP";
|
||||||
public const BASE_VERSION = "4.12.0";
|
public const BASE_VERSION = "4.12.10";
|
||||||
public const IS_DEVELOPMENT_BUILD = false;
|
public const IS_DEVELOPMENT_BUILD = false;
|
||||||
public const BUILD_CHANNEL = "stable";
|
public const BUILD_CHANNEL = "stable";
|
||||||
|
|
||||||
|
@ -112,7 +112,14 @@ abstract class BaseBanner extends Transparent{
|
|||||||
return SupportType::NONE();
|
return SupportType::NONE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canBeSupportedBy(Block $block) : bool{
|
||||||
|
return $block->isSolid();
|
||||||
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
|
if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportingFace()))){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if($item instanceof ItemBanner){
|
if($item instanceof ItemBanner){
|
||||||
$this->color = $item->getColor();
|
$this->color = $item->getColor();
|
||||||
$this->setPatterns($item->getPatterns());
|
$this->setPatterns($item->getPatterns());
|
||||||
@ -124,7 +131,7 @@ abstract class BaseBanner extends Transparent{
|
|||||||
abstract protected function getSupportingFace() : int;
|
abstract protected function getSupportingFace() : int;
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
if($this->getSide($this->getSupportingFace())->getId() === BlockLegacyIds::AIR){
|
if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,14 +114,13 @@ final class Bell extends Transparent{
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function canBeSupportedBy(Block $block) : bool{
|
private function canBeSupportedBy(Block $block, int $face) : bool{
|
||||||
//TODO: this isn't the actual logic, but it's the closest approximation we can support for now
|
return !$block->getSupportType($face)->equals(SupportType::NONE());
|
||||||
return $block->isSolid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($face === Facing::UP){
|
if($face === Facing::UP){
|
||||||
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()))){
|
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()), Facing::UP)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if($player !== null){
|
if($player !== null){
|
||||||
@ -129,18 +128,18 @@ final class Bell extends Transparent{
|
|||||||
}
|
}
|
||||||
$this->setAttachmentType(BellAttachmentType::FLOOR());
|
$this->setAttachmentType(BellAttachmentType::FLOOR());
|
||||||
}elseif($face === Facing::DOWN){
|
}elseif($face === Facing::DOWN){
|
||||||
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()))){
|
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()), Facing::DOWN)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$this->setAttachmentType(BellAttachmentType::CEILING());
|
$this->setAttachmentType(BellAttachmentType::CEILING());
|
||||||
}else{
|
}else{
|
||||||
$this->setFacing($face);
|
$this->setFacing($face);
|
||||||
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))))){
|
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))), $face)){
|
||||||
$this->setAttachmentType(BellAttachmentType::ONE_WALL());
|
$this->setAttachmentType(BellAttachmentType::ONE_WALL());
|
||||||
}else{
|
}else{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)))){
|
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)), Facing::opposite($face))){
|
||||||
$this->setAttachmentType(BellAttachmentType::TWO_WALLS());
|
$this->setAttachmentType(BellAttachmentType::TWO_WALLS());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,10 +148,10 @@ final class Bell extends Transparent{
|
|||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
if(
|
if(
|
||||||
($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP))) ||
|
($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP), Facing::DOWN)) ||
|
||||||
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))) ||
|
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN), Facing::UP)) ||
|
||||||
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))) ||
|
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)) ||
|
||||||
($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))))
|
($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing), Facing::opposite($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)))
|
||||||
){
|
){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
@ -161,21 +160,20 @@ final class Bell extends Transparent{
|
|||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($player !== null){
|
if($player !== null){
|
||||||
$faceHit = Facing::opposite($player->getHorizontalFacing());
|
$faceHit = Facing::opposite($player->getHorizontalFacing());
|
||||||
if($this->attachmentType->equals(BellAttachmentType::CEILING())){
|
|
||||||
$this->ring($faceHit);
|
|
||||||
}
|
|
||||||
if($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)){
|
|
||||||
$this->ring($faceHit);
|
|
||||||
}
|
|
||||||
if(
|
if(
|
||||||
|
$this->attachmentType->equals(BellAttachmentType::CEILING()) ||
|
||||||
|
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)) ||
|
||||||
|
(
|
||||||
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
|
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
|
||||||
($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
|
($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
|
||||||
|
)
|
||||||
){
|
){
|
||||||
$this->ring($faceHit);
|
$this->ring($faceHit);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ring(int $faceHit) : void{
|
public function ring(int $faceHit) : void{
|
||||||
|
@ -253,6 +253,14 @@ class Block{
|
|||||||
* Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block
|
* Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block
|
||||||
* itself, but may be multiple blocks in some cases (such as doors).
|
* itself, but may be multiple blocks in some cases (such as doors).
|
||||||
*
|
*
|
||||||
|
* @param BlockTransaction $tx Blocks to be set should be added to this transaction (do not modify thr world directly)
|
||||||
|
* @param Item $item Item used to place the block
|
||||||
|
* @param Block $blockReplace Block expected to be replaced
|
||||||
|
* @param Block $blockClicked Block that was clicked using the item
|
||||||
|
* @param int $face Face of the clicked block which was clicked
|
||||||
|
* @param Vector3 $clickVector Exact position inside the clicked block where the click occurred, relative to the block's position
|
||||||
|
* @param Player|null $player Player who placed the block, or null if it was not a player
|
||||||
|
*
|
||||||
* @return bool whether the placement should go ahead
|
* @return bool whether the placement should go ahead
|
||||||
*/
|
*/
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
|
@ -61,7 +61,7 @@ abstract class Button extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($this->canBeSupportedBy($blockClicked, $face)){
|
if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
|
||||||
$this->facing = $face;
|
$this->facing = $face;
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class ItemFrame extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($face === Facing::DOWN || $face === Facing::UP || !$blockClicked->isSolid()){
|
if($face === Facing::DOWN || $face === Facing::UP || !$blockReplace->getSide(Facing::opposite($face))->isSolid()){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ class Ladder extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($this->canBeSupportedBy($blockClicked, $face) && Facing::axis($face) !== Axis::Y){
|
if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face) && Facing::axis($face) !== Axis::Y){
|
||||||
$this->facing = $face;
|
$this->facing = $face;
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ class Lever extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if(!$this->canBeSupportedBy($blockClicked, $face)){
|
if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ abstract class PressurePlate extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($this->canBeSupportedBy($blockClicked)){
|
if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -55,5 +55,11 @@ abstract class PressurePlate extends Transparent{
|
|||||||
return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
|
return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onNearbyBlockChange() : void{
|
||||||
|
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||||
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class Thin extends Transparent{
|
|||||||
|
|
||||||
foreach(Facing::HORIZONTAL as $facing){
|
foreach(Facing::HORIZONTAL as $facing){
|
||||||
$side = $this->getSide($facing);
|
$side = $this->getSide($facing);
|
||||||
if($side instanceof Thin || $side->isFullCube()){
|
if($side instanceof Thin || $side instanceof Wall || $side->isFullCube()){
|
||||||
$this->connections[$facing] = true;
|
$this->connections[$facing] = true;
|
||||||
}else{
|
}else{
|
||||||
unset($this->connections[$facing]);
|
unset($this->connections[$facing]);
|
||||||
|
@ -65,7 +65,6 @@ class Torch extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$below = $this->getSide(Facing::DOWN);
|
|
||||||
$face = Facing::opposite($this->facing);
|
$face = Facing::opposite($this->facing);
|
||||||
|
|
||||||
if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){
|
if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){
|
||||||
@ -74,10 +73,7 @@ class Torch extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($blockClicked->canBeReplaced() && $this->canBeSupportedBy($blockClicked->getSide(Facing::DOWN), Facing::UP)){
|
if($face !== Facing::DOWN && $this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
|
||||||
$this->facing = Facing::UP;
|
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
|
||||||
}elseif($face !== Facing::DOWN && $this->canBeSupportedBy($blockClicked, $face)){
|
|
||||||
$this->facing = $face;
|
$this->facing = $face;
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}else{
|
}else{
|
||||||
|
@ -116,7 +116,7 @@ class Vine extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){
|
if(!$blockReplace->getSide(Facing::opposite($face))->isFullCube() || Facing::axis($face) === Axis::Y){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class Wall extends Transparent{
|
|||||||
|
|
||||||
foreach(Facing::HORIZONTAL as $facing){
|
foreach(Facing::HORIZONTAL as $facing){
|
||||||
$block = $this->getSide($facing);
|
$block = $this->getSide($facing);
|
||||||
if($block instanceof static || $block instanceof FenceGate || ($block->isSolid() && !$block->isTransparent())){
|
if($block instanceof static || $block instanceof FenceGate || $block instanceof Thin || ($block->isSolid() && !$block->isTransparent())){
|
||||||
$this->connections[$facing] = $facing;
|
$this->connections[$facing] = $facing;
|
||||||
}else{
|
}else{
|
||||||
unset($this->connections[$facing]);
|
unset($this->connections[$facing]);
|
||||||
|
@ -113,7 +113,7 @@ final class WallCoralFan extends BaseCoral{
|
|||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
$axis = Facing::axis($face);
|
$axis = Facing::axis($face);
|
||||||
if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockClicked, $face)){
|
if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$this->facing = $face;
|
$this->facing = $face;
|
||||||
|
@ -39,19 +39,23 @@ class WaterLily extends Flowable{
|
|||||||
return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)];
|
return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||||
if($blockClicked instanceof Water){
|
return !$blockReplace instanceof Water && parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
|
||||||
$up = $blockClicked->getSide(Facing::UP);
|
|
||||||
if($up->canBeReplaced()){
|
|
||||||
return parent::place($tx, $item, $up, $blockClicked, $face, $clickVector, $player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canBeSupportedBy(Block $block) : bool{
|
||||||
|
return $block instanceof Water;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
|
if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
if(!($this->getSide(Facing::DOWN) instanceof Water)){
|
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,11 @@ class GamemodeCommand extends VanillaCommand{
|
|||||||
throw new InvalidCommandSyntaxException();
|
throw new InvalidCommandSyntaxException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($target->getGamemode()->equals($gameMode)){
|
||||||
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$target->setGamemode($gameMode);
|
$target->setGamemode($gameMode);
|
||||||
if(!$gameMode->equals($target->getGamemode())){
|
if(!$gameMode->equals($target->getGamemode())){
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
|
||||||
|
@ -34,9 +34,15 @@ use function usort;
|
|||||||
class CraftingManager{
|
class CraftingManager{
|
||||||
use DestructorCallbackTrait;
|
use DestructorCallbackTrait;
|
||||||
|
|
||||||
/** @var ShapedRecipe[][] */
|
/**
|
||||||
|
* @var ShapedRecipe[][]
|
||||||
|
* @phpstan-var array<string, list<ShapedRecipe>>
|
||||||
|
*/
|
||||||
protected $shapedRecipes = [];
|
protected $shapedRecipes = [];
|
||||||
/** @var ShapelessRecipe[][] */
|
/**
|
||||||
|
* @var ShapelessRecipe[][]
|
||||||
|
* @phpstan-var array<string, list<ShapelessRecipe>>
|
||||||
|
*/
|
||||||
protected $shapelessRecipes = [];
|
protected $shapelessRecipes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,6 +139,7 @@ class CraftingManager{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ShapelessRecipe[][]
|
* @return ShapelessRecipe[][]
|
||||||
|
* @phpstan-return array<string, list<ShapelessRecipe>>
|
||||||
*/
|
*/
|
||||||
public function getShapelessRecipes() : array{
|
public function getShapelessRecipes() : array{
|
||||||
return $this->shapelessRecipes;
|
return $this->shapelessRecipes;
|
||||||
@ -140,6 +147,7 @@ class CraftingManager{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ShapedRecipe[][]
|
* @return ShapedRecipe[][]
|
||||||
|
* @phpstan-return array<string, list<ShapedRecipe>>
|
||||||
*/
|
*/
|
||||||
public function getShapedRecipes() : array{
|
public function getShapedRecipes() : array{
|
||||||
return $this->shapedRecipes;
|
return $this->shapedRecipes;
|
||||||
|
@ -37,6 +37,7 @@ class ShapelessRecipe implements CraftingRecipe{
|
|||||||
/**
|
/**
|
||||||
* @param Item[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
|
* @param Item[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
|
||||||
* @param Item[] $results List of result items created by this recipe.
|
* @param Item[] $results List of result items created by this recipe.
|
||||||
|
*
|
||||||
* TODO: we'll want to make the type parameter mandatory in PM5
|
* TODO: we'll want to make the type parameter mandatory in PM5
|
||||||
*/
|
*/
|
||||||
public function __construct(array $ingredients, array $results, ?ShapelessRecipeType $type = null){
|
public function __construct(array $ingredients, array $results, ?ShapelessRecipeType $type = null){
|
||||||
|
@ -33,13 +33,13 @@ final class PotionTypeIdMap{
|
|||||||
* @var PotionType[]
|
* @var PotionType[]
|
||||||
* @phpstan-var array<int, PotionType>
|
* @phpstan-var array<int, PotionType>
|
||||||
*/
|
*/
|
||||||
private array $idToEnum;
|
private array $idToEnum = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int[]
|
* @var int[]
|
||||||
* @phpstan-var array<int, int>
|
* @phpstan-var array<int, int>
|
||||||
*/
|
*/
|
||||||
private array $enumToId;
|
private array $enumToId = [];
|
||||||
|
|
||||||
private function __construct(){
|
private function __construct(){
|
||||||
$this->register(PotionTypeIds::WATER, PotionType::WATER());
|
$this->register(PotionTypeIds::WATER, PotionType::WATER());
|
||||||
|
@ -31,6 +31,10 @@ class HandlerList{
|
|||||||
/** @var RegisteredListener[][] */
|
/** @var RegisteredListener[][] */
|
||||||
private array $handlerSlots = [];
|
private array $handlerSlots = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-template TEvent of Event
|
||||||
|
* @phpstan-param class-string<TEvent> $class
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $class,
|
private string $class,
|
||||||
private ?HandlerList $parentList
|
private ?HandlerList $parentList
|
||||||
|
@ -80,6 +80,10 @@ class Network{
|
|||||||
return $this->sessionManager->getSessionCount();
|
return $this->sessionManager->getSessionCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getValidConnectionCount() : int{
|
||||||
|
return $this->sessionManager->getValidSessionCount();
|
||||||
|
}
|
||||||
|
|
||||||
public function tick() : void{
|
public function tick() : void{
|
||||||
foreach($this->interfaces as $interface){
|
foreach($this->interfaces as $interface){
|
||||||
$interface->tick();
|
$interface->tick();
|
||||||
|
@ -32,12 +32,25 @@ class NetworkSessionManager{
|
|||||||
/** @var NetworkSession[] */
|
/** @var NetworkSession[] */
|
||||||
private array $sessions = [];
|
private array $sessions = [];
|
||||||
|
|
||||||
|
/** @var NetworkSession[] */
|
||||||
|
private array $pendingLoginSessions = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a network session to the manager. This should only be called on session creation.
|
* Adds a network session to the manager. This should only be called on session creation.
|
||||||
*/
|
*/
|
||||||
public function add(NetworkSession $session) : void{
|
public function add(NetworkSession $session) : void{
|
||||||
$idx = spl_object_id($session);
|
$idx = spl_object_id($session);
|
||||||
$this->sessions[$idx] = $session;
|
$this->sessions[$idx] = $session;
|
||||||
|
$this->pendingLoginSessions[$idx] = $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the session as having sent a login request. After this point, they are counted towards the total player
|
||||||
|
* count.
|
||||||
|
*/
|
||||||
|
public function markLoginReceived(NetworkSession $session) : void{
|
||||||
|
$idx = spl_object_id($session);
|
||||||
|
unset($this->pendingLoginSessions[$idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,15 +60,24 @@ class NetworkSessionManager{
|
|||||||
public function remove(NetworkSession $session) : void{
|
public function remove(NetworkSession $session) : void{
|
||||||
$idx = spl_object_id($session);
|
$idx = spl_object_id($session);
|
||||||
unset($this->sessions[$idx]);
|
unset($this->sessions[$idx]);
|
||||||
|
unset($this->pendingLoginSessions[$idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of known connected sessions.
|
* Returns the number of known connected sessions, including sessions which have not yet sent a login request.
|
||||||
*/
|
*/
|
||||||
public function getSessionCount() : int{
|
public function getSessionCount() : int{
|
||||||
return count($this->sessions);
|
return count($this->sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of connected sessions which have either sent a login request, or have already completed the
|
||||||
|
* login sequence.
|
||||||
|
*/
|
||||||
|
public function getValidSessionCount() : int{
|
||||||
|
return count($this->sessions) - count($this->pendingLoginSessions);
|
||||||
|
}
|
||||||
|
|
||||||
/** @return NetworkSession[] */
|
/** @return NetworkSession[] */
|
||||||
public function getSessions() : array{ return $this->sessions; }
|
public function getSessions() : array{ return $this->sessions; }
|
||||||
|
|
||||||
|
@ -191,8 +191,10 @@ class InventoryManager{
|
|||||||
*/
|
*/
|
||||||
public function addPredictedSlotChanges(array $networkInventoryActions) : void{
|
public function addPredictedSlotChanges(array $networkInventoryActions) : void{
|
||||||
foreach($networkInventoryActions as $action){
|
foreach($networkInventoryActions as $action){
|
||||||
if($action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && isset($this->windowMap[$action->windowId])){
|
if($action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && (
|
||||||
//this won't cover stuff like crafting grid due to too much magic
|
isset($this->windowMap[$action->windowId]) ||
|
||||||
|
($action->windowId === ContainerIds::UI && isset($this->complexSlotToWindowMap[$action->inventorySlot]))
|
||||||
|
)){
|
||||||
try{
|
try{
|
||||||
$item = TypeConverter::getInstance()->netItemStackToCore($action->newItem->getItemStack());
|
$item = TypeConverter::getInstance()->netItemStackToCore($action->newItem->getItemStack());
|
||||||
}catch(TypeConversionException $e){
|
}catch(TypeConversionException $e){
|
||||||
|
@ -128,20 +128,39 @@ use function array_values;
|
|||||||
use function base64_encode;
|
use function base64_encode;
|
||||||
use function bin2hex;
|
use function bin2hex;
|
||||||
use function count;
|
use function count;
|
||||||
|
use function function_exists;
|
||||||
use function get_class;
|
use function get_class;
|
||||||
|
use function hrtime;
|
||||||
use function in_array;
|
use function in_array;
|
||||||
|
use function intdiv;
|
||||||
use function json_encode;
|
use function json_encode;
|
||||||
use function ksort;
|
use function ksort;
|
||||||
|
use function min;
|
||||||
use function strcasecmp;
|
use function strcasecmp;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
use function strtolower;
|
use function strtolower;
|
||||||
use function substr;
|
use function substr;
|
||||||
use function time;
|
use function time;
|
||||||
use function ucfirst;
|
use function ucfirst;
|
||||||
|
use function xdebug_is_debugger_active;
|
||||||
use const JSON_THROW_ON_ERROR;
|
use const JSON_THROW_ON_ERROR;
|
||||||
use const SORT_NUMERIC;
|
use const SORT_NUMERIC;
|
||||||
|
|
||||||
class NetworkSession{
|
class NetworkSession{
|
||||||
|
private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately
|
||||||
|
private const INCOMING_PACKET_BATCH_MAX_BUDGET = 100 * self::INCOMING_PACKET_BATCH_PER_TICK; //enough to account for a 5-second lag spike
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At most this many more packets can be received. If this reaches zero, any additional packets received will cause
|
||||||
|
* the player to be kicked from the server.
|
||||||
|
* This number is increased every tick up to a maximum limit.
|
||||||
|
*
|
||||||
|
* @see self::INCOMING_PACKET_BATCH_PER_TICK
|
||||||
|
* @see self::INCOMING_PACKET_BATCH_MAX_BUDGET
|
||||||
|
*/
|
||||||
|
private int $incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
|
||||||
|
private int $lastPacketBudgetUpdateTimeNs;
|
||||||
|
|
||||||
private \PrefixedLogger $logger;
|
private \PrefixedLogger $logger;
|
||||||
private ?Player $player = null;
|
private ?Player $player = null;
|
||||||
private ?PlayerInfo $info = null;
|
private ?PlayerInfo $info = null;
|
||||||
@ -199,6 +218,7 @@ class NetworkSession{
|
|||||||
$this->disposeHooks = new ObjectSet();
|
$this->disposeHooks = new ObjectSet();
|
||||||
|
|
||||||
$this->connectTime = time();
|
$this->connectTime = time();
|
||||||
|
$this->lastPacketBudgetUpdateTimeNs = hrtime(true);
|
||||||
|
|
||||||
$this->setHandler(new SessionStartPacketHandler(
|
$this->setHandler(new SessionStartPacketHandler(
|
||||||
$this->server,
|
$this->server,
|
||||||
@ -229,6 +249,7 @@ class NetworkSession{
|
|||||||
$this->info = $info;
|
$this->info = $info;
|
||||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||||
$this->logger->setPrefix($this->getLogPrefix());
|
$this->logger->setPrefix($this->getLogPrefix());
|
||||||
|
$this->manager->markLoginReceived($this);
|
||||||
},
|
},
|
||||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||||
@ -338,6 +359,17 @@ class NetworkSession{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->incomingPacketBatchBudget <= 0){
|
||||||
|
if(!function_exists('xdebug_is_debugger_active') || !xdebug_is_debugger_active()){
|
||||||
|
throw new PacketHandlingException("Receiving packets too fast");
|
||||||
|
}else{
|
||||||
|
//when a debugging session is active, the server may halt at any point for an indefinite length of time,
|
||||||
|
//in which time the client will continue to send packets
|
||||||
|
$this->incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->incomingPacketBatchBudget--;
|
||||||
|
|
||||||
if($this->cipher !== null){
|
if($this->cipher !== null){
|
||||||
Timings::$playerNetworkReceiveDecrypt->startTiming();
|
Timings::$playerNetworkReceiveDecrypt->startTiming();
|
||||||
try{
|
try{
|
||||||
@ -365,7 +397,7 @@ class NetworkSession{
|
|||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){
|
||||||
if($packet === null){
|
if($packet === null){
|
||||||
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
|
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
|
||||||
throw new PacketHandlingException("Unknown packet received");
|
throw new PacketHandlingException("Unknown packet received");
|
||||||
@ -1128,5 +1160,16 @@ class NetworkSession{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->flushSendBuffer();
|
$this->flushSendBuffer();
|
||||||
|
|
||||||
|
$nowNs = hrtime(true);
|
||||||
|
$timeSinceLastUpdateNs = $nowNs - $this->lastPacketBudgetUpdateTimeNs;
|
||||||
|
if($timeSinceLastUpdateNs > 50_000_000){
|
||||||
|
$ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, 50_000_000);
|
||||||
|
$this->incomingPacketBatchBudget = min(
|
||||||
|
$this->incomingPacketBatchBudget + (self::INCOMING_PACKET_BATCH_PER_TICK * 2 * $ticksSinceLastUpdate),
|
||||||
|
self::INCOMING_PACKET_BATCH_MAX_BUDGET
|
||||||
|
);
|
||||||
|
$this->lastPacketBudgetUpdateTimeNs = $nowNs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,7 @@ class TypeConverter{
|
|||||||
$nbt = new CompoundTag();
|
$nbt = new CompoundTag();
|
||||||
}
|
}
|
||||||
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
|
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
|
||||||
|
$meta = 0;
|
||||||
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
|
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
|
||||||
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
|
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
|
||||||
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
|
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
|
||||||
@ -183,6 +184,7 @@ class TypeConverter{
|
|||||||
$nbt = new CompoundTag();
|
$nbt = new CompoundTag();
|
||||||
}
|
}
|
||||||
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
|
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
|
||||||
|
$meta = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ use function openssl_digest;
|
|||||||
use function openssl_error_string;
|
use function openssl_error_string;
|
||||||
use function openssl_pkey_derive;
|
use function openssl_pkey_derive;
|
||||||
use function str_pad;
|
use function str_pad;
|
||||||
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
final class EncryptionUtils{
|
final class EncryptionUtils{
|
||||||
|
|
||||||
|
@ -112,7 +112,6 @@ use function array_push;
|
|||||||
use function base64_encode;
|
use function base64_encode;
|
||||||
use function count;
|
use function count;
|
||||||
use function fmod;
|
use function fmod;
|
||||||
use function implode;
|
|
||||||
use function in_array;
|
use function in_array;
|
||||||
use function is_bool;
|
use function is_bool;
|
||||||
use function is_infinite;
|
use function is_infinite;
|
||||||
@ -122,12 +121,9 @@ use function json_encode;
|
|||||||
use function max;
|
use function max;
|
||||||
use function mb_strlen;
|
use function mb_strlen;
|
||||||
use function microtime;
|
use function microtime;
|
||||||
use function preg_match;
|
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
use function strpos;
|
use function strpos;
|
||||||
use function substr;
|
|
||||||
use function trim;
|
|
||||||
use const JSON_THROW_ON_ERROR;
|
use const JSON_THROW_ON_ERROR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -252,18 +248,11 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
|
|
||||||
$packetHandled = true;
|
$packetHandled = true;
|
||||||
|
|
||||||
$useItemTransaction = $packet->getItemInteractionData();
|
|
||||||
if($useItemTransaction !== null){
|
|
||||||
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
|
|
||||||
$packetHandled = false;
|
|
||||||
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
|
|
||||||
}else{
|
|
||||||
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$blockActions = $packet->getBlockActions();
|
$blockActions = $packet->getBlockActions();
|
||||||
if($blockActions !== null){
|
if($blockActions !== null){
|
||||||
|
if(count($blockActions) > 100){
|
||||||
|
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
|
||||||
|
}
|
||||||
foreach($blockActions as $k => $blockAction){
|
foreach($blockActions as $k => $blockAction){
|
||||||
$actionHandled = false;
|
$actionHandled = false;
|
||||||
if($blockAction instanceof PlayerBlockActionStopBreak){
|
if($blockAction instanceof PlayerBlockActionStopBreak){
|
||||||
@ -279,6 +268,20 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$useItemTransaction = $packet->getItemInteractionData();
|
||||||
|
if($useItemTransaction !== null){
|
||||||
|
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
|
||||||
|
throw new PacketHandlingException("Too many actions in item use transaction");
|
||||||
|
}
|
||||||
|
$this->inventoryManager->addPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
|
||||||
|
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
|
||||||
|
$packetHandled = false;
|
||||||
|
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
|
||||||
|
}else{
|
||||||
|
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $packetHandled;
|
return $packetHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +313,12 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
|
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
|
||||||
$result = true;
|
$result = true;
|
||||||
|
|
||||||
|
if(count($packet->trData->getActions()) > 100){
|
||||||
|
throw new PacketHandlingException("Too many actions in inventory transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions());
|
||||||
|
|
||||||
if($packet->trData instanceof NormalTransactionData){
|
if($packet->trData instanceof NormalTransactionData){
|
||||||
$result = $this->handleNormalTransaction($packet->trData);
|
$result = $this->handleNormalTransaction($packet->trData);
|
||||||
}elseif($packet->trData instanceof MismatchTransactionData){
|
}elseif($packet->trData instanceof MismatchTransactionData){
|
||||||
@ -324,9 +333,7 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
$result = $this->handleReleaseItemTransaction($packet->trData);
|
$result = $this->handleReleaseItemTransaction($packet->trData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$result){
|
if($this->craftingTransaction === null){ //don't sync if we're waiting to complete a crafting transaction
|
||||||
$this->inventoryManager->syncAll();
|
|
||||||
}else{
|
|
||||||
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
@ -381,15 +388,9 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
}
|
}
|
||||||
$this->player->setUsingItem(false);
|
$this->player->setUsingItem(false);
|
||||||
try{
|
try{
|
||||||
$this->inventoryManager->onTransactionStart($this->craftingTransaction);
|
|
||||||
$this->craftingTransaction->execute();
|
$this->craftingTransaction->execute();
|
||||||
}catch(TransactionException $e){
|
}catch(TransactionException $e){
|
||||||
$this->session->getLogger()->debug("Failed to execute crafting transaction: " . $e->getMessage());
|
$this->session->getLogger()->debug("Failed to execute crafting transaction: " . $e->getMessage());
|
||||||
|
|
||||||
//TODO: only sync slots that the client tried to change
|
|
||||||
foreach($this->craftingTransaction->getInventories() as $inventory){
|
|
||||||
$this->inventoryManager->syncContents($inventory);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}finally{
|
}finally{
|
||||||
$this->craftingTransaction = null;
|
$this->craftingTransaction = null;
|
||||||
@ -409,18 +410,12 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
|
|
||||||
$this->player->setUsingItem(false);
|
$this->player->setUsingItem(false);
|
||||||
$transaction = new InventoryTransaction($this->player, $actions);
|
$transaction = new InventoryTransaction($this->player, $actions);
|
||||||
$this->inventoryManager->onTransactionStart($transaction);
|
|
||||||
try{
|
try{
|
||||||
$transaction->execute();
|
$transaction->execute();
|
||||||
}catch(TransactionException $e){
|
}catch(TransactionException $e){
|
||||||
$logger = $this->session->getLogger();
|
$logger = $this->session->getLogger();
|
||||||
$logger->debug("Failed to execute inventory transaction: " . $e->getMessage());
|
$logger->debug("Failed to execute inventory transaction: " . $e->getMessage());
|
||||||
$logger->debug("Actions: " . json_encode($data->getActions()));
|
$logger->debug("Actions: " . json_encode($data->getActions()));
|
||||||
|
|
||||||
foreach($transaction->getInventories() as $inventory){
|
|
||||||
$this->inventoryManager->syncContents($inventory);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,7 +425,6 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
|
|
||||||
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
|
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
|
||||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||||
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
|
|
||||||
|
|
||||||
switch($data->getActionType()){
|
switch($data->getActionType()){
|
||||||
case UseItemTransactionData::ACTION_CLICK_BLOCK:
|
case UseItemTransactionData::ACTION_CLICK_BLOCK:
|
||||||
@ -516,9 +510,7 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||||
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
|
|
||||||
|
|
||||||
//TODO: use transactiondata for rollbacks here
|
|
||||||
switch($data->getActionType()){
|
switch($data->getActionType()){
|
||||||
case UseItemOnEntityTransactionData::ACTION_INTERACT:
|
case UseItemOnEntityTransactionData::ACTION_INTERACT:
|
||||||
$this->player->interactEntity($target, $data->getClickPosition());
|
$this->player->interactEntity($target, $data->getClickPosition());
|
||||||
@ -533,14 +525,9 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
|
|
||||||
private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{
|
private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{
|
||||||
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
$this->player->selectHotbarSlot($data->getHotbarSlot());
|
||||||
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
|
|
||||||
|
|
||||||
//TODO: use transactiondata for rollbacks here (resending entire inventory is very wasteful)
|
if($data->getActionType() == ReleaseItemTransactionData::ACTION_RELEASE){
|
||||||
switch($data->getActionType()){
|
$this->player->releaseHeldItem();
|
||||||
case ReleaseItemTransactionData::ACTION_RELEASE:
|
|
||||||
if(!$this->player->releaseHeldItem()){
|
|
||||||
$this->inventoryManager->syncContents($this->player->getInventory());
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -884,60 +871,17 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
//TODO: make APIs for this to allow plugins to use this information
|
//TODO: make APIs for this to allow plugins to use this information
|
||||||
return $this->player->onFormSubmit($packet->formId, null);
|
return $this->player->onFormSubmit($packet->formId, null);
|
||||||
}elseif($packet->formData !== null){
|
}elseif($packet->formData !== null){
|
||||||
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
|
try{
|
||||||
|
$responseData = json_decode($packet->formData, true, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
|
||||||
|
}catch(\JsonException $e){
|
||||||
|
throw PacketHandlingException::wrap($e, "Failed to decode form response data");
|
||||||
|
}
|
||||||
|
return $this->player->onFormSubmit($packet->formId, $responseData);
|
||||||
}else{
|
}else{
|
||||||
throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket");
|
throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
* @throws PacketHandlingException
|
|
||||||
*/
|
|
||||||
private static function stupid_json_decode(string $json, bool $assoc = false){
|
|
||||||
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
|
|
||||||
$raw = $matches[1];
|
|
||||||
$lastComma = -1;
|
|
||||||
$newParts = [];
|
|
||||||
$inQuotes = false;
|
|
||||||
for($i = 0, $len = strlen($raw); $i <= $len; ++$i){
|
|
||||||
if($i === $len || ($raw[$i] === "," && !$inQuotes)){
|
|
||||||
$part = substr($raw, $lastComma + 1, $i - ($lastComma + 1));
|
|
||||||
if(trim($part) === ""){ //regular parts will have quotes or something else that makes them non-empty
|
|
||||||
$part = '""';
|
|
||||||
}
|
|
||||||
$newParts[] = $part;
|
|
||||||
$lastComma = $i;
|
|
||||||
}elseif($raw[$i] === '"'){
|
|
||||||
if(!$inQuotes){
|
|
||||||
$inQuotes = true;
|
|
||||||
}else{
|
|
||||||
$backslashes = 0;
|
|
||||||
for(; $backslashes < $i && $raw[$i - $backslashes - 1] === "\\"; ++$backslashes){}
|
|
||||||
if(($backslashes % 2) === 0){ //unescaped quote
|
|
||||||
$inQuotes = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$fixed = "[" . implode(",", $newParts) . "]";
|
|
||||||
try{
|
|
||||||
return json_decode($fixed, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
|
|
||||||
}catch(\JsonException $e){
|
|
||||||
throw PacketHandlingException::wrap($e, "Failed to fix JSON (original: $json, modified: $fixed)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
return json_decode($json, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
|
|
||||||
}catch(\JsonException $e){
|
|
||||||
throw PacketHandlingException::wrap($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
|
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
|
||||||
return false; //TODO: GUI stuff
|
return false; //TODO: GUI stuff
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ class LoginPacketHandler extends PacketHandler{
|
|||||||
$this->session->getPort(),
|
$this->session->getPort(),
|
||||||
$this->server->requiresAuthentication()
|
$this->server->requiresAuthentication()
|
||||||
);
|
);
|
||||||
if($this->server->getNetwork()->getConnectionCount() > $this->server->getMaxPlayers()){
|
if($this->server->getNetwork()->getValidConnectionCount() > $this->server->getMaxPlayers()){
|
||||||
$ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL);
|
$ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL);
|
||||||
}
|
}
|
||||||
if(!$this->server->isWhitelisted($playerInfo->getUsername())){
|
if(!$this->server->isWhitelisted($playerInfo->getUsername())){
|
||||||
|
@ -36,6 +36,8 @@ use pocketmine\network\NetworkInterfaceStartException;
|
|||||||
use pocketmine\network\PacketHandlingException;
|
use pocketmine\network\PacketHandlingException;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\snooze\SleeperNotifier;
|
use pocketmine\snooze\SleeperNotifier;
|
||||||
|
use pocketmine\timings\Timings;
|
||||||
|
use pocketmine\timings\TimingsHandler;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use raklib\generic\SocketException;
|
use raklib\generic\SocketException;
|
||||||
use raklib\protocol\EncapsulatedPacket;
|
use raklib\protocol\EncapsulatedPacket;
|
||||||
@ -52,6 +54,7 @@ use function mt_rand;
|
|||||||
use function random_bytes;
|
use function random_bytes;
|
||||||
use function rtrim;
|
use function rtrim;
|
||||||
use function substr;
|
use function substr;
|
||||||
|
use const PHP_INT_MAX;
|
||||||
|
|
||||||
class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||||
/**
|
/**
|
||||||
@ -109,7 +112,12 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
|||||||
|
|
||||||
public function start() : void{
|
public function start() : void{
|
||||||
$this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
|
$this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{
|
||||||
|
Timings::$connection->startTiming();
|
||||||
|
try{
|
||||||
while($this->eventReceiver->handle($this));
|
while($this->eventReceiver->handle($this));
|
||||||
|
}finally{
|
||||||
|
Timings::$connection->stopTiming();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
$this->server->getLogger()->debug("Waiting for RakLib to start...");
|
$this->server->getLogger()->debug("Waiting for RakLib to start...");
|
||||||
try{
|
try{
|
||||||
|
@ -40,6 +40,7 @@ use function strlen;
|
|||||||
use function time;
|
use function time;
|
||||||
use function trim;
|
use function trim;
|
||||||
use const AF_INET;
|
use const AF_INET;
|
||||||
|
use const AF_INET6;
|
||||||
use const IPPROTO_IPV6;
|
use const IPPROTO_IPV6;
|
||||||
use const IPV6_V6ONLY;
|
use const IPV6_V6ONLY;
|
||||||
use const PHP_INT_MAX;
|
use const PHP_INT_MAX;
|
||||||
|
@ -73,6 +73,12 @@ use function sprintf;
|
|||||||
use function strlen;
|
use function strlen;
|
||||||
use function trim;
|
use function trim;
|
||||||
use const AF_INET;
|
use const AF_INET;
|
||||||
|
use const PREG_BACKTRACK_LIMIT_ERROR;
|
||||||
|
use const PREG_BAD_UTF8_ERROR;
|
||||||
|
use const PREG_BAD_UTF8_OFFSET_ERROR;
|
||||||
|
use const PREG_INTERNAL_ERROR;
|
||||||
|
use const PREG_JIT_STACKLIMIT_ERROR;
|
||||||
|
use const PREG_RECURSION_LIMIT_ERROR;
|
||||||
use const SO_RCVTIMEO;
|
use const SO_RCVTIMEO;
|
||||||
use const SOCK_DGRAM;
|
use const SOCK_DGRAM;
|
||||||
use const SOCKET_ETIMEDOUT;
|
use const SOCKET_ETIMEDOUT;
|
||||||
|
@ -2448,7 +2448,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
$this->cursorInventory = new PlayerCursorInventory($this);
|
$this->cursorInventory = new PlayerCursorInventory($this);
|
||||||
$this->craftingGrid = new PlayerCraftingInventory($this);
|
$this->craftingGrid = new PlayerCraftingInventory($this);
|
||||||
|
|
||||||
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory);
|
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory, $this->craftingGrid);
|
||||||
|
|
||||||
//TODO: more windows
|
//TODO: more windows
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ use function stream_copy_to_stream;
|
|||||||
use function strpos;
|
use function strpos;
|
||||||
use function strtolower;
|
use function strtolower;
|
||||||
use function trim;
|
use function trim;
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
abstract class PluginBase implements Plugin, CommandExecutor{
|
abstract class PluginBase implements Plugin, CommandExecutor{
|
||||||
private bool $isEnabled = false;
|
private bool $isEnabled = false;
|
||||||
|
@ -41,6 +41,8 @@ abstract class Timings{
|
|||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $serverTick;
|
public static $serverTick;
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
|
public static $serverInterrupts;
|
||||||
|
/** @var TimingsHandler */
|
||||||
public static $memoryManager;
|
public static $memoryManager;
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $garbageCollector;
|
public static $garbageCollector;
|
||||||
@ -140,7 +142,8 @@ abstract class Timings{
|
|||||||
self::$initialized = true;
|
self::$initialized = true;
|
||||||
|
|
||||||
self::$fullTick = new TimingsHandler("Full Server Tick");
|
self::$fullTick = new TimingsHandler("Full Server Tick");
|
||||||
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Full Server Tick", self::$fullTick);
|
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Tick Update Cycle", self::$fullTick);
|
||||||
|
self::$serverInterrupts = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Mid-Tick Processing", self::$fullTick);
|
||||||
self::$memoryManager = new TimingsHandler("Memory Manager");
|
self::$memoryManager = new TimingsHandler("Memory Manager");
|
||||||
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
|
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
|
||||||
self::$titleTick = new TimingsHandler("Console Title Tick");
|
self::$titleTick = new TimingsHandler("Console Title Tick");
|
||||||
|
@ -29,6 +29,7 @@ use pocketmine\lang\Translatable;
|
|||||||
use pocketmine\permission\PermissibleBase;
|
use pocketmine\permission\PermissibleBase;
|
||||||
use pocketmine\permission\PermissibleDelegateTrait;
|
use pocketmine\permission\PermissibleDelegateTrait;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
|
use const PHP_INT_MAX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and
|
* Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and
|
||||||
|
@ -23,6 +23,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\utils;
|
namespace pocketmine\utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This trait offers the same functionality as RegistryTrait, but also clones any returned objects to prevent outside
|
||||||
|
* modification.
|
||||||
|
*/
|
||||||
trait CloningRegistryTrait{
|
trait CloningRegistryTrait{
|
||||||
use RegistryTrait;
|
use RegistryTrait;
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ use const CASE_LOWER;
|
|||||||
use const JSON_BIGINT_AS_STRING;
|
use const JSON_BIGINT_AS_STRING;
|
||||||
use const JSON_PRETTY_PRINT;
|
use const JSON_PRETTY_PRINT;
|
||||||
use const JSON_THROW_ON_ERROR;
|
use const JSON_THROW_ON_ERROR;
|
||||||
|
use const YAML_UTF8_ENCODING;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config Class for simple config manipulation of multiple formats.
|
* Config Class for simple config manipulation of multiple formats.
|
||||||
|
@ -23,6 +23,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\utils;
|
namespace pocketmine\utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This trait allows a class to simulate a Java-style enum. Members are exposed as static methods and handled via
|
||||||
|
* __callStatic().
|
||||||
|
*
|
||||||
|
* Classes using this trait need to include \@method tags in their class docblock for every enum member.
|
||||||
|
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
|
||||||
|
*/
|
||||||
trait EnumTrait{
|
trait EnumTrait{
|
||||||
use RegistryTrait;
|
use RegistryTrait;
|
||||||
use NotCloneable;
|
use NotCloneable;
|
||||||
|
@ -187,9 +187,10 @@ final class Filesystem{
|
|||||||
* @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied)
|
* @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied)
|
||||||
*/
|
*/
|
||||||
public static function createLockFile(string $lockFilePath) : ?int{
|
public static function createLockFile(string $lockFilePath) : ?int{
|
||||||
$resource = fopen($lockFilePath, "a+b");
|
try{
|
||||||
if($resource === false){
|
$resource = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($lockFilePath, "a+b"));
|
||||||
throw new \InvalidArgumentException("Invalid lock file path or read/write permissions denied");
|
}catch(\ErrorException $e){
|
||||||
|
throw new \InvalidArgumentException("Failed to open lock file: " . $e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
if(!flock($resource, LOCK_EX | LOCK_NB)){
|
if(!flock($resource, LOCK_EX | LOCK_NB)){
|
||||||
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
|
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
|
||||||
|
@ -28,6 +28,13 @@ use function count;
|
|||||||
use function mb_strtoupper;
|
use function mb_strtoupper;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This trait allows a class to simulate object class constants, since PHP doesn't currently support this.
|
||||||
|
* These faux constants are exposed in static class methods, which are handled using __callStatic().
|
||||||
|
*
|
||||||
|
* Classes using this trait need to include \@method tags in their class docblock for every faux constant.
|
||||||
|
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
|
||||||
|
*/
|
||||||
trait RegistryTrait{
|
trait RegistryTrait{
|
||||||
/**
|
/**
|
||||||
* @var object[]
|
* @var object[]
|
||||||
|
@ -58,6 +58,7 @@ use pocketmine\item\StringToItemParser;
|
|||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use pocketmine\lang\KnownTranslationFactory;
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
use pocketmine\math\AxisAlignedBB;
|
use pocketmine\math\AxisAlignedBB;
|
||||||
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\nbt\tag\IntTag;
|
use pocketmine\nbt\tag\IntTag;
|
||||||
use pocketmine\nbt\tag\StringTag;
|
use pocketmine\nbt\tag\StringTag;
|
||||||
@ -1222,7 +1223,8 @@ class World implements ChunkManager{
|
|||||||
private function tickChunk(int $chunkX, int $chunkZ) : void{
|
private function tickChunk(int $chunkX, int $chunkZ) : void{
|
||||||
$chunk = $this->getChunk($chunkX, $chunkZ);
|
$chunk = $this->getChunk($chunkX, $chunkZ);
|
||||||
if($chunk === null){
|
if($chunk === null){
|
||||||
throw new \InvalidArgumentException("Chunk is not loaded");
|
//the chunk may have been unloaded during a previous chunk's update (e.g. during BlockGrowEvent)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
foreach($this->getChunkEntities($chunkX, $chunkZ) as $entity){
|
foreach($this->getChunkEntities($chunkX, $chunkZ) as $entity){
|
||||||
$entity->onRandomUpdate();
|
$entity->onRandomUpdate();
|
||||||
@ -1320,7 +1322,9 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify the blocks at and around the position that the block at the position may have changed.
|
* Notify the blocks at and around the position that the block at the position may have changed.
|
||||||
* This will cause onNeighbourBlockUpdate() to be called for these blocks.
|
* This will cause onNearbyBlockChange() to be called for these blocks.
|
||||||
|
*
|
||||||
|
* @see Block::onNearbyBlockChange()
|
||||||
*/
|
*/
|
||||||
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
|
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
|
||||||
$this->tryAddToNeighbourUpdateQueue($pos);
|
$this->tryAddToNeighbourUpdateQueue($pos);
|
||||||
@ -1952,6 +1956,10 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){
|
if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){
|
||||||
$blockReplace = $blockClicked;
|
$blockReplace = $blockClicked;
|
||||||
|
//TODO: while this mimics the vanilla behaviour with replaceable blocks, we should really pass some other
|
||||||
|
//value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know about
|
||||||
|
//the vanilla behaviour.
|
||||||
|
$face = Facing::UP;
|
||||||
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z);
|
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z);
|
||||||
}elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){
|
}elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){
|
||||||
return false;
|
return false;
|
||||||
|
@ -15,11 +15,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: ../../../src/entity/projectile/Projectile.php
|
path: ../../../src/entity/projectile/Projectile.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Empty array passed to foreach\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../../../src/network/mcpe/cache/ChunkCache.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Match arm comparison between 4 and 4 is always true\\.$#"
|
message: "#^Match arm comparison between 4 and 4 is always true\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@ -51,7 +46,7 @@ parameters:
|
|||||||
path: ../../../src/plugin/PluginManager.php
|
path: ../../../src/plugin/PluginManager.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
message: "#^Offset \\(int\\|string\\) on array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../../../src/plugin/PluginManager.php
|
path: ../../../src/plugin/PluginManager.php
|
||||||
|
|
||||||
@ -70,11 +65,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: ../../../src/thread/Worker.php
|
path: ../../../src/thread/Worker.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\world\\\\World\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../../../src/world/WorldManager.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#"
|
message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
|
@ -1,68 +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\network\mcpe\handler;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class StupidJsonDecodeTest extends TestCase{
|
|
||||||
/**
|
|
||||||
* @var \Closure
|
|
||||||
* @phpstan-var \Closure(string $json, bool $assoc=) : mixed
|
|
||||||
*/
|
|
||||||
private $stupidJsonDecodeFunc;
|
|
||||||
|
|
||||||
public function setUp() : void{
|
|
||||||
$this->stupidJsonDecodeFunc = (new \ReflectionMethod(InGamePacketHandler::class, 'stupid_json_decode'))->getClosure();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[][]
|
|
||||||
* @phpstan-return list<array{string,mixed}>
|
|
||||||
*/
|
|
||||||
public function stupidJsonDecodeProvider() : array{
|
|
||||||
return [
|
|
||||||
["[\n \"a\",\"b,c,d,e\\\" \",,0,1,2, false, 0.001]", ['a', 'b,c,d,e" ', '', 0, 1, 2, false, 0.001]],
|
|
||||||
["0", 0],
|
|
||||||
["false", false],
|
|
||||||
["null", null],
|
|
||||||
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
|
|
||||||
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']],
|
|
||||||
['["Hello,, PocketMine"]', ['Hello,, PocketMine']],
|
|
||||||
['[,]', ['', '']],
|
|
||||||
['[]', []]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider stupidJsonDecodeProvider
|
|
||||||
*
|
|
||||||
* @param mixed $expect
|
|
||||||
*
|
|
||||||
* @throws \ReflectionException
|
|
||||||
*/
|
|
||||||
public function testStupidJsonDecode(string $brokenJson, $expect) : void{
|
|
||||||
$decoded = ($this->stupidJsonDecodeFunc)($brokenJson, true);
|
|
||||||
self::assertEquals($expect, $decoded);
|
|
||||||
}
|
|
||||||
}
|
|
@ -43,6 +43,10 @@ use function rename;
|
|||||||
use function round;
|
use function round;
|
||||||
use function scandir;
|
use function scandir;
|
||||||
use function unlink;
|
use function unlink;
|
||||||
|
use const PATHINFO_EXTENSION;
|
||||||
|
use const PHP_BINARY;
|
||||||
|
use const SCANDIR_SORT_NONE;
|
||||||
|
use const SORT_NUMERIC;
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
89
tools/decode-crashdump.php
Normal file
89
tools/decode-crashdump.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?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\tools\decode_crashdump;
|
||||||
|
|
||||||
|
use function array_pop;
|
||||||
|
use function array_slice;
|
||||||
|
use function base64_decode;
|
||||||
|
use function count;
|
||||||
|
use function file;
|
||||||
|
use function file_put_contents;
|
||||||
|
use function fwrite;
|
||||||
|
use function implode;
|
||||||
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
|
use function realpath;
|
||||||
|
use function trim;
|
||||||
|
use function zlib_decode;
|
||||||
|
use const FILE_IGNORE_NEW_LINES;
|
||||||
|
use const JSON_PRETTY_PRINT;
|
||||||
|
use const JSON_UNESCAPED_SLASHES;
|
||||||
|
use const PHP_EOL;
|
||||||
|
use const STDERR;
|
||||||
|
|
||||||
|
if(count($argv) === 2){
|
||||||
|
$input = $argv[1];
|
||||||
|
$output = "decoded.json";
|
||||||
|
}elseif(count($argv) === 3){
|
||||||
|
[, $input, $output] = $argv;
|
||||||
|
}else{
|
||||||
|
fwrite(STDERR, "Required arguments: input file, output file" . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = file($input, FILE_IGNORE_NEW_LINES);
|
||||||
|
if($lines === false){
|
||||||
|
fwrite(STDERR, "Unable to read file $input" . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$start = -1;
|
||||||
|
foreach($lines as $num => $line){
|
||||||
|
if(trim($line) === "===BEGIN CRASH DUMP==="){
|
||||||
|
$start = $num + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($start === -1){
|
||||||
|
fwrite(STDERR, "Crashdump encoded data not found in target file" . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array_slice($lines, $start);
|
||||||
|
array_pop($data);
|
||||||
|
|
||||||
|
$zlibData = base64_decode(implode("", $data), true);
|
||||||
|
if($zlibData === false){
|
||||||
|
fwrite(STDERR, "Invalid encoded data in crashdump" . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$decoded = zlib_decode($zlibData);
|
||||||
|
if($decoded === false){
|
||||||
|
fwrite(STDERR, "Invalid compressed data in crashdump" . PHP_EOL);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($output, json_encode(json_decode($decoded), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
echo "Wrote decoded crashdump to " . realpath($output) . PHP_EOL;
|
@ -49,9 +49,13 @@ use function socket_strerror;
|
|||||||
use function strlen;
|
use function strlen;
|
||||||
use function time;
|
use function time;
|
||||||
use function trim;
|
use function trim;
|
||||||
|
use const AF_INET;
|
||||||
use const MSG_DONTROUTE;
|
use const MSG_DONTROUTE;
|
||||||
use const PHP_BINARY;
|
use const PHP_BINARY;
|
||||||
use const PHP_INT_MAX;
|
use const PHP_INT_MAX;
|
||||||
|
use const SOCK_DGRAM;
|
||||||
|
use const SOL_UDP;
|
||||||
|
use const STDIN;
|
||||||
|
|
||||||
require_once 'vendor/autoload.php';
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user