mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 19:24:12 +00:00
Compare commits
554 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
a498b0415a | |||
5b01cf72dd | |||
ec21c2baa0 | |||
11a0d9b502 | |||
a7fc245291 | |||
6db51e2380 | |||
d6f35f2342 | |||
d1df72ec78 | |||
9bd6d5c67e | |||
aaa23361d1 | |||
691d92a959 | |||
50101663f2 | |||
e369966890 | |||
63f57841de | |||
ac3bba0a11 | |||
1ff3df6ff0 | |||
4e29b216bf | |||
809dad2ac8 | |||
e238d583b8 | |||
3f89bd7bff | |||
8da7e789fd | |||
0766952f39 | |||
eeee1fbe73 | |||
46c224da86 | |||
3c001b310f | |||
198a106b9f | |||
1f5e0bc96d | |||
41f7c07703 | |||
f0a0c9a85f | |||
5b620d964e | |||
756840f11d | |||
df2c3136c9 | |||
a6b5cddd5a | |||
5b9453af43 | |||
8bba25f4f5 | |||
f9bd7016aa | |||
213406fc60 | |||
7ff6e5895e | |||
2e6b62fdec | |||
4fc5b9772a | |||
5d4880b0a7 | |||
2b1a0e1e72 | |||
cd022f1592 | |||
4ae3fd7734 | |||
b2249f93c0 | |||
303344783a | |||
75e0844ff5 | |||
18fabf5466 | |||
2751c59979 | |||
d99ffbd66c | |||
a34f3261cb | |||
8ce0022de6 | |||
fb6491ddeb | |||
3b961d0e5f | |||
a60fc4cc28 | |||
b747899fdd | |||
57b6451e16 | |||
8cf025a2df | |||
8480ee82ea | |||
a6c1b7bf9c | |||
c267137fde | |||
461bc94236 | |||
4fed08bcd4 | |||
e990c5a0a5 | |||
c616d9bb7c | |||
81051441ba | |||
3ecae0db19 | |||
c5bbb2bcbc | |||
24a2889758 | |||
60b26a7ea8 | |||
22b52f03d1 | |||
df76c02e7a | |||
d343187e58 | |||
c5ad127854 | |||
0f6dc9082a | |||
2b6dcbc2e2 | |||
763c8ebfe3 | |||
c572e9bb6a | |||
89521f166d | |||
49d3a42120 | |||
c523595e85 | |||
7c7e4f2093 | |||
88c1014f03 | |||
e32180ce93 | |||
e105578be0 | |||
a9d98bdf73 | |||
c601352777 | |||
77c71e22b2 | |||
1c13ba5656 | |||
f970be0e4d | |||
11a3f9f1b9 | |||
09771849ae | |||
57a310230a | |||
130c55d9f1 | |||
2712befa82 | |||
a4e250a3e6 | |||
23b97d8e2d | |||
1fb5043eb1 | |||
b0b1b29de4 | |||
1c3b641e37 | |||
f3063e797f | |||
8dcc88712c | |||
04191ec44a | |||
62ea7c93a9 | |||
cf06b5b8cf | |||
a8ec51daac | |||
6a7b77fee2 | |||
da42c8d020 | |||
b902f9ded0 | |||
9bb8a8f761 | |||
63b14a083c | |||
627a7c951a | |||
bb2685ca65 | |||
d38709a7ae | |||
b559a65346 | |||
b92a2ded8a | |||
22f25dfbdb | |||
6bf840c72e | |||
745be19a56 | |||
e05bee5ffb | |||
087ba0cc1d | |||
d8d994351b | |||
0029efa370 | |||
df13e967fd | |||
097c260dbb | |||
a219b727f2 | |||
710c162604 | |||
409c8c1703 | |||
376926c700 | |||
c3fabe833e | |||
3e09ff5350 | |||
7255065106 | |||
a7f10d8ccf | |||
76f1add3b3 | |||
fcc9e62c65 | |||
42613618a5 | |||
1bbeb62457 | |||
64893426fa | |||
3d50aafcc4 | |||
50fed41642 | |||
3f971a0c65 | |||
a27b29897c | |||
a90132a30e | |||
dfbd857771 | |||
323d96d5c1 | |||
f495ba1d0b | |||
643cf0ebf8 | |||
1614206a6d | |||
0ae2c6302a | |||
4f59c1b26c | |||
00916ade0c | |||
f4ee2912db | |||
a0de9b0d46 | |||
03e8cd3ed4 | |||
7af4e70f64 | |||
c864647cd1 | |||
92ed9e6125 | |||
c32026333f | |||
915224c8e5 | |||
734bc6c4a7 | |||
d36b24c518 | |||
d554d8060b | |||
b48243fd09 | |||
5c63e06b0f | |||
3be83e09f2 | |||
f24be2b055 | |||
92cffc00d0 | |||
e87e974323 | |||
a3f6338626 | |||
21aef97ba7 | |||
ed0d1978aa | |||
d64561b0b1 | |||
d234d3e45e | |||
5056754cea | |||
2dc3cf8162 | |||
8c5a81cf5c | |||
2b58f2bafd | |||
5dadf12374 | |||
0d4e473bdd | |||
11cedc4011 | |||
3f2455f090 | |||
9d26a224a2 | |||
c4ad390463 | |||
42e14f749e | |||
484557935e | |||
485f573955 | |||
71e0521286 | |||
3f07f06874 | |||
10279e11ed | |||
673e444456 | |||
89c49d77c6 | |||
c3a795e876 | |||
4199c3796f | |||
ecbf21acea | |||
45c89d084c | |||
56f90a2901 | |||
bf6af269c8 | |||
73d1f84072 | |||
a29424f5b3 | |||
ff3af492f8 | |||
80b804f7aa | |||
3a78735982 | |||
ab32784c74 | |||
816234a379 | |||
b7bf92a5e9 | |||
dcca000ead | |||
c4ea51f985 | |||
8202bb1cd8 | |||
b75758e35e | |||
38a06f76f8 | |||
84f99ed418 | |||
fd63f19199 | |||
66d44aa814 | |||
f3089f577e | |||
9516ef1632 | |||
e982a57cb5 | |||
f57fa2252b | |||
02cc370855 | |||
0a5d14a840 | |||
3ec2994d7f | |||
da4a2d8552 | |||
dc9351b024 | |||
786f416f2e | |||
a67d2ae978 | |||
089180fef4 | |||
15baf09339 | |||
083dde8395 | |||
fffa4b9501 | |||
22b5de09b4 | |||
71a8b0340c | |||
e544055bbc | |||
8f5db7c297 | |||
a5edfa368e | |||
27f55e4c96 | |||
9b6b3f50a1 | |||
1fb0ba6fc0 | |||
f1d378468e | |||
9ebd6d6b0f | |||
58e32086c0 | |||
8c0d441a13 | |||
25fb5140a2 | |||
f5a49b6d55 | |||
189f12a644 | |||
5a8917f6f2 | |||
f3e436592a | |||
35747874f6 | |||
59445902b8 | |||
2eb62c85f6 | |||
ad2a39bf13 | |||
0847358070 | |||
8766d4050c | |||
54f41dc145 | |||
ded45bddfe | |||
b044550475 | |||
a70fa15690 | |||
bd1d7b8d75 | |||
1513a0e092 | |||
c4150d4520 | |||
2f47597d75 | |||
56883f9ff9 | |||
7cdd26add5 | |||
717b866605 | |||
ef97c8f99e | |||
84932ce908 | |||
6bfc309a0a | |||
06e8c6a3ad | |||
71271a0e03 | |||
f87e96026c | |||
b63ad032a9 | |||
d9b0e373bb | |||
8e1b3edd2c | |||
32262d9bb5 | |||
4c1b10b24b | |||
61dc9d7f6b | |||
da9731ef59 | |||
e6f64c609e | |||
8c7fbf379b | |||
3d2ca457f8 | |||
1579f41056 | |||
34a3e0d8b1 | |||
d42217ff57 | |||
70a4f73d73 | |||
7d43dffac4 | |||
804a062c3a | |||
22a4639162 | |||
39d02a67d2 | |||
77d45bf116 | |||
f79182852b | |||
a107ad7404 | |||
7a072931df | |||
f428a9bf52 | |||
2e720b48d9 | |||
a6e79bedf5 | |||
a5ba570fdf | |||
0d5164af02 | |||
534af770f8 | |||
619a9892e5 | |||
63b109f23e | |||
79ed377c7a | |||
2da8ce7a20 | |||
959dd4cbf1 | |||
0a3788f9ac | |||
cdda74ef93 | |||
bbe428a874 | |||
755919c496 | |||
88b216a17b | |||
8020912448 | |||
5571ae05b5 | |||
bc985198a0 | |||
27b2710c56 | |||
1755b25808 | |||
a78133d0e3 | |||
a51a16a55c | |||
099562d582 | |||
ae76e8f08f | |||
42a08e7e4a | |||
b193d9f919 | |||
24d64eedab | |||
0c9d16f1ef | |||
d246933e3e | |||
41d7b8c0e4 | |||
2622c34542 | |||
5c9419b55c | |||
83c40f4502 | |||
372202b3dc | |||
917c744266 | |||
2281fe4e67 | |||
cf538d83bf | |||
7e9c38a9d9 | |||
ccad97727f | |||
e3ebf8bb61 | |||
cb6b59a52a | |||
53dbbd5f97 | |||
51908ec45a | |||
a2543ff80d | |||
20f3030709 | |||
3aa58f54dc | |||
6e08b622b3 | |||
5c12a95151 | |||
604900d4c5 | |||
5f07c5df1c | |||
6422ed7722 | |||
5f33ef35e3 | |||
ec949840b2 | |||
e45e84b236 | |||
dfe68c9788 | |||
35b8f0bf25 | |||
d4dc1c8a0c | |||
517f9a3c3a | |||
636c35dcf1 | |||
d22f0da1de | |||
310de5a2b2 | |||
06a9c98ded | |||
5c7b05c2ba | |||
9c86763322 | |||
35490ca41c | |||
47c7872c88 | |||
f84abcd1fe | |||
b5dd147ec7 | |||
f8ce01e2fd | |||
3907a2b6ba | |||
0dd68e587f | |||
1171cd2493 | |||
330e93e5e3 | |||
e2579e0a2a | |||
2020fcd18e | |||
5a9a576bfa | |||
b8caf34e62 | |||
456d9a722a | |||
344c980cff | |||
167492087f | |||
db215283a2 | |||
6a507bb149 | |||
dc757c25c8 | |||
73267ae077 | |||
a72e6ee706 | |||
8ec0a4d0d6 | |||
89ea7f0a76 | |||
df65f1009c | |||
a6ca37429c | |||
4bf9fb278b | |||
15d81154e6 | |||
93e5c80962 | |||
c19ab97610 | |||
dbaf851be7 | |||
7aa8bd18d3 | |||
53067c26d7 | |||
04581e2700 | |||
93597dcd50 | |||
778814a35e | |||
3cd1da196a | |||
365d4a1592 | |||
2d7f37ac47 | |||
50fcdd6e7e | |||
10317527e4 | |||
46ac4cbca1 | |||
19bd283807 | |||
20d1a048dd | |||
15b76a24b7 | |||
2d51971b84 | |||
f08e411cad | |||
870c66d1fe | |||
1a467420e3 | |||
02fcfcc383 | |||
09961b5cd0 | |||
71a472e0eb | |||
e65bc5c3ae | |||
2ae37cc1c5 | |||
9a67192f74 | |||
4cfceeeb8e | |||
3e4e0d51df | |||
cb76f8a5df | |||
0591458ef6 | |||
eeddaced9f | |||
c237ff538c | |||
23b00bea5b | |||
cde2c10c1d | |||
87fb42cabd | |||
6566dd8c8f | |||
1e65ac0d85 | |||
cb247a5f28 | |||
bb048fb361 | |||
9e993aa83f | |||
fab12707ae | |||
51f299f196 | |||
2bb52cf811 | |||
6afc689529 | |||
5a17a0d1aa | |||
b38c81c96f | |||
0fabc0c199 | |||
ec5598dbb1 | |||
7b98d203f4 | |||
4635b93f4d | |||
a8433697ad | |||
680cdb8e3e | |||
eaa78fe849 | |||
eedea4998b | |||
4e5a80c481 | |||
4d54dc30c1 | |||
ac5339414a | |||
9fd922fe6a | |||
fdaf9dce73 | |||
732e27751c | |||
932c489de1 |
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
|
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.
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -41,3 +41,9 @@ test_data/*
|
||||
|
||||
# Doxygen
|
||||
Documentation/*
|
||||
|
||||
# PHPUnit
|
||||
/.phpunit.result.cache
|
||||
|
||||
# php-cs-fixer
|
||||
/.php_cs.cache
|
||||
|
8
.gitmodules
vendored
8
.gitmodules
vendored
@ -1,12 +1,12 @@
|
||||
[submodule "src/pocketmine/lang/locale"]
|
||||
path = src/pocketmine/lang/locale
|
||||
url = https://github.com/pmmp/PocketMine-Language.git
|
||||
url = https://github.com/pmmp/Language.git
|
||||
[submodule "tests/preprocessor"]
|
||||
path = build/preprocessor
|
||||
url = https://github.com/pmmp/preprocessor.git
|
||||
[submodule "tests/plugins/PocketMine-DevTools"]
|
||||
path = tests/plugins/PocketMine-DevTools
|
||||
url = https://github.com/pmmp/PocketMine-DevTools.git
|
||||
[submodule "tests/plugins/DevTools"]
|
||||
path = tests/plugins/DevTools
|
||||
url = https://github.com/pmmp/DevTools.git
|
||||
[submodule "build/php"]
|
||||
path = build/php
|
||||
url = https://github.com/pmmp/php-build-scripts.git
|
||||
|
27
.travis.yml
27
.travis.yml
@ -1,22 +1,5 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
# - pecl install channel://pecl.php.net/pthreads-3.1.6
|
||||
- echo | pecl install channel://pecl.php.net/yaml-2.0.4
|
||||
- git clone https://github.com/pmmp/pthreads.git
|
||||
- cd pthreads
|
||||
- git checkout 1b7da492b944146fa9680f6399bd9c6c6c6095e0
|
||||
- phpize
|
||||
- ./configure
|
||||
- make
|
||||
- make install
|
||||
- cd ..
|
||||
- echo "extension=pthreads.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
import:
|
||||
source: ./tests/travis/setup-php.yml
|
||||
|
||||
script:
|
||||
- composer install --prefer-dist
|
||||
@ -31,4 +14,8 @@ cache:
|
||||
- $HOME/.composer/cache/vcs
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
email:
|
||||
recipients:
|
||||
- team@pmmp.io
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
@ -2,7 +2,7 @@
|
||||
## Pre-requisites
|
||||
- A bash shell (git bash is sufficient for Windows)
|
||||
- [`git`](https://git-scm.com) available in your shell
|
||||
- PHP 7.2 or newer available in your shell
|
||||
- PHP 7.3 or newer available in your shell
|
||||
- [`composer`](https://getcomposer.org) available in your shell
|
||||
|
||||
## Custom PHP binaries
|
||||
@ -30,9 +30,9 @@ 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` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
|
||||
Run `build/server-phar.php` 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.
|
||||
|
||||
## Running PocketMine-MP from source code
|
||||
Run `src/pocketmine/PocketMine.php` using your preferred PHP binary.
|
||||
Run `src/pocketmine/PocketMine.php` using your preferred PHP binary.
|
||||
|
@ -116,7 +116,7 @@ class ExampleClass{
|
||||
<!-- TODO: RFC and voting on the forums instead -->
|
||||
### RFC and Voting
|
||||
* These are big Pull Requests or contributions that change important behavior.
|
||||
* RFCs will be tagged with the *PR: RFC* label
|
||||
* RFCs will be tagged with the *Type: Request For Comments* label
|
||||
* A vote will be held once the RFC is ready. All users can vote commenting on the Pull Request
|
||||
* Comments MUST use "Yes" or "No" on the FIRST sentence to signify the vote, except when they don't want it to be counted.
|
||||
* If your comment is a voting comment, specify the reason of your vote or it won't be counted.
|
||||
|
@ -3,7 +3,7 @@
|
||||
<b>A highly customisable, open source server software for Minecraft: Bedrock Edition written in PHP</b>
|
||||
</p>
|
||||
|
||||
[](https://travis-ci.org/pmmp/PocketMine-MP)
|
||||
[](https://travis-ci.com/pmmp/PocketMine-MP)
|
||||
|
||||
## Getting started
|
||||
- [Documentation](http://pmmp.readthedocs.org/)
|
||||
@ -19,7 +19,7 @@
|
||||
## For developers
|
||||
* [Building and running from source](BUILDING.md)
|
||||
* [Latest API documentation](https://jenkins.pmmp.io/job/PocketMine-MP-doc/doxygen/) - Doxygen documentation generated from development
|
||||
* [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
|
||||
* [DevTools](https://github.com/pmmp/DevTools/) - Development tools plugin for creating plugins
|
||||
* [ExamplePlugin](https://github.com/pmmp/ExamplePlugin/) - Example plugin demonstrating some basic API features
|
||||
* [Contributing Guidelines](CONTRIBUTING.md)
|
||||
|
||||
|
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.
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\build\make_release;
|
||||
|
||||
use pocketmine\utils\VersionString;
|
||||
use function defined;
|
||||
use function dirname;
|
||||
use function fgets;
|
||||
use function file_get_contents;
|
||||
@ -37,7 +38,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(
|
||||
|
Submodule build/php updated: 61e20ab9e3...e45cfc1ece
@ -49,7 +49,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
function preg_quote_array(array $strings, string $delim = null) : array{
|
||||
function preg_quote_array(array $strings, string $delim) : array{
|
||||
return array_map(function(string $str) use ($delim) : string{ return preg_quote($str, $delim); }, $strings);
|
||||
}
|
||||
|
||||
@ -130,9 +130,13 @@ function main() : void{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$opts = getopt("", ["out:"]);
|
||||
$gitHash = Git::getRepositoryStatePretty(dirname(__DIR__));
|
||||
echo "Git hash detected as $gitHash" . PHP_EOL;
|
||||
$opts = getopt("", ["out:", "git:"]);
|
||||
if(isset($opts["git"])){
|
||||
$gitHash = $opts["git"];
|
||||
}else{
|
||||
$gitHash = Git::getRepositoryStatePretty(dirname(__DIR__));
|
||||
echo "Git hash detected as $gitHash" . PHP_EOL;
|
||||
}
|
||||
foreach(buildPhar(
|
||||
$opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
|
||||
dirname(__DIR__) . DIRECTORY_SEPARATOR,
|
||||
|
@ -65,7 +65,7 @@ Plugin developers should **only** update their required API to this version if y
|
||||
- `build/server-phar.php` now uses GZIP compression on created phars, providing a 75% size reduction.
|
||||
- `ClientboundMapItemDataPacket` now uses `MapDecoration` objects for decorations instead of associative arrays.
|
||||
- Updated Composer dependencies to get bug fixes in `pocketmine/nbt` and other libraries.
|
||||
- Packages `pocketmine/classloader` and `pockegtmine/log` are now required; these provide classes previously part of `pocketmine/spl`. This change has no effect on API compatibility.
|
||||
- Packages `pocketmine/classloader` and `pocketmine/log` are now required; these provide classes previously part of `pocketmine/spl`. This change has no effect on API compatibility.
|
||||
|
||||
# 3.11.6
|
||||
- Core code, tests and build scripts are now analyzed using `phpstan-strict-rules` and `phpstan-phpunit` rules.
|
||||
@ -79,3 +79,19 @@ Plugin developers should **only** update their required API to this version if y
|
||||
- `ThreadManager` is now lazily initialized.
|
||||
- Removed raw NBT storage from `Item` internals. The following methods are now deprecated:
|
||||
- `Item::setCompoundTag()`
|
||||
|
||||
# 3.11.7
|
||||
- Build system: Fixed crash reports of Jenkins builds being rejected by the crash archive as invalid.
|
||||
- Introduced a new dependency on `pocketmine/log-pthreads`, which contains classes separated from `pocketmine/log`.
|
||||
- Fixed minimum composer stability preventing any newer version of `pocketmine/pocketmine-mp` being installed than 3.3.4 by replacing `daverandom/callback-validator` with [`pocketmine/callback-validator`](https://github.com/pmmp/CallbackValidator).
|
||||
- Fixed every player seeing eating particles when any player eats.
|
||||
- Fixed setting held item not working during `BlockBreakEvent`, `PlayerInteractEvent` and `EntityDamageEvent`.
|
||||
- Fixed some incorrect documented types in `PlayerQuitEvent` reported by PHPStan.
|
||||
- Fixed documentation of `Item->pop()` return value.
|
||||
- Fixed server crash on encountering corrupted compressed data stored in region files.
|
||||
- Protocol: Split screen header is now properly accounted for during decoding. Note that split screen is still not supported natively, but their packets can be decoded properly now.
|
||||
- Protocol: Fixed wrong order of fields in `UpdateTradePacket`.
|
||||
- Protocol: Fixed loss of `fullSkinId` when decoding network skins.
|
||||
- Fixed RCON not being able to bind to port after a fast server restart.
|
||||
|
||||
|
||||
|
58
changelogs/3.12.md
Normal file
58
changelogs/3.12.md
Normal file
@ -0,0 +1,58 @@
|
||||
**For Minecraft: Bedrock Edition 1.14.60**
|
||||
|
||||
### 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.12.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.14.60
|
||||
- Removed compatibility with 1.14.0-1.14.30
|
||||
|
||||
# 3.12.1
|
||||
- Fixed parsing of single-line doc comments for event handlers, e.g. `/** @ignoreCancelled */` should now work correctly.
|
||||
- The server will no longer crash on failure to load `level.dat` contents, but will gracefully shutdown instead without producing a crashdump.
|
||||
- Fixed some bugs in login verification that could cause undefined behaviour.
|
||||
- Fixed item-use behaviour when sneaking - sneaking and clicking a block with an empty hand, and sneaking and using an item, both now follow vanilla behaviour.
|
||||
- `start.sh` will now work on platforms where `/bin/bash` is not available, as long as `/usr/bin/env` knows where bash is.
|
||||
|
||||
# 3.12.2
|
||||
- Fixed permission default timings not being reported in timings reports (they were never stopped, only started).
|
||||
- Resource packs with a directory tree like `pack.zip/MyPack/manifest.json` are now supported. Note that the manifest closest to the root will be used.
|
||||
- Fixed `SkinImage` height and width being inverted at the protocol layer.
|
||||
- Fixed blocks being able to be placed inside the spawn protection radius by clicking the side of a block outside the radius.
|
||||
- Fixed server crash when `network.compression-level` is overridden by a CLI parameter.
|
||||
- Fixed moving entities spawning themselves to players registered on chunks when the players haven't received the chunk yet.
|
||||
- Cocoa pods now drop cocoa beans when broken instead of the block itself.
|
||||
|
||||
# 3.12.3
|
||||
- Core code is now analyzed using PHPStan level 8 (using baselines). While not all the code is level 8 compliant, this does mean that new code will be held to a higher standard, ensuring quality going forwards.
|
||||
- Players no longer burn when melee-attacked by other players. (vanilla parity)
|
||||
- Arrows shot by burning players are no longer on fire. (vanilla parity)
|
||||
- Fixed a crash that could occur with plugins on Unix filesystems that had backslashes in their names.
|
||||
- Cleaned up a whole bunch of unknowns in the protocol layer. Many new constants have been added.
|
||||
- Fixed player walking sounds.
|
||||
- Default generation queue size has been raised to 32 (previously 8). The previous default was selected in a time when PHP was much less performant than it is today, and in today's world it just needlessly slows things down.
|
||||
- Double plants are now burned away by fire.
|
||||
- Snow layers can now be stacked. (vanilla parity)
|
||||
- Resource pack sending chunk size has been reduced to 128 KB (previously 1 MB). This change was made after analyzing the effects that larger pack chunk sizes have on RakNet. Given the technical evidence, a smaller size, while slightly less bandwidth-efficient, should be more manageable for RakNet due to lower split reassembly overhead and reduced memory pressure.
|
||||
- Fixed "switching" (an exploit often complained about by PvP players). Now, the previous damage is subtracted from current damage when an entity is attacked while on cooldown. This means that attacking with a wooden sword and then diamond sword while attack cooldown is active will only deal as much damage as the diamond sword would have, instead of the combined total. This can be controlled using the `EntityDamageEvent::MODIFIER_PREVIOUS_DAMAGE_COOLDOWN` modifier. (vanilla parity)
|
||||
- Fixed projectiles knocking mobs back in unexpected directions on collision.
|
||||
- Fixed inventories not being synchronized on failed inventory transactions.
|
||||
- Vector3s decoded from packets are no longer rounded directly. Instead, the player movement handler takes responsibility for rounding the coordinates to prevent anti cheat doing something it's not supposed to.
|
||||
- `mobflame` particle can now be spawned using the `/particle` command.
|
||||
- Fixed several internal errors that could occur while modifying writable books.
|
||||
- Fixed swapping writable book pages not working in some cases.
|
||||
- `WritableBook->getPageText()` no longer throws an exception when the page doesn't exist, but returns null (as it was originally intended to).
|
||||
|
||||
# 3.12.4
|
||||
- Fixed absorption hearts not being consumed.
|
||||
|
||||
# 3.12.5
|
||||
- Fixed broken attack cooldowns.
|
||||
|
||||
# 3.12.6
|
||||
- Fixed entities not getting movement updates after teleports.
|
||||
- Fixed slow flight in spectator mode when starting from the ground and after teleportation.
|
||||
- Errors communicating with the crash archive on automatic crash submission are now logged.
|
131
changelogs/3.13.md
Normal file
131
changelogs/3.13.md
Normal file
@ -0,0 +1,131 @@
|
||||
**For Minecraft: Bedrock Edition 1.14.60**
|
||||
|
||||
This is a feature release, containing various minor API additions, deprecations and a few minor features.
|
||||
|
||||
### 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.13.0
|
||||
## Core
|
||||
- PHP 7.3.0 or newer is now required.
|
||||
- Player movement processing has been revamped. It's now more tolerant of network lag and doesn't have as many problems with falling.
|
||||
|
||||
## User Interface
|
||||
- `/time` now supports additional aliases `noon`, `sunset`, `midnight` and `sunrise`.
|
||||
- Removed warnings when a plugin registers a handler for a deprecated event. Since this warning is developer-focused, and too specific to be useful, it just caused annoyance and confusion to users who didn't know what it meant.
|
||||
|
||||
## API
|
||||
### General
|
||||
- It's now possible to require a specific operating system using the `os` directive in `plugin.yml`. More information about this directive can be found in the [developer documentation](https://github.com/pmmp/DeveloperDocs).
|
||||
|
||||
### Player
|
||||
- `Player->resetItemCooldown()` now accepts a second parameter, allowing plugins to provide a custom duration.
|
||||
- The following methods have been deprecated and have recommended replacements:
|
||||
- `Player->addTitle()` -> `Player->sendTitle()`
|
||||
- `Player->addSubTitle()` -> `Player->sendSubTitle()`
|
||||
- `Player->addActionBarMessage()` -> `Player->sendActionBarMessage()`
|
||||
|
||||
### Event
|
||||
- The following methods have been deprecated:
|
||||
- `EntityDespawnEvent->getType()`
|
||||
- `EntityDespawnEvent->getPosition()`
|
||||
- `EntityDespawnEvent->isCreature()`
|
||||
- `EntityDespawnEvent->isHuman()`
|
||||
- `EntityDespawnEvent->isProjectile()`
|
||||
- `EntityDespawnEvent->isVehicle()`
|
||||
- `EntityDespawnEvent->isItem()`
|
||||
- `EntitySpawnEvent->getType()`
|
||||
- `EntitySpawnEvent->getPosition()`
|
||||
- `EntitySpawnEvent->isCreature()`
|
||||
- `EntitySpawnEvent->isHuman()`
|
||||
- `EntitySpawnEvent->isProjectile()`
|
||||
- `EntitySpawnEvent->isVehicle()`
|
||||
- `EntitySpawnEvent->isItem()`
|
||||
- Added the following API methods:
|
||||
- `EntityDeathEvent->getXpDropAmount()`
|
||||
- `EntityDeathEvent->setXpDropAmount()`
|
||||
- `PlayerDeathEvent::__construct()` now accepts a fourth (optional) parameter `int $xp`.
|
||||
- `EntityDeathEvent::__construct()` now accepts a third (optional) parameter `int $xp`.
|
||||
|
||||
### Inventory
|
||||
- The following classes have been deprecated:
|
||||
- `Recipe`
|
||||
- The following methods have been deprecated:
|
||||
- `CraftingManager->registerRecipe()`
|
||||
- `Recipe->registerToCraftingManager()` (and all its implementations)
|
||||
|
||||
### Item
|
||||
- New `Enchantment` type ID constants have been added.
|
||||
- `ItemFactory::fromStringSingle()` has been added. This works exactly the same as `ItemFactory::fromString()`, but it has a return type of `Item` instead of `Item|Item[]` (more static analysis friendly).
|
||||
|
||||
### Level
|
||||
- Added the following API methods:
|
||||
- `Position->getLevelNonNull()`: this is the same as `Position->getLevel()`, but throws an `AssumptionFailedError` if the level is null or invalid (more static analysis friendly).
|
||||
- `Level->getTimeOfDay()`
|
||||
- The following constants have been changed:
|
||||
- `Level::TIME_DAY` now has a value of `1000`
|
||||
- `Level::TIME_NIGHT` now has a value of `13000`
|
||||
- Added the following constants:
|
||||
- `Level::TIME_MIDNIGHT`
|
||||
- `Level::TIME_NOON`
|
||||
- The following types of particles now accept optional `Color` parameters in the constructor:
|
||||
- `EnchantParticle`
|
||||
- `InstantEnchantParticle`
|
||||
|
||||
### Network
|
||||
- Added the following API methods:
|
||||
- `RakLibInterface->setPacketLimit()`
|
||||
|
||||
### Scheduler
|
||||
AsyncTask thread-local storage has been improved, making it simpler and easier to use.
|
||||
- `AsyncTask->fetchLocal()` no longer deletes stored thread-local data. Instead, the storage behaves more like properties, and gets deleted when the AsyncTask object goes out of scope.
|
||||
- `AsyncTask->peekLocal()` has been `@deprecated` (use `fetchLocal()` instead).
|
||||
- Notices are no longer emitted if an async task doesn't fetch its locally stored data.
|
||||
- The following methods have been deprecated:
|
||||
- `AsyncTask->getFromThreadStore()` (use its worker's corresponding method)
|
||||
- `AsyncTask->saveToThreadStore()` (use its worker's corresponding method)
|
||||
- `AsyncTask->removeFromThreadStore()` (use its worker's corresponding method)
|
||||
|
||||
### Utils
|
||||
- The following functions have been deprecated and have recommended replacements:
|
||||
- `Utils::getMemoryUsage()` -> split into `Process::getMemoryUsage()` and `Process::getAdvancedMemoryUsage()` (not 1:1 replacement!!)
|
||||
- `Utils::getRealMemoryUsage()` -> `Process::getRealMemoryUsage()`
|
||||
- `Utils::getThreadCount()` -> `Process::getThreadCount()`
|
||||
- `Utils::kill()` -> `Process::kill()`
|
||||
- `Utils::execute()` -> `Process::execute()`
|
||||
- Added the following constants:
|
||||
- `Utils::OS_WINDOWS`
|
||||
- `Utils::OS_IOS`
|
||||
- `Utils::OS_MACOS`
|
||||
- `Utils::OS_ANDROID`
|
||||
- `Utils::OS_LINUX`
|
||||
- `Utils::OS_BSD`
|
||||
- `Utils::OS_UNKNOWN`
|
||||
- Added the following API methods:
|
||||
- `Config->getPath()`
|
||||
- `Utils::recursiveUnlink()`
|
||||
- `Terminal::write()`
|
||||
- `Terminal::writeLine()`
|
||||
|
||||
# 3.13.1
|
||||
- Fixed issues with `server.lock` not being unlocked on some platforms. Now, the server explicitly releases it before exiting.
|
||||
- `/timings` now sends a usage message when using an unknown subcommand. Previously, it would just give no output.
|
||||
- `/whitelist` now sends a usage message when using an unknown subcommand. Previously, it would just give no output.
|
||||
- The output from `/timings` is now broadcasted on the `pocketmine.broadcast.admin` broadcast channel for auditability, similarly to other operator commands.
|
||||
- Fixed `ShapedRecipe` deprecation warning on PHP 7.4.
|
||||
- Fixed some potential crashes with Bedrock worlds when chunk data is corrupted or missing.
|
||||
- Fixed a bug in region handling that caused region loaders to overestimate the amount of space used in the file. This resulted in an up to 4 MB growth of the file size every time the region was reloaded after writing a chunk.
|
||||
- Region handlers now try to reuse free space in region files before putting the chunk at the end of the file. Previously, space was only reused if the new version of the chunk was <= the size of the old. This fixes endless growth of region files.
|
||||
- Regions now never directly overwrite old copies of chunks when saving; instead they try to find an alternative location (preferring unused space within the file first). This avoids chunk corruption on power failure (the old copy of the chunk won't be damaged, so a rollback might occur instead), and as happy side effect, causes oversized regions to gradually shrink towards their most packed state over time, saving disk space.
|
||||
- Regions now have a hard size cap at 64 GB. This is because the header pointers will overflow beyond 64 GB (besides, a normal region shouldn't be this big anyway).
|
||||
- Fixed a crash that could occur when reading a too-short region header.
|
||||
- `VerifyLoginTask` now only copies JWTs to verify instead of the entire login packet. This reduces the amount of data copied between threads, improving performance.
|
||||
- Added a fast-fail check to `VerifyLoginTask` by checking the JWT header's `x5u` against the expected public key.
|
||||
- `Skin->validate()` now throws `InvalidSkinException` instead of `\InvalidArgumentException`.
|
||||
- A debug message is now logged when a player is kicked for having an invalid skin, giving a brief line of detail why.
|
||||
- Fixed players not being kicked for having an invalid `resourcePatch`.
|
||||
- Fixed block meta value of cake being preserved when using pick-block.
|
||||
- Fixed explosions not fully destroying multi-block objects like beds and doors.
|
40
changelogs/3.14.md
Normal file
40
changelogs/3.14.md
Normal file
@ -0,0 +1,40 @@
|
||||
**For Minecraft: Bedrock Edition 1.16.0**
|
||||
|
||||
### 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.14.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.0.
|
||||
- Removed compatibility with 1.14.60.
|
||||
|
||||
## 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.14.1
|
||||
- All skins are now trusted, bypassing the client-side trusted skin setting. Note that this means that NSFW skin filtering will **not** apply.
|
||||
- Fixed projectile motion being altered by ladders.
|
||||
- Fixed client-sided crashes when pressing E repeatedly very quickly on a high-latency connection.
|
||||
- `/plugins`, `/whitelist list`, `/banlist` and `/list` now show output in alphabetical order.
|
||||
- Some `pocketmine\event` APIs which accept arrays now have more robust type checking, fixing type errors caused by plugin input occurring in core code.
|
||||
- `Attribute::getAttributeByName()` is now aware of the `minecraft:lava_movement` attribute.
|
||||
|
||||
# 3.14.2
|
||||
- Exception stack traces are now logged as CRITICAL. It's hoped that users will recognize that they are just as important as the error message and not leave them out when asking for help with errors on Discord.
|
||||
- `TaskScheduler` no longer accepts tasks that already have a handler. This fixes undefined behaviour which occurs when scheduling the same task instance twice, but it does break plugins such as **MyPlot** which unintentionally used this buggy behaviour.
|
||||
- Players will now correctly receive the needed number of spawn chunks if they are teleported between `PlayerLoginEvent` and `PlayerJoinEvent`. This fixes a bug that could occur when teleporting players in delayed tasks between login and join.
|
||||
- `PlayerRespawnEvent->setRespawnPosition()` now throws an exception if the provided `Position` has an invalid world associated with it (null or unloaded).
|
||||
- Fixed a crash that occurred when stats reporting was enabled.
|
||||
|
||||
# 3.14.3
|
||||
- Fixed deprecation error when running `/whitelist list` on PHP 7.4.
|
||||
- Fixed podzol breaking animation being incorrect (incorrect hardness).
|
||||
- `Entity::getSaveId()` now reports the class name in the message thrown for unregistered entities.
|
||||
- Fixed `CraftingManager->validate()` producing different results when called multiple times for the same transaction.
|
||||
- Fixed various issues with batch-crafting items using the recipe book and shift-clicking.
|
||||
- `tests/plugins/PocketMine-DevTools` submodule has been renamed to `tests/plugins/DevTools`.
|
34
changelogs/3.15.md
Normal file
34
changelogs/3.15.md
Normal file
@ -0,0 +1,34 @@
|
||||
**For Minecraft: Bedrock Edition 1.16.20**
|
||||
|
||||
### 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.15.0
|
||||
- Added support for Minecraft: Bedrock Edition 1.16.20.
|
||||
- Removed compatibility with 1.16.0.
|
||||
|
||||
## 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.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`.
|
@ -5,7 +5,7 @@
|
||||
"homepage": "https://pmmp.io",
|
||||
"license": "LGPL-3.0",
|
||||
"require": {
|
||||
"php": ">=7.2.0",
|
||||
"php": ">=7.3.0",
|
||||
"php-64bit": "*",
|
||||
"ext-bcmath": "*",
|
||||
"ext-curl": "*",
|
||||
@ -31,15 +31,17 @@
|
||||
"pocketmine/math": "^0.2.0",
|
||||
"pocketmine/snooze": "^0.1.0",
|
||||
"pocketmine/classloader": "^0.1.0",
|
||||
"pocketmine/log": "^0.1.0",
|
||||
"daverandom/callback-validator": "dev-master",
|
||||
"adhocore/json-comment": "^0.1.0"
|
||||
"pocketmine/log": "^0.2.0",
|
||||
"pocketmine/log-pthreads": "^0.1.0",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"adhocore/json-comment": "^0.1.0",
|
||||
"composer-runtime-api": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.9",
|
||||
"irstea/phpunit-shim": "^8.5",
|
||||
"phpstan/phpstan": "0.12.54",
|
||||
"phpstan/phpstan-phpunit": "^0.12.6",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2"
|
||||
"phpstan/phpstan-strict-rules": "^0.12.2",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -55,5 +57,10 @@
|
||||
"psr-4": {
|
||||
"pocketmine\\": "tests/phpunit/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2462
composer.lock
generated
2462
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,6 @@ This site contains auto-generated API documentation for PocketMine-MP (and depen
|
||||
This site can be accessed via https://apidoc.pmmp.io.
|
||||
|
||||
### Additional developer resources
|
||||
- [DevTools](https://github.com/pmmp/PocketMine-DevTools/) - Development tools plugin for creating plugins
|
||||
- [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
|
||||
|
@ -1,7 +1,10 @@
|
||||
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
|
||||
- tests/phpstan/configs/php-bugs.neon
|
||||
- tests/phpstan/configs/phpstan-bugs.neon
|
||||
- tests/phpstan/configs/phpunit-wiring-tests.neon
|
||||
@ -12,177 +15,29 @@ includes:
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
|
||||
parameters:
|
||||
level: 6
|
||||
autoload_files:
|
||||
level: 8
|
||||
checkExplicitMixed: true
|
||||
bootstrapFiles:
|
||||
- tests/phpstan/bootstrap.php
|
||||
scanDirectories:
|
||||
- tests/plugins/TesterPlugin
|
||||
scanFiles:
|
||||
- src/pocketmine/PocketMine.php
|
||||
- build/make-release.php
|
||||
- build/server-phar.php
|
||||
- vendor/irstea/phpunit-shim/phpunit
|
||||
paths:
|
||||
- src
|
||||
- build/make-release.php
|
||||
- build/server-phar.php
|
||||
- tests/phpunit
|
||||
- tests/plugins/TesterPlugin
|
||||
dynamicConstantNames:
|
||||
- pocketmine\IS_DEVELOPMENT_BUILD
|
||||
- pocketmine\DEBUG
|
||||
stubFiles:
|
||||
- tests/phpstan/stubs/pthreads.stub
|
||||
- tests/phpstan/stubs/chunkutils.stub
|
||||
- tests/phpstan/stubs/leveldb.stub
|
||||
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Instanceof between pocketmine\\\\plugin\\\\PluginManager and pocketmine\\\\plugin\\\\PluginManager will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/CrashDump.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\Player\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\entity\\\\Human\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Player.php
|
||||
|
||||
-
|
||||
message: "#^Cannot instantiate interface pocketmine\\\\level\\\\format\\\\io\\\\LevelProvider\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Server.php
|
||||
|
||||
-
|
||||
message: "#^Instanceof between pocketmine\\\\plugin\\\\PluginManager and pocketmine\\\\plugin\\\\PluginManager will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Server.php
|
||||
|
||||
-
|
||||
message: "#^Instanceof between pocketmine\\\\scheduler\\\\AsyncPool and pocketmine\\\\scheduler\\\\AsyncPool will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Server.php
|
||||
|
||||
-
|
||||
message: "#^Instanceof between pocketmine\\\\command\\\\CommandReader and pocketmine\\\\command\\\\CommandReader will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Server.php
|
||||
|
||||
-
|
||||
message: "#^Instanceof between pocketmine\\\\network\\\\Network and pocketmine\\\\network\\\\Network will always evaluate to true\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/Server.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\block\\\\[A-Za-z\\d]+\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\block\\\\Block\\.$#"
|
||||
path: src/pocketmine/block
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\block\\\\Block\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\level\\\\Position\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/block/Block.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method pocketmine\\\\command\\\\CommandSender\\:\\:teleport\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/command/defaults/TeleportCommand.php
|
||||
# comment: "not actually possible, but high cost to fix warning"
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\EntityDeathEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\Living but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/EntityDeathEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\EntityShootBowEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\Living but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/EntityShootBowEvent.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\event\\\\entity\\\\EntityShootBowEvent\\:\\:\\$projectile \\(pocketmine\\\\entity\\\\projectile\\\\Projectile\\) does not accept pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/EntityShootBowEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\ItemDespawnEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\object\\\\ItemEntity but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/ItemDespawnEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\ItemSpawnEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\object\\\\ItemEntity but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/ItemSpawnEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\ProjectileHitEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\projectile\\\\Projectile but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/ProjectileHitEvent.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\event\\\\entity\\\\ProjectileLaunchEvent\\:\\:getEntity\\(\\) should return pocketmine\\\\entity\\\\projectile\\\\Projectile but returns pocketmine\\\\entity\\\\Entity\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/event/entity/ProjectileLaunchEvent.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\inventory\\\\DoubleChestInventory\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\inventory\\\\ChestInventory\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/inventory/DoubleChestInventory.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\inventory\\\\EnderChestInventory\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\inventory\\\\ChestInventory\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/inventory/EnderChestInventory.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\item\\\\GoldenAppleEnchanted\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\item\\\\GoldenApple\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/item/GoldenAppleEnchanted.php
|
||||
|
||||
-
|
||||
message: "#^pocketmine\\\\item\\\\WrittenBook\\:\\:__construct\\(\\) does not call parent constructor from pocketmine\\\\item\\\\WritableBook\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/item/WrittenBook.php
|
||||
|
||||
-
|
||||
message: "#^Variable property access on \\$this\\(pocketmine\\\\level\\\\generator\\\\PopulationTask\\)\\.$#"
|
||||
count: 4
|
||||
path: src/pocketmine/level/generator/PopulationTask.php
|
||||
|
||||
-
|
||||
message: "#^Constructor of class pocketmine\\\\level\\\\generator\\\\hell\\\\Nether has an unused parameter \\$options\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/level/generator/hell/Nether.php
|
||||
|
||||
-
|
||||
message: "#^Constructor of class pocketmine\\\\level\\\\generator\\\\normal\\\\Normal has an unused parameter \\$options\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/level/generator/normal/Normal.php
|
||||
|
||||
-
|
||||
message: "#^Variable method call on pocketmine\\\\event\\\\Listener\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/plugin/MethodEventExecutor.php
|
||||
|
||||
-
|
||||
message: "#^Constructor of class pocketmine\\\\scheduler\\\\TaskScheduler has an unused parameter \\$logger\\.$#"
|
||||
count: 1
|
||||
path: src/pocketmine/scheduler/TaskScheduler.php
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\COMPOSER_AUTOLOADER_PATH not found\\.$#"
|
||||
path: src
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\DATA not found\\.$#"
|
||||
path: src
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\GIT_COMMIT not found\\.$#"
|
||||
path: src
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\PLUGIN_PATH not found\\.$#"
|
||||
path: src
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\START_TIME not found\\.$#"
|
||||
path: src
|
||||
|
||||
-
|
||||
message: "#^Constant pocketmine\\\\VERSION not found\\.$#"
|
||||
path: src
|
||||
|
||||
staticReflectionClassNamePatterns:
|
||||
- "#^COM$#"
|
||||
|
@ -23,6 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use function define;
|
||||
use function defined;
|
||||
use function dirname;
|
||||
|
||||
// composer autoload doesn't use require_once and also pthreads can inherit things
|
||||
if(defined('pocketmine\_CORE_CONSTANTS_INCLUDED')){
|
||||
return;
|
||||
|
@ -23,13 +23,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\plugin\PluginBase;
|
||||
use pocketmine\plugin\PluginLoadOrder;
|
||||
use pocketmine\plugin\PluginManager;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\VersionString;
|
||||
use raklib\RakLib;
|
||||
use function base64_encode;
|
||||
use function date;
|
||||
use function error_get_last;
|
||||
@ -54,6 +54,7 @@ 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;
|
||||
@ -88,7 +89,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 = 2;
|
||||
private const FORMAT_VERSION = 3;
|
||||
|
||||
private const PLUGIN_INVOLVEMENT_NONE = "none";
|
||||
private const PLUGIN_INVOLVEMENT_DIRECT = "direct";
|
||||
@ -117,10 +118,11 @@ class CrashDump{
|
||||
mkdir($this->server->getDataPath() . "crashdumps");
|
||||
}
|
||||
$this->path = $this->server->getDataPath() . "crashdumps/" . date("D_M_j-H.i.s-T_Y", $this->time) . ".log";
|
||||
$this->fp = @fopen($this->path, "wb");
|
||||
if(!is_resource($this->fp)){
|
||||
$fp = @fopen($this->path, "wb");
|
||||
if(!is_resource($fp)){
|
||||
throw new \RuntimeException("Could not create Crash Dump");
|
||||
}
|
||||
$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));
|
||||
@ -228,7 +230,10 @@ class CrashDump{
|
||||
if(isset($lastExceptionError)){
|
||||
$error = $lastExceptionError;
|
||||
}else{
|
||||
$error = (array) error_get_last();
|
||||
$error = error_get_last();
|
||||
if($error === null){
|
||||
throw new \RuntimeException("Crash error information missing - did something use exit()?");
|
||||
}
|
||||
$error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
|
||||
$errorConversion = [
|
||||
E_ERROR => "E_ERROR",
|
||||
@ -288,9 +293,11 @@ class CrashDump{
|
||||
|
||||
if($this->server->getProperty("auto-report.send-code", true) !== false and file_exists($error["fullFile"])){
|
||||
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
|
||||
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
|
||||
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
|
||||
$this->data["code"][$l + 1] = $file[$l];
|
||||
if($file !== false){
|
||||
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
|
||||
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
|
||||
$this->data["code"][$l + 1] = $file[$l];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,6 +339,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;
|
||||
@ -339,18 +355,22 @@ class CrashDump{
|
||||
$this->data["general"]["is_dev"] = \pocketmine\IS_DEVELOPMENT_BUILD;
|
||||
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
|
||||
$this->data["general"]["git"] = \pocketmine\GIT_COMMIT;
|
||||
$this->data["general"]["raklib"] = RakLib::VERSION;
|
||||
$this->data["general"]["uname"] = php_uname("a");
|
||||
$this->data["general"]["php"] = phpversion();
|
||||
$this->data["general"]["zend"] = zend_version();
|
||||
$this->data["general"]["php_os"] = PHP_OS;
|
||||
$this->data["general"]["os"] = Utils::getOS();
|
||||
$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"));
|
||||
$this->addLine("PHP Version: " . phpversion());
|
||||
$this->addLine("Zend version: " . zend_version());
|
||||
$this->addLine("OS : " . PHP_OS . ", " . Utils::getOS());
|
||||
$this->addLine("Composer libraries: ");
|
||||
foreach($composerLibraries as $library => $libraryVersion){
|
||||
$this->addLine("- $library $libraryVersion");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// composer autoload doesn't use require_once and also pthreads can inherit things
|
||||
if(defined('pocketmine\_GLOBAL_CONSTANTS_INCLUDED')){
|
||||
return;
|
||||
|
@ -27,6 +27,8 @@ use pocketmine\event\server\LowMemoryEvent;
|
||||
use pocketmine\scheduler\DumpWorkerMemoryTask;
|
||||
use pocketmine\scheduler\GarbageCollectionTask;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Process;
|
||||
use pocketmine\utils\Utils;
|
||||
use function arsort;
|
||||
use function count;
|
||||
@ -38,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;
|
||||
@ -48,6 +51,7 @@ use function is_object;
|
||||
use function is_resource;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
use function mb_strtoupper;
|
||||
use function min;
|
||||
use function mkdir;
|
||||
use function preg_match;
|
||||
@ -56,7 +60,6 @@ use function round;
|
||||
use function spl_object_hash;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const JSON_UNESCAPED_SLASHES;
|
||||
@ -125,7 +128,7 @@ class MemoryManager{
|
||||
if($m <= 0){
|
||||
$defaultMemory = 0;
|
||||
}else{
|
||||
switch(strtoupper($matches[2])){
|
||||
switch(mb_strtoupper($matches[2])){
|
||||
case "K":
|
||||
$defaultMemory = $m / 1024;
|
||||
break;
|
||||
@ -225,7 +228,7 @@ class MemoryManager{
|
||||
|
||||
if(($this->memoryLimit > 0 or $this->globalMemoryLimit > 0) and ++$this->checkTicker >= $this->checkRate){
|
||||
$this->checkTicker = 0;
|
||||
$memory = Utils::getMemoryUsage(true);
|
||||
$memory = Process::getAdvancedMemoryUsage();
|
||||
$trigger = false;
|
||||
if($this->memoryLimit > 0 and $memory[0] > $this->memoryLimit){
|
||||
$trigger = 0;
|
||||
@ -271,6 +274,7 @@ class MemoryManager{
|
||||
}
|
||||
|
||||
$cycles = gc_collect_cycles();
|
||||
gc_mem_caches();
|
||||
|
||||
Timings::$garbageCollectorTimer->stopTiming();
|
||||
|
||||
@ -304,6 +308,7 @@ class MemoryManager{
|
||||
*/
|
||||
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger){
|
||||
$hardLimit = ini_get('memory_limit');
|
||||
if($hardLimit === false) throw new AssumptionFailedError("memory_limit INI directive should always exist");
|
||||
ini_set('memory_limit', '-1');
|
||||
gc_disable();
|
||||
|
||||
@ -403,8 +408,8 @@ class MemoryManager{
|
||||
"properties" => []
|
||||
];
|
||||
|
||||
if($reflection->getParentClass()){
|
||||
$info["parent"] = $reflection->getParentClass()->getName();
|
||||
if(($parent = $reflection->getParentClass()) !== false){
|
||||
$info["parent"] = $parent->getName();
|
||||
}
|
||||
|
||||
if(count($reflection->getInterfaceNames()) > 0){
|
||||
|
@ -33,6 +33,7 @@ use pocketmine\entity\Effect;
|
||||
use pocketmine\entity\EffectInstance;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Human;
|
||||
use pocketmine\entity\InvalidSkinException;
|
||||
use pocketmine\entity\object\ItemEntity;
|
||||
use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\entity\Skin;
|
||||
@ -112,6 +113,7 @@ use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\BookEditPacket;
|
||||
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
|
||||
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\network\mcpe\protocol\DataPacket;
|
||||
use pocketmine\network\mcpe\protocol\DisconnectPacket;
|
||||
use pocketmine\network\mcpe\protocol\InteractPacket;
|
||||
@ -146,11 +148,18 @@ 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\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\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinAdapterSingleton;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinAnimation;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinData;
|
||||
use pocketmine\network\mcpe\protocol\types\SkinImage;
|
||||
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
|
||||
use pocketmine\network\mcpe\VerifyLoginTask;
|
||||
@ -168,7 +177,9 @@ use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\UUID;
|
||||
use function abs;
|
||||
use function array_key_exists;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function base64_decode;
|
||||
use function ceil;
|
||||
@ -214,6 +225,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
public const SPECTATOR = 3;
|
||||
public const VIEW = Player::SPECTATOR;
|
||||
|
||||
private const MOVES_PER_TICK = 2;
|
||||
private const MOVE_BACKLOG_SIZE = 100 * self::MOVES_PER_TICK; //100 ticks backlog (5 seconds)
|
||||
|
||||
private const RESOURCE_PACK_CHUNK_SIZE = 128 * 1024; //128KB
|
||||
|
||||
//TODO: HACK!
|
||||
//these IDs are used for 1.16 to restore 1.14ish crafting & inventory behaviour; since they don't seem to have any
|
||||
//effect on the behaviour of inventory transactions I don't currently plan to integrate these into the main system.
|
||||
private const RESERVED_WINDOW_ID_RANGE_START = ContainerIds::LAST - 10;
|
||||
private const RESERVED_WINDOW_ID_RANGE_END = ContainerIds::LAST;
|
||||
public const HARDCODED_CRAFTING_GRID_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 1;
|
||||
public const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
|
||||
|
||||
/**
|
||||
* Validates the given username.
|
||||
*/
|
||||
@ -259,6 +283,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/** @var bool */
|
||||
public $loggedIn = false;
|
||||
|
||||
/** @var bool */
|
||||
private $seenLoginPacket = false;
|
||||
/** @var bool */
|
||||
private $resourcePacksDone = false;
|
||||
|
||||
@ -291,6 +317,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/** @var CraftingTransaction|null */
|
||||
protected $craftingTransaction = null;
|
||||
|
||||
/**
|
||||
* TODO: HACK! This tracks GUIs for inventories that the server considers "always open" so that the client can't
|
||||
* open them twice. (1.16 hack)
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
* @internal
|
||||
*/
|
||||
public $openHardcodedWindows = [];
|
||||
|
||||
/** @var int */
|
||||
protected $messageCounter = 2;
|
||||
/** @var bool */
|
||||
@ -324,10 +359,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/** @var bool[] map: raw UUID (string) => bool */
|
||||
protected $hiddenPlayers = [];
|
||||
|
||||
/** @var float */
|
||||
protected $moveRateLimit = 10 * self::MOVES_PER_TICK;
|
||||
/** @var float|null */
|
||||
protected $lastMovementProcess = null;
|
||||
/** @var Vector3|null */
|
||||
protected $newPosition;
|
||||
/** @var bool */
|
||||
protected $isTeleporting = false;
|
||||
protected $forceMoveSync = null;
|
||||
|
||||
/** @var int */
|
||||
protected $inAirTicks = 0;
|
||||
/** @var float */
|
||||
@ -517,7 +555,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function spawnTo(Player $player) : void{
|
||||
if($this->spawned and $player->spawned and $this->isAlive() and $player->isAlive() and $player->getLevel() === $this->level and $player->canSee($this) and !$this->isSpectator()){
|
||||
if($this->spawned and $player->spawned and $this->isAlive() and $player->isAlive() and $player->getLevelNonNull() === $this->level and $player->canSee($this) and !$this->isSpectator()){
|
||||
parent::spawnTo($player);
|
||||
}
|
||||
}
|
||||
@ -729,7 +767,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
$data->aliases = new CommandEnum();
|
||||
$data->aliases->enumName = ucfirst($command->getName()) . "Aliases";
|
||||
$data->aliases->enumValues = $aliases;
|
||||
$data->aliases->enumValues = array_values($aliases);
|
||||
}
|
||||
|
||||
$pk->commandData[$command->getName()] = $data;
|
||||
@ -860,8 +898,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->lastPingMeasure = $pingMS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getNextPosition() : Position{
|
||||
return $this->newPosition !== null ? Position::fromObject($this->newPosition, $this->level) : $this->getPosition();
|
||||
return $this->getPosition();
|
||||
}
|
||||
|
||||
public function getInAirTicks() : int{
|
||||
@ -902,8 +943,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
/**
|
||||
* Resets the player's cooldown time for the given item back to the maximum.
|
||||
*/
|
||||
public function resetItemCooldown(Item $item) : void{
|
||||
$ticks = $item->getCooldownTicks();
|
||||
public function resetItemCooldown(Item $item, ?int $ticks = null) : void{
|
||||
$ticks = $ticks ?? $item->getCooldownTicks();
|
||||
if($ticks > 0){
|
||||
$this->usedItemsCooldown[$item->getId()] = $this->server->getTick() + $ticks;
|
||||
}
|
||||
@ -1207,15 +1248,16 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if(!($pos instanceof Position)){
|
||||
$level = $this->level;
|
||||
}else{
|
||||
$level = $pos->getLevel();
|
||||
$level = $pos->getLevelNonNull();
|
||||
}
|
||||
$this->spawnPosition = new Position($pos->x, $pos->y, $pos->z, $level);
|
||||
$pk = new SetSpawnPositionPacket();
|
||||
$pk->x = $this->spawnPosition->getFloorX();
|
||||
$pk->y = $this->spawnPosition->getFloorY();
|
||||
$pk->z = $this->spawnPosition->getFloorZ();
|
||||
$pk->x = $pk->x2 = $this->spawnPosition->getFloorX();
|
||||
$pk->y = $pk->y2 = $this->spawnPosition->getFloorY();
|
||||
$pk->z = $pk->z2 = $this->spawnPosition->getFloorZ();
|
||||
$pk->dimension = DimensionIds::OVERWORLD;
|
||||
$pk->spawnType = SetSpawnPositionPacket::TYPE_PLAYER_SPAWN;
|
||||
$pk->spawnForced = false;
|
||||
|
||||
$this->dataPacket($pk);
|
||||
}
|
||||
|
||||
@ -1329,12 +1371,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* TODO: remove this when Spectator Mode gets added properly to MCPE
|
||||
*/
|
||||
public static function getClientFriendlyGamemode(int $gamemode) : int{
|
||||
$gamemode &= 0x03;
|
||||
if($gamemode === Player::SPECTATOR){
|
||||
return Player::CREATIVE;
|
||||
}
|
||||
|
||||
return $gamemode;
|
||||
static $map = [
|
||||
self::SURVIVAL => GameMode::SURVIVAL,
|
||||
self::CREATIVE => GameMode::CREATIVE,
|
||||
self::ADVENTURE => GameMode::ADVENTURE,
|
||||
self::SPECTATOR => GameMode::CREATIVE
|
||||
];
|
||||
return $map[$gamemode & 0x3];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1362,9 +1405,16 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($this->isSpectator()){
|
||||
$this->setFlying(true);
|
||||
$this->keepMovement = true;
|
||||
$this->onGround = false;
|
||||
|
||||
//TODO: HACK! this syncs the onground flag with the client so that flying works properly
|
||||
//this is a yucky hack but we don't have any other options :(
|
||||
$this->sendPosition($this, null, null, MovePlayerPacket::MODE_TELEPORT);
|
||||
|
||||
$this->despawnFromAll();
|
||||
}else{
|
||||
$this->keepMovement = $this->allowMovementCheats;
|
||||
$this->checkGroundState(0, 0, 0, 0, 0, 0);
|
||||
if($this->isSurvival()){
|
||||
$this->setFlying(false);
|
||||
}
|
||||
@ -1485,11 +1535,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
protected function checkGroundState(float $movX, float $movY, float $movZ, float $dx, float $dy, float $dz) : void{
|
||||
$bb = clone $this->boundingBox;
|
||||
$bb->minY = $this->y - 0.2;
|
||||
$bb->maxY = $this->y + 0.2;
|
||||
if($this->isSpectator()){
|
||||
$this->onGround = false;
|
||||
}else{
|
||||
$bb = clone $this->boundingBox;
|
||||
$bb->minY = $this->y - 0.2;
|
||||
$bb->maxY = $this->y + 0.2;
|
||||
|
||||
$this->onGround = $this->isCollided = count($this->level->getCollisionBlocks($bb, true)) > 0;
|
||||
$this->onGround = $this->isCollided = count($this->level->getCollisionBlocks($bb, true)) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function canBeMovedByCurrents() : bool{
|
||||
@ -1511,23 +1565,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function processMovement(int $tickDiff){
|
||||
if(!$this->isAlive() or !$this->spawned or $this->newPosition === null or $this->isSleeping()){
|
||||
protected function handleMovement(Vector3 $newPos) : void{
|
||||
$this->moveRateLimit--;
|
||||
if($this->moveRateLimit < 0){
|
||||
return;
|
||||
}
|
||||
|
||||
assert($this->x !== null and $this->y !== null and $this->z !== null);
|
||||
assert($this->newPosition->x !== null and $this->newPosition->y !== null and $this->newPosition->z !== null);
|
||||
|
||||
$newPos = $this->newPosition;
|
||||
$distanceSquared = $newPos->distanceSquared($this);
|
||||
$oldPos = $this->asLocation();
|
||||
$distanceSquared = $newPos->distanceSquared($oldPos);
|
||||
|
||||
$revert = false;
|
||||
|
||||
if(($distanceSquared / ($tickDiff ** 2)) > 100){
|
||||
if($distanceSquared > 100){
|
||||
//TODO: this is probably too big if we process every movement
|
||||
/* !!! BEWARE YE WHO ENTER HERE !!!
|
||||
*
|
||||
* This is NOT an anti-cheat check. It is a safety check.
|
||||
@ -1539,7 +1589,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* asking for help if you suffer the consequences of messing with this.
|
||||
*/
|
||||
$this->server->getLogger()->debug($this->getName() . " moved too fast, reverting movement");
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $this->newPosition);
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos);
|
||||
$revert = true;
|
||||
}elseif(!$this->level->isInLoadedTerrain($newPos) or !$this->level->isChunkGenerated($newPos->getFloorX() >> 4, $newPos->getFloorZ() >> 4)){
|
||||
$revert = true;
|
||||
@ -1551,10 +1601,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) / $tickDiff ** 2;
|
||||
$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);
|
||||
@ -1564,7 +1619,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: " . $this->newPosition);
|
||||
$this->server->getLogger()->debug("Old position: " . $this->asVector3() . ", new position: " . $newPos . ", expected clip distance: $expectedClipDistance");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1573,13 +1628,28 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
if($revert){
|
||||
$this->revertMovement($oldPos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires movement events and synchronizes player movement, every tick.
|
||||
*/
|
||||
protected function processMostRecentMovements() : void{
|
||||
$now = microtime(true);
|
||||
$multiplier = $this->lastMovementProcess !== null ? ($now - $this->lastMovementProcess) * 20 : 1;
|
||||
$exceededRateLimit = $this->moveRateLimit < 0;
|
||||
$this->moveRateLimit = min(self::MOVE_BACKLOG_SIZE, max(0, $this->moveRateLimit) + self::MOVES_PER_TICK * $multiplier);
|
||||
$this->lastMovementProcess = $now;
|
||||
|
||||
$from = new Location($this->lastX, $this->lastY, $this->lastZ, $this->lastYaw, $this->lastPitch, $this->level);
|
||||
$to = $this->getLocation();
|
||||
|
||||
$delta = (($this->lastX - $to->x) ** 2) + (($this->lastY - $to->y) ** 2) + (($this->lastZ - $to->z) ** 2);
|
||||
$deltaAngle = abs($this->lastYaw - $to->yaw) + abs($this->lastPitch - $to->pitch);
|
||||
|
||||
if(!$revert and ($delta > 0.0001 or $deltaAngle > 1.0)){
|
||||
if($delta > 0.0001 or $deltaAngle > 1.0){
|
||||
$this->lastX = $to->x;
|
||||
$this->lastY = $to->y;
|
||||
$this->lastZ = $to->z;
|
||||
@ -1591,42 +1661,47 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$ev->call();
|
||||
|
||||
if(!($revert = $ev->isCancelled())){ //Yes, this is intended
|
||||
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
|
||||
$this->teleport($ev->getTo());
|
||||
}else{
|
||||
//TODO: workaround 1.14.30 bug: MoveActor(Absolute|Delta)Packet don't work on players anymore :(
|
||||
$this->sendPosition($this, $this->yaw, $this->pitch, MovePlayerPacket::MODE_NORMAL, $this->hasSpawned);
|
||||
|
||||
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
|
||||
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
|
||||
if($this->isSprinting()){
|
||||
$this->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
|
||||
}else{
|
||||
$this->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING);
|
||||
}
|
||||
}
|
||||
if($ev->isCancelled()){
|
||||
$this->revertMovement($from);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($revert){
|
||||
if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination
|
||||
$this->teleport($ev->getTo());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lastX = $from->x;
|
||||
$this->lastY = $from->y;
|
||||
$this->lastZ = $from->z;
|
||||
$this->broadcastMovement();
|
||||
|
||||
$this->lastYaw = $from->yaw;
|
||||
$this->lastPitch = $from->pitch;
|
||||
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
|
||||
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
|
||||
if($this->isSprinting()){
|
||||
$this->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
|
||||
}else{
|
||||
$this->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING);
|
||||
}
|
||||
|
||||
$this->setPosition($from);
|
||||
$this->sendPosition($from, $from->yaw, $from->pitch, MovePlayerPacket::MODE_RESET);
|
||||
}else{
|
||||
if($distanceSquared != 0 and $this->nextChunkOrderRun > 20){
|
||||
if($this->nextChunkOrderRun > 20){
|
||||
$this->nextChunkOrderRun = 20;
|
||||
}
|
||||
}
|
||||
|
||||
$this->newPosition = null;
|
||||
if($exceededRateLimit){ //client and server positions will be out of sync if this happens
|
||||
$this->server->getLogger()->debug("Player " . $this->getName() . " exceeded movement rate limit, forcing to last accepted position");
|
||||
$this->sendPosition($this, $this->yaw, $this->pitch, MovePlayerPacket::MODE_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function fall(float $fallDistance) : void{
|
||||
@ -1698,7 +1773,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->timings->startTiming();
|
||||
|
||||
if($this->spawned){
|
||||
$this->processMovement($tickDiff);
|
||||
$this->processMostRecentMovements();
|
||||
$this->motion->x = $this->motion->y = $this->motion->z = 0; //TODO: HACK! (Fixes player knockback being messed up)
|
||||
if($this->onGround){
|
||||
$this->inAirTicks = 0;
|
||||
@ -1815,9 +1890,10 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function handleLogin(LoginPacket $packet) : bool{
|
||||
if($this->loggedIn){
|
||||
if($this->seenLoginPacket){
|
||||
return false;
|
||||
}
|
||||
$this->seenLoginPacket = true;
|
||||
|
||||
if($packet->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
|
||||
if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){
|
||||
@ -1827,7 +1903,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
|
||||
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol ?? "unknown"]), false);
|
||||
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol]), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1857,26 +1933,65 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$animations = [];
|
||||
foreach($packet->clientData["AnimatedImageData"] as $animation){
|
||||
$animations[] = new SkinAnimation(new SkinImage($animation["ImageHeight"], $animation["ImageWidth"], base64_decode($animation["Image"], true)), $animation["Type"], $animation["Frames"]);
|
||||
$animations[] = new SkinAnimation(
|
||||
new SkinImage(
|
||||
$animation["ImageHeight"],
|
||||
$animation["ImageWidth"],
|
||||
base64_decode($animation["Image"], true)),
|
||||
$animation["Type"],
|
||||
$animation["Frames"]
|
||||
);
|
||||
}
|
||||
|
||||
$personaPieces = [];
|
||||
foreach($packet->clientData["PersonaPieces"] as $piece){
|
||||
$personaPieces[] = new PersonaSkinPiece(
|
||||
$piece["PieceId"],
|
||||
$piece["PieceType"],
|
||||
$piece["PackId"],
|
||||
$piece["IsDefault"],
|
||||
$piece["ProductId"]
|
||||
);
|
||||
}
|
||||
|
||||
$pieceTintColors = [];
|
||||
foreach($packet->clientData["PieceTintColors"] as $tintColor){
|
||||
$pieceTintColors[] = new PersonaPieceTintColor($tintColor["PieceType"], $tintColor["Colors"]);
|
||||
}
|
||||
|
||||
$skinData = new SkinData(
|
||||
$packet->clientData["SkinId"],
|
||||
base64_decode($packet->clientData["SkinResourcePatch"] ?? "", true),
|
||||
new SkinImage($packet->clientData["SkinImageHeight"], $packet->clientData["SkinImageWidth"], base64_decode($packet->clientData["SkinData"], true)),
|
||||
new SkinImage(
|
||||
$packet->clientData["SkinImageHeight"],
|
||||
$packet->clientData["SkinImageWidth"],
|
||||
base64_decode($packet->clientData["SkinData"], true)
|
||||
),
|
||||
$animations,
|
||||
new SkinImage($packet->clientData["CapeImageHeight"], $packet->clientData["CapeImageWidth"], base64_decode($packet->clientData["CapeData"] ?? "", true)),
|
||||
new SkinImage(
|
||||
$packet->clientData["CapeImageHeight"],
|
||||
$packet->clientData["CapeImageWidth"],
|
||||
base64_decode($packet->clientData["CapeData"] ?? "", true)
|
||||
),
|
||||
base64_decode($packet->clientData["SkinGeometryData"] ?? "", true),
|
||||
base64_decode($packet->clientData["SkinAnimationData"] ?? "", true),
|
||||
$packet->clientData["PremiumSkin"] ?? false,
|
||||
$packet->clientData["PersonaSkin"] ?? false,
|
||||
$packet->clientData["CapeOnClassicSkin"] ?? false,
|
||||
$packet->clientData["CapeId"] ?? ""
|
||||
$packet->clientData["CapeId"] ?? "",
|
||||
null,
|
||||
$packet->clientData["ArmSize"] ?? SkinData::ARM_SIZE_WIDE,
|
||||
$packet->clientData["SkinColor"] ?? "",
|
||||
$personaPieces,
|
||||
$pieceTintColors,
|
||||
true
|
||||
);
|
||||
|
||||
$skin = SkinAdapterSingleton::get()->fromSkinData($skinData);
|
||||
|
||||
if(!$skin->isValid()){
|
||||
try{
|
||||
$skin = SkinAdapterSingleton::get()->fromSkinData($skinData);
|
||||
$skin->validate();
|
||||
}catch(InvalidSkinException $e){
|
||||
$this->server->getLogger()->debug("$this->username: Invalid skin: " . $e->getMessage());
|
||||
$this->close("", "disconnectionScreen.invalidSkin");
|
||||
|
||||
return true;
|
||||
@ -2048,7 +2163,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$pk = new ResourcePackDataInfoPacket();
|
||||
$pk->packId = $pack->getPackId();
|
||||
$pk->maxChunkSize = 1048576; //1MB
|
||||
$pk->maxChunkSize = self::RESOURCE_PACK_CHUNK_SIZE;
|
||||
$pk->chunkCount = (int) ceil($pack->getPackSize() / $pk->maxChunkSize);
|
||||
$pk->compressedPackSize = $pack->getPackSize();
|
||||
$pk->sha256 = $pack->getSha256();
|
||||
@ -2115,7 +2230,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$pk->pitch = $this->pitch;
|
||||
$pk->yaw = $this->yaw;
|
||||
$pk->seed = -1;
|
||||
$pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly
|
||||
$pk->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
$pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode());
|
||||
$pk->difficulty = $this->level->getDifficulty();
|
||||
$pk->spawnX = $spawnPosition->getFloorX();
|
||||
@ -2214,10 +2329,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function handleMovePlayer(MovePlayerPacket $packet) : bool{
|
||||
$newPos = $packet->position->subtract(0, $this->baseOffset, 0);
|
||||
$newPos = $packet->position->round(4)->subtract(0, $this->baseOffset, 0);
|
||||
|
||||
if($this->isTeleporting and $newPos->distanceSquared($this) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks
|
||||
$this->sendPosition($this, null, null, MovePlayerPacket::MODE_RESET);
|
||||
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
|
||||
}elseif((!$this->isAlive() or !$this->spawned) and $newPos->distanceSquared($this) > 0.01){
|
||||
@ -2225,9 +2339,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->server->getLogger()->debug("Reverted movement of " . $this->getName() . " due to not alive or not spawned, received " . $newPos . ", locked at " . $this->asVector3());
|
||||
}else{
|
||||
// Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock
|
||||
if($this->isTeleporting){
|
||||
$this->isTeleporting = false;
|
||||
}
|
||||
$this->forceMoveSync = null;
|
||||
|
||||
$packet->yaw = fmod($packet->yaw, 360);
|
||||
$packet->pitch = fmod($packet->pitch, 360);
|
||||
@ -2237,7 +2349,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$this->setRotation($packet->yaw, $packet->pitch);
|
||||
$this->newPosition = $newPos;
|
||||
$this->handleMovement($newPos);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2245,11 +2357,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
public function handleLevelSoundEvent(LevelSoundEventPacket $packet) : bool{
|
||||
//TODO: add events so plugins can change this
|
||||
$this->getLevel()->broadcastPacketToViewers($this, $packet);
|
||||
$this->getLevelNonNull()->broadcastPacketToViewers($this, $packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleEntityEvent(ActorEventPacket $packet) : bool{
|
||||
if($packet->entityRuntimeId !== $this->id){
|
||||
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
|
||||
return $packet->event === ActorEventPacket::EATING_ITEM;
|
||||
}
|
||||
if(!$this->spawned or !$this->isAlive()){
|
||||
return true;
|
||||
}
|
||||
@ -2279,14 +2395,24 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->isSpectator()){
|
||||
$this->sendAllInventories();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var InventoryAction[] $actions */
|
||||
$actions = [];
|
||||
$isCraftingPart = false;
|
||||
foreach($packet->actions as $networkInventoryAction){
|
||||
if(
|
||||
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_TODO and (
|
||||
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_RESULT or
|
||||
$networkInventoryAction->windowId === NetworkInventoryAction::SOURCE_TYPE_CRAFTING_USE_INGREDIENT
|
||||
) or (
|
||||
$this->craftingTransaction !== null &&
|
||||
!$networkInventoryAction->oldItem->equalsExact($networkInventoryAction->newItem) &&
|
||||
$networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER &&
|
||||
$networkInventoryAction->windowId === ContainerIds::UI &&
|
||||
$networkInventoryAction->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT
|
||||
)
|
||||
){
|
||||
$isCraftingPart = true;
|
||||
}
|
||||
try{
|
||||
$action = $networkInventoryAction->createInventoryAction($this);
|
||||
if($action !== null){
|
||||
@ -2299,7 +2425,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
if($packet->isCraftingPart){
|
||||
if($isCraftingPart){
|
||||
if($this->craftingTransaction === null){
|
||||
$this->craftingTransaction = new CraftingTransaction($this, $actions);
|
||||
}else{
|
||||
@ -2308,23 +2434,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
if($packet->isFinalCraftingPart){
|
||||
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
|
||||
//trying to execute it
|
||||
|
||||
$ret = true;
|
||||
try{
|
||||
$this->craftingTransaction->execute();
|
||||
}catch(TransactionValidationException $e){
|
||||
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
|
||||
$ret = false;
|
||||
}
|
||||
|
||||
$this->craftingTransaction = null;
|
||||
return $ret;
|
||||
try{
|
||||
$this->craftingTransaction->validate();
|
||||
}catch(TransactionValidationException $e){
|
||||
//transaction is incomplete - crafting transaction comes in lots of little bits, so we have to collect
|
||||
//all of the parts before we can execute it
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
try{
|
||||
$this->craftingTransaction->execute();
|
||||
return true;
|
||||
}catch(TransactionValidationException $e){
|
||||
$this->server->getLogger()->debug("Failed to execute crafting transaction for " . $this->getName() . ": " . $e->getMessage());
|
||||
return false;
|
||||
}finally{
|
||||
$this->craftingTransaction = null;
|
||||
}
|
||||
}elseif($this->craftingTransaction !== null){
|
||||
$this->server->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction from " . $this->getName() . ", refusing to execute crafting");
|
||||
$this->craftingTransaction = null;
|
||||
@ -2377,7 +2503,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$this->setUsingItem(false);
|
||||
|
||||
if(!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13) or $this->isSpectator()){
|
||||
if(!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13)){
|
||||
}elseif($this->isCreative()){
|
||||
$item = $this->inventory->getItemInHand();
|
||||
if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->clickPos, $this, true)){
|
||||
@ -2389,7 +2515,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$item = $this->inventory->getItemInHand();
|
||||
$oldItem = clone $item;
|
||||
if($this->level->useItemOn($blockVector, $item, $face, $packet->trData->clickPos, $this, true)){
|
||||
if(!$item->equalsExact($oldItem)){
|
||||
if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){
|
||||
$this->inventory->setItemInHand($item);
|
||||
$this->inventory->sendHeldItem($this->hasSpawned);
|
||||
}
|
||||
@ -2421,7 +2547,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
if($this->canInteract($blockVector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7) and $this->level->useBreakOn($blockVector, $item, $this, true)){
|
||||
if($this->isSurvival()){
|
||||
if(!$item->equalsExact($oldItem)){
|
||||
if(!$item->equalsExact($oldItem) and $oldItem->equalsExact($this->inventory->getItemInHand())){
|
||||
$this->inventory->setItemInHand($item);
|
||||
$this->inventory->sendHeldItem($this->hasSpawned);
|
||||
}
|
||||
@ -2483,7 +2609,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
$ev = new PlayerInteractEvent($this, $item, null, $directionVector, $face, PlayerInteractEvent::RIGHT_CLICK_AIR);
|
||||
if($this->hasItemCooldown($item)){
|
||||
if($this->hasItemCooldown($item) or $this->isSpectator()){
|
||||
$ev->setCancelled();
|
||||
}
|
||||
|
||||
@ -2532,8 +2658,9 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$cancelled = false;
|
||||
|
||||
$heldItem = $this->inventory->getItemInHand();
|
||||
$oldItem = clone $heldItem;
|
||||
|
||||
if(!$this->canInteract($target, 8)){
|
||||
if(!$this->canInteract($target, 8) or $this->isSpectator()){
|
||||
$cancelled = true;
|
||||
}elseif($target instanceof Player){
|
||||
if(!$this->server->getConfigBool("pvp")){
|
||||
@ -2591,7 +2718,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($this->isAlive()){
|
||||
//reactive damage like thorns might cause us to be killed by attacking another mob, which
|
||||
//would mean we'd already have dropped the inventory by the time we reached here
|
||||
if($heldItem->onAttackEntity($target) and $this->isSurvival()){ //always fire the hook, even if we are survival
|
||||
if($heldItem->onAttackEntity($target) and $this->isSurvival() and $oldItem->equalsExact($this->inventory->getItemInHand())){ //always fire the hook, even if we are survival
|
||||
$this->inventory->setItemInHand($heldItem);
|
||||
}
|
||||
|
||||
@ -2685,8 +2812,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
case InteractPacket::ACTION_LEAVE_VEHICLE:
|
||||
case InteractPacket::ACTION_MOUSEOVER:
|
||||
break; //TODO: handle these
|
||||
case InteractPacket::ACTION_OPEN_INVENTORY:
|
||||
if($target === $this && !array_key_exists($windowId = self::HARDCODED_INVENTORY_WINDOW_ID, $this->openHardcodedWindows)){
|
||||
//TODO: HACK! this restores 1.14ish behaviour, but this should be able to be listened to and
|
||||
//controlled by plugins. However, the player is always a subscriber to their own inventory so it
|
||||
//doesn't integrate well with the regular container system right now.
|
||||
$this->openHardcodedWindows[$windowId] = true;
|
||||
$pk = new ContainerOpenPacket();
|
||||
$pk->windowId = $windowId;
|
||||
$pk->type = WindowTypes::INVENTORY;
|
||||
$pk->x = $pk->y = $pk->z = 0;
|
||||
$pk->entityUniqueId = $this->getId();
|
||||
$this->sendDataPacket($pk);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
$this->server->getLogger()->debug("Unhandled/unknown interaction type " . $packet->action . "received from " . $this->getName());
|
||||
$this->server->getLogger()->debug("Unhandled/unknown interaction type " . $packet->action . " received from " . $this->getName());
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -2702,7 +2844,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$item = $block->getPickedItem();
|
||||
if($packet->addUserData){
|
||||
$tile = $this->getLevel()->getTile($block);
|
||||
$tile = $this->getLevelNonNull()->getTile($block);
|
||||
if($tile instanceof Tile){
|
||||
$nbt = $tile->getCleanedNBT();
|
||||
if($nbt instanceof CompoundTag){
|
||||
@ -2744,7 +2886,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();
|
||||
}
|
||||
|
||||
@ -2898,18 +3040,23 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
public function handleContainerClose(ContainerClosePacket $packet) : bool{
|
||||
if(!$this->spawned or $packet->windowId === 0){
|
||||
if(!$this->spawned){
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->doCloseInventory();
|
||||
|
||||
if(array_key_exists($packet->windowId, $this->openHardcodedWindows)){
|
||||
unset($this->openHardcodedWindows[$packet->windowId]);
|
||||
$pk = new ContainerClosePacket();
|
||||
$pk->windowId = $packet->windowId;
|
||||
$this->sendDataPacket($pk);
|
||||
return true;
|
||||
}
|
||||
if(isset($this->windowIndex[$packet->windowId])){
|
||||
(new InventoryCloseEvent($this->windowIndex[$packet->windowId], $this))->call();
|
||||
$this->removeWindow($this->windowIndex[$packet->windowId]);
|
||||
return true;
|
||||
}elseif($packet->windowId === 255){
|
||||
//Closed a fake window
|
||||
//removeWindow handles sending the appropriate
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3031,8 +3178,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$pk = new ResourcePackChunkDataPacket();
|
||||
$pk->packId = $pack->getPackId();
|
||||
$pk->chunkIndex = $packet->chunkIndex;
|
||||
$pk->data = $pack->getPackChunk(1048576 * $packet->chunkIndex, 1048576);
|
||||
$pk->progress = (1048576 * $packet->chunkIndex);
|
||||
$pk->data = $pack->getPackChunk(self::RESOURCE_PACK_CHUNK_SIZE * $packet->chunkIndex, self::RESOURCE_PACK_CHUNK_SIZE);
|
||||
$pk->progress = (self::RESOURCE_PACK_CHUNK_SIZE * $packet->chunkIndex);
|
||||
$this->dataPacket($pk);
|
||||
return true;
|
||||
}
|
||||
@ -3053,14 +3200,26 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$modifiedPages[] = $packet->pageNumber;
|
||||
break;
|
||||
case BookEditPacket::TYPE_ADD_PAGE:
|
||||
if(!$newBook->pageExists($packet->pageNumber)){
|
||||
//this may only come before a page which already exists
|
||||
//TODO: the client can send insert-before actions on trailing client-side pages which cause odd behaviour on the server
|
||||
return false;
|
||||
}
|
||||
$newBook->insertPage($packet->pageNumber, $packet->text);
|
||||
$modifiedPages[] = $packet->pageNumber;
|
||||
break;
|
||||
case BookEditPacket::TYPE_DELETE_PAGE:
|
||||
if(!$newBook->pageExists($packet->pageNumber)){
|
||||
return false;
|
||||
}
|
||||
$newBook->deletePage($packet->pageNumber);
|
||||
$modifiedPages[] = $packet->pageNumber;
|
||||
break;
|
||||
case BookEditPacket::TYPE_SWAP_PAGES:
|
||||
if(!$newBook->pageExists($packet->pageNumber) or !$newBook->pageExists($packet->secondaryPageNumber)){
|
||||
//the client will create pages on its own without telling us until it tries to switch them
|
||||
$newBook->addPage(max($packet->pageNumber, $packet->secondaryPageNumber));
|
||||
}
|
||||
$newBook->swapPages($packet->pageNumber, $packet->secondaryPageNumber);
|
||||
$modifiedPages = [$packet->pageNumber, $packet->secondaryPageNumber];
|
||||
break;
|
||||
@ -3222,7 +3381,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a title text to the user's screen, with an optional subtitle.
|
||||
* @deprecated
|
||||
* @see Player::sendTitle()
|
||||
*
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
@ -3231,28 +3391,55 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* @return void
|
||||
*/
|
||||
public function addTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1){
|
||||
$this->sendTitle($title, $subtitle, $fadeIn, $stay, $fadeOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a title text to the user's screen, with an optional subtitle.
|
||||
*
|
||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||
* @param int $stay Duration in ticks to stay on screen for
|
||||
* @param int $fadeOut Duration in ticks for fade-out.
|
||||
*/
|
||||
public function sendTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1) : void{
|
||||
$this->setTitleDuration($fadeIn, $stay, $fadeOut);
|
||||
if($subtitle !== ""){
|
||||
$this->addSubTitle($subtitle);
|
||||
$this->sendSubTitle($subtitle);
|
||||
}
|
||||
$this->sendTitleText($title, SetTitlePacket::TYPE_SET_TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle message, without sending a title.
|
||||
* @deprecated
|
||||
* @see Player::sendSubTitle()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addSubTitle(string $subtitle){
|
||||
$this->sendSubTitle($subtitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle message, without sending a title.
|
||||
*/
|
||||
public function sendSubTitle(string $subtitle) : void{
|
||||
$this->sendTitleText($subtitle, SetTitlePacket::TYPE_SET_SUBTITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds small text to the user's screen.
|
||||
* @deprecated
|
||||
* @see Player::sendActionBarMessage()
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addActionBarMessage(string $message){
|
||||
$this->sendActionBarMessage($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds small text to the user's screen.
|
||||
*/
|
||||
public function sendActionBarMessage(string $message) : void{
|
||||
$this->sendTitleText($message, SetTitlePacket::TYPE_SET_ACTIONBAR_MESSAGE);
|
||||
}
|
||||
|
||||
@ -3395,14 +3582,15 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
* Sends a Form to the player, or queue to send it if a form is already open.
|
||||
*/
|
||||
public function sendForm(Form $form) : void{
|
||||
$formData = json_encode($form);
|
||||
if($formData === false){
|
||||
throw new \InvalidArgumentException("Failed to encode form JSON: " . json_last_error_msg());
|
||||
}
|
||||
$id = $this->formIdCounter++;
|
||||
$pk = new ModalFormRequestPacket();
|
||||
$pk->formId = $id;
|
||||
$pk->formData = json_encode($form);
|
||||
if($pk->formData === false){
|
||||
throw new \InvalidArgumentException("Failed to encode form JSON: " . json_last_error_msg());
|
||||
}
|
||||
if($this->dataPacket($pk)){
|
||||
$pk->formData = $formData;
|
||||
if($this->dataPacket($pk) !== false){
|
||||
$this->forms[$id] = $form;
|
||||
}
|
||||
}
|
||||
@ -3551,7 +3739,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
|
||||
if($this->hasValidSpawnPosition()){
|
||||
$this->namedtag->setString("SpawnLevel", $this->spawnPosition->getLevel()->getFolderName());
|
||||
$this->namedtag->setString("SpawnLevel", $this->spawnPosition->getLevelNonNull()->getFolderName());
|
||||
$this->namedtag->setInt("SpawnX", $this->spawnPosition->getFloorX());
|
||||
$this->namedtag->setInt("SpawnY", $this->spawnPosition->getFloorY());
|
||||
$this->namedtag->setInt("SpawnZ", $this->spawnPosition->getFloorZ());
|
||||
@ -3595,7 +3783,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
//main inventory and drops the rest on the ground.
|
||||
$this->doCloseInventory();
|
||||
|
||||
$ev = new PlayerDeathEvent($this, $this->getDrops());
|
||||
$ev = new PlayerDeathEvent($this, $this->getDrops(), null, $this->getXpDropAmount());
|
||||
$ev->call();
|
||||
|
||||
if(!$ev->getKeepInventory()){
|
||||
@ -3612,8 +3800,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: allow this number to be manipulated during PlayerDeathEvent
|
||||
$this->level->dropExperience($this, $this->getXpDropAmount());
|
||||
$this->level->dropExperience($this, $ev->getXpDropAmount());
|
||||
$this->setXpAndProgress(0, 0);
|
||||
|
||||
if($ev->getDeathMessage() != ""){
|
||||
@ -3635,7 +3822,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$ev = new PlayerRespawnEvent($this, $this->getSpawn());
|
||||
$ev->call();
|
||||
|
||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getLevel());
|
||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getLevelNonNull());
|
||||
$this->teleport($realSpawn);
|
||||
|
||||
$this->setSprinting(false);
|
||||
@ -3716,14 +3903,19 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$pk->headYaw = $yaw;
|
||||
$pk->yaw = $yaw;
|
||||
$pk->mode = $mode;
|
||||
$pk->onGround = $this->onGround;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$this->newPosition = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3741,11 +3933,11 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
|
||||
$this->resetFallDistance();
|
||||
$this->nextChunkOrderRun = 0;
|
||||
$this->newPosition = null;
|
||||
if($this->spawnChunkLoadCount !== -1){
|
||||
$this->spawnChunkLoadCount = 0;
|
||||
}
|
||||
$this->stopSleep();
|
||||
|
||||
$this->isTeleporting = true;
|
||||
|
||||
//TODO: workaround for player last pos not getting updated
|
||||
//Entity::updateMovement() normally handles this, but it's overridden with an empty function in Player
|
||||
$this->resetLastMovements();
|
||||
@ -3839,7 +4031,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
if($forceId === null){
|
||||
$cnt = $this->windowCnt;
|
||||
do{
|
||||
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % ContainerIds::LAST);
|
||||
$cnt = max(ContainerIds::FIRST, ($cnt + 1) % self::RESERVED_WINDOW_ID_RANGE_START);
|
||||
if($cnt === $this->windowCnt){ //wraparound, no free slots
|
||||
throw new \InvalidStateException("No free window IDs found");
|
||||
}
|
||||
@ -3847,7 +4039,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
|
||||
$this->windowCnt = $cnt;
|
||||
}else{
|
||||
$cnt = $forceId;
|
||||
if(isset($this->windowIndex[$cnt])){
|
||||
if(isset($this->windowIndex[$cnt]) or ($cnt >= self::RESERVED_WINDOW_ID_RANGE_START && $cnt <= self::RESERVED_WINDOW_ID_RANGE_END)){
|
||||
throw new \InvalidArgumentException("Requested force ID $forceId already in use");
|
||||
}
|
||||
}
|
||||
@ -3930,7 +4122,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;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine {
|
||||
|
||||
use pocketmine\utils\Git;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\Process;
|
||||
use pocketmine\utils\ServerKiller;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\Timezone;
|
||||
@ -34,7 +35,7 @@ namespace pocketmine {
|
||||
|
||||
require_once __DIR__ . '/VersionInfo.php';
|
||||
|
||||
const MIN_PHP_VERSION = "7.2.0";
|
||||
const MIN_PHP_VERSION = "7.3.0";
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
@ -177,15 +178,14 @@ namespace pocketmine {
|
||||
}else{
|
||||
$bootstrap = dirname(__FILE__, 3) . '/vendor/autoload.php';
|
||||
}
|
||||
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
|
||||
|
||||
if(\pocketmine\COMPOSER_AUTOLOADER_PATH !== false and is_file(\pocketmine\COMPOSER_AUTOLOADER_PATH)){
|
||||
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
|
||||
}else{
|
||||
if($bootstrap === false or !is_file($bootstrap)){
|
||||
critical_error("Composer autoloader not found at " . $bootstrap);
|
||||
critical_error("Please install/update Composer dependencies or use provided builds.");
|
||||
exit(1);
|
||||
}
|
||||
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
|
||||
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
|
||||
|
||||
set_error_handler([Utils::class, 'errorExceptionHandler']);
|
||||
|
||||
@ -280,7 +280,7 @@ namespace pocketmine {
|
||||
|
||||
if(ThreadManager::getInstance()->stopAll() > 0){
|
||||
$logger->debug("Some threads could not be stopped, performing a force-kill");
|
||||
Utils::kill(getmypid());
|
||||
Process::kill(getmypid());
|
||||
}
|
||||
}while(false);
|
||||
|
||||
@ -289,6 +289,14 @@ namespace pocketmine {
|
||||
|
||||
echo Terminal::$FORMAT_RESET . PHP_EOL;
|
||||
|
||||
if(!flock(\pocketmine\LOCK_FILE, LOCK_UN)){
|
||||
critical_error("Failed to release the server.lock file.");
|
||||
}
|
||||
|
||||
if(!fclose(\pocketmine\LOCK_FILE)){
|
||||
critical_error("Could not close server.lock resource.");
|
||||
}
|
||||
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ use pocketmine\updater\AutoUpdater;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Internet;
|
||||
use pocketmine\utils\MainLogger;
|
||||
use pocketmine\utils\Process;
|
||||
use pocketmine\utils\Terminal;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
@ -692,29 +693,32 @@ class Server{
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getPlayerDataPath(string $username) : string{
|
||||
return $this->getDataPath() . '/players/' . strtolower($username) . '.dat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the server has stored any saved data for this player.
|
||||
*/
|
||||
public function hasOfflinePlayerData(string $name) : bool{
|
||||
$name = strtolower($name);
|
||||
return file_exists($this->getDataPath() . "players/$name.dat");
|
||||
return file_exists($this->getPlayerDataPath($name));
|
||||
}
|
||||
|
||||
public function getOfflinePlayerData(string $name) : CompoundTag{
|
||||
$name = strtolower($name);
|
||||
$path = $this->getDataPath() . "players/";
|
||||
$path = $this->getPlayerDataPath($name);
|
||||
if($this->shouldSavePlayerData()){
|
||||
if(file_exists($path . "$name.dat")){
|
||||
if(file_exists($path)){
|
||||
try{
|
||||
$nbt = new BigEndianNBTStream();
|
||||
$compound = $nbt->readCompressed(file_get_contents($path . "$name.dat"));
|
||||
$compound = $nbt->readCompressed(file_get_contents($path));
|
||||
if(!($compound instanceof CompoundTag)){
|
||||
throw new \RuntimeException("Invalid data found in \"$name.dat\", expected " . CompoundTag::class . ", got " . (is_object($compound) ? get_class($compound) : gettype($compound)));
|
||||
}
|
||||
|
||||
return $compound;
|
||||
}catch(\Throwable $e){ //zlib decode error / corrupt data
|
||||
rename($path . "$name.dat", $path . "$name.dat.bak");
|
||||
rename($path, $path . '.bak');
|
||||
$this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerCorrupted", [$name]));
|
||||
}
|
||||
}else{
|
||||
@ -775,7 +779,7 @@ class Server{
|
||||
if(!$ev->isCancelled()){
|
||||
$nbt = new BigEndianNBTStream();
|
||||
try{
|
||||
file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed($ev->getSaveData()));
|
||||
file_put_contents($this->getPlayerDataPath($name), $nbt->writeCompressed($ev->getSaveData()));
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()]));
|
||||
$this->logger->logException($e);
|
||||
@ -1396,10 +1400,10 @@ class Server{
|
||||
Network::$BATCH_THRESHOLD = -1;
|
||||
}
|
||||
|
||||
$this->networkCompressionLevel = $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);
|
||||
|
||||
@ -1539,7 +1543,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{
|
||||
@ -1665,7 +1669,7 @@ class Server{
|
||||
}
|
||||
|
||||
foreach($recipients as $recipient){
|
||||
$recipient->addTitle($title, $subtitle, $fadeIn, $stay, $fadeOut);
|
||||
$recipient->sendTitle($title, $subtitle, $fadeIn, $stay, $fadeOut);
|
||||
}
|
||||
|
||||
return count($recipients);
|
||||
@ -1935,7 +1939,7 @@ class Server{
|
||||
}catch(\Throwable $e){
|
||||
$this->logger->logException($e);
|
||||
$this->logger->emergency("Crashed while crashing, killing process");
|
||||
@Utils::kill(getmypid());
|
||||
@Process::kill(getmypid());
|
||||
}
|
||||
|
||||
}
|
||||
@ -2091,17 +2095,24 @@ class Server{
|
||||
|
||||
if($report){
|
||||
$url = ((bool) $this->getProperty("auto-report.use-https", true) ? "https" : "http") . "://" . $this->getProperty("auto-report.host", "crash.pmmp.io") . "/submit/api";
|
||||
$postUrlError = "Unknown error";
|
||||
$reply = Internet::postURL($url, [
|
||||
"report" => "yes",
|
||||
"name" => $this->getName() . " " . $this->getPocketMineVersion(),
|
||||
"email" => "crash@pocketmine.net",
|
||||
"reportPaste" => base64_encode($dump->getEncodedData())
|
||||
]);
|
||||
], 10, [], $postUrlError);
|
||||
|
||||
if($reply !== false and ($data = json_decode($reply)) !== null and isset($data->crashId) and isset($data->crashUrl)){
|
||||
$reportId = $data->crashId;
|
||||
$reportUrl = $data->crashUrl;
|
||||
$this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.archive", [$reportUrl, $reportId]));
|
||||
if($reply !== false and ($data = json_decode($reply)) !== null){
|
||||
if(isset($data->crashId) and isset($data->crashUrl)){
|
||||
$reportId = $data->crashId;
|
||||
$reportUrl = $data->crashUrl;
|
||||
$this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.archive", [$reportUrl, $reportId]));
|
||||
}elseif(isset($data->error)){
|
||||
$this->logger->emergency("Automatic crash report submission failed: $data->error");
|
||||
}
|
||||
}else{
|
||||
$this->logger->emergency("Failed to communicate with crash archive: $postUrlError");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2121,7 +2132,7 @@ class Server{
|
||||
echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
|
||||
sleep($spacing);
|
||||
}
|
||||
@Utils::kill(getmypid());
|
||||
@Process::kill(getmypid());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -2322,10 +2333,10 @@ class Server{
|
||||
|
||||
private function titleTick() : void{
|
||||
Timings::$titleTickTimer->startTiming();
|
||||
$d = Utils::getRealMemoryUsage();
|
||||
$d = Process::getRealMemoryUsage();
|
||||
|
||||
$u = Utils::getMemoryUsage(true);
|
||||
$usage = sprintf("%g/%g/%g/%g MB @ %d threads", round(($u[0] / 1024) / 1024, 2), round(($d[0] / 1024) / 1024, 2), round(($u[1] / 1024) / 1024, 2), round(($u[2] / 1024) / 1024, 2), Utils::getThreadCount());
|
||||
$u = Process::getAdvancedMemoryUsage();
|
||||
$usage = sprintf("%g/%g/%g/%g MB @ %d threads", round(($u[0] / 1024) / 1024, 2), round(($d[0] / 1024) / 1024, 2), round(($u[1] / 1024) / 1024, 2), round(($u[2] / 1024) / 1024, 2), Process::getThreadCount());
|
||||
|
||||
echo "\x1b]0;" . $this->getName() . " " .
|
||||
$this->getPocketMineVersion() .
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use const PTHREADS_INHERIT_ALL;
|
||||
|
||||
/**
|
||||
* This class must be extended by all custom threading classes
|
||||
*/
|
||||
@ -74,11 +76,9 @@ abstract class Thread extends \Thread{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $options TODO: pthreads bug
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
public function start(int $options = PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if($this->getClassLoader() === null){
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use function defined;
|
||||
|
||||
// composer autoload doesn't use require_once and also pthreads can inherit things
|
||||
// TODO: drop this file and use a final class with constants
|
||||
if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
|
||||
@ -31,6 +33,6 @@ if(defined('pocketmine\_VERSION_INFO_INCLUDED')){
|
||||
const _VERSION_INFO_INCLUDED = true;
|
||||
|
||||
const NAME = "PocketMine-MP";
|
||||
const BASE_VERSION = "3.11.6";
|
||||
const BASE_VERSION = "3.15.3";
|
||||
const IS_DEVELOPMENT_BUILD = false;
|
||||
const BUILD_NUMBER = 0;
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use const PTHREADS_INHERIT_ALL;
|
||||
|
||||
/**
|
||||
* This class must be extended by all custom threading classes
|
||||
*/
|
||||
@ -74,11 +76,9 @@ abstract class Worker extends \Worker{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $options TODO: pthreads bug
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function start(?int $options = \PTHREADS_INHERIT_ALL){
|
||||
public function start(int $options = PTHREADS_INHERIT_ALL){
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
||||
if($this->getClassLoader() === null){
|
||||
|
@ -110,6 +110,6 @@ class Anvil extends Fallable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$direction = ($player !== null ? $player->getDirection() : 0) & 0x03;
|
||||
$this->meta = $this->getVariant() | $direction;
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ abstract class BaseRail extends Flowable{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent() and $this->getLevel()->setBlock($blockReplace, $this, true, true)){
|
||||
if(!$blockReplace->getSide(Vector3::SIDE_DOWN)->isTransparent() and $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true)){
|
||||
$this->tryReconnect();
|
||||
return true;
|
||||
}
|
||||
@ -279,7 +279,7 @@ abstract class BaseRail extends Flowable{
|
||||
isset(self::ASCENDING_SIDES[$this->meta & 0x07]) and
|
||||
$this->getSide(self::ASCENDING_SIDES[$this->meta & 0x07])->isTransparent()
|
||||
)){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ class Bed extends Transparent{
|
||||
$this->meta &= ~self::BITFLAG_OCCUPIED;
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($this, $this, false, false);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, false, false);
|
||||
|
||||
if(($other = $this->getOtherHalf()) !== null and $other->isOccupied() !== $occupied){
|
||||
$other->setOccupied($occupied);
|
||||
@ -137,7 +137,7 @@ class Bed extends Transparent{
|
||||
return true;
|
||||
}
|
||||
|
||||
$time = $this->getLevel()->getTime() % Level::TIME_FULL;
|
||||
$time = $this->getLevelNonNull()->getTimeOfDay();
|
||||
|
||||
$isNight = ($time >= Level::TIME_NIGHT and $time < Level::TIME_SUNRISE);
|
||||
|
||||
@ -168,11 +168,11 @@ class Bed extends Transparent{
|
||||
$meta = (($player instanceof Player ? $player->getDirection() : 0) - 1) & 0x03;
|
||||
$next = $this->getSide(self::getOtherHalfSide($meta));
|
||||
if($next->canBeReplaced() and !$next->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->id, $meta), true, true);
|
||||
$this->getLevel()->setBlock($next, BlockFactory::get($this->id, $meta | self::BITFLAG_HEAD), true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get($this->id, $meta), true, true);
|
||||
$this->getLevelNonNull()->setBlock($next, BlockFactory::get($this->id, $meta | self::BITFLAG_HEAD), true, true);
|
||||
|
||||
Tile::createTile(Tile::BED, $this->getLevel(), TileBed::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::BED, $this->getLevel(), TileBed::createNBT($next, $face, $item, $player));
|
||||
Tile::createTile(Tile::BED, $this->getLevelNonNull(), TileBed::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::BED, $this->getLevelNonNull(), TileBed::createNBT($next, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -194,7 +194,7 @@ class Bed extends Transparent{
|
||||
}
|
||||
|
||||
private function getItem() : Item{
|
||||
$tile = $this->getLevel()->getTile($this);
|
||||
$tile = $this->getLevelNonNull()->getTile($this);
|
||||
if($tile instanceof TileBed){
|
||||
return ItemFactory::get($this->getItemId(), $tile->getColor());
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ use pocketmine\math\RayTraceResult;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\metadata\Metadatable;
|
||||
use pocketmine\metadata\MetadataValue;
|
||||
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use function array_merge;
|
||||
@ -154,7 +154,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
* Places the Block, using block space and block target, and side. Returns if the block has been placed.
|
||||
*/
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
return $this->getLevel()->setBlock($this, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($this, $this, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,7 +204,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
* Do the actions needed so the block is broken with the Item
|
||||
*/
|
||||
public function onBreak(Item $item, Player $player = null) : bool{
|
||||
return $this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true, true);
|
||||
return $this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -479,7 +479,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
||||
*/
|
||||
public function getSide(int $side, int $step = 1){
|
||||
if($this->isValid()){
|
||||
return $this->getLevel()->getBlock(Vector3::getSide($side, $step));
|
||||
return $this->getLevelNonNull()->getBlock(Vector3::getSide($side, $step));
|
||||
}
|
||||
|
||||
return BlockFactory::get(Block::AIR, 0, Position::fromObject(Vector3::getSide($side, $step)));
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\level\Position;
|
||||
use pocketmine\network\mcpe\protocol\types\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
|
@ -55,7 +55,7 @@ class BoneBlock extends Solid{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
|
@ -68,18 +68,18 @@ class BurningFurnace extends Solid{
|
||||
3 => 3
|
||||
];
|
||||
$this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0];
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
Tile::createTile(Tile::FURNACE, $this->getLevel(), TileFurnace::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::FURNACE, $this->getLevelNonNull(), TileFurnace::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($player instanceof Player){
|
||||
$furnace = $this->getLevel()->getTile($this);
|
||||
$furnace = $this->getLevelNonNull()->getTile($this);
|
||||
if(!($furnace instanceof TileFurnace)){
|
||||
$furnace = Tile::createTile(Tile::FURNACE, $this->getLevel(), TileFurnace::createNBT($this));
|
||||
$furnace = Tile::createTile(Tile::FURNACE, $this->getLevelNonNull(), TileFurnace::createNBT($this));
|
||||
if(!($furnace instanceof TileFurnace)){
|
||||
return true;
|
||||
}
|
||||
|
@ -72,12 +72,12 @@ class Cactus extends Transparent{
|
||||
public function onNearbyBlockChange() : void{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() !== self::SAND and $down->getId() !== self::CACTUS){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}else{
|
||||
for($side = 2; $side <= 5; ++$side){
|
||||
$b = $this->getSide($side);
|
||||
if($b->isSolid()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -92,23 +92,23 @@ class Cactus extends Transparent{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() !== self::CACTUS){
|
||||
if($this->meta === 0x0f){
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
$b = $this->getLevelNonNull()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::CACTUS));
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($b, $ev->getNewState(), true);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->meta = 0;
|
||||
$this->getLevel()->setBlock($this, $this);
|
||||
$this->getLevelNonNull()->setBlock($this, $this);
|
||||
}else{
|
||||
++$this->meta;
|
||||
$this->getLevel()->setBlock($this, $this);
|
||||
$this->getLevelNonNull()->setBlock($this, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,7 +121,7 @@ class Cactus extends Transparent{
|
||||
$block2 = $this->getSide(Vector3::SIDE_WEST);
|
||||
$block3 = $this->getSide(Vector3::SIDE_EAST);
|
||||
if(!$block0->isSolid() and !$block1->isSolid() and !$block2->isSolid() and !$block3->isSolid()){
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class Cake extends Transparent implements FoodSource{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() !== self::AIR){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -76,7 +76,7 @@ class Cake extends Transparent implements FoodSource{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){ //Replace with common break method
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,6 +109,10 @@ class Cake extends Transparent implements FoodSource{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Block
|
||||
*/
|
||||
|
@ -64,7 +64,7 @@ class Carpet extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() !== self::AIR){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -74,7 +74,7 @@ class Carpet extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ class Chest extends Transparent{
|
||||
}
|
||||
$c = $this->getSide($side);
|
||||
if($c->getId() === $this->id and $c->getDamage() === $this->meta){
|
||||
$tile = $this->getLevel()->getTile($c);
|
||||
$tile = $this->getLevelNonNull()->getTile($c);
|
||||
if($tile instanceof TileChest and !$tile->isPaired()){
|
||||
$chest = $tile;
|
||||
break;
|
||||
@ -89,8 +89,8 @@ class Chest extends Transparent{
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$tile = Tile::createTile(Tile::CHEST, $this->getLevel(), TileChest::createNBT($this, $face, $item, $player));
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
$tile = Tile::createTile(Tile::CHEST, $this->getLevelNonNull(), TileChest::createNBT($this, $face, $item, $player));
|
||||
|
||||
if($chest instanceof TileChest and $tile instanceof TileChest){
|
||||
$chest->pairWith($tile);
|
||||
@ -103,12 +103,12 @@ class Chest extends Transparent{
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($player instanceof Player){
|
||||
|
||||
$t = $this->getLevel()->getTile($this);
|
||||
$t = $this->getLevelNonNull()->getTile($this);
|
||||
$chest = null;
|
||||
if($t instanceof TileChest){
|
||||
$chest = $t;
|
||||
}else{
|
||||
$chest = Tile::createTile(Tile::CHEST, $this->getLevel(), TileChest::createNBT($this));
|
||||
$chest = Tile::createTile(Tile::CHEST, $this->getLevelNonNull(), TileChest::createNBT($this));
|
||||
if(!($chest instanceof TileChest)){
|
||||
return true;
|
||||
}
|
||||
|
@ -23,6 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\item\ItemIds;
|
||||
use function mt_rand;
|
||||
|
||||
class CocoaBlock extends Transparent{
|
||||
|
||||
protected $id = self::COCOA_BLOCK;
|
||||
@ -48,4 +53,14 @@ class CocoaBlock extends Transparent{
|
||||
public function isAffectedBySilkTouch() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [
|
||||
ItemFactory::get(ItemIds::DYE, 3, ($this->meta >> 2) === 2 ? mt_rand(2, 3) : 1)
|
||||
];
|
||||
}
|
||||
|
||||
public function getPickedItem() : Item{
|
||||
return ItemFactory::get(ItemIds::DYE, 3);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\inventory\CraftingGrid;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\WindowTypes;
|
||||
use pocketmine\Player;
|
||||
use function array_key_exists;
|
||||
|
||||
class CraftingTable extends Solid{
|
||||
|
||||
@ -50,6 +53,19 @@ class CraftingTable extends Solid{
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($player instanceof Player){
|
||||
$player->setCraftingGrid(new CraftingGrid($player, CraftingGrid::SIZE_BIG));
|
||||
|
||||
if(!array_key_exists($windowId = Player::HARDCODED_CRAFTING_GRID_WINDOW_ID, $player->openHardcodedWindows)){
|
||||
//TODO: HACK! crafting grid doesn't fit very well into the current PM container system, so this hack allows
|
||||
//it to carry on working approximately the same way as it did in 1.14
|
||||
$pk = new ContainerOpenPacket();
|
||||
$pk->windowId = $windowId;
|
||||
$pk->type = WindowTypes::WORKBENCH;
|
||||
$pk->x = $this->getFloorX();
|
||||
$pk->y = $this->getFloorY();
|
||||
$pk->z = $this->getFloorZ();
|
||||
$player->sendDataPacket($pk);
|
||||
$player->openHardcodedWindows[$windowId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -33,7 +33,7 @@ abstract class Crops extends Flowable{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if($blockReplace->getSide(Vector3::SIDE_DOWN)->getId() === Block::FARMLAND){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -52,10 +52,10 @@ abstract class Crops extends Flowable{
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $ev->getNewState(), true, true);
|
||||
}
|
||||
|
||||
$item->count--;
|
||||
$item->pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -65,7 +65,7 @@ abstract class Crops extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() !== Block::FARMLAND){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ abstract class Crops extends Flowable{
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $ev->getNewState(), true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class Dandelion extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === Block::GRASS or $down->getId() === Block::DIRT or $down->getId() === Block::FARMLAND){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -52,7 +52,7 @@ class Dandelion extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ class DeadBush extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,9 +54,9 @@ class Dirt extends Solid{
|
||||
if($item instanceof Hoe){
|
||||
$item->applyDamage(1);
|
||||
if($this->meta === 1){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::DIRT), true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::DIRT), true);
|
||||
}else{
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND), true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::FARMLAND), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -201,9 +201,9 @@ abstract class Door extends Transparent{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){ //Replace with common break method
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), false);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), false);
|
||||
if($this->getSide(Vector3::SIDE_UP) instanceof Door){
|
||||
$this->getLevel()->setBlock($this->getSide(Vector3::SIDE_UP), BlockFactory::get(Block::AIR), false);
|
||||
$this->getLevelNonNull()->setBlock($this->getSide(Vector3::SIDE_UP), BlockFactory::get(Block::AIR), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,8 +230,8 @@ abstract class Door extends Transparent{
|
||||
}
|
||||
|
||||
$this->setDamage($player->getDirection() & 0x03);
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true); //Bottom
|
||||
$this->getLevel()->setBlock($blockUp, BlockFactory::get($this->getId(), $metaUp), true); //Top
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true); //Bottom
|
||||
$this->getLevelNonNull()->setBlock($blockUp, BlockFactory::get($this->getId(), $metaUp), true); //Top
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ class DoublePlant extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$id = $blockReplace->getSide(Vector3::SIDE_DOWN)->getId();
|
||||
if(($id === Block::GRASS or $id === Block::DIRT) and $blockReplace->getSide(Vector3::SIDE_UP)->canBeReplaced()){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, false, false);
|
||||
$this->getLevel()->setBlock($blockReplace->getSide(Vector3::SIDE_UP), BlockFactory::get($this->id, $this->meta | self::BITFLAG_TOP), false, false);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, false, false);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace->getSide(Vector3::SIDE_UP), BlockFactory::get($this->id, $this->meta | self::BITFLAG_TOP), false, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -85,7 +85,7 @@ class DoublePlant extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->isValidHalfPlant() or (($this->meta & self::BITFLAG_TOP) === 0 and $this->getSide(Vector3::SIDE_DOWN)->isTransparent())){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,4 +124,12 @@ class DoublePlant extends Flowable{
|
||||
|
||||
return parent::getAffectedBlocks();
|
||||
}
|
||||
|
||||
public function getFlameEncouragement() : int{
|
||||
return 60;
|
||||
}
|
||||
|
||||
public function getFlammability() : int{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,9 @@ class EnchantingTable extends Transparent{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
Tile::createTile(Tile::ENCHANT_TABLE, $this->getLevel(), TileEnchantTable::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::ENCHANT_TABLE, $this->getLevelNonNull(), TileEnchantTable::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -69,8 +69,8 @@ class EnderChest extends Chest{
|
||||
|
||||
$this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0];
|
||||
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
Tile::createTile(Tile::ENDER_CHEST, $this->getLevel(), TileEnderChest::createNBT($this, $face, $item, $player));
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
Tile::createTile(Tile::ENDER_CHEST, $this->getLevelNonNull(), TileEnderChest::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -78,12 +78,12 @@ class EnderChest extends Chest{
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($player instanceof Player){
|
||||
|
||||
$t = $this->getLevel()->getTile($this);
|
||||
$t = $this->getLevelNonNull()->getTile($this);
|
||||
$enderChest = null;
|
||||
if($t instanceof TileEnderChest){
|
||||
$enderChest = $t;
|
||||
}else{
|
||||
$enderChest = Tile::createTile(Tile::ENDER_CHEST, $this->getLevel(), TileEnderChest::createNBT($this));
|
||||
$enderChest = Tile::createTile(Tile::ENDER_CHEST, $this->getLevelNonNull(), TileEnderChest::createNBT($this));
|
||||
if(!($enderChest instanceof TileEnderChest)){
|
||||
return true;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ abstract class Fallable extends Solid{
|
||||
$nbt->setInt("TileID", $this->getId());
|
||||
$nbt->setByte("Data", $this->getDamage());
|
||||
|
||||
$fall = Entity::createEntity("FallingSand", $this->getLevel(), $nbt);
|
||||
$fall = Entity::createEntity("FallingSand", $this->getLevelNonNull(), $nbt);
|
||||
|
||||
if($fall !== null){
|
||||
$fall->spawnToAll();
|
||||
|
@ -69,7 +69,7 @@ class FenceGate extends Transparent{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->meta = ($player instanceof Player ? ($player->getDirection() - 1) & 0x03 : 0);
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -85,7 +85,7 @@ class FenceGate extends Transparent{
|
||||
$this->meta |= (($player->getDirection() - 1) & 0x02);
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
$this->level->addSound(new DoorSound($this));
|
||||
return true;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class Fire extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->getSide(Vector3::SIDE_DOWN)->isSolid() and !$this->hasAdjacentFlammableBlocks()){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
}else{
|
||||
$this->level->scheduleDelayedBlockUpdate($this, mt_rand(30, 40));
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class Flower extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === Block::GRASS or $down->getId() === Block::DIRT or $down->getId() === Block::FARMLAND){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -72,7 +72,7 @@ class Flower extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,19 +62,19 @@ class FlowerPot extends Flowable{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
Tile::createTile(Tile::FLOWER_POT, $this->getLevel(), TileFlowerPot::createNBT($this, $face, $item, $player));
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
Tile::createTile(Tile::FLOWER_POT, $this->getLevelNonNull(), TileFlowerPot::createNBT($this, $face, $item, $player));
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
$pot = $this->getLevel()->getTile($this);
|
||||
$pot = $this->getLevelNonNull()->getTile($this);
|
||||
if(!($pot instanceof TileFlowerPot)){
|
||||
return false;
|
||||
}
|
||||
@ -83,7 +83,7 @@ class FlowerPot extends Flowable{
|
||||
}
|
||||
|
||||
$this->setDamage(self::STATE_FULL); //specific damage value is unnecessary, it just needs to be non-zero to show an item.
|
||||
$this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true, false);
|
||||
$pot->setItem($item->pop());
|
||||
|
||||
return true;
|
||||
@ -96,7 +96,7 @@ class FlowerPot extends Flowable{
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
$items = parent::getDropsForCompatibleTool($item);
|
||||
|
||||
$tile = $this->getLevel()->getTile($this);
|
||||
$tile = $this->getLevelNonNull()->getTile($this);
|
||||
if($tile instanceof TileFlowerPot){
|
||||
$item = $tile->getItem();
|
||||
if($item->getId() !== Item::AIR){
|
||||
|
@ -53,7 +53,7 @@ class GlazedTerracotta extends Solid{
|
||||
$this->meta = $faces[(~($player->getDirection() - 1)) & 0x03];
|
||||
}
|
||||
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
|
@ -53,6 +53,6 @@ class GlowingRedstoneOre extends RedstoneOre{
|
||||
}
|
||||
|
||||
public function onRandomTick() : void{
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::REDSTONE_ORE, $this->meta), false, false);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::REDSTONE_ORE, $this->meta), false, false);
|
||||
}
|
||||
}
|
||||
|
@ -99,18 +99,18 @@ class Grass extends Solid{
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){
|
||||
$item->count--;
|
||||
TallGrassObject::growGrass($this->getLevel(), $this, new Random(mt_rand()), 8, 2);
|
||||
$item->pop();
|
||||
TallGrassObject::growGrass($this->getLevelNonNull(), $this, new Random(mt_rand()), 8, 2);
|
||||
|
||||
return true;
|
||||
}elseif($item instanceof Hoe){
|
||||
$item->applyDamage(1);
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::FARMLAND));
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::FARMLAND));
|
||||
|
||||
return true;
|
||||
}elseif($item instanceof Shovel and $this->getSide(Vector3::SIDE_UP)->getId() === Block::AIR){
|
||||
$item->applyDamage(1);
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::GRASS_PATH));
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::GRASS_PATH));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class HayBale extends Solid{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class Ice extends Transparent{
|
||||
|
||||
public function onBreak(Item $item, Player $player = null) : bool{
|
||||
if(($player === null or $player->isSurvival()) and !$item->hasEnchantment(Enchantment::SILK_TOUCH)){
|
||||
return $this->getLevel()->setBlock($this, BlockFactory::get(Block::WATER), true);
|
||||
return $this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::WATER), true);
|
||||
}
|
||||
return parent::onBreak($item, $player);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class ItemFrame extends Flowable{
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
$tile = $this->level->getTile($this);
|
||||
if(!($tile instanceof TileItemFrame)){
|
||||
$tile = Tile::createTile(Tile::ITEM_FRAME, $this->getLevel(), TileItemFrame::createNBT($this));
|
||||
$tile = Tile::createTile(Tile::ITEM_FRAME, $this->getLevelNonNull(), TileItemFrame::createNBT($this));
|
||||
if(!($tile instanceof TileItemFrame)){
|
||||
return true;
|
||||
}
|
||||
@ -88,7 +88,7 @@ class ItemFrame extends Flowable{
|
||||
$this->meta = $faces[$face];
|
||||
$this->level->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
Tile::createTile(Tile::ITEM_FRAME, $this->getLevel(), TileItemFrame::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::ITEM_FRAME, $this->getLevelNonNull(), TileItemFrame::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -58,7 +59,7 @@ class Ladder extends Transparent{
|
||||
}
|
||||
|
||||
public function onEntityCollide(Entity $entity) : void{
|
||||
if($entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
|
||||
if($entity instanceof Living and $entity->asVector3()->floor()->distanceSquared($this) < 1){ //entity coordinates must be inside block
|
||||
$entity->resetFallDistance();
|
||||
$entity->onGround = true;
|
||||
}
|
||||
@ -100,7 +101,7 @@ class Ladder extends Transparent{
|
||||
];
|
||||
if(isset($faces[$face])){
|
||||
$this->meta = $faces[$face];
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -115,8 +115,8 @@ class Lava extends Liquid{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$ret = $this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
$ret = $this->getLevelNonNull()->setBlock($this, $this, true, false);
|
||||
$this->getLevelNonNull()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ class Leaves extends Transparent{
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(($this->meta & 0b00001100) === 0){
|
||||
$this->meta |= 0x08;
|
||||
$this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,16 +155,16 @@ class Leaves extends Transparent{
|
||||
$ev = new LeavesDecayEvent($this);
|
||||
$ev->call();
|
||||
if($ev->isCancelled() or $this->findLog($this, $visited, 0)){
|
||||
$this->getLevel()->setBlock($this, $this, false, false);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, false, false);
|
||||
}else{
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->meta |= 0x04;
|
||||
return $this->getLevel()->setBlock($this, $this, true);
|
||||
return $this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
|
@ -49,7 +49,7 @@ class MelonStem extends Crops{
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($this, $ev->getNewState(), true);
|
||||
}
|
||||
}else{
|
||||
for($side = 2; $side <= 5; ++$side){
|
||||
@ -64,7 +64,7 @@ class MelonStem extends Crops{
|
||||
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::MELON_BLOCK));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($side, $ev->getNewState(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,13 +64,13 @@ class Mycelium extends Solid{
|
||||
$x = mt_rand($this->x - 1, $this->x + 1);
|
||||
$y = mt_rand($this->y - 2, $this->y + 2);
|
||||
$z = mt_rand($this->z - 1, $this->z + 1);
|
||||
$block = $this->getLevel()->getBlockAt($x, $y, $z);
|
||||
$block = $this->getLevelNonNull()->getBlockAt($x, $y, $z);
|
||||
if($block->getId() === Block::DIRT){
|
||||
if($block->getSide(Vector3::SIDE_UP) instanceof Transparent){
|
||||
$ev = new BlockSpreadEvent($block, $this, BlockFactory::get(Block::MYCELIUM));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($block, $ev->getNewState());
|
||||
$this->getLevelNonNull()->setBlock($block, $ev->getNewState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class NetherWartPlant extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === Block::SOUL_SAND){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, false, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, false, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -56,7 +56,7 @@ class NetherWartPlant extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() !== Block::SOUL_SAND){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ class NetherWartPlant extends Flowable{
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), false, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $ev->getNewState(), false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,6 @@ class Podzol extends Solid{
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
return 2.5;
|
||||
return 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -51,7 +51,7 @@ class Pumpkin extends Solid{
|
||||
if($player instanceof Player){
|
||||
$this->meta = ((int) $player->getDirection() + 1) % 4;
|
||||
}
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class PumpkinStem extends Crops{
|
||||
$ev = new BlockGrowEvent($this, $block);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($this, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($this, $ev->getNewState(), true);
|
||||
}
|
||||
}else{
|
||||
for($side = 2; $side <= 5; ++$side){
|
||||
@ -64,7 +64,7 @@ class PumpkinStem extends Crops{
|
||||
$ev = new BlockGrowEvent($side, BlockFactory::get(Block::PUMPKIN));
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->getLevel()->setBlock($side, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($side, $ev->getNewState(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class Quartz extends Solid{
|
||||
if($this->getVariant() !== self::NORMAL){
|
||||
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
|
||||
}
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
|
@ -71,6 +71,7 @@ class Rail extends BaseRail{
|
||||
}
|
||||
|
||||
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
|
||||
/** @var int[] $horizontal */
|
||||
static $horizontal = [
|
||||
Vector3::SIDE_NORTH,
|
||||
Vector3::SIDE_SOUTH,
|
||||
|
@ -45,14 +45,14 @@ class RedMushroom extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if(!$down->isTransparent()){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -47,16 +47,16 @@ class RedstoneOre extends Solid{
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
return $this->getLevel()->setBlock($this, $this, true, false);
|
||||
return $this->getLevelNonNull()->setBlock($this, $this, true, false);
|
||||
}
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
return false; //this shouldn't prevent block placement
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::GLOWING_REDSTONE_ORE, $this->meta));
|
||||
}
|
||||
|
||||
public function getToolType() : int{
|
||||
|
@ -59,7 +59,7 @@ class Sapling extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === self::GRASS or $down->getId() === self::DIRT or $down->getId() === self::FARMLAND){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -70,9 +70,9 @@ class Sapling extends Flowable{
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
|
||||
//TODO: change log type
|
||||
Tree::growTree($this->getLevel(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->getVariant());
|
||||
Tree::growTree($this->getLevelNonNull(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->getVariant());
|
||||
|
||||
$item->count--;
|
||||
$item->pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -82,7 +82,7 @@ class Sapling extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,10 +93,10 @@ class Sapling extends Flowable{
|
||||
public function onRandomTick() : void{
|
||||
if($this->level->getFullLightAt($this->x, $this->y, $this->z) >= 8 and mt_rand(1, 7) === 1){
|
||||
if(($this->meta & 0x08) === 0x08){
|
||||
Tree::growTree($this->getLevel(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->getVariant());
|
||||
Tree::growTree($this->getLevelNonNull(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->getVariant());
|
||||
}else{
|
||||
$this->meta |= 0x08;
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,13 +62,13 @@ class SignPost extends Transparent{
|
||||
|
||||
if($face === Vector3::SIDE_UP){
|
||||
$this->meta = $player !== null ? (floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f) : 0;
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
}else{
|
||||
$this->meta = $face;
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get(Block::WALL_SIGN, $this->meta), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get(Block::WALL_SIGN, $this->meta), true);
|
||||
}
|
||||
|
||||
Tile::createTile(Tile::SIGN, $this->getLevel(), TileSign::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::SIGN, $this->getLevelNonNull(), TileSign::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -78,7 +78,7 @@ class SignPost extends Transparent{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ class Skull extends Flowable{
|
||||
}
|
||||
|
||||
$this->meta = $face;
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
Tile::createTile(Tile::SKULL, $this->getLevel(), TileSkull::createNBT($this, $face, $item, $player));
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
Tile::createTile(Tile::SKULL, $this->getLevelNonNull(), TileSkull::createNBT($this, $face, $item, $player));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -56,11 +56,11 @@ abstract class Slab extends Transparent{
|
||||
$this->meta &= 0x07;
|
||||
if($face === Vector3::SIDE_DOWN){
|
||||
if($blockClicked->getId() === $this->id and ($blockClicked->getDamage() & 0x08) === 0x08 and $blockClicked->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockClicked, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
$this->getLevelNonNull()->setBlock($blockClicked, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
}elseif($blockReplace->getId() === $this->id and $blockReplace->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
}else{
|
||||
@ -68,18 +68,18 @@ abstract class Slab extends Transparent{
|
||||
}
|
||||
}elseif($face === Vector3::SIDE_UP){
|
||||
if($blockClicked->getId() === $this->id and ($blockClicked->getDamage() & 0x08) === 0 and $blockClicked->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockClicked, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
$this->getLevelNonNull()->setBlock($blockClicked, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
}elseif($blockReplace->getId() === $this->id and $blockReplace->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
}else{ //TODO: collision
|
||||
if($blockReplace->getId() === $this->id){
|
||||
if($blockReplace->getVariant() === $this->getVariant()){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get($this->getDoubleSlabId(), $this->getVariant()), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -95,7 +95,7 @@ abstract class Slab extends Transparent{
|
||||
if($blockReplace->getId() === $this->id and $blockClicked->getVariant() !== $this->getVariant()){
|
||||
return false;
|
||||
}
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class SnowLayer extends Flowable{
|
||||
}
|
||||
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
return $this->meta < 7; //8 snow layers
|
||||
}
|
||||
|
||||
public function getHardness() : float{
|
||||
@ -57,10 +57,16 @@ class SnowLayer extends Flowable{
|
||||
return TieredTool::TIER_WOODEN;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $b) : bool{
|
||||
return $b->isSolid() or ($b->getId() === $this->getId() and $b->getDamage() === 7);
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
if($blockReplace->getSide(Vector3::SIDE_DOWN)->isSolid()){
|
||||
//TODO: fix placement
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
if($blockReplace->getId() === $this->getId() and $blockReplace->getDamage() < 7){
|
||||
$this->setDamage($blockReplace->getDamage() + 1);
|
||||
}
|
||||
if($this->canBeSupportedBy($blockReplace->getSide(Vector3::SIDE_DOWN))){
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -69,8 +75,8 @@ class SnowLayer extends Flowable{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->getSide(Vector3::SIDE_DOWN)->isSolid()){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), false, false);
|
||||
if(!$this->canBeSupportedBy($this->getSide(Vector3::SIDE_DOWN))){
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +86,7 @@ class SnowLayer extends Flowable{
|
||||
|
||||
public function onRandomTick() : void{
|
||||
if($this->level->getBlockLightAt($this->x, $this->y, $this->z) >= 12){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), false, false);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ abstract class Stair extends Transparent{
|
||||
if(($clickVector->y > 0.5 and $face !== Vector3::SIDE_UP) or $face === Vector3::SIDE_DOWN){
|
||||
$this->meta |= 0x04; //Upside-down stairs
|
||||
}
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -62,13 +62,13 @@ class StandingBanner extends Transparent{
|
||||
if($face !== Vector3::SIDE_DOWN){
|
||||
if($face === Vector3::SIDE_UP and $player !== null){
|
||||
$this->meta = floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f;
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
}else{
|
||||
$this->meta = $face;
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get(Block::WALL_BANNER, $this->meta), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get(Block::WALL_BANNER, $this->meta), true);
|
||||
}
|
||||
|
||||
Tile::createTile(Tile::BANNER, $this->getLevel(), TileBanner::createNBT($this, $face, $item, $player));
|
||||
Tile::createTile(Tile::BANNER, $this->getLevelNonNull(), TileBanner::createNBT($this, $face, $item, $player));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ class StandingBanner extends Transparent{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,23 +46,23 @@ class Sugarcane extends Flowable{
|
||||
if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() !== self::SUGARCANE_BLOCK){
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
$b = $this->getLevelNonNull()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
$ev = new BlockGrowEvent($b, BlockFactory::get(Block::SUGARCANE_BLOCK));
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
break;
|
||||
}
|
||||
$this->getLevel()->setBlock($b, $ev->getNewState(), true);
|
||||
$this->getLevelNonNull()->setBlock($b, $ev->getNewState(), true);
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->meta = 0;
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
}
|
||||
|
||||
$item->count--;
|
||||
$item->pop();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -73,7 +73,7 @@ class Sugarcane extends Flowable{
|
||||
public function onNearbyBlockChange() : void{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->isTransparent() and $down->getId() !== self::SUGARCANE_BLOCK){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,17 +85,22 @@ class Sugarcane extends Flowable{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->getId() !== self::SUGARCANE_BLOCK){
|
||||
if($this->meta === 0x0F){
|
||||
for($y = 1; $y < 3; ++$y){
|
||||
$b = $this->getLevel()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
$b = $this->getLevelNonNull()->getBlockAt($this->x, $this->y + $y, $this->z);
|
||||
if($b->getId() === self::AIR){
|
||||
$this->getLevel()->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;
|
||||
}
|
||||
}
|
||||
$this->meta = 0;
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
}else{
|
||||
++$this->meta;
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +108,7 @@ class Sugarcane extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN);
|
||||
if($down->getId() === self::SUGARCANE_BLOCK){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
|
||||
|
||||
return true;
|
||||
}elseif($down->getId() === self::GRASS or $down->getId() === self::DIRT or $down->getId() === self::SAND){
|
||||
@ -112,7 +117,7 @@ class Sugarcane extends Flowable{
|
||||
$block2 = $down->getSide(Vector3::SIDE_WEST);
|
||||
$block3 = $down->getSide(Vector3::SIDE_EAST);
|
||||
if(($block0 instanceof Water) or ($block1 instanceof Water) or ($block2 instanceof Water) or ($block3 instanceof Water)){
|
||||
$this->getLevel()->setBlock($blockReplace, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, BlockFactory::get(Block::SUGARCANE_BLOCK), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -78,13 +78,13 @@ class TNT extends Solid{
|
||||
* @return void
|
||||
*/
|
||||
public function ignite(int $fuse = 80){
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true);
|
||||
|
||||
$mot = (new Random())->nextSignedFloat() * M_PI * 2;
|
||||
$nbt = Entity::createBaseNBT($this->add(0.5, 0, 0.5), new Vector3(-sin($mot) * 0.02, 0.2, -cos($mot) * 0.02));
|
||||
$nbt->setShort("Fuse", $fuse);
|
||||
|
||||
$tnt = Entity::createEntity("PrimedTNT", $this->getLevel(), $nbt);
|
||||
$tnt = Entity::createEntity("PrimedTNT", $this->getLevelNonNull(), $nbt);
|
||||
|
||||
if($tnt !== null){
|
||||
$tnt->spawnToAll();
|
||||
|
@ -53,7 +53,7 @@ class TallGrass extends Flowable{
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$down = $this->getSide(Vector3::SIDE_DOWN)->getId();
|
||||
if($down === self::GRASS or $down === self::DIRT){
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -63,7 +63,7 @@ class TallGrass extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){ //Replace with common break method
|
||||
$this->getLevel()->setBlock($this, BlockFactory::get(Block::AIR), true, true);
|
||||
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ class Torch extends Flowable{
|
||||
$face = $faces[$meta] ?? Vector3::SIDE_DOWN;
|
||||
|
||||
if($this->getSide($face)->isTransparent() and !($face === Vector3::SIDE_DOWN and ($below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL))){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,12 +73,12 @@ class Torch extends Flowable{
|
||||
Vector3::SIDE_EAST => 1
|
||||
];
|
||||
$this->meta = $faces[$face];
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}elseif(!$below->isTransparent() or $below->getId() === self::FENCE or $below->getId() === self::COBBLESTONE_WALL){
|
||||
$this->meta = 0;
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ class Trapdoor extends Transparent{
|
||||
if(($clickVector->y > 0.5 and $face !== self::SIDE_UP) or $face === self::SIDE_DOWN){
|
||||
$this->meta |= self::MASK_UPPER; //top half of block
|
||||
}
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -146,7 +146,7 @@ class Trapdoor extends Transparent{
|
||||
|
||||
public function onActivate(Item $item, Player $player = null) : bool{
|
||||
$this->meta ^= self::MASK_OPENED;
|
||||
$this->getLevel()->setBlock($this, $this, true);
|
||||
$this->getLevelNonNull()->setBlock($this, $this, true);
|
||||
$this->level->addSound(new DoorSound($this));
|
||||
return true;
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ class Vine extends Flowable{
|
||||
$this->meta |= $blockReplace->meta;
|
||||
}
|
||||
|
||||
$this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class WallBanner extends StandingBanner{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide($this->meta ^ 0x01)->getId() === self::AIR){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class WallSign extends SignPost{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide($this->meta ^ 0x01)->getId() === self::AIR){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,13 +70,11 @@ class Water extends Liquid{
|
||||
if($entity->isOnFire()){
|
||||
$entity->extinguish();
|
||||
}
|
||||
|
||||
$entity->resetFallDistance();
|
||||
}
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$ret = $this->getLevel()->setBlock($this, $this, true, false);
|
||||
$this->getLevel()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
$ret = $this->getLevelNonNull()->setBlock($this, $this, true, false);
|
||||
$this->getLevelNonNull()->scheduleDelayedBlockUpdate($this, $this->tickRate());
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class WaterLily extends Flowable{
|
||||
if($blockClicked instanceof Water){
|
||||
$up = $blockClicked->getSide(Vector3::SIDE_UP);
|
||||
if($up->getId() === Block::AIR){
|
||||
$this->getLevel()->setBlock($up, $this, true, true);
|
||||
$this->getLevelNonNull()->setBlock($up, $this, true, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ class WaterLily extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!($this->getSide(Vector3::SIDE_DOWN) instanceof Water)){
|
||||
$this->getLevel()->useBreakOn($this);
|
||||
$this->getLevelNonNull()->useBreakOn($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ class Wood extends Solid{
|
||||
|
||||
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
|
||||
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
|
||||
return $this->getLevel()->setBlock($blockReplace, $this, true, true);
|
||||
return $this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
|
||||
}
|
||||
|
||||
public function getVariantBitmask() : int{
|
||||
|
@ -68,7 +68,7 @@ class CommandReader extends Thread{
|
||||
|
||||
$opts = getopt("", ["disable-readline", "enable-readline"]);
|
||||
|
||||
if(extension_loaded("readline") and (Utils::getOS() === "win" ? isset($opts["enable-readline"]) : !isset($opts["disable-readline"])) and !$this->isPipe(STDIN)){
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,9 @@ use pocketmine\permission\BanEntry;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function sort;
|
||||
use function strtolower;
|
||||
use const SORT_STRING;
|
||||
|
||||
class BanListCommand extends VanillaCommand{
|
||||
|
||||
@ -62,10 +64,11 @@ class BanListCommand extends VanillaCommand{
|
||||
$args[0] = "players";
|
||||
}
|
||||
|
||||
$list = $list->getEntries();
|
||||
$message = implode(", ", array_map(function(BanEntry $entry) : string{
|
||||
$list = array_map(function(BanEntry $entry) : string{
|
||||
return $entry->getName();
|
||||
}, $list));
|
||||
}, $list->getEntries());
|
||||
sort($list, SORT_STRING);
|
||||
$message = implode(", ", $list);
|
||||
|
||||
if($args[0] === "ips"){
|
||||
$sender->sendMessage(new TranslationContainer("commands.banlist.ips", [count($list)]));
|
||||
|
@ -62,7 +62,7 @@ class GiveCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
try{
|
||||
$item = ItemFactory::fromString($args[1]);
|
||||
$item = ItemFactory::fromStringSingle($args[1]);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.give.item.notFound", [$args[1]]));
|
||||
return true;
|
||||
|
@ -30,6 +30,8 @@ use function array_filter;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function sort;
|
||||
use const SORT_STRING;
|
||||
|
||||
class ListCommand extends VanillaCommand{
|
||||
|
||||
@ -52,6 +54,7 @@ class ListCommand extends VanillaCommand{
|
||||
}, array_filter($sender->getServer()->getOnlinePlayers(), function(Player $player) use ($sender) : bool{
|
||||
return $player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player));
|
||||
}));
|
||||
sort($playerNames, SORT_STRING);
|
||||
|
||||
$sender->sendMessage(new TranslationContainer("commands.players.list", [count($playerNames), $sender->getServer()->getMaxPlayers()]));
|
||||
$sender->sendMessage(implode(", ", $playerNames));
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user