mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 03:06:55 +00:00
Compare commits
320 Commits
Author | SHA1 | Date | |
---|---|---|---|
fd4ac885bb | |||
f35886f18a | |||
a9eaa55427 | |||
5c41f79be4 | |||
34c2b62ffe | |||
e42a691da9 | |||
3b3fb5e662 | |||
01ffe8bf57 | |||
4abf4aecad | |||
b29f83ee99 | |||
4bc57f00b8 | |||
ff61e1e018 | |||
fb20bb3832 | |||
3333df31df | |||
c09fcb2df2 | |||
c7cdaeae85 | |||
4416cd5a28 | |||
cb93095857 | |||
c8f396ecbc | |||
cb06be615a | |||
0d3c11699c | |||
3667e95ff6 | |||
0e2dc51ec8 | |||
eaf85b028a | |||
9479a1a0ab | |||
1bb2d162ab | |||
ee868bcccc | |||
cbc8576d4a | |||
edcf296086 | |||
9e27c47116 | |||
a0368a843e | |||
3f64906263 | |||
19bb8a00df | |||
4816a66fb8 | |||
06f4e1e4c2 | |||
5b8166c1f0 | |||
30c5cad5b3 | |||
a7a7fe3895 | |||
55ac2f07dc | |||
0660888029 | |||
0476e6bcfc | |||
72bd37e442 | |||
f95fcecb5b | |||
61391b6e23 | |||
fbb6f1f81c | |||
712df04bc4 | |||
1563e25378 | |||
ed84252942 | |||
3d90625020 | |||
9f6b914925 | |||
0e614ea8fd | |||
401bd09d60 | |||
649671cc69 | |||
e755e1dc23 | |||
e34a444dde | |||
78f9985377 | |||
fac2bd3379 | |||
fcfd51dfc7 | |||
ccc76cf338 | |||
10a73488ed | |||
9c5114084b | |||
11630ab1aa | |||
a3068b39a2 | |||
edbc73a72c | |||
3a0c8dd594 | |||
16fa26405a | |||
57423540f0 | |||
8f8821c904 | |||
094102fe92 | |||
2aef83e7d7 | |||
5c3e78e1d3 | |||
18666e5a60 | |||
f72163c173 | |||
7407e504b6 | |||
0e396dc47d | |||
d2204da1d5 | |||
2a51269305 | |||
928041ddf1 | |||
007f4f9350 | |||
46e9f0cec6 | |||
606d56b55d | |||
78a62a8b27 | |||
c5bdd7dd64 | |||
8ee37a3033 | |||
7e3e63f342 | |||
8ef1e54e20 | |||
eaf3a86981 | |||
317a48d9b0 | |||
bc14660e55 | |||
b1bb9fbd1c | |||
96181f8cf5 | |||
4771e3dc28 | |||
2e9117d102 | |||
06493da7d9 | |||
bd303b1062 | |||
fe731b9018 | |||
1a24afc6d1 | |||
bef906b0f0 | |||
37e8dd6444 | |||
02ee0f23c0 | |||
cda472333c | |||
47cf58be8a | |||
ccf9691927 | |||
16fa958416 | |||
bac57c159f | |||
38b2d83799 | |||
c134b1cd8a | |||
62deafda48 | |||
31b6df4376 | |||
b296ae1b87 | |||
f9e42b716a | |||
34c1d455a7 | |||
af8936dba5 | |||
e8ffab1787 | |||
ecc1e1f698 | |||
ea5931e274 | |||
988cf7f535 | |||
e156fb47e8 | |||
efc5f34877 | |||
dd0d8842d5 | |||
7bdc564ccc | |||
69fff23f1a | |||
ae43698e88 | |||
0987e03c03 | |||
97c124edf9 | |||
56501178b7 | |||
da663deea1 | |||
972c911485 | |||
0d8858f948 | |||
da71540fce | |||
ec9b39862b | |||
efca8077d5 | |||
5066d5225b | |||
aefaf73685 | |||
15401d740f | |||
5920b0ba40 | |||
dea75a0687 | |||
873e8740e0 | |||
260c55f23a | |||
9ed430acb9 | |||
f0241043de | |||
135f1c95e4 | |||
5431807e43 | |||
d49ae832e8 | |||
ff9d013005 | |||
b0e1317818 | |||
8653afb0fb | |||
995b56aaa0 | |||
3ecddf312d | |||
470243ca6f | |||
3f21e59917 | |||
fdd74a4f46 | |||
a43b46a93c | |||
0604dfc9e5 | |||
dd2c3db285 | |||
c95e283507 | |||
6afbd1f55c | |||
0682c93f5a | |||
da90ae85da | |||
e87127f309 | |||
0237a50d90 | |||
8b53e4150e | |||
1c43538238 | |||
68887105b2 | |||
104e90b794 | |||
994062f6dc | |||
69a41a5ed4 | |||
3903b70ef5 | |||
692e63ad7c | |||
4d1be4d41d | |||
5f0310a8b6 | |||
9b01fb3d89 | |||
f28405fcfb | |||
9c07c206f6 | |||
d0d701f232 | |||
07cae8a129 | |||
6869ee1c2d | |||
26155acff2 | |||
b550cf5163 | |||
48fa19fdcd | |||
bac986d0b2 | |||
215bac8dd7 | |||
3709ba172b | |||
ef034f2d68 | |||
ab18332572 | |||
48595630fc | |||
4102205ba6 | |||
9e85ee4a7a | |||
e8e6b9304c | |||
23849b7f63 | |||
d2f68836c6 | |||
d19db5d2e4 | |||
98cdc80d37 | |||
8273f789ee | |||
29eccba5f0 | |||
9984b15de6 | |||
6ea01e0dd4 | |||
46331df7db | |||
691c49fb32 | |||
db815360d1 | |||
6e297168c2 | |||
95dbb00d4c | |||
50e29a5ed8 | |||
9f3fb935b5 | |||
e30b1ee2c7 | |||
574b7f6343 | |||
e8b6b56330 | |||
c368ebb5e7 | |||
fa920aa868 | |||
a421d32273 | |||
6c21c23444 | |||
55e0d9c520 | |||
37ee3f2775 | |||
bfdcc12e81 | |||
b2299e08e0 | |||
d7741050c5 | |||
6cff08cd65 | |||
fec42f16ba | |||
deb0cee8a0 | |||
c0dafe7872 | |||
340881d590 | |||
e2e960e43d | |||
500fd2d842 | |||
0b550b346b | |||
1424114cf2 | |||
a8980a0f67 | |||
69aa7c5ac1 | |||
11b74868ee | |||
9a53de0903 | |||
0f8101d4a6 | |||
55ecac4c80 | |||
2a1d1e90a2 | |||
4444a79468 | |||
4cbeee3ab8 | |||
a251960c1c | |||
52f734799e | |||
42171f6e06 | |||
1fe4fdc67c | |||
af4f30d1c8 | |||
3e2926441d | |||
0b33762be0 | |||
e6f89213dc | |||
f8d249b240 | |||
b39afa20d1 | |||
7027a9b972 | |||
b02f3f4090 | |||
8564912149 | |||
873535f719 | |||
78f4fcf6ab | |||
90b749c260 | |||
d5398b2781 | |||
d7a66ad755 | |||
b3f88e7b73 | |||
55adc1ef63 | |||
868d236ddc | |||
59e9c84806 | |||
a110317d1b | |||
b169d89291 | |||
74bef7f423 | |||
8db7867881 | |||
d8f8afe531 | |||
7dabf305f8 | |||
ed0053d0ee | |||
28255e35d1 | |||
0fc9170bbf | |||
7e2efae024 | |||
e9fa07b550 | |||
8ac32824a2 | |||
1322defead | |||
d084b7a34b | |||
c2d0605b1e | |||
0ff0b33047 | |||
114df07622 | |||
4a88db7f43 | |||
d3ea29d527 | |||
d2f1a3cf5b | |||
f9c2ed6200 | |||
e47a711494 | |||
2ea7a9e216 | |||
9f60484212 | |||
3031d89ec5 | |||
883e135bc0 | |||
4448f603a6 | |||
9365525efa | |||
17bee5e349 | |||
c6e0753c3e | |||
2ae7ba275b | |||
6aa0a82341 | |||
0af08a7375 | |||
81c1613e5d | |||
9cf8f608d8 | |||
dd4f26a9cf | |||
f976545f56 | |||
9929fb0abd | |||
37e453b875 | |||
b7578fef9c | |||
09eb904f6b | |||
b47d6bbc22 | |||
aa26ddf8b1 | |||
119c72980f | |||
eba888449d | |||
dac76f0e0f | |||
89fe8f7f10 | |||
2d77b1e364 | |||
e59a4296f8 | |||
6856761946 | |||
4fe3401182 | |||
e80ad22702 | |||
c22ab37372 | |||
1f9d672cfc | |||
974cbae725 | |||
b53f88027e | |||
9a0f723dff | |||
ab2003a85d | |||
4befd9095a | |||
06623d788a | |||
730ee74a65 | |||
700e0afee0 | |||
4b9712fdee | |||
dbd015b866 |
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Help & support on Discord
|
||||
url: https://discord.gg/bmSAZBG
|
||||
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
|
||||
- name: Help & support on forums
|
||||
url: https://forums.pmmp.io
|
||||
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
|
||||
- name: Documentation
|
||||
url: https://pmmp.rtfd.io
|
||||
about: PocketMine-MP documentation
|
1
.github/ISSUE_TEMPLATE/crash.md
vendored
1
.github/ISSUE_TEMPLATE/crash.md
vendored
@ -9,6 +9,7 @@ assignees: ''
|
||||
|
||||
<!--- submit crashdump files to https://crash.pmmp.io -->
|
||||
<!--- or, copy the data between ===BEGIN CRASH DUMP=== and ===END CRASH DUMP and paste it on a site like https://pastebin.com -->
|
||||
<!--- DON'T JUST PASTE the crashdump into an issue -->
|
||||
Link to crashdump:
|
||||
|
||||
<!--- write additional information about the crash to help us find the problem -->
|
||||
|
14
.github/ISSUE_TEMPLATE/help---support.md
vendored
14
.github/ISSUE_TEMPLATE/help---support.md
vendored
@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Help & support
|
||||
about: We don't accept support requests here. Try the links on the README.
|
||||
title: ''
|
||||
labels: Support request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
We don't accept support requests on the issue tracker. Please try the following links instead:
|
||||
|
||||
Documentation: http://pmmp.rtfd.io
|
||||
Forums: https://forums.pmmp.io
|
||||
Discord: https://discord.gg/bmSAZBG
|
@ -1,12 +0,0 @@
|
||||
---
|
||||
name: Security/DoS vulnerability
|
||||
about: 'Bug or exploit that can be used to attack servers (hint: don''t report it
|
||||
on a public issue tracker)'
|
||||
title: ''
|
||||
labels: 'Auto: Spam'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please DO NOT report security vulnerabilities here.
|
||||
Instead, send an email to team@pmmp.io or contact a developer directly, IN PRIVATE.
|
16
.github/support.yml
vendored
16
.github/support.yml
vendored
@ -1,16 +0,0 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: "Support request"
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
Thanks, but this issue tracker is not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
|
||||
|
||||
|
||||
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
|
||||
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
lock: false
|
281
.github/workflows/main.yml
vendored
Normal file
281
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,281 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-php:
|
||||
name: Prepare PHP
|
||||
runs-on: ${{ matrix.image }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [7.3.27, 7.4.16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2 #needed for build.sh
|
||||
- name: Check for PHP build cache
|
||||
id: php-build-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: "./bin"
|
||||
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}"
|
||||
|
||||
- name: Compile PHP
|
||||
if: steps.php-build-cache.outputs.cache-hit != 'true'
|
||||
run: ./tests/gh-actions/build.sh "${{ matrix.php }}"
|
||||
|
||||
phpstan:
|
||||
name: PHPStan analysis
|
||||
needs: build-php
|
||||
runs-on: ${{ matrix.image }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [7.3.27, 7.4.16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore PHP build cache
|
||||
id: php-build-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: "./bin"
|
||||
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}"
|
||||
|
||||
- name: Kill build on PHP build cache miss (should never happen)
|
||||
if: steps.php-build-cache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Install cached PHP's dependencies
|
||||
if: steps.php-build-cache.outputs.cache-hit == 'true'
|
||||
run: chmod +x ./bin/php7/install-dependencies.sh && ./bin/php7/install-dependencies.sh
|
||||
|
||||
- name: Prefix PHP to PATH
|
||||
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: php composer.phar install --prefer-dist --no-interaction
|
||||
|
||||
- name: Run PHPStan
|
||||
run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
|
||||
|
||||
phpunit:
|
||||
name: PHPUnit tests
|
||||
needs: build-php
|
||||
runs-on: ${{ matrix.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [7.3.27, 7.4.16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore PHP build cache
|
||||
id: php-build-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: "./bin"
|
||||
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}"
|
||||
|
||||
- name: Kill build on PHP build cache miss (should never happen)
|
||||
if: steps.php-build-cache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Install cached PHP's dependencies
|
||||
if: steps.php-build-cache.outputs.cache-hit == 'true'
|
||||
run: chmod +x ./bin/php7/install-dependencies.sh && ./bin/php7/install-dependencies.sh
|
||||
|
||||
- name: Prefix PHP to PATH
|
||||
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: php composer.phar install --prefer-dist --no-interaction
|
||||
|
||||
- name: Run PHPUnit tests
|
||||
run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
|
||||
|
||||
integration:
|
||||
name: Integration tests
|
||||
needs: build-php
|
||||
runs-on: ${{ matrix.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [7.3.27, 7.4.16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Restore PHP build cache
|
||||
id: php-build-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: "./bin"
|
||||
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}"
|
||||
|
||||
- name: Kill build on PHP build cache miss (should never happen)
|
||||
if: steps.php-build-cache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Install cached PHP's dependencies
|
||||
if: steps.php-build-cache.outputs.cache-hit == 'true'
|
||||
run: chmod +x ./bin/php7/install-dependencies.sh && ./bin/php7/install-dependencies.sh
|
||||
|
||||
- name: Prefix PHP to PATH
|
||||
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: php composer.phar install --no-dev --prefer-dist --no-interaction
|
||||
|
||||
- name: Run integration tests
|
||||
run: ./tests/travis.sh -t4
|
||||
|
||||
preprocessor:
|
||||
name: Preprocessor tests
|
||||
needs: build-php
|
||||
runs-on: ${{ matrix.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [7.3.27, 7.4.16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Restore PHP build cache
|
||||
id: php-build-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: "./bin"
|
||||
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}"
|
||||
|
||||
- name: Kill build on PHP build cache miss (should never happen)
|
||||
if: steps.php-build-cache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Install cached PHP's dependencies
|
||||
if: steps.php-build-cache.outputs.cache-hit == 'true'
|
||||
run: chmod +x ./bin/php7/install-dependencies.sh && ./bin/php7/install-dependencies.sh
|
||||
|
||||
- name: Prefix PHP to PATH
|
||||
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-${{ matrix.php }}-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: php composer.phar install --no-dev --prefer-dist --no-interaction
|
||||
|
||||
- name: Run preprocessor
|
||||
run: |
|
||||
PM_PREPROCESSOR_PATH="$GITHUB_WORKSPACE/build/preprocessor"
|
||||
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path=src --multisize || (echo "Preprocessor exited with code $?" && exit 1)
|
||||
|
||||
#dump the diff of preprocessor replacements to a patch in case it has bugs
|
||||
git diff > preprocessor_diff.patch
|
||||
|
||||
VENDOR_PM="$GITHUB_WORKSPACE/vendor"
|
||||
VENDOR_PM_BACKUP="$GITHUB_WORKSPACE/vendor-before-preprocess"
|
||||
cp -r "$VENDOR_PM" "$VENDOR_PM_BACKUP"
|
||||
for f in $(ls $VENDOR_PM/pocketmine); do
|
||||
echo "Processing directory \"$f\"..."
|
||||
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path="$VENDOR_PM/pocketmine/$f" --multisize || (echo "Preprocessor exited with code $?" && exit 1)
|
||||
echo "Checking for changes in \"$f\"..."
|
||||
DIFF=$(git diff --no-index "$VENDOR_PM_BACKUP/pocketmine/$f" "$VENDOR_PM/pocketmine/$f" || true)
|
||||
if [ "$DIFF" != "" ]; then
|
||||
PATCH="$GITHUB_WORKSPACE/preprocessor_diff_$f.patch"
|
||||
echo "$DIFF" > "$PATCH"
|
||||
echo "Generated patch file \"$PATCH\""
|
||||
else
|
||||
echo "No diff generated for \"$f\" (preprocessor made no changes)"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload preprocessor diffs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: preprocessor_diffs_${{ matrix.php }}_${{ matrix.image }}
|
||||
path: ${{ github.workspace }}/preprocessor_diff*.patch
|
||||
|
||||
codestyle:
|
||||
name: Code Style checks
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.9.0
|
||||
with:
|
||||
php-version: 7.4
|
||||
tools: php-cs-fixer
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: php-cs-fixer fix --dry-run --diff --diff-format=udiff
|
22
.github/workflows/support.yml
vendored
Normal file
22
.github/workflows/support.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: 'Manage support request issues'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
jobs:
|
||||
support:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
support-label: "Support request"
|
||||
issue-comment: >
|
||||
Thanks, but this issue tracker is not intended for support requests. Please read the guidelines on [submitting an issue](https://github.com/pmmp/PocketMine-MP/blob/master/CONTRIBUTING.md#creating-an-issue).
|
||||
|
||||
|
||||
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
|
||||
|
||||
close-issue: true
|
||||
lock-issue: false
|
71
.php_cs
Normal file
71
.php_cs
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/src')
|
||||
->in(__DIR__ . '/build')
|
||||
->in(__DIR__ . '/tests')
|
||||
->notPath('plugins/DevTools')
|
||||
->notPath('preprocessor')
|
||||
->notContains('#ifndef COMPILE') //preprocessor will break if these are changed
|
||||
->notName('PocketMine.php');
|
||||
|
||||
return PhpCsFixer\Config::create()
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'align_multiline_comment' => [
|
||||
'comment_type' => 'phpdocs_only'
|
||||
],
|
||||
'array_indentation' => true,
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short'
|
||||
],
|
||||
'blank_line_after_namespace' => true,
|
||||
'blank_line_after_opening_tag' => true,
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => [
|
||||
'declare'
|
||||
]
|
||||
],
|
||||
'cast_spaces' => [
|
||||
'space' => 'single'
|
||||
],
|
||||
'concat_space' => [
|
||||
'spacing' => 'one'
|
||||
],
|
||||
'declare_strict_types' => true,
|
||||
'elseif' => true,
|
||||
'global_namespace_import' => [
|
||||
'import_constants' => true,
|
||||
'import_functions' => true,
|
||||
'import_classes' => null,
|
||||
],
|
||||
'indentation_type' => true,
|
||||
'native_function_invocation' => [
|
||||
'scope' => 'namespaced'
|
||||
],
|
||||
'no_closing_tag' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
'no_extra_blank_lines' => true,
|
||||
'no_superfluous_phpdoc_tags' => [
|
||||
'allow_mixed' => true,
|
||||
],
|
||||
'no_trailing_whitespace' => true,
|
||||
'no_trailing_whitespace_in_comment' => true,
|
||||
'no_whitespace_in_blank_line' => true,
|
||||
'no_unused_imports' => true,
|
||||
'ordered_imports' => [
|
||||
'imports_order' => [
|
||||
'class',
|
||||
'function',
|
||||
'const',
|
||||
],
|
||||
'sort_algorithm' => 'alpha'
|
||||
],
|
||||
'phpdoc_trim' => true,
|
||||
'phpdoc_trim_consecutive_blank_line_separation' => true,
|
||||
'single_import_per_statement' => true,
|
||||
'strict_param' => true,
|
||||
])
|
||||
->setFinder($finder)
|
||||
->setIndent("\t")
|
||||
->setLineEnding("\n");
|
32
.travis.yml
32
.travis.yml
@ -1,32 +0,0 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.3
|
||||
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.1.0
|
||||
- git clone https://github.com/pmmp/pthreads.git
|
||||
- cd pthreads
|
||||
- git checkout 45579e1e622acd80f9c880f3a025ba3b98b8ebef
|
||||
- phpize
|
||||
- ./configure
|
||||
- make
|
||||
- make install
|
||||
- cd ..
|
||||
- echo "extension=pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
|
||||
script:
|
||||
- composer install --prefer-dist
|
||||
- ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
|
||||
- ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
|
||||
- composer install --no-dev --prefer-dist
|
||||
- ./tests/travis.sh -t4
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache/files
|
||||
- $HOME/.composer/cache/vcs
|
||||
|
||||
notifications:
|
||||
email: false
|
10
BUILDING.md
10
BUILDING.md
@ -8,7 +8,7 @@
|
||||
## Custom PHP binaries
|
||||
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
|
||||
|
||||
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-7.3-Aggregate)
|
||||
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-7.4-Aggregate)
|
||||
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
|
||||
|
||||
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
|
||||
@ -30,9 +30,15 @@ If you use a custom binary, you'll need to replace `composer` usages in this gui
|
||||
Preprocessor requires that the `cpp` (c preprocessor) is available in your PATH.
|
||||
|
||||
## Building `PocketMine-MP.phar`
|
||||
Run `build/server-phar.php` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
|
||||
Run `composer make-server` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
|
||||
|
||||
You can also use the `--out` option to change the output filename.
|
||||
|
||||
There is a bug in PHP that might cause an error which looks like this:
|
||||
```
|
||||
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
|
||||
```
|
||||
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 7.4.16 or 8.0.3.
|
||||
|
||||
## Running PocketMine-MP from source code
|
||||
Run `src/pocketmine/PocketMine.php` using your preferred PHP binary.
|
||||
|
@ -3,7 +3,12 @@
|
||||
<b>A highly customisable, open source server software for Minecraft: Bedrock Edition written in PHP</b>
|
||||
</p>
|
||||
|
||||
[](https://travis-ci.com/pmmp/PocketMine-MP)
|
||||
<p align="center">
|
||||
<img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" />
|
||||
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img src="https://img.shields.io/github/v/tag/pmmp/PocketMine-MP?label=release&logo=github" alt="GitHub tag (latest semver)" /></a>
|
||||
<a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a>
|
||||
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
|
||||
</p>
|
||||
|
||||
## Getting started
|
||||
- [Documentation](http://pmmp.readthedocs.org/)
|
||||
@ -18,6 +23,7 @@
|
||||
|
||||
## For developers
|
||||
* [Building and running from source](BUILDING.md)
|
||||
* [Developer documentation](https://devdoc.pmmp.io) - General documentation for PocketMine-MP plugin developers
|
||||
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
|
||||
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
|
||||
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
|
||||
|
33
SECURITY.md
Normal file
33
SECURITY.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
The following release lines are currently receiving active security updates and bug fixes:
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| 3.15.x | :white_check_mark: |
|
||||
| < 3.15.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**DO NOT report vulnerabilities on the GitHub issue tracker.**
|
||||
GitHub is public and anyone can see the issues you post on the issue tracker, including people who would exploit vulnerabilities for their own gain.
|
||||
|
||||
**WARNING: You may put live servers at risk by reporting a vulnerability on the GitHub issue tracker.**
|
||||
|
||||
**Contact us** by sending an email to [**team@pmmp.io**](mailto:team@pmmp.io?subject=Security%20Vulnerability%20in%20PocketMine-MP). Include the following information:
|
||||
|
||||
- Version of PocketMine-MP
|
||||
- Detailed description of the vulnerability (e.g. how to exploit it, what the effects are)
|
||||
|
||||
Please note that we can't guarantee a reply to every email.
|
||||
|
||||
## FAQ
|
||||
### Do you offer a bug bounty?
|
||||
No.
|
||||
|
||||
### How soon can I expect a fix for a vulnerability I've reported?
|
||||
This depends on the nature of the problem. We can't provide any general ETA (nor would it be wise to provide one).
|
||||
In general, it depends on when developers have time to look into the problem, how complex the problem is to fix, and how many users it impacts.
|
||||
|
||||
When a fix for a severe vulnerability is pushed, a patch release for the target version will usually be released within 24 hours so that users can update.
|
@ -37,7 +37,6 @@ use const STDIN;
|
||||
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
|
||||
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev) : void{
|
||||
$versionInfo = file_get_contents($versionInfoPath);
|
||||
$versionInfo = preg_replace(
|
||||
@ -72,7 +71,7 @@ function main(array $argv) : void{
|
||||
|
||||
$versionInfoPath = dirname(__DIR__) . '/src/pocketmine/VersionInfo.php';
|
||||
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false);
|
||||
|
||||
|
||||
echo "please add appropriate notes to the changelog and press enter...";
|
||||
fgets(STDIN);
|
||||
system('git add "' . dirname(__DIR__) . '/changelogs"');
|
||||
@ -86,6 +85,4 @@ function main(array $argv) : void{
|
||||
system('git push origin HEAD ' . $currentVer->getBaseVersion());
|
||||
}
|
||||
|
||||
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){
|
||||
main($argv);
|
||||
}
|
||||
main($argv);
|
||||
|
Submodule build/php updated: cec63c3093...c64baa0f1c
Submodule build/preprocessor updated: da363df5f1...1b9304de61
@ -26,7 +26,6 @@ namespace pocketmine\build\server_phar;
|
||||
use pocketmine\utils\Git;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function dirname;
|
||||
use function file_exists;
|
||||
use function getcwd;
|
||||
@ -41,6 +40,7 @@ use function rtrim;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function unlink;
|
||||
use const PHP_EOL;
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
@ -129,6 +129,10 @@ function main() : void{
|
||||
echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
if(file_exists(dirname(__DIR__) . '/vendor/phpunit')){
|
||||
echo "Remove Composer dev dependencies before building (composer install --no-dev)" . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$opts = getopt("", ["out:", "git:"]);
|
||||
if(isset($opts["git"])){
|
||||
@ -169,6 +173,4 @@ STUB
|
||||
}
|
||||
}
|
||||
|
||||
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){
|
||||
main();
|
||||
}
|
||||
main();
|
||||
|
@ -6,7 +6,7 @@ Plugin developers should **only** update their required API to this version if y
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 3.14.0
|
||||
# 3.15.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.20.
|
||||
- Removed compatibility with 1.16.0.
|
||||
|
||||
@ -15,3 +15,40 @@ Plugin developers should **only** update their required API to this version if y
|
||||
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
|
||||
- New blocks, items & mobs aren't implemented
|
||||
- Nether doesn't exist
|
||||
|
||||
# 3.15.1
|
||||
- Fixed various PHP 7.4 compatibility issues in Composer dependencies (primarily callback-validator).
|
||||
- Fixed LevelDB worlds with corrupted `level.dat` crashing the server instead of failing gracefully.
|
||||
- Fixed error spam when using strings for layers in flatworld presets (`e.g. bedrock,3xdirt,grass`).
|
||||
- Fixed blocks not getting updated properly on explosions.
|
||||
- Fixed `BlockGrowEvent` not being called when sugarcane grows.
|
||||
- Potato crops now drop poisonous potatoes when harvested.
|
||||
- Fixed the wrong number of potatoes being dropped when harvesting potato crops.
|
||||
- Players no longer get pullbacks when sprinting on slabs, stairs and various other blocks when `player.anti-cheat.allow-movement-cheats` is set to `false`. (This bug has been around for over 5 years, so many of you will be used to its existence.)
|
||||
- Fixed entity collision box calculation not taking clip distance into account.
|
||||
- Entities now step up the correct height of the target block, instead of jumping into the air 0.6 blocks and falling back down.
|
||||
|
||||
# 3.15.2
|
||||
- Fixed issues with preloading `SubChunk`.
|
||||
- `/gc` and automatic garbage collection will now release unused heap blocks back to the OS. Previously, the PHP process might hold onto these blocks indefinitely even when not used, causing elevated real memory usage.
|
||||
- Added some documentation to `FurnaceBurnEvent`.
|
||||
|
||||
# 3.15.3
|
||||
- Fixed fall damage accumulation over continuous knockbacks (e.g. combo attacks in PvP).
|
||||
- Fixed a bug in `Human->addXp()` that would cause a crash when saving player data.
|
||||
- `Human->addXp()` will no longer modify the target's total XP if `PlayerExperienceChangeEvent` was cancelled.
|
||||
- `AsyncPool->getTaskQueueSizes()` has been added to allow external detection of async pool overload. This is planned to be implemented as a core feature in the future, but it hasn't been done yet.
|
||||
- `BaseInventory->canAddItem()` behaviour now matches `addItem()` by considering the max stack size of the given item.
|
||||
- Fixed a bug in generator options handling for worlds loaded via `pocketmine.yml`. This fix has the following side effects:
|
||||
- It's now possible to provide generator options as an `options` key when loading a world via `pocketmine.yml`.
|
||||
- If generator options are not provided, the options from `server.properties` will be used, instead of using an empty preset. (It's not clear whether this is desired behaviour, but it was clearly intended, since there is code to do this which was broken until this release. As such, this behaviour is subject to change in the future.)
|
||||
- Fixed a bug in region-based world loading where some files without filename extensions and names containing a region filename extension (e.g a file named `amca` in a McRegion world) would cause the world not to load. These files are now ignored.
|
||||
- Default network compression level has been lowered to 6, due to level 7 being 25% more expensive for only a marginal improvement in bandwidth.
|
||||
- Fixed a performance issue with chunk requesting when players trigger chunk generation on first join.
|
||||
- Setup wizard will now always show IP information, even if the user chose to skip the setup wizard when prompted. (This doesn't affect `--no-wizard` in any way.)
|
||||
- `Maximum memory (system)` is no longer reported in `/status` due to having a misleading output (it was the same as the current memory usage).
|
||||
- The `Player Chunk Send` timer on timings reports now actually reports measurements of chunk sending, not chunk loading.
|
||||
- A new parent timer `World Load` has been added to timings reports, which aggregates timings from `syncChunkLoad` and subtimings from all worlds.
|
||||
|
||||
# 3.15.4
|
||||
- Fixed a bug in the inventory transaction system that caused the server to freeze under some circumstances.
|
||||
|
24
changelogs/3.16.md
Normal file
24
changelogs/3.16.md
Normal file
@ -0,0 +1,24 @@
|
||||
**For Minecraft: Bedrock Edition 1.16.100**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 3.16.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.100.
|
||||
- Removed compatibility with earlier versions.
|
||||
- Added new custom composer commands `make-server` and `make-devtools` to ease setting up a development environment and building the server.
|
||||
|
||||
## Known issues (please don't open issues for these)
|
||||
- Walls don't connect to each other
|
||||
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
|
||||
- New blocks, items & mobs aren't implemented
|
||||
- Nether doesn't exist
|
||||
|
||||
# 3.16.1
|
||||
- Fixed incorrect encoding of skins in the protocol.
|
||||
- `/version` no longer crashes when a plugin provides `string[]` for the `author` field in `plugin.yml`.
|
||||
- `author` in `plugin.yml` now accepts arrays, just like `authors`.
|
||||
- Fixed `HellBiome` never being registered.
|
63
changelogs/3.17.md
Normal file
63
changelogs/3.17.md
Normal file
@ -0,0 +1,63 @@
|
||||
**For Minecraft: Bedrock Edition 1.16.200**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 3.17.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.200.
|
||||
- Removed compatibility with earlier versions.
|
||||
|
||||
## Known issues (please don't open issues for these)
|
||||
- Walls don't connect to each other
|
||||
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
|
||||
- New blocks, items & mobs aren't implemented
|
||||
- Nether doesn't exist
|
||||
|
||||
# 3.17.1
|
||||
- Fixed some instances of plugin-caused crashes not being detected (eval()'d code, custom plugin paths).
|
||||
- Server uptime is now included in crash reports.
|
||||
- Hoes now take damage when used to break sponges.
|
||||
- Using lava as fuel in a furnace now leaves behind an empty bucket.
|
||||
|
||||
# 3.17.2
|
||||
- Fixed region header corruption when chunks with larger-than-expected lengths are found. These chunks are now treated as corrupted, instead of automatically attempting to salvage them (which usually fails anyway).
|
||||
- `RegionLoader->removeChunk()` now allows the space used by the removed chunk to be reused by future region saves.
|
||||
- Extracted `Living->applyConsumptionResults()` from `Living->consumeObject()` (preparation for a future bug fix).
|
||||
|
||||
# 3.17.3
|
||||
- Improved performance of chunk loading in Region-based worlds.
|
||||
- Improved performance of region header validation in Region-based worlds (indirect improvement to chunk loading performance).
|
||||
- Fixed some PHP 8.0 language-level compatibility issues.
|
||||
- Source installations will now exit with an error when Composer dependencies are not in sync with the current Git revision. Now, it's required to run `composer install` after every git pull to make sure the correct dependency versions are installed.
|
||||
|
||||
# 3.17.4
|
||||
- Removed `readline` support. This hasn't been maintained for many years, never worked correctly, and isn't thread-safe in any case.
|
||||
- Fixed false-positives of region corruption in Region-based worlds (outdated file stat cache).
|
||||
- Fixed more deprecation warnings on PHP 8.0 (optional parameter before required).
|
||||
- `CraftItemEvent->getInputs()` now returns a list starting at offset 0, instead of random offsets. (Note that the contents still won't be ordered.)
|
||||
- `CraftItemEvent->getOutputs()` now returns a list starting at offset 0, instead of random offsets. (Note that the contents still won't be ordered.)
|
||||
- Fixed a bug that broke synchronized building, bridging, towering and more.
|
||||
- Objects in memory dumps no longer show inherited properties multiple times.
|
||||
|
||||
# 3.17.5
|
||||
- Reduced CPU wastage by the logger thread.
|
||||
- Fixed LevelDB deprecation errors on PHP 8.0.
|
||||
- Added some protocol changes for 1.16.200 which were previously overlooked.
|
||||
- Player XUIDs are now tracked. If a player's XUID does not match the previously recorded XUID when they next join the server, they will be kicked. This can be disabled by the `player.verify-xuid` setting in `pocketmine.yml`.
|
||||
- `BUILDING.md` now has a note about `build/server-phar.php`'s compression bug (a bug in PHP).
|
||||
|
||||
# 3.17.6
|
||||
- Fixed core race conditions that could have led to server freezes (race conditions in pmmp/Snooze).
|
||||
- The log message about Xbox Live authentication being enabled has been reduced to INFO, and the tip on how to turn it off removed (disabling it should usually only be done by power users anyway).
|
||||
- Fixed `PlayerMoveEvent->getFrom()` returning incorrect results for players who experienced movement reversions.
|
||||
- Fixed a bug in `ResourcePackClientResponsePacket` decoding that caused unexpected results when decoding the packet twice.
|
||||
- XUID verification now compares XUIDs against players who are already on the server to detect mismatches to avoid unnecessary loading of playerdata.
|
||||
- Fixed an inventory duplication bug which could occur when the same player joined with two devices at the same time.
|
||||
- Fixed cursor item not being synced on inventory transaction rollbacks.
|
||||
- Fixed items with TAG_Float in their NBT not being able to be moved around in the inventory.
|
||||
|
||||
# 3.17.7
|
||||
- Fixed crash caused by preprocessor in 3.17.6.
|
26
changelogs/3.18.md
Normal file
26
changelogs/3.18.md
Normal file
@ -0,0 +1,26 @@
|
||||
**For Minecraft: Bedrock Edition 1.16.210**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 3.18.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.210.
|
||||
- Removed compatibility with earlier versions.
|
||||
|
||||
## Known issues (please don't open issues for these)
|
||||
- Walls don't connect to each other
|
||||
- Pumpkin and melon stems may not connect to their corresponding pumpkin/melon
|
||||
- New blocks, items & mobs aren't implemented
|
||||
- Nether doesn't exist
|
||||
- Items can't be removed from item frames in Survival mode
|
||||
|
||||
# 3.18.1
|
||||
- UPnP is now supported on all platforms instead of just Windows. Note that it's still experimental. Please file issues for any bugs that you find.
|
||||
- Fixed server joining when default game mode is set to Spectator mode.
|
||||
- Fixed items not being able to be removed from item frames in Survival mode.
|
||||
- Fixed field order in ClientCacheBlobStatusPacket (hits and misses were inverted).
|
||||
- Fixed a deadlock that could occur when MainLogger->syncFlushBuffer() was used (usually only used during exception logging).
|
||||
- Updated constants for various things in the protocol.
|
@ -5,11 +5,10 @@
|
||||
"homepage": "https://pmmp.io",
|
||||
"license": "LGPL-3.0",
|
||||
"require": {
|
||||
"php": ">=7.3.0",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"php-64bit": "*",
|
||||
"ext-bcmath": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-date": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
@ -19,26 +18,27 @@
|
||||
"ext-phar": "*",
|
||||
"ext-pthreads": "~3.2.0",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
"ext-spl": "*",
|
||||
"ext-yaml": ">=2.0.0",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": ">=1.2.11",
|
||||
"pocketmine/raklib": "^0.12.7",
|
||||
"pocketmine/spl": "^0.4.0",
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "^0.1.0",
|
||||
"pocketmine/binaryutils": "^0.1.9",
|
||||
"pocketmine/nbt": "^0.2.10",
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/snooze": "^0.1.0",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/classloader": "^0.1.0",
|
||||
"pocketmine/log": "^0.2.0",
|
||||
"pocketmine/log-pthreads": "^0.1.0",
|
||||
"pocketmine/callback-validator": "^1.0.1",
|
||||
"adhocore/json-comment": "^0.1.0",
|
||||
"ocramius/package-versions": "^1.5"
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/nbt": "^0.2.18",
|
||||
"pocketmine/raklib": "^0.12.7",
|
||||
"pocketmine/snooze": "^0.1.0",
|
||||
"pocketmine/spl": "^0.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "0.12.37",
|
||||
"phpstan/phpstan": "0.12.82",
|
||||
"phpstan/phpstan-phpunit": "^0.12.6",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
@ -57,5 +57,18 @@
|
||||
"psr-4": {
|
||||
"pocketmine\\": "tests/phpunit/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.3.0"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
|
||||
"make-server": [
|
||||
"@composer install --no-dev --classmap-authoritative",
|
||||
"@php -dphar.readonly=0 build/server-phar.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
987
composer.lock
generated
987
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,4 +7,4 @@ This site can be accessed via https://apidoc.pmmp.io.
|
||||
### Additional developer resources
|
||||
- [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
|
||||
- [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
|
||||
- [DeveloperDocs](https://github.com/pmmp/DeveloperDocs/) - Reference, guides and specifications for the PocketMine-MP API
|
||||
- [DeveloperDocs](https://devdoc.pmmp.io) - General documentation for PocketMine-MP plugin developers
|
||||
|
@ -1,8 +1,6 @@
|
||||
includes:
|
||||
- tests/phpstan/configs/actual-problems.neon
|
||||
- tests/phpstan/configs/check-explicit-mixed-baseline.neon
|
||||
- tests/phpstan/configs/com-dotnet-magic.neon
|
||||
- tests/phpstan/configs/custom-leveldb.neon
|
||||
- tests/phpstan/configs/gc-hacks.neon
|
||||
- tests/phpstan/configs/l7-baseline.neon
|
||||
- tests/phpstan/configs/l8-baseline.neon
|
||||
@ -18,8 +16,11 @@ includes:
|
||||
parameters:
|
||||
level: 8
|
||||
checkExplicitMixed: true
|
||||
checkMissingCallableSignature: true
|
||||
bootstrapFiles:
|
||||
- tests/phpstan/bootstrap.php
|
||||
scanDirectories:
|
||||
- tests/plugins/TesterPlugin
|
||||
scanFiles:
|
||||
- src/pocketmine/PocketMine.php
|
||||
- build/make-release.php
|
||||
@ -29,6 +30,7 @@ parameters:
|
||||
- build/make-release.php
|
||||
- build/server-phar.php
|
||||
- tests/phpunit
|
||||
- tests/plugins/TesterPlugin
|
||||
dynamicConstantNames:
|
||||
- pocketmine\IS_DEVELOPMENT_BUILD
|
||||
- pocketmine\DEBUG
|
||||
@ -39,3 +41,9 @@ parameters:
|
||||
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
|
||||
staticReflectionClassNamePatterns:
|
||||
- "#^COM$#"
|
||||
typeAliases:
|
||||
#variadics don't work for this - mixed probably shouldn't work either, but for now it does
|
||||
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that,
|
||||
#we'll just fill it with 10 - it's very unlikely to encounter a callable with 10 parameters anyway.
|
||||
anyCallable: 'callable(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed'
|
||||
anyClosure: '\Closure(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed'
|
||||
|
@ -23,11 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use PackageVersions\Versions;
|
||||
use Composer\InstalledVersions;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
use pocketmine\plugin\PluginLoadOrder;
|
||||
use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\VersionString;
|
||||
use function base64_encode;
|
||||
@ -46,6 +47,7 @@ use function is_resource;
|
||||
use function json_encode;
|
||||
use function json_last_error_msg;
|
||||
use function max;
|
||||
use function microtime;
|
||||
use function mkdir;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_contents;
|
||||
@ -54,10 +56,10 @@ use function php_uname;
|
||||
use function phpinfo;
|
||||
use function phpversion;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_split;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
use function time;
|
||||
use function zend_version;
|
||||
use function zlib_encode;
|
||||
use const E_COMPILE_ERROR;
|
||||
@ -88,7 +90,7 @@ class CrashDump{
|
||||
* having their content changed, version format changing, etc.
|
||||
* It is not necessary to increase this when adding new fields.
|
||||
*/
|
||||
private const FORMAT_VERSION = 3;
|
||||
private const FORMAT_VERSION = 4;
|
||||
|
||||
private const PLUGIN_INVOLVEMENT_NONE = "none";
|
||||
private const PLUGIN_INVOLVEMENT_DIRECT = "direct";
|
||||
@ -98,7 +100,7 @@ class CrashDump{
|
||||
private $server;
|
||||
/** @var resource */
|
||||
private $fp;
|
||||
/** @var int */
|
||||
/** @var float */
|
||||
private $time;
|
||||
/**
|
||||
* @var mixed[]
|
||||
@ -111,12 +113,12 @@ class CrashDump{
|
||||
private $path;
|
||||
|
||||
public function __construct(Server $server){
|
||||
$this->time = time();
|
||||
$this->time = microtime(true);
|
||||
$this->server = $server;
|
||||
if(!is_dir($this->server->getDataPath() . "crashdumps")){
|
||||
mkdir($this->server->getDataPath() . "crashdumps");
|
||||
}
|
||||
$this->path = $this->server->getDataPath() . "crashdumps/" . date("D_M_j-H.i.s-T_Y", $this->time) . ".log";
|
||||
$this->path = $this->server->getDataPath() . "crashdumps/" . date("D_M_j-H.i.s-T_Y", (int) $this->time) . ".log";
|
||||
$fp = @fopen($this->path, "wb");
|
||||
if(!is_resource($fp)){
|
||||
throw new \RuntimeException("Could not create Crash Dump");
|
||||
@ -124,7 +126,8 @@ class CrashDump{
|
||||
$this->fp = $fp;
|
||||
$this->data["format_version"] = self::FORMAT_VERSION;
|
||||
$this->data["time"] = $this->time;
|
||||
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", $this->time));
|
||||
$this->data["uptime"] = $this->time - \pocketmine\START_TIME;
|
||||
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time));
|
||||
$this->addLine();
|
||||
$this->baseCrash();
|
||||
$this->generalData();
|
||||
@ -165,7 +168,9 @@ class CrashDump{
|
||||
if($json === false){
|
||||
throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
|
||||
}
|
||||
$this->encodedData = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
|
||||
$zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
|
||||
if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed");
|
||||
$this->encodedData = $zlibEncoded;
|
||||
foreach(str_split(base64_encode($this->encodedData), 76) as $line){
|
||||
$this->addLine($line);
|
||||
}
|
||||
@ -201,9 +206,16 @@ class CrashDump{
|
||||
|
||||
if($this->server->getProperty("auto-report.send-settings", true) !== false){
|
||||
$this->data["parameters"] = (array) $argv;
|
||||
$this->data["server.properties"] = @file_get_contents($this->server->getDataPath() . "server.properties");
|
||||
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $this->data["server.properties"]);
|
||||
$this->data["pocketmine.yml"] = @file_get_contents($this->server->getDataPath() . "pocketmine.yml");
|
||||
if(($serverDotProperties = @file_get_contents($this->server->getDataPath() . "server.properties")) !== false){
|
||||
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties);
|
||||
}else{
|
||||
$this->data["server.properties"] = $serverDotProperties;
|
||||
}
|
||||
if(($pocketmineDotYml = @file_get_contents($this->server->getDataPath() . "pocketmine.yml")) !== false){
|
||||
$this->data["pocketmine.yml"] = $pocketmineDotYml;
|
||||
}else{
|
||||
$this->data["pocketmine.yml"] = "";
|
||||
}
|
||||
}else{
|
||||
$this->data["pocketmine.yml"] = "";
|
||||
$this->data["server.properties"] = "";
|
||||
@ -309,8 +321,8 @@ class CrashDump{
|
||||
}
|
||||
|
||||
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
|
||||
$frameCleanPath = Utils::cleanPath($filePath); //this will be empty in phar stub
|
||||
if(strpos($frameCleanPath, "plugins") === 0 and file_exists($filePath)){
|
||||
$frameCleanPath = Utils::cleanPath($filePath);
|
||||
if(strpos($frameCleanPath, Utils::CLEAN_PATH_SRC_PREFIX) !== 0){
|
||||
$this->addLine();
|
||||
if($crashFrame){
|
||||
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
|
||||
@ -320,15 +332,17 @@ class CrashDump{
|
||||
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT;
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass(PluginBase::class);
|
||||
$file = $reflection->getProperty("file");
|
||||
$file->setAccessible(true);
|
||||
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
|
||||
$filePath = Utils::cleanPath($file->getValue($plugin));
|
||||
if(strpos($frameCleanPath, $filePath) === 0){
|
||||
$this->data["plugin"] = $plugin->getName();
|
||||
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
|
||||
break;
|
||||
if(file_exists($filePath)){
|
||||
$reflection = new \ReflectionClass(PluginBase::class);
|
||||
$file = $reflection->getProperty("file");
|
||||
$file->setAccessible(true);
|
||||
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
|
||||
$filePath = Utils::cleanPath($file->getValue($plugin));
|
||||
if(strpos($frameCleanPath, $filePath) === 0){
|
||||
$this->data["plugin"] = $plugin->getName();
|
||||
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -338,6 +352,15 @@ class CrashDump{
|
||||
|
||||
private function generalData() : void{
|
||||
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
|
||||
$composerLibraries = [];
|
||||
foreach(InstalledVersions::getInstalledPackages() as $package){
|
||||
$composerLibraries[$package] = sprintf(
|
||||
"%s@%s",
|
||||
InstalledVersions::getPrettyVersion($package) ?? "unknown",
|
||||
InstalledVersions::getReference($package) ?? "unknown"
|
||||
);
|
||||
}
|
||||
|
||||
$this->data["general"] = [];
|
||||
$this->data["general"]["name"] = $this->server->getName();
|
||||
$this->data["general"]["base_version"] = \pocketmine\BASE_VERSION;
|
||||
@ -350,7 +373,7 @@ class CrashDump{
|
||||
$this->data["general"]["zend"] = zend_version();
|
||||
$this->data["general"]["php_os"] = PHP_OS;
|
||||
$this->data["general"]["os"] = Utils::getOS();
|
||||
$this->data["general"]["composer_libraries"] = Versions::VERSIONS;
|
||||
$this->data["general"]["composer_libraries"] = $composerLibraries;
|
||||
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
|
||||
$this->addLine("Git commit: " . \pocketmine\GIT_COMMIT);
|
||||
$this->addLine("uname -a: " . php_uname("a"));
|
||||
@ -358,7 +381,7 @@ class CrashDump{
|
||||
$this->addLine("Zend version: " . zend_version());
|
||||
$this->addLine("OS : " . PHP_OS . ", " . Utils::getOS());
|
||||
$this->addLine("Composer libraries: ");
|
||||
foreach(Versions::VERSIONS as $library => $libraryVersion){
|
||||
foreach($composerLibraries as $library => $libraryVersion){
|
||||
$this->addLine("- $library $libraryVersion");
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ use function fwrite;
|
||||
use function gc_collect_cycles;
|
||||
use function gc_disable;
|
||||
use function gc_enable;
|
||||
use function gc_mem_caches;
|
||||
use function get_class;
|
||||
use function get_declared_classes;
|
||||
use function implode;
|
||||
@ -273,6 +274,7 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$cycles = gc_collect_cycles();
|
||||
gc_mem_caches();
|
||||
|
||||
Timings::$garbageCollectorTimer->stopTiming();
|
||||
|
||||
@ -421,8 +423,12 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$name = $property->getName();
|
||||
if($reflection !== $original and !$property->isPublic()){
|
||||
$name = $reflection->getName() . ":" . $name;
|
||||
if($reflection !== $original){
|
||||
if($property->isPrivate()){
|
||||
$name = $reflection->getName() . ":" . $name;
|
||||
}else{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(!$property->isPublic()){
|
||||
$property->setAccessible(true);
|
||||
|
@ -100,6 +100,8 @@ use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\DoubleTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\mcpe\convert\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\PlayerNetworkSessionAdapter;
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
@ -148,11 +150,14 @@ use pocketmine\network\mcpe\protocol\types\CommandEnum;
|
||||
use pocketmine\network\mcpe\protocol\types\CommandParameter;
|
||||
use pocketmine\network\mcpe\protocol\types\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\Experiments;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\network\mcpe\protocol\types\NetworkInventoryAction;
|
||||
use pocketmine\network\mcpe\protocol\types\PersonaPieceTintColor;
|
||||
use pocketmine\network\mcpe\protocol\types\PersonaSkinPiece;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
|
||||
@ -191,7 +196,9 @@ use function get_class;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_infinite;
|
||||
use function is_int;
|
||||
use function is_nan;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
@ -406,8 +413,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
/** @var float */
|
||||
protected $lastRightClickTime = 0.0;
|
||||
/** @var Vector3|null */
|
||||
protected $lastRightClickPos = null;
|
||||
/** @var \stdClass|null */
|
||||
protected $lastRightClickData = null;
|
||||
|
||||
/**
|
||||
* @return TranslationContainer|string
|
||||
@ -1601,10 +1608,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$dy = $newPos->y - $this->y;
|
||||
$dz = $newPos->z - $this->z;
|
||||
|
||||
//the client likes to clip into blocks like stairs, but we do full server-side prediction of that without
|
||||
//help from the client's position changes, so we deduct the expected clip height from the moved distance.
|
||||
$expectedClipDistance = $this->ySize * (1 - self::STEP_CLIP_MULTIPLIER);
|
||||
$dy -= $expectedClipDistance;
|
||||
$this->move($dx, $dy, $dz);
|
||||
|
||||
$diff = $this->distanceSquared($newPos);
|
||||
|
||||
//TODO: Explore lowering this threshold now that stairs work properly.
|
||||
if($this->isSurvival() and $diff > 0.0625){
|
||||
$ev = new PlayerIllegalMoveEvent($this, $newPos, new Vector3($this->lastX, $this->lastY, $this->lastZ));
|
||||
$ev->setCancelled($this->allowMovementCheats);
|
||||
@ -1614,7 +1626,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if(!$ev->isCancelled()){
|
||||
$revert = true;
|
||||
$this->server->getLogger()->debug($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidMove", [$this->getName()]));
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos);
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos . ", expected clip distance: $expectedClipDistance");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1645,13 +1657,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$deltaAngle = abs($this->lastYaw - $to->yaw) + abs($this->lastPitch - $to->pitch);
|
||||
|
||||
if($delta > 0.0001 or $deltaAngle > 1.0){
|
||||
$this->lastX = $to->x;
|
||||
$this->lastY = $to->y;
|
||||
$this->lastZ = $to->z;
|
||||
|
||||
$this->lastYaw = $to->yaw;
|
||||
$this->lastPitch = $to->pitch;
|
||||
|
||||
$ev = new PlayerMoveEvent($this, $from, $to);
|
||||
|
||||
$ev->call();
|
||||
@ -1666,6 +1671,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lastX = $to->x;
|
||||
$this->lastY = $to->y;
|
||||
$this->lastZ = $to->z;
|
||||
|
||||
$this->lastYaw = $to->yaw;
|
||||
$this->lastPitch = $to->pitch;
|
||||
$this->broadcastMovement();
|
||||
|
||||
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
|
||||
@ -1688,13 +1699,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
protected function revertMovement(Location $from) : void{
|
||||
$this->lastX = $from->x;
|
||||
$this->lastY = $from->y;
|
||||
$this->lastZ = $from->z;
|
||||
|
||||
$this->lastYaw = $from->yaw;
|
||||
$this->lastPitch = $from->pitch;
|
||||
|
||||
$this->setPosition($from);
|
||||
$this->sendPosition($from, $from->yaw, $from->pitch, MovePlayerPacket::MODE_RESET);
|
||||
}
|
||||
@ -1934,7 +1938,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$animation["ImageWidth"],
|
||||
base64_decode($animation["Image"], true)),
|
||||
$animation["Type"],
|
||||
$animation["Frames"]
|
||||
$animation["Frames"],
|
||||
$animation["AnimationExpression"]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1956,6 +1961,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$skinData = new SkinData(
|
||||
$packet->clientData["SkinId"],
|
||||
$packet->clientData["PlayFabId"],
|
||||
base64_decode($packet->clientData["SkinResourcePatch"] ?? "", true),
|
||||
new SkinImage(
|
||||
$packet->clientData["SkinImageHeight"],
|
||||
@ -2072,17 +2078,42 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @return void
|
||||
*/
|
||||
protected function processLogin(){
|
||||
$checkXUID = (bool) $this->server->getProperty("player.verify-xuid", true);
|
||||
$kickForXUIDMismatch = function(string $xuid) use ($checkXUID) : bool{
|
||||
if($checkXUID && $this->xuid !== $xuid){
|
||||
if($this->kick("XUID does not match (possible impersonation attempt)", false)){
|
||||
//TODO: Longer term, we should be identifying playerdata using something more reliable, like XUID or UUID.
|
||||
//However, that would be a very disruptive change, so this will serve as a stopgap for now.
|
||||
//Side note: this will also prevent offline players hijacking XBL playerdata on online servers, since their
|
||||
//XUID will always be empty.
|
||||
return true;
|
||||
}
|
||||
$this->server->getLogger()->debug("XUID mismatch for " . $this->getName() . ", but plugin cancelled event allowing them to join anyway");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
foreach($this->server->getLoggedInPlayers() as $p){
|
||||
if($p !== $this and ($p->iusername === $this->iusername or $this->getUniqueId()->equals($p->getUniqueId()))){
|
||||
if($kickForXUIDMismatch($p->getXuid())){
|
||||
return;
|
||||
}
|
||||
if(!$p->kick("logged in from another location")){
|
||||
$this->close($this->getLeaveMessage(), "Logged in from another location");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->namedtag = $this->server->getOfflinePlayerData($this->username);
|
||||
if($checkXUID){
|
||||
$recordedXUID = $this->namedtag->getTag("LastKnownXUID");
|
||||
if(!($recordedXUID instanceof StringTag)){
|
||||
$this->server->getLogger()->debug("No previous XUID recorded for " . $this->getName() . ", no choice but to trust this player");
|
||||
}elseif(!$kickForXUIDMismatch($recordedXUID->getValue())){
|
||||
$this->server->getLogger()->debug("XUID match for " . $this->getName());
|
||||
}
|
||||
}
|
||||
|
||||
$this->playedBefore = ($this->getLastPlayed() - $this->getFirstPlayed()) > 1; // microtime(true) - microtime(true) may have less than one millisecond difference
|
||||
$this->namedtag->setString("NameTag", $this->username);
|
||||
@ -2174,6 +2205,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
//but it does have an annoying side-effect when true: it makes
|
||||
//the client remove its own non-server-supplied resource packs.
|
||||
$pk->mustAccept = false;
|
||||
$pk->experiments = new Experiments([], false);
|
||||
$this->dataPacket($pk);
|
||||
break;
|
||||
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
|
||||
@ -2239,6 +2271,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$pk->commandsEnabled = true;
|
||||
$pk->levelId = "";
|
||||
$pk->worldName = $this->server->getMotd();
|
||||
$pk->experiments = new Experiments([], false);
|
||||
$pk->itemTable = ItemTypeDictionary::getInstance()->getEntries();
|
||||
$pk->playerMovementSettings = new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false);
|
||||
$this->dataPacket($pk);
|
||||
|
||||
$this->sendDataPacket(new AvailableActorIdentifiersPacket());
|
||||
@ -2324,8 +2359,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function handleMovePlayer(MovePlayerPacket $packet) : bool{
|
||||
$newPos = $packet->position->round(4)->subtract(0, $this->baseOffset, 0);
|
||||
$rawPos = $packet->position;
|
||||
foreach([$rawPos->x, $rawPos->y, $rawPos->z, $packet->yaw, $packet->headYaw, $packet->pitch] as $float){
|
||||
if(is_infinite($float) || is_nan($float)){
|
||||
$this->server->getLogger()->debug("Invalid movement from " . $this->getName() . ", contains NAN/INF components");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$newPos = $rawPos->round(4)->subtract(0, $this->baseOffset, 0);
|
||||
if($this->forceMoveSync !== null and $newPos->distanceSquared($this->forceMoveSync) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks
|
||||
$this->server->getLogger()->debug("Got outdated pre-teleport movement from " . $this->getName() . ", received " . $newPos . ", expected " . $this->asVector3());
|
||||
//Still getting movements from before teleport, ignore them
|
||||
@ -2484,12 +2526,16 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
switch($type){
|
||||
case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_BLOCK:
|
||||
//TODO: start hack for client spam bug
|
||||
$spamBug = ($this->lastRightClickPos !== null and
|
||||
$spamBug = ($this->lastRightClickData !== null and
|
||||
microtime(true) - $this->lastRightClickTime < 0.1 and //100ms
|
||||
$this->lastRightClickPos->distanceSquared($packet->trData->clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
|
||||
$this->lastRightClickData->playerPos->distanceSquared($packet->trData->playerPos) < 0.00001 and
|
||||
$this->lastRightClickData->x === $packet->trData->x and
|
||||
$this->lastRightClickData->y === $packet->trData->y and
|
||||
$this->lastRightClickData->z === $packet->trData->z and
|
||||
$this->lastRightClickData->clickPos->distanceSquared($packet->trData->clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
|
||||
);
|
||||
//get rid of continued spam if the player clicks and holds right-click
|
||||
$this->lastRightClickPos = clone $packet->trData->clickPos;
|
||||
$this->lastRightClickData = $packet->trData;
|
||||
$this->lastRightClickTime = microtime(true);
|
||||
if($spamBug){
|
||||
return true;
|
||||
@ -2865,7 +2911,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
|
||||
if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE_REQUEST)){
|
||||
if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN)){
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2881,7 +2927,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$target = $this->level->getBlock($pos);
|
||||
|
||||
$ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, null, $packet->face, PlayerInteractEvent::LEFT_CLICK_BLOCK);
|
||||
if($this->level->checkSpawnProtection($this, $target)){
|
||||
if($this->isSpectator() || $this->level->checkSpawnProtection($this, $target)){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
@ -2891,6 +2937,16 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
break;
|
||||
}
|
||||
|
||||
$tile = $this->level->getTile($pos);
|
||||
if($tile instanceof ItemFrame and $tile->hasItem()){
|
||||
if (lcg_value() <= $tile->getItemDropChance()){
|
||||
$this->level->dropItem($tile->getBlock(), $tile->getItem());
|
||||
}
|
||||
$tile->setItem(null);
|
||||
$tile->setItemRotation(0);
|
||||
break;
|
||||
}
|
||||
|
||||
$block = $target->getSide($packet->face);
|
||||
if($block->getId() === Block::FIRE){
|
||||
$this->level->setBlock($block, BlockFactory::get(Block::AIR));
|
||||
@ -2942,7 +2998,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
case PlayerActionPacket::ACTION_START_GLIDE:
|
||||
case PlayerActionPacket::ACTION_STOP_GLIDE:
|
||||
break; //TODO
|
||||
case PlayerActionPacket::ACTION_CONTINUE_BREAK:
|
||||
case PlayerActionPacket::ACTION_CRACK_BREAK:
|
||||
$block = $this->level->getBlock($pos);
|
||||
$this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, $block->getRuntimeId() | ($packet->face << 24));
|
||||
//TODO: destroy-progress level event
|
||||
@ -2952,7 +3008,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
case PlayerActionPacket::ACTION_STOP_SWIMMING:
|
||||
//TODO: handle this when it doesn't spam every damn tick (yet another spam bug!!)
|
||||
break;
|
||||
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //ignored (for now)
|
||||
case PlayerActionPacket::ACTION_INTERACT_BLOCK: //TODO: ignored (for now)
|
||||
break;
|
||||
case PlayerActionPacket::ACTION_CREATIVE_PLAYER_DESTROY_BLOCK:
|
||||
//TODO: do we need to handle this?
|
||||
break;
|
||||
default:
|
||||
$this->server->getLogger()->debug("Unhandled/unknown player action type " . $packet->action . " from " . $this->getName());
|
||||
@ -3034,6 +3093,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var int|null */
|
||||
private $closingWindowId = null;
|
||||
|
||||
/** @internal */
|
||||
public function getClosingWindowId() : ?int{ return $this->closingWindowId; }
|
||||
|
||||
public function handleContainerClose(ContainerClosePacket $packet) : bool{
|
||||
if(!$this->spawned){
|
||||
return true;
|
||||
@ -3045,12 +3110,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
unset($this->openHardcodedWindows[$packet->windowId]);
|
||||
$pk = new ContainerClosePacket();
|
||||
$pk->windowId = $packet->windowId;
|
||||
$pk->server = false;
|
||||
$this->sendDataPacket($pk);
|
||||
return true;
|
||||
}
|
||||
if(isset($this->windowIndex[$packet->windowId])){
|
||||
$this->closingWindowId = $packet->windowId;
|
||||
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
|
||||
$this->removeWindow($this->windowIndex[$packet->windowId]);
|
||||
$this->closingWindowId = null;
|
||||
//removeWindow handles sending the appropriate
|
||||
return true;
|
||||
}
|
||||
@ -3729,6 +3797,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
parent::saveNBT();
|
||||
|
||||
$this->namedtag->setString("LastKnownXUID", $this->xuid);
|
||||
|
||||
if($this->isValid()){
|
||||
$this->namedtag->setString("Level", $this->level->getFolderName());
|
||||
}
|
||||
@ -3903,10 +3973,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($targets !== null){
|
||||
if(in_array($this, $targets, true)){
|
||||
$this->forceMoveSync = $pos->asVector3();
|
||||
$this->ySize = 0;
|
||||
}
|
||||
$this->server->broadcastPacket($targets, $pk);
|
||||
}else{
|
||||
$this->forceMoveSync = $pos->asVector3();
|
||||
$this->ySize = 0;
|
||||
$this->dataPacket($pk);
|
||||
}
|
||||
}
|
||||
@ -4115,7 +4187,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function onChunkChanged(Chunk $chunk){
|
||||
if(isset($this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())])){
|
||||
$hasSent = $this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())] ?? false;
|
||||
if($hasSent){
|
||||
$this->usedChunks[$hash] = false;
|
||||
$this->nextChunkOrderRun = 0;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine {
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use pocketmine\utils\Git;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\Process;
|
||||
@ -73,7 +74,6 @@ namespace pocketmine {
|
||||
}
|
||||
|
||||
$extensions = [
|
||||
"bcmath" => "BC Math",
|
||||
"curl" => "cURL",
|
||||
"ctype" => "ctype",
|
||||
"date" => "Date",
|
||||
@ -206,6 +206,19 @@ namespace pocketmine {
|
||||
|
||||
define('pocketmine\GIT_COMMIT', $gitHash);
|
||||
|
||||
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
|
||||
if($composerGitHash !== null){
|
||||
$currentGitHash = explode("-", \pocketmine\GIT_COMMIT)[0];
|
||||
if($currentGitHash !== $composerGitHash){
|
||||
critical_error("Composer dependencies and/or autoloader are out of sync.");
|
||||
critical_error("- Current revision is $currentGitHash");
|
||||
critical_error("- Composer dependencies were last synchronized for revision $composerGitHash");
|
||||
critical_error("Out-of-sync Composer dependencies may result in crashes and classes not being found.");
|
||||
critical_error("Please synchronize Composer dependencies before running the server.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
|
||||
|
||||
define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR);
|
||||
@ -215,7 +228,12 @@ namespace pocketmine {
|
||||
mkdir(\pocketmine\DATA, 0777, true);
|
||||
}
|
||||
|
||||
define('pocketmine\LOCK_FILE', fopen(\pocketmine\DATA . 'server.lock', "a+b"));
|
||||
$lockFile = fopen(\pocketmine\DATA . 'server.lock', "a+b");
|
||||
if($lockFile === false){
|
||||
critical_error("Unable to open server.lock file. Please check that the current user has read/write permissions to it.");
|
||||
exit(1);
|
||||
}
|
||||
define('pocketmine\LOCK_FILE', $lockFile);
|
||||
if(!flock(\pocketmine\LOCK_FILE, 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
|
||||
//other server wrote its PID and released exclusive lock before we get our lock
|
||||
@ -280,7 +298,7 @@ namespace pocketmine {
|
||||
|
||||
if(ThreadManager::getInstance()->stopAll() > 0){
|
||||
$logger->debug("Some threads could not be stopped, performing a force-kill");
|
||||
Process::kill(getmypid());
|
||||
Process::kill(Process::pid());
|
||||
}
|
||||
}while(false);
|
||||
|
||||
@ -300,7 +318,5 @@ namespace pocketmine {
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){
|
||||
\pocketmine\server();
|
||||
}
|
||||
\pocketmine\server();
|
||||
}
|
||||
|
@ -126,7 +126,6 @@ use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function function_exists;
|
||||
use function get_class;
|
||||
use function getmypid;
|
||||
use function getopt;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
@ -1400,10 +1399,10 @@ class Server{
|
||||
Network::$BATCH_THRESHOLD = -1;
|
||||
}
|
||||
|
||||
$this->networkCompressionLevel = (int) $this->getProperty("network.compression-level", 7);
|
||||
$this->networkCompressionLevel = (int) $this->getProperty("network.compression-level", 6);
|
||||
if($this->networkCompressionLevel < 1 or $this->networkCompressionLevel > 9){
|
||||
$this->logger->warning("Invalid network compression level $this->networkCompressionLevel set, setting to default 7");
|
||||
$this->networkCompressionLevel = 7;
|
||||
$this->logger->warning("Invalid network compression level $this->networkCompressionLevel set, setting to default 6");
|
||||
$this->networkCompressionLevel = 6;
|
||||
}
|
||||
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
|
||||
|
||||
@ -1462,8 +1461,7 @@ class Server{
|
||||
|
||||
$this->onlineMode = $this->getConfigBool("xbox-auth", true);
|
||||
if($this->onlineMode){
|
||||
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.auth.enabled"));
|
||||
$this->logger->notice($this->getLanguage()->translateString("pocketmine.server.authProperty.enabled"));
|
||||
$this->logger->info($this->getLanguage()->translateString("pocketmine.server.auth.enabled"));
|
||||
}else{
|
||||
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.auth.disabled"));
|
||||
$this->logger->warning($this->getLanguage()->translateString("pocketmine.server.authWarning"));
|
||||
@ -1543,7 +1541,7 @@ class Server{
|
||||
if(isset($options["generator"])){
|
||||
$generatorOptions = explode(":", $options["generator"]);
|
||||
$generator = GeneratorManager::getGenerator(array_shift($generatorOptions));
|
||||
if(count($options) > 0){
|
||||
if(count($generatorOptions) > 0){
|
||||
$options["preset"] = implode(":", $generatorOptions);
|
||||
}
|
||||
}else{
|
||||
@ -1939,7 +1937,7 @@ class Server{
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->logException($e);
|
||||
$this->logger->emergency("Crashed while crashing, killing process");
|
||||
@Process::kill(getmypid());
|
||||
@Process::kill(Process::pid());
|
||||
}
|
||||
|
||||
}
|
||||
@ -2132,7 +2130,7 @@ class Server{
|
||||
echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
|
||||
sleep($spacing);
|
||||
}
|
||||
@Process::kill(getmypid());
|
||||
@Process::kill(Process::pid());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
|
||||
const _VERSION_INFO_INCLUDED = true;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.15.0";
|
||||
const BASE_VERSION = "3.18.1";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
@ -35,5 +35,6 @@ interface BlockToolType{
|
||||
public const TYPE_PICKAXE = 1 << 2;
|
||||
public const TYPE_AXE = 1 << 3;
|
||||
public const TYPE_SHEARS = 1 << 4;
|
||||
public const TYPE_HOE = 1 << 5;
|
||||
|
||||
}
|
||||
|
@ -418,6 +418,9 @@ abstract class Liquid extends Transparent{
|
||||
return $isOptimalFlowDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-impure This function modifies the adjacent sources count (premature optimisation)
|
||||
*/
|
||||
private function getSmallestFlowDecay(Block $block, int $decay) : int{
|
||||
$blockDecay = $this->getFlowDecay($block);
|
||||
|
||||
|
@ -40,9 +40,13 @@ class Potato extends Crops{
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [
|
||||
ItemFactory::get(Item::POTATO, 0, $this->getDamage() >= 0x07 ? mt_rand(1, 4) : 1)
|
||||
$result = [
|
||||
ItemFactory::get(Item::POTATO, 0, $this->getDamage() >= 0x07 ? mt_rand(1, 5) : 1)
|
||||
];
|
||||
if($this->getDamage() >= 7 && mt_rand(0, 49) === 0){
|
||||
$result[] = ItemFactory::get(Item::POISONOUS_POTATO);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
|
@ -31,6 +31,10 @@ class Sponge extends Solid{
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
return BlockToolType::TYPE_HOE;
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 0.6;
|
||||
}
|
||||
|
@ -87,7 +87,12 @@ class Sugarcane extends Flowable{
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevelNonNull()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
$this->getLevelNonNull()->setBlock($b, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->getLevelNonNull()->setBlock($b, $ev->getNewState(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -25,23 +25,18 @@ namespace pocketmine\command;
|
||||
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\Thread;
|
||||
use pocketmine\utils\Utils;
|
||||
use function extension_loaded;
|
||||
use function fclose;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
use function fstat;
|
||||
use function getopt;
|
||||
use function is_resource;
|
||||
use function microtime;
|
||||
use function preg_replace;
|
||||
use function readline;
|
||||
use function readline_add_history;
|
||||
use function stream_isatty;
|
||||
use function stream_select;
|
||||
use function trim;
|
||||
use function usleep;
|
||||
use const STDIN;
|
||||
|
||||
class CommandReader extends Thread{
|
||||
|
||||
@ -65,12 +60,6 @@ class CommandReader extends Thread{
|
||||
public function __construct(?SleeperNotifier $notifier = null){
|
||||
$this->buffer = new \Threaded;
|
||||
$this->notifier = $notifier;
|
||||
|
||||
$opts = getopt("", ["disable-readline", "enable-readline"]);
|
||||
|
||||
if(extension_loaded("readline") and (Utils::getOS() === Utils::OS_WINDOWS ? isset($opts["enable-readline"]) : !isset($opts["disable-readline"])) and !$this->isPipe(STDIN)){
|
||||
$this->type = self::TYPE_READLINE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,44 +116,28 @@ class CommandReader extends Thread{
|
||||
* @return bool if the main execution should continue reading lines
|
||||
*/
|
||||
private function readLine() : bool{
|
||||
$line = "";
|
||||
if($this->type === self::TYPE_READLINE){
|
||||
if(($raw = readline("> ")) !== false and ($line = trim($raw)) !== ""){
|
||||
readline_add_history($line);
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if(!is_resource(self::$stdin)){
|
||||
$this->initStdin();
|
||||
}
|
||||
|
||||
switch($this->type){
|
||||
/** @noinspection PhpMissingBreakStatementInspection */
|
||||
case self::TYPE_STREAM:
|
||||
//stream_select doesn't work on piped streams for some reason
|
||||
$r = [self::$stdin];
|
||||
$w = $e = null;
|
||||
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
|
||||
return true;
|
||||
}elseif($count === false){ //stream error
|
||||
$this->initStdin();
|
||||
}
|
||||
|
||||
case self::TYPE_PIPED:
|
||||
if(($raw = fgets(self::$stdin)) === false){ //broken pipe or EOF
|
||||
$this->initStdin();
|
||||
$this->synchronized(function() : void{
|
||||
$this->wait(200000);
|
||||
}); //prevent CPU waste if it's end of pipe
|
||||
return true; //loop back round
|
||||
}
|
||||
|
||||
$line = trim($raw);
|
||||
break;
|
||||
}
|
||||
if(!is_resource(self::$stdin)){
|
||||
$this->initStdin();
|
||||
}
|
||||
|
||||
$r = [self::$stdin];
|
||||
$w = $e = null;
|
||||
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
|
||||
return true;
|
||||
}elseif($count === false){ //stream error
|
||||
$this->initStdin();
|
||||
}
|
||||
|
||||
if(($raw = fgets(self::$stdin)) === false){ //broken pipe or EOF
|
||||
$this->initStdin();
|
||||
$this->synchronized(function() : void{
|
||||
$this->wait(200000);
|
||||
}); //prevent CPU waste if it's end of pipe
|
||||
return true; //loop back round
|
||||
}
|
||||
|
||||
$line = trim($raw);
|
||||
|
||||
if($line !== ""){
|
||||
$this->buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line);
|
||||
if($this->notifier !== null){
|
||||
@ -193,17 +166,11 @@ class CommandReader extends Thread{
|
||||
*/
|
||||
public function run(){
|
||||
$this->registerClassLoader();
|
||||
|
||||
if($this->type !== self::TYPE_READLINE){
|
||||
$this->initStdin();
|
||||
}
|
||||
$this->initStdin();
|
||||
|
||||
while(!$this->shutdown and $this->readLine());
|
||||
|
||||
if($this->type !== self::TYPE_READLINE){
|
||||
fclose(self::$stdin);
|
||||
}
|
||||
|
||||
fclose(self::$stdin);
|
||||
}
|
||||
|
||||
public function getThreadName() : string{
|
||||
|
@ -54,9 +54,9 @@ class StatusCommand extends VanillaCommand{
|
||||
$server = $sender->getServer();
|
||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
|
||||
|
||||
$time = microtime(true) - \pocketmine\START_TIME;
|
||||
$time = (int) (microtime(true) - \pocketmine\START_TIME);
|
||||
|
||||
$seconds = floor($time % 60);
|
||||
$seconds = $time % 60;
|
||||
$minutes = null;
|
||||
$hours = null;
|
||||
$days = null;
|
||||
@ -100,7 +100,6 @@ class StatusCommand extends VanillaCommand{
|
||||
$sender->sendMessage(TextFormat::GOLD . "Total memory: " . TextFormat::RED . number_format(round(($mUsage[1] / 1024) / 1024, 2), 2) . " MB.");
|
||||
$sender->sendMessage(TextFormat::GOLD . "Total virtual memory: " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2), 2) . " MB.");
|
||||
$sender->sendMessage(TextFormat::GOLD . "Heap memory: " . TextFormat::RED . number_format(round(($rUsage[0] / 1024) / 1024, 2), 2) . " MB.");
|
||||
$sender->sendMessage(TextFormat::GOLD . "Maximum memory (system): " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2), 2) . " MB.");
|
||||
|
||||
if($server->getProperty("memory.global-limit") > 0){
|
||||
$sender->sendMessage(TextFormat::GOLD . "Maximum memory (manager): " . TextFormat::RED . number_format(round($server->getProperty("memory.global-limit"), 2), 2) . " MB.");
|
||||
|
@ -77,9 +77,9 @@ use function abs;
|
||||
use function assert;
|
||||
use function cos;
|
||||
use function count;
|
||||
use function current;
|
||||
use function deg2rad;
|
||||
use function floor;
|
||||
use function fmod;
|
||||
use function get_class;
|
||||
use function in_array;
|
||||
use function is_a;
|
||||
@ -94,6 +94,7 @@ use const M_PI_2;
|
||||
abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
public const MOTION_THRESHOLD = 0.00001;
|
||||
protected const STEP_CLIP_MULTIPLIER = 0.4;
|
||||
|
||||
public const NETWORK_ID = -1;
|
||||
|
||||
@ -174,56 +175,56 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
public const DATA_RIDER_ROTATION_LOCKED = 57; //byte
|
||||
public const DATA_RIDER_MAX_ROTATION = 58; //float
|
||||
public const DATA_RIDER_MIN_ROTATION = 59; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 60; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_WAITING = 61; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 62; //int
|
||||
/* 63 (int) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_FACE = 64; //byte
|
||||
/* 65 (short) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_POS = 66; //block coords
|
||||
public const DATA_TRADING_PLAYER_EID = 67; //long
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS = 61; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_WAITING = 62; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_PARTICLE_ID = 63; //int
|
||||
/* 64 (int) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_FACE = 65; //byte
|
||||
/* 66 (short) shulker-related */
|
||||
public const DATA_SHULKER_ATTACH_POS = 67; //block coords
|
||||
public const DATA_TRADING_PLAYER_EID = 68; //long
|
||||
|
||||
/* 69 (byte) command-block */
|
||||
public const DATA_COMMAND_BLOCK_COMMAND = 70; //string
|
||||
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 71; //string
|
||||
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 72; //byte
|
||||
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 73; //byte
|
||||
public const DATA_STRENGTH = 74; //int
|
||||
public const DATA_MAX_STRENGTH = 75; //int
|
||||
/* 76 (int) */
|
||||
public const DATA_LIMITED_LIFE = 77;
|
||||
public const DATA_ARMOR_STAND_POSE_INDEX = 78; //int
|
||||
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 79; //int
|
||||
public const DATA_ALWAYS_SHOW_NAMETAG = 80; //byte: -1 = default, 0 = only when looked at, 1 = always
|
||||
public const DATA_COLOR_2 = 81; //byte
|
||||
/* 82 (unknown) */
|
||||
public const DATA_SCORE_TAG = 83; //string
|
||||
public const DATA_BALLOON_ATTACHED_ENTITY = 84; //int64, entity unique ID of owner
|
||||
public const DATA_PUFFERFISH_SIZE = 85; //byte
|
||||
public const DATA_BOAT_BUBBLE_TIME = 86; //int (time in bubble column)
|
||||
public const DATA_PLAYER_AGENT_EID = 87; //long
|
||||
/* 88 (float) related to panda sitting
|
||||
* 89 (float) related to panda sitting */
|
||||
public const DATA_EAT_COUNTER = 90; //int (used by pandas)
|
||||
public const DATA_FLAGS2 = 91; //long (extended data flags)
|
||||
/* 92 (float) related to panda lying down
|
||||
* 93 (float) related to panda lying down */
|
||||
public const DATA_AREA_EFFECT_CLOUD_DURATION = 94; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 95; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 96; //float, usually negative
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 97; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 98; //int
|
||||
public const DATA_INTERACTIVE_TAG = 99; //string (button text)
|
||||
public const DATA_TRADE_TIER = 100; //int
|
||||
public const DATA_MAX_TRADE_TIER = 101; //int
|
||||
public const DATA_TRADE_XP = 102; //int
|
||||
public const DATA_SKIN_ID = 103; //int ???
|
||||
/* 104 (int) related to wither */
|
||||
public const DATA_COMMAND_BLOCK_TICK_DELAY = 105; //int
|
||||
public const DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 106; //byte
|
||||
public const DATA_AMBIENT_SOUND_INTERVAL_MIN = 107; //float
|
||||
public const DATA_AMBIENT_SOUND_INTERVAL_RANGE = 108; //float
|
||||
public const DATA_AMBIENT_SOUND_EVENT = 109; //string
|
||||
/* 70 (byte) command-block */
|
||||
public const DATA_COMMAND_BLOCK_COMMAND = 71; //string
|
||||
public const DATA_COMMAND_BLOCK_LAST_OUTPUT = 72; //string
|
||||
public const DATA_COMMAND_BLOCK_TRACK_OUTPUT = 73; //byte
|
||||
public const DATA_CONTROLLING_RIDER_SEAT_NUMBER = 74; //byte
|
||||
public const DATA_STRENGTH = 75; //int
|
||||
public const DATA_MAX_STRENGTH = 76; //int
|
||||
/* 77 (int) */
|
||||
public const DATA_LIMITED_LIFE = 78;
|
||||
public const DATA_ARMOR_STAND_POSE_INDEX = 79; //int
|
||||
public const DATA_ENDER_CRYSTAL_TIME_OFFSET = 80; //int
|
||||
public const DATA_ALWAYS_SHOW_NAMETAG = 81; //byte: -1 = default, 0 = only when looked at, 1 = always
|
||||
public const DATA_COLOR_2 = 82; //byte
|
||||
/* 83 (unknown) */
|
||||
public const DATA_SCORE_TAG = 84; //string
|
||||
public const DATA_BALLOON_ATTACHED_ENTITY = 85; //int64, entity unique ID of owner
|
||||
public const DATA_PUFFERFISH_SIZE = 86; //byte
|
||||
public const DATA_BOAT_BUBBLE_TIME = 87; //int (time in bubble column)
|
||||
public const DATA_PLAYER_AGENT_EID = 88; //long
|
||||
/* 89 (float) related to panda sitting
|
||||
* 90 (float) related to panda sitting */
|
||||
public const DATA_EAT_COUNTER = 91; //int (used by pandas)
|
||||
public const DATA_FLAGS2 = 92; //long (extended data flags)
|
||||
/* 93 (float) related to panda lying down
|
||||
* 94 (float) related to panda lying down */
|
||||
public const DATA_AREA_EFFECT_CLOUD_DURATION = 95; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_SPAWN_TIME = 96; //int
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_PER_TICK = 97; //float, usually negative
|
||||
public const DATA_AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP = 98; //float
|
||||
public const DATA_AREA_EFFECT_CLOUD_PICKUP_COUNT = 99; //int
|
||||
public const DATA_INTERACTIVE_TAG = 100; //string (button text)
|
||||
public const DATA_TRADE_TIER = 101; //int
|
||||
public const DATA_MAX_TRADE_TIER = 102; //int
|
||||
public const DATA_TRADE_XP = 103; //int
|
||||
public const DATA_SKIN_ID = 104; //int ???
|
||||
/* 105 (int) related to wither */
|
||||
public const DATA_COMMAND_BLOCK_TICK_DELAY = 106; //int
|
||||
public const DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 107; //byte
|
||||
public const DATA_AMBIENT_SOUND_INTERVAL_MIN = 108; //float
|
||||
public const DATA_AMBIENT_SOUND_INTERVAL_RANGE = 109; //float
|
||||
public const DATA_AMBIENT_SOUND_EVENT = 110; //string
|
||||
|
||||
public const DATA_FLAG_ONFIRE = 0;
|
||||
public const DATA_FLAG_SNEAKING = 1;
|
||||
@ -332,8 +333,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
*/
|
||||
private static $knownEntities = [];
|
||||
/**
|
||||
* @var string[][]
|
||||
* @phpstan-var array<class-string<Entity>, list<string>>
|
||||
* @var string[]
|
||||
* @phpstan-var array<class-string<Entity>, string>
|
||||
*/
|
||||
private static $saveNames = [];
|
||||
|
||||
@ -412,7 +413,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
self::$knownEntities[$name] = $className;
|
||||
}
|
||||
|
||||
self::$saveNames[$className] = $saveNames;
|
||||
self::$saveNames[$className] = reset($saveNames);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -707,10 +708,10 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
$this->boundingBox->setBounds(
|
||||
$this->x - $halfWidth,
|
||||
$this->y,
|
||||
$this->y + $this->ySize,
|
||||
$this->z - $halfWidth,
|
||||
$this->x + $halfWidth,
|
||||
$this->y + $this->height,
|
||||
$this->y + $this->height + $this->ySize,
|
||||
$this->z + $halfWidth
|
||||
);
|
||||
}
|
||||
@ -870,8 +871,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
if(!isset(self::$saveNames[static::class])){
|
||||
throw new \InvalidStateException("Entity " . static::class . " is not registered");
|
||||
}
|
||||
reset(self::$saveNames[static::class]);
|
||||
return current(self::$saveNames[static::class]);
|
||||
return self::$saveNames[static::class];
|
||||
}
|
||||
|
||||
public function saveNBT() : void{
|
||||
@ -1311,7 +1311,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
}
|
||||
|
||||
public function getDirection() : ?int{
|
||||
$rotation = ($this->yaw - 90) % 360;
|
||||
$rotation = fmod($this->yaw - 90, 360);
|
||||
if($rotation < 0){
|
||||
$rotation += 360.0;
|
||||
}
|
||||
@ -1449,8 +1449,14 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
$this->fall($this->fallDistance);
|
||||
$this->resetFallDistance();
|
||||
}
|
||||
}elseif($distanceThisTick < 0){
|
||||
}elseif($distanceThisTick < $this->fallDistance){
|
||||
//we've fallen some distance (distanceThisTick is negative)
|
||||
//or we ascended back towards where fall distance was measured from initially (distanceThisTick is positive but less than existing fallDistance)
|
||||
$this->fallDistance -= $distanceThisTick;
|
||||
}else{
|
||||
//we ascended past the apex where fall distance was originally being measured from
|
||||
//reset it so it will be measured starting from the new, higher position
|
||||
$this->fallDistance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1538,7 +1544,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
if($this->keepMovement){
|
||||
$this->boundingBox->offset($dx, $dy, $dz);
|
||||
}else{
|
||||
$this->ySize *= 0.4;
|
||||
$this->ySize *= self::STEP_CLIP_MULTIPLIER;
|
||||
|
||||
/*
|
||||
if($this->isColliding){ //With cobweb?
|
||||
@ -1605,7 +1611,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
$this->boundingBox->offset(0, 0, $dz);
|
||||
|
||||
if($this->stepHeight > 0 and $fallingFlag and $this->ySize < 0.05 and ($movX != $dx or $movZ != $dz)){
|
||||
if($this->stepHeight > 0 and $fallingFlag and ($movX != $dx or $movZ != $dz)){
|
||||
$cx = $dx;
|
||||
$cy = $dy;
|
||||
$cz = $dz;
|
||||
@ -1637,13 +1643,20 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
|
||||
|
||||
$this->boundingBox->offset(0, 0, $dz);
|
||||
|
||||
$reverseDY = -$dy;
|
||||
foreach($list as $bb){
|
||||
$reverseDY = $bb->calculateYOffset($this->boundingBox, $reverseDY);
|
||||
}
|
||||
$dy += $reverseDY;
|
||||
$this->boundingBox->offset(0, $reverseDY, 0);
|
||||
|
||||
if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){
|
||||
$dx = $cx;
|
||||
$dy = $cy;
|
||||
$dz = $cz;
|
||||
$this->boundingBox->setBB($axisalignedbb1);
|
||||
}else{
|
||||
$this->ySize += 0.5; //FIXME: this should be the height of the block it walked up, not fixed 0.5
|
||||
$this->ySize += $dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -300,17 +300,20 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
if($consumable instanceof MaybeConsumable and !$consumable->canBeConsumed()){
|
||||
return false;
|
||||
}
|
||||
if($consumable instanceof FoodSource && $consumable->requiresHunger() and !$this->isHungry()){
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::consumeObject($consumable);
|
||||
}
|
||||
|
||||
protected function applyConsumptionResults(Consumable $consumable) : void{
|
||||
if($consumable instanceof FoodSource){
|
||||
if($consumable->requiresHunger() and !$this->isHungry()){
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->addFood($consumable->getFoodRestore());
|
||||
$this->addSaturation($consumable->getSaturationRestore());
|
||||
}
|
||||
|
||||
return parent::consumeObject($consumable);
|
||||
parent::applyConsumptionResults($consumable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -400,12 +403,14 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
|
||||
* @param bool $playSound Whether to play level-up and XP gained sounds.
|
||||
*/
|
||||
public function addXp(int $amount, bool $playSound = true) : bool{
|
||||
$this->totalXp += $amount;
|
||||
|
||||
$oldLevel = $this->getXpLevel();
|
||||
$oldTotal = $this->getCurrentTotalXp();
|
||||
|
||||
if($this->setCurrentTotalXp($oldTotal + $amount)){
|
||||
if($amount > 0){
|
||||
$this->totalXp += $amount;
|
||||
}
|
||||
|
||||
if($playSound){
|
||||
$newLevel = $this->getXpLevel();
|
||||
if((int) ($newLevel / 5) > (int) ($oldLevel / 5)){
|
||||
|
@ -45,6 +45,7 @@ use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\FloatTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEffectPacket;
|
||||
@ -97,17 +98,15 @@ abstract class Living extends Entity implements Damageable{
|
||||
//TODO: load/save armor inventory contents
|
||||
$this->armorInventory->setEventProcessor(new ArmorInventoryEventProcessor($this));
|
||||
|
||||
$health = $this->getMaxHealth();
|
||||
|
||||
if($this->namedtag->hasTag("HealF", FloatTag::class)){
|
||||
$health = $this->namedtag->getFloat("HealF");
|
||||
$this->namedtag->removeTag("HealF");
|
||||
}elseif($this->namedtag->hasTag("Health")){
|
||||
$healthTag = $this->namedtag->getTag("Health");
|
||||
$health = (float) $healthTag->getValue(); //Older versions of PocketMine-MP incorrectly saved this as a short instead of a float
|
||||
if(!($healthTag instanceof FloatTag)){
|
||||
$this->namedtag->removeTag("Health");
|
||||
}
|
||||
}elseif($this->namedtag->hasTag("Health", ShortTag::class)){
|
||||
//Older versions of PocketMine-MP incorrectly saved this as a short instead of a float
|
||||
$health = $this->namedtag->getShort("Health");
|
||||
$this->namedtag->removeTag("Health");
|
||||
}else{
|
||||
$health = $this->namedtag->getFloat("Health", $this->getMaxHealth());
|
||||
}
|
||||
|
||||
$this->setHealth($health);
|
||||
@ -364,13 +363,20 @@ abstract class Living extends Entity implements Damageable{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->applyConsumptionResults($consumable);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies effects from consuming the object. This shouldn't do any can-consume checks (those are expected to be
|
||||
* handled by the caller).
|
||||
*/
|
||||
protected function applyConsumptionResults(Consumable $consumable) : void{
|
||||
foreach($consumable->getAdditionalEffects() as $effect){
|
||||
$this->addEffect($effect);
|
||||
}
|
||||
|
||||
$consumable->onConsume($this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,8 @@ declare(strict_types=1);
|
||||
namespace pocketmine\entity\utils;
|
||||
|
||||
use pocketmine\math\Math;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function count;
|
||||
use function max;
|
||||
|
||||
abstract class ExperienceUtils{
|
||||
@ -59,6 +61,9 @@ abstract class ExperienceUtils{
|
||||
* This returns a floating-point number, the decimal part being the progress through the resulting level.
|
||||
*/
|
||||
public static function getLevelFromXp(int $xp) : float{
|
||||
if($xp < 0){
|
||||
throw new \InvalidArgumentException("XP must be at least 0");
|
||||
}
|
||||
if($xp <= self::getXpToReachLevel(16)){
|
||||
$a = 1;
|
||||
$b = 6;
|
||||
@ -74,6 +79,9 @@ abstract class ExperienceUtils{
|
||||
}
|
||||
|
||||
$x = Math::solveQuadratic($a, $b, $c - $xp);
|
||||
if(count($x) === 0){
|
||||
throw new AssumptionFailedError("Expected at least 1 solution");
|
||||
}
|
||||
|
||||
return max($x); //we're only interested in the positive solution
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class BlockBreakEvent extends BlockEvent implements Cancellable{
|
||||
/**
|
||||
* @param Item[] $drops
|
||||
*/
|
||||
public function __construct(Player $player, Block $block, Item $item, bool $instaBreak = false, array $drops, int $xpDrops = 0){
|
||||
public function __construct(Player $player, Block $block, Item $item, bool $instaBreak = false, array $drops = [], int $xpDrops = 0){
|
||||
parent::__construct($block);
|
||||
$this->item = $item;
|
||||
$this->player = $player;
|
||||
|
@ -28,6 +28,9 @@ use pocketmine\event\Cancellable;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\tile\Furnace;
|
||||
|
||||
/**
|
||||
* Called when a furnace is about to consume a new fuel item.
|
||||
*/
|
||||
class FurnaceBurnEvent extends BlockEvent implements Cancellable{
|
||||
/** @var Furnace */
|
||||
private $furnace;
|
||||
@ -53,18 +56,31 @@ class FurnaceBurnEvent extends BlockEvent implements Cancellable{
|
||||
return $this->fuel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ticks that the furnace will be powered for.
|
||||
*/
|
||||
public function getBurnTime() : int{
|
||||
return $this->burnTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of ticks that the given fuel will power the furnace for.
|
||||
*/
|
||||
public function setBurnTime(int $burnTime) : void{
|
||||
$this->burnTime = $burnTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the fuel item will be consumed.
|
||||
*/
|
||||
public function isBurning() : bool{
|
||||
return $this->burning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the fuel will be consumed. If false, the furnace will smelt as if it consumed fuel, but no fuel
|
||||
* will be deducted.
|
||||
*/
|
||||
public function setBurning(bool $burning) : void{
|
||||
$this->burning = $burning;
|
||||
}
|
||||
|
@ -39,14 +39,22 @@ class PlayerCreationEvent extends Event{
|
||||
/** @var int */
|
||||
private $port;
|
||||
|
||||
/** @var string */
|
||||
/**
|
||||
* @var string
|
||||
* @phpstan-var class-string<Player>
|
||||
*/
|
||||
private $baseClass;
|
||||
/** @var string */
|
||||
/**
|
||||
* @var string
|
||||
* @phpstan-var class-string<Player>
|
||||
*/
|
||||
private $playerClass;
|
||||
|
||||
/**
|
||||
* @param string $baseClass
|
||||
* @param string $playerClass
|
||||
* @phpstan-param class-string<Player> $baseClass
|
||||
* @phpstan-param class-string<Player> $playerClass
|
||||
*/
|
||||
public function __construct(SourceInterface $interface, $baseClass, $playerClass, string $address, int $port){
|
||||
$this->interface = $interface;
|
||||
@ -80,6 +88,7 @@ class PlayerCreationEvent extends Event{
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-return class-string<Player>
|
||||
*/
|
||||
public function getBaseClass(){
|
||||
return $this->baseClass;
|
||||
@ -87,6 +96,7 @@ class PlayerCreationEvent extends Event{
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<Player> $class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@ -100,6 +110,7 @@ class PlayerCreationEvent extends Event{
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @phpstan-return class-string<Player>
|
||||
*/
|
||||
public function getPlayerClass(){
|
||||
return $this->playerClass;
|
||||
@ -107,6 +118,7 @@ class PlayerCreationEvent extends Event{
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @phpstan-param class-string<Player> $class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
@ -252,11 +252,11 @@ abstract class BaseInventory implements Inventory{
|
||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||
$slot = $this->getItem($i);
|
||||
if($item->equals($slot)){
|
||||
if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){
|
||||
if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){
|
||||
$count -= $diff;
|
||||
}
|
||||
}elseif($slot->isNull()){
|
||||
$count -= $this->getMaxStackSize();
|
||||
$count -= min($this->getMaxStackSize(), $item->getMaxStackSize());
|
||||
}
|
||||
|
||||
if($count <= 0){
|
||||
|
@ -64,6 +64,7 @@ abstract class ContainerInventory extends BaseInventory{
|
||||
public function onClose(Player $who) : void{
|
||||
$pk = new ContainerClosePacket();
|
||||
$pk->windowId = $who->getWindowId($this);
|
||||
$pk->server = $who->getClosingWindowId() !== $pk->windowId;
|
||||
$who->dataPacket($pk);
|
||||
parent::onClose($who);
|
||||
}
|
||||
|
@ -53,4 +53,13 @@ class PlayerCursorInventory extends BaseInventory{
|
||||
public function getHolder(){
|
||||
return $this->holder;
|
||||
}
|
||||
|
||||
public function sendContents($target) : void{
|
||||
//TODO: HACK!
|
||||
//Since 1.13, this is now part of a larger "UI inventory", and sending contents for this larger inventory does
|
||||
//not work the way it's intended to. Even if it did, it would be necessary to send all 51 slots just to update
|
||||
//this one, which is just not worth it.
|
||||
//This workaround isn't great, but it's at least simple.
|
||||
$this->sendSlot(0, $target);
|
||||
}
|
||||
}
|
||||
|
@ -108,9 +108,6 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
}
|
||||
}
|
||||
|
||||
if($iterations < 1){
|
||||
throw new TransactionValidationException("Tried to craft zero times");
|
||||
}
|
||||
if(count($txItems) > 0){
|
||||
//all items should be destroyed in this process
|
||||
throw new TransactionValidationException("Expected 0 ingredients left over, have " . count($txItems));
|
||||
@ -166,6 +163,7 @@ class CraftingTransaction extends InventoryTransaction{
|
||||
*/
|
||||
$pk = new ContainerClosePacket();
|
||||
$pk->windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID;
|
||||
$pk->server = true;
|
||||
$this->source->dataPacket($pk);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ use pocketmine\inventory\transaction\action\SlotChangeAction;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Player;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function get_class;
|
||||
@ -168,6 +169,8 @@ class InventoryTransaction{
|
||||
}
|
||||
}
|
||||
}
|
||||
$needItems = array_values($needItems);
|
||||
$haveItems = array_values($haveItems);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,21 +233,34 @@ class InventoryTransaction{
|
||||
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
|
||||
assert(count($possibleActions) > 0);
|
||||
|
||||
$candidate = null;
|
||||
$newList = $possibleActions;
|
||||
foreach($possibleActions as $i => $action){
|
||||
if($action->getSourceItem()->equalsExact($needOrigin)){
|
||||
$newList = $possibleActions;
|
||||
if($candidate !== null){
|
||||
/*
|
||||
* we found multiple possible actions that match the origin action
|
||||
* this means that there are multiple ways that this chain could play out
|
||||
* if we cared so much about this, we could build all the possible chains in parallel and see which
|
||||
* variation managed to complete the chain, but this has an extremely high complexity which is not
|
||||
* worth the trouble for this scenario (we don't usually expect to see chains longer than a couple
|
||||
* of actions in here anyway), and might still result in multiple possible results.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
$candidate = $action;
|
||||
unset($newList[$i]);
|
||||
if(count($newList) === 0){
|
||||
return $action->getTargetItem();
|
||||
}
|
||||
$result = $this->findResultItem($action->getTargetItem(), $newList);
|
||||
if($result !== null){
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
if($candidate === null){
|
||||
//chaining is not possible with this origin, none of the actions are valid
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
if(count($newList) === 0){
|
||||
return $candidate->getTargetItem();
|
||||
}
|
||||
return $this->findResultItem($candidate->getTargetItem(), $newList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,14 @@ class Bucket extends Item implements MaybeConsumable{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getFuelResidue() : Item{
|
||||
if($this->meta === Block::LAVA or $this->meta === Block::FLOWING_LAVA){
|
||||
return ItemFactory::get(Item::BUCKET);
|
||||
}
|
||||
|
||||
return parent::getFuelResidue();
|
||||
}
|
||||
|
||||
public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector) : bool{
|
||||
$resultBlock = BlockFactory::get($this->meta);
|
||||
|
||||
|
@ -23,11 +23,24 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockToolType;
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
class Hoe extends TieredTool{
|
||||
|
||||
public function getBlockToolType() : int{
|
||||
return BlockToolType::TYPE_HOE;
|
||||
}
|
||||
|
||||
public function onAttackEntity(Entity $victim) : bool{
|
||||
return $this->applyDamage(1);
|
||||
}
|
||||
|
||||
public function onDestroyBlock(Block $block) : bool{
|
||||
if($block->getHardness() > 0){
|
||||
return $this->applyDamage(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ use pocketmine\nbt\tag\NamedTag;
|
||||
use pocketmine\nbt\tag\ShortTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use function array_map;
|
||||
use function base64_decode;
|
||||
@ -475,7 +476,12 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
public function getLore() : array{
|
||||
$display = $this->getNamedTagEntry(self::TAG_DISPLAY);
|
||||
if($display instanceof CompoundTag and ($lore = $display->getListTag(self::TAG_DISPLAY_LORE)) !== null){
|
||||
return $lore->getAllValues();
|
||||
return array_map(function(NamedTag $line) : string{
|
||||
if(!($line instanceof StringTag)){
|
||||
throw new AssumptionFailedError("Nobody bothered to handle this error case and we can't fix it until PM4, oops ... #blameshoghi");
|
||||
}
|
||||
return $line->getValue();
|
||||
}, $lore->getValue());
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -649,6 +655,16 @@ class Item implements ItemIds, \JsonSerializable{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an item after burning fuel
|
||||
*/
|
||||
public function getFuelResidue() : Item{
|
||||
$item = clone $this;
|
||||
$item->pop();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many points of damage this item will deal to an entity when used as a weapon.
|
||||
*/
|
||||
|
Submodule src/pocketmine/lang/locale updated: 85343cfb7f...c85a7b79f3
@ -154,7 +154,6 @@ class Explosion{
|
||||
* and creating sounds and particles.
|
||||
*/
|
||||
public function explodeB() : bool{
|
||||
$send = [];
|
||||
$updateBlocks = [];
|
||||
|
||||
$source = (new Vector3($this->source->x, $this->source->y, $this->source->z))->floor();
|
||||
@ -232,7 +231,9 @@ class Explosion{
|
||||
|
||||
$t->close();
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->affectedBlocks as $block){
|
||||
$pos = new Vector3($block->x, $block->y, $block->z);
|
||||
|
||||
for($side = 0; $side <= 5; $side++){
|
||||
@ -252,7 +253,6 @@ class Explosion{
|
||||
$updateBlocks[$index] = true;
|
||||
}
|
||||
}
|
||||
$send[] = new Vector3($block->x - $source->x, $block->y - $source->y, $block->z - $source->z);
|
||||
}
|
||||
|
||||
$this->level->addParticle(new HugeExplodeSeedParticle($source));
|
||||
|
@ -53,7 +53,6 @@ use pocketmine\level\format\io\ChunkRequestTask;
|
||||
use pocketmine\level\format\io\exception\CorruptedChunkException;
|
||||
use pocketmine\level\format\io\exception\UnsupportedChunkFormatException;
|
||||
use pocketmine\level\format\io\LevelProvider;
|
||||
use pocketmine\level\generator\Generator;
|
||||
use pocketmine\level\generator\GeneratorManager;
|
||||
use pocketmine\level\generator\GeneratorRegisterTask;
|
||||
use pocketmine\level\generator\GeneratorUnregisterTask;
|
||||
@ -289,7 +288,10 @@ class Level implements ChunkManager, Metadatable{
|
||||
/** @var bool */
|
||||
private $doingTick = false;
|
||||
|
||||
/** @var string|Generator */
|
||||
/**
|
||||
* @var string
|
||||
* @phpstan-var class-string<\pocketmine\level\generator\Generator>
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/** @var bool */
|
||||
|
@ -80,15 +80,14 @@ class LevelTimings{
|
||||
$this->entityTick = new TimingsHandler("** " . $name . "entityTick");
|
||||
$this->tileEntityTick = new TimingsHandler("** " . $name . "tileEntityTick");
|
||||
|
||||
$this->syncChunkSendTimer = new TimingsHandler("** " . $name . "syncChunkSend");
|
||||
$this->syncChunkSendPrepareTimer = new TimingsHandler("** " . $name . "syncChunkSendPrepare");
|
||||
Timings::init(); //make sure the timers we want are available
|
||||
$this->syncChunkSendTimer = new TimingsHandler("** " . $name . "syncChunkSend", Timings::$playerChunkSendTimer);
|
||||
$this->syncChunkSendPrepareTimer = new TimingsHandler("** " . $name . "syncChunkSendPrepare", Timings::$playerChunkSendTimer);
|
||||
|
||||
$this->syncChunkLoadTimer = new TimingsHandler("** " . $name . "syncChunkLoad");
|
||||
$this->syncChunkLoadTimer = new TimingsHandler("** " . $name . "syncChunkLoad", Timings::$worldLoadTimer);
|
||||
$this->syncChunkLoadDataTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Data");
|
||||
$this->syncChunkLoadEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Entities");
|
||||
$this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileEntities");
|
||||
|
||||
Timings::init(); //make sure the timer we want is available
|
||||
$this->syncChunkSaveTimer = new TimingsHandler("** " . $name . "syncChunkSave", Timings::$worldSaveTimer);
|
||||
|
||||
$this->doTick = new TimingsHandler($name . "doTick");
|
||||
|
@ -99,6 +99,8 @@ abstract class Biome{
|
||||
self::register(self::SWAMP, new SwampBiome());
|
||||
self::register(self::RIVER, new RiverBiome());
|
||||
|
||||
self::register(self::HELL, new HellBiome());
|
||||
|
||||
self::register(self::ICE_PLAINS, new IcePlainsBiome());
|
||||
|
||||
self::register(self::SMALL_MOUNTAINS, new SmallMountainsBiome());
|
||||
|
@ -30,6 +30,7 @@ use pocketmine\block\BlockFactory;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\tile\Spawnable;
|
||||
@ -691,13 +692,14 @@ class Chunk{
|
||||
|
||||
$level->timings->syncChunkLoadEntitiesTimer->startTiming();
|
||||
foreach($this->NBTentities as $nbt){
|
||||
if(!$nbt->hasTag("id")){ //allow mixed types (because of leveldb)
|
||||
$idTag = $nbt->getTag("id");
|
||||
if(!($idTag instanceof IntTag) && !($idTag instanceof StringTag)){ //allow mixed types (because of leveldb)
|
||||
$changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
try{
|
||||
$entity = Entity::createEntity($nbt->getTag("id")->getValue(), $level, $nbt);
|
||||
$entity = Entity::createEntity($idTag->getValue(), $level, $nbt);
|
||||
if(!($entity instanceof Entity)){
|
||||
$changed = true;
|
||||
continue;
|
||||
@ -919,7 +921,9 @@ class Chunk{
|
||||
|
||||
$biomeIds = $stream->get(256);
|
||||
if($lightPopulated){
|
||||
$heightMap = array_values(unpack("v*", $stream->get(512)));
|
||||
/** @var int[] $unpackedHeightMap */
|
||||
$unpackedHeightMap = unpack("v*", $stream->get(512)); //unpack() will never fail here
|
||||
$heightMap = array_values($unpackedHeightMap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -927,6 +931,7 @@ class Chunk{
|
||||
$chunk->setGenerated($terrainGenerated);
|
||||
$chunk->setPopulated($terrainPopulated);
|
||||
$chunk->setLightPopulated($lightPopulated);
|
||||
$chunk->setChanged(false);
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
|
||||
}
|
||||
|
||||
class SubChunk implements SubChunkInterface{
|
||||
private const ZERO_NIBBLE_ARRAY = ZERO_NIBBLE_ARRAY;
|
||||
|
||||
/** @var string */
|
||||
protected $ids;
|
||||
/** @var string */
|
||||
@ -68,7 +70,7 @@ class SubChunk implements SubChunkInterface{
|
||||
substr_count($this->ids, "\x00") === 4096 and
|
||||
(!$checkLight or (
|
||||
substr_count($this->skyLight, "\xff") === 2048 and
|
||||
$this->blockLight === ZERO_NIBBLE_ARRAY
|
||||
$this->blockLight === self::ZERO_NIBBLE_ARRAY
|
||||
))
|
||||
);
|
||||
}
|
||||
@ -230,14 +232,14 @@ class SubChunk implements SubChunkInterface{
|
||||
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
|
||||
* modified, which is perfect for this purpose.
|
||||
*/
|
||||
if($this->data === ZERO_NIBBLE_ARRAY){
|
||||
$this->data = ZERO_NIBBLE_ARRAY;
|
||||
if($this->data === self::ZERO_NIBBLE_ARRAY){
|
||||
$this->data = self::ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
if($this->skyLight === ZERO_NIBBLE_ARRAY){
|
||||
$this->skyLight = ZERO_NIBBLE_ARRAY;
|
||||
if($this->skyLight === self::ZERO_NIBBLE_ARRAY){
|
||||
$this->skyLight = self::ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
if($this->blockLight === ZERO_NIBBLE_ARRAY){
|
||||
$this->blockLight = ZERO_NIBBLE_ARRAY;
|
||||
if($this->blockLight === self::ZERO_NIBBLE_ARRAY){
|
||||
$this->blockLight = self::ZERO_NIBBLE_ARRAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,6 @@ abstract class LevelProviderManager{
|
||||
throw new \InvalidArgumentException("Class $class cannot be constructed");
|
||||
}
|
||||
|
||||
/** @var LevelProvider $class */
|
||||
self::$providers[strtolower($class::getProviderName())] = $class;
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,11 @@ class LevelDB extends BaseLevelProvider{
|
||||
throw new LevelException("Truncated level.dat");
|
||||
}
|
||||
$nbt = new LittleEndianNBTStream();
|
||||
$levelData = $nbt->read(substr($rawLevelData, 8));
|
||||
try{
|
||||
$levelData = $nbt->read(substr($rawLevelData, 8));
|
||||
}catch(\UnexpectedValueException $e){
|
||||
throw new LevelException("Invalid level.dat (" . $e->getMessage() . ")", 0, $e);
|
||||
}
|
||||
if($levelData instanceof CompoundTag){
|
||||
$this->levelData = $levelData;
|
||||
}else{
|
||||
@ -179,8 +183,6 @@ class LevelDB extends BaseLevelProvider{
|
||||
if(!$this->levelData->hasTag("generatorOptions", StringTag::class)){
|
||||
$this->levelData->setString("generatorOptions", "");
|
||||
}
|
||||
|
||||
$db->close();
|
||||
}
|
||||
|
||||
public static function getProviderName() : string{
|
||||
@ -265,9 +267,6 @@ class LevelDB extends BaseLevelProvider{
|
||||
$db->put(self::ENTRY_FLAT_WORLD_LAYERS, $out); //Add vanilla flatworld layers to allow terrain generation by MCPE to continue seamlessly
|
||||
}
|
||||
}
|
||||
|
||||
$db->close();
|
||||
|
||||
}
|
||||
|
||||
public function saveLevelData(){
|
||||
@ -364,7 +363,9 @@ class LevelDB extends BaseLevelProvider{
|
||||
if(($maps2d = $this->db->get($index . self::TAG_DATA_2D)) !== false){
|
||||
$binaryStream->setBuffer($maps2d, 0);
|
||||
|
||||
$heightMap = array_values(unpack("v*", $binaryStream->get(512)));
|
||||
/** @var int[] $unpackedHeightMap */
|
||||
$unpackedHeightMap = unpack("v*", $binaryStream->get(512)); //unpack() will never fail here
|
||||
$heightMap = array_values($unpackedHeightMap);
|
||||
$biomeIds = $binaryStream->get(256);
|
||||
}
|
||||
break;
|
||||
@ -407,8 +408,13 @@ class LevelDB extends BaseLevelProvider{
|
||||
$subChunks[$yy] = new SubChunk($ids, $data, $skyLight, $blockLight);
|
||||
}
|
||||
|
||||
$heightMap = array_values(unpack("C*", $binaryStream->get(256)));
|
||||
$biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", $binaryStream->get(1024))));
|
||||
/** @var int[] $unpackedHeightMap */
|
||||
$unpackedHeightMap = unpack("C*", $binaryStream->get(256)); //unpack() will never fail here, but static analysers don't know that
|
||||
$heightMap = array_values($unpackedHeightMap);
|
||||
|
||||
/** @var int[] $unpackedBiomeIds */
|
||||
$unpackedBiomeIds = unpack("N*", $binaryStream->get(1024)); //nor here
|
||||
$biomeIds = ChunkUtils::convertBiomeColors(array_values($unpackedBiomeIds));
|
||||
break;
|
||||
default:
|
||||
//TODO: set chunks read-only so the version on disk doesn't get overwritten
|
||||
@ -544,6 +550,6 @@ class LevelDB extends BaseLevelProvider{
|
||||
}
|
||||
|
||||
public function close(){
|
||||
$this->db->close();
|
||||
unset($this->db);
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +182,9 @@ class McRegion extends BaseLevelProvider{
|
||||
|
||||
$heightMap = [];
|
||||
if($chunk->hasTag("HeightMap", ByteArrayTag::class)){
|
||||
$heightMap = array_values(unpack("C*", $chunk->getByteArray("HeightMap")));
|
||||
/** @var int[] $unpackedHeightMap */
|
||||
$unpackedHeightMap = unpack("C*", $chunk->getByteArray("HeightMap")); //unpack() will never fail here
|
||||
$heightMap = array_values($unpackedHeightMap);
|
||||
}elseif($chunk->hasTag("HeightMap", IntArrayTag::class)){
|
||||
$heightMap = $chunk->getIntArray("HeightMap"); #blameshoghicp
|
||||
}
|
||||
@ -245,11 +247,13 @@ class McRegion extends BaseLevelProvider{
|
||||
|
||||
if($isValid){
|
||||
$files = array_filter(scandir($path . "/region/", SCANDIR_SORT_NONE), function(string $file) : bool{
|
||||
return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file
|
||||
$extPos = strrpos($file, ".");
|
||||
return $extPos !== false && substr($file, $extPos + 1, 2) === "mc"; //region file
|
||||
});
|
||||
|
||||
foreach($files as $f){
|
||||
if(substr($f, strrpos($f, ".") + 1) !== static::REGION_FILE_EXTENSION){
|
||||
$extPos = strrpos($f, ".");
|
||||
if($extPos !== false && substr($f, $extPos + 1) !== static::REGION_FILE_EXTENSION){
|
||||
$isValid = false;
|
||||
break;
|
||||
}
|
||||
@ -389,6 +393,9 @@ class McRegion extends BaseLevelProvider{
|
||||
self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ);
|
||||
assert(is_int($regionX) and is_int($regionZ));
|
||||
|
||||
if(!file_exists($this->pathToRegion($regionX, $regionZ))){
|
||||
return null;
|
||||
}
|
||||
$this->loadRegion($regionX, $regionZ);
|
||||
|
||||
$chunkData = $this->getRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f);
|
||||
|
@ -69,11 +69,14 @@ final class RegionGarbageMap{
|
||||
/** @var RegionLocationTableEntry|null $prevEntry */
|
||||
$prevEntry = null;
|
||||
foreach($usedMap as $firstSector => $entry){
|
||||
$expectedStart = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
|
||||
$actualStart = $entry->getFirstSector();
|
||||
if($expectedStart < $actualStart){
|
||||
$prevEndPlusOne = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
|
||||
$currentStart = $entry->getFirstSector();
|
||||
if($prevEndPlusOne < $currentStart){
|
||||
//found a gap in the table
|
||||
$garbageMap[$expectedStart] = new RegionLocationTableEntry($expectedStart, $actualStart - $expectedStart, 0);
|
||||
$garbageMap[$prevEndPlusOne] = new RegionLocationTableEntry($prevEndPlusOne, $currentStart - $prevEndPlusOne, 0);
|
||||
}elseif($prevEndPlusOne > $currentStart){
|
||||
//current entry starts inside the previous. This would be a bug since RegionLoader should prevent this
|
||||
throw new AssumptionFailedError("Overlapping entries detected");
|
||||
}
|
||||
$prevEntry = $entry;
|
||||
}
|
||||
|
@ -27,12 +27,13 @@ use pocketmine\level\format\ChunkException;
|
||||
use pocketmine\level\format\io\exception\CorruptedChunkException;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use function assert;
|
||||
use function ceil;
|
||||
use function chr;
|
||||
use function clearstatcache;
|
||||
use function fclose;
|
||||
use function feof;
|
||||
use function file_exists;
|
||||
use function filesize;
|
||||
use function fopen;
|
||||
@ -43,14 +44,12 @@ use function fwrite;
|
||||
use function is_resource;
|
||||
use function ksort;
|
||||
use function max;
|
||||
use function ord;
|
||||
use function pack;
|
||||
use function str_pad;
|
||||
use function str_repeat;
|
||||
use function stream_set_read_buffer;
|
||||
use function stream_set_write_buffer;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function time;
|
||||
use function touch;
|
||||
use function unpack;
|
||||
@ -99,6 +98,7 @@ class RegionLoader{
|
||||
* @throws CorruptedRegionException
|
||||
*/
|
||||
public function open(){
|
||||
clearstatcache(false, $this->filePath);
|
||||
$exists = file_exists($this->filePath);
|
||||
if(!$exists){
|
||||
touch($this->filePath);
|
||||
@ -146,37 +146,34 @@ class RegionLoader{
|
||||
|
||||
fseek($this->filePointer, $this->locationTable[$index]->getFirstSector() << 12);
|
||||
|
||||
$prefix = fread($this->filePointer, 4);
|
||||
if($prefix === false or strlen($prefix) !== 4){
|
||||
throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)");
|
||||
}
|
||||
$length = Binary::readInt($prefix);
|
||||
/*
|
||||
* this might cause us to read some junk, but under normal circumstances it won't be any more than 4096 bytes wasted.
|
||||
* doing this in a single call is faster than making two seeks and reads to fetch the chunk.
|
||||
* this relies on the assumption that the end of the file is always padded to a multiple of 4096 bytes.
|
||||
*/
|
||||
$bytesToRead = $this->locationTable[$index]->getSectorCount() << 12;
|
||||
$payload = fread($this->filePointer, $bytesToRead);
|
||||
|
||||
if($length <= 0){ //TODO: if we reached here, the locationTable probably needs updating
|
||||
return null;
|
||||
}
|
||||
if($length > self::MAX_SECTOR_LENGTH){ //corrupted
|
||||
throw new CorruptedChunkException("Length for chunk x=$x,z=$z ($length) is larger than maximum " . self::MAX_SECTOR_LENGTH);
|
||||
if($payload === false || strlen($payload) !== $bytesToRead){
|
||||
throw new CorruptedChunkException("Corrupted chunk detected (unexpected EOF, truncated or non-padded chunk found)");
|
||||
}
|
||||
$stream = new BinaryStream($payload);
|
||||
|
||||
if($length > ($this->locationTable[$index]->getSectorCount() << 12)){ //Invalid chunk, bigger than defined number of sectors
|
||||
MainLogger::getLogger()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index]->getSectorCount() << 12) . " sectors, got $length sectors)");
|
||||
$old = $this->locationTable[$index];
|
||||
$this->locationTable[$index] = new RegionLocationTableEntry($old->getFirstSector(), $length >> 12, time());
|
||||
$this->writeLocationIndex($index);
|
||||
}
|
||||
try{
|
||||
$length = $stream->getInt();
|
||||
if($length <= 0){ //TODO: if we reached here, the locationTable probably needs updating
|
||||
return null;
|
||||
}
|
||||
|
||||
$chunkData = fread($this->filePointer, $length);
|
||||
if($chunkData === false or strlen($chunkData) !== $length){
|
||||
throw new CorruptedChunkException("Corrupted chunk detected (unexpected end of file reading chunk data)");
|
||||
}
|
||||
$compression = $stream->getByte();
|
||||
if($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
|
||||
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
|
||||
}
|
||||
|
||||
$compression = ord($chunkData[0]);
|
||||
if($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
|
||||
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
|
||||
return $stream->get($length - 1); //length prefix includes the compression byte
|
||||
}catch(BinaryDataException $e){
|
||||
throw new CorruptedChunkException("Corrupted chunk detected: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return substr($chunkData, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,6 +183,23 @@ class RegionLoader{
|
||||
return $this->isChunkGenerated(self::getChunkOffset($x, $z));
|
||||
}
|
||||
|
||||
private function disposeGarbageArea(RegionLocationTableEntry $oldLocation) : void{
|
||||
/* release the area containing the old copy to the garbage pool */
|
||||
$this->garbageTable->add($oldLocation);
|
||||
|
||||
$endGarbage = $this->garbageTable->end();
|
||||
$nextSector = $this->nextSector;
|
||||
for(; $endGarbage !== null and $endGarbage->getLastSector() + 1 === $nextSector; $endGarbage = $this->garbageTable->end()){
|
||||
$nextSector = $endGarbage->getFirstSector();
|
||||
$this->garbageTable->remove($endGarbage);
|
||||
}
|
||||
|
||||
if($nextSector !== $this->nextSector){
|
||||
$this->nextSector = $nextSector;
|
||||
ftruncate($this->filePointer, $this->nextSector << 12);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws ChunkException
|
||||
@ -230,20 +244,7 @@ class RegionLoader{
|
||||
$this->writeLocationIndex($index);
|
||||
|
||||
if($oldLocation !== null){
|
||||
/* release the area containing the old copy to the garbage pool */
|
||||
$this->garbageTable->add($oldLocation);
|
||||
|
||||
$endGarbage = $this->garbageTable->end();
|
||||
$nextSector = $this->nextSector;
|
||||
for(; $endGarbage !== null and $endGarbage->getLastSector() + 1 === $nextSector; $endGarbage = $this->garbageTable->end()){
|
||||
$nextSector = $endGarbage->getFirstSector();
|
||||
$this->garbageTable->remove($endGarbage);
|
||||
}
|
||||
|
||||
if($nextSector !== $this->nextSector){
|
||||
$this->nextSector = $nextSector;
|
||||
ftruncate($this->filePointer, $this->nextSector << 12);
|
||||
}
|
||||
$this->disposeGarbageArea($oldLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,8 +254,12 @@ class RegionLoader{
|
||||
*/
|
||||
public function removeChunk(int $x, int $z){
|
||||
$index = self::getChunkOffset($x, $z);
|
||||
$oldLocation = $this->locationTable[$index];
|
||||
$this->locationTable[$index] = null;
|
||||
$this->writeLocationIndex($index);
|
||||
if($oldLocation !== null){
|
||||
$this->disposeGarbageArea($oldLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,6 +308,7 @@ class RegionLoader{
|
||||
throw new CorruptedRegionException("Corrupted region header (unexpected end of file)");
|
||||
}
|
||||
|
||||
/** @var int[] $data */
|
||||
$data = unpack("N*", $headerRaw);
|
||||
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
@ -335,6 +341,8 @@ class RegionLoader{
|
||||
/** @var int[] $usedOffsets */
|
||||
$usedOffsets = [];
|
||||
|
||||
$fileSize = filesize($this->filePath);
|
||||
if($fileSize === false) throw new AssumptionFailedError("filesize() should not return false here");
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$entry = $this->locationTable[$i];
|
||||
if($entry === null){
|
||||
@ -347,8 +355,7 @@ class RegionLoader{
|
||||
|
||||
//TODO: more validity checks
|
||||
|
||||
fseek($this->filePointer, $fileOffset);
|
||||
if(feof($this->filePointer)){
|
||||
if($fileOffset >= $fileSize){
|
||||
throw new CorruptedRegionException("Region file location offset x=$x,z=$z points to invalid file location $fileOffset");
|
||||
}
|
||||
if(isset($usedOffsets[$offset])){
|
||||
@ -401,6 +408,7 @@ class RegionLoader{
|
||||
fwrite($this->filePointer, Binary::writeInt($entry !== null ? ($entry->getFirstSector() << 8) | $entry->getSectorCount() : 0), 4);
|
||||
fseek($this->filePointer, 4096 + ($index << 2));
|
||||
fwrite($this->filePointer, Binary::writeInt($entry !== null ? $entry->getTimestamp() : 0), 4);
|
||||
clearstatcache(false, $this->filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\level\generator;
|
||||
|
||||
use pocketmine\block\BlockFactory;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\level\biome\Biome;
|
||||
use pocketmine\level\Level;
|
||||
use pocketmine\level\SimpleChunkManager;
|
||||
@ -34,7 +35,10 @@ use function unserialize;
|
||||
|
||||
class GeneratorRegisterTask extends AsyncTask{
|
||||
|
||||
/** @var string */
|
||||
/**
|
||||
* @var string
|
||||
* @phpstan-var class-string<Generator>
|
||||
*/
|
||||
public $generatorClass;
|
||||
/** @var string */
|
||||
public $settings;
|
||||
@ -47,6 +51,7 @@ class GeneratorRegisterTask extends AsyncTask{
|
||||
|
||||
/**
|
||||
* @param mixed[] $generatorSettings
|
||||
* @phpstan-param class-string<Generator> $generatorClass
|
||||
* @phpstan-param array<string, mixed> $generatorSettings
|
||||
*/
|
||||
public function __construct(Level $level, string $generatorClass, array $generatorSettings = []){
|
||||
@ -59,6 +64,7 @@ class GeneratorRegisterTask extends AsyncTask{
|
||||
|
||||
public function onRun(){
|
||||
BlockFactory::init();
|
||||
ItemFactory::init();
|
||||
Biome::init();
|
||||
$manager = new SimpleChunkManager($this->seed, $this->worldHeight);
|
||||
$this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager);
|
||||
|
@ -29,7 +29,10 @@ namespace pocketmine\metadata;
|
||||
use pocketmine\plugin\Plugin;
|
||||
|
||||
abstract class MetadataStore{
|
||||
/** @var \SplObjectStorage[]|MetadataValue[][] */
|
||||
/**
|
||||
* @var \SplObjectStorage[]|MetadataValue[][]
|
||||
* @phpstan-var array<string, \SplObjectStorage<Plugin, MetadataValue>>
|
||||
*/
|
||||
private $metadataMap;
|
||||
|
||||
/**
|
||||
@ -41,6 +44,7 @@ abstract class MetadataStore{
|
||||
$owningPlugin = $newMetadataValue->getOwningPlugin();
|
||||
|
||||
if(!isset($this->metadataMap[$key])){
|
||||
/** @phpstan-var \SplObjectStorage<Plugin, MetadataValue> $entry */
|
||||
$entry = new \SplObjectStorage();
|
||||
$this->metadataMap[$key] = $entry;
|
||||
}else{
|
||||
@ -92,7 +96,6 @@ abstract class MetadataStore{
|
||||
* @return void
|
||||
*/
|
||||
public function invalidateAll(Plugin $owningPlugin){
|
||||
/** @var \SplObjectStorage|MetadataValue[] $values */
|
||||
foreach($this->metadataMap as $values){
|
||||
if(isset($values[$owningPlugin])){
|
||||
$values[$owningPlugin]->invalidate();
|
||||
|
@ -35,6 +35,9 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\NetworkLittleEndianNBTStream;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\NamedTag;
|
||||
use pocketmine\network\mcpe\convert\ItemTranslator;
|
||||
use pocketmine\network\mcpe\convert\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\types\CommandOriginData;
|
||||
use pocketmine\network\mcpe\protocol\types\EntityLink;
|
||||
use pocketmine\network\mcpe\protocol\types\GameRuleType;
|
||||
@ -47,6 +50,7 @@ use pocketmine\network\mcpe\protocol\types\StructureEditorData;
|
||||
use pocketmine\network\mcpe\protocol\types\StructureSettings;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\UUID;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function strlen;
|
||||
|
||||
@ -83,6 +87,7 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
|
||||
public function getSkin() : SkinData{
|
||||
$skinId = $this->getString();
|
||||
$skinPlayFabId = $this->getString();
|
||||
$skinResourcePatch = $this->getString();
|
||||
$skinData = $this->getSkinImage();
|
||||
$animationCount = $this->getLInt();
|
||||
@ -91,7 +96,8 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$skinImage = $this->getSkinImage();
|
||||
$animationType = $this->getLInt();
|
||||
$animationFrames = $this->getLFloat();
|
||||
$animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames);
|
||||
$expressionType = $this->getLInt();
|
||||
$animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames, $expressionType);
|
||||
}
|
||||
$capeData = $this->getSkinImage();
|
||||
$geometryData = $this->getString();
|
||||
@ -128,7 +134,7 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
);
|
||||
}
|
||||
|
||||
return new SkinData($skinId, $skinResourcePatch, $skinData, $animations, $capeData, $geometryData, $animationData, $premium, $persona, $capeOnClassic, $capeId, $fullSkinId, $armSize, $skinColor, $personaPieces, $pieceTintColors);
|
||||
return new SkinData($skinId, $skinPlayFabId, $skinResourcePatch, $skinData, $animations, $capeData, $geometryData, $animationData, $premium, $persona, $capeOnClassic, $capeId, $fullSkinId, $armSize, $skinColor, $personaPieces, $pieceTintColors);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,6 +142,7 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
*/
|
||||
public function putSkin(SkinData $skin){
|
||||
$this->putString($skin->getSkinId());
|
||||
$this->putString($skin->getPlayFabId());
|
||||
$this->putString($skin->getResourcePatch());
|
||||
$this->putSkinImage($skin->getSkinImage());
|
||||
$this->putLInt(count($skin->getAnimations()));
|
||||
@ -143,6 +150,7 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->putSkinImage($animation->getImage());
|
||||
$this->putLInt($animation->getType());
|
||||
$this->putLFloat($animation->getFrames());
|
||||
$this->putLInt($animation->getExpressionType());
|
||||
}
|
||||
$this->putSkinImage($skin->getCapeImage());
|
||||
$this->putString($skin->getGeometryData());
|
||||
@ -186,15 +194,17 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
}
|
||||
|
||||
public function getSlot() : Item{
|
||||
$id = $this->getVarInt();
|
||||
if($id === 0){
|
||||
$netId = $this->getVarInt();
|
||||
if($netId === 0){
|
||||
return ItemFactory::get(0, 0, 0);
|
||||
}
|
||||
|
||||
$auxValue = $this->getVarInt();
|
||||
$data = $auxValue >> 8;
|
||||
$netData = $auxValue >> 8;
|
||||
$cnt = $auxValue & 0xff;
|
||||
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($netId, $netData);
|
||||
|
||||
$nbtLen = $this->getLShort();
|
||||
|
||||
/** @var CompoundTag|null $nbt */
|
||||
@ -223,26 +233,23 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->getString();
|
||||
}
|
||||
|
||||
if($id === ItemIds::SHIELD){
|
||||
if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){
|
||||
$this->getVarLong(); //"blocking tick" (ffs mojang)
|
||||
}
|
||||
if($nbt !== null){
|
||||
if($nbt->hasTag(self::DAMAGE_TAG, IntTag::class)){
|
||||
$data = $nbt->getInt(self::DAMAGE_TAG);
|
||||
$meta = $nbt->getInt(self::DAMAGE_TAG);
|
||||
$nbt->removeTag(self::DAMAGE_TAG);
|
||||
if($nbt->count() === 0){
|
||||
if(($conflicted = $nbt->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){
|
||||
$nbt->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
|
||||
$conflicted->setName(self::DAMAGE_TAG);
|
||||
$nbt->setTag($conflicted);
|
||||
}elseif($nbt->count() === 0){
|
||||
$nbt = null;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if(($conflicted = $nbt->getTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION)) !== null){
|
||||
$nbt->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
|
||||
$conflicted->setName(self::DAMAGE_TAG);
|
||||
$nbt->setTag($conflicted);
|
||||
}
|
||||
}
|
||||
end:
|
||||
return ItemFactory::get($id, $data, $cnt, $nbt);
|
||||
return ItemFactory::get($id, $meta, $cnt, $nbt);
|
||||
}
|
||||
|
||||
public function putSlot(Item $item) : void{
|
||||
@ -252,8 +259,10 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->putVarInt($item->getId());
|
||||
$auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount();
|
||||
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage());
|
||||
|
||||
$this->putVarInt($netId);
|
||||
$auxValue = (($netData & 0x7fff) << 8) | $item->getCount();
|
||||
$this->putVarInt($auxValue);
|
||||
|
||||
$nbt = null;
|
||||
@ -284,20 +293,18 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->putVarInt(0); //CanPlaceOn entry count (TODO)
|
||||
$this->putVarInt(0); //CanDestroy entry count (TODO)
|
||||
|
||||
if($item->getId() === ItemIds::SHIELD){
|
||||
if($netId === ItemTypeDictionary::getInstance()->fromStringId("minecraft:shield")){
|
||||
$this->putVarLong(0); //"blocking tick" (ffs mojang)
|
||||
}
|
||||
}
|
||||
|
||||
public function getRecipeIngredient() : Item{
|
||||
$id = $this->getVarInt();
|
||||
if($id === 0){
|
||||
$netId = $this->getVarInt();
|
||||
if($netId === 0){
|
||||
return ItemFactory::get(ItemIds::AIR, 0, 0);
|
||||
}
|
||||
$meta = $this->getVarInt();
|
||||
if($meta === 0x7fff){
|
||||
$meta = -1;
|
||||
}
|
||||
$netData = $this->getVarInt();
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($netId, $netData);
|
||||
$count = $this->getVarInt();
|
||||
return ItemFactory::get($id, $meta, $count);
|
||||
}
|
||||
@ -306,8 +313,14 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
if($item->isNull()){
|
||||
$this->putVarInt(0);
|
||||
}else{
|
||||
$this->putVarInt($item->getId());
|
||||
$this->putVarInt($item->getDamage() & 0x7fff);
|
||||
if($item->hasAnyDamageValue()){
|
||||
[$netId, ] = ItemTranslator::getInstance()->toNetworkId($item->getId(), 0);
|
||||
$netData = 0x7fff;
|
||||
}else{
|
||||
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($item->getId(), $item->getDamage());
|
||||
}
|
||||
$this->putVarInt($netId);
|
||||
$this->putVarInt($netData);
|
||||
$this->putVarInt($item->getCount());
|
||||
}
|
||||
}
|
||||
@ -750,6 +763,25 @@ class NetworkBinaryStream extends BinaryStream{
|
||||
$this->putVarInt($structureEditorData->structureRedstoneSaveMove);
|
||||
}
|
||||
|
||||
public function getNbtRoot() : NamedTag{
|
||||
$offset = $this->getOffset();
|
||||
try{
|
||||
$result = (new NetworkLittleEndianNBTStream())->read($this->getBuffer(), false, $offset, 512);
|
||||
assert($result instanceof NamedTag, "doMultiple is false so we should definitely have a NamedTag here");
|
||||
return $result;
|
||||
}finally{
|
||||
$this->setOffset($offset);
|
||||
}
|
||||
}
|
||||
|
||||
public function getNbtCompoundRoot() : CompoundTag{
|
||||
$root = $this->getNbtRoot();
|
||||
if(!($root instanceof CompoundTag)){
|
||||
throw new \UnexpectedValueException("Expected TAG_Compound root");
|
||||
}
|
||||
return $root;
|
||||
}
|
||||
|
||||
public function readGenericTypeNetworkId() : int{
|
||||
return $this->getVarInt();
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorFallPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddBehaviorTreePacket;
|
||||
@ -33,6 +32,7 @@ use pocketmine\network\mcpe\protocol\AddItemActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPaintingPacket;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AnimateEntityPacket;
|
||||
use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\AnvilDamagePacket;
|
||||
use pocketmine\network\mcpe\protocol\AutomationClientConnectPacket;
|
||||
@ -45,8 +45,10 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\BookEditPacket;
|
||||
use pocketmine\network\mcpe\protocol\BossEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\CameraPacket;
|
||||
use pocketmine\network\mcpe\protocol\CameraShakePacket;
|
||||
use pocketmine\network\mcpe\protocol\ChangeDimensionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundDebugRendererPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundMapItemDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientCacheBlobStatusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientCacheMissResponsePacket;
|
||||
@ -60,6 +62,7 @@ use pocketmine\network\mcpe\protocol\CompletedUsingItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CorrectPlayerMovePredictionPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
|
||||
@ -70,6 +73,7 @@ use pocketmine\network\mcpe\protocol\EducationSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\EmoteListPacket;
|
||||
use pocketmine\network\mcpe\protocol\EmotePacket;
|
||||
use pocketmine\network\mcpe\protocol\EventPacket;
|
||||
use pocketmine\network\mcpe\protocol\FilterTextPacket;
|
||||
use pocketmine\network\mcpe\protocol\GameRulesChangedPacket;
|
||||
use pocketmine\network\mcpe\protocol\GuiDataPickItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\HurtArmorPacket;
|
||||
@ -77,6 +81,7 @@ use pocketmine\network\mcpe\protocol\InteractPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventorySlotPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemComponentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
|
||||
@ -96,6 +101,7 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\MotionPredictionHintsPacket;
|
||||
use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket;
|
||||
use pocketmine\network\mcpe\protocol\MoveActorDeltaPacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
@ -111,6 +117,7 @@ use pocketmine\network\mcpe\protocol\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerArmorDamagePacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerEnchantOptionsPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerFogPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
@ -171,7 +178,6 @@ use pocketmine\network\mcpe\protocol\TickSyncPacket;
|
||||
use pocketmine\network\mcpe\protocol\TransferPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPropertiesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockSyncedPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateEquipPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdatePlayerGameTypePacket;
|
||||
@ -325,10 +331,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleActorFall(ActorFallPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleHurtArmor(HurtArmorPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -705,10 +707,6 @@ abstract class NetworkSession{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleUpdateBlockProperties(UpdateBlockPropertiesPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleClientCacheBlobStatus(ClientCacheBlobStatusPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
@ -796,4 +794,36 @@ abstract class NetworkSession{
|
||||
public function handlePacketViolationWarning(PacketViolationWarningPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleMotionPredictionHints(MotionPredictionHintsPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleAnimateEntity(AnimateEntityPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCameraShake(CameraShakePacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handlePlayerFog(PlayerFogPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleCorrectPlayerMovePrediction(CorrectPlayerMovePredictionPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleItemComponent(ItemComponentPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleFilterText(FilterTextPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handleClientboundDebugRenderer(ClientboundDebugRendererPacket $packet) : bool{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\network\mcpe;
|
||||
|
||||
use pocketmine\event\server\DataPacketReceiveEvent;
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorFallPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
@ -174,10 +173,6 @@ class PlayerNetworkSessionAdapter extends NetworkSession{
|
||||
return $this->player->handlePlayerAction($packet);
|
||||
}
|
||||
|
||||
public function handleActorFall(ActorFallPacket $packet) : bool{
|
||||
return true; //Not used
|
||||
}
|
||||
|
||||
public function handleAnimate(AnimatePacket $packet) : bool{
|
||||
return $this->player->handleAnimate($packet);
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
|
||||
$info->getMaxPlayerCount(),
|
||||
$this->rakLib->getServerId(),
|
||||
$this->server->getName(),
|
||||
Server::getGamemodeName($this->server->getGamemode())
|
||||
Server::getGamemodeName(Player::getClientFriendlyGamemode($this->server->getGamemode()))
|
||||
]) . ";"
|
||||
);
|
||||
}
|
||||
|
183
src/pocketmine/network/mcpe/convert/ItemTranslator.php
Normal file
183
src/pocketmine/network/mcpe/convert/ItemTranslator.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?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\convert;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use function array_key_exists;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
|
||||
/**
|
||||
* This class handles translation between network item ID+metadata to PocketMine-MP internal ID+metadata and vice versa.
|
||||
*/
|
||||
final class ItemTranslator{
|
||||
use SingletonTrait;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private $simpleCoreToNetMapping = [];
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private $simpleNetToCoreMapping = [];
|
||||
|
||||
/**
|
||||
* runtimeId = array[internalId][metadata]
|
||||
* @var int[][]
|
||||
* @phpstan-var array<int, array<int, int>>
|
||||
*/
|
||||
private $complexCoreToNetMapping = [];
|
||||
/**
|
||||
* [internalId, metadata] = array[runtimeId]
|
||||
* @var int[][]
|
||||
* @phpstan-var array<int, array{int, int}>
|
||||
*/
|
||||
private $complexNetToCoreMapping = [];
|
||||
|
||||
private static function make() : self{
|
||||
$data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/r16_to_current_item_map.json');
|
||||
if($data === false) throw new AssumptionFailedError("Missing required resource file");
|
||||
$json = json_decode($data, true);
|
||||
if(!is_array($json) or !isset($json["simple"], $json["complex"]) || !is_array($json["simple"]) || !is_array($json["complex"])){
|
||||
throw new AssumptionFailedError("Invalid item table format");
|
||||
}
|
||||
|
||||
$legacyStringToIntMapRaw = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/item_id_map.json');
|
||||
if($legacyStringToIntMapRaw === false){
|
||||
throw new AssumptionFailedError("Missing required resource file");
|
||||
}
|
||||
$legacyStringToIntMap = json_decode($legacyStringToIntMapRaw, true);
|
||||
if(!is_array($legacyStringToIntMap)){
|
||||
throw new AssumptionFailedError("Invalid mapping table format");
|
||||
}
|
||||
|
||||
/** @phpstan-var array<string, int> $simpleMappings */
|
||||
$simpleMappings = [];
|
||||
foreach($json["simple"] as $oldId => $newId){
|
||||
if(!is_string($oldId) || !is_string($newId)){
|
||||
throw new AssumptionFailedError("Invalid item table format");
|
||||
}
|
||||
$simpleMappings[$newId] = $legacyStringToIntMap[$oldId];
|
||||
}
|
||||
foreach($legacyStringToIntMap as $stringId => $intId){
|
||||
if(isset($simpleMappings[$stringId])){
|
||||
throw new \UnexpectedValueException("Old ID $stringId collides with new ID");
|
||||
}
|
||||
$simpleMappings[$stringId] = $intId;
|
||||
}
|
||||
|
||||
/** @phpstan-var array<string, array{int, int}> $complexMappings */
|
||||
$complexMappings = [];
|
||||
foreach($json["complex"] as $oldId => $map){
|
||||
if(!is_string($oldId) || !is_array($map)){
|
||||
throw new AssumptionFailedError("Invalid item table format");
|
||||
}
|
||||
foreach($map as $meta => $newId){
|
||||
if(!is_numeric($meta) || !is_string($newId)){
|
||||
throw new AssumptionFailedError("Invalid item table format");
|
||||
}
|
||||
$complexMappings[$newId] = [$legacyStringToIntMap[$oldId], (int) $meta];
|
||||
}
|
||||
}
|
||||
|
||||
return new self(ItemTypeDictionary::getInstance(), $simpleMappings, $complexMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $simpleMappings
|
||||
* @param int[][] $complexMappings
|
||||
* @phpstan-param array<string, int> $simpleMappings
|
||||
* @phpstan-param array<string, array<int, int>> $complexMappings
|
||||
*/
|
||||
public function __construct(ItemTypeDictionary $dictionary, array $simpleMappings, array $complexMappings){
|
||||
foreach($dictionary->getEntries() as $entry){
|
||||
$stringId = $entry->getStringId();
|
||||
$netId = $entry->getNumericId();
|
||||
if(isset($complexMappings[$stringId])){
|
||||
[$id, $meta] = $complexMappings[$stringId];
|
||||
$this->complexCoreToNetMapping[$id][$meta] = $netId;
|
||||
$this->complexNetToCoreMapping[$netId] = [$id, $meta];
|
||||
}elseif(isset($simpleMappings[$stringId])){
|
||||
$this->simpleCoreToNetMapping[$simpleMappings[$stringId]] = $netId;
|
||||
$this->simpleNetToCoreMapping[$netId] = $simpleMappings[$stringId];
|
||||
}elseif($stringId !== "minecraft:unknown"){
|
||||
throw new \InvalidArgumentException("Unmapped entry " . $stringId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
*/
|
||||
public function toNetworkId(int $internalId, int $internalMeta) : array{
|
||||
if(isset($this->complexCoreToNetMapping[$internalId][$internalMeta])){
|
||||
return [$this->complexCoreToNetMapping[$internalId][$internalMeta], 0];
|
||||
}
|
||||
if(array_key_exists($internalId, $this->simpleCoreToNetMapping)){
|
||||
return [$this->simpleCoreToNetMapping[$internalId], $internalMeta];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Unmapped ID/metadata combination $internalId:$internalMeta");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
*/
|
||||
public function fromNetworkId(int $networkId, int $networkMeta, ?bool &$isComplexMapping = null) : array{
|
||||
if(isset($this->complexNetToCoreMapping[$networkId])){
|
||||
if($networkMeta !== 0){
|
||||
throw new \UnexpectedValueException("Unexpected non-zero network meta on complex item mapping");
|
||||
}
|
||||
$isComplexMapping = true;
|
||||
return $this->complexNetToCoreMapping[$networkId];
|
||||
}
|
||||
$isComplexMapping = false;
|
||||
if(isset($this->simpleNetToCoreMapping[$networkId])){
|
||||
return [$this->simpleNetToCoreMapping[$networkId], $networkMeta];
|
||||
}
|
||||
throw new \UnexpectedValueException("Unmapped network ID/metadata combination $networkId:$networkMeta");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
*/
|
||||
public function fromNetworkIdWithWildcardHandling(int $networkId, int $networkMeta) : array{
|
||||
$isComplexMapping = false;
|
||||
if($networkMeta !== 0x7fff){
|
||||
return $this->fromNetworkId($networkId, $networkMeta);
|
||||
}
|
||||
[$id, $meta] = $this->fromNetworkId($networkId, 0, $isComplexMapping);
|
||||
return [$id, $isComplexMapping ? $meta : -1];
|
||||
}
|
||||
}
|
106
src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php
Normal file
106
src/pocketmine/network/mcpe/convert/ItemTypeDictionary.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use function array_key_exists;
|
||||
use function file_get_contents;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
|
||||
final class ItemTypeDictionary{
|
||||
use SingletonTrait;
|
||||
|
||||
/**
|
||||
* @var ItemTypeEntry[]
|
||||
* @phpstan-var list<ItemTypeEntry>
|
||||
*/
|
||||
private $itemTypes;
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<int, string>
|
||||
*/
|
||||
private $intToStringIdMap = [];
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<string, int>
|
||||
*/
|
||||
private $stringToIntMap = [];
|
||||
|
||||
private static function make() : self{
|
||||
$data = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/required_item_list.json');
|
||||
if($data === false) throw new AssumptionFailedError("Missing required resource file");
|
||||
$table = json_decode($data, true);
|
||||
if(!is_array($table)){
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
}
|
||||
|
||||
$params = [];
|
||||
foreach($table as $name => $entry){
|
||||
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
}
|
||||
$params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]);
|
||||
}
|
||||
return new self($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ItemTypeEntry[] $itemTypes
|
||||
*/
|
||||
public function __construct(array $itemTypes){
|
||||
$this->itemTypes = $itemTypes;
|
||||
foreach($this->itemTypes as $type){
|
||||
$this->stringToIntMap[$type->getStringId()] = $type->getNumericId();
|
||||
$this->intToStringIdMap[$type->getNumericId()] = $type->getStringId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ItemTypeEntry[]
|
||||
* @phpstan-return list<ItemTypeEntry>
|
||||
*/
|
||||
public function getEntries() : array{
|
||||
return $this->itemTypes;
|
||||
}
|
||||
|
||||
public function fromStringId(string $stringId) : int{
|
||||
if(!array_key_exists($stringId, $this->stringToIntMap)){
|
||||
throw new \InvalidArgumentException("Unmapped string ID \"$stringId\"");
|
||||
}
|
||||
return $this->stringToIntMap[$stringId];
|
||||
}
|
||||
|
||||
public function fromIntId(int $intId) : string{
|
||||
if(!array_key_exists($intId, $this->intToStringIdMap)){
|
||||
throw new \InvalidArgumentException("Unmapped int ID $intId");
|
||||
}
|
||||
return $this->intToStringIdMap[$intId];
|
||||
}
|
||||
}
|
@ -27,14 +27,10 @@ use pocketmine\block\BlockIds;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\NetworkLittleEndianNBTStream;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\network\mcpe\NetworkBinaryStream;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function file_get_contents;
|
||||
use function getmypid;
|
||||
use function json_decode;
|
||||
use function mt_rand;
|
||||
use function mt_srand;
|
||||
use function shuffle;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -53,14 +49,16 @@ final class RuntimeBlockMapping{
|
||||
}
|
||||
|
||||
public static function init() : void{
|
||||
$tag = (new NetworkLittleEndianNBTStream())->read(file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/required_block_states.nbt"));
|
||||
if(!($tag instanceof ListTag) or $tag->getTagType() !== NBT::TAG_Compound){ //this is a little redundant currently, but good for auto complete and makes phpstan happy
|
||||
throw new \RuntimeException("Invalid blockstates table, expected TAG_List<TAG_Compound> root");
|
||||
$canonicalBlockStatesFile = file_get_contents(\pocketmine\RESOURCE_PATH . "vanilla/canonical_block_states.nbt");
|
||||
if($canonicalBlockStatesFile === false){
|
||||
throw new AssumptionFailedError("Missing required resource file");
|
||||
}
|
||||
|
||||
/** @var CompoundTag[] $list */
|
||||
$list = $tag->getValue();
|
||||
self::$bedrockKnownStates = self::randomizeTable($list);
|
||||
$stream = new NetworkBinaryStream($canonicalBlockStatesFile);
|
||||
$list = [];
|
||||
while(!$stream->feof()){
|
||||
$list[] = $stream->getNbtCompoundRoot();
|
||||
}
|
||||
self::$bedrockKnownStates = $list;
|
||||
|
||||
self::setupLegacyMappings();
|
||||
}
|
||||
@ -90,7 +88,7 @@ final class RuntimeBlockMapping{
|
||||
*/
|
||||
$idToStatesMap = [];
|
||||
foreach(self::$bedrockKnownStates as $k => $state){
|
||||
$idToStatesMap[$state->getCompoundTag("block")->getString("name")][] = $k;
|
||||
$idToStatesMap[$state->getString("name")][] = $k;
|
||||
}
|
||||
foreach($legacyStateMap as $pair){
|
||||
$id = $legacyIdMap[$pair->getId()] ?? null;
|
||||
@ -105,14 +103,14 @@ final class RuntimeBlockMapping{
|
||||
$mappedState = $pair->getBlockState();
|
||||
|
||||
//TODO HACK: idiotic NBT compare behaviour on 3.x compares keys which are stored by values
|
||||
$mappedState->setName("block");
|
||||
$mappedState->setName("");
|
||||
$mappedName = $mappedState->getString("name");
|
||||
if(!isset($idToStatesMap[$mappedName])){
|
||||
throw new \RuntimeException("Mapped new state does not appear in network table");
|
||||
}
|
||||
foreach($idToStatesMap[$mappedName] as $k){
|
||||
$networkState = self::$bedrockKnownStates[$k];
|
||||
if($mappedState->equals($networkState->getCompoundTag("block"))){
|
||||
if($mappedState->equals($networkState)){
|
||||
self::registerMapping($k, $id, $data);
|
||||
continue 2;
|
||||
}
|
||||
@ -127,23 +125,6 @@ final class RuntimeBlockMapping{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomizes the order of the runtimeID table to prevent plugins relying on them.
|
||||
* Plugins shouldn't use this stuff anyway, but plugin devs have an irritating habit of ignoring what they
|
||||
* aren't supposed to do, so we have to deliberately break it to make them stop.
|
||||
*
|
||||
* @param CompoundTag[] $table
|
||||
*
|
||||
* @return CompoundTag[]
|
||||
*/
|
||||
private static function randomizeTable(array $table) : array{
|
||||
$postSeed = mt_rand(); //save a seed to set afterwards, to avoid poor quality randoms
|
||||
mt_srand(getmypid()); //Use a seed which is the same on all threads. This isn't a secure seed, but we don't care.
|
||||
shuffle($table);
|
||||
mt_srand($postSeed); //restore a good quality seed that isn't dependent on PID
|
||||
return $table;
|
||||
}
|
||||
|
||||
public static function toStaticRuntimeId(int $id, int $meta = 0) : int{
|
||||
self::lazyInit();
|
||||
/*
|
||||
|
@ -54,13 +54,15 @@ class AdventureSettingsPacket extends DataPacket{
|
||||
public const FLYING = 0x200;
|
||||
public const MUTED = 0x400;
|
||||
|
||||
public const BUILD_AND_MINE = 0x01 | self::BITFLAG_SECOND_SET;
|
||||
public const MINE = 0x01 | self::BITFLAG_SECOND_SET;
|
||||
public const DOORS_AND_SWITCHES = 0x02 | self::BITFLAG_SECOND_SET;
|
||||
public const OPEN_CONTAINERS = 0x04 | self::BITFLAG_SECOND_SET;
|
||||
public const ATTACK_PLAYERS = 0x08 | self::BITFLAG_SECOND_SET;
|
||||
public const ATTACK_MOBS = 0x10 | self::BITFLAG_SECOND_SET;
|
||||
public const OPERATOR = 0x20 | self::BITFLAG_SECOND_SET;
|
||||
public const TELEPORT = 0x80 | self::BITFLAG_SECOND_SET;
|
||||
public const BUILD = 0x100 | self::BITFLAG_SECOND_SET;
|
||||
public const DEFAULT = 0x200 | self::BITFLAG_SECOND_SET;
|
||||
|
||||
/** @var int */
|
||||
public $flags = 0;
|
||||
|
108
src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php
Normal file
108
src/pocketmine/network/mcpe/protocol/AnimateEntityPacket.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use function count;
|
||||
|
||||
class AnimateEntityPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::ANIMATE_ENTITY_PACKET;
|
||||
|
||||
/** @var string */
|
||||
private $animation;
|
||||
/** @var string */
|
||||
private $nextState;
|
||||
/** @var string */
|
||||
private $stopExpression;
|
||||
/** @var string */
|
||||
private $controller;
|
||||
/** @var float */
|
||||
private $blendOutTime;
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var list<int>
|
||||
*/
|
||||
private $actorRuntimeIds;
|
||||
|
||||
/**
|
||||
* @param int[] $actorRuntimeIds
|
||||
* @phpstan-param list<int> $actorRuntimeIds
|
||||
*/
|
||||
public static function create(string $animation, string $nextState, string $stopExpression, string $controller, float $blendOutTime, array $actorRuntimeIds) : self{
|
||||
$result = new self;
|
||||
$result->animation = $animation;
|
||||
$result->nextState = $nextState;
|
||||
$result->stopExpression = $stopExpression;
|
||||
$result->controller = $controller;
|
||||
$result->blendOutTime = $blendOutTime;
|
||||
$result->actorRuntimeIds = $actorRuntimeIds;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getAnimation() : string{ return $this->animation; }
|
||||
|
||||
public function getNextState() : string{ return $this->nextState; }
|
||||
|
||||
public function getStopExpression() : string{ return $this->stopExpression; }
|
||||
|
||||
public function getController() : string{ return $this->controller; }
|
||||
|
||||
public function getBlendOutTime() : float{ return $this->blendOutTime; }
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return list<int>
|
||||
*/
|
||||
public function getActorRuntimeIds() : array{ return $this->actorRuntimeIds; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->animation = $this->getString();
|
||||
$this->nextState = $this->getString();
|
||||
$this->stopExpression = $this->getString();
|
||||
$this->controller = $this->getString();
|
||||
$this->blendOutTime = $this->getLFloat();
|
||||
$this->actorRuntimeIds = [];
|
||||
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
|
||||
$this->actorRuntimeIds[] = $this->getEntityRuntimeId();
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putString($this->animation);
|
||||
$this->putString($this->nextState);
|
||||
$this->putString($this->stopExpression);
|
||||
$this->putString($this->controller);
|
||||
$this->putLFloat($this->blendOutTime);
|
||||
$this->putUnsignedVarInt(count($this->actorRuntimeIds));
|
||||
foreach($this->actorRuntimeIds as $id){
|
||||
$this->putEntityRuntimeId($id);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleAnimateEntity($this);
|
||||
}
|
||||
}
|
@ -49,25 +49,26 @@ class AvailableCommandsPacket extends DataPacket{
|
||||
* ARG_FLAG_VALID | (type const)
|
||||
*/
|
||||
public const ARG_TYPE_INT = 0x01;
|
||||
public const ARG_TYPE_FLOAT = 0x02;
|
||||
public const ARG_TYPE_VALUE = 0x03;
|
||||
public const ARG_TYPE_WILDCARD_INT = 0x04;
|
||||
public const ARG_TYPE_OPERATOR = 0x05;
|
||||
public const ARG_TYPE_TARGET = 0x06;
|
||||
public const ARG_TYPE_FLOAT = 0x03;
|
||||
public const ARG_TYPE_VALUE = 0x04;
|
||||
public const ARG_TYPE_WILDCARD_INT = 0x05;
|
||||
public const ARG_TYPE_OPERATOR = 0x06;
|
||||
public const ARG_TYPE_TARGET = 0x07;
|
||||
public const ARG_TYPE_WILDCARD_TARGET = 0x08;
|
||||
|
||||
public const ARG_TYPE_FILEPATH = 0x0e;
|
||||
public const ARG_TYPE_FILEPATH = 0x10;
|
||||
|
||||
public const ARG_TYPE_STRING = 0x1d;
|
||||
public const ARG_TYPE_STRING = 0x20;
|
||||
|
||||
public const ARG_TYPE_POSITION = 0x25;
|
||||
public const ARG_TYPE_POSITION = 0x28;
|
||||
|
||||
public const ARG_TYPE_MESSAGE = 0x29;
|
||||
public const ARG_TYPE_MESSAGE = 0x2c;
|
||||
|
||||
public const ARG_TYPE_RAWTEXT = 0x2b;
|
||||
public const ARG_TYPE_RAWTEXT = 0x2e;
|
||||
|
||||
public const ARG_TYPE_JSON = 0x2f;
|
||||
public const ARG_TYPE_JSON = 0x32;
|
||||
|
||||
public const ARG_TYPE_COMMAND = 0x36;
|
||||
public const ARG_TYPE_COMMAND = 0x3f;
|
||||
|
||||
/**
|
||||
* Enums are a little different: they are composed as follows:
|
||||
|
@ -27,6 +27,7 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
|
||||
use pocketmine\network\mcpe\NetworkBinaryStream;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function assert;
|
||||
use function get_class;
|
||||
use function strlen;
|
||||
@ -72,7 +73,9 @@ class BatchPacket extends DataPacket{
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->put(zlib_encode($this->payload, ZLIB_ENCODING_RAW, $this->compressionLevel));
|
||||
$encoded = zlib_encode($this->payload, ZLIB_ENCODING_RAW, $this->compressionLevel);
|
||||
if($encoded === false) throw new AssumptionFailedError("ZLIB compression failed");
|
||||
$this->put($encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
|
82
src/pocketmine/network/mcpe/protocol/CameraShakePacket.php
Normal file
82
src/pocketmine/network/mcpe/protocol/CameraShakePacket.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class CameraShakePacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::CAMERA_SHAKE_PACKET;
|
||||
|
||||
public const TYPE_POSITIONAL = 0;
|
||||
public const TYPE_ROTATIONAL = 1;
|
||||
|
||||
public const ACTION_ADD = 0;
|
||||
public const ACTION_STOP = 1;
|
||||
|
||||
/** @var float */
|
||||
private $intensity;
|
||||
/** @var float */
|
||||
private $duration;
|
||||
/** @var int */
|
||||
private $shakeType;
|
||||
/** @var int */
|
||||
private $shakeAction;
|
||||
|
||||
public static function create(float $intensity, float $duration, int $shakeType, int $shakeAction) : self{
|
||||
$result = new self;
|
||||
$result->intensity = $intensity;
|
||||
$result->duration = $duration;
|
||||
$result->shakeType = $shakeType;
|
||||
$result->shakeAction = $shakeAction;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getIntensity() : float{ return $this->intensity; }
|
||||
|
||||
public function getDuration() : float{ return $this->duration; }
|
||||
|
||||
public function getShakeType() : int{ return $this->shakeType; }
|
||||
|
||||
public function getShakeAction() : int{ return $this->shakeAction; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->intensity = $this->getLFloat();
|
||||
$this->duration = $this->getLFloat();
|
||||
$this->shakeType = $this->getByte();
|
||||
$this->shakeAction = $this->getByte();
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putLFloat($this->intensity);
|
||||
$this->putLFloat($this->duration);
|
||||
$this->putByte($this->shakeType);
|
||||
$this->putByte($this->shakeAction);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleCameraShake($this);
|
||||
}
|
||||
}
|
@ -66,23 +66,23 @@ class ClientCacheBlobStatusPacket extends DataPacket/* implements ServerboundPac
|
||||
}
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$hitCount = $this->getUnsignedVarInt();
|
||||
$missCount = $this->getUnsignedVarInt();
|
||||
for($i = 0; $i < $hitCount; ++$i){
|
||||
$this->hitHashes[] = $this->getLLong();
|
||||
}
|
||||
$hitCount = $this->getUnsignedVarInt();
|
||||
for($i = 0; $i < $missCount; ++$i){
|
||||
$this->missHashes[] = $this->getLLong();
|
||||
}
|
||||
for($i = 0; $i < $hitCount; ++$i){
|
||||
$this->hitHashes[] = $this->getLLong();
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putUnsignedVarInt(count($this->hitHashes));
|
||||
$this->putUnsignedVarInt(count($this->missHashes));
|
||||
foreach($this->hitHashes as $hash){
|
||||
$this->putUnsignedVarInt(count($this->hitHashes));
|
||||
foreach($this->missHashes as $hash){
|
||||
$this->putLLong($hash);
|
||||
}
|
||||
foreach($this->missHashes as $hash){
|
||||
foreach($this->hitHashes as $hash){
|
||||
$this->putLLong($hash);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,137 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class ClientboundDebugRendererPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::CLIENTBOUND_DEBUG_RENDERER_PACKET;
|
||||
|
||||
public const TYPE_CLEAR = 1;
|
||||
public const TYPE_ADD_CUBE = 2;
|
||||
|
||||
/** @var int */
|
||||
private $type;
|
||||
|
||||
//TODO: if more types are added, we'll probably want to make a separate data type and interfaces
|
||||
/** @var string */
|
||||
private $text;
|
||||
/** @var Vector3 */
|
||||
private $position;
|
||||
/** @var float */
|
||||
private $red;
|
||||
/** @var float */
|
||||
private $green;
|
||||
/** @var float */
|
||||
private $blue;
|
||||
/** @var float */
|
||||
private $alpha;
|
||||
/** @var int */
|
||||
private $durationMillis;
|
||||
|
||||
private static function base(int $type) : self{
|
||||
$result = new self;
|
||||
$result->type = $type;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function clear() : self{ return self::base(self::TYPE_CLEAR); }
|
||||
|
||||
public static function addCube(string $text, Vector3 $position, float $red, float $green, float $blue, float $alpha, int $durationMillis) : self{
|
||||
$result = self::base(self::TYPE_ADD_CUBE);
|
||||
$result->text = $text;
|
||||
$result->position = $position;
|
||||
$result->red = $red;
|
||||
$result->green = $green;
|
||||
$result->blue = $blue;
|
||||
$result->alpha = $alpha;
|
||||
$result->durationMillis = $durationMillis;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getType() : int{ return $this->type; }
|
||||
|
||||
public function getText() : string{ return $this->text; }
|
||||
|
||||
public function getPosition() : Vector3{ return $this->position; }
|
||||
|
||||
public function getRed() : float{ return $this->red; }
|
||||
|
||||
public function getGreen() : float{ return $this->green; }
|
||||
|
||||
public function getBlue() : float{ return $this->blue; }
|
||||
|
||||
public function getAlpha() : float{ return $this->alpha; }
|
||||
|
||||
public function getDurationMillis() : int{ return $this->durationMillis; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->type = $this->getLInt();
|
||||
|
||||
switch($this->type){
|
||||
case self::TYPE_CLEAR:
|
||||
//NOOP
|
||||
break;
|
||||
case self::TYPE_ADD_CUBE:
|
||||
$this->text = $this->getString();
|
||||
$this->position = $this->getVector3();
|
||||
$this->red = $this->getLFloat();
|
||||
$this->green = $this->getLFloat();
|
||||
$this->blue = $this->getLFloat();
|
||||
$this->alpha = $this->getLFloat();
|
||||
$this->durationMillis = $this->getLLong();
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException("Unknown type " . $this->type);
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putLInt($this->type);
|
||||
|
||||
switch($this->type){
|
||||
case self::TYPE_CLEAR:
|
||||
//NOOP
|
||||
break;
|
||||
case self::TYPE_ADD_CUBE:
|
||||
$this->putString($this->text);
|
||||
$this->putVector3($this->position);
|
||||
$this->putLFloat($this->red);
|
||||
$this->putLFloat($this->green);
|
||||
$this->putLFloat($this->blue);
|
||||
$this->putLFloat($this->alpha);
|
||||
$this->putLLong($this->durationMillis);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown type " . $this->type);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
return $session->handleClientboundDebugRenderer($this);
|
||||
}
|
||||
}
|
@ -33,6 +33,11 @@ use function count;
|
||||
class CommandOutputPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::COMMAND_OUTPUT_PACKET;
|
||||
|
||||
public const TYPE_LAST = 1;
|
||||
public const TYPE_SILENT = 2;
|
||||
public const TYPE_ALL = 3;
|
||||
public const TYPE_DATA_SET = 4;
|
||||
|
||||
/** @var CommandOriginData */
|
||||
public $originData;
|
||||
/** @var int */
|
||||
@ -53,7 +58,7 @@ class CommandOutputPacket extends DataPacket{
|
||||
$this->messages[] = $this->getCommandMessage();
|
||||
}
|
||||
|
||||
if($this->outputType === 4){
|
||||
if($this->outputType === self::TYPE_DATA_SET){
|
||||
$this->unknownString = $this->getString();
|
||||
}
|
||||
}
|
||||
@ -81,7 +86,7 @@ class CommandOutputPacket extends DataPacket{
|
||||
$this->putCommandMessage($message);
|
||||
}
|
||||
|
||||
if($this->outputType === 4){
|
||||
if($this->outputType === self::TYPE_DATA_SET){
|
||||
$this->putString($this->unknownString);
|
||||
}
|
||||
}
|
||||
|
@ -32,13 +32,17 @@ class ContainerClosePacket extends DataPacket{
|
||||
|
||||
/** @var int */
|
||||
public $windowId;
|
||||
/** @var bool */
|
||||
public $server = false;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->windowId = $this->getByte();
|
||||
$this->server = $this->getBool();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
$this->putByte($this->windowId);
|
||||
$this->putBool($this->server);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -0,0 +1,77 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class CorrectPlayerMovePredictionPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::CORRECT_PLAYER_MOVE_PREDICTION_PACKET;
|
||||
|
||||
/** @var Vector3 */
|
||||
private $position;
|
||||
/** @var Vector3 */
|
||||
private $delta;
|
||||
/** @var bool */
|
||||
private $onGround;
|
||||
/** @var int */
|
||||
private $tick;
|
||||
|
||||
public static function create(Vector3 $position, Vector3 $delta, bool $onGround, int $tick) : self{
|
||||
$result = new self;
|
||||
$result->position = $position;
|
||||
$result->delta = $delta;
|
||||
$result->onGround = $onGround;
|
||||
$result->tick = $tick;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getPosition() : Vector3{ return $this->position; }
|
||||
|
||||
public function getDelta() : Vector3{ return $this->delta; }
|
||||
|
||||
public function isOnGround() : bool{ return $this->onGround; }
|
||||
|
||||
public function getTick() : int{ return $this->tick; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->position = $this->getVector3();
|
||||
$this->delta = $this->getVector3();
|
||||
$this->onGround = $this->getBool();
|
||||
$this->tick = $this->getUnsignedVarLong();
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putVector3($this->position);
|
||||
$this->putVector3($this->delta);
|
||||
$this->putBool($this->onGround);
|
||||
$this->putUnsignedVarLong($this->tick);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleCorrectPlayerMovePrediction($this);
|
||||
}
|
||||
}
|
@ -28,8 +28,8 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
use pocketmine\inventory\FurnaceRecipe;
|
||||
use pocketmine\inventory\ShapedRecipe;
|
||||
use pocketmine\inventory\ShapelessRecipe;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\network\mcpe\convert\ItemTranslator;
|
||||
use pocketmine\network\mcpe\NetworkBinaryStream;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\PotionContainerChangeRecipe;
|
||||
@ -83,7 +83,6 @@ class CraftingDataPacket extends DataPacket{
|
||||
case self::ENTRY_SHAPELESS_CHEMISTRY:
|
||||
$entry["recipe_id"] = $this->getString();
|
||||
$ingredientCount = $this->getUnsignedVarInt();
|
||||
/** @var Item */
|
||||
$entry["input"] = [];
|
||||
for($j = 0; $j < $ingredientCount; ++$j){
|
||||
$entry["input"][] = $in = $this->getRecipeIngredient();
|
||||
@ -124,13 +123,12 @@ class CraftingDataPacket extends DataPacket{
|
||||
break;
|
||||
case self::ENTRY_FURNACE:
|
||||
case self::ENTRY_FURNACE_DATA:
|
||||
$inputId = $this->getVarInt();
|
||||
$inputData = -1;
|
||||
if($recipeType === self::ENTRY_FURNACE_DATA){
|
||||
$inputData = $this->getVarInt();
|
||||
if($inputData === 0x7fff){
|
||||
$inputData = -1;
|
||||
}
|
||||
$inputIdNet = $this->getVarInt();
|
||||
if($recipeType === self::ENTRY_FURNACE){
|
||||
[$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, 0x7fff);
|
||||
}else{
|
||||
$inputMetaNet = $this->getVarInt();
|
||||
[$inputId, $inputData] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($inputIdNet, $inputMetaNet);
|
||||
}
|
||||
$entry["input"] = ItemFactory::get($inputId, $inputData);
|
||||
$entry["output"] = $out = $this->getSlot();
|
||||
@ -150,18 +148,25 @@ class CraftingDataPacket extends DataPacket{
|
||||
$this->decodedEntries[] = $entry;
|
||||
}
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$input = $this->getVarInt();
|
||||
$inputMeta = $this->getVarInt();
|
||||
$ingredient = $this->getVarInt();
|
||||
$ingredientMeta = $this->getVarInt();
|
||||
$output = $this->getVarInt();
|
||||
$outputMeta = $this->getVarInt();
|
||||
$inputIdNet = $this->getVarInt();
|
||||
$inputMetaNet = $this->getVarInt();
|
||||
[$input, $inputMeta] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, $inputMetaNet);
|
||||
$ingredientIdNet = $this->getVarInt();
|
||||
$ingredientMetaNet = $this->getVarInt();
|
||||
[$ingredient, $ingredientMeta] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, $ingredientMetaNet);
|
||||
$outputIdNet = $this->getVarInt();
|
||||
$outputMetaNet = $this->getVarInt();
|
||||
[$output, $outputMeta] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, $outputMetaNet);
|
||||
$this->potionTypeRecipes[] = new PotionTypeRecipe($input, $inputMeta, $ingredient, $ingredientMeta, $output, $outputMeta);
|
||||
}
|
||||
for($i = 0, $count = $this->getUnsignedVarInt(); $i < $count; ++$i){
|
||||
$input = $this->getVarInt();
|
||||
$ingredient = $this->getVarInt();
|
||||
$output = $this->getVarInt();
|
||||
//TODO: we discard inbound ID here, not safe because netID on its own might map to internalID+internalMeta for us
|
||||
$inputIdNet = $this->getVarInt();
|
||||
[$input, ] = ItemTranslator::getInstance()->fromNetworkId($inputIdNet, 0);
|
||||
$ingredientIdNet = $this->getVarInt();
|
||||
[$ingredient, ] = ItemTranslator::getInstance()->fromNetworkId($ingredientIdNet, 0);
|
||||
$outputIdNet = $this->getVarInt();
|
||||
[$output, ] = ItemTranslator::getInstance()->fromNetworkId($outputIdNet, 0);
|
||||
$this->potionContainerRecipes[] = new PotionContainerChangeRecipe($input, $ingredient, $output);
|
||||
}
|
||||
$this->cleanRecipes = $this->getBool();
|
||||
@ -230,15 +235,18 @@ class CraftingDataPacket extends DataPacket{
|
||||
}
|
||||
|
||||
private static function writeFurnaceRecipe(FurnaceRecipe $recipe, NetworkBinaryStream $stream) : int{
|
||||
$stream->putVarInt($recipe->getInput()->getId());
|
||||
$result = CraftingDataPacket::ENTRY_FURNACE;
|
||||
if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe
|
||||
$stream->putVarInt($recipe->getInput()->getDamage());
|
||||
$result = CraftingDataPacket::ENTRY_FURNACE_DATA;
|
||||
$input = $recipe->getInput();
|
||||
if($input->hasAnyDamageValue()){
|
||||
[$netId, ] = ItemTranslator::getInstance()->toNetworkId($input->getId(), 0);
|
||||
$netData = 0x7fff;
|
||||
}else{
|
||||
[$netId, $netData] = ItemTranslator::getInstance()->toNetworkId($input->getId(), $input->getDamage());
|
||||
}
|
||||
$stream->putVarInt($netId);
|
||||
$stream->putVarInt($netData);
|
||||
$stream->putSlot($recipe->getResult());
|
||||
$stream->putString("furnace"); //TODO: blocktype (no prefix) (this might require internal API breaks)
|
||||
return $result;
|
||||
return CraftingDataPacket::ENTRY_FURNACE_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,7 +174,7 @@ abstract class DataPacket extends NetworkBinaryStream{
|
||||
public function __debugInfo(){
|
||||
$data = [];
|
||||
foreach((array) $this as $k => $v){
|
||||
if($k === "buffer" and is_string($v)){
|
||||
if($k === "buffer"){
|
||||
$data[$k] = bin2hex($v);
|
||||
}elseif(is_string($v) or (is_object($v) and method_exists($v, "__toString"))){
|
||||
$data[$k] = Utils::printable((string) $v);
|
||||
|
@ -48,6 +48,13 @@ class EventPacket extends DataPacket{
|
||||
public const TYPE_CAULDRON_BLOCK_USED = 15;
|
||||
public const TYPE_COMPOSTER_BLOCK_USED = 16;
|
||||
public const TYPE_BELL_BLOCK_USED = 17;
|
||||
public const TYPE_ACTOR_DEFINITION = 18;
|
||||
public const TYPE_RAID_UPDATE = 19;
|
||||
public const TYPE_PLAYER_MOVEMENT_ANOMALY = 20; //anti cheat
|
||||
public const TYPE_PLAYER_MOVEMENT_CORRECTED = 21;
|
||||
public const TYPE_HONEY_HARVESTED = 22;
|
||||
public const TYPE_TARGET_BLOCK_HIT = 23;
|
||||
public const TYPE_PIGLIN_BARTER = 24;
|
||||
|
||||
/** @var int */
|
||||
public $playerRuntimeId;
|
||||
|
@ -25,31 +25,38 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\nbt\NetworkLittleEndianNBTStream;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class UpdateBlockPropertiesPacket extends DataPacket{
|
||||
public const NETWORK_ID = ProtocolInfo::UPDATE_BLOCK_PROPERTIES_PACKET;
|
||||
class FilterTextPacket extends DataPacket/* implements ClientboundPacket, ServerboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::FILTER_TEXT_PACKET;
|
||||
|
||||
/** @var string */
|
||||
private $nbt;
|
||||
private $text;
|
||||
/** @var bool */
|
||||
private $fromServer;
|
||||
|
||||
public static function create(CompoundTag $data) : self{
|
||||
public static function create(string $text, bool $server) : self{
|
||||
$result = new self;
|
||||
$result->nbt = (new NetworkLittleEndianNBTStream())->write($data);
|
||||
$result->text = $text;
|
||||
$result->fromServer = $server;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getText() : string{ return $this->text; }
|
||||
|
||||
public function isFromServer() : bool{ return $this->fromServer; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->nbt = $this->getRemaining();
|
||||
$this->text = $this->getString();
|
||||
$this->fromServer = $this->getBool();
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->put($this->nbt);
|
||||
$this->putString($this->text);
|
||||
$this->putBool($this->fromServer);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleUpdateBlockProperties($this);
|
||||
return $handler->handleFilterText($this);
|
||||
}
|
||||
}
|
78
src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php
Normal file
78
src/pocketmine/network/mcpe/protocol/ItemComponentPacket.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\nbt\NetworkLittleEndianNBTStream;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\ItemComponentPacketEntry;
|
||||
use function count;
|
||||
|
||||
class ItemComponentPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::ITEM_COMPONENT_PACKET;
|
||||
|
||||
/**
|
||||
* @var ItemComponentPacketEntry[]
|
||||
* @phpstan-var list<ItemComponentPacketEntry>
|
||||
*/
|
||||
private $entries;
|
||||
|
||||
/**
|
||||
* @param ItemComponentPacketEntry[] $entries
|
||||
* @phpstan-param list<ItemComponentPacketEntry> $entries
|
||||
*/
|
||||
public static function create(array $entries) : self{
|
||||
$result = new self;
|
||||
$result->entries = $entries;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ItemComponentPacketEntry[]
|
||||
* @phpstan-return list<ItemComponentPacketEntry>
|
||||
*/
|
||||
public function getEntries() : array{ return $this->entries; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->entries = [];
|
||||
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
|
||||
$name = $this->getString();
|
||||
$nbt = $this->getNbtCompoundRoot();
|
||||
$this->entries[] = new ItemComponentPacketEntry($name, $nbt);
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putUnsignedVarInt(count($this->entries));
|
||||
foreach($this->entries as $entry){
|
||||
$this->putString($entry->getName());
|
||||
$this->put((new NetworkLittleEndianNBTStream())->write($entry->getComponentNbt()));
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleItemComponent($this);
|
||||
}
|
||||
}
|
@ -255,7 +255,7 @@ class LevelSoundEventPacket extends DataPacket{
|
||||
public const SOUND_BLOCK_TURTLE_EGG_BREAK = 224;
|
||||
public const SOUND_BLOCK_TURTLE_EGG_CRACK = 225;
|
||||
public const SOUND_BLOCK_TURTLE_EGG_HATCH = 226;
|
||||
|
||||
public const SOUND_LAY_EGG = 227;
|
||||
public const SOUND_BLOCK_TURTLE_EGG_ATTACK = 228;
|
||||
public const SOUND_BEACON_ACTIVATE = 229;
|
||||
public const SOUND_BEACON_AMBIENT = 230;
|
||||
@ -309,7 +309,56 @@ class LevelSoundEventPacket extends DataPacket{
|
||||
public const SOUND_BLOCK_SMOKER_SMOKE = 278;
|
||||
public const SOUND_BLOCK_BLASTFURNACE_FIRE_CRACKLE = 279;
|
||||
public const SOUND_BLOCK_SMITHING_TABLE_USE = 280;
|
||||
public const SOUND_UNDEFINED = 281;
|
||||
public const SOUND_SCREECH = 281;
|
||||
public const SOUND_SLEEP = 282;
|
||||
public const SOUND_BLOCK_FURNACE_LIT = 283;
|
||||
public const SOUND_CONVERT_MOOSHROOM = 284;
|
||||
public const SOUND_MILK_SUSPICIOUSLY = 285;
|
||||
public const SOUND_CELEBRATE = 286;
|
||||
public const SOUND_JUMP_PREVENT = 287;
|
||||
public const SOUND_AMBIENT_POLLINATE = 288;
|
||||
public const SOUND_BLOCK_BEEHIVE_DRIP = 289;
|
||||
public const SOUND_BLOCK_BEEHIVE_ENTER = 290;
|
||||
public const SOUND_BLOCK_BEEHIVE_EXIT = 291;
|
||||
public const SOUND_BLOCK_BEEHIVE_WORK = 292;
|
||||
public const SOUND_BLOCK_BEEHIVE_SHEAR = 293;
|
||||
public const SOUND_DRINK_HONEY = 294;
|
||||
public const SOUND_AMBIENT_CAVE = 295;
|
||||
public const SOUND_RETREAT = 296;
|
||||
public const SOUND_CONVERTED_TO_ZOMBIFIED = 297;
|
||||
public const SOUND_ADMIRE = 298;
|
||||
public const SOUND_STEP_LAVA = 299;
|
||||
public const SOUND_TEMPT = 300;
|
||||
public const SOUND_PANIC = 301;
|
||||
public const SOUND_ANGRY = 302;
|
||||
public const SOUND_AMBIENT_WARPED_FOREST_MOOD = 303;
|
||||
public const SOUND_AMBIENT_SOULSAND_VALLEY_MOOD = 304;
|
||||
public const SOUND_AMBIENT_NETHER_WASTES_MOOD = 305;
|
||||
public const SOUND_RESPAWN_ANCHOR_BASALT_DELTAS_MOOD = 306;
|
||||
public const SOUND_AMBIENT_CRIMSON_FOREST_MOOD = 307;
|
||||
public const SOUND_RESPAWN_ANCHOR_CHARGE = 308;
|
||||
public const SOUND_RESPAWN_ANCHOR_DEPLETE = 309;
|
||||
public const SOUND_RESPAWN_ANCHOR_SET_SPAWN = 310;
|
||||
public const SOUND_RESPAWN_ANCHOR_AMBIENT = 311;
|
||||
public const SOUND_PARTICLE_SOUL_ESCAPE_QUIET = 312;
|
||||
public const SOUND_PARTICLE_SOUL_ESCAPE_LOUD = 313;
|
||||
public const SOUND_RECORD_PIGSTEP = 314;
|
||||
public const SOUND_LODESTONE_COMPASS_LINK_COMPASS_TO_LODESTONE = 315;
|
||||
public const SOUND_SMITHING_TABLE_USE = 316;
|
||||
public const SOUND_ARMOR_EQUIP_NETHERITE = 317;
|
||||
public const SOUND_AMBIENT_WARPED_FOREST_LOOP = 318;
|
||||
public const SOUND_AMBIENT_SOULSAND_VALLEY_LOOP = 319;
|
||||
public const SOUND_AMBIENT_NETHER_WASTES_LOOP = 320;
|
||||
public const SOUND_AMBIENT_BASALT_DELTAS_LOOP = 321;
|
||||
public const SOUND_AMBIENT_CRIMSON_FOREST_LOOP = 322;
|
||||
public const SOUND_AMBIENT_WARPED_FOREST_ADDITIONS = 323;
|
||||
public const SOUND_AMBIENT_SOULSAND_VALLEY_ADDITIONS = 324;
|
||||
public const SOUND_AMBIENT_NETHER_WASTES_ADDITIONS = 325;
|
||||
public const SOUND_AMBIENT_BASALT_DELTAS_ADDITIONS = 326;
|
||||
public const SOUND_AMBIENT_CRIMSON_FOREST_ADDITIONS = 327;
|
||||
public const SOUND_BUCKET_FILL_POWDER_SNOW = 328;
|
||||
public const SOUND_BUCKET_EMPTY_POWDER_SNOW = 329;
|
||||
public const SOUND_UNDEFINED = 330;
|
||||
|
||||
/** @var int */
|
||||
public $sound;
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
class MotionPredictionHintsPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::MOTION_PREDICTION_HINTS_PACKET;
|
||||
|
||||
/** @var int */
|
||||
private $entityRuntimeId;
|
||||
/** @var Vector3 */
|
||||
private $motion;
|
||||
/** @var bool */
|
||||
private $onGround;
|
||||
|
||||
public static function create(int $entityRuntimeId, Vector3 $motion, bool $onGround) : self{
|
||||
$result = new self;
|
||||
$result->entityRuntimeId = $entityRuntimeId;
|
||||
$result->motion = $motion;
|
||||
$result->onGround = $onGround;
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getEntityRuntimeIdField() : int{ return $this->entityRuntimeId; } //TODO: rename this on PM4 (crap architecture, thanks shoghi)
|
||||
|
||||
public function getMotion() : Vector3{ return $this->motion; }
|
||||
|
||||
public function isOnGround() : bool{ return $this->onGround; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->entityRuntimeId = $this->getEntityRuntimeId();
|
||||
$this->motion = $this->getVector3();
|
||||
$this->onGround = $this->getBool();
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putEntityRuntimeId($this->entityRuntimeId);
|
||||
$this->putVector3($this->motion);
|
||||
$this->putBool($this->onGround);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handleMotionPredictionHints($this);
|
||||
}
|
||||
}
|
@ -44,12 +44,12 @@ class MoveActorDeltaPacket extends DataPacket{
|
||||
public $entityRuntimeId;
|
||||
/** @var int */
|
||||
public $flags;
|
||||
/** @var int */
|
||||
public $xDiff = 0;
|
||||
/** @var int */
|
||||
public $yDiff = 0;
|
||||
/** @var int */
|
||||
public $zDiff = 0;
|
||||
/** @var float */
|
||||
public $xPos = 0;
|
||||
/** @var float */
|
||||
public $yPos = 0;
|
||||
/** @var float */
|
||||
public $zPos = 0;
|
||||
/** @var float */
|
||||
public $xRot = 0.0;
|
||||
/** @var float */
|
||||
@ -57,9 +57,9 @@ class MoveActorDeltaPacket extends DataPacket{
|
||||
/** @var float */
|
||||
public $zRot = 0.0;
|
||||
|
||||
private function maybeReadCoord(int $flag) : int{
|
||||
private function maybeReadCoord(int $flag) : float{
|
||||
if(($this->flags & $flag) !== 0){
|
||||
return $this->getVarInt();
|
||||
return $this->getLFloat();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -74,17 +74,17 @@ class MoveActorDeltaPacket extends DataPacket{
|
||||
protected function decodePayload(){
|
||||
$this->entityRuntimeId = $this->getEntityRuntimeId();
|
||||
$this->flags = $this->getLShort();
|
||||
$this->xDiff = $this->maybeReadCoord(self::FLAG_HAS_X);
|
||||
$this->yDiff = $this->maybeReadCoord(self::FLAG_HAS_Y);
|
||||
$this->zDiff = $this->maybeReadCoord(self::FLAG_HAS_Z);
|
||||
$this->xPos = $this->maybeReadCoord(self::FLAG_HAS_X);
|
||||
$this->yPos = $this->maybeReadCoord(self::FLAG_HAS_Y);
|
||||
$this->zPos = $this->maybeReadCoord(self::FLAG_HAS_Z);
|
||||
$this->xRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_X);
|
||||
$this->yRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Y);
|
||||
$this->zRot = $this->maybeReadRotation(self::FLAG_HAS_ROT_Z);
|
||||
}
|
||||
|
||||
private function maybeWriteCoord(int $flag, int $val) : void{
|
||||
private function maybeWriteCoord(int $flag, float $val) : void{
|
||||
if(($this->flags & $flag) !== 0){
|
||||
$this->putVarInt($val);
|
||||
$this->putLFloat($val);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,9 +97,9 @@ class MoveActorDeltaPacket extends DataPacket{
|
||||
protected function encodePayload(){
|
||||
$this->putEntityRuntimeId($this->entityRuntimeId);
|
||||
$this->putLShort($this->flags);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_X, $this->xDiff);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yDiff);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zDiff);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_X, $this->xPos);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_Y, $this->yPos);
|
||||
$this->maybeWriteCoord(self::FLAG_HAS_Z, $this->zPos);
|
||||
$this->maybeWriteRotation(self::FLAG_HAS_ROT_X, $this->xRot);
|
||||
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Y, $this->yRot);
|
||||
$this->maybeWriteRotation(self::FLAG_HAS_ROT_Z, $this->zRot);
|
||||
|
@ -56,6 +56,8 @@ class MovePlayerPacket extends DataPacket{
|
||||
public $teleportCause = 0;
|
||||
/** @var int */
|
||||
public $teleportItem = 0;
|
||||
/** @var int */
|
||||
public $tick = 0;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->entityRuntimeId = $this->getEntityRuntimeId();
|
||||
@ -70,6 +72,7 @@ class MovePlayerPacket extends DataPacket{
|
||||
$this->teleportCause = $this->getLInt();
|
||||
$this->teleportItem = $this->getLInt();
|
||||
}
|
||||
$this->tick = $this->getUnsignedVarLong();
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
@ -85,6 +88,7 @@ class MovePlayerPacket extends DataPacket{
|
||||
$this->putLInt($this->teleportCause);
|
||||
$this->putLInt($this->teleportItem);
|
||||
}
|
||||
$this->putUnsignedVarLong($this->tick);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -71,7 +71,6 @@ class PacketPool{
|
||||
static::registerPacket(new BlockPickRequestPacket());
|
||||
static::registerPacket(new ActorPickRequestPacket());
|
||||
static::registerPacket(new PlayerActionPacket());
|
||||
static::registerPacket(new ActorFallPacket());
|
||||
static::registerPacket(new HurtArmorPacket());
|
||||
static::registerPacket(new SetActorDataPacket());
|
||||
static::registerPacket(new SetActorMotionPacket());
|
||||
@ -166,7 +165,6 @@ class PacketPool{
|
||||
static::registerPacket(new MapCreateLockedCopyPacket());
|
||||
static::registerPacket(new StructureTemplateDataRequestPacket());
|
||||
static::registerPacket(new StructureTemplateDataResponsePacket());
|
||||
static::registerPacket(new UpdateBlockPropertiesPacket());
|
||||
static::registerPacket(new ClientCacheBlobStatusPacket());
|
||||
static::registerPacket(new ClientCacheMissResponsePacket());
|
||||
static::registerPacket(new EducationSettingsPacket());
|
||||
@ -189,6 +187,14 @@ class PacketPool{
|
||||
static::registerPacket(new PositionTrackingDBClientRequestPacket());
|
||||
static::registerPacket(new DebugInfoPacket());
|
||||
static::registerPacket(new PacketViolationWarningPacket());
|
||||
static::registerPacket(new MotionPredictionHintsPacket());
|
||||
static::registerPacket(new AnimateEntityPacket());
|
||||
static::registerPacket(new CameraShakePacket());
|
||||
static::registerPacket(new PlayerFogPacket());
|
||||
static::registerPacket(new CorrectPlayerMovePredictionPacket());
|
||||
static::registerPacket(new ItemComponentPacket());
|
||||
static::registerPacket(new FilterTextPacket());
|
||||
static::registerPacket(new ClientboundDebugRendererPacket());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,12 +43,12 @@ class PlayerActionPacket extends DataPacket{
|
||||
public const ACTION_STOP_SPRINT = 10;
|
||||
public const ACTION_START_SNEAK = 11;
|
||||
public const ACTION_STOP_SNEAK = 12;
|
||||
public const ACTION_DIMENSION_CHANGE_REQUEST = 13; //sent when dying in different dimension
|
||||
public const ACTION_CREATIVE_PLAYER_DESTROY_BLOCK = 13;
|
||||
public const ACTION_DIMENSION_CHANGE_ACK = 14; //sent when spawning in a different dimension to tell the server we spawned
|
||||
public const ACTION_START_GLIDE = 15;
|
||||
public const ACTION_STOP_GLIDE = 16;
|
||||
public const ACTION_BUILD_DENIED = 17;
|
||||
public const ACTION_CONTINUE_BREAK = 18;
|
||||
public const ACTION_CRACK_BREAK = 18;
|
||||
public const ACTION_CHANGE_SKIN = 19;
|
||||
public const ACTION_SET_ENCHANTMENT_SEED = 20; //no longer used
|
||||
public const ACTION_START_SWIMMING = 21;
|
||||
@ -56,6 +56,8 @@ class PlayerActionPacket extends DataPacket{
|
||||
public const ACTION_START_SPIN_ATTACK = 23;
|
||||
public const ACTION_STOP_SPIN_ATTACK = 24;
|
||||
public const ACTION_INTERACT_BLOCK = 25;
|
||||
public const ACTION_PREDICT_DESTROY_BLOCK = 26;
|
||||
public const ACTION_CONTINUE_DESTROY_BLOCK = 27;
|
||||
|
||||
/** @var int */
|
||||
public $entityRuntimeId;
|
||||
|
@ -28,6 +28,7 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\InputMode;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerAuthInputFlags;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayMode;
|
||||
use function assert;
|
||||
|
||||
@ -54,13 +55,18 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
private $playMode;
|
||||
/** @var Vector3|null */
|
||||
private $vrGazeDirection = null;
|
||||
/** @var int */
|
||||
private $tick;
|
||||
/** @var Vector3 */
|
||||
private $delta;
|
||||
|
||||
/**
|
||||
* @param int $inputFlags @see InputFlags
|
||||
* @param int $inputMode @see InputMode
|
||||
* @param int $playMode @see PlayMode
|
||||
* @param Vector3|null $vrGazeDirection only used when PlayMode::VR
|
||||
*/
|
||||
public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection = null) : self{
|
||||
public static function create(Vector3 $position, float $pitch, float $yaw, float $headYaw, float $moveVecX, float $moveVecZ, int $inputFlags, int $inputMode, int $playMode, ?Vector3 $vrGazeDirection, int $tick, Vector3 $delta) : self{
|
||||
if($playMode === PlayMode::VR and $vrGazeDirection === null){
|
||||
//yuck, can we get a properly written packet just once? ...
|
||||
throw new \InvalidArgumentException("Gaze direction must be provided for VR play mode");
|
||||
@ -78,6 +84,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
if($vrGazeDirection !== null){
|
||||
$result->vrGazeDirection = $vrGazeDirection->asVector3();
|
||||
}
|
||||
$result->tick = $tick;
|
||||
$result->delta = $delta;
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -105,6 +113,9 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
return $this->moveVecZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlayerAuthInputFlags
|
||||
*/
|
||||
public function getInputFlags() : int{
|
||||
return $this->inputFlags;
|
||||
}
|
||||
@ -127,6 +138,10 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
return $this->vrGazeDirection;
|
||||
}
|
||||
|
||||
public function getTick() : int{ return $this->tick; }
|
||||
|
||||
public function getDelta() : Vector3{ return $this->delta; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->pitch = $this->getLFloat();
|
||||
$this->yaw = $this->getLFloat();
|
||||
@ -140,6 +155,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
if($this->playMode === PlayMode::VR){
|
||||
$this->vrGazeDirection = $this->getVector3();
|
||||
}
|
||||
$this->tick = $this->getUnsignedVarLong();
|
||||
$this->delta = $this->getVector3();
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
@ -156,6 +173,8 @@ class PlayerAuthInputPacket extends DataPacket/* implements ServerboundPacket*/{
|
||||
assert($this->vrGazeDirection !== null);
|
||||
$this->putVector3($this->vrGazeDirection);
|
||||
}
|
||||
$this->putUnsignedVarLong($this->tick);
|
||||
$this->putVector3($this->delta);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
|
73
src/pocketmine/network/mcpe/protocol/PlayerFogPacket.php
Normal file
73
src/pocketmine/network/mcpe/protocol/PlayerFogPacket.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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\protocol;
|
||||
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use function count;
|
||||
|
||||
class PlayerFogPacket extends DataPacket/* implements ClientboundPacket*/{
|
||||
public const NETWORK_ID = ProtocolInfo::PLAYER_FOG_PACKET;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private $fogLayers;
|
||||
|
||||
/**
|
||||
* @param string[] $fogLayers
|
||||
* @phpstan-param list<string> $fogLayers
|
||||
*/
|
||||
public static function create(array $fogLayers) : self{
|
||||
$result = new self;
|
||||
$result->fogLayers = $fogLayers;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public function getFogLayers() : array{ return $this->fogLayers; }
|
||||
|
||||
protected function decodePayload() : void{
|
||||
$this->fogLayers = [];
|
||||
for($i = 0, $len = $this->getUnsignedVarInt(); $i < $len; ++$i){
|
||||
$this->fogLayers[] = $this->getString();
|
||||
}
|
||||
}
|
||||
|
||||
protected function encodePayload() : void{
|
||||
$this->putUnsignedVarInt(count($this->fogLayers));
|
||||
foreach($this->fogLayers as $fogLayer){
|
||||
$this->putString($fogLayer);
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $handler) : bool{
|
||||
return $handler->handlePlayerFog($this);
|
||||
}
|
||||
}
|
@ -37,11 +37,11 @@ interface ProtocolInfo{
|
||||
*/
|
||||
|
||||
/** Actual Minecraft: PE protocol version */
|
||||
public const CURRENT_PROTOCOL = 408;
|
||||
public const CURRENT_PROTOCOL = 428;
|
||||
/** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */
|
||||
public const MINECRAFT_VERSION = 'v1.16.20';
|
||||
public const MINECRAFT_VERSION = 'v1.16.210';
|
||||
/** Version number sent to clients in ping responses. */
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.16.20';
|
||||
public const MINECRAFT_VERSION_NETWORK = '1.16.210';
|
||||
|
||||
public const LOGIN_PACKET = 0x01;
|
||||
public const PLAY_STATUS_PACKET = 0x02;
|
||||
@ -79,7 +79,7 @@ interface ProtocolInfo{
|
||||
public const BLOCK_PICK_REQUEST_PACKET = 0x22;
|
||||
public const ACTOR_PICK_REQUEST_PACKET = 0x23;
|
||||
public const PLAYER_ACTION_PACKET = 0x24;
|
||||
public const ACTOR_FALL_PACKET = 0x25;
|
||||
|
||||
public const HURT_ARMOR_PACKET = 0x26;
|
||||
public const SET_ACTOR_DATA_PACKET = 0x27;
|
||||
public const SET_ACTOR_MOTION_PACKET = 0x28;
|
||||
@ -176,7 +176,7 @@ interface ProtocolInfo{
|
||||
public const MAP_CREATE_LOCKED_COPY_PACKET = 0x83;
|
||||
public const STRUCTURE_TEMPLATE_DATA_REQUEST_PACKET = 0x84;
|
||||
public const STRUCTURE_TEMPLATE_DATA_RESPONSE_PACKET = 0x85;
|
||||
public const UPDATE_BLOCK_PROPERTIES_PACKET = 0x86;
|
||||
|
||||
public const CLIENT_CACHE_BLOB_STATUS_PACKET = 0x87;
|
||||
public const CLIENT_CACHE_MISS_RESPONSE_PACKET = 0x88;
|
||||
public const EDUCATION_SETTINGS_PACKET = 0x89;
|
||||
@ -199,5 +199,13 @@ interface ProtocolInfo{
|
||||
public const POSITION_TRACKING_D_B_CLIENT_REQUEST_PACKET = 0x9a;
|
||||
public const DEBUG_INFO_PACKET = 0x9b;
|
||||
public const PACKET_VIOLATION_WARNING_PACKET = 0x9c;
|
||||
public const MOTION_PREDICTION_HINTS_PACKET = 0x9d;
|
||||
public const ANIMATE_ENTITY_PACKET = 0x9e;
|
||||
public const CAMERA_SHAKE_PACKET = 0x9f;
|
||||
public const PLAYER_FOG_PACKET = 0xa0;
|
||||
public const CORRECT_PLAYER_MOVE_PREDICTION_PACKET = 0xa1;
|
||||
public const ITEM_COMPONENT_PACKET = 0xa2;
|
||||
public const FILTER_TEXT_PACKET = 0xa3;
|
||||
public const CLIENTBOUND_DEBUG_RENDERER_PACKET = 0xa4;
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class ResourcePackClientResponsePacket extends DataPacket{
|
||||
protected function decodePayload(){
|
||||
$this->status = $this->getByte();
|
||||
$entryCount = $this->getLShort();
|
||||
$this->packIds = [];
|
||||
while($entryCount-- > 0){
|
||||
$this->packIds[] = $this->getString();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
|
||||
#include <rules/DataPacket.h>
|
||||
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\types\Experiments;
|
||||
use pocketmine\resourcepacks\ResourcePack;
|
||||
use function count;
|
||||
|
||||
@ -40,11 +41,12 @@ class ResourcePackStackPacket extends DataPacket{
|
||||
/** @var ResourcePack[] */
|
||||
public $resourcePackStack = [];
|
||||
|
||||
/** @var bool */
|
||||
public $isExperimental = false;
|
||||
/** @var string */
|
||||
public $baseGameVersion = ProtocolInfo::MINECRAFT_VERSION_NETWORK;
|
||||
|
||||
/** @var Experiments */
|
||||
public $experiments;
|
||||
|
||||
protected function decodePayload(){
|
||||
$this->mustAccept = $this->getBool();
|
||||
$behaviorPackCount = $this->getUnsignedVarInt();
|
||||
@ -61,8 +63,8 @@ class ResourcePackStackPacket extends DataPacket{
|
||||
$this->getString();
|
||||
}
|
||||
|
||||
$this->isExperimental = $this->getBool();
|
||||
$this->baseGameVersion = $this->getString();
|
||||
$this->experiments = Experiments::read($this);
|
||||
}
|
||||
|
||||
protected function encodePayload(){
|
||||
@ -82,8 +84,8 @@ class ResourcePackStackPacket extends DataPacket{
|
||||
$this->putString(""); //TODO: subpack name
|
||||
}
|
||||
|
||||
$this->putBool($this->isExperimental);
|
||||
$this->putString($this->baseGameVersion);
|
||||
$this->experiments->write($this);
|
||||
}
|
||||
|
||||
public function handle(NetworkSession $session) : bool{
|
||||
|
@ -64,6 +64,7 @@ class ResourcePacksInfoPacket extends DataPacket{
|
||||
$this->getString();
|
||||
$this->getString();
|
||||
$this->getBool();
|
||||
$this->getBool();
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +90,7 @@ class ResourcePacksInfoPacket extends DataPacket{
|
||||
$this->putString(""); //TODO: subpack name
|
||||
$this->putString(""); //TODO: content identity
|
||||
$this->putBool(false); //TODO: seems useless for resource packs
|
||||
$this->putBool(false); //TODO: supports RTX
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user