Compare commits

..

125 Commits

Author SHA1 Message Date
6d62b06ce6 Release 4.0.0-BETA14 2021-11-30 01:26:07 +00:00
8be92d16fe Merge branch 'stable' 2021-11-30 01:19:54 +00:00
8079ae341a Updated build/php submodule to pmmp/php-build-scripts@bd329dba08 2021-11-30 01:19:14 +00:00
ba295dc7dc Always use LF in .neon files 2021-11-30 01:16:28 +00:00
38325c8573 Updated translations 2021-11-30 01:14:21 +00:00
f239b077b9 Fixed PHPStan complaints 2021-11-30 00:36:38 +00:00
6f8f460a6c Partially revert "ConsoleReaderChildProcess: Commit suicide in more cases"
This reverts commit cbe0f44c4f.

This achieves the same result as the reverted commit wrt. process in the
same manner (writing a keepalive into the socket and checking if it
failed to send). However, it does _not_ allow the process to die on
reaching pipe EOF, since this can cause many spams of subprocesses when
stdin is actually not a tty (e.g. in a Docker container).
2021-11-30 00:27:52 +00:00
882df94bcb ConsoleReaderThread: fixed zombie process leak 2021-11-29 23:45:10 +00:00
4a8ca603a1 Log a message when forceShutdown() is called for anything other than a graceful shutdown 2021-11-28 18:53:34 +00:00
52f0c4f3ed Removed dodgy test using invalid block metadata 2021-11-27 22:51:14 +00:00
e2815eed60 BlockFactory: remap a bunch more invalid states 2021-11-27 20:07:58 +00:00
932a88764c composer commands suck 2021-11-27 04:07:25 +00:00
9540193766 Fixed everything lighting on fire 2021-11-27 03:54:30 +00:00
cc23e0b7a1 Updated DevTools submodule to pmmp/DevTools@6af57741e6 2021-11-27 03:52:32 +00:00
1f9400f901 World: automatically remap invalid blockstates on chunk load
this fixes a wide range of blocks with invalid blockstates becoming update! blocks on the client.

The most common occurrence of this was air with nonzero metadata left behind by world editors which set blockIDs but not block metadata. This caused large ghost structures of update! blocks to appear from nowhere.

The performance impact of this is very minimal (20 microseconds per chunk load in timings, compared to average 660 microseconds to load tiles).
2021-11-27 01:12:30 +00:00
e5149756a8 WorldTimings: fixed merge error introduced by 3bf87378ef 2021-11-27 00:06:09 +00:00
bc18969a09 Merge branch 'stable' 2021-11-26 23:45:09 +00:00
c19174a174 3.25.7 is next 2021-11-26 23:37:47 +00:00
f95142f6b6 Release 3.25.6 2021-11-26 23:37:46 +00:00
7ace24caab Fixed borked build number
this was a problem before the recent clean-up; the only reason it just decided to show now is because 2000+25 is valid PHP code, so PHP saved our asses.
2021-11-26 23:36:19 +00:00
32f619ac49 3.25.6 is next 2021-11-26 23:20:48 +00:00
1bb6ac4fb6 Release 3.25.5 2021-11-26 23:20:40 +00:00
533d3aae8b Merge branch 'stable' 2021-11-26 22:41:18 +00:00
52a891ba73 shut 2021-11-26 22:32:25 +00:00
71b813d4f9 Define pocketmine\BUILD_NUMBER from phar metadata
this way we don't have to patch the code (no idea why we were doing that anyway).
2021-11-26 22:27:58 +00:00
f2540a72ad Backport improved make-release.php from PM4 2021-11-26 22:10:46 +00:00
03f13495b7 Merge branch 'stable' 2021-11-26 21:59:55 +00:00
7e0f6c02a1 Updated build/php submodule to pmmp/php-build-scripts@a59722c676 2021-11-26 21:59:39 +00:00
1bc7869f6e Added remapping for almost 4000 invalid blockstates
when a block has sole ownership of an ID, the state bitmask can be ignored and we can just claim the whole metadata range for that single block.
This fixes a large number of issues with unknown blocks on older worlds where world editors did not remove the metadata, although update blocks will currently still appear on initial chunk send due to lack of AOT conversion (TODO).
2021-11-26 01:58:52 +00:00
5556861000 ItemFactory: move SweetBerries registration to the correct place 2021-11-26 00:46:35 +00:00
7dd5d0b593 4.0.0-BETA14 is next 2021-11-25 00:40:43 +00:00
9338d42742 Release 4.0.0-BETA13 2021-11-25 00:40:40 +00:00
9346ecdc39 Merge branch 'stable' 2021-11-25 00:01:48 +00:00
c023c02b6c MemoryManager: Removed obsolete workaround for $GLOBALS not being defined on threads
this was long since fixed, and everyone has since been forced to upgrade to pthreads 4.0.0, which definitely has the fix.
2021-11-24 23:57:55 +00:00
bb7683158f Remove dead ignoreErrors patterns 2021-11-24 23:52:51 +00:00
fad96b77ce stfu 2021-11-24 23:49:56 +00:00
40575a6dcf Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-24 23:43:03 +00:00
40f8f042da Merge branch 'stable' 2021-11-24 23:42:53 +00:00
0fe6038c41 Merge branch 'stable' 2021-11-24 23:41:40 +00:00
adff561483 phpstan: go nuclear on OPcache
when using dynamic reflection (which is the default), any time static reflection comes into play, bad shit starts to happen because of FileReadTrapStreamWrapper.
I attempted to fix these issues (phpstan/phpstan-src#801) and failed miserably.
So, to save the hassle, it's time to just remove OPcache from the picture (which, unfortunately, also means that PHPStan will not benefit from JIT).
2021-11-24 23:40:54 +00:00
ad56392d95 Skull: fixed calculation of collision boxes (#4591) 2021-11-24 21:42:51 +00:00
472ffb28ff ScriptPluginLoader: use parseDocComment() instead of reinventing the wheel 2021-11-24 17:22:49 +00:00
726c5652f7 ScriptPluginLoader: fixed reading @tags from non-docblock lines preceding the first docblock 2021-11-24 17:07:34 +00:00
b784a04e08 Utils: fixed parseDocComment() ignoring tags containing hyphens 2021-11-24 16:38:37 +00:00
5c7125f190 Improved error handling for loading broken entity / tile data 2021-11-23 17:41:26 +00:00
eb0cf52d81 Remove useless code (#4590) 2021-11-23 17:09:33 +00:00
d8f0fd0a7e McRegion: skip chunks with TerrainGenerated=false
legacy PM used to save even ungenerated chunks, and omitted some tags when doing so which we expect to always be present.
2021-11-23 01:49:48 +00:00
fb0eebc0dc RegionWorldProvider: Show a more specific message on missing required ByteArrayTags 2021-11-23 01:39:35 +00:00
020cd7b966 CrashDump: fixed encodedData being uninitialized before getEncodedData() is called 2021-11-22 22:31:07 +00:00
c37c261c0f Separate crashdump file generation from crashdump data collection
this allows CrashDump to be used just to generate data, which will come in useful for non-crash error reporting in the future (e.g. packet decoding errors).
2021-11-22 22:19:40 +00:00
2bb97d8904 Be quiet CS 2021-11-22 15:40:47 +00:00
d3878b2d57 fixed spam 2021-11-22 15:37:33 +00:00
cbe0f44c4f ConsoleReaderChildProcess: Commit suicide in more cases
this makes it slightly less annoying to get rid of as an orphan process, though it still won't immediately die.
2021-11-22 14:58:45 +00:00
37622e02b8 Updated translations 2021-11-21 21:11:39 +00:00
ed8b4950a3 Updated BedrockProtocol 2021-11-21 21:10:58 +00:00
fc7d297f60 Added missing fields of StructureSettings 2021-11-21 20:51:35 +00:00
7b4ef293bd NetworkBinaryStream: fixed incorrect field types for StructureSettings 2021-11-21 20:49:00 +00:00
c72d66f370 Merge branch 'stable' 2021-11-20 18:28:55 +00:00
3683884b9c Updated build/php submodule to pmmp/php-build-scripts@7a2ab5b922 2021-11-20 18:27:43 +00:00
37e8b1ee8c Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-20 18:25:45 +00:00
046dafc34f Merge branch 'stable' 2021-11-20 18:25:30 +00:00
db135788b9 Updated transient dependencies 2021-11-20 18:19:27 +00:00
b34e6f53eb Changed visibility of Projectile->move to Protected. (#4585) 2021-11-19 23:21:10 +00:00
b4b954cc5f build/generate-registry-annotations: accommodate code with CRLF 2021-11-19 21:38:43 +00:00
7210db25b0 Bump phpstan/phpstan from 1.1.2 to 1.2.0 (#4583)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.1.2 to 1.2.0.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.1.2...1.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-19 14:42:01 +00:00
4599913034 Separate crashdump rendering from crashdump data collection
this allows this code to be reused for reproducing crashdumps based on the original data.
2021-11-18 00:58:20 +00:00
c48aa274e7 Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-15 22:52:47 +00:00
269231c228 Ban foreach(arrayWithStringKeys as k => v)
this is not as good as phpstan/phpstan-src#769 (e.g. array_key_first()/array_key_last() aren't covered by this, nor is array_rand()) but it does eliminate the most infuriating cases where this usually crops up.
2021-11-15 22:52:05 +00:00
4cad552909 Allow input of relative coordinates to setworldspawn command (#4575) 2021-11-14 20:07:37 +00:00
f2d5455c5e changelog: mention that armor right-click equipping is now supported
[ci skip]
closes #4570
2021-11-14 16:42:35 +00:00
65247b7248 changelog: add notes about ender inventory
closes #4569
2021-11-14 16:41:57 +00:00
2f408708f0 Explosion: fixed blocks with tiles not using said tiles for drop info
closes #4571
2021-11-14 16:27:47 +00:00
3dd03075cb StringToItemParser: added some quality-of-life aliases 2021-11-14 15:52:50 +00:00
82b5bca83e Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-14 15:52:05 +00:00
639867a640 Added missing aliases for wooden items 2021-11-14 15:51:41 +00:00
d4a382d568 Fix position of setworldspawn command (#4574)
* The world spawn position is no longer rounded

* Remove round() since the position is always int
2021-11-14 15:40:20 +00:00
399824c31c Add correct drop for Podzol (#4573) 2021-11-14 14:15:36 +00:00
ada469bc45 README: do not show beta releases on badge
[ci skip]
2021-11-12 01:35:39 +00:00
dc8243f88b Bump phpstan/phpstan from 1.1.1 to 1.1.2 (#4564)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/1.1.1...1.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-12 00:24:23 +00:00
7668171c56 Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-12 00:17:07 +00:00
e4754ab029 PluginBase: Improved error messages for commands containing illegal characters 2021-11-12 00:16:53 +00:00
3276047497 Updated composer dependencies 2021-11-12 00:13:22 +00:00
49a8eff11e BUILDING: submodules are no longer required
submodules are useful (e.g. devtools, build/php) but they are not required to build a server phar.
2021-11-11 14:29:56 +00:00
73592349cd 4.0.0-BETA13 is next 2021-11-09 16:50:46 +00:00
635a9143de Release 4.0.0-BETA12 2021-11-09 16:50:42 +00:00
c3ec9c0948 Effect default duration is once again NOT hardcoded, like PM3
I have no fucking idea why I hardcoded this to begin with. Not one of my better ideas ...
2021-11-09 01:52:47 +00:00
09a2e006a8 CS AGAIN 2021-11-09 00:20:06 +00:00
fed59d3ebe added missing file 2021-11-09 00:11:39 +00:00
c7beb0a702 Clean up inventory auto close mess from PM3
on PM3 there was no concept of 'current window', we had no idea which window the player was actually looking at.
2021-11-08 23:51:25 +00:00
5be429a8c4 Ensure inventories get evacuated on server-side window close 2021-11-08 23:48:05 +00:00
ab002ca06d Improved handling of temporary inventory windows
evacuation behaviour is now consistent regardless of who is doing it
2021-11-08 23:36:58 +00:00
6efb1db107 Fixed inventories not working after dying with inventory open
closes #4185
closes #4177
2021-11-08 23:04:00 +00:00
6fdcfb01c8 Seal up main inventory open/close logic inside InventoryManager where it belongs 2021-11-08 22:58:06 +00:00
1beec348f9 3.25.5 is next 2021-11-08 22:33:09 +00:00
7306a2d939 Release 3.25.4 2021-11-08 22:33:08 +00:00
4bf338f783 Player: fixed removeWindow() causing all other inventories to be unopenable 2021-11-08 22:29:14 +00:00
255ff63fda 3.25.4 is next 2021-11-08 20:35:15 +00:00
d72f6a3ac6 Release 3.25.3 2021-11-08 20:35:14 +00:00
93a1e84ad9 TypeConverter: further simplification 2021-11-08 20:27:53 +00:00
c33f97ae41 TypeConverter: clean up absurdly overcomplicated bullshit in createInventoryAction() 2021-11-08 20:18:19 +00:00
cc4bb91fcb Implemented IPv6 support (#4554) 2021-11-08 20:03:28 +00:00
eb9012401b Merge branch 'stable' 2021-11-08 19:53:56 +00:00
3b34268ed6 Human: try to trap this stupid float cast bug in the wild 2021-11-08 19:48:39 +00:00
4c07078586 Merge branch 'stable' 2021-11-08 19:01:08 +00:00
19a3efe893 ....... 2021-11-08 18:57:14 +00:00
a1ecdc27e5 Removed Vanilla*::fromString()
these were misbegotten and should never have existed.
If someone really needs these for some reason, they can use getAll()[name].
2021-11-08 18:52:14 +00:00
f93b5be789 Added new dynamic StringToEffectParser 2021-11-08 18:49:28 +00:00
1fb60b5b3a CS fix again 2021-11-08 18:45:05 +00:00
08420c2556 Added new dynamic StringToEnchantmentParser
this should be used instead of VanillaEnchantments::fromString(), because it allows registering custom aliases.
2021-11-08 18:44:15 +00:00
18f5fb66bb Abstract the base functionality of StringToItemParser 2021-11-08 18:37:05 +00:00
a6f6b60bed fix CS again 2021-11-08 18:02:24 +00:00
c6c992a1f0 Preparations for negative Y support 2021-11-08 17:28:22 +00:00
df39a1ca07 TeleportCommand: do not hardcode world bounds 2021-11-08 17:22:01 +00:00
be6d1843de Merge branch 'master' of github.com:pmmp/PocketMine-MP 2021-11-08 17:17:20 +00:00
2b0b9bd8ed Update composer dependencies 2021-11-08 17:16:57 +00:00
76dad46e13 Bump phpstan/phpstan from 1.0.2 to 1.1.1 (#4560)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.0.2 to 1.1.1.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/1.0.2...1.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-08 13:36:06 +00:00
eb3530b6e6 Use pmmp/setup-php-action to compile PHP 2021-11-07 23:13:56 +00:00
4131bcef08 Changed "Level" string to "World" in Position::__toString() method. (#4559) 2021-11-07 21:11:55 +00:00
b84f7c18ec Install ext/crypto from PECL 2021-11-07 19:18:09 +00:00
45edb94607 Crafting tables now work the same way as anvils and enchanting tables
Removing almost all special-case logic for crafting tables.
2021-11-07 16:20:07 +00:00
6b316dc29a PluginManager: Make declaration of duplicate permissions a load error 2021-11-06 17:05:37 +00:00
d9d37f7fa6 ResourcePacksPacketHandler: fixed a mistake from c773e43eda
fixes #4557

Note: you may need to clear your local pack cache in order to get it working again.
2021-11-06 16:45:14 +00:00
4cb6c7dc1e PluginManager: fixed plugins being able to alter groups of other plugins' permissions
this could happen if a plugin declared a permission already declared by another plugin, and then declared a different default for it (e.g. true instead of op).
2021-11-06 16:32:19 +00:00
b392651354 pocketmine.yml: always refer to worlds as worlds in config comments, not levels 2021-11-06 02:22:14 +00:00
f81c55ce6c 4.0.0-BETA12 is next 2021-11-06 01:17:03 +00:00
119 changed files with 1862 additions and 1229 deletions

1
.gitattributes vendored
View File

@ -4,6 +4,7 @@
*.sh text eol=lf *.sh text eol=lf
*.txt text eol=lf *.txt text eol=lf
*.properties text eol=lf *.properties text eol=lf
*.neon text eol=lf
*.bat text eol=crlf *.bat text eol=crlf
*.cmd text eol=crlf *.cmd text eol=crlf
*.ps1 text eol=crlf *.ps1 text eol=crlf

View File

@ -35,17 +35,18 @@ jobs:
- name: Install Composer dependencies - name: Install Composer dependencies
run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
- name: Patch VersionInfo - name: Calculate build number
id: build-number
run: | run: |
BUILD_NUMBER=2000+$GITHUB_RUN_NUMBER #to stay above jenkins BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins
echo "Build number: $BUILD_NUMBER" echo "Build number: $BUILD_NUMBER"
sed -i "s/const BUILD_NUMBER = 0/const BUILD_NUMBER = ${BUILD_NUMBER}/" src/VersionInfo.php echo ::set-output name=BUILD_NUMBER::$BUILD_NUMBER
- name: Minify BedrockData JSON files - name: Minify BedrockData JSON files
run: php vendor/pocketmine/bedrock-data/.minify_json.php run: php vendor/pocketmine/bedrock-data/.minify_json.php
- name: Build PocketMine-MP.phar - name: Build PocketMine-MP.phar
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }} run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }} --build ${{ steps.build-number.outputs.BUILD_NUMBER }}
- name: Get PocketMine-MP release version - name: Get PocketMine-MP release version
id: get-pm-version id: get-pm-version
@ -56,7 +57,7 @@ jobs:
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);') echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
- name: Generate build info - name: Generate build info
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} > build_info.json run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json
- name: Upload release artifacts - name: Upload release artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2

View File

@ -16,17 +16,11 @@ jobs:
php: [8.0.11] php: [8.0.11]
steps: steps:
- uses: actions/checkout@v2 #needed for build.sh - name: Build and prepare PHP cache
- name: Check for PHP build cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
id: php-build-cache
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Compile PHP
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: ./tests/gh-actions/build.sh "${{ matrix.php }}"
phpstan: phpstan:
name: PHPStan analysis name: PHPStan analysis
@ -42,23 +36,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -92,23 +74,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -144,23 +114,11 @@ jobs:
with: with:
submodules: true submodules: true
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -194,23 +152,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php

View File

@ -14,13 +14,12 @@ Because PocketMine-MP requires several non-standard PHP extensions and configura
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`. If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
## Setting up environment ## Setting up environment
1. `git clone --recursive https://github.com/pmmp/PocketMine-MP.git` 1. `git clone https://github.com/pmmp/PocketMine-MP.git`
2. `composer install` 2. `composer install`
## Checking out a different branch to build ## Checking out a different branch to build
1. `git checkout <branch to checkout>` 1. `git checkout <branch to checkout>`
2. `git submodule update --init` 2. Re-run `composer install` to synchronize dependencies.
3. Re-run `composer install` to synchronize dependencies.
## Optimizing for release builds ## Optimizing for release builds
1. Add the flags `--no-dev --classmap-authoritative` to your `composer install` command. This will reduce build size and improve autoloading speed. 1. Add the flags `--no-dev --classmap-authoritative` to your `composer install` command. This will reduce build size and improve autoloading speed.

View File

@ -5,7 +5,7 @@
<p align="center"> <p align="center">
<img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" /> <img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" />
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img src="https://img.shields.io/github/v/tag/pmmp/PocketMine-MP?label=release&logo=github" alt="GitHub tag (latest semver)" /></a> <img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/pmmp/PocketMine-MP?label=release&sort=semver">
<a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a> <a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a>
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a> <a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
</p> </p>

View File

@ -23,15 +23,15 @@ declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
if(count($argv) !== 4){ if(count($argv) !== 5){
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)>"); fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number>");
exit(1); exit(1);
} }
echo json_encode([ echo json_encode([
"php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION), "php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
"base_version" => \pocketmine\VersionInfo::BASE_VERSION, "base_version" => \pocketmine\VersionInfo::BASE_VERSION,
"build" => \pocketmine\VersionInfo::BUILD_NUMBER, "build" => (int) $argv[4],
"is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD, "is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD,
"channel" => \pocketmine\VersionInfo::BUILD_CHANNEL, "channel" => \pocketmine\VersionInfo::BUILD_CHANNEL,
"git_commit" => $argv[1], "git_commit" => $argv[1],

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\build\generate_known_translation_apis; namespace pocketmine\build\generate_known_translation_apis;
use pocketmine\lang\Translatable; use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function array_map; use function array_map;
use function count; use function count;
@ -100,7 +101,7 @@ final class KnownTranslationKeys{
HEADER; HEADER;
ksort($languageDefinitions, SORT_STRING); ksort($languageDefinitions, SORT_STRING);
foreach($languageDefinitions as $k => $_){ foreach(Utils::stringifyKeys($languageDefinitions) as $k => $_){
echo "\tpublic const "; echo "\tpublic const ";
echo constantify($k); echo constantify($k);
echo " = \"" . $k . "\";\n"; echo " = \"" . $k . "\";\n";
@ -135,7 +136,7 @@ HEADER;
$parameterRegex = '/{%(.+?)}/'; $parameterRegex = '/{%(.+?)}/';
$translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName(); $translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName();
foreach($languageDefinitions as $key => $value){ foreach(Utils::stringifyKeys($languageDefinitions) as $key => $value){
$parameters = []; $parameters = [];
if(preg_match_all($parameterRegex, $value, $matches) > 0){ if(preg_match_all($parameterRegex, $value, $matches) > 0){
foreach($matches[1] as $parameterName){ foreach($matches[1] as $parameterName){

View File

@ -91,7 +91,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
throw new \RuntimeException("Failed to get contents of $file"); throw new \RuntimeException("Failed to get contents of $file");
} }
if(preg_match("/^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/^((final|abstract)\s+)?class /m', $contents) !== 1){ if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
continue; continue;
} }
$shortClassName = basename($file, ".php"); $shortClassName = basename($file, ".php");
@ -101,7 +101,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
} }
$reflect = new \ReflectionClass($className); $reflect = new \ReflectionClass($className);
$docComment = $reflect->getDocComment(); $docComment = $reflect->getDocComment();
if($docComment === false || preg_match("/^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){ if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){
continue; continue;
} }
echo "Found registry in $file\n"; echo "Found registry in $file\n";

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\make_release; namespace pocketmine\build\make_release;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString; use pocketmine\utils\VersionString;
use pocketmine\VersionInfo; use pocketmine\VersionInfo;
use function array_keys; use function array_keys;
@ -76,7 +77,7 @@ const ACCEPTED_OPTS = [
function main() : void{ function main() : void{
$filteredOpts = []; $filteredOpts = [];
foreach(getopt("", ["current:", "next:", "channel:", "help"]) as $optName => $optValue){ foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){
if($optName === "help"){ if($optName === "help"){
fwrite(STDOUT, "Options:\n"); fwrite(STDOUT, "Options:\n");

View File

@ -134,13 +134,18 @@ function main() : void{
exit(1); exit(1);
} }
$opts = getopt("", ["out:", "git:"]); $opts = getopt("", ["out:", "git:", "build:"]);
if(isset($opts["git"])){ if(isset($opts["git"])){
$gitHash = $opts["git"]; $gitHash = $opts["git"];
}else{ }else{
$gitHash = Git::getRepositoryStatePretty(dirname(__DIR__)); $gitHash = Git::getRepositoryStatePretty(dirname(__DIR__));
echo "Git hash detected as $gitHash" . PHP_EOL; echo "Git hash detected as $gitHash" . PHP_EOL;
} }
if(isset($opts["build"])){
$build = (int) $opts["build"];
}else{
$build = 0;
}
foreach(buildPhar( foreach(buildPhar(
$opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar", $opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
dirname(__DIR__) . DIRECTORY_SEPARATOR, dirname(__DIR__) . DIRECTORY_SEPARATOR,
@ -150,7 +155,8 @@ function main() : void{
'vendor' 'vendor'
], ],
[ [
'git' => $gitHash 'git' => $gitHash,
'build' => $build
], ],
<<<'STUB' <<<'STUB'
<?php <?php

View File

@ -21,3 +21,18 @@ Plugin developers should **only** update their required API to this version if y
- Fixed crash in `Player->showPlayer()` when the target is not in the same world. - Fixed crash in `Player->showPlayer()` when the target is not in the same world.
- `Human->setLifetimeTotalXp()` now limits the maximum value to 2^31. - `Human->setLifetimeTotalXp()` now limits the maximum value to 2^31.
- Fixed players, who died in hardcore mode and were unbanned, getting re-banned on next server join. - Fixed players, who died in hardcore mode and were unbanned, getting re-banned on next server join.
# 3.25.3
- Fixed crash when players try to pickup XP while already having max XP.
- Added a sanity check to `Human->setCurrentTotalXp()` to try and catch an elusive bug that's been appearing in the wild - please get in touch if you know how to reproduce it!
# 3.25.4
- Fixed a long-standing issue with `Player->removeWindow()` breaking inventory UIs on the client.
# 3.25.5
- Protocol: Fixed incorrect encoding in `StructureSettings`
- Fixed reading tags from non-docblock comments in script plugins.
- Build number is now defined in phar metadata instead of being patched into the source code directly.
# 3.25.6
- Fixed borked build number in release build of 3.25.5.

View File

@ -313,6 +313,10 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
### Entity ### Entity
#### General #### General
- `Entity` no longer extends from `Location`. `Entity->getLocation()` and `Entity->getPosition()` should be used instead. - `Entity` no longer extends from `Location`. `Entity->getLocation()` and `Entity->getPosition()` should be used instead.
- Ender inventory has been refactored. It's now split into two parts:
- `EnderChestInventory` is a temporary gateway "inventory" that acts as a proxy to the player's ender inventory. It has a `Position` for holder. This is discarded when the player closes the inventory window.
- `PlayerEnderInventory` is the storage part. This is stored in `Human` and does not contain any block info.
- To open the player's ender inventory, use `Player->setCurrentWindow(new EnderChestInventory($blockPos, $player->getEnderInventory()))`.
- The following public fields have been removed: - The following public fields have been removed:
- `Entity->chunk`: Entities no longer know which chunk they are in (the `World` now manages this instead). - `Entity->chunk`: Entities no longer know which chunk they are in (the `World` now manages this instead).
- `Entity->height`: moved to `EntitySizeInfo`; use `Entity->size` instead - `Entity->height`: moved to `EntitySizeInfo`; use `Entity->size` instead
@ -351,6 +355,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- `Human->getMaxFood()` -> `HungerManager->getMaxFood()` - `Human->getMaxFood()` -> `HungerManager->getMaxFood()`
- `Human->addFood()` -> `HungerManager->addFood()` - `Human->addFood()` -> `HungerManager->addFood()`
- `Human->isHungry()` -> `HungerManager->isHungry()` - `Human->isHungry()` -> `HungerManager->isHungry()`
- `Human->getEnderChestInventory()` -> `Human->getEnderInventory()`
- `Human->getSaturation()` -> `HungerManager->getSaturation()` - `Human->getSaturation()` -> `HungerManager->getSaturation()`
- `Human->setSaturation()` -> `HungerManager->setSaturation()` - `Human->setSaturation()` -> `HungerManager->setSaturation()`
- `Human->addSaturation()` -> `HungerManager->addSaturation()` - `Human->addSaturation()` -> `HungerManager->addSaturation()`
@ -564,6 +569,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- `CallbackInventoryChangeListener` - `CallbackInventoryChangeListener`
- `CreativeInventory`: contains the creative functionality previously embedded in `pocketmine\item\Item`, see Item changes for details - `CreativeInventory`: contains the creative functionality previously embedded in `pocketmine\item\Item`, see Item changes for details
- `InventoryChangeListener`: allows listening (but not interfering with) events in an inventory. - `InventoryChangeListener`: allows listening (but not interfering with) events in an inventory.
- `PlayerEnderInventory`: represents the pure storage part of the player's ender inventory, without any block information
- `transaction\CreateItemAction` - `transaction\CreateItemAction`
- `transaction\DestroyItemAction` - `transaction\DestroyItemAction`
- The following classes have been renamed / moved: - The following classes have been renamed / moved:
@ -588,6 +594,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- The following API methods have been removed: - The following API methods have been removed:
- `BaseInventory->getDefaultSize()` - `BaseInventory->getDefaultSize()`
- `BaseInventory->setSize()` - `BaseInventory->setSize()`
- `EnderChestInventory->setHolderPosition()`
- `Inventory->close()` - `Inventory->close()`
- `Inventory->dropContents()` - `Inventory->dropContents()`
- `Inventory->getName()` - `Inventory->getName()`
@ -1276,6 +1283,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- Implemented offhand inventory. - Implemented offhand inventory.
- Block-picking is now supported in survival mode. - Block-picking is now supported in survival mode.
- Block picking behaviour now matches vanilla (no longer overwrites held item, jumps to existing item where possible). - Block picking behaviour now matches vanilla (no longer overwrites held item, jumps to existing item where possible).
- Armor can now be equipped by right-clicking while holding it.
# 4.0.0-BETA2 # 4.0.0-BETA2
Released 10th September 2021. Released 10th September 2021.
@ -1634,3 +1642,119 @@ Released 6th November 2021.
### Gameplay ### Gameplay
- Chunks are now sent in proper circles. This improves the experience when flying quickly parallel to X or Z axis, since now more chunks in front of the player will load sooner. - Chunks are now sent in proper circles. This improves the experience when flying quickly parallel to X or Z axis, since now more chunks in front of the player will load sooner.
- Added support for emotes. - Added support for emotes.
# 4.0.0-BETA12
Released 9th November 2021.
## General
- Introduced support for connecting via IPv6.
- PHP binary used must now always be built with IPv6 support, even if IPv6 is disabled. This is because RakNet may still send link-local IPv6 loopback addresses in connection packets even when only using IPv4.
- The default port for IPv6 is `19133` (similar to Bedrock Dedicated Server).
- Port `19133` is used by default so that Minecraft Bedrock can detect IPv6 servers on LAN.
- GS4 Query is supported on both IPv4 and IPv6 according to `server.properties` settings.
- The following `server.properties` settings are influential:
- `enable-ipv6`: `on` by default. Disabling this completely disables IPv6 support.
- `server-ipv6`: `::` by default (equivalent to "any IP", like `0.0.0.0` for IPv4). Most users shouldn't need to change this setting, and it doesn't appear in `server.properties` by default.
- `server-portv6`: `19133` by default. You may run both IPv4 and IPv6 on the same port.
- Various internal changes have been made to prepare for negative Y axis support (upcoming 1.18).
## Fixes
- Fixed resource packs not applying.
- Fixed inventory windows being unopenable after dying with inventory windows open.
- Fixed plugins being able to alter other plugins' permission defaults by redeclaring them in the `plugin.yml`.
## API
### Block
- `VanillaBlocks::fromString()` has been removed.
- Added `CraftingTableInventory`. This exclusively represents a crafting table's 3x3 crafting grid.
### Crafting
- `CraftingGrid` is now abstract.
- Removed `CraftingGrid->getHolder()`.
- The constructor of `CraftingGrid` no longer accepts a `Player` parameter.
### Entity
#### Effect
- `Effect->__construct()` once again accepts an `int $defaultDuration` parameter.
- Removed `VanillaEffects::fromString()`.
- Added `StringToEffectParser`
- Supports custom aliases!
- This is used by `/effect` to provide name support.
### Event
- `InventoryOpenEvent` is now fired when a player opens a crafting table's UI.
- `InventoryCloseEvent` is now fired when a player closes a crafting table's UI.
- `PlayerDropItemEvent` will now prevent the drops from force-closing of the following inventories:
- anvil
- enchanting table
- loom
### Inventory
- Added `TemporaryInventory`. This should be implemented by any inventory whose contents should be evacuated when closing.
- Added `PlayerCraftingInventory`. This exclusively represents the player's own 2x2 crafting grid.
### Item
- Removed `VanillaItems::fromString()`
- Obsoleted by the far superior, much more dynamic, and plugin-customizable `StringToItemParser`.
- `StringToItemParser` allows mapping strings to closure callbacks, allowing you to create aliases for `/give` for any item, including custom ones.
#### Enchantment
- Removed `VanillaEnchantments::fromString()`.
- Added `StringToEnchantmentParser`
- Supports custom aliases!
- This is used by `/enchant` to provide name support.
### Player
- Removed `Player->setCraftingGrid()`. To open a 3x3 crafting grid to a player, use `setCurrentWindow(new CraftingTableInventory)`.
### Server
- Added the following API methods:
- `Server->getIpV6()`
- `Server->getPortV6()`
# 4.0.0-BETA13
Released 25th November 2021.
## General
- Improved error messages when a plugin command name or alias contains an illegal character.
- Fixed script plugins reading tags from non-docblocks before the actual docblock.
- Added a sanity check to `Human->setCurrentTotalXp()` to try and catch an elusive bug that's been appearing in the wild - please get in touch if you know how to reproduce it!
- Updated `BUILDING.md` to reflect the fact that submodules are no longer required to build or run the server.
## Internals
- Crashdump rendering has been separated from crashdump data generation. This allows rendering crashdumps from existing JSON data.
- Direct iteration of arrays with string keys is now disallowed by a custom PHPStan rule. This is because numeric strings are casted to integers when used as array keys, which produces a variety of unexpected behaviour particularly for iteration.
- To iterate on arrays with string keys, `Utils::stringifyKeys()` must now be used.
## Fixes
- Fixed various crashes involving bad entity data saved on disk (e.g. an item entity with invalid item would crash the server).
- Fixed various crashes involving arrays with numeric string keys.
- Fixed crash when players try to pickup XP while already having max XP.
- Fixed ungenerated chunks saved on disk by old versions of PocketMine-MP (before 3.0.0) being treated as corrupted during world conversion (they are now ignored instead).
- Fixed misleading corruption error message when saved chunks have missing NBT tags.
## Gameplay
- Fixed `/setworldspawn` setting incorrect positions based on player position when in negative coordinates.
- `/setworldspawn` now accepts relative coordinates when used as a player.
- Added some extra aliases for `/give` and `/clear`: `chipped_anvil`, `coarse_dirt`, `damaged_anvil`, `dark_oak_standing_sign`, `jungle_wood_stairs`, `jungle_wooden_stairs` and `oak_standing_sign`.
- Some of these were added for quality-of-life, others were added just to be consistent.
- Fixed explosions dropping incorrect items when destroying blocks with tile data (e.g. banners, beds).
- Fixed the bounding box of skulls when mounted on a wall.
- Fixed podzol dropping itself when mined (instead of dirt).
## API
### Entity
- `Projectile->move()` is now protected, like its parent.
### Utils
- `Utils::parseDocComment()` now allows `-` in tag names.
# 4.0.0-BETA14
Released 30th November 2021.
## General
- The server will now log an EMERGENCY-level message when `forceShutdown()` is used for any other reason than a graceful shutdown.
- The server will now attempt to translate invalid blocks to valid equivalents when loading chunks. This fixes many issues with `update!` blocks appearing in worlds, particularly ghost structures (these would appear when world editors previously erased some blocks by setting their IDs but not metadata).
## Fixes
- Fixed `ConsoleReaderThread` spawning many zombie processes when running a server inside a Docker container.

View File

@ -35,13 +35,13 @@
"fgrosse/phpasn1": "^2.3", "fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0", "netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-data": "^1.4.0+bedrock-1.17.40", "pocketmine/bedrock-data": "^1.4.0+bedrock-1.17.40",
"pocketmine/bedrock-protocol": "^5.0.0+bedrock-1.17.40", "pocketmine/bedrock-protocol": "^6.0.0+bedrock-1.17.40",
"pocketmine/binaryutils": "^0.2.1", "pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2", "pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0", "pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0", "pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0", "pocketmine/errorhandler": "^0.3.0",
"pocketmine/locale-data": "^1.0.3", "pocketmine/locale-data": "^2.0.16",
"pocketmine/log": "^0.4.0", "pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0", "pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0", "pocketmine/math": "^0.4.0",
@ -53,7 +53,7 @@
"webmozart/path-util": "^2.3" "webmozart/path-util": "^2.3"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "1.0.2", "phpstan/phpstan": "1.2.0",
"phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0", "phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.2" "phpunit/phpunit": "^9.2"
@ -79,7 +79,7 @@
"sort-packages": true "sort-packages": true
}, },
"scripts": { "scripts": {
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar", "make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
"make-server": [ "make-server": [
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs", "@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
"@php -dphar.readonly=0 build/server-phar.php" "@php -dphar.readonly=0 build/server-phar.php"

83
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "e36db7fb94bd79034dcc599c3029a621", "content-hash": "fb545e4c8e17b0b07e8e20986b64e5a6",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@ -275,16 +275,16 @@
}, },
{ {
"name": "pocketmine/bedrock-protocol", "name": "pocketmine/bedrock-protocol",
"version": "5.0.0+bedrock-1.17.40", "version": "6.0.0+bedrock-1.17.40",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git", "url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab" "reference": "906bafec4fc41f548749ce01d120902b25c1bbfe"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/67c0c15b4044cab2190501933912c3d02c5f63ab", "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/906bafec4fc41f548749ce01d120902b25c1bbfe",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab", "reference": "906bafec4fc41f548749ce01d120902b25c1bbfe",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -298,7 +298,7 @@
"ramsey/uuid": "^4.1" "ramsey/uuid": "^4.1"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "1.0.0", "phpstan/phpstan": "1.2.0",
"phpstan/phpstan-phpunit": "^1.0.0", "phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0", "phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5" "phpunit/phpunit": "^9.5"
@ -316,9 +316,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues", "issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/5.0.0+bedrock-1.17.40" "source": "https://github.com/pmmp/BedrockProtocol/tree/6.0.0+bedrock-1.17.40"
}, },
"time": "2021-11-02T01:27:05+00:00" "time": "2021-11-21T20:56:18+00:00"
}, },
{ {
"name": "pocketmine/binaryutils", "name": "pocketmine/binaryutils",
@ -533,16 +533,16 @@
}, },
{ {
"name": "pocketmine/locale-data", "name": "pocketmine/locale-data",
"version": "1.0.3", "version": "2.0.20",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/Language.git", "url": "https://github.com/pmmp/Language.git",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19" "reference": "a6e4eb22587e0014f6d658732cf633262723f8d3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/7342b4eb593036c739e7f0c0ed95299ada69ff19", "url": "https://api.github.com/repos/pmmp/Language/zipball/a6e4eb22587e0014f6d658732cf633262723f8d3",
"reference": "7342b4eb593036c739e7f0c0ed95299ada69ff19", "reference": "a6e4eb22587e0014f6d658732cf633262723f8d3",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -550,9 +550,9 @@
"description": "Language resources used by PocketMine-MP", "description": "Language resources used by PocketMine-MP",
"support": { "support": {
"issues": "https://github.com/pmmp/Language/issues", "issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/1.0.3" "source": "https://github.com/pmmp/Language/tree/2.0.20"
}, },
"time": "2021-11-06T00:27:03+00:00" "time": "2021-11-25T20:56:12+00:00"
}, },
{ {
"name": "pocketmine/log", "name": "pocketmine/log",
@ -1370,6 +1370,7 @@
"issues": "https://github.com/webmozart/path-util/issues", "issues": "https://github.com/webmozart/path-util/issues",
"source": "https://github.com/webmozart/path-util/tree/2.3.0" "source": "https://github.com/webmozart/path-util/tree/2.3.0"
}, },
"abandoned": "symfony/filesystem",
"time": "2015-12-17T08:42:14+00:00" "time": "2015-12-17T08:42:14+00:00"
} }
], ],
@ -1897,16 +1898,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.0.2", "version": "1.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91" "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e9e2a501102ba0b126b2f63a7f0a3b151056fe91", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
"reference": "e9e2a501102ba0b126b2f63a7f0a3b151056fe91", "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1922,7 +1923,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0-dev" "dev-master": "1.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -1937,7 +1938,7 @@
"description": "PHPStan - PHP Static Analysis Tool", "description": "PHPStan - PHP Static Analysis Tool",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan/issues", "issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.0.2" "source": "https://github.com/phpstan/phpstan/tree/1.2.0"
}, },
"funding": [ "funding": [
{ {
@ -1957,7 +1958,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-03T16:09:51+00:00" "time": "2021-11-18T14:09:01+00:00"
}, },
{ {
"name": "phpstan/phpstan-phpunit", "name": "phpstan/phpstan-phpunit",
@ -2016,21 +2017,21 @@
}, },
{ {
"name": "phpstan/phpstan-strict-rules", "name": "phpstan/phpstan-strict-rules",
"version": "1.0.0", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git", "url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940" "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7f50eb112f37fda2ef956813d3f1e9b1e69d7940", "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717",
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940", "reference": "e12d55f74a8cca18c6e684c6450767e055ba7717",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.1 || ^8.0", "php": "^7.1 || ^8.0",
"phpstan/phpstan": "^1.0" "phpstan/phpstan": "^1.2.0"
}, },
"require-dev": { "require-dev": {
"nikic/php-parser": "^4.13.0", "nikic/php-parser": "^4.13.0",
@ -2061,22 +2062,22 @@
"description": "Extra strict and opinionated rules for PHPStan", "description": "Extra strict and opinionated rules for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.0.0" "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.1.0"
}, },
"time": "2021-10-11T06:57:58+00:00" "time": "2021-11-18T09:30:29+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.8", "version": "9.2.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e" "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2132,7 +2133,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9"
}, },
"funding": [ "funding": [
{ {
@ -2140,7 +2141,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-10-30T08:01:38+00:00" "time": "2021-11-19T15:21:02+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -2915,16 +2916,16 @@
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
"version": "4.0.3", "version": "4.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git", "url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2973,14 +2974,14 @@
} }
], ],
"description": "Provides the functionality to export PHP variables for visualization", "description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter", "homepage": "https://www.github.com/sebastianbergmann/exporter",
"keywords": [ "keywords": [
"export", "export",
"exporter" "exporter"
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues", "issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
}, },
"funding": [ "funding": [
{ {
@ -2988,7 +2989,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-09-28T05:24:23+00:00" "time": "2021-11-11T14:18:36+00:00"
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",

View File

@ -4,7 +4,6 @@ includes:
- tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/impossible-generics.neon
- tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/php-bugs.neon
- tests/phpstan/configs/phpstan-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/pthreads-bugs.neon
- tests/phpstan/configs/runtime-type-checks.neon - tests/phpstan/configs/runtime-type-checks.neon
- tests/phpstan/configs/spl-fixed-array-sucks.neon - tests/phpstan/configs/spl-fixed-array-sucks.neon
- vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/extension.neon
@ -13,6 +12,7 @@ includes:
rules: rules:
- pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
parameters: parameters:

View File

@ -108,7 +108,7 @@ player:
verify-xuid: true verify-xuid: true
level-settings: level-settings:
#The default format that levels will use when created #The default format that worlds will use when created
default-format: leveldb default-format: leveldb
chunk-sending: chunk-sending:
@ -176,7 +176,7 @@ aliases:
#savestop: [save-all, stop] #savestop: [save-all, stop]
worlds: worlds:
#These settings will override the generator set in server.properties and allows loading multiple levels #These settings will override the generator set in server.properties and allows loading multiple worlds
#Example: #Example:
#world: #world:
# seed: 404 # seed: 404

View File

@ -352,35 +352,33 @@ class MemoryManager{
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("Wrote $staticCount static properties"); $logger->info("Wrote $staticCount static properties");
if(isset($GLOBALS)){ //This might be null if we're on a different thread $globalVariables = [];
$globalVariables = []; $globalCount = 0;
$globalCount = 0;
$ignoredGlobals = [ $ignoredGlobals = [
'GLOBALS' => true, 'GLOBALS' => true,
'_SERVER' => true, '_SERVER' => true,
'_REQUEST' => true, '_REQUEST' => true,
'_POST' => true, '_POST' => true,
'_GET' => true, '_GET' => true,
'_FILES' => true, '_FILES' => true,
'_ENV' => true, '_ENV' => true,
'_COOKIE' => true, '_COOKIE' => true,
'_SESSION' => true '_SESSION' => true
]; ];
foreach($GLOBALS as $varName => $value){ foreach(Utils::stringifyKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){ if(isset($ignoredGlobals[$varName])){
continue; continue;
}
$globalCount++;
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
} }
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); $globalCount++;
$logger->info("Wrote $globalCount global variables"); $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
} }
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("Wrote $globalCount global variables");
foreach(get_defined_functions()["user"] as $function){ foreach(get_defined_functions()["user"] as $function){
$reflect = new \ReflectionFunction($function); $reflect = new \ReflectionFunction($function);

View File

@ -34,6 +34,7 @@ namespace pocketmine {
use pocketmine\utils\Timezone; use pocketmine\utils\Timezone;
use pocketmine\wizard\SetupWizard; use pocketmine\wizard\SetupWizard;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function defined;
use function extension_loaded; use function extension_loaded;
use function phpversion; use function phpversion;
use function preg_match; use function preg_match;
@ -145,6 +146,10 @@ namespace pocketmine {
$messages[] = "The native PocketMine extension is no longer supported."; $messages[] = "The native PocketMine extension is no longer supported.";
} }
if(!defined('AF_INET6')){
$messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support.";
}
return $messages; return $messages;
} }

View File

@ -35,6 +35,7 @@ use pocketmine\console\ConsoleReaderThread;
use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crafting\CraftingManagerFromDataHelper;
use pocketmine\crash\CrashDump; use pocketmine\crash\CrashDump;
use pocketmine\crash\CrashDumpRenderer;
use pocketmine\data\java\GameModeIdMap; use pocketmine\data\java\GameModeIdMap;
use pocketmine\entity\EntityDataHelper; use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\Location; use pocketmine\entity\Location;
@ -121,13 +122,18 @@ use function base64_encode;
use function cli_set_process_title; use function cli_set_process_title;
use function copy; use function copy;
use function count; use function count;
use function date;
use function fclose;
use function file_exists; use function file_exists;
use function file_get_contents; use function file_get_contents;
use function file_put_contents; use function file_put_contents;
use function filemtime; use function filemtime;
use function fopen;
use function get_class; use function get_class;
use function ini_set; use function ini_set;
use function is_array; use function is_array;
use function is_dir;
use function is_resource;
use function is_string; use function is_string;
use function json_decode; use function json_decode;
use function max; use function max;
@ -321,6 +327,10 @@ class Server{
return $this->configGroup->getConfigInt("server-port", 19132); return $this->configGroup->getConfigInt("server-port", 19132);
} }
public function getPortV6() : int{
return $this->configGroup->getConfigInt("server-portv6", 19133);
}
public function getViewDistance() : int{ public function getViewDistance() : int{
return max(2, $this->configGroup->getConfigInt("view-distance", 8)); return max(2, $this->configGroup->getConfigInt("view-distance", 8));
} }
@ -337,6 +347,11 @@ class Server{
return $str !== "" ? $str : "0.0.0.0"; return $str !== "" ? $str : "0.0.0.0";
} }
public function getIpV6() : string{
$str = $this->configGroup->getConfigString("server-ipv6");
return $str !== "" ? $str : "::";
}
public function getServerUniqueId() : UuidInterface{ public function getServerUniqueId() : UuidInterface{
return $this->serverID; return $this->serverID;
} }
@ -784,6 +799,8 @@ class Server{
new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [ new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [
"motd" => VersionInfo::NAME . " Server", "motd" => VersionInfo::NAME . " Server",
"server-port" => 19132, "server-port" => 19132,
"server-portv6" => 19133,
"enable-ipv6" => true,
"white-list" => false, "white-list" => false,
"max-players" => 20, "max-players" => 20,
"gamemode" => 0, "gamemode" => 0,
@ -1114,25 +1131,42 @@ class Server{
return true; return true;
} }
private function startupPrepareNetworkInterfaces() : bool{ private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery) : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true); $prettyIp = $ipV6 ? "[$ip]" : $ip;
try{ try{
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this)); $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6));
}catch(NetworkInterfaceStartException $e){ }catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$this->getIp(), $ip,
(string) $this->getPort(), (string) $port,
$e->getMessage() $e->getMessage()
))); )));
return false; return false;
} }
if(!$rakLibRegistered && $useQuery){ $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
//RakLib would normally handle the transport for Query packets if($useQuery){
//if it's not registered we need to make sure Query still works if(!$rakLibRegistered){
$this->network->registerInterface(new DedicatedQueryNetworkInterface($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface"))); //RakLib would normally handle the transport for Query packets
//if it's not registered we need to make sure Query still works
$this->network->registerInterface(new DedicatedQueryNetworkInterface($ip, $port, $ipV6, new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
}
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($prettyIp, (string) $port)));
}
return true;
}
private function startupPrepareNetworkInterfaces() : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
if(
!$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery) ||
(
$this->configGroup->getConfigBool("enable-ipv6", true) &&
!$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery)
)
){
return false;
} }
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($this->getIp(), (string) $this->getPort())));
if($useQuery){ if($useQuery){
$this->network->registerRawPacketHandler(new QueryHandler($this)); $this->network->registerRawPacketHandler(new QueryHandler($this));
@ -1173,7 +1207,7 @@ class Server{
* Unsubscribes from all broadcast channels. * Unsubscribes from all broadcast channels.
*/ */
public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{ public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{
foreach($this->broadcastSubscribers as $channelId => $recipients){ foreach(Utils::stringifyKeys($this->broadcastSubscribers) as $channelId => $recipients){
$this->unsubscribeFromBroadcastChannel($channelId, $subscriber); $this->unsubscribeFromBroadcastChannel($channelId, $subscriber);
} }
} }
@ -1379,6 +1413,9 @@ class Server{
echo "\x1b]0;\x07"; echo "\x1b]0;\x07";
} }
if($this->isRunning){
$this->logger->emergency("Forcing server shutdown");
}
try{ try{
if(!$this->isRunning()){ if(!$this->isRunning()){
$this->sendUsage(SendUsageTask::TYPE_CLOSE); $this->sendUsage(SendUsageTask::TYPE_CLOSE);
@ -1477,6 +1514,25 @@ class Server{
$this->crashDump(); $this->crashDump();
} }
private function writeCrashDumpFile(CrashDump $dump) : string{
$crashFolder = Path::join($this->getDataPath(), "crashdumps");
if(!is_dir($crashFolder)){
mkdir($crashFolder);
}
$crashDumpPath = Path::join($crashFolder, date("D_M_j-H.i.s-T_Y", (int) $dump->getData()->time) . ".log");
$fp = @fopen($crashDumpPath, "wb");
if(!is_resource($fp)){
throw new \RuntimeException("Unable to open new file to generate crashdump");
}
$writer = new CrashDumpRenderer($fp, $dump->getData());
$writer->renderHumanReadable();
$dump->encodeData($writer);
fclose($fp);
return $crashDumpPath;
}
public function crashDump() : void{ public function crashDump() : void{
while(@ob_end_flush()){} while(@ob_end_flush()){}
if(!$this->isRunning){ if(!$this->isRunning){
@ -1493,7 +1549,9 @@ class Server{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create())); $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this, $this->pluginManager ?? null); $dump = new CrashDump($this, $this->pluginManager ?? null);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($dump->getPath()))); $crashDumpPath = $this->writeCrashDumpFile($dump);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
if($this->configGroup->getPropertyBool("auto-report.enabled", true)){ if($this->configGroup->getPropertyBool("auto-report.enabled", true)){
$report = true; $report = true;

View File

@ -25,13 +25,14 @@ namespace pocketmine;
use pocketmine\utils\Git; use pocketmine\utils\Git;
use pocketmine\utils\VersionString; use pocketmine\utils\VersionString;
use function is_array;
use function is_int;
use function str_repeat; use function str_repeat;
final class VersionInfo{ final class VersionInfo{
public const NAME = "PocketMine-MP"; public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA11"; public const BASE_VERSION = "4.0.0-BETA14";
public const IS_DEVELOPMENT_BUILD = false; public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_NUMBER = 0;
public const BUILD_CHANNEL = "beta"; public const BUILD_CHANNEL = "beta";
private function __construct(){ private function __construct(){
@ -61,12 +62,29 @@ final class VersionInfo{
return self::$gitHash; return self::$gitHash;
} }
private static ?int $buildNumber = null;
public static function BUILD_NUMBER() : int{
if(self::$buildNumber === null){
self::$buildNumber = 0;
if(\Phar::running(true) !== ""){
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(is_array($meta) && isset($meta["build"]) && is_int($meta["build"])){
self::$buildNumber = $meta["build"];
}
}
}
return self::$buildNumber;
}
/** @var VersionString|null */ /** @var VersionString|null */
private static $fullVersion = null; private static $fullVersion = null;
public static function VERSION() : VersionString{ public static function VERSION() : VersionString{
if(self::$fullVersion === null){ if(self::$fullVersion === null){
self::$fullVersion = new VersionString(self::BASE_VERSION, self::IS_DEVELOPMENT_BUILD, self::BUILD_NUMBER); self::$fullVersion = new VersionString(self::BASE_VERSION, self::IS_DEVELOPMENT_BUILD, self::BUILD_NUMBER());
} }
return self::$fullVersion; return self::$fullVersion;
} }

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\crafting\CraftingGrid; use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
@ -32,7 +32,7 @@ class CraftingTable extends Opaque{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){ if($player instanceof Player){
$player->setCraftingGrid(new CraftingGrid($player, CraftingGrid::SIZE_BIG)); $player->setCurrentWindow(new CraftingTableInventory($this->position));
} }
return true; return true;

View File

@ -23,6 +23,13 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\item\Item;
class Podzol extends Opaque{ class Podzol extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaBlocks::DIRT()->asItem()
];
}
} }

View File

@ -124,8 +124,14 @@ class Skull extends Flowable{
* @return AxisAlignedBB[] * @return AxisAlignedBB[]
*/ */
protected function recalculateCollisionBoxes() : array{ protected function recalculateCollisionBoxes() : array{
//TODO: different bounds depending on attached face $collisionBox = AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5);
return [AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5)]; return match($this->facing){
Facing::NORTH => [$collisionBox->offset(0, 0.25, 0.25)],
Facing::SOUTH => [$collisionBox->offset(0, 0.25, -0.25)],
Facing::WEST => [$collisionBox->offset(0.25, 0.25, 0)],
Facing::EAST => [$collisionBox->offset(-0.25, 0.25, 0)],
default => [$collisionBox]
};
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\utils\CloningRegistryTrait; use pocketmine\utils\CloningRegistryTrait;
use function assert;
/** /**
* This doc-block is generated automatically, do not modify it manually. * This doc-block is generated automatically, do not modify it manually.
@ -581,12 +580,6 @@ final class VanillaBlocks{
self::_registryRegister($name, $block); self::_registryRegister($name, $block);
} }
public static function fromString(string $name) : Block{
$result = self::_registryFromString($name);
assert($result instanceof Block);
return $result;
}
/** /**
* @return Block[] * @return Block[]
*/ */

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace pocketmine\block\inventory; namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player; use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position; use pocketmine\world\Position;
class AnvilInventory extends SimpleInventory implements BlockInventory{ class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait; use BlockInventoryTrait;
public const SLOT_INPUT = 0; public const SLOT_INPUT = 0;
@ -37,13 +37,4 @@ class AnvilInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder; $this->holder = $holder;
parent::__construct(2); parent::__construct(2);
} }
public function onClose(Player $who) : void{
parent::onClose($who);
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
} }

View File

@ -0,0 +1,37 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
final class CraftingTableInventory extends CraftingGrid implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public function __construct(Position $holder){
$this->holder = $holder;
parent::__construct(CraftingGrid::SIZE_BIG);
}
}

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace pocketmine\block\inventory; namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player; use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position; use pocketmine\world\Position;
class EnchantInventory extends SimpleInventory implements BlockInventory{ class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait; use BlockInventoryTrait;
public const SLOT_INPUT = 0; public const SLOT_INPUT = 0;
@ -37,13 +37,4 @@ class EnchantInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder; $this->holder = $holder;
parent::__construct(2); parent::__construct(2);
} }
public function onClose(Player $who) : void{
parent::onClose($who);
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
} }

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace pocketmine\block\inventory; namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player; use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position; use pocketmine\world\Position;
final class LoomInventory extends SimpleInventory implements BlockInventory{ final class LoomInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait; use BlockInventoryTrait;
public const SLOT_BANNER = 0; public const SLOT_BANNER = 0;
@ -38,13 +38,4 @@ final class LoomInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder; $this->holder = $holder;
parent::__construct($size); parent::__construct($size);
} }
public function onClose(Player $who) : void{
parent::onClose($who);
foreach($this->getContents() as $item){
$who->dropItem($item);
}
$this->clearAll();
}
} }

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block\tile; namespace pocketmine\block\tile;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException; use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
@ -112,21 +113,25 @@ final class TileFactory{
/** /**
* @internal * @internal
* @throws NbtDataException * @throws SavedDataLoadingException
*/ */
public function createFromData(World $world, CompoundTag $nbt) : ?Tile{ public function createFromData(World $world, CompoundTag $nbt) : ?Tile{
$type = $nbt->getString(Tile::TAG_ID, ""); try{
if(!isset($this->knownTiles[$type])){ $type = $nbt->getString(Tile::TAG_ID, "");
return null; if(!isset($this->knownTiles[$type])){
return null;
}
$class = $this->knownTiles[$type];
assert(is_a($class, Tile::class, true));
/**
* @var Tile $tile
* @see Tile::__construct()
*/
$tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z)));
$tile->readSaveData($nbt);
}catch(NbtException $e){
throw new SavedDataLoadingException($e->getMessage(), 0, $e);
} }
$class = $this->knownTiles[$type];
assert(is_a($class, Tile::class, true));
/**
* @var Tile $tile
* @see Tile::__construct()
*/
$tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z)));
$tile->readSaveData($nbt);
return $tile; return $tile;
} }

View File

@ -32,7 +32,7 @@ use pocketmine\player\Player;
use function array_shift; use function array_shift;
use function count; use function count;
use function implode; use function implode;
use function preg_match; use function inet_pton;
class BanIpCommand extends VanillaCommand{ class BanIpCommand extends VanillaCommand{
@ -57,7 +57,7 @@ class BanIpCommand extends VanillaCommand{
$value = array_shift($args); $value = array_shift($args);
$reason = implode(" ", $args); $reason = implode(" ", $args);
if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $value)){ if(inet_pton($value) !== false){
$this->processIPBan($value, $sender, $reason); $this->processIPBan($value, $sender, $reason);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success($value)); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success($value));

View File

@ -26,7 +26,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender; use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\effect\StringToEffectParser;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\Limits; use pocketmine\utils\Limits;
@ -69,9 +69,8 @@ class EffectCommand extends VanillaCommand{
return true; return true;
} }
try{ $effect = StringToEffectParser::getInstance()->parse($args[1]);
$effect = VanillaEffects::fromString($args[1]); if($effect === null){
}catch(\InvalidArgumentException $e){
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED)); $sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED));
return true; return true;
} }

View File

@ -26,7 +26,7 @@ namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender; use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\StringToEnchantmentParser;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
@ -66,9 +66,8 @@ class EnchantCommand extends VanillaCommand{
return true; return true;
} }
try{ $enchantment = StringToEnchantmentParser::getInstance()->parse($args[1]);
$enchantment = VanillaEnchantments::fromString($args[1]); if($enchantment === null){
}catch(\InvalidArgumentException $e){
$sender->sendMessage(KnownTranslationFactory::commands_enchant_notFound($args[1])); $sender->sendMessage(KnownTranslationFactory::commands_enchant_notFound($args[1]));
return true; return true;
} }

View File

@ -29,7 +29,7 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use function count; use function count;
use function preg_match; use function inet_pton;
class PardonIpCommand extends VanillaCommand{ class PardonIpCommand extends VanillaCommand{
@ -52,7 +52,7 @@ class PardonIpCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException(); throw new InvalidCommandSyntaxException();
} }
if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $args[0])){ if(inet_pton($args[0]) !== false){
$sender->getServer()->getIPBans()->remove($args[0]); $sender->getServer()->getIPBans()->remove($args[0]);
$sender->getServer()->getNetwork()->unblockAddress($args[0]); $sender->getServer()->getNetwork()->unblockAddress($args[0]);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unbanip_success($args[0])); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unbanip_success($args[0]));

View File

@ -31,8 +31,8 @@ use pocketmine\math\Vector3;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
use pocketmine\world\World;
use function count; use function count;
use function round;
class SetWorldSpawnCommand extends VanillaCommand{ class SetWorldSpawnCommand extends VanillaCommand{
@ -54,22 +54,32 @@ class SetWorldSpawnCommand extends VanillaCommand{
if($sender instanceof Player){ if($sender instanceof Player){
$location = $sender->getPosition(); $location = $sender->getPosition();
$world = $location->getWorld(); $world = $location->getWorld();
$pos = $location->asVector3()->round(); $pos = $location->asVector3()->floor();
}else{ }else{
$sender->sendMessage(TextFormat::RED . "You can only perform this command as a player"); $sender->sendMessage(TextFormat::RED . "You can only perform this command as a player");
return true; return true;
} }
}elseif(count($args) === 3){ }elseif(count($args) === 3){
$world = $sender->getServer()->getWorldManager()->getDefaultWorld(); if($sender instanceof Player){
$pos = new Vector3($this->getInteger($sender, $args[0]), $this->getInteger($sender, $args[1]), $this->getInteger($sender, $args[2])); $base = $sender->getPosition();
$world = $base->getWorld();
}else{
$base = new Vector3(0.0, 0.0, 0.0);
$world = $sender->getServer()->getWorldManager()->getDefaultWorld();
}
$pos = (new Vector3(
$this->getRelativeDouble($base->x, $sender, $args[0]),
$this->getRelativeDouble($base->y, $sender, $args[1], World::Y_MIN, World::Y_MAX),
$this->getRelativeDouble($base->z, $sender, $args[2]),
))->floor();
}else{ }else{
throw new InvalidCommandSyntaxException(); throw new InvalidCommandSyntaxException();
} }
$world->setSpawnLocation($pos); $world->setSpawnLocation($pos);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_setworldspawn_success((string) round($pos->x, 2), (string) round($pos->y, 2), (string) round($pos->z, 2))); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_setworldspawn_success((string) $pos->x, (string) $pos->y, (string) $pos->z));
return true; return true;
} }

View File

@ -32,6 +32,7 @@ use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
use pocketmine\world\World;
use function array_shift; use function array_shift;
use function count; use function count;
use function round; use function round;
@ -111,7 +112,7 @@ class TeleportCommand extends VanillaCommand{
} }
$x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]); $x = $this->getRelativeDouble($base->x, $sender, $targetArgs[0]);
$y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], 0, 256); $y = $this->getRelativeDouble($base->y, $sender, $targetArgs[1], World::Y_MIN, World::Y_MAX);
$z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]); $z = $this->getRelativeDouble($base->z, $sender, $targetArgs[2]);
$targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch); $targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\console; namespace pocketmine\console;
use pocketmine\utils\AssumptionFailedError;
use function fclose; use function fclose;
use function fgets; use function fgets;
use function fopen; use function fopen;
@ -44,7 +45,9 @@ final class ConsoleReader{
fclose($this->stdin); fclose($this->stdin);
} }
$this->stdin = fopen("php://stdin", "r"); $stdin = fopen("php://stdin", "r");
if($stdin === false) throw new AssumptionFailedError("Opening stdin should never fail");
$this->stdin = $stdin;
} }
/** /**
@ -60,11 +63,10 @@ final class ConsoleReader{
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
return null; return null;
}elseif($count === false){ //stream error }elseif($count === false){ //stream error
$this->initStdin(); return null;
} }
if(($raw = fgets($this->stdin)) === false){ //broken pipe or EOF if(($raw = fgets($this->stdin)) === false){ //broken pipe or EOF
$this->initStdin();
usleep(200000); //prevent CPU waste if it's end of pipe usleep(200000); //prevent CPU waste if it's end of pipe
return null; //loop back round return null; //loop back round
} }

View File

@ -46,7 +46,10 @@ if($socket === false){
$consoleReader = new ConsoleReader(); $consoleReader = new ConsoleReader();
while(!feof($socket)){ while(!feof($socket)){
$line = $consoleReader->readLine(); $line = $consoleReader->readLine();
if($line !== null){ if(@fwrite($socket, ($line ?? "") . "\n") === false){
fwrite($socket, $line . "\n"); //Always send even if there's no line, to check if the parent is alive
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
//false even though the connection is actually broken. However, fwrite() will fail.
break;
} }
} }

View File

@ -31,6 +31,7 @@ use function base64_encode;
use function fgets; use function fgets;
use function fopen; use function fopen;
use function preg_replace; use function preg_replace;
use function proc_close;
use function proc_open; use function proc_open;
use function proc_terminate; use function proc_terminate;
use function sprintf; use function sprintf;
@ -116,6 +117,9 @@ final class ConsoleReaderThread extends Thread{
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); $command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); $command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
if($command === ""){
continue;
}
$buffer[] = $command; $buffer[] = $command;
if($notifier !== null){ if($notifier !== null){
$notifier->wakeupSleeper(); $notifier->wakeupSleeper();
@ -127,6 +131,7 @@ final class ConsoleReaderThread extends Thread{
//gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in //gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in
//the first place). //the first place).
proc_terminate($sub); proc_terminate($sub);
proc_close($sub);
stream_socket_shutdown($client, STREAM_SHUT_RDWR); stream_socket_shutdown($client, STREAM_SHUT_RDWR);
} }

View File

@ -25,17 +25,14 @@ namespace pocketmine\crafting;
use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\player\Player;
use function max; use function max;
use function min; use function min;
use const PHP_INT_MAX; use const PHP_INT_MAX;
class CraftingGrid extends SimpleInventory{ abstract class CraftingGrid extends SimpleInventory{
public const SIZE_SMALL = 2; public const SIZE_SMALL = 2;
public const SIZE_BIG = 3; public const SIZE_BIG = 3;
/** @var Player */
protected $holder;
/** @var int */ /** @var int */
private $gridWidth; private $gridWidth;
@ -48,8 +45,7 @@ class CraftingGrid extends SimpleInventory{
/** @var int|null */ /** @var int|null */
private $yLen; private $yLen;
public function __construct(Player $holder, int $gridWidth){ public function __construct(int $gridWidth){
$this->holder = $holder;
$this->gridWidth = $gridWidth; $this->gridWidth = $gridWidth;
parent::__construct($this->getGridWidth() ** 2); parent::__construct($this->getGridWidth() ** 2);
} }
@ -63,13 +59,6 @@ class CraftingGrid extends SimpleInventory{
$this->seekRecipeBounds(); $this->seekRecipeBounds();
} }
/**
* @return Player
*/
public function getHolder(){
return $this->holder;
}
private function seekRecipeBounds() : void{ private function seekRecipeBounds() : void{
$minX = PHP_INT_MAX; $minX = PHP_INT_MAX;
$maxX = 0; $maxX = 0;

View File

@ -35,25 +35,17 @@ use pocketmine\utils\Utils;
use pocketmine\VersionInfo; use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function base64_encode; use function base64_encode;
use function date;
use function error_get_last; use function error_get_last;
use function fclose;
use function file; use function file;
use function file_exists; use function file_exists;
use function file_get_contents; use function file_get_contents;
use function fopen;
use function fwrite;
use function get_loaded_extensions; use function get_loaded_extensions;
use function implode;
use function is_dir;
use function is_resource;
use function json_encode; use function json_encode;
use function json_last_error_msg; use function json_last_error_msg;
use function ksort; use function ksort;
use function max; use function max;
use function mb_strtoupper; use function mb_strtoupper;
use function microtime; use function microtime;
use function mkdir;
use function ob_end_clean; use function ob_end_clean;
use function ob_get_contents; use function ob_get_contents;
use function ob_start; use function ob_start;
@ -69,10 +61,10 @@ use function zend_version;
use function zlib_encode; use function zlib_encode;
use const FILE_IGNORE_NEW_LINES; use const FILE_IGNORE_NEW_LINES;
use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL;
use const PHP_OS; use const PHP_OS;
use const PHP_VERSION; use const PHP_VERSION;
use const SORT_STRING; use const SORT_STRING;
use const ZLIB_ENCODING_DEFLATE;
class CrashDump{ class CrashDump{
@ -84,58 +76,41 @@ class CrashDump{
*/ */
private const FORMAT_VERSION = 4; private const FORMAT_VERSION = 4;
private const PLUGIN_INVOLVEMENT_NONE = "none"; public const PLUGIN_INVOLVEMENT_NONE = "none";
private const PLUGIN_INVOLVEMENT_DIRECT = "direct"; public const PLUGIN_INVOLVEMENT_DIRECT = "direct";
private const PLUGIN_INVOLVEMENT_INDIRECT = "indirect"; public const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
/** @var Server */ /** @var Server */
private $server; private $server;
/** @var resource */
private $fp;
/** @var float */
private $time;
private CrashDumpData $data; private CrashDumpData $data;
/** @var string */ /** @var string */
private $encodedData; private $encodedData;
/** @var string */
private $path;
private ?PluginManager $pluginManager; private ?PluginManager $pluginManager;
public function __construct(Server $server, ?PluginManager $pluginManager){ public function __construct(Server $server, ?PluginManager $pluginManager){
$this->time = microtime(true); $now = microtime(true);
$this->server = $server; $this->server = $server;
$this->pluginManager = $pluginManager; $this->pluginManager = $pluginManager;
$crashPath = Path::join($this->server->getDataPath(), "crashdumps");
if(!is_dir($crashPath)){
mkdir($crashPath);
}
$this->path = Path::join($crashPath, date("D_M_j-H.i.s-T_Y", (int) $this->time) . ".log");
$fp = @fopen($this->path, "wb");
if(!is_resource($fp)){
throw new \RuntimeException("Could not create Crash Dump");
}
$this->fp = $fp;
$this->data = new CrashDumpData(); $this->data = new CrashDumpData();
$this->data->format_version = self::FORMAT_VERSION; $this->data->format_version = self::FORMAT_VERSION;
$this->data->time = $this->time; $this->data->time = $now;
$this->data->uptime = $this->time - $this->server->getStartTime(); $this->data->uptime = $now - $this->server->getStartTime();
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time));
$this->addLine();
$this->baseCrash(); $this->baseCrash();
$this->generalData(); $this->generalData();
$this->pluginsData(); $this->pluginsData();
$this->extraData(); $this->extraData();
$this->encodeData(); $json = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if($json === false){
fclose($this->fp); throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
} }
$zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
public function getPath() : string{ if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed");
return $this->path; $this->encodedData = $zlibEncoded;
} }
public function getEncodedData() : string{ public function getEncodedData() : string{
@ -146,29 +121,20 @@ class CrashDump{
return $this->data; return $this->data;
} }
private function encodeData() : void{ public function encodeData(CrashDumpRenderer $renderer) : void{
$this->addLine(); $renderer->addLine();
$this->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------"); $renderer->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
$this->addLine(); $renderer->addLine();
$this->addLine("===BEGIN CRASH DUMP==="); $renderer->addLine("===BEGIN CRASH DUMP===");
$json = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if($json === false){
throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
}
$zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed");
$this->encodedData = $zlibEncoded;
foreach(str_split(base64_encode($this->encodedData), 76) as $line){ foreach(str_split(base64_encode($this->encodedData), 76) as $line){
$this->addLine($line); $renderer->addLine($line);
} }
$this->addLine("===END CRASH DUMP==="); $renderer->addLine("===END CRASH DUMP===");
} }
private function pluginsData() : void{ private function pluginsData() : void{
if($this->pluginManager !== null){ if($this->pluginManager !== null){
$plugins = $this->pluginManager->getPlugins(); $plugins = $this->pluginManager->getPlugins();
$this->addLine();
$this->addLine("Loaded plugins:");
ksort($plugins, SORT_STRING); ksort($plugins, SORT_STRING);
foreach($plugins as $p){ foreach($plugins as $p){
$d = $p->getDescription(); $d = $p->getDescription();
@ -184,7 +150,6 @@ class CrashDump{
load: mb_strtoupper($d->getOrder()->name()), load: mb_strtoupper($d->getOrder()->name()),
website: $d->getWebsite() website: $d->getWebsite()
); );
$this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis()));
} }
} }
} }
@ -250,10 +215,6 @@ class CrashDump{
$this->data->error = $error; $this->data->error = $error;
unset($this->data->error["fullFile"]); unset($this->data->error["fullFile"]);
unset($this->data->error["trace"]); unset($this->data->error["trace"]);
$this->addLine("Error: " . $error["message"]);
$this->addLine("File: " . $error["file"]);
$this->addLine("Line: " . $error["line"]);
$this->addLine("Type: " . $error["type"]);
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE;
if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace
@ -267,36 +228,24 @@ class CrashDump{
} }
} }
$this->addLine();
$this->addLine("Code:");
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){ if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES); $file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){ if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){ 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]; $this->data->code[$l + 1] = $file[$l];
} }
} }
} }
$this->addLine(); $this->data->trace = Utils::printableTrace($error["trace"]);
$this->addLine("Backtrace:");
foreach(($this->data->trace = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line);
}
$this->addLine();
} }
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
$frameCleanPath = Filesystem::cleanPath($filePath); $frameCleanPath = Filesystem::cleanPath($filePath);
if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){ if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){
$this->addLine();
if($crashFrame){ if($crashFrame){
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
}else{ }else{
$this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH");
$this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT;
} }
@ -308,7 +257,6 @@ class CrashDump{
$filePath = Filesystem::cleanPath($file->getValue($plugin)); $filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){ if(strpos($frameCleanPath, $filePath) === 0){
$this->data->plugin = $plugin->getName(); $this->data->plugin = $plugin->getName();
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
break; break;
} }
} }
@ -319,7 +267,6 @@ class CrashDump{
} }
private function generalData() : void{ private function generalData() : void{
$version = VersionInfo::VERSION();
$composerLibraries = []; $composerLibraries = [];
foreach(InstalledVersions::getInstalledPackages() as $package){ foreach(InstalledVersions::getInstalledPackages() as $package){
$composerLibraries[$package] = sprintf( $composerLibraries[$package] = sprintf(
@ -332,7 +279,7 @@ class CrashDump{
$this->data->general = new CrashDumpDataGeneral( $this->data->general = new CrashDumpDataGeneral(
name: $this->server->getName(), name: $this->server->getName(),
base_version: VersionInfo::BASE_VERSION, base_version: VersionInfo::BASE_VERSION,
build: VersionInfo::BUILD_NUMBER, build: VersionInfo::BUILD_NUMBER(),
is_dev: VersionInfo::IS_DEVELOPMENT_BUILD, is_dev: VersionInfo::IS_DEVELOPMENT_BUILD,
protocol: ProtocolInfo::CURRENT_PROTOCOL, protocol: ProtocolInfo::CURRENT_PROTOCOL,
git: VersionInfo::GIT_HASH(), git: VersionInfo::GIT_HASH(),
@ -343,29 +290,5 @@ class CrashDump{
os: Utils::getOS(), os: Utils::getOS(),
composer_libraries: $composerLibraries, composer_libraries: $composerLibraries,
); );
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . VersionInfo::GIT_HASH());
$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");
}
}
/**
* @param string $line
*/
public function addLine($line = "") : void{
fwrite($this->fp, $line . PHP_EOL);
}
/**
* @param string $str
*/
public function add($str) : void{
fwrite($this->fp, $str);
} }
} }

View File

@ -0,0 +1,103 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\crash;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use function count;
use function date;
use function fwrite;
use function implode;
use const PHP_EOL;
final class CrashDumpRenderer{
/**
* @param resource $fp
*/
public function __construct(private $fp, private CrashDumpData $data){
}
public function renderHumanReadable() : void{
$this->addLine($this->data->general->name . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->data->time));
$this->addLine();
$this->addLine("Error: " . $this->data->error["message"]);
$this->addLine("File: " . $this->data->error["file"]);
$this->addLine("Line: " . $this->data->error["line"]);
$this->addLine("Type: " . $this->data->error["type"]);
if($this->data->plugin_involvement !== CrashDump::PLUGIN_INVOLVEMENT_NONE){
$this->addLine();
$this->addLine(match($this->data->plugin_involvement){
CrashDump::PLUGIN_INVOLVEMENT_DIRECT => "THIS CRASH WAS CAUSED BY A PLUGIN",
CrashDump::PLUGIN_INVOLVEMENT_INDIRECT => "A PLUGIN WAS INVOLVED IN THIS CRASH",
default => "Unknown plugin involvement!"
});
}
if($this->data->plugin !== ""){
$this->addLine("BAD PLUGIN: " . $this->data->plugin);
}
$this->addLine();
$this->addLine("Code:");
foreach($this->data->code as $lineNumber => $line){
$this->addLine("[$lineNumber] $line");
}
$this->addLine();
$this->addLine("Backtrace:");
foreach($this->data->trace as $line){
$this->addLine($line);
}
$this->addLine();
$version = new VersionString($this->data->general->base_version, $this->data->general->is_dev, $this->data->general->build);
$this->addLine($this->data->general->name . " version: " . $version->getFullVersion(true) . " [Protocol " . $this->data->general->protocol . "]");
$this->addLine("Git commit: " . $this->data->general->git);
$this->addLine("uname -a: " . $this->data->general->uname);
$this->addLine("PHP Version: " . $this->data->general->php);
$this->addLine("Zend version: " . $this->data->general->zend);
$this->addLine("OS: " . $this->data->general->php_os . ", " . $this->data->general->os);
$this->addLine("Composer libraries: ");
foreach(Utils::stringifyKeys($this->data->general->composer_libraries) as $library => $libraryVersion){
$this->addLine("- $library $libraryVersion");
}
if(count($this->data->plugins) > 0){
$this->addLine();
$this->addLine("Loaded plugins:");
foreach($this->data->plugins as $p){
$this->addLine($p->name . " " . $p->version . " by " . implode(", ", $p->authors) . " for API(s) " . implode(", ", $p->api));
}
}
}
public function addLine(string $line = "") : void{
fwrite($this->fp, $line . PHP_EOL);
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data;
final class SavedDataLoadingException extends \RuntimeException{
}

View File

@ -635,7 +635,7 @@ abstract class Entity{
$this->checkBlockIntersections(); $this->checkBlockIntersections();
if($this->location->y <= -16 and $this->isAlive()){ if($this->location->y <= World::Y_MIN - 16 and $this->isAlive()){
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10);
$this->attack($ev); $this->attack($ev);
$hasUpdate = true; $hasUpdate = true;

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NBT; use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -38,34 +39,40 @@ final class EntityDataHelper{
//NOOP //NOOP
} }
/**
* @throws SavedDataLoadingException
*/
public static function parseLocation(CompoundTag $nbt, World $world) : Location{ public static function parseLocation(CompoundTag $nbt, World $world) : Location{
$pos = self::parseVec3($nbt, "Pos", false); $pos = self::parseVec3($nbt, "Pos", false);
$yawPitch = $nbt->getTag("Rotation"); $yawPitch = $nbt->getTag("Rotation");
if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){ if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){
throw new \UnexpectedValueException("'Rotation' should be a List<Float>"); throw new SavedDataLoadingException("'Rotation' should be a List<Float>");
} }
/** @var FloatTag[] $values */ /** @var FloatTag[] $values */
$values = $yawPitch->getValue(); $values = $yawPitch->getValue();
if(count($values) !== 2){ if(count($values) !== 2){
throw new \UnexpectedValueException("Expected exactly 2 entries for 'Rotation'"); throw new SavedDataLoadingException("Expected exactly 2 entries for 'Rotation'");
} }
return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue()); return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue());
} }
/**
* @throws SavedDataLoadingException
*/
public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{ public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{
$pos = $nbt->getTag($tagName); $pos = $nbt->getTag($tagName);
if($pos === null and $optional){ if($pos === null and $optional){
return new Vector3(0, 0, 0); return new Vector3(0, 0, 0);
} }
if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){ if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){
throw new \UnexpectedValueException("'$tagName' should be a List<Double> or List<Float>"); throw new SavedDataLoadingException("'$tagName' should be a List<Double> or List<Float>");
} }
/** @var DoubleTag[]|FloatTag[] $values */ /** @var DoubleTag[]|FloatTag[] $values */
$values = $pos->getValue(); $values = $pos->getValue();
if(count($values) !== 3){ if(count($values) !== 3){
throw new \UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag"); throw new SavedDataLoadingException("Expected exactly 3 entries in '$tagName' tag");
} }
return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue());
} }

View File

@ -30,6 +30,7 @@ use pocketmine\block\BlockFactory;
use pocketmine\data\bedrock\EntityLegacyIds; use pocketmine\data\bedrock\EntityLegacyIds;
use pocketmine\data\bedrock\PotionTypeIdMap; use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIds; use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\object\ExperienceOrb; use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock; use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity; use pocketmine\entity\object\ItemEntity;
@ -45,7 +46,7 @@ use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException; use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
@ -113,12 +114,12 @@ final class EntityFactory{
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{ $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
$itemTag = $nbt->getCompoundTag("Item"); $itemTag = $nbt->getCompoundTag("Item");
if($itemTag === null){ if($itemTag === null){
throw new \UnexpectedValueException("Expected \"Item\" NBT tag not found"); throw new SavedDataLoadingException("Expected \"Item\" NBT tag not found");
} }
$item = Item::nbtDeserialize($itemTag); $item = Item::nbtDeserialize($itemTag);
if($item->isNull()){ if($item->isNull()){
throw new \UnexpectedValueException("Item is invalid"); throw new SavedDataLoadingException("Item is invalid");
} }
return new ItemEntity(EntityDataHelper::parseLocation($nbt, $world), $item, $nbt); return new ItemEntity(EntityDataHelper::parseLocation($nbt, $world), $item, $nbt);
}, ['Item', 'minecraft:item'], EntityLegacyIds::ITEM); }, ['Item', 'minecraft:item'], EntityLegacyIds::ITEM);
@ -126,7 +127,7 @@ final class EntityFactory{
$this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{ $this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{
$motive = PaintingMotive::getMotiveByName($nbt->getString("Motive")); $motive = PaintingMotive::getMotiveByName($nbt->getString("Motive"));
if($motive === null){ if($motive === null){
throw new \UnexpectedValueException("Unknown painting motive"); throw new SavedDataLoadingException("Unknown painting motive");
} }
$blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ")); $blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ"));
if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){ if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){
@ -134,7 +135,7 @@ final class EntityFactory{
}elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){ }elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){
$facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH; $facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH;
}else{ }else{
throw new \UnexpectedValueException("Missing facing info"); throw new SavedDataLoadingException("Missing facing info");
} }
return new Painting(EntityDataHelper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt); return new Painting(EntityDataHelper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt);
@ -151,7 +152,7 @@ final class EntityFactory{
$this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{ $this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER)); $potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER));
if($potionType === null){ if($potionType === null){
throw new \UnexpectedValueException("No such potion type"); throw new SavedDataLoadingException("No such potion type");
} }
return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt); return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt);
}, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION); }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION);
@ -222,25 +223,28 @@ final class EntityFactory{
/** /**
* Creates an entity from data stored on a chunk. * Creates an entity from data stored on a chunk.
* *
* @throws \RuntimeException * @throws SavedDataLoadingException
* @throws NbtDataException
* @internal * @internal
*/ */
public function createFromData(World $world, CompoundTag $nbt) : ?Entity{ public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
$saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); try{
$func = null; $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier");
if($saveId instanceof StringTag){ $func = null;
$func = $this->creationFuncs[$saveId->getValue()] ?? null; if($saveId instanceof StringTag){
}elseif($saveId instanceof IntTag){ //legacy MCPE format $func = $this->creationFuncs[$saveId->getValue()] ?? null;
$func = $this->creationFuncs[$saveId->getValue() & 0xff] ?? null; }elseif($saveId instanceof IntTag){ //legacy MCPE format
} $func = $this->creationFuncs[$saveId->getValue() & 0xff] ?? null;
if($func === null){ }
return null; if($func === null){
} return null;
/** @var Entity $entity */ }
$entity = $func($world, $nbt); /** @var Entity $entity */
$entity = $func($world, $nbt);
return $entity; return $entity;
}catch(NbtException $e){
throw new SavedDataLoadingException($e->getMessage(), 0, $e);
}
} }
public function injectSaveId(string $class, CompoundTag $saveData) : void{ public function injectSaveId(string $class, CompoundTag $saveData) : void{

View File

@ -27,6 +27,7 @@ use pocketmine\entity\utils\ExperienceUtils;
use pocketmine\event\player\PlayerExperienceChangeEvent; use pocketmine\event\player\PlayerExperienceChangeEvent;
use pocketmine\item\Durable; use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits; use pocketmine\utils\Limits;
use pocketmine\world\sound\XpCollectSound; use pocketmine\world\sound\XpCollectSound;
use pocketmine\world\sound\XpLevelUpSound; use pocketmine\world\sound\XpLevelUpSound;
@ -35,6 +36,7 @@ use function ceil;
use function count; use function count;
use function max; use function max;
use function min; use function min;
use function sprintf;
class ExperienceManager{ class ExperienceManager{
@ -142,7 +144,12 @@ class ExperienceManager{
public function setCurrentTotalXp(int $amount) : bool{ public function setCurrentTotalXp(int $amount) : bool{
$newLevel = ExperienceUtils::getLevelFromXp($amount); $newLevel = ExperienceUtils::getLevelFromXp($amount);
return $this->setXpAndProgress((int) $newLevel, $newLevel - ((int) $newLevel)); $xpLevel = (int) $newLevel;
$xpProgress = $newLevel - (int) $newLevel;
if($xpProgress > 1.0){
throw new AssumptionFailedError(sprintf("newLevel - (int) newLevel should never be bigger than 1, but have %.53f (newLevel=%.53f)", $xpProgress, $newLevel));
}
return $this->setXpAndProgress($xpLevel, $xpProgress);
} }
/** /**

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\animation\TotemUseAnimation; use pocketmine\entity\animation\TotemUseAnimation;
use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\effect\VanillaEffects;
@ -104,12 +105,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
/** /**
* @throws InvalidSkinException * @throws InvalidSkinException
* @throws \UnexpectedValueException * @throws SavedDataLoadingException
*/ */
public static function parseSkinNBT(CompoundTag $nbt) : Skin{ public static function parseSkinNBT(CompoundTag $nbt) : Skin{
$skinTag = $nbt->getCompoundTag("Skin"); $skinTag = $nbt->getCompoundTag("Skin");
if($skinTag === null){ if($skinTag === null){
throw new \UnexpectedValueException("Missing skin data"); throw new SavedDataLoadingException("Missing skin data");
} }
return new Skin( //this throws if the skin is invalid return new Skin( //this throws if the skin is invalid
$skinTag->getString("Name"), $skinTag->getString("Name"),

View File

@ -38,12 +38,14 @@ class Effect{
* @param Translatable|string $name Translation key used for effect name * @param Translatable|string $name Translation key used for effect name
* @param Color $color Color of bubbles given by this effect * @param Color $color Color of bubbles given by this effect
* @param bool $bad Whether the effect is harmful * @param bool $bad Whether the effect is harmful
* @param int $defaultDuration
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles) * @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
*/ */
public function __construct( public function __construct(
protected Translatable|string $name, protected Translatable|string $name,
protected Color $color, protected Color $color,
protected bool $bad = false, protected bool $bad = false,
private int $defaultDuration = 600,
protected bool $hasBubbles = true protected bool $hasBubbles = true
){} ){}
@ -73,7 +75,7 @@ class Effect{
* Returns the default duration (in ticks) this effect will apply for if a duration is not specified. * Returns the default duration (in ticks) this effect will apply for if a duration is not specified.
*/ */
public function getDefaultDuration() : int{ public function getDefaultDuration() : int{
return 600; return $this->defaultDuration;
} }
/** /**

View File

@ -23,10 +23,13 @@ declare(strict_types=1);
namespace pocketmine\entity\effect; namespace pocketmine\entity\effect;
use pocketmine\color\Color;
use pocketmine\lang\Translatable;
abstract class InstantEffect extends Effect{ abstract class InstantEffect extends Effect{
public function getDefaultDuration() : int{ public function __construct(Translatable|string $name, Color $color, bool $bad = false, bool $hasBubbles = true){
return 1; parent::__construct($name, $color, $bad, 1, $hasBubbles);
} }
public function canTick(EffectInstance $instance) : bool{ public function canTick(EffectInstance $instance) : bool{

View File

@ -34,8 +34,8 @@ class PoisonEffect extends Effect{
/** @var bool */ /** @var bool */
private $fatal; private $fatal;
public function __construct(Translatable|string $name, Color $color, bool $isBad = false, bool $hasBubbles = true, bool $fatal = false){ public function __construct(Translatable|string $name, Color $color, bool $isBad = false, int $defaultDuration = 600, bool $hasBubbles = true, bool $fatal = false){
parent::__construct($name, $color, $isBad, $hasBubbles); parent::__construct($name, $color, $isBad, $defaultDuration, $hasBubbles);
$this->fatal = $fatal; $this->fatal = $fatal;
} }

View File

@ -0,0 +1,73 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\entity\effect;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\StringToTParser;
/**
* Handles parsing effect types from strings. This is used to interpret names in the /effect command.
*
* @phpstan-extends StringToTParser<Effect>
*/
final class StringToEffectParser extends StringToTParser{
use SingletonTrait;
private static function make() : self{
$result = new self;
$result->register("absorption", fn() => VanillaEffects::ABSORPTION());
$result->register("blindness", fn() => VanillaEffects::BLINDNESS());
$result->register("conduit_power", fn() => VanillaEffects::CONDUIT_POWER());
$result->register("fatal_poison", fn() => VanillaEffects::FATAL_POISON());
$result->register("fire_resistance", fn() => VanillaEffects::FIRE_RESISTANCE());
$result->register("haste", fn() => VanillaEffects::HASTE());
$result->register("health_boost", fn() => VanillaEffects::HEALTH_BOOST());
$result->register("hunger", fn() => VanillaEffects::HUNGER());
$result->register("instant_damage", fn() => VanillaEffects::INSTANT_DAMAGE());
$result->register("instant_health", fn() => VanillaEffects::INSTANT_HEALTH());
$result->register("invisibility", fn() => VanillaEffects::INVISIBILITY());
$result->register("jump_boost", fn() => VanillaEffects::JUMP_BOOST());
$result->register("levitation", fn() => VanillaEffects::LEVITATION());
$result->register("mining_fatigue", fn() => VanillaEffects::MINING_FATIGUE());
$result->register("nausea", fn() => VanillaEffects::NAUSEA());
$result->register("night_vision", fn() => VanillaEffects::NIGHT_VISION());
$result->register("poison", fn() => VanillaEffects::POISON());
$result->register("regeneration", fn() => VanillaEffects::REGENERATION());
$result->register("resistance", fn() => VanillaEffects::RESISTANCE());
$result->register("saturation", fn() => VanillaEffects::SATURATION());
$result->register("slowness", fn() => VanillaEffects::SLOWNESS());
$result->register("speed", fn() => VanillaEffects::SPEED());
$result->register("strength", fn() => VanillaEffects::STRENGTH());
$result->register("water_breathing", fn() => VanillaEffects::WATER_BREATHING());
$result->register("weakness", fn() => VanillaEffects::WEAKNESS());
$result->register("wither", fn() => VanillaEffects::WITHER());
return $result;
}
public function parse(string $input) : ?Effect{
return parent::parse($input);
}
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\entity\effect;
use pocketmine\color\Color; use pocketmine\color\Color;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\utils\RegistryTrait; use pocketmine\utils\RegistryTrait;
use function assert;
/** /**
* This doc-block is generated automatically, do not modify it manually. * This doc-block is generated automatically, do not modify it manually.
@ -69,7 +68,7 @@ final class VanillaEffects{
//TODO: bad_omen //TODO: bad_omen
self::register("blindness", new Effect(KnownTranslationFactory::potion_blindness(), new Color(0x1f, 0x1f, 0x23), true)); self::register("blindness", new Effect(KnownTranslationFactory::potion_blindness(), new Color(0x1f, 0x1f, 0x23), true));
self::register("conduit_power", new Effect(KnownTranslationFactory::potion_conduitPower(), new Color(0x1d, 0xc2, 0xd1))); self::register("conduit_power", new Effect(KnownTranslationFactory::potion_conduitPower(), new Color(0x1d, 0xc2, 0xd1)));
self::register("fatal_poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true, true, true)); self::register("fatal_poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true, 600, true, true));
self::register("fire_resistance", new Effect(KnownTranslationFactory::potion_fireResistance(), new Color(0xe4, 0x9a, 0x3a))); self::register("fire_resistance", new Effect(KnownTranslationFactory::potion_fireResistance(), new Color(0xe4, 0x9a, 0x3a)));
self::register("haste", new Effect(KnownTranslationFactory::potion_digSpeed(), new Color(0xd9, 0xc0, 0x43))); self::register("haste", new Effect(KnownTranslationFactory::potion_digSpeed(), new Color(0xd9, 0xc0, 0x43)));
self::register("health_boost", new HealthBoostEffect(KnownTranslationFactory::potion_healthBoost(), new Color(0xf8, 0x7d, 0x23))); self::register("health_boost", new HealthBoostEffect(KnownTranslationFactory::potion_healthBoost(), new Color(0xf8, 0x7d, 0x23)));
@ -85,7 +84,7 @@ final class VanillaEffects{
self::register("poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true)); self::register("poison", new PoisonEffect(KnownTranslationFactory::potion_poison(), new Color(0x4e, 0x93, 0x31), true));
self::register("regeneration", new RegenerationEffect(KnownTranslationFactory::potion_regeneration(), new Color(0xcd, 0x5c, 0xab))); self::register("regeneration", new RegenerationEffect(KnownTranslationFactory::potion_regeneration(), new Color(0xcd, 0x5c, 0xab)));
self::register("resistance", new Effect(KnownTranslationFactory::potion_resistance(), new Color(0x99, 0x45, 0x3a))); self::register("resistance", new Effect(KnownTranslationFactory::potion_resistance(), new Color(0x99, 0x45, 0x3a)));
self::register("saturation", new SaturationEffect(KnownTranslationFactory::potion_saturation(), new Color(0xf8, 0x24, 0x23), false)); self::register("saturation", new SaturationEffect(KnownTranslationFactory::potion_saturation(), new Color(0xf8, 0x24, 0x23)));
//TODO: slow_falling //TODO: slow_falling
self::register("slowness", new SlownessEffect(KnownTranslationFactory::potion_moveSlowdown(), new Color(0x5a, 0x6c, 0x81), true)); self::register("slowness", new SlownessEffect(KnownTranslationFactory::potion_moveSlowdown(), new Color(0x5a, 0x6c, 0x81), true));
self::register("speed", new SpeedEffect(KnownTranslationFactory::potion_moveSpeed(), new Color(0x7c, 0xaf, 0xc6))); self::register("speed", new SpeedEffect(KnownTranslationFactory::potion_moveSpeed(), new Color(0x7c, 0xaf, 0xc6)));
@ -109,10 +108,4 @@ final class VanillaEffects{
$result = self::_registryGetAll(); $result = self::_registryGetAll();
return $result; return $result;
} }
public static function fromString(string $name) : Effect{
$result = self::_registryFromString($name);
assert($result instanceof Effect);
return $result;
}
} }

View File

@ -166,7 +166,7 @@ abstract class Projectile extends Entity{
return $this->blockHit === null and parent::hasMovementUpdate(); return $this->blockHit === null and parent::hasMovementUpdate();
} }
public function move(float $dx, float $dy, float $dz) : void{ protected function move(float $dx, float $dy, float $dz) : void{
$this->blocksAround = null; $this->blocksAround = null;
Timings::$entityMove->startTiming(); Timings::$entityMove->startTiming();

View File

@ -0,0 +1,36 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\crafting\CraftingGrid;
use pocketmine\player\Player;
final class PlayerCraftingInventory extends CraftingGrid implements TemporaryInventory{
public function __construct(private Player $holder){
parent::__construct(CraftingGrid::SIZE_SMALL);
}
public function getHolder() : Player{ return $this->holder; }
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\inventory;
use pocketmine\player\Player; use pocketmine\player\Player;
class PlayerCursorInventory extends SimpleInventory{ class PlayerCursorInventory extends SimpleInventory implements TemporaryInventory{
/** @var Player */ /** @var Player */
protected $holder; protected $holder;

View File

@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
interface TemporaryInventory extends Inventory{
}

View File

@ -31,6 +31,7 @@ use pocketmine\block\BlockBreakInfo;
use pocketmine\block\BlockToolType; use pocketmine\block\BlockToolType;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
@ -671,6 +672,8 @@ class Item implements \JsonSerializable{
/** /**
* Deserializes an Item from an NBT CompoundTag * Deserializes an Item from an NBT CompoundTag
* @throws NbtException
* @throws SavedDataLoadingException
*/ */
public static function nbtDeserialize(CompoundTag $tag) : Item{ public static function nbtDeserialize(CompoundTag $tag) : Item{
if($tag->getTag("id") === null or $tag->getTag("Count") === null){ if($tag->getTag("id") === null or $tag->getTag("Count") === null){
@ -692,7 +695,7 @@ class Item implements \JsonSerializable{
} }
$item->setCount($count); $item->setCount($count);
}else{ }else{
throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given"); throw new SavedDataLoadingException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given");
} }
$itemNBT = $tag->getCompoundTag("tag"); $itemNBT = $tag->getCompoundTag("tag");

View File

@ -252,6 +252,7 @@ class ItemFactory{
$this->register(new Steak(new ItemIdentifier(ItemIds::STEAK, 0), "Steak")); $this->register(new Steak(new ItemIdentifier(ItemIds::STEAK, 0), "Steak"));
$this->register(new Stick(new ItemIdentifier(ItemIds::STICK, 0), "Stick")); $this->register(new Stick(new ItemIdentifier(ItemIds::STICK, 0), "Stick"));
$this->register(new StringItem(new ItemIdentifier(ItemIds::STRING, 0), "String")); $this->register(new StringItem(new ItemIdentifier(ItemIds::STRING, 0), "String"));
$this->register(new SweetBerries(new ItemIdentifier(ItemIds::SWEET_BERRIES, 0), "Sweet Berries"));
$this->register(new Totem(new ItemIdentifier(ItemIds::TOTEM, 0), "Totem of Undying")); $this->register(new Totem(new ItemIdentifier(ItemIds::TOTEM, 0), "Totem of Undying"));
$this->register(new WheatSeeds(new ItemIdentifier(ItemIds::WHEAT_SEEDS, 0), "Wheat Seeds")); $this->register(new WheatSeeds(new ItemIdentifier(ItemIds::WHEAT_SEEDS, 0), "Wheat Seeds"));
$this->register(new WritableBook(new ItemIdentifier(ItemIds::WRITABLE_BOOK, 0), "Book & Quill")); $this->register(new WritableBook(new ItemIdentifier(ItemIds::WRITABLE_BOOK, 0), "Book & Quill"));
@ -327,7 +328,6 @@ class ItemFactory{
//TODO: minecraft:shield //TODO: minecraft:shield
//TODO: minecraft:sparkler //TODO: minecraft:sparkler
//TODO: minecraft:spawn_egg //TODO: minecraft:spawn_egg
$this->register(new SweetBerries(new ItemIdentifier(ItemIds::SWEET_BERRIES, 0), "Sweet Berries"));
//TODO: minecraft:tnt_minecart //TODO: minecraft:tnt_minecart
//TODO: minecraft:trident //TODO: minecraft:trident
//TODO: minecraft:turtle_helmet //TODO: minecraft:turtle_helmet

View File

@ -29,25 +29,16 @@ use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType; use pocketmine\block\utils\SlabType;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use function array_keys; use pocketmine\utils\StringToTParser;
use function str_replace;
use function strtolower;
use function trim;
/** /**
* Handles parsing items from strings. This is used to interpret names from the /give command (and others). * Handles parsing items from strings. This is used to interpret names from the /give command (and others).
* Custom aliases may be registered. *
* Note that the aliases should be user-friendly, i.e. easily readable and writable. * @phpstan-extends StringToTParser<Item>
*/ */
final class StringToItemParser{ final class StringToItemParser extends StringToTParser{
use SingletonTrait; use SingletonTrait;
/**
* @var \Closure[]
* @phpstan-var array<string, \Closure(string $input) : Item>
*/
private array $callbackMap = [];
private static function make() : self{ private static function make() : self{
$result = new self; $result = new self;
@ -167,6 +158,7 @@ final class StringToItemParser{
$result->registerBlock("chemical_heat", fn() => VanillaBlocks::CHEMICAL_HEAT()); $result->registerBlock("chemical_heat", fn() => VanillaBlocks::CHEMICAL_HEAT());
$result->registerBlock("chemistry_table", fn() => VanillaBlocks::COMPOUND_CREATOR()); $result->registerBlock("chemistry_table", fn() => VanillaBlocks::COMPOUND_CREATOR());
$result->registerBlock("chest", fn() => VanillaBlocks::CHEST()); $result->registerBlock("chest", fn() => VanillaBlocks::CHEST());
$result->registerBlock("chipped_anvil", fn() => VanillaBlocks::ANVIL()->setDamage(1));
$result->registerBlock("chiseled_quartz", fn() => VanillaBlocks::CHISELED_QUARTZ()); $result->registerBlock("chiseled_quartz", fn() => VanillaBlocks::CHISELED_QUARTZ());
$result->registerBlock("chiseled_red_sandstone", fn() => VanillaBlocks::CHISELED_RED_SANDSTONE()); $result->registerBlock("chiseled_red_sandstone", fn() => VanillaBlocks::CHISELED_RED_SANDSTONE());
$result->registerBlock("chiseled_sandstone", fn() => VanillaBlocks::CHISELED_SANDSTONE()); $result->registerBlock("chiseled_sandstone", fn() => VanillaBlocks::CHISELED_SANDSTONE());
@ -174,6 +166,7 @@ final class StringToItemParser{
$result->registerBlock("clay_block", fn() => VanillaBlocks::CLAY()); $result->registerBlock("clay_block", fn() => VanillaBlocks::CLAY());
$result->registerBlock("coal_block", fn() => VanillaBlocks::COAL()); $result->registerBlock("coal_block", fn() => VanillaBlocks::COAL());
$result->registerBlock("coal_ore", fn() => VanillaBlocks::COAL_ORE()); $result->registerBlock("coal_ore", fn() => VanillaBlocks::COAL_ORE());
$result->registerBlock("coarse_dirt", fn() => VanillaBlocks::DIRT()->setCoarse(true));
$result->registerBlock("cobble", fn() => VanillaBlocks::COBBLESTONE()); $result->registerBlock("cobble", fn() => VanillaBlocks::COBBLESTONE());
$result->registerBlock("cobble_stairs", fn() => VanillaBlocks::COBBLESTONE_STAIRS()); $result->registerBlock("cobble_stairs", fn() => VanillaBlocks::COBBLESTONE_STAIRS());
$result->registerBlock("cobble_wall", fn() => VanillaBlocks::COBBLESTONE_WALL()); $result->registerBlock("cobble_wall", fn() => VanillaBlocks::COBBLESTONE_WALL());
@ -209,6 +202,7 @@ final class StringToItemParser{
$result->registerBlock("cut_sandstone", fn() => VanillaBlocks::CUT_SANDSTONE()); $result->registerBlock("cut_sandstone", fn() => VanillaBlocks::CUT_SANDSTONE());
$result->registerBlock("cut_sandstone_slab", fn() => VanillaBlocks::CUT_SANDSTONE_SLAB()); $result->registerBlock("cut_sandstone_slab", fn() => VanillaBlocks::CUT_SANDSTONE_SLAB());
$result->registerBlock("cyan_glazed_terracotta", fn() => VanillaBlocks::CYAN_GLAZED_TERRACOTTA()); $result->registerBlock("cyan_glazed_terracotta", fn() => VanillaBlocks::CYAN_GLAZED_TERRACOTTA());
$result->registerBlock("damaged_anvil", fn() => VanillaBlocks::ANVIL()->setDamage(2));
$result->registerBlock("dandelion", fn() => VanillaBlocks::DANDELION()); $result->registerBlock("dandelion", fn() => VanillaBlocks::DANDELION());
$result->registerBlock("dark_oak_button", fn() => VanillaBlocks::DARK_OAK_BUTTON()); $result->registerBlock("dark_oak_button", fn() => VanillaBlocks::DARK_OAK_BUTTON());
$result->registerBlock("dark_oak_door", fn() => VanillaBlocks::DARK_OAK_DOOR()); $result->registerBlock("dark_oak_door", fn() => VanillaBlocks::DARK_OAK_DOOR());
@ -223,6 +217,7 @@ final class StringToItemParser{
$result->registerBlock("dark_oak_sign", fn() => VanillaBlocks::DARK_OAK_SIGN()); $result->registerBlock("dark_oak_sign", fn() => VanillaBlocks::DARK_OAK_SIGN());
$result->registerBlock("dark_oak_slab", fn() => VanillaBlocks::DARK_OAK_SLAB()); $result->registerBlock("dark_oak_slab", fn() => VanillaBlocks::DARK_OAK_SLAB());
$result->registerBlock("dark_oak_stairs", fn() => VanillaBlocks::DARK_OAK_STAIRS()); $result->registerBlock("dark_oak_stairs", fn() => VanillaBlocks::DARK_OAK_STAIRS());
$result->registerBlock("dark_oak_standing_sign", fn() => VanillaBlocks::DARK_OAK_SIGN());
$result->registerBlock("dark_oak_trapdoor", fn() => VanillaBlocks::DARK_OAK_TRAPDOOR()); $result->registerBlock("dark_oak_trapdoor", fn() => VanillaBlocks::DARK_OAK_TRAPDOOR());
$result->registerBlock("dark_oak_wall_sign", fn() => VanillaBlocks::DARK_OAK_WALL_SIGN()); $result->registerBlock("dark_oak_wall_sign", fn() => VanillaBlocks::DARK_OAK_WALL_SIGN());
$result->registerBlock("dark_oak_wood", fn() => VanillaBlocks::DARK_OAK_WOOD()); $result->registerBlock("dark_oak_wood", fn() => VanillaBlocks::DARK_OAK_WOOD());
@ -618,6 +613,8 @@ final class StringToItemParser{
$result->registerBlock("jungle_trapdoor", fn() => VanillaBlocks::JUNGLE_TRAPDOOR()); $result->registerBlock("jungle_trapdoor", fn() => VanillaBlocks::JUNGLE_TRAPDOOR());
$result->registerBlock("jungle_wall_sign", fn() => VanillaBlocks::JUNGLE_WALL_SIGN()); $result->registerBlock("jungle_wall_sign", fn() => VanillaBlocks::JUNGLE_WALL_SIGN());
$result->registerBlock("jungle_wood", fn() => VanillaBlocks::JUNGLE_WOOD()); $result->registerBlock("jungle_wood", fn() => VanillaBlocks::JUNGLE_WOOD());
$result->registerBlock("jungle_wood_stairs", fn() => VanillaBlocks::JUNGLE_STAIRS());
$result->registerBlock("jungle_wooden_stairs", fn() => VanillaBlocks::JUNGLE_STAIRS());
$result->registerBlock("lab_table", fn() => VanillaBlocks::LAB_TABLE()); $result->registerBlock("lab_table", fn() => VanillaBlocks::LAB_TABLE());
$result->registerBlock("ladder", fn() => VanillaBlocks::LADDER()); $result->registerBlock("ladder", fn() => VanillaBlocks::LADDER());
$result->registerBlock("lantern", fn() => VanillaBlocks::LANTERN()); $result->registerBlock("lantern", fn() => VanillaBlocks::LANTERN());
@ -704,6 +701,7 @@ final class StringToItemParser{
$result->registerBlock("oak_sign", fn() => VanillaBlocks::OAK_SIGN()); $result->registerBlock("oak_sign", fn() => VanillaBlocks::OAK_SIGN());
$result->registerBlock("oak_slab", fn() => VanillaBlocks::OAK_SLAB()); $result->registerBlock("oak_slab", fn() => VanillaBlocks::OAK_SLAB());
$result->registerBlock("oak_stairs", fn() => VanillaBlocks::OAK_STAIRS()); $result->registerBlock("oak_stairs", fn() => VanillaBlocks::OAK_STAIRS());
$result->registerBlock("oak_standing_sign", fn() => VanillaBlocks::OAK_SIGN());
$result->registerBlock("oak_trapdoor", fn() => VanillaBlocks::OAK_TRAPDOOR()); $result->registerBlock("oak_trapdoor", fn() => VanillaBlocks::OAK_TRAPDOOR());
$result->registerBlock("oak_wall_sign", fn() => VanillaBlocks::OAK_WALL_SIGN()); $result->registerBlock("oak_wall_sign", fn() => VanillaBlocks::OAK_WALL_SIGN());
$result->registerBlock("oak_wood", fn() => VanillaBlocks::OAK_WOOD()); $result->registerBlock("oak_wood", fn() => VanillaBlocks::OAK_WOOD());
@ -1325,41 +1323,12 @@ final class StringToItemParser{
return $result; return $result;
} }
/** @phpstan-param \Closure(string $input) : Item $callback */
public function register(string $alias, \Closure $callback) : void{
$key = $this->reprocess($alias);
if(isset($this->callbackMap[$key])){
throw new \InvalidArgumentException("Alias \"$key\" is already registered");
}
$this->callbackMap[$key] = $callback;
}
/** @phpstan-param \Closure(string $input) : Block $callback */ /** @phpstan-param \Closure(string $input) : Block $callback */
public function registerBlock(string $alias, \Closure $callback) : void{ public function registerBlock(string $alias, \Closure $callback) : void{
$this->register($alias, fn(string $input) => $callback($input)->asItem()); $this->register($alias, fn(string $input) => $callback($input)->asItem());
} }
/** @phpstan-param \Closure(string $input) : Item $callback */
public function override(string $alias, \Closure $callback) : void{
$this->callbackMap[$this->reprocess($alias)] = $callback;
}
/** Tries to parse the specified string into an item. */
public function parse(string $input) : ?Item{ public function parse(string $input) : ?Item{
$key = $this->reprocess($input); return parent::parse($input);
if(isset($this->callbackMap[$key])){
return ($this->callbackMap[$key])($input);
}
return null;
}
protected function reprocess(string $input) : string{
return strtolower(str_replace([" ", "minecraft:"], ["_", ""], trim($input)));
}
/** @return string[] */
public function getKnownAliases() : array{
return array_keys($this->callbackMap);
} }
} }

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\item; namespace pocketmine\item;
use pocketmine\utils\CloningRegistryTrait; use pocketmine\utils\CloningRegistryTrait;
use function assert;
/** /**
* This doc-block is generated automatically, do not modify it manually. * This doc-block is generated automatically, do not modify it manually.
@ -381,12 +380,6 @@ final class VanillaItems{
self::_registryRegister($name, $item); self::_registryRegister($name, $item);
} }
public static function fromString(string $name) : Item{
$result = self::_registryFromString($name);
assert($result instanceof Item);
return $result;
}
/** /**
* @return Item[] * @return Item[]
*/ */

View File

@ -0,0 +1,66 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item\enchantment;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\StringToTParser;
/**
* Handles parsing enchantments from strings. This is used to interpret names in the /enchant command.
*
* @phpstan-extends StringToTParser<Enchantment>
*/
final class StringToEnchantmentParser extends StringToTParser{
use SingletonTrait;
private static function make() : self{
$result = new self;
$result->register("blast_protection", fn() => VanillaEnchantments::BLAST_PROTECTION());
$result->register("efficiency", fn() => VanillaEnchantments::EFFICIENCY());
$result->register("feather_falling", fn() => VanillaEnchantments::FEATHER_FALLING());
$result->register("fire_aspect", fn() => VanillaEnchantments::FIRE_ASPECT());
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
$result->register("flame", fn() => VanillaEnchantments::FLAME());
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
$result->register("mending", fn() => VanillaEnchantments::MENDING());
$result->register("power", fn() => VanillaEnchantments::POWER());
$result->register("projectile_protection", fn() => VanillaEnchantments::PROJECTILE_PROTECTION());
$result->register("protection", fn() => VanillaEnchantments::PROTECTION());
$result->register("punch", fn() => VanillaEnchantments::PUNCH());
$result->register("respiration", fn() => VanillaEnchantments::RESPIRATION());
$result->register("sharpness", fn() => VanillaEnchantments::SHARPNESS());
$result->register("silk_touch", fn() => VanillaEnchantments::SILK_TOUCH());
$result->register("thorns", fn() => VanillaEnchantments::THORNS());
$result->register("unbreaking", fn() => VanillaEnchantments::UNBREAKING());
$result->register("vanishing", fn() => VanillaEnchantments::VANISHING());
return $result;
}
public function parse(string $input) : ?Enchantment{
return parent::parse($input);
}
}

View File

@ -113,10 +113,4 @@ final class VanillaEnchantments{
$result = self::_registryGetAll(); $result = self::_registryGetAll();
return $result; return $result;
} }
public static function fromString(string $name) : Enchantment{
/** @var Enchantment $result */
$result = self::_registryFromString($name);
return $result;
}
} }

View File

@ -1666,10 +1666,11 @@ final class KnownTranslationFactory{
]); ]);
} }
public static function pocketmine_plugin_aliasError(Translatable|string $param0, Translatable|string $param1) : Translatable{ public static function pocketmine_plugin_aliasError(Translatable|string $param0, Translatable|string $param1, Translatable|string $param2) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_ALIASERROR, [ return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_ALIASERROR, [
0 => $param0, 0 => $param0,
1 => $param1, 1 => $param1,
2 => $param2,
]); ]);
} }
@ -1689,10 +1690,11 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_CIRCULARDEPENDENCY, []); return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_CIRCULARDEPENDENCY, []);
} }
public static function pocketmine_plugin_commandError(Translatable|string $param0, Translatable|string $param1) : Translatable{ public static function pocketmine_plugin_commandError(Translatable|string $param0, Translatable|string $param1, Translatable|string $param2) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_COMMANDERROR, [ return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_COMMANDERROR, [
0 => $param0, 0 => $param0,
1 => $param1, 1 => $param1,
2 => $param2,
]); ]);
} }
@ -1724,6 +1726,12 @@ final class KnownTranslationFactory{
]); ]);
} }
public static function pocketmine_plugin_duplicatePermissionError(Translatable|string $permissionName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_DUPLICATEPERMISSIONERROR, [
"permissionName" => $permissionName,
]);
}
public static function pocketmine_plugin_emptyExtensionVersionConstraint(Translatable|string $constraintIndex, Translatable|string $extensionName) : Translatable{ public static function pocketmine_plugin_emptyExtensionVersionConstraint(Translatable|string $constraintIndex, Translatable|string $extensionName) : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT, [ return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT, [
"constraintIndex" => $constraintIndex, "constraintIndex" => $constraintIndex,

View File

@ -361,6 +361,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_PLUGIN_DISALLOWEDBYBLACKLIST = "pocketmine.plugin.disallowedByBlacklist"; public const POCKETMINE_PLUGIN_DISALLOWEDBYBLACKLIST = "pocketmine.plugin.disallowedByBlacklist";
public const POCKETMINE_PLUGIN_DISALLOWEDBYWHITELIST = "pocketmine.plugin.disallowedByWhitelist"; public const POCKETMINE_PLUGIN_DISALLOWEDBYWHITELIST = "pocketmine.plugin.disallowedByWhitelist";
public const POCKETMINE_PLUGIN_DUPLICATEERROR = "pocketmine.plugin.duplicateError"; public const POCKETMINE_PLUGIN_DUPLICATEERROR = "pocketmine.plugin.duplicateError";
public const POCKETMINE_PLUGIN_DUPLICATEPERMISSIONERROR = "pocketmine.plugin.duplicatePermissionError";
public const POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT = "pocketmine.plugin.emptyExtensionVersionConstraint"; public const POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT = "pocketmine.plugin.emptyExtensionVersionConstraint";
public const POCKETMINE_PLUGIN_ENABLE = "pocketmine.plugin.enable"; public const POCKETMINE_PLUGIN_ENABLE = "pocketmine.plugin.enable";
public const POCKETMINE_PLUGIN_EXTENSIONNOTLOADED = "pocketmine.plugin.extensionNotLoaded"; public const POCKETMINE_PLUGIN_EXTENSIONNOTLOADED = "pocketmine.plugin.extensionNotLoaded";

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe;
use pocketmine\block\inventory\AnvilInventory; use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\inventory\BlockInventory; use pocketmine\block\inventory\BlockInventory;
use pocketmine\block\inventory\BrewingStandInventory; use pocketmine\block\inventory\BrewingStandInventory;
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\block\inventory\EnchantInventory; use pocketmine\block\inventory\EnchantInventory;
use pocketmine\block\inventory\FurnaceInventory; use pocketmine\block\inventory\FurnaceInventory;
use pocketmine\block\inventory\HopperInventory; use pocketmine\block\inventory\HopperInventory;
@ -66,9 +67,7 @@ class InventoryManager{
//these IDs are used for 1.16 to restore 1.14ish crafting & inventory behaviour; since they don't seem to have any //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. //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_START = ContainerIds::LAST - 10;
private const RESERVED_WINDOW_ID_RANGE_END = ContainerIds::LAST; private const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
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;
/** @var Player */ /** @var Player */
private $player; private $player;
@ -80,6 +79,15 @@ class InventoryManager{
/** @var int */ /** @var int */
private $lastInventoryNetworkId = ContainerIds::FIRST; private $lastInventoryNetworkId = ContainerIds::FIRST;
/**
* 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
*/
protected $openHardcodedWindows = [];
/** /**
* @var Item[][] * @var Item[][]
* @phpstan-var array<int, array<int, Item>> * @phpstan-var array<int, array<int, Item>>
@ -178,6 +186,7 @@ class InventoryManager{
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND, $inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
$inv instanceof AnvilInventory => WindowTypes::ANVIL, $inv instanceof AnvilInventory => WindowTypes::ANVIL,
$inv instanceof HopperInventory => WindowTypes::HOPPER, $inv instanceof HopperInventory => WindowTypes::HOPPER,
$inv instanceof CraftingTableInventory => WindowTypes::WORKBENCH,
default => WindowTypes::CONTAINER default => WindowTypes::CONTAINER
}; };
return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)]; return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)];
@ -185,6 +194,21 @@ class InventoryManager{
return null; return null;
} }
public function onClientOpenMainInventory() : void{
$id = self::HARDCODED_INVENTORY_WINDOW_ID;
if(!isset($this->openHardcodedWindows[$id])){
//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[$id] = true;
$this->session->sendDataPacket(ContainerOpenPacket::entityInv(
InventoryManager::HARDCODED_INVENTORY_WINDOW_ID,
WindowTypes::INVENTORY,
$this->player->getId()
));
}
}
public function onCurrentWindowRemove() : void{ public function onCurrentWindowRemove() : void{
if(isset($this->windowMap[$this->lastInventoryNetworkId])){ if(isset($this->windowMap[$this->lastInventoryNetworkId])){
$this->remove($this->lastInventoryNetworkId); $this->remove($this->lastInventoryNetworkId);
@ -193,16 +217,18 @@ class InventoryManager{
} }
public function onClientRemoveWindow(int $id) : void{ public function onClientRemoveWindow(int $id) : void{
if($id >= self::RESERVED_WINDOW_ID_RANGE_START && $id <= self::RESERVED_WINDOW_ID_RANGE_END){ if(isset($this->openHardcodedWindows[$id])){
//TODO: HACK! crafting grid & main inventory currently use these fake IDs unset($this->openHardcodedWindows[$id]);
return; }elseif($id === $this->lastInventoryNetworkId){
}
if($id === $this->lastInventoryNetworkId){
$this->remove($id); $this->remove($id);
$this->player->removeCurrentWindow(); $this->player->removeCurrentWindow();
}else{ }else{
$this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId"); $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId");
} }
//Always send this, even if no window matches. If we told the client to close a window, it will behave as if it
//initiated the close and expect an ack.
$this->session->sendDataPacket(ContainerClosePacket::create($id, false));
} }
public function syncSlot(Inventory $inventory, int $slot) : void{ public function syncSlot(Inventory $inventory, int $slot) : void{

View File

@ -111,6 +111,7 @@ use pocketmine\player\UsedChunkStatus;
use pocketmine\player\XboxLivePlayerInfo; use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet; use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
@ -714,7 +715,7 @@ class NetworkSession{
public function onServerDeath() : void{ public function onServerDeath() : void{
if($this->handler instanceof InGamePacketHandler){ //TODO: this is a bad fix for pre-spawn death, this shouldn't be reachable at all at this stage :( if($this->handler instanceof InGamePacketHandler){ //TODO: this is a bad fix for pre-spawn death, this shouldn't be reachable at all at this stage :(
$this->setHandler(new DeathPacketHandler($this->player, $this)); $this->setHandler(new DeathPacketHandler($this->player, $this, $this->invManager ?? throw new AssumptionFailedError()));
} }
} }

View File

@ -27,6 +27,7 @@ use pocketmine\data\bedrock\LegacyItemIdToStringIdMap;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function array_key_exists; use function array_key_exists;
use function file_get_contents; use function file_get_contents;
@ -73,10 +74,6 @@ final class ItemTranslator{
throw new AssumptionFailedError("Invalid item table format"); throw new AssumptionFailedError("Invalid item table format");
} }
$legacyStringToIntMapRaw = file_get_contents(Path::join(\pocketmine\BEDROCK_DATA_PATH, 'item_id_map.json'));
if($legacyStringToIntMapRaw === false){
throw new AssumptionFailedError("Missing required resource file");
}
$legacyStringToIntMap = LegacyItemIdToStringIdMap::getInstance(); $legacyStringToIntMap = LegacyItemIdToStringIdMap::getInstance();
/** @phpstan-var array<string, int> $simpleMappings */ /** @phpstan-var array<string, int> $simpleMappings */
@ -92,7 +89,7 @@ final class ItemTranslator{
} }
$simpleMappings[$newId] = $intId; $simpleMappings[$newId] = $intId;
} }
foreach($legacyStringToIntMap->getStringToLegacyMap() as $stringId => $intId){ foreach(Utils::stringifyKeys($legacyStringToIntMap->getStringToLegacyMap()) as $stringId => $intId){
if(isset($simpleMappings[$stringId])){ if(isset($simpleMappings[$stringId])){
throw new \UnexpectedValueException("Old ID $stringId collides with new ID"); throw new \UnexpectedValueException("Old ID $stringId collides with new ID");
} }

View File

@ -24,10 +24,9 @@ namespace pocketmine\network\mcpe\convert;
use pocketmine\block\BlockLegacyIds; use pocketmine\block\BlockLegacyIds;
use pocketmine\block\inventory\AnvilInventory; use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\block\inventory\EnchantInventory; use pocketmine\block\inventory\EnchantInventory;
use pocketmine\block\inventory\LoomInventory; use pocketmine\block\inventory\LoomInventory;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction; use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction; use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\action\DropItemAction;
@ -51,7 +50,6 @@ use pocketmine\player\GameMode;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use function array_key_exists;
class TypeConverter{ class TypeConverter{
use SingletonTrait; use SingletonTrait;
@ -247,22 +245,6 @@ class TypeConverter{
} }
} }
/**
* @param int[] $test
* @phpstan-param array<int, int> $test
* @phpstan-param \Closure(Inventory) : bool $c
* @phpstan-return array{int, Inventory}
*/
protected function mapUIInventory(int $slot, array $test, ?Inventory $inventory, \Closure $c) : ?array{
if($inventory === null){
return null;
}
if(array_key_exists($slot, $test) && $c($inventory)){
return [$test[$slot], $inventory];
}
return null;
}
/** /**
* @throws TypeConversionException * @throws TypeConversionException
*/ */
@ -283,32 +265,32 @@ class TypeConverter{
} }
switch($action->sourceType){ switch($action->sourceType){
case NetworkInventoryAction::SOURCE_CONTAINER: case NetworkInventoryAction::SOURCE_CONTAINER:
$window = null;
if($action->windowId === ContainerIds::UI and $action->inventorySlot > 0){ if($action->windowId === ContainerIds::UI and $action->inventorySlot > 0){
if($action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){ if($action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
return null; //useless noise return null; //useless noise
} }
$pSlot = $action->inventorySlot; $pSlot = $action->inventorySlot;
$craftingGrid = $player->getCraftingGrid(); $slot = UIInventorySlotOffset::CRAFTING2X2_INPUT[$pSlot] ?? null;
$mapped = if($slot !== null){
$this->mapUIInventory($pSlot, UIInventorySlotOffset::CRAFTING2X2_INPUT, $craftingGrid, $window = $player->getCraftingGrid();
function(Inventory $i) : bool{ return $i instanceof CraftingGrid && $i->getGridWidth() === CraftingGrid::SIZE_SMALL; }) ?? }elseif(($current = $player->getCurrentWindow()) !== null){
$this->mapUIInventory($pSlot, UIInventorySlotOffset::CRAFTING3X3_INPUT, $craftingGrid, $slotMap = match(true){
function(Inventory $i) : bool{ return $i instanceof CraftingGrid && $i->getGridWidth() === CraftingGrid::SIZE_BIG; }); $current instanceof AnvilInventory => UIInventorySlotOffset::ANVIL,
if($mapped === null){ $current instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE,
$current = $player->getCurrentWindow(); $current instanceof LoomInventory => UIInventorySlotOffset::LOOM,
$mapped = $current instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT,
$this->mapUIInventory($pSlot, UIInventorySlotOffset::ANVIL, $current, default => null
function(Inventory $i) : bool{ return $i instanceof AnvilInventory; }) ?? };
$this->mapUIInventory($pSlot, UIInventorySlotOffset::ENCHANTING_TABLE, $current, if($slotMap !== null){
function(Inventory $i) : bool{ return $i instanceof EnchantInventory; }) ?? $window = $current;
$this->mapUIInventory($pSlot, UIInventorySlotOffset::LOOM, $current, $slot = $slotMap[$pSlot] ?? null;
fn(Inventory $i) => $i instanceof LoomInventory); }
} }
if($mapped === null){ if($slot === null){
throw new TypeConversionException("Unmatched UI inventory slot offset $pSlot"); throw new TypeConversionException("Unmatched UI inventory slot offset $pSlot");
} }
[$slot, $window] = $mapped;
}else{ }else{
$window = $inventoryManager->getWindow($action->windowId); $window = $inventoryManager->getWindow($action->windowId);
$slot = $action->inventorySlot; $slot = $action->inventorySlot;

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler; namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket; use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket; use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\types\PlayerAction; use pocketmine\network\mcpe\protocol\types\PlayerAction;
@ -35,10 +37,12 @@ class DeathPacketHandler extends PacketHandler{
private $player; private $player;
/** @var NetworkSession */ /** @var NetworkSession */
private $session; private $session;
private InventoryManager $inventoryManager;
public function __construct(Player $player, NetworkSession $session){ public function __construct(Player $player, NetworkSession $session, InventoryManager $inventoryManager){
$this->player = $player; $this->player = $player;
$this->session = $session; $this->session = $session;
$this->inventoryManager = $inventoryManager;
} }
public function setUp() : void{ public function setUp() : void{
@ -58,6 +62,11 @@ class DeathPacketHandler extends PacketHandler{
return false; return false;
} }
public function handleContainerClose(ContainerClosePacket $packet) : bool{
$this->inventoryManager->onClientRemoveWindow($packet->windowId);
return true;
}
public function handleRespawn(RespawnPacket $packet) : bool{ public function handleRespawn(RespawnPacket $packet) : bool{
if($packet->respawnState === RespawnPacket::CLIENT_READY_TO_SPAWN){ if($packet->respawnState === RespawnPacket::CLIENT_READY_TO_SPAWN){
$this->session->sendDataPacket(RespawnPacket::create( $this->session->sendDataPacket(RespawnPacket::create(

View File

@ -26,7 +26,6 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\block\BaseSign; use pocketmine\block\BaseSign;
use pocketmine\block\ItemFrame; use pocketmine\block\ItemFrame;
use pocketmine\block\utils\SignText; use pocketmine\block\utils\SignText;
use pocketmine\crafting\CraftingGrid;
use pocketmine\entity\animation\ConsumingItemAnimation; use pocketmine\entity\animation\ConsumingItemAnimation;
use pocketmine\entity\InvalidSkinException; use pocketmine\entity\InvalidSkinException;
use pocketmine\event\player\PlayerEditBookEvent; use pocketmine\event\player\PlayerEditBookEvent;
@ -57,7 +56,6 @@ use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket; use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket; use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket; use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\EmotePacket; use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket; use pocketmine\network\mcpe\protocol\InteractPacket;
@ -93,12 +91,10 @@ use pocketmine\network\mcpe\protocol\types\inventory\ReleaseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData; use pocketmine\network\mcpe\protocol\types\inventory\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData; use pocketmine\network\mcpe\protocol\types\inventory\UseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\inventory\WindowTypes;
use pocketmine\network\mcpe\protocol\types\PlayerAction; use pocketmine\network\mcpe\protocol\types\PlayerAction;
use pocketmine\network\PacketHandlingException; use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use function array_key_exists;
use function array_push; use function array_push;
use function base64_encode; use function base64_encode;
use function count; use function count;
@ -138,15 +134,6 @@ class InGamePacketHandler extends PacketHandler{
/** @var bool */ /** @var bool */
public $forceMoveSync = false; public $forceMoveSync = false;
/**
* 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
*/
protected $openHardcodedWindows = [];
private InventoryManager $inventoryManager; private InventoryManager $inventoryManager;
public function __construct(Player $player, NetworkSession $session, InventoryManager $inventoryManager){ public function __construct(Player $player, NetworkSession $session, InventoryManager $inventoryManager){
@ -206,7 +193,7 @@ class InGamePacketHandler extends PacketHandler{
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier) //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->actorRuntimeId === ActorEvent::EATING_ITEM; return $packet->actorRuntimeId === ActorEvent::EATING_ITEM;
} }
$this->player->doCloseInventory(); $this->player->removeCurrentWindow();
switch($packet->eventId){ switch($packet->eventId){
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
@ -309,14 +296,6 @@ class InGamePacketHandler extends PacketHandler{
foreach($this->craftingTransaction->getInventories() as $inventory){ foreach($this->craftingTransaction->getInventories() as $inventory){
$this->inventoryManager->syncContents($inventory); $this->inventoryManager->syncContents($inventory);
} }
/*
* TODO: HACK!
* we can't resend the contents of the crafting window, so we force the client to close it instead.
* So people don't whine about messy desync issues when someone cancels CraftItemEvent, or when a crafting
* transaction goes wrong.
*/
$this->session->sendDataPacket(ContainerClosePacket::create(InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID, true));
return false; return false;
}finally{ }finally{
$this->craftingTransaction = null; $this->craftingTransaction = null;
@ -380,18 +359,6 @@ class InGamePacketHandler extends PacketHandler{
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ()); $vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){ if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
$this->onFailedBlockAction($vBlockPos, $data->getFace()); $this->onFailedBlockAction($vBlockPos, $data->getFace());
}elseif(
!array_key_exists($windowId = InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID, $this->openHardcodedWindows) &&
$this->player->getCraftingGrid()->getGridWidth() === CraftingGrid::SIZE_BIG
){
//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
$this->openHardcodedWindows[$windowId] = true;
$this->session->sendDataPacket(ContainerOpenPacket::blockInv(
InventoryManager::HARDCODED_CRAFTING_GRID_WINDOW_ID,
WindowTypes::WORKBENCH,
$blockPos
));
} }
return true; return true;
case UseItemTransactionData::ACTION_BREAK_BLOCK: case UseItemTransactionData::ACTION_BREAK_BLOCK:
@ -509,19 +476,8 @@ class InGamePacketHandler extends PacketHandler{
if($target === null){ if($target === null){
return false; return false;
} }
if( if($packet->action === InteractPacket::ACTION_OPEN_INVENTORY && $target === $this->player){
$packet->action === InteractPacket::ACTION_OPEN_INVENTORY && $target === $this->player && $this->inventoryManager->onClientOpenMainInventory();
!array_key_exists($windowId = InventoryManager::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;
$this->session->sendDataPacket(ContainerOpenPacket::entityInv(
InventoryManager::HARDCODED_INVENTORY_WINDOW_ID,
WindowTypes::INVENTORY,
$this->player->getId()
));
return true; return true;
} }
return false; //TODO return false; //TODO
@ -614,15 +570,7 @@ class InGamePacketHandler extends PacketHandler{
} }
public function handleContainerClose(ContainerClosePacket $packet) : bool{ public function handleContainerClose(ContainerClosePacket $packet) : bool{
$this->player->doCloseInventory(); $this->inventoryManager->onClientRemoveWindow($packet->windowId);
if(array_key_exists($packet->windowId, $this->openHardcodedWindows)){
unset($this->openHardcodedWindows[$packet->windowId]);
}else{
$this->inventoryManager->onClientRemoveWindow($packet->windowId);
}
$this->session->sendDataPacket(ContainerClosePacket::create($packet->windowId, false));
return true; return true;
} }

View File

@ -117,7 +117,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
$pack->getPackSize(), $pack->getPackSize(),
$pack->getSha256(), $pack->getSha256(),
false, false,
ResourcePackType::ADDON //TODO: this might be an addon (not behaviour pack), needed to properly support client-side custom items ResourcePackType::RESOURCES //TODO: this might be an addon (not behaviour pack), needed to properly support client-side custom items
)); ));
} }
$this->session->getLogger()->debug("Player requested download of " . count($packet->packIds) . " resource packs"); $this->session->getLogger()->debug("Player requested download of " . count($packet->packIds) . " resource packs");

View File

@ -88,7 +88,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
/** @var PacketBroadcaster */ /** @var PacketBroadcaster */
private $broadcaster; private $broadcaster;
public function __construct(Server $server){ public function __construct(Server $server, string $ip, int $port, bool $ipV6){
$this->server = $server; $this->server = $server;
$this->rakServerId = mt_rand(0, PHP_INT_MAX); $this->rakServerId = mt_rand(0, PHP_INT_MAX);
@ -101,7 +101,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->server->getLogger(), $this->server->getLogger(),
$mainToThreadBuffer, $mainToThreadBuffer,
$threadToMainBuffer, $threadToMainBuffer,
new InternetAddress($this->server->getIp(), $this->server->getPort(), 4), new InternetAddress($ip, $port, $ipV6 ? 6 : 4),
$this->rakServerId, $this->rakServerId,
$this->server->getConfigGroup()->getPropertyInt("network.max-mtu-size", 1492), $this->server->getConfigGroup()->getPropertyInt("network.max-mtu-size", 1492),
self::MCPE_RAKNET_PROTOCOL_VERSION, self::MCPE_RAKNET_PROTOCOL_VERSION,

View File

@ -46,8 +46,8 @@ final class ChunkSerializer{
* Chunks are sent in a stack, so every chunk below the top non-empty one must be sent. * Chunks are sent in a stack, so every chunk below the top non-empty one must be sent.
*/ */
public static function getSubChunkCount(Chunk $chunk) : int{ public static function getSubChunkCount(Chunk $chunk) : int{
for($count = count($chunk->getSubChunks()); $count > 0; --$count){ for($y = Chunk::MAX_SUBCHUNK_INDEX, $count = count($chunk->getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){
if($chunk->getSubChunk($count - 1)->isEmptyFast()){ if($chunk->getSubChunk($y)->isEmptyFast()){
continue; continue;
} }
return $count; return $count;
@ -59,7 +59,7 @@ final class ChunkSerializer{
public static function serializeFullChunk(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ public static function serializeFullChunk(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
$stream = PacketSerializer::encoder($encoderContext); $stream = PacketSerializer::encoder($encoderContext);
$subChunkCount = self::getSubChunkCount($chunk); $subChunkCount = self::getSubChunkCount($chunk);
for($y = 0; $y < $subChunkCount; ++$y){ for($y = Chunk::MIN_SUBCHUNK_INDEX, $writtenCount = 0; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){
self::serializeSubChunk($chunk->getSubChunk($y), $blockMapper, $stream, false); self::serializeSubChunk($chunk->getSubChunk($y), $blockMapper, $stream, false);
} }
$stream->put($chunk->getBiomeIdArray()); $stream->put($chunk->getBiomeIdArray());

View File

@ -34,11 +34,14 @@ use function socket_recvfrom;
use function socket_select; use function socket_select;
use function socket_sendto; use function socket_sendto;
use function socket_set_nonblock; use function socket_set_nonblock;
use function socket_set_option;
use function socket_strerror; use function socket_strerror;
use function strlen; use function strlen;
use function time; use function time;
use function trim; use function trim;
use const AF_INET; use const AF_INET;
use const IPPROTO_IPV6;
use const IPV6_V6ONLY;
use const PHP_INT_MAX; use const PHP_INT_MAX;
use const SOCK_DGRAM; use const SOCK_DGRAM;
use const SOCKET_EADDRINUSE; use const SOCKET_EADDRINUSE;
@ -74,15 +77,18 @@ final class DedicatedQueryNetworkInterface implements AdvancedNetworkInterface{
/** @var string[] */ /** @var string[] */
private $rawPacketPatterns = []; private $rawPacketPatterns = [];
public function __construct(string $ip, int $port, \Logger $logger){ public function __construct(string $ip, int $port, bool $ipV6, \Logger $logger){
$this->ip = $ip; $this->ip = $ip;
$this->port = $port; $this->port = $port;
$this->logger = $logger; $this->logger = $logger;
$socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); $socket = @socket_create($ipV6 ? AF_INET6 : AF_INET, SOCK_DGRAM, SOL_UDP);
if($socket === false){ if($socket === false){
throw new \RuntimeException("Failed to create socket"); throw new \RuntimeException("Failed to create socket");
} }
if($ipV6){
socket_set_option($socket, IPPROTO_IPV6, IPV6_V6ONLY, 1); //disable linux's cool but annoying ipv4-over-ipv6 network stack
}
$this->socket = $socket; $this->socket = $socket;
} }

View File

@ -27,7 +27,6 @@ declare(strict_types=1);
*/ */
namespace pocketmine\network\query; namespace pocketmine\network\query;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\network\AdvancedNetworkInterface; use pocketmine\network\AdvancedNetworkInterface;
use pocketmine\network\RawPacketHandler; use pocketmine\network\RawPacketHandler;
use pocketmine\Server; use pocketmine\Server;
@ -57,8 +56,6 @@ class QueryHandler implements RawPacketHandler{
public function __construct(Server $server){ public function __construct(Server $server){
$this->server = $server; $this->server = $server;
$this->logger = new \PrefixedLogger($this->server->getLogger(), "Query Handler"); $this->logger = new \PrefixedLogger($this->server->getLogger(), "Query Handler");
$addr = $this->server->getIp();
$port = $this->server->getPort();
/* /*
The Query protocol is built on top of the existing Minecraft PE UDP network stack. The Query protocol is built on top of the existing Minecraft PE UDP network stack.
@ -71,7 +68,6 @@ class QueryHandler implements RawPacketHandler{
$this->regenerateToken(); $this->regenerateToken();
$this->lastToken = $this->token; $this->lastToken = $this->token;
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($addr, (string) $port)));
} }
public function getPattern() : string{ public function getPattern() : string{

View File

@ -233,7 +233,7 @@ final class QueryInfo{
$query .= $key . "\x00" . $value . "\x00"; $query .= $key . "\x00" . $value . "\x00";
} }
foreach($this->extraData as $key => $value){ foreach(Utils::stringifyKeys($this->extraData) as $key => $value){
$query .= $key . "\x00" . $value . "\x00"; $query .= $key . "\x00" . $value . "\x00";
} }

View File

@ -27,6 +27,7 @@ use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException; use pocketmine\plugin\PluginException;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\ObjectSet; use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils;
use function count; use function count;
use function spl_object_id; use function spl_object_id;
@ -143,7 +144,7 @@ class PermissibleInternal implements Permissible{
$oldPermissions = $this->permissions; $oldPermissions = $this->permissions;
$this->permissions = []; $this->permissions = [];
foreach($this->rootPermissions as $name => $isGranted){ foreach(Utils::stringifyKeys($this->rootPermissions) as $name => $isGranted){
$perm = $permManager->getPermission($name); $perm = $permManager->getPermission($name);
if($perm === null){ if($perm === null){
throw new \LogicException("Unregistered root permission $name"); throw new \LogicException("Unregistered root permission $name");
@ -187,11 +188,12 @@ class PermissibleInternal implements Permissible{
} }
/** /**
* @param bool[] $children * @param bool[] $children
* @phpstan-param array<string, bool> $children
*/ */
private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment, ?PermissionAttachmentInfo $parent) : void{ private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment, ?PermissionAttachmentInfo $parent) : void{
$permManager = PermissionManager::getInstance(); $permManager = PermissionManager::getInstance();
foreach($children as $name => $v){ foreach(Utils::stringifyKeys($children) as $name => $v){
$perm = $permManager->getPermission($name); $perm = $permManager->getPermission($name);
$value = ($v xor $invert); $value = ($v xor $invert);
$this->permissions[$name] = new PermissionAttachmentInfo($name, $attachment, $value, $parent); $this->permissions[$name] = new PermissionAttachmentInfo($name, $attachment, $value, $parent);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\permission; namespace pocketmine\permission;
use pocketmine\utils\Utils;
use function is_bool; use function is_bool;
use function strtolower; use function strtolower;
@ -83,7 +84,7 @@ class PermissionParser{
*/ */
public static function loadPermissions(array $data, string $default = self::DEFAULT_FALSE) : array{ public static function loadPermissions(array $data, string $default = self::DEFAULT_FALSE) : array{
$result = []; $result = [];
foreach($data as $name => $entry){ foreach(Utils::stringifyKeys($data) as $name => $entry){
$desc = null; $desc = null;
if(isset($entry["default"])){ if(isset($entry["default"])){
$default = PermissionParser::defaultFromString($entry["default"]); $default = PermissionParser::defaultFromString($entry["default"]);

View File

@ -75,7 +75,9 @@ use pocketmine\form\Form;
use pocketmine\form\FormValidationException; use pocketmine\form\FormValidationException;
use pocketmine\inventory\CallbackInventoryListener; use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory; use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCraftingInventory;
use pocketmine\inventory\PlayerCursorInventory; use pocketmine\inventory\PlayerCursorInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilderInventory; use pocketmine\inventory\transaction\TransactionBuilderInventory;
@ -182,7 +184,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** @var Inventory[] */ /** @var Inventory[] */
protected array $permanentWindows = []; protected array $permanentWindows = [];
protected PlayerCursorInventory $cursorInventory; protected PlayerCursorInventory $cursorInventory;
protected CraftingGrid $craftingGrid; protected PlayerCraftingInventory $craftingGrid;
protected int $messageCounter = 2; protected int $messageCounter = 2;
@ -1314,7 +1316,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* as a command. * as a command.
*/ */
public function chat(string $message) : bool{ public function chat(string $message) : bool{
$this->doCloseInventory(); $this->removeCurrentWindow();
$message = TextFormat::clean($message, false); $message = TextFormat::clean($message, false);
foreach(explode("\n", $message) as $messagePart){ foreach(explode("\n", $message) as $messagePart){
@ -1570,7 +1572,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @return bool if the block was successfully broken, false if a rollback needs to take place. * @return bool if the block was successfully broken, false if a rollback needs to take place.
*/ */
public function breakBlock(Vector3 $pos) : bool{ public function breakBlock(Vector3 $pos) : bool{
$this->doCloseInventory(); $this->removeCurrentWindow();
if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7)){ if($this->canInteract($pos->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 7)){
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
@ -1989,7 +1991,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//prevent the player receiving their own disconnect message //prevent the player receiving their own disconnect message
$this->server->unsubscribeFromAllBroadcastChannels($this); $this->server->unsubscribeFromAllBroadcastChannels($this);
$this->doCloseInventory(); $this->removeCurrentWindow();
$ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason); $ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason);
$ev->call(); $ev->call();
@ -2102,7 +2104,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected function onDeath() : void{ protected function onDeath() : void{
//Crafting grid must always be evacuated even if keep-inventory is true. This dumps the contents into the //Crafting grid must always be evacuated even if keep-inventory is true. This dumps the contents into the
//main inventory and drops the rest on the ground. //main inventory and drops the rest on the ground.
$this->doCloseInventory(); $this->removeCurrentWindow();
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null); $ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
$ev->call(); $ev->call();
@ -2301,7 +2303,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected function addDefaultWindows() : void{ protected function addDefaultWindows() : void{
$this->cursorInventory = new PlayerCursorInventory($this); $this->cursorInventory = new PlayerCursorInventory($this);
$this->craftingGrid = new CraftingGrid($this, CraftingGrid::SIZE_SMALL); $this->craftingGrid = new PlayerCraftingInventory($this);
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory); $this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory);
@ -2316,17 +2318,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return $this->craftingGrid; return $this->craftingGrid;
} }
public function setCraftingGrid(CraftingGrid $grid) : void{
$this->craftingGrid = $grid;
}
/** /**
* @internal Called to clean up crafting grid and cursor inventory when it is detected that the player closed their * @internal Called to clean up crafting grid and cursor inventory when it is detected that the player closed their
* inventory. * inventory.
*/ */
public function doCloseInventory() : void{ private function doCloseInventory() : void{
/** @var Inventory[] $inventories */
$inventories = [$this->craftingGrid, $this->cursorInventory]; $inventories = [$this->craftingGrid, $this->cursorInventory];
if($this->currentWindow instanceof TemporaryInventory){
$inventories[] = $this->currentWindow;
}
$transaction = new InventoryTransaction($this); $transaction = new InventoryTransaction($this);
$mainInventoryTransactionBuilder = new TransactionBuilderInventory($this->inventory); $mainInventoryTransactionBuilder = new TransactionBuilderInventory($this->inventory);
@ -2363,10 +2363,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
throw new AssumptionFailedError("This server-generated transaction should never be invalid", 0, $e); throw new AssumptionFailedError("This server-generated transaction should never be invalid", 0, $e);
} }
} }
if($this->craftingGrid->getGridWidth() > CraftingGrid::SIZE_SMALL){
$this->craftingGrid = new CraftingGrid($this, CraftingGrid::SIZE_SMALL);
}
} }
/** /**
@ -2403,6 +2399,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
public function removeCurrentWindow() : void{ public function removeCurrentWindow() : void{
$this->doCloseInventory();
if($this->currentWindow !== null){ if($this->currentWindow !== null){
(new InventoryCloseEvent($this->currentWindow, $this))->call(); (new InventoryCloseEvent($this->currentWindow, $this))->call();

View File

@ -32,6 +32,7 @@ use pocketmine\scheduler\TaskScheduler;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Config; use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function count; use function count;
use function dirname; use function dirname;
@ -162,9 +163,9 @@ abstract class PluginBase implements Plugin, CommandExecutor{
private function registerYamlCommands() : void{ private function registerYamlCommands() : void{
$pluginCmds = []; $pluginCmds = [];
foreach($this->getDescription()->getCommands() as $key => $data){ foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
if(strpos($key, ":") !== false){ if(strpos($key, ":") !== false){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName()))); $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->getDescription()->getFullName(), ":")));
continue; continue;
} }
@ -180,7 +181,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$aliasList = []; $aliasList = [];
foreach($data->getAliases() as $alias){ foreach($data->getAliases() as $alias){
if(strpos($alias, ":") !== false){ if(strpos($alias, ":") !== false){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName()))); $this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_aliasError($alias, $this->getDescription()->getFullName(), ":")));
continue; continue;
} }
$aliasList[] = $alias; $aliasList[] = $alias;

View File

@ -76,7 +76,7 @@ final class PluginLoadabilityChecker{
} }
} }
foreach($description->getRequiredExtensions() as $extensionName => $versionConstrs){ foreach(Utils::stringifyKeys($description->getRequiredExtensions()) as $extensionName => $versionConstrs){
$gotVersion = phpversion($extensionName); $gotVersion = phpversion($extensionName);
if($gotVersion === false){ if($gotVersion === false){
return KnownTranslationFactory::pocketmine_plugin_extensionNotLoaded($extensionName); return KnownTranslationFactory::pocketmine_plugin_extensionNotLoaded($extensionName);

View File

@ -168,9 +168,20 @@ class PluginManager{
} }
$permManager = PermissionManager::getInstance(); $permManager = PermissionManager::getInstance();
foreach($description->getPermissions() as $permsGroup){
foreach($permsGroup as $perm){
if($permManager->getPermission($perm->getName()) !== null){
$this->server->getLogger()->error($language->translate(KnownTranslationFactory::pocketmine_plugin_loadError(
$description->getName(),
KnownTranslationFactory::pocketmine_plugin_duplicatePermissionError($perm->getName())
)));
return null;
}
}
}
$opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR); $opRoot = $permManager->getPermission(DefaultPermissions::ROOT_OPERATOR);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER); $everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){ foreach(Utils::stringifyKeys($description->getPermissions()) as $default => $perms){
foreach($perms as $perm){ foreach($perms as $perm){
$permManager->addPermission($perm); $permManager->addPermission($perm);
switch($default){ switch($default){
@ -334,7 +345,7 @@ class PluginManager{
while(count($triage->plugins) > 0){ while(count($triage->plugins) > 0){
$loadedThisLoop = 0; $loadedThisLoop = 0;
foreach($triage->plugins as $name => $entry){ foreach(Utils::stringifyKeys($triage->plugins) as $name => $entry){
$this->checkDepsForTriage($name, "hard", $triage->dependencies, $loadedPlugins, $triage); $this->checkDepsForTriage($name, "hard", $triage->dependencies, $loadedPlugins, $triage);
$this->checkDepsForTriage($name, "soft", $triage->softDependencies, $loadedPlugins, $triage); $this->checkDepsForTriage($name, "soft", $triage->softDependencies, $loadedPlugins, $triage);
@ -366,7 +377,7 @@ class PluginManager{
//No plugins loaded :( //No plugins loaded :(
//check for skippable soft dependencies first, in case the dependents could resolve hard dependencies //check for skippable soft dependencies first, in case the dependents could resolve hard dependencies
foreach($triage->plugins as $name => $file){ foreach(Utils::stringifyKeys($triage->plugins) as $name => $file){
if(isset($triage->softDependencies[$name]) && !isset($triage->dependencies[$name])){ if(isset($triage->softDependencies[$name]) && !isset($triage->dependencies[$name])){
foreach($triage->softDependencies[$name] as $k => $dependency){ foreach($triage->softDependencies[$name] as $k => $dependency){
if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){ if($this->getPlugin($dependency) === null && !array_key_exists($dependency, $triage->plugins)){
@ -381,7 +392,7 @@ class PluginManager{
} }
} }
foreach($triage->plugins as $name => $file){ foreach(Utils::stringifyKeys($triage->plugins) as $name => $file){
if(isset($triage->dependencies[$name])){ if(isset($triage->dependencies[$name])){
$unknownDependencies = []; $unknownDependencies = [];
@ -404,7 +415,7 @@ class PluginManager{
} }
} }
foreach($triage->plugins as $name => $file){ foreach(Utils::stringifyKeys($triage->plugins) as $name => $file){
$this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, KnownTranslationFactory::pocketmine_plugin_circularDependency()))); $this->server->getLogger()->critical($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_loadError($name, KnownTranslationFactory::pocketmine_plugin_circularDependency())));
} }
break; break;

View File

@ -23,13 +23,14 @@ declare(strict_types=1);
namespace pocketmine\plugin; namespace pocketmine\plugin;
use pocketmine\utils\Utils;
use function count;
use function file; use function file;
use function implode;
use function is_file; use function is_file;
use function preg_match;
use function strlen; use function strlen;
use function strpos; use function strpos;
use function substr; use function substr;
use function trim;
use const FILE_IGNORE_NEW_LINES; use const FILE_IGNORE_NEW_LINES;
use const FILE_SKIP_EMPTY_LINES; use const FILE_SKIP_EMPTY_LINES;
@ -60,30 +61,27 @@ class ScriptPluginLoader implements PluginLoader{
return null; return null;
} }
$data = [];
$insideHeader = false; $insideHeader = false;
$docCommentLines = [];
foreach($content as $line){ foreach($content as $line){
if(!$insideHeader and strpos($line, "/**") !== false){ if(!$insideHeader){
$insideHeader = true; if(strpos($line, "/**") !== false){
} $insideHeader = true;
}else{
if(preg_match("/^[ \t]+\\*[ \t]+@([a-zA-Z]+)([ \t]+(.*))?$/", $line, $matches) > 0){ continue;
$key = $matches[1];
$content = trim($matches[3] ?? "");
if($key === "notscript"){
return null;
} }
$data[$key] = $content;
} }
if($insideHeader and strpos($line, "*/") !== false){ $docCommentLines[] = $line;
if(strpos($line, "*/") !== false){
break; break;
} }
} }
if($insideHeader){
$data = Utils::parseDocComment(implode("\n", $docCommentLines));
if(count($data) !== 0){
return new PluginDescription($data); return new PluginDescription($data);
} }

View File

@ -123,9 +123,7 @@ class SendUsageTask extends AsyncTask{
]; ];
//This anonymizes the user ids so they cannot be reversed to the original //This anonymizes the user ids so they cannot be reversed to the original
foreach($playerList as $k => $v){ $playerList = array_map('md5', $playerList);
$playerList[$k] = md5($v);
}
$players = array_map(function(Player $p) : string{ return md5($p->getUniqueId()->getBytes()); }, $server->getOnlinePlayers()); $players = array_map(function(Player $p) : string{ return md5($p->getUniqueId()->getBytes()); }, $server->getOnlinePlayers());

View File

@ -425,7 +425,7 @@ class Config{
public function set($k, $v = true) : void{ public function set($k, $v = true) : void{
$this->config[$k] = $v; $this->config[$k] = $v;
$this->changed = true; $this->changed = true;
foreach($this->nestedCache as $nestedKey => $nvalue){ foreach(Utils::stringifyKeys($this->nestedCache) as $nestedKey => $nvalue){
if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){ if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){
unset($this->nestedCache[$nestedKey]); unset($this->nestedCache[$nestedKey]);
} }
@ -487,7 +487,7 @@ class Config{
*/ */
private function fillDefaults(array $default, &$data) : int{ private function fillDefaults(array $default, &$data) : int{
$changed = 0; $changed = 0;
foreach($default as $k => $v){ foreach(Utils::stringifyKeys($default) as $k => $v){
if(is_array($v)){ if(is_array($v)){
if(!isset($data[$k]) or !is_array($data[$k])){ if(!isset($data[$k]) or !is_array($data[$k])){
$data[$k] = []; $data[$k] = [];
@ -536,7 +536,7 @@ class Config{
*/ */
public static function writeProperties(array $config) : string{ public static function writeProperties(array $config) : string{
$content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n"; $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n";
foreach($config as $k => $v){ foreach(Utils::stringifyKeys($config) as $k => $v){
if(is_bool($v)){ if(is_bool($v)){
$v = $v ? "on" : "off"; $v = $v ? "on" : "off";
} }

View File

@ -163,7 +163,8 @@ final class Filesystem{
$result = str_replace([DIRECTORY_SEPARATOR, ".php", "phar://"], ["/", "", ""], $path); $result = str_replace([DIRECTORY_SEPARATOR, ".php", "phar://"], ["/", "", ""], $path);
//remove relative paths //remove relative paths
foreach(self::$cleanedPaths as $cleanPath => $replacement){ //this should probably never have integer keys, but it's safer than making PHPStan ignore it
foreach(Utils::stringifyKeys(self::$cleanedPaths) as $cleanPath => $replacement){
$cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/"); $cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/");
if(strpos($result, $cleanPath) === 0){ if(strpos($result, $cleanPath) === 0){
$result = ltrim(str_replace($cleanPath, $replacement, $result), "/"); $result = ltrim(str_replace($cleanPath, $replacement, $result), "/");

View File

@ -0,0 +1,82 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function array_keys;
use function str_replace;
use function strtolower;
use function trim;
/**
* Handles parsing any Minecraft thing from strings. This can be used, for example, to implement a user-friendly item
* parser to be used by the /give command (and others).
* Custom aliases may be registered.
* Note that the aliases should be user-friendly, i.e. easily readable and writable.
*
* @phpstan-template T
*/
abstract class StringToTParser{
/**
* @var \Closure[]
* @phpstan-var array<string, \Closure(string $input) : T>
*/
private array $callbackMap = [];
/** @phpstan-param \Closure(string $input) : T $callback */
public function register(string $alias, \Closure $callback) : void{
$key = $this->reprocess($alias);
if(isset($this->callbackMap[$key])){
throw new \InvalidArgumentException("Alias \"$key\" is already registered");
}
$this->callbackMap[$key] = $callback;
}
/** @phpstan-param \Closure(string $input) : T $callback */
public function override(string $alias, \Closure $callback) : void{
$this->callbackMap[$this->reprocess($alias)] = $callback;
}
/**
* Tries to parse the specified string into an enchantment.
* @phpstan-return T|null
*/
public function parse(string $input){
$key = $this->reprocess($input);
if(isset($this->callbackMap[$key])){
return ($this->callbackMap[$key])($input);
}
return null;
}
protected function reprocess(string $input) : string{
return strtolower(str_replace([" ", "minecraft:"], ["_", ""], trim($input)));
}
/** @return string[]|int[] */
public function getKnownAliases() : array{
return array_keys($this->callbackMap);
}
}

View File

@ -506,7 +506,7 @@ final class Utils{
if($rawDocComment === false){ //usually empty doc comment, but this is safer and statically analysable if($rawDocComment === false){ //usually empty doc comment, but this is safer and statically analysable
return []; return [];
} }
preg_match_all('/(*ANYCRLF)^[\t ]*(?:\* )?@([a-zA-Z]+)(?:[\t ]+(.+?))?[\t ]*$/m', $rawDocComment, $matches); preg_match_all('/(*ANYCRLF)^[\t ]*(?:\* )?@([a-zA-Z\-]+)(?:[\t ]+(.+?))?[\t ]*$/m', $rawDocComment, $matches);
return array_combine($matches[1], $matches[2]); return array_combine($matches[1], $matches[2]);
} }
@ -571,6 +571,22 @@ final class Utils{
} }
} }
/**
* Generator which forces array keys to string during iteration.
* This is necessary because PHP has an anti-feature where it casts numeric string keys to integers, leading to
* various crashes.
*
* @phpstan-template TKeyType of string
* @phpstan-template TValueType
* @phpstan-param array<TKeyType, TValueType> $array
* @phpstan-return \Generator<TKeyType, TValueType, void, void>
*/
public static function stringifyKeys(array $array) : \Generator{
foreach($array as $key => $value){ // @phpstan-ignore-line - this is where we fix the stupid bullshit with array keys :)
yield (string) $key => $value;
}
}
public static function checkUTF8(string $string) : void{ public static function checkUTF8(string $string) : void{
if(!mb_check_encoding($string, 'UTF-8')){ if(!mb_check_encoding($string, 'UTF-8')){
throw new \InvalidArgumentException("Text must be valid UTF-8"); throw new \InvalidArgumentException("Text must be valid UTF-8");

View File

@ -35,6 +35,7 @@ use pocketmine\player\GameMode;
use pocketmine\utils\Config; use pocketmine\utils\Config;
use pocketmine\utils\Internet; use pocketmine\utils\Internet;
use pocketmine\utils\InternetException; use pocketmine\utils\InternetException;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo; use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function fgets; use function fgets;
@ -69,7 +70,7 @@ class SetupWizard{
} }
$this->message("Please select a language"); $this->message("Please select a language");
foreach($langs as $short => $native){ foreach(Utils::stringifyKeys($langs) as $short => $native){
$this->writeLine(" $native => $short"); $this->writeLine(" $native => $short");
} }

View File

@ -134,8 +134,7 @@ class Explosion{
$blastForce -= ($blastResistance / 5 + 0.3) * $this->stepLen; $blastForce -= ($blastResistance / 5 + 0.3) * $this->stepLen;
if($blastForce > 0){ if($blastForce > 0){
if(!isset($this->affectedBlocks[World::blockHash($vBlockX, $vBlockY, $vBlockZ)])){ if(!isset($this->affectedBlocks[World::blockHash($vBlockX, $vBlockY, $vBlockZ)])){
$_block = $blockFactory->fromFullBlock($state); $_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false);
$_block->position($this->world, $vBlockX, $vBlockY, $vBlockZ);
foreach($_block->getAffectedBlocks() as $_affectedBlock){ foreach($_block->getAffectedBlocks() as $_affectedBlock){
$_affectedBlockPos = $_affectedBlock->getPosition(); $_affectedBlockPos = $_affectedBlock->getPosition();
$this->affectedBlocks[World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z)] = $_affectedBlock; $this->affectedBlocks[World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z)] = $_affectedBlock;

View File

@ -98,7 +98,7 @@ class Position extends Vector3{
} }
public function __toString(){ public function __toString(){
return "Position(level=" . ($this->isValid() ? $this->getWorld()->getDisplayName() : "null") . ",x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")"; return "Position(world=" . ($this->isValid() ? $this->getWorld()->getDisplayName() : "null") . ",x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")";
} }
public function equals(Vector3 $v) : bool{ public function equals(Vector3 $v) : bool{

View File

@ -36,6 +36,7 @@ use pocketmine\block\tile\TileFactory;
use pocketmine\block\UnknownBlock; use pocketmine\block\UnknownBlock;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\data\bedrock\BiomeIds; use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\EntityFactory; use pocketmine\entity\EntityFactory;
use pocketmine\entity\Location; use pocketmine\entity\Location;
@ -57,7 +58,6 @@ use pocketmine\item\LegacyStringToItemParser;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
@ -331,7 +331,7 @@ class World implements ChunkManager{
private const BLOCKHASH_Y_OFFSET = self::BLOCKHASH_Y_PADDING - self::Y_MIN; private const BLOCKHASH_Y_OFFSET = self::BLOCKHASH_Y_PADDING - self::Y_MIN;
private const BLOCKHASH_Y_MASK = (1 << self::BLOCKHASH_Y_BITS) - 1; private const BLOCKHASH_Y_MASK = (1 << self::BLOCKHASH_Y_BITS) - 1;
private const BLOCKHASH_XZ_MASK = (1 << self::MORTON3D_BIT_SIZE) - 1; private const BLOCKHASH_XZ_MASK = (1 << self::MORTON3D_BIT_SIZE) - 1;
private const BLOCKHASH_XZ_EXTRA_BITS = 6; private const BLOCKHASH_XZ_EXTRA_BITS = (self::MORTON3D_BIT_SIZE - self::BLOCKHASH_Y_BITS) >> 1;
private const BLOCKHASH_XZ_EXTRA_MASK = (1 << self::BLOCKHASH_XZ_EXTRA_BITS) - 1; private const BLOCKHASH_XZ_EXTRA_MASK = (1 << self::BLOCKHASH_XZ_EXTRA_BITS) - 1;
private const BLOCKHASH_XZ_SIGN_SHIFT = 64 - self::MORTON3D_BIT_SIZE - self::BLOCKHASH_XZ_EXTRA_BITS; private const BLOCKHASH_XZ_SIGN_SHIFT = 64 - self::MORTON3D_BIT_SIZE - self::BLOCKHASH_XZ_EXTRA_BITS;
private const BLOCKHASH_X_SHIFT = self::BLOCKHASH_Y_BITS; private const BLOCKHASH_X_SHIFT = self::BLOCKHASH_Y_BITS;
@ -2466,13 +2466,34 @@ class World implements ChunkManager{
private function initChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{ private function initChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{
$logger = new \PrefixedLogger($this->logger, "Loading chunk $chunkX $chunkZ"); $logger = new \PrefixedLogger($this->logger, "Loading chunk $chunkX $chunkZ");
$this->timings->syncChunkLoadFixInvalidBlocks->startTiming();
$blockFactory = BlockFactory::getInstance();
$invalidBlocks = 0;
foreach($chunkData->getChunk()->getSubChunks() as $subChunk){
foreach($subChunk->getBlockLayers() as $blockLayer){
foreach($blockLayer->getPalette() as $blockStateId){
$mappedStateId = $blockFactory->getMappedStateId($blockStateId);
if($mappedStateId !== $blockStateId){
$blockLayer->replaceAll($blockStateId, $mappedStateId);
$invalidBlocks++;
}
}
}
}
if($invalidBlocks > 0){
$logger->debug("Fixed $invalidBlocks invalid blockstates");
$chunkData->getChunk()->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS, true);
}
$this->timings->syncChunkLoadFixInvalidBlocks->stopTiming();
if(count($chunkData->getEntityNBT()) !== 0){ if(count($chunkData->getEntityNBT()) !== 0){
$this->timings->syncChunkLoadEntities->startTiming(); $this->timings->syncChunkLoadEntities->startTiming();
$entityFactory = EntityFactory::getInstance(); $entityFactory = EntityFactory::getInstance();
foreach($chunkData->getEntityNBT() as $k => $nbt){ foreach($chunkData->getEntityNBT() as $k => $nbt){
try{ try{
$entity = $entityFactory->createFromData($this, $nbt); $entity = $entityFactory->createFromData($this, $nbt);
}catch(NbtDataException $e){ }catch(SavedDataLoadingException $e){
$logger->error("Bad entity data at list position $k: " . $e->getMessage()); $logger->error("Bad entity data at list position $k: " . $e->getMessage());
$logger->logException($e); $logger->logException($e);
continue; continue;
@ -2500,7 +2521,7 @@ class World implements ChunkManager{
foreach($chunkData->getTileNBT() as $k => $nbt){ foreach($chunkData->getTileNBT() as $k => $nbt){
try{ try{
$tile = $tileFactory->createFromData($this, $nbt); $tile = $tileFactory->createFromData($this, $nbt);
}catch(NbtDataException $e){ }catch(SavedDataLoadingException $e){
$logger->error("Bad tile entity data at list position $k: " . $e->getMessage()); $logger->error("Bad tile entity data at list position $k: " . $e->getMessage());
$logger->logException($e); $logger->logException($e);
continue; continue;

View File

@ -45,6 +45,7 @@ class WorldTimings{
public TimingsHandler $syncChunkLoad; public TimingsHandler $syncChunkLoad;
public TimingsHandler $syncChunkLoadData; public TimingsHandler $syncChunkLoadData;
public TimingsHandler $syncChunkLoadFixInvalidBlocks;
public TimingsHandler $syncChunkLoadEntities; public TimingsHandler $syncChunkLoadEntities;
public TimingsHandler $syncChunkLoadTileEntities; public TimingsHandler $syncChunkLoadTileEntities;
public TimingsHandler $syncChunkSave; public TimingsHandler $syncChunkSave;
@ -64,14 +65,15 @@ class WorldTimings{
$this->entityTick = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Tick Entities"); $this->entityTick = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Tick Entities");
Timings::init(); //make sure the timers we want are available Timings::init(); //make sure the timers we want are available
$this->syncChunkSend = new TimingsHandler("** " . $name . "Player Send Chunks", Timings::$playerChunkSend); $this->syncChunkSend = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunks", Timings::$playerChunkSend);
$this->syncChunkSendPrepare = new TimingsHandler("** " . $name . "Player Send Chunk Prepare", Timings::$playerChunkSend); $this->syncChunkSendPrepare = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunk Prepare", Timings::$playerChunkSend);
$this->syncChunkLoad = new TimingsHandler("** " . $name . "Chunk Load", Timings::$worldLoad); $this->syncChunkLoad = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load", Timings::$worldLoad);
$this->syncChunkLoadData = new TimingsHandler("** " . $name . "Chunk Load - Data"); $this->syncChunkLoadData = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Data");
$this->syncChunkLoadEntities = new TimingsHandler("** " . $name . "Chunk Load - Entities"); $this->syncChunkLoadFixInvalidBlocks = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Fix Invalid Blocks");
$this->syncChunkLoadTileEntities = new TimingsHandler("** " . $name . "Chunk Load - TileEntities"); $this->syncChunkLoadEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Entities");
$this->syncChunkSave = new TimingsHandler("** " . $name . "Chunk Save", Timings::$worldSave); $this->syncChunkLoadTileEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - TileEntities");
$this->syncChunkSave = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Save", Timings::$worldSave);
$this->doTick = new TimingsHandler($name . "World Tick"); $this->doTick = new TimingsHandler($name . "World Tick");
} }

View File

@ -35,7 +35,9 @@ class Chunk{
public const DIRTY_FLAG_BLOCKS = 1 << 0; public const DIRTY_FLAG_BLOCKS = 1 << 0;
public const DIRTY_FLAG_BIOMES = 1 << 3; public const DIRTY_FLAG_BIOMES = 1 << 3;
public const MAX_SUBCHUNKS = 16; public const MIN_SUBCHUNK_INDEX = 0;
public const MAX_SUBCHUNK_INDEX = 15;
public const MAX_SUBCHUNKS = self::MAX_SUBCHUNK_INDEX - self::MIN_SUBCHUNK_INDEX + 1;
public const EDGE_LENGTH = SubChunk::EDGE_LENGTH; public const EDGE_LENGTH = SubChunk::EDGE_LENGTH;
public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE; public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE;
@ -71,10 +73,10 @@ class Chunk{
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS); $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
foreach($this->subChunks as $y => $null){ foreach($this->subChunks as $y => $null){
$this->subChunks[$y] = $subChunks[$y] ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []);
} }
$val = ($this->subChunks->getSize() * SubChunk::EDGE_LENGTH); $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH;
$this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this? $this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this?
$this->biomeIds = $biomeIds; $this->biomeIds = $biomeIds;
@ -118,7 +120,7 @@ class Chunk{
* @return int|null 0-255, or null if there are no blocks in the column * @return int|null 0-255, or null if there are no blocks in the column
*/ */
public function getHighestBlockAt(int $x, int $z) : ?int{ public function getHighestBlockAt(int $x, int $z) : ?int{
for($y = $this->subChunks->count() - 1; $y >= 0; --$y){ for($y = self::MAX_SUBCHUNK_INDEX; $y >= self::MIN_SUBCHUNK_INDEX; --$y){
$height = $this->getSubChunk($y)->getHighestBlockAt($x, $z); $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z);
if($height !== null){ if($height !== null){
return $height | ($y << SubChunk::COORD_BIT_SIZE); return $height | ($y << SubChunk::COORD_BIT_SIZE);
@ -280,21 +282,21 @@ class Chunk{
} }
public function getSubChunk(int $y) : SubChunk{ public function getSubChunk(int $y) : SubChunk{
if($y < 0 || $y >= $this->subChunks->getSize()){ if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y"); throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");
} }
return $this->subChunks[$y]; return $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX];
} }
/** /**
* Sets a subchunk in the chunk index * Sets a subchunk in the chunk index
*/ */
public function setSubChunk(int $y, ?SubChunk $subChunk) : void{ public function setSubChunk(int $y, ?SubChunk $subChunk) : void{
if($y < 0 or $y >= $this->subChunks->getSize()){ if($y < self::MIN_SUBCHUNK_INDEX or $y > self::MAX_SUBCHUNK_INDEX){
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y"); throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");
} }
$this->subChunks[$y] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []);
$this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true); $this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true);
} }
@ -303,7 +305,11 @@ class Chunk{
* @phpstan-return array<int, SubChunk> * @phpstan-return array<int, SubChunk>
*/ */
public function getSubChunks() : array{ public function getSubChunks() : array{
return $this->subChunks->toArray(); $result = [];
foreach($this->subChunks as $yOffset => $subChunk){
$result[$yOffset + self::MIN_SUBCHUNK_INDEX] = $subChunk;
}
return $result;
} }
/** /**

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\world\format\io; namespace pocketmine\world\format\io;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream; use pocketmine\utils\BinaryStream;
use pocketmine\world\format\BiomeArray; use pocketmine\world\format\BiomeArray;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
@ -96,7 +97,7 @@ final class FastChunkSerializer{
$count = $stream->getByte(); $count = $stream->getByte();
for($subCount = 0; $subCount < $count; ++$subCount){ for($subCount = 0; $subCount < $count; ++$subCount){
$y = $stream->getByte(); $y = Binary::signByte($stream->getByte());
$airBlockId = $stream->getInt(); $airBlockId = $stream->getInt();
/** @var PalettedBlockArray[] $layers */ /** @var PalettedBlockArray[] $layers */

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\world\format\io; namespace pocketmine\world\format\io;
use pocketmine\utils\Utils;
use pocketmine\world\format\io\leveldb\LevelDB; use pocketmine\world\format\io\leveldb\LevelDB;
use pocketmine\world\format\io\region\Anvil; use pocketmine\world\format\io\region\Anvil;
use pocketmine\world\format\io\region\McRegion; use pocketmine\world\format\io\region\McRegion;
@ -77,7 +78,7 @@ final class WorldProviderManager{
*/ */
public function getMatchingProviders(string $path) : array{ public function getMatchingProviders(string $path) : array{
$result = []; $result = [];
foreach($this->providers as $alias => $providerEntry){ foreach(Utils::stringifyKeys($this->providers) as $alias => $providerEntry){
if($providerEntry->isValid($path)){ if($providerEntry->isValid($path)){
$result[$alias] = $providerEntry; $result[$alias] = $providerEntry;
} }

View File

@ -266,7 +266,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
case 3: //MCPE 1.0 case 3: //MCPE 1.0
$convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion); $convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion);
for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){ for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){ if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){
continue; continue;
} }

View File

@ -51,7 +51,7 @@ trait LegacyAnvilChunkTrait{
/** /**
* @throws CorruptedChunkException * @throws CorruptedChunkException
*/ */
protected function deserializeChunk(string $data) : ChunkData{ protected function deserializeChunk(string $data) : ?ChunkData{
$decompressed = @zlib_decode($data); $decompressed = @zlib_decode($data);
if($decompressed === false){ if($decompressed === false){
throw new CorruptedChunkException("Failed to decompress chunk NBT"); throw new CorruptedChunkException("Failed to decompress chunk NBT");

Some files were not shown because too many files have changed in this diff Show More