Compare commits

..

127 Commits

Author SHA1 Message Date
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
002feacf8e Release 4.0.0-BETA11 2021-11-06 01:16:58 +00:00
b8523f7a18 Player: fix the fix which just degraded performance
if a chunk was requested for generation, count++ and count(activeRequests)++, which means that we would only get to submit half as many generation requests as we're allowed to.
Calculate the limit at the start and remember it instead.
2021-11-06 01:00:35 +00:00
640e88009b Player: fixed a mistake in generation rate limit
we don't want to allow sending further chunks when we haven't generated near ones, because we won't be able to see them anyway, and we might end up not needing them.
This now fully matches the results of PM3.
2021-11-06 00:57:37 +00:00
6cd272c9e1 Update transient composer dependencies 2021-11-06 00:55:13 +00:00
566c57bcd3 we no longer need submodules for these jobs 2021-11-06 00:35:39 +00:00
3c754b079c Move resources/locale to Composer dependency
all remaining submodules are now non-essential to running a server.
They are also versioned and updates can be done automatically using 'composer update'.

Finally, we can also put an end to the issue of translations being rendered incorrectly or being missing due to outdated submodules.
2021-11-06 00:32:58 +00:00
dbf9a33160 ChunkSelector: Improve algorithm to send chunks in proper circles, instead of squares
this ensures that the edge of loaded terain is always the same distance
away in any direction. This also means that when flying parallel to X or
Z axes, you now have about 12% more chunks directly in front of you,
instead of to your left and right, which gives the impression that
chunks are loading faster (they aren't, they are just being ordered in a
more sensible way).
2021-11-06 00:15:04 +00:00
07b1cff306 Bonemeal is no longer consumed when cancelling plant growth events (#4551) 2021-11-05 16:15:55 +00:00
0989c77037 Player: check that horizontal distance travelled is > 0 before adding exhaustion, or triggering a new chunk order
closes #4548
2021-11-05 15:46:55 +00:00
5107d0df4e Player: cap maximum number of active generation requests
this fixes the horrible spotty chunk loading seen in https://twitter.com/dktapps/status/1456397007946461190?s=20.
In practice, this made chunks invisible on teleport for several tens of seconds after teleporting. Having a larger chunks-per-tick with large render distance compounded to worsen the problem.
It wasn't really noticeable on small render distances, but very obvious on large ones with fast chunk sending and slow generation.

This also fixes #4187 (at least to the extent that it works on PM3, anyway).
2021-11-05 15:09:42 +00:00
579ef63663 EntityDataHelper: accept FloatTag for vector3 as well as Double
MCPE uses Float for entity positions.
2021-11-04 20:46:34 +00:00
8abc952c74 simulate-chunk-selector: do not reallocate colours on every frame 2021-11-04 19:38:40 +00:00
4c3a5fdd73 Clean PHPStan baselines from 1.0.2 2021-11-04 19:28:52 +00:00
54f287feb6 Merge branch 'stable' 2021-11-04 19:27:41 +00:00
84f8b3eb2d Move CrashDump to pocketmine\crash namespace 2021-11-04 19:23:45 +00:00
15fca84f3b remove some PHPStan error patterns 2021-11-04 19:22:49 +00:00
c60144210f Regenerate PHPStan bugs baseline 2021-11-04 19:18:29 +00:00
8ac999cbd4 Use object models for crashdump generation 2021-11-04 16:55:04 +00:00
4f8501ff34 Added a tool to visualise behaviour of ChunkSelector
I actually intended to write a tool for debugging generation, but it turns out this, as an intermediary step, is also useful and a whole bunch of fun to play with.
2021-11-04 14:14:55 +00:00
2405e45b35 Player: mark as not using item when held item slot is changed
closes #4538
2021-11-03 21:26:20 +00:00
e0b07ff308 Human: do not add more XP if totalXp limit was already reached
this matches the vanilla behaviour. For some reason it doesn't consider levels (so you can have a level higher or lower than this without actually having that amount of XP), but this matches Java behaviour as of 1.10.

fixes #4543
2021-11-03 20:45:55 +00:00
729f831b8f PHPStan 1.0.2 2021-11-03 20:26:32 +00:00
0356716e8e PopulationTask: do not expose internal fields as public
this code dates back to pthreads v2, when visibility on Threaded object fields meant different things (wtf, krakjoe??)
2021-11-03 15:23:43 +00:00
5c81b04813 PopulationTask: use typed properties 2021-11-03 15:22:49 +00:00
1ebb206762 World: fixed yet another edge case in drainPopulationRequestQueue() leading to assertion failure
really had to go fucking nuclear on it :(
2021-11-03 14:58:58 +00:00
29e2d92098 Bump phpstan/phpstan from 1.0.0 to 1.0.1 (#4541)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Commits](https://github.com/phpstan/phpstan/compare/1.0.0...1.0.1)

---
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-03 11:27:42 +00:00
ef82a2cd79 Fixed PlayerEmoteEvent::setEmoteId() being useless 2021-11-02 23:10:26 +00:00
87031627bf Do not call PlayerEmoteEvent if rate limit was reached 2021-11-02 23:09:43 +00:00
f066199971 Implement emote support (#4523) 2021-11-02 23:04:55 +00:00
a0e9eec652 4.0.0-BETA11 is next 2021-11-02 19:18:20 +00:00
fa6a432d58 Release 4.0.0-BETA10 2021-11-02 19:18:19 +00:00
102277c636 draft-release: fixed BedrockData JSON minification 2021-11-02 19:16:36 +00:00
f50f26d52e 4.0.0-BETA10 is next 2021-11-02 19:14:15 +00:00
124 changed files with 1963 additions and 1035 deletions

View File

@ -42,7 +42,7 @@ jobs:
sed -i "s/const BUILD_NUMBER = 0/const BUILD_NUMBER = ${BUILD_NUMBER}/" src/VersionInfo.php
- name: Minify BedrockData JSON files
run: php resources/vanilla/.minify_json.php
run: php vendor/pocketmine/bedrock-data/.minify_json.php
- name: Build PocketMine-MP.phar
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }}

View File

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

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "resources/locale"]
path = resources/locale
url = https://github.com/pmmp/Language.git
[submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git

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`.
## 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`
## Checking out a different branch to build
1. `git checkout <branch to checkout>`
2. `git submodule update --init`
3. Re-run `composer install` to synchronize dependencies.
2. Re-run `composer install` to synchronize dependencies.
## 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.

View File

@ -5,7 +5,7 @@
<p align="center">
<img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" />
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img src="https://img.shields.io/github/v/tag/pmmp/PocketMine-MP?label=release&logo=github" alt="GitHub tag (latest semver)" /></a>
<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://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
</p>

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\build\generate_known_translation_apis;
use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use function array_map;
use function count;
@ -100,7 +101,7 @@ final class KnownTranslationKeys{
HEADER;
ksort($languageDefinitions, SORT_STRING);
foreach($languageDefinitions as $k => $_){
foreach(Utils::stringifyKeys($languageDefinitions) as $k => $_){
echo "\tpublic const ";
echo constantify($k);
echo " = \"" . $k . "\";\n";
@ -135,7 +136,7 @@ HEADER;
$parameterRegex = '/{%(.+?)}/';
$translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName();
foreach($languageDefinitions as $key => $value){
foreach(Utils::stringifyKeys($languageDefinitions) as $key => $value){
$parameters = [];
if(preg_match_all($parameterRegex, $value, $matches) > 0){
foreach($matches[1] as $parameterName){
@ -172,7 +173,7 @@ HEADER;
echo "Done generating KnownTranslationFactory.\n";
}
$lang = parse_ini_file(Path::join(\pocketmine\RESOURCE_PATH, "locale", "eng.ini"), false, INI_SCANNER_RAW);
$lang = parse_ini_file(Path::join(\pocketmine\LOCALE_DATA_PATH, "eng.ini"), false, INI_SCANNER_RAW);
if($lang === false){
fwrite(STDERR, "Missing language files!\n");
exit(1);

View File

@ -91,7 +91,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
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;
}
$shortClassName = basename($file, ".php");
@ -101,7 +101,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
}
$reflect = new \ReflectionClass($className);
$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;
}
echo "Found registry in $file\n";

View File

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

View File

@ -21,3 +21,10 @@ 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.
- `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.
# 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.

View File

@ -313,6 +313,10 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
### Entity
#### General
- `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:
- `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
@ -351,6 +355,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- `Human->getMaxFood()` -> `HungerManager->getMaxFood()`
- `Human->addFood()` -> `HungerManager->addFood()`
- `Human->isHungry()` -> `HungerManager->isHungry()`
- `Human->getEnderChestInventory()` -> `Human->getEnderInventory()`
- `Human->getSaturation()` -> `HungerManager->getSaturation()`
- `Human->setSaturation()` -> `HungerManager->setSaturation()`
- `Human->addSaturation()` -> `HungerManager->addSaturation()`
@ -564,6 +569,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- `CallbackInventoryChangeListener`
- `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.
- `PlayerEnderInventory`: represents the pure storage part of the player's ender inventory, without any block information
- `transaction\CreateItemAction`
- `transaction\DestroyItemAction`
- 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:
- `BaseInventory->getDefaultSize()`
- `BaseInventory->setSize()`
- `EnderChestInventory->setHolderPosition()`
- `Inventory->close()`
- `Inventory->dropContents()`
- `Inventory->getName()`
@ -1276,6 +1283,7 @@ However, if we add `src-namespace-prefix: pmmp\TesterPlugin` to the `plugin.yml`
- Implemented offhand inventory.
- 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).
- Armor can now be equipped by right-clicking while holding it.
# 4.0.0-BETA2
Released 10th September 2021.
@ -1601,3 +1609,142 @@ Released 2nd November 2021.
- Fixed arrows getting added to creative players' inventories when picked up.
- Fixed players re-requesting the same ungenerated chunks multiple times before they were sent.
- Fixed commands not working in some cases after using some control sequences on the console.
# 4.0.0-BETA10
Released 2nd November 2021.
## Fixes
- Fixed an issue with BedrockData JSON minification which broke the release build of 4.0.0-BETA9.
# 4.0.0-BETA11
Released 6th November 2021.
## General
- `resources/locale` submodule has been removed. Language files are now included via Composer dependency [`pocketmine/locale-data`](https://packagist.org/packages/pocketmine/locale-data).
- This means it's now possible to run a server from git sources without cloning submodules :)
- All remaining submodules (DevTools, build/php) are non-essential for building and running a server.
- Added a tool `tools/simulate-chunk-sending.php` to visualise the behaviour of `ChunkSelector`.
## Fixes
- Fixed server crash on saving when player XP has reached int32 max (XP is now capped, similar to Java Edition).
- Fixed another edge case in chunk generation that led to assertion failures.
- Fixed server crash when finding a list of `TAG_Float` for entity positions instead of `TAG_Double`.
- Fixed fast eating when picking up items.
- Fixed terrain being invisible for a long time when teleporting into ungenerated terrain.
- Fixed weird chunk loading when teleporting into ungenerated terrain (sometimes farther chunks would render before closer ones, leaving holes in the map temporarily).
- Fixed players re-requesting chunks when turning their heads or jumping.
- Fixed bonemeal sometimes being consumed even when cancelling `BlockGrowEvent` and `StructureGrowEvent`.
## API
### Event
- Added `PlayerEmoteEvent`.
### 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.
- 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.

View File

@ -35,12 +35,13 @@
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"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/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0",
"pocketmine/locale-data": "^2.0.16",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
@ -52,7 +53,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.0.0",
"phpstan/phpstan": "1.2.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.2"

106
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3fa50836a0e8560fe59ba9e73cc50c44",
"content-hash": "fb545e4c8e17b0b07e8e20986b64e5a6",
"packages": [
{
"name": "adhocore/json-comment",
@ -275,16 +275,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "5.0.0+bedrock-1.17.40",
"version": "6.0.0+bedrock-1.17.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab"
"reference": "906bafec4fc41f548749ce01d120902b25c1bbfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/67c0c15b4044cab2190501933912c3d02c5f63ab",
"reference": "67c0c15b4044cab2190501933912c3d02c5f63ab",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/906bafec4fc41f548749ce01d120902b25c1bbfe",
"reference": "906bafec4fc41f548749ce01d120902b25c1bbfe",
"shasum": ""
},
"require": {
@ -298,7 +298,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.0.0",
"phpstan/phpstan": "1.2.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
@ -316,9 +316,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"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",
@ -531,6 +531,29 @@
},
"time": "2021-02-12T18:56:22+00:00"
},
{
"name": "pocketmine/locale-data",
"version": "2.0.17",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "30e4a64d5674bac556c4e2b9842b19a981471ac4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/30e4a64d5674bac556c4e2b9842b19a981471ac4",
"reference": "30e4a64d5674bac556c4e2b9842b19a981471ac4",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.0.17"
},
"time": "2021-11-12T00:59:39+00:00"
},
{
"name": "pocketmine/log",
"version": "0.4.0",
@ -1347,6 +1370,7 @@
"issues": "https://github.com/webmozart/path-util/issues",
"source": "https://github.com/webmozart/path-util/tree/2.3.0"
},
"abandoned": "symfony/filesystem",
"time": "2015-12-17T08:42:14+00:00"
}
],
@ -1480,16 +1504,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.13.0",
"version": "v4.13.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53"
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
"reference": "50953a2691a922aa1769461637869a0a2faa3f53",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd",
"shasum": ""
},
"require": {
@ -1530,9 +1554,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1"
},
"time": "2021-09-20T12:20:58+00:00"
"time": "2021-11-03T20:52:16+00:00"
},
{
"name": "phar-io/manifest",
@ -1874,16 +1898,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.0.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "0d13a99513182e521271d46bde8f28caa4f84d97"
"reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d13a99513182e521271d46bde8f28caa4f84d97",
"reference": "0d13a99513182e521271d46bde8f28caa4f84d97",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
"reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
"shasum": ""
},
"require": {
@ -1899,7 +1923,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
"dev-master": "1.2-dev"
}
},
"autoload": {
@ -1914,7 +1938,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.0.0"
"source": "https://github.com/phpstan/phpstan/tree/1.2.0"
},
"funding": [
{
@ -1934,7 +1958,7 @@
"type": "tidelift"
}
],
"time": "2021-11-01T06:38:20+00:00"
"time": "2021-11-18T14:09:01+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -1993,21 +2017,21 @@
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.0.0",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940"
"reference": "e12d55f74a8cca18c6e684c6450767e055ba7717"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7f50eb112f37fda2ef956813d3f1e9b1e69d7940",
"reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e12d55f74a8cca18c6e684c6450767e055ba7717",
"reference": "e12d55f74a8cca18c6e684c6450767e055ba7717",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"phpstan/phpstan": "^1.0"
"phpstan/phpstan": "^1.2.0"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@ -2038,22 +2062,22 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"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",
"version": "9.2.8",
"version": "9.2.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e"
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
"reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b",
"shasum": ""
},
"require": {
@ -2109,7 +2133,7 @@
],
"support": {
"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": [
{
@ -2117,7 +2141,7 @@
"type": "github"
}
],
"time": "2021-10-30T08:01:38+00:00"
"time": "2021-11-19T15:21:02+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -2892,16 +2916,16 @@
},
{
"name": "sebastian/exporter",
"version": "4.0.3",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65"
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65",
"reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"shasum": ""
},
"require": {
@ -2950,14 +2974,14 @@
}
],
"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": [
"export",
"exporter"
],
"support": {
"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": [
{
@ -2965,7 +2989,7 @@
"type": "github"
}
],
"time": "2020-09-28T05:24:23+00:00"
"time": "2021-11-11T14:18:36+00:00"
},
{
"name": "sebastian/global-state",

View File

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

Submodule resources/locale deleted from f9076e4a6e

View File

@ -108,7 +108,7 @@ player:
verify-xuid: true
level-settings:
#The default format that levels will use when created
#The default format that worlds will use when created
default-format: leveldb
chunk-sending:
@ -176,7 +176,7 @@ aliases:
#savestop: [save-all, stop]
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:
#world:
# seed: 404

View File

@ -36,4 +36,5 @@ define('pocketmine\_CORE_CONSTANTS_INCLUDED', true);
define('pocketmine\PATH', dirname(__DIR__) . '/');
define('pocketmine\RESOURCE_PATH', dirname(__DIR__) . '/resources/');
define('pocketmine\BEDROCK_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-data/');
define('pocketmine\LOCALE_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/locale-data/');
define('pocketmine\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__) . '/vendor/autoload.php');

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

View File

@ -34,6 +34,7 @@ namespace pocketmine {
use pocketmine\utils\Timezone;
use pocketmine\wizard\SetupWizard;
use Webmozart\PathUtil\Path;
use function defined;
use function extension_loaded;
use function phpversion;
use function preg_match;
@ -145,6 +146,10 @@ namespace pocketmine {
$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;
}

View File

@ -34,6 +34,8 @@ use pocketmine\console\ConsoleCommandSender;
use pocketmine\console\ConsoleReaderThread;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\CraftingManagerFromDataHelper;
use pocketmine\crash\CrashDump;
use pocketmine\crash\CrashDumpRenderer;
use pocketmine\data\java\GameModeIdMap;
use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\Location;
@ -120,13 +122,18 @@ use function base64_encode;
use function cli_set_process_title;
use function copy;
use function count;
use function date;
use function fclose;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function filemtime;
use function fopen;
use function get_class;
use function ini_set;
use function is_array;
use function is_dir;
use function is_resource;
use function is_string;
use function json_decode;
use function max;
@ -320,6 +327,10 @@ class Server{
return $this->configGroup->getConfigInt("server-port", 19132);
}
public function getPortV6() : int{
return $this->configGroup->getConfigInt("server-portv6", 19133);
}
public function getViewDistance() : int{
return max(2, $this->configGroup->getConfigInt("view-distance", 8));
}
@ -336,6 +347,11 @@ class Server{
return $str !== "" ? $str : "0.0.0.0";
}
public function getIpV6() : string{
$str = $this->configGroup->getConfigString("server-ipv6");
return $str !== "" ? $str : "::";
}
public function getServerUniqueId() : UuidInterface{
return $this->serverID;
}
@ -783,6 +799,8 @@ class Server{
new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [
"motd" => VersionInfo::NAME . " Server",
"server-port" => 19132,
"server-portv6" => 19133,
"enable-ipv6" => true,
"white-list" => false,
"max-players" => 20,
"gamemode" => 0,
@ -1113,25 +1131,42 @@ class Server{
return true;
}
private function startupPrepareNetworkInterfaces() : bool{
$useQuery = $this->configGroup->getConfigBool("enable-query", true);
private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery) : bool{
$prettyIp = $ipV6 ? "[$ip]" : $ip;
try{
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this));
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$this->getIp(),
(string) $this->getPort(),
$ip,
(string) $port,
$e->getMessage()
)));
return false;
}
if(!$rakLibRegistered && $useQuery){
//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($this->getIp(), $this->getPort(), new \PrefixedLogger($this->logger, "Dedicated Query Interface")));
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_networkStart($prettyIp, (string) $port)));
if($useQuery){
if(!$rakLibRegistered){
//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){
$this->network->registerRawPacketHandler(new QueryHandler($this));
@ -1172,7 +1207,7 @@ class Server{
* Unsubscribes from all broadcast channels.
*/
public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{
foreach($this->broadcastSubscribers as $channelId => $recipients){
foreach(Utils::stringifyKeys($this->broadcastSubscribers) as $channelId => $recipients){
$this->unsubscribeFromBroadcastChannel($channelId, $subscriber);
}
}
@ -1476,6 +1511,25 @@ class Server{
$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{
while(@ob_end_flush()){}
if(!$this->isRunning){
@ -1492,7 +1546,9 @@ class Server{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$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)){
$report = true;
@ -1504,8 +1560,8 @@ class Server{
}
@touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"];
if(is_string($plugin)){
$plugin = $dump->getData()->plugin;
if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
@ -1513,7 +1569,7 @@ class Server{
}
}
if($dump->getData()["error"]["type"] === \ParseError::class){
if($dump->getData()->error["type"] === \ParseError::class){
$report = false;
}

View File

@ -29,7 +29,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA9";
public const BASE_VERSION = "4.0.0-BETA13";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_NUMBER = 0;
public const BUILD_CHANNEL = "beta";

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\crafting\CraftingGrid;
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
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{
if($player instanceof Player){
$player->setCraftingGrid(new CraftingGrid($player, CraftingGrid::SIZE_BIG));
$player->setCurrentWindow(new CraftingTableInventory($this->position));
}
return true;

View File

@ -80,10 +80,9 @@ abstract class Crops extends Flowable{
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
return true;
}

View File

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

View File

@ -76,9 +76,7 @@ class Sapling extends Flowable{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$this->grow($player);
if($item instanceof Fertilizer && $this->grow($player)){
$item->pop();
return true;
@ -108,19 +106,20 @@ class Sapling extends Flowable{
}
}
private function grow(?Player $player) : void{
private function grow(?Player $player) : bool{
$random = new Random(mt_rand());
$tree = TreeFactory::get($random, $this->treeType);
$transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random);
if($transaction === null){
return;
return false;
}
$ev = new StructureGrowEvent($this, $transaction, $player);
$ev->call();
if(!$ev->isCancelled()){
$transaction->apply();
return $transaction->apply();
}
return false;
}
public function getFuelTime() : int{

View File

@ -124,8 +124,14 @@ class Skull extends Flowable{
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//TODO: different bounds depending on attached face
return [AxisAlignedBB::one()->contract(0.25, 0, 0.25)->trim(Facing::UP, 0.5)];
$collisionBox = 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{

View File

@ -48,7 +48,8 @@ class Sugarcane extends Flowable{
return 0b1111;
}
private function grow() : void{
private function grow() : bool{
$grew = false;
for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break;
@ -61,12 +62,14 @@ class Sugarcane extends Flowable{
break;
}
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
$grew = true;
}else{
break;
}
}
$this->age = 0;
$this->position->getWorld()->setBlock($this->position, $this);
return $grew;
}
public function getAge() : int{ return $this->age; }
@ -82,12 +85,10 @@ class Sugarcane extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
$this->grow();
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
$item->pop();
}
$item->pop();
return true;
}

View File

@ -99,9 +99,9 @@ class SweetBerryBush extends Flowable{
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
$item->pop();
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));

View File

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

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace pocketmine\block\inventory;
use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
class AnvilInventory extends SimpleInventory implements BlockInventory{
class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public const SLOT_INPUT = 0;
@ -37,13 +37,4 @@ class AnvilInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder;
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;
use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
class EnchantInventory extends SimpleInventory implements BlockInventory{
class EnchantInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public const SLOT_INPUT = 0;
@ -37,13 +37,4 @@ class EnchantInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder;
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;
use pocketmine\inventory\SimpleInventory;
use pocketmine\player\Player;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\world\Position;
final class LoomInventory extends SimpleInventory implements BlockInventory{
final class LoomInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
use BlockInventoryTrait;
public const SLOT_BANNER = 0;
@ -38,13 +38,4 @@ final class LoomInventory extends SimpleInventory implements BlockInventory{
$this->holder = $holder;
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;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
@ -112,21 +113,25 @@ final class TileFactory{
/**
* @internal
* @throws NbtDataException
* @throws SavedDataLoadingException
*/
public function createFromData(World $world, CompoundTag $nbt) : ?Tile{
$type = $nbt->getString(Tile::TAG_ID, "");
if(!isset($this->knownTiles[$type])){
return null;
try{
$type = $nbt->getString(Tile::TAG_ID, "");
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;
}

View File

@ -32,7 +32,7 @@ use pocketmine\player\Player;
use function array_shift;
use function count;
use function implode;
use function preg_match;
use function inet_pton;
class BanIpCommand extends VanillaCommand{
@ -57,7 +57,7 @@ class BanIpCommand extends VanillaCommand{
$value = array_shift($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);
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\utils\InvalidCommandSyntaxException;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\entity\effect\StringToEffectParser;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\Limits;
@ -69,9 +69,8 @@ class EffectCommand extends VanillaCommand{
return true;
}
try{
$effect = VanillaEffects::fromString($args[1]);
}catch(\InvalidArgumentException $e){
$effect = StringToEffectParser::getInstance()->parse($args[1]);
if($effect === null){
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED));
return true;
}

View File

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

View File

@ -29,7 +29,7 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use function count;
use function preg_match;
use function inet_pton;
class PardonIpCommand extends VanillaCommand{
@ -52,7 +52,7 @@ class PardonIpCommand extends VanillaCommand{
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()->getNetwork()->unblockAddress($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\player\Player;
use pocketmine\utils\TextFormat;
use pocketmine\world\World;
use function count;
use function round;
class SetWorldSpawnCommand extends VanillaCommand{
@ -54,22 +54,32 @@ class SetWorldSpawnCommand extends VanillaCommand{
if($sender instanceof Player){
$location = $sender->getPosition();
$world = $location->getWorld();
$pos = $location->asVector3()->round();
$pos = $location->asVector3()->floor();
}else{
$sender->sendMessage(TextFormat::RED . "You can only perform this command as a player");
return true;
}
}elseif(count($args) === 3){
$world = $sender->getServer()->getWorldManager()->getDefaultWorld();
$pos = new Vector3($this->getInteger($sender, $args[0]), $this->getInteger($sender, $args[1]), $this->getInteger($sender, $args[2]));
if($sender instanceof Player){
$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{
throw new InvalidCommandSyntaxException();
}
$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;
}

View File

@ -32,6 +32,7 @@ use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
use pocketmine\world\World;
use function array_shift;
use function count;
use function round;
@ -111,7 +112,7 @@ class TeleportCommand extends VanillaCommand{
}
$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]);
$targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch);

View File

@ -23,50 +23,38 @@ declare(strict_types=1);
namespace pocketmine\console;
use pocketmine\utils\AssumptionFailedError;
use function fclose;
use function fgets;
use function fopen;
use function is_resource;
use function stream_select;
use function trim;
use function usleep;
final class ConsoleReader{
/** @var resource */
private $stdin;
public function __construct(){
$this->initStdin();
}
private function initStdin() : void{
if(is_resource($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;
}
/**
* Reads a line from the console and adds it to the buffer. This method may block the thread.
* @throws ConsoleReaderException
*/
public function readLine() : ?string{
if(!is_resource($this->stdin)){
$this->initStdin();
}
$r = [$this->stdin];
$w = $e = null;
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
return null;
}elseif($count === false){ //stream error
$this->initStdin();
throw new ConsoleReaderException("Unexpected EOF on select()");
}
if(($raw = fgets($this->stdin)) === false){ //broken pipe or EOF
$this->initStdin();
usleep(200000); //prevent CPU waste if it's end of pipe
return null; //loop back round
throw new ConsoleReaderException("Unexpected EOF on fgets()");
}
$line = trim($raw);

View File

@ -45,8 +45,18 @@ if($socket === false){
}
$consoleReader = new ConsoleReader();
while(!feof($socket)){
$line = $consoleReader->readLine();
if($line !== null){
fwrite($socket, $line . "\n");
try{
$line = $consoleReader->readLine();
}catch(ConsoleReaderException $e){
//Encountered unexpected EOF. This might be because the user did something stupid, or because the parent process
//has died. In either case, commit suicide. If the parent process is still alive, it will start a new console
//reader.
break;
}
if(@fwrite($socket, ($line ?? "") . "\n") === false){
//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

@ -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\console;
final class ConsoleReaderException extends \RuntimeException{
}

View File

@ -116,6 +116,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('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
if($command === ""){
continue;
}
$buffer[] = $command;
if($notifier !== null){
$notifier->wakeupSleeper();

View File

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

View File

@ -21,37 +21,31 @@
declare(strict_types=1);
namespace pocketmine;
namespace pocketmine\crash;
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginManager;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path;
use function base64_encode;
use function date;
use function error_get_last;
use function fclose;
use function file;
use function file_exists;
use function file_get_contents;
use function fopen;
use function fwrite;
use function get_loaded_extensions;
use function implode;
use function is_dir;
use function is_resource;
use function json_encode;
use function json_last_error_msg;
use function ksort;
use function max;
use function mb_strtoupper;
use function microtime;
use function mkdir;
use function ob_end_clean;
use function ob_get_contents;
use function ob_start;
@ -67,9 +61,10 @@ use function zend_version;
use function zlib_encode;
use const FILE_IGNORE_NEW_LINES;
use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL;
use const PHP_OS;
use const PHP_VERSION;
use const SORT_STRING;
use const ZLIB_ENCODING_DEFLATE;
class CrashDump{
@ -81,80 +76,34 @@ class CrashDump{
*/
private const FORMAT_VERSION = 4;
private const PLUGIN_INVOLVEMENT_NONE = "none";
private const PLUGIN_INVOLVEMENT_DIRECT = "direct";
private const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
public const PLUGIN_INVOLVEMENT_NONE = "none";
public const PLUGIN_INVOLVEMENT_DIRECT = "direct";
public const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
/** @var Server */
private $server;
/** @var resource */
private $fp;
/** @var float */
private $time;
/**
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private $data = [];
private CrashDumpData $data;
/** @var string */
private $encodedData = "";
/** @var string */
private $path;
private $encodedData;
private ?PluginManager $pluginManager;
public function __construct(Server $server, ?PluginManager $pluginManager){
$this->time = microtime(true);
$now = microtime(true);
$this->server = $server;
$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["format_version"] = self::FORMAT_VERSION;
$this->data["time"] = $this->time;
$this->data["uptime"] = $this->time - $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->data = new CrashDumpData();
$this->data->format_version = self::FORMAT_VERSION;
$this->data->time = $now;
$this->data->uptime = $now - $this->server->getStartTime();
$this->baseCrash();
$this->generalData();
$this->pluginsData();
$this->extraData();
$this->encodeData();
fclose($this->fp);
}
public function getPath() : string{
return $this->path;
}
public function getEncodedData() : string{
return $this->encodedData;
}
/**
* @return mixed[]
* @phpstan-return array<string, mixed>
*/
public function getData() : array{
return $this->data;
}
private function encodeData() : void{
$this->addLine();
$this->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
$this->addLine();
$this->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());
@ -162,34 +111,45 @@ class CrashDump{
$zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed");
$this->encodedData = $zlibEncoded;
}
public function getEncodedData() : string{
return $this->encodedData;
}
public function getData() : CrashDumpData{
return $this->data;
}
public function encodeData(CrashDumpRenderer $renderer) : void{
$renderer->addLine();
$renderer->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
$renderer->addLine();
$renderer->addLine("===BEGIN CRASH DUMP===");
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{
if($this->pluginManager !== null){
$plugins = $this->pluginManager->getPlugins();
$this->addLine();
$this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
ksort($plugins, SORT_STRING);
foreach($plugins as $p){
$d = $p->getDescription();
$this->data["plugins"][$d->getName()] = [
"name" => $d->getName(),
"version" => $d->getVersion(),
"authors" => $d->getAuthors(),
"api" => $d->getCompatibleApis(),
"enabled" => $p->isEnabled(),
"depends" => $d->getDepend(),
"softDepends" => $d->getSoftDepend(),
"main" => $d->getMain(),
"load" => mb_strtoupper($d->getOrder()->name()),
"website" => $d->getWebsite()
];
$this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis()));
$this->data->plugins[$d->getName()] = new CrashDumpDataPluginEntry(
name: $d->getName(),
version: $d->getVersion(),
authors: $d->getAuthors(),
api: $d->getCompatibleApis(),
enabled: $p->isEnabled(),
depends: $d->getDepend(),
softDepends: $d->getSoftDepend(),
main: $d->getMain(),
load: mb_strtoupper($d->getOrder()->name()),
website: $d->getWebsite()
);
}
}
}
@ -198,32 +158,26 @@ class CrashDump{
global $argv;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
$this->data["parameters"] = (array) $argv;
$this->data->parameters = (array) $argv;
if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties);
}else{
$this->data["server.properties"] = $serverDotProperties;
$this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid");
}
if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){
$this->data["pocketmine.yml"] = $pocketmineDotYml;
}else{
$this->data["pocketmine.yml"] = "";
$this->data->pocketmineDotYml = $pocketmineDotYml;
}
}else{
$this->data["pocketmine.yml"] = "";
$this->data["server.properties"] = "";
$this->data["parameters"] = [];
}
$extensions = [];
foreach(get_loaded_extensions() as $ext){
$extensions[$ext] = phpversion($ext);
$version = phpversion($ext);
if($version === false) throw new AssumptionFailedError();
$extensions[$ext] = $version;
}
$this->data["extensions"] = $extensions;
$this->data->extensions = $extensions;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
ob_start();
phpinfo();
$this->data["phpinfo"] = ob_get_contents();
$this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line
ob_end_clean();
}
}
@ -255,18 +209,14 @@ class CrashDump{
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$this->data["lastError"] = $lastError;
$this->data->lastError = $lastError;
}
$this->data["error"] = $error;
unset($this->data["error"]["fullFile"]);
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->error = $error;
unset($this->data->error["fullFile"]);
unset($this->data->error["trace"]);
$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
foreach($error["trace"] as $frame){
if(!isset($frame["file"])){
@ -278,38 +228,25 @@ class CrashDump{
}
}
$this->addLine();
$this->addLine("Code:");
$this->data["code"] = [];
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
$this->addLine("[" . ($l + 1) . "] " . $file[$l]);
$this->data["code"][$l + 1] = $file[$l];
$this->data->code[$l + 1] = $file[$l];
}
}
}
$this->addLine();
$this->addLine("Backtrace:");
foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line);
}
$this->addLine();
$this->data->trace = Utils::printableTrace($error["trace"]);
}
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
$frameCleanPath = Filesystem::cleanPath($filePath);
if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){
$this->addLine();
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{
$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;
}
if(file_exists($filePath)){
@ -319,8 +256,7 @@ class CrashDump{
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){
$this->data["plugin"] = $plugin->getName();
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
$this->data->plugin = $plugin->getName();
break;
}
}
@ -331,7 +267,6 @@ class CrashDump{
}
private function generalData() : void{
$version = VersionInfo::VERSION();
$composerLibraries = [];
foreach(InstalledVersions::getInstalledPackages() as $package){
$composerLibraries[$package] = sprintf(
@ -341,42 +276,19 @@ class CrashDump{
);
}
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["base_version"] = VersionInfo::BASE_VERSION;
$this->data["general"]["build"] = VersionInfo::BUILD_NUMBER;
$this->data["general"]["is_dev"] = VersionInfo::IS_DEVELOPMENT_BUILD;
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL;
$this->data["general"]["git"] = VersionInfo::GIT_HASH();
$this->data["general"]["uname"] = php_uname("a");
$this->data["general"]["php"] = phpversion();
$this->data["general"]["zend"] = zend_version();
$this->data["general"]["php_os"] = PHP_OS;
$this->data["general"]["os"] = Utils::getOS();
$this->data["general"]["composer_libraries"] = $composerLibraries;
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . 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);
$this->data->general = new CrashDumpDataGeneral(
name: $this->server->getName(),
base_version: VersionInfo::BASE_VERSION,
build: VersionInfo::BUILD_NUMBER,
is_dev: VersionInfo::IS_DEVELOPMENT_BUILD,
protocol: ProtocolInfo::CURRENT_PROTOCOL,
git: VersionInfo::GIT_HASH(),
uname: php_uname("a"),
php: PHP_VERSION,
zend: zend_version(),
php_os: PHP_OS,
os: Utils::getOS(),
composer_libraries: $composerLibraries,
);
}
}

View File

@ -0,0 +1,84 @@
<?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;
final class CrashDumpData implements \JsonSerializable{
public int $format_version;
public float $time;
public float $uptime;
/** @var mixed[] */
public array $lastError = [];
/** @var mixed[] */
public array $error;
public string $plugin_involvement;
public string $plugin = "";
/** @var string[] */
public array $code = [];
/** @var string[] */
public array $trace;
/**
* @var CrashDumpDataPluginEntry[]
* @phpstan-var array<string, CrashDumpDataPluginEntry>
*/
public array $plugins = [];
/** @var string[] */
public array $parameters = [];
public string $serverDotProperties = "";
public string $pocketmineDotYml = "";
/**
* @var string[]
* @phpstan-var array<string, string>
*/
public array $extensions = [];
public string $phpinfo = "";
public CrashDumpDataGeneral $general;
/**
* @return mixed[]
*/
public function jsonSerialize() : array{
$result = (array) $this;
unset($result["serverDotProperties"]);
unset($result["pocketmineDotYml"]);
$result["pocketmine.yml"] = $this->pocketmineDotYml;
$result["server.properties"] = $this->serverDotProperties;
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?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;
final class CrashDumpDataGeneral{
/**
* @param string[] $composer_libraries
* @phpstan-param array<string, string> $composer_libraries
*/
public function __construct(
public string $name,
public string $base_version,
public int $build,
public bool $is_dev,
public int $protocol,
public string $git,
public string $uname,
public string $php,
public string $zend,
public string $php_os,
public string $os,
public array $composer_libraries,
){}
}

View File

@ -0,0 +1,45 @@
<?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;
final class CrashDumpDataPluginEntry{
/**
* @param string[] $authors
* @param string[] $api
* @param string[] $depends
* @param string[] $softDepends
*/
public function __construct(
public string $name,
public string $version,
public array $authors,
public array $api,
public bool $enabled,
public array $depends,
public array $softDepends,
public string $main,
public string $load,
public string $website,
){}
}

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();
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);
$this->attack($ev);
$hasUpdate = true;

View File

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

View File

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

View File

@ -27,6 +27,7 @@ use pocketmine\entity\utils\ExperienceUtils;
use pocketmine\event\player\PlayerExperienceChangeEvent;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\world\sound\XpCollectSound;
use pocketmine\world\sound\XpLevelUpSound;
@ -35,6 +36,7 @@ use function ceil;
use function count;
use function max;
use function min;
use function sprintf;
class ExperienceManager{
@ -142,7 +144,12 @@ class ExperienceManager{
public function setCurrentTotalXp(int $amount) : bool{
$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);
}
/**
@ -152,6 +159,7 @@ class ExperienceManager{
* @param bool $playSound Whether to play level-up and XP gained sounds.
*/
public function addXp(int $amount, bool $playSound = true) : bool{
$amount = min($amount, Limits::INT32_MAX - $this->totalXp);
$oldLevel = $this->getXpLevel();
$oldTotal = $this->getCurrentTotalXp();

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\animation\TotemUseAnimation;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects;
@ -104,12 +105,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
/**
* @throws InvalidSkinException
* @throws \UnexpectedValueException
* @throws SavedDataLoadingException
*/
public static function parseSkinNBT(CompoundTag $nbt) : Skin{
$skinTag = $nbt->getCompoundTag("Skin");
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
$skinTag->getString("Name"),

View File

@ -38,12 +38,14 @@ class Effect{
* @param Translatable|string $name Translation key used for effect name
* @param Color $color Color of bubbles given by this effect
* @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)
*/
public function __construct(
protected Translatable|string $name,
protected Color $color,
protected bool $bad = false,
private int $defaultDuration = 600,
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.
*/
public function getDefaultDuration() : int{
return 600;
return $this->defaultDuration;
}
/**

View File

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

View File

@ -34,8 +34,8 @@ class PoisonEffect extends Effect{
/** @var bool */
private $fatal;
public function __construct(Translatable|string $name, Color $color, bool $isBad = false, bool $hasBubbles = true, bool $fatal = false){
parent::__construct($name, $color, $isBad, $hasBubbles);
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, $defaultDuration, $hasBubbles);
$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\lang\KnownTranslationFactory;
use pocketmine\utils\RegistryTrait;
use function assert;
/**
* This doc-block is generated automatically, do not modify it manually.
@ -69,7 +68,7 @@ final class VanillaEffects{
//TODO: bad_omen
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("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("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)));
@ -85,7 +84,7 @@ final class VanillaEffects{
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("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
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)));
@ -109,10 +108,4 @@ final class VanillaEffects{
$result = self::_registryGetAll();
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();
}
public function move(float $dx, float $dy, float $dz) : void{
protected function move(float $dx, float $dy, float $dz) : void{
$this->blocksAround = null;
Timings::$entityMove->startTiming();

View File

@ -0,0 +1,51 @@
<?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\event\player;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\player\Player;
/**
* Called when a player uses an emote.
*/
class PlayerEmoteEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private string $emoteId
){
$this->player = $player;
}
public function getEmoteId() : string{
return $this->emoteId;
}
public function setEmoteId(string $emoteId) : void{
$this->emoteId = $emoteId;
}
}

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;
class PlayerCursorInventory extends SimpleInventory{
class PlayerCursorInventory extends SimpleInventory implements TemporaryInventory{
/** @var Player */
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\VanillaBlocks;
use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity;
use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\math\Vector3;
@ -671,6 +672,8 @@ class Item implements \JsonSerializable{
/**
* Deserializes an Item from an NBT CompoundTag
* @throws NbtException
* @throws SavedDataLoadingException
*/
public static function nbtDeserialize(CompoundTag $tag) : Item{
if($tag->getTag("id") === null or $tag->getTag("Count") === null){
@ -692,7 +695,7 @@ class Item implements \JsonSerializable{
}
$item->setCount($count);
}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");

View File

@ -29,25 +29,16 @@ use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType;
use pocketmine\block\VanillaBlocks;
use pocketmine\utils\SingletonTrait;
use function array_keys;
use function str_replace;
use function strtolower;
use function trim;
use pocketmine\utils\StringToTParser;
/**
* 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;
/**
* @var \Closure[]
* @phpstan-var array<string, \Closure(string $input) : Item>
*/
private array $callbackMap = [];
private static function make() : self{
$result = new self;
@ -167,6 +158,7 @@ final class StringToItemParser{
$result->registerBlock("chemical_heat", fn() => VanillaBlocks::CHEMICAL_HEAT());
$result->registerBlock("chemistry_table", fn() => VanillaBlocks::COMPOUND_CREATOR());
$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_red_sandstone", fn() => VanillaBlocks::CHISELED_RED_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("coal_block", fn() => VanillaBlocks::COAL());
$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_stairs", fn() => VanillaBlocks::COBBLESTONE_STAIRS());
$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_slab", fn() => VanillaBlocks::CUT_SANDSTONE_SLAB());
$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("dark_oak_button", fn() => VanillaBlocks::DARK_OAK_BUTTON());
$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_slab", fn() => VanillaBlocks::DARK_OAK_SLAB());
$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_wall_sign", fn() => VanillaBlocks::DARK_OAK_WALL_SIGN());
$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_wall_sign", fn() => VanillaBlocks::JUNGLE_WALL_SIGN());
$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("ladder", fn() => VanillaBlocks::LADDER());
$result->registerBlock("lantern", fn() => VanillaBlocks::LANTERN());
@ -704,6 +701,7 @@ final class StringToItemParser{
$result->registerBlock("oak_sign", fn() => VanillaBlocks::OAK_SIGN());
$result->registerBlock("oak_slab", fn() => VanillaBlocks::OAK_SLAB());
$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_wall_sign", fn() => VanillaBlocks::OAK_WALL_SIGN());
$result->registerBlock("oak_wood", fn() => VanillaBlocks::OAK_WOOD());
@ -1325,41 +1323,12 @@ final class StringToItemParser{
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 */
public function registerBlock(string $alias, \Closure $callback) : void{
$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{
$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[] */
public function getKnownAliases() : array{
return array_keys($this->callbackMap);
return parent::parse($input);
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\utils\CloningRegistryTrait;
use function assert;
/**
* This doc-block is generated automatically, do not modify it manually.
@ -381,12 +380,6 @@ final class VanillaItems{
self::_registryRegister($name, $item);
}
public static function fromString(string $name) : Item{
$result = self::_registryFromString($name);
assert($result instanceof Item);
return $result;
}
/**
* @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();
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, [
0 => $param0,
1 => $param1,
2 => $param2,
]);
}
@ -1689,10 +1690,11 @@ final class KnownTranslationFactory{
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, [
0 => $param0,
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{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGIN_EMPTYEXTENSIONVERSIONCONSTRAINT, [
"constraintIndex" => $constraintIndex,

View File

@ -361,6 +361,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_PLUGIN_DISALLOWEDBYBLACKLIST = "pocketmine.plugin.disallowedByBlacklist";
public const POCKETMINE_PLUGIN_DISALLOWEDBYWHITELIST = "pocketmine.plugin.disallowedByWhitelist";
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_ENABLE = "pocketmine.plugin.enable";
public const POCKETMINE_PLUGIN_EXTENSIONNOTLOADED = "pocketmine.plugin.extensionNotLoaded";

View File

@ -52,7 +52,7 @@ class Language{
*/
public static function getLanguageList(string $path = "") : array{
if($path === ""){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
if(is_dir($path)){
@ -101,7 +101,7 @@ class Language{
$this->langName = strtolower($lang);
if($path === null){
$path = Path::join(\pocketmine\RESOURCE_PATH, "locale");
$path = \pocketmine\LOCALE_DATA_PATH;
}
$this->lang = self::loadLang($path, $this->langName);

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe;
use pocketmine\block\inventory\AnvilInventory;
use pocketmine\block\inventory\BlockInventory;
use pocketmine\block\inventory\BrewingStandInventory;
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\block\inventory\FurnaceInventory;
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
//effect on the behaviour of inventory transactions I don't currently plan to integrate these into the main system.
private const RESERVED_WINDOW_ID_RANGE_START = ContainerIds::LAST - 10;
private const RESERVED_WINDOW_ID_RANGE_END = ContainerIds::LAST;
public const HARDCODED_CRAFTING_GRID_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 1;
public const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
private const HARDCODED_INVENTORY_WINDOW_ID = self::RESERVED_WINDOW_ID_RANGE_START + 2;
/** @var Player */
private $player;
@ -80,6 +79,15 @@ class InventoryManager{
/** @var int */
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[][]
* @phpstan-var array<int, array<int, Item>>
@ -178,6 +186,7 @@ class InventoryManager{
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
$inv instanceof AnvilInventory => WindowTypes::ANVIL,
$inv instanceof HopperInventory => WindowTypes::HOPPER,
$inv instanceof CraftingTableInventory => WindowTypes::WORKBENCH,
default => WindowTypes::CONTAINER
};
return [ContainerOpenPacket::blockInv($id, $windowType, $blockPosition)];
@ -185,6 +194,21 @@ class InventoryManager{
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{
if(isset($this->windowMap[$this->lastInventoryNetworkId])){
$this->remove($this->lastInventoryNetworkId);
@ -193,16 +217,18 @@ class InventoryManager{
}
public function onClientRemoveWindow(int $id) : void{
if($id >= self::RESERVED_WINDOW_ID_RANGE_START && $id <= self::RESERVED_WINDOW_ID_RANGE_END){
//TODO: HACK! crafting grid & main inventory currently use these fake IDs
return;
}
if($id === $this->lastInventoryNetworkId){
if(isset($this->openHardcodedWindows[$id])){
unset($this->openHardcodedWindows[$id]);
}elseif($id === $this->lastInventoryNetworkId){
$this->remove($id);
$this->player->removeCurrentWindow();
}else{
$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{

View File

@ -62,6 +62,7 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
@ -110,6 +111,7 @@ use pocketmine\player\UsedChunkStatus;
use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
@ -713,7 +715,7 @@ class NetworkSession{
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 :(
$this->setHandler(new DeathPacketHandler($this->player, $this));
$this->setHandler(new DeathPacketHandler($this->player, $this, $this->invManager ?? throw new AssumptionFailedError()));
}
}
@ -1043,6 +1045,10 @@ class NetworkSession{
$this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut));
}
public function onEmote(Player $from, string $emoteId) : void{
$this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
}
public function tick() : void{
if($this->info === null){
if(time() >= $this->connectTime + 10){

View File

@ -27,6 +27,7 @@ use pocketmine\data\bedrock\LegacyItemIdToStringIdMap;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use function array_key_exists;
use function file_get_contents;
@ -73,10 +74,6 @@ final class ItemTranslator{
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();
/** @phpstan-var array<string, int> $simpleMappings */
@ -92,7 +89,7 @@ final class ItemTranslator{
}
$simpleMappings[$newId] = $intId;
}
foreach($legacyStringToIntMap->getStringToLegacyMap() as $stringId => $intId){
foreach(Utils::stringifyKeys($legacyStringToIntMap->getStringToLegacyMap()) as $stringId => $intId){
if(isset($simpleMappings[$stringId])){
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\inventory\AnvilInventory;
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\block\inventory\LoomInventory;
use pocketmine\crafting\CraftingGrid;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\transaction\action\CreateItemAction;
use pocketmine\inventory\transaction\action\DestroyItemAction;
use pocketmine\inventory\transaction\action\DropItemAction;
@ -51,7 +50,6 @@ use pocketmine\player\GameMode;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use function array_key_exists;
class TypeConverter{
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
*/
@ -283,32 +265,32 @@ class TypeConverter{
}
switch($action->sourceType){
case NetworkInventoryAction::SOURCE_CONTAINER:
$window = null;
if($action->windowId === ContainerIds::UI and $action->inventorySlot > 0){
if($action->inventorySlot === UIInventorySlotOffset::CREATED_ITEM_OUTPUT){
return null; //useless noise
}
$pSlot = $action->inventorySlot;
$craftingGrid = $player->getCraftingGrid();
$mapped =
$this->mapUIInventory($pSlot, UIInventorySlotOffset::CRAFTING2X2_INPUT, $craftingGrid,
function(Inventory $i) : bool{ return $i instanceof CraftingGrid && $i->getGridWidth() === CraftingGrid::SIZE_SMALL; }) ??
$this->mapUIInventory($pSlot, UIInventorySlotOffset::CRAFTING3X3_INPUT, $craftingGrid,
function(Inventory $i) : bool{ return $i instanceof CraftingGrid && $i->getGridWidth() === CraftingGrid::SIZE_BIG; });
if($mapped === null){
$current = $player->getCurrentWindow();
$mapped =
$this->mapUIInventory($pSlot, UIInventorySlotOffset::ANVIL, $current,
function(Inventory $i) : bool{ return $i instanceof AnvilInventory; }) ??
$this->mapUIInventory($pSlot, UIInventorySlotOffset::ENCHANTING_TABLE, $current,
function(Inventory $i) : bool{ return $i instanceof EnchantInventory; }) ??
$this->mapUIInventory($pSlot, UIInventorySlotOffset::LOOM, $current,
fn(Inventory $i) => $i instanceof LoomInventory);
$slot = UIInventorySlotOffset::CRAFTING2X2_INPUT[$pSlot] ?? null;
if($slot !== null){
$window = $player->getCraftingGrid();
}elseif(($current = $player->getCurrentWindow()) !== null){
$slotMap = match(true){
$current instanceof AnvilInventory => UIInventorySlotOffset::ANVIL,
$current instanceof EnchantInventory => UIInventorySlotOffset::ENCHANTING_TABLE,
$current instanceof LoomInventory => UIInventorySlotOffset::LOOM,
$current instanceof CraftingTableInventory => UIInventorySlotOffset::CRAFTING3X3_INPUT,
default => null
};
if($slotMap !== null){
$window = $current;
$slot = $slotMap[$pSlot] ?? null;
}
}
if($mapped === null){
if($slot === null){
throw new TypeConversionException("Unmatched UI inventory slot offset $pSlot");
}
[$slot, $window] = $mapped;
}else{
$window = $inventoryManager->getWindow($action->windowId);
$slot = $action->inventorySlot;

View File

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

View File

@ -26,7 +26,6 @@ namespace pocketmine\network\mcpe\handler;
use pocketmine\block\BaseSign;
use pocketmine\block\ItemFrame;
use pocketmine\block\utils\SignText;
use pocketmine\crafting\CraftingGrid;
use pocketmine\entity\animation\ConsumingItemAnimation;
use pocketmine\entity\InvalidSkinException;
use pocketmine\event\player\PlayerEditBookEvent;
@ -57,8 +56,8 @@ use pocketmine\network\mcpe\protocol\BossEventPacket;
use pocketmine\network\mcpe\protocol\CommandBlockUpdatePacket;
use pocketmine\network\mcpe\protocol\CommandRequestPacket;
use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\ContainerOpenPacket;
use pocketmine\network\mcpe\protocol\CraftingEventPacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
@ -92,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\UseItemOnEntityTransactionData;
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\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use function array_key_exists;
use function array_push;
use function base64_encode;
use function count;
@ -137,15 +134,6 @@ class InGamePacketHandler extends PacketHandler{
/** @var bool */
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;
public function __construct(Player $player, NetworkSession $session, InventoryManager $inventoryManager){
@ -205,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)
return $packet->actorRuntimeId === ActorEvent::EATING_ITEM;
}
$this->player->doCloseInventory();
$this->player->removeCurrentWindow();
switch($packet->eventId){
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
@ -308,14 +296,6 @@ class InGamePacketHandler extends PacketHandler{
foreach($this->craftingTransaction->getInventories() as $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;
}finally{
$this->craftingTransaction = null;
@ -379,18 +359,6 @@ class InGamePacketHandler extends PacketHandler{
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
if(!$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos)){
$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;
case UseItemTransactionData::ACTION_BREAK_BLOCK:
@ -508,19 +476,8 @@ class InGamePacketHandler extends PacketHandler{
if($target === null){
return false;
}
if(
$packet->action === InteractPacket::ACTION_OPEN_INVENTORY && $target === $this->player &&
!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()
));
if($packet->action === InteractPacket::ACTION_OPEN_INVENTORY && $target === $this->player){
$this->inventoryManager->onClientOpenMainInventory();
return true;
}
return false; //TODO
@ -613,15 +570,7 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleContainerClose(ContainerClosePacket $packet) : bool{
$this->player->doCloseInventory();
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));
$this->inventoryManager->onClientRemoveWindow($packet->windowId);
return true;
}
@ -890,4 +839,9 @@ class InGamePacketHandler extends PacketHandler{
*/
return true;
}
public function handleEmote(EmotePacket $packet) : bool{
$this->player->emote($packet->getEmoteId());
return true;
}
}

View File

@ -117,7 +117,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
$pack->getPackSize(),
$pack->getSha256(),
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");

View File

@ -88,7 +88,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
/** @var PacketBroadcaster */
private $broadcaster;
public function __construct(Server $server){
public function __construct(Server $server, string $ip, int $port, bool $ipV6){
$this->server = $server;
$this->rakServerId = mt_rand(0, PHP_INT_MAX);
@ -101,7 +101,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->server->getLogger(),
$mainToThreadBuffer,
$threadToMainBuffer,
new InternetAddress($this->server->getIp(), $this->server->getPort(), 4),
new InternetAddress($ip, $port, $ipV6 ? 6 : 4),
$this->rakServerId,
$this->server->getConfigGroup()->getPropertyInt("network.max-mtu-size", 1492),
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.
*/
public static function getSubChunkCount(Chunk $chunk) : int{
for($count = count($chunk->getSubChunks()); $count > 0; --$count){
if($chunk->getSubChunk($count - 1)->isEmptyFast()){
for($y = Chunk::MAX_SUBCHUNK_INDEX, $count = count($chunk->getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){
if($chunk->getSubChunk($y)->isEmptyFast()){
continue;
}
return $count;
@ -59,7 +59,7 @@ final class ChunkSerializer{
public static function serializeFullChunk(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
$stream = PacketSerializer::encoder($encoderContext);
$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);
}
$stream->put($chunk->getBiomeIdArray());

View File

@ -34,11 +34,14 @@ use function socket_recvfrom;
use function socket_select;
use function socket_sendto;
use function socket_set_nonblock;
use function socket_set_option;
use function socket_strerror;
use function strlen;
use function time;
use function trim;
use const AF_INET;
use const IPPROTO_IPV6;
use const IPV6_V6ONLY;
use const PHP_INT_MAX;
use const SOCK_DGRAM;
use const SOCKET_EADDRINUSE;
@ -74,15 +77,18 @@ final class DedicatedQueryNetworkInterface implements AdvancedNetworkInterface{
/** @var string[] */
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->port = $port;
$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){
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;
}

View File

@ -27,7 +27,6 @@ declare(strict_types=1);
*/
namespace pocketmine\network\query;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\network\AdvancedNetworkInterface;
use pocketmine\network\RawPacketHandler;
use pocketmine\Server;
@ -57,8 +56,6 @@ class QueryHandler implements RawPacketHandler{
public function __construct(Server $server){
$this->server = $server;
$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.
@ -71,7 +68,6 @@ class QueryHandler implements RawPacketHandler{
$this->regenerateToken();
$this->lastToken = $this->token;
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_query_running($addr, (string) $port)));
}
public function getPattern() : string{

View File

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

View File

@ -27,6 +27,7 @@ use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginException;
use pocketmine\timings\Timings;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils;
use function count;
use function spl_object_id;
@ -143,7 +144,7 @@ class PermissibleInternal implements Permissible{
$oldPermissions = $this->permissions;
$this->permissions = [];
foreach($this->rootPermissions as $name => $isGranted){
foreach(Utils::stringifyKeys($this->rootPermissions) as $name => $isGranted){
$perm = $permManager->getPermission($name);
if($perm === null){
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{
$permManager = PermissionManager::getInstance();
foreach($children as $name => $v){
foreach(Utils::stringifyKeys($children) as $name => $v){
$perm = $permManager->getPermission($name);
$value = ($v xor $invert);
$this->permissions[$name] = new PermissionAttachmentInfo($name, $attachment, $value, $parent);

View File

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

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\world\World;
use const M_SQRT2;
//TODO: turn this into an interface?
final class ChunkSelector{
@ -33,34 +34,44 @@ final class ChunkSelector{
* @phpstan-return \Generator<int, int, void, void>
*/
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
$radiusSquared = $radius ** 2;
for($subRadius = 0; $subRadius < $radius; $subRadius++){
$subRadiusSquared = $subRadius ** 2;
$nextSubRadiusSquared = ($subRadius + 1) ** 2;
$minX = (int) ($subRadius / M_SQRT2);
for($x = 0; $x < $radius; ++$x){
for($z = 0; $z <= $x; ++$z){
if(($x ** 2 + $z ** 2) > $radiusSquared){
break; //skip to next band
}
$lastZ = 0;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
for($x = $subRadius; $x >= $minX; --$x){
for($z = $lastZ; $z <= $x; ++$z){
$distanceSquared = ($x ** 2 + $z ** 2);
if($distanceSquared < $subRadiusSquared){
continue;
}elseif($distanceSquared >= $nextSubRadiusSquared){
break; //skip to next X
}
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
$lastZ = $z;
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
}
}
}
}

View File

@ -53,6 +53,7 @@ use pocketmine\event\player\PlayerChatEvent;
use pocketmine\event\player\PlayerCommandPreprocessEvent;
use pocketmine\event\player\PlayerDeathEvent;
use pocketmine\event\player\PlayerDisplayNameChangeEvent;
use pocketmine\event\player\PlayerEmoteEvent;
use pocketmine\event\player\PlayerEntityInteractEvent;
use pocketmine\event\player\PlayerExhaustEvent;
use pocketmine\event\player\PlayerGameModeChangeEvent;
@ -72,8 +73,11 @@ use pocketmine\event\player\PlayerToggleSprintEvent;
use pocketmine\event\player\PlayerTransferEvent;
use pocketmine\form\Form;
use pocketmine\form\FormValidationException;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\PlayerCraftingInventory;
use pocketmine\inventory\PlayerCursorInventory;
use pocketmine\inventory\TemporaryInventory;
use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilderInventory;
@ -180,7 +184,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** @var Inventory[] */
protected array $permanentWindows = [];
protected PlayerCursorInventory $cursorInventory;
protected CraftingGrid $craftingGrid;
protected PlayerCraftingInventory $craftingGrid;
protected int $messageCounter = 2;
@ -193,6 +197,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @phpstan-var array<int, UsedChunkStatus>
*/
protected array $usedChunks = [];
/**
* @var true[]
* @phpstan-var array<int, true>
*/
private array $activeChunkGenerationRequests = [];
/**
* @var true[] chunkHash => dummy
* @phpstan-var array<int, true>
@ -234,6 +243,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** @var int[] ID => ticks map */
protected array $usedItemsCooldown = [];
private int $lastEmoteTick = 0;
protected int $formIdCounter = 0;
/** @var Form[] */
protected array $forms = [];
@ -288,6 +299,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
parent::initEntity($nbt);
$this->addDefaultWindows();
$this->inventory->getListeners()->add(new CallbackInventoryListener(
function(Inventory $unused, int $slot) : void{
if($slot === $this->inventory->getHeldItemIndex()){
$this->setUsingItem(false);
}
},
function() : void{
$this->setUsingItem(false);
}
));
$this->firstPlayed = $nbt->getLong("firstPlayed", $now = (int) (microtime(true) * 1000));
$this->lastPlayed = $nbt->getLong("lastPlayed", $now);
@ -628,6 +650,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
$this->getNetworkSession()->stopUsingChunk($x, $z);
unset($this->usedChunks[$index]);
unset($this->activeChunkGenerationRequests[$index]);
}
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
$world->unregisterChunkListener($this, $x, $z);
@ -664,8 +687,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$count = 0;
$world = $this->getWorld();
$limit = $this->chunksPerTick - count($this->activeChunkGenerationRequests);
foreach($this->loadQueue as $index => $distance){
if($count >= $this->chunksPerTick){
if($count >= $limit){
break;
}
@ -677,6 +702,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
++$count;
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_GENERATION();
$this->activeChunkGenerationRequests[$index] = true;
unset($this->loadQueue[$index]);
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
$this->getWorld()->registerChunkListener($this, $X, $Z);
@ -692,6 +718,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//multiple callbacks for this player. In that case, only the first one matters.
return;
}
unset($this->activeChunkGenerationRequests[$index]);
$this->usedChunks[$index] = UsedChunkStatus::REQUESTED_SENDING();
$this->getNetworkSession()->startUsingChunk($X, $Z, function() use ($X, $Z, $index) : void{
@ -1163,16 +1190,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->lastLocation = $to;
$this->broadcastMovement();
$distance = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $distance, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $distance, PlayerExhaustEvent::CAUSE_WALKING);
}
$horizontalDistanceTravelled = sqrt((($from->x - $to->x) ** 2) + (($from->z - $to->z) ** 2));
if($horizontalDistanceTravelled > 0){
//TODO: check swimming (adds 0.015 exhaustion in MCPE)
if($this->isSprinting()){
$this->hungerManager->exhaust(0.1 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_SPRINTING);
}else{
$this->hungerManager->exhaust(0.01 * $horizontalDistanceTravelled, PlayerExhaustEvent::CAUSE_WALKING);
}
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
if($this->nextChunkOrderRun > 20){
$this->nextChunkOrderRun = 20;
}
}
}
@ -1287,7 +1316,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* as a command.
*/
public function chat(string $message) : bool{
$this->doCloseInventory();
$this->removeCurrentWindow();
$message = TextFormat::clean($message, false);
foreach(explode("\n", $message) as $messagePart){
@ -1543,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.
*/
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)){
$this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers());
@ -1727,6 +1756,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true;
}
public function emote(string $emoteId) : void{
$currentTick = $this->server->getTick();
if($currentTick - $this->lastEmoteTick > 5){
$this->lastEmoteTick = $currentTick;
$event = new PlayerEmoteEvent($this, $emoteId);
$event->call();
if(!$event->isCancelled()){
$emoteId = $event->getEmoteId();
foreach($this->getViewers() as $player){
$player->getNetworkSession()->onEmote($this, $emoteId);
}
}
}
}
/**
* Drops an item on the ground in front of the player.
*/
@ -1947,7 +1991,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//prevent the player receiving their own disconnect message
$this->server->unsubscribeFromAllBroadcastChannels($this);
$this->doCloseInventory();
$this->removeCurrentWindow();
$ev = new PlayerQuitEvent($this, $quitMessage ?? $this->getLeaveMessage(), $reason);
$ev->call();
@ -2060,7 +2104,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected function onDeath() : void{
//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.
$this->doCloseInventory();
$this->removeCurrentWindow();
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
$ev->call();
@ -2259,7 +2303,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected function addDefaultWindows() : void{
$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);
@ -2274,17 +2318,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
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
* inventory.
*/
public function doCloseInventory() : void{
/** @var Inventory[] $inventories */
private function doCloseInventory() : void{
$inventories = [$this->craftingGrid, $this->cursorInventory];
if($this->currentWindow instanceof TemporaryInventory){
$inventories[] = $this->currentWindow;
}
$transaction = new InventoryTransaction($this);
$mainInventoryTransactionBuilder = new TransactionBuilderInventory($this->inventory);
@ -2321,10 +2363,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
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);
}
}
/**
@ -2361,6 +2399,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
public function removeCurrentWindow() : void{
$this->doCloseInventory();
if($this->currentWindow !== null){
(new InventoryCloseEvent($this->currentWindow, $this))->call();

View File

@ -32,6 +32,7 @@ use pocketmine\scheduler\TaskScheduler;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use function count;
use function dirname;
@ -162,9 +163,9 @@ abstract class PluginBase implements Plugin, CommandExecutor{
private function registerYamlCommands() : void{
$pluginCmds = [];
foreach($this->getDescription()->getCommands() as $key => $data){
foreach(Utils::stringifyKeys($this->getDescription()->getCommands()) as $key => $data){
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;
}
@ -180,7 +181,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$aliasList = [];
foreach($data->getAliases() as $alias){
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;
}
$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);
if($gotVersion === false){
return KnownTranslationFactory::pocketmine_plugin_extensionNotLoaded($extensionName);

View File

@ -168,9 +168,20 @@ class PluginManager{
}
$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);
$everyoneRoot = $permManager->getPermission(DefaultPermissions::ROOT_USER);
foreach($description->getPermissions() as $default => $perms){
foreach(Utils::stringifyKeys($description->getPermissions()) as $default => $perms){
foreach($perms as $perm){
$permManager->addPermission($perm);
switch($default){
@ -334,7 +345,7 @@ class PluginManager{
while(count($triage->plugins) > 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, "soft", $triage->softDependencies, $loadedPlugins, $triage);
@ -366,7 +377,7 @@ class PluginManager{
//No plugins loaded :(
//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])){
foreach($triage->softDependencies[$name] as $k => $dependency){
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])){
$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())));
}
break;

View File

@ -23,13 +23,14 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use pocketmine\utils\Utils;
use function count;
use function file;
use function implode;
use function is_file;
use function preg_match;
use function strlen;
use function strpos;
use function substr;
use function trim;
use const FILE_IGNORE_NEW_LINES;
use const FILE_SKIP_EMPTY_LINES;
@ -60,30 +61,27 @@ class ScriptPluginLoader implements PluginLoader{
return null;
}
$data = [];
$insideHeader = false;
$docCommentLines = [];
foreach($content as $line){
if(!$insideHeader and strpos($line, "/**") !== false){
$insideHeader = true;
}
if(preg_match("/^[ \t]+\\*[ \t]+@([a-zA-Z]+)([ \t]+(.*))?$/", $line, $matches) > 0){
$key = $matches[1];
$content = trim($matches[3] ?? "");
if($key === "notscript"){
return null;
if(!$insideHeader){
if(strpos($line, "/**") !== false){
$insideHeader = true;
}else{
continue;
}
$data[$key] = $content;
}
if($insideHeader and strpos($line, "*/") !== false){
$docCommentLines[] = $line;
if(strpos($line, "*/") !== false){
break;
}
}
if($insideHeader){
$data = Utils::parseDocComment(implode("\n", $docCommentLines));
if(count($data) !== 0){
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
foreach($playerList as $k => $v){
$playerList[$k] = md5($v);
}
$playerList = array_map('md5', $playerList);
$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{
$this->config[$k] = $v;
$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 . ".")){
unset($this->nestedCache[$nestedKey]);
}
@ -487,7 +487,7 @@ class Config{
*/
private function fillDefaults(array $default, &$data) : int{
$changed = 0;
foreach($default as $k => $v){
foreach(Utils::stringifyKeys($default) as $k => $v){
if(is_array($v)){
if(!isset($data[$k]) or !is_array($data[$k])){
$data[$k] = [];
@ -536,7 +536,7 @@ class Config{
*/
public static function writeProperties(array $config) : string{
$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)){
$v = $v ? "on" : "off";
}

View File

@ -163,7 +163,8 @@ final class Filesystem{
$result = str_replace([DIRECTORY_SEPARATOR, ".php", "phar://"], ["/", "", ""], $path);
//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), "/");
if(strpos($result, $cleanPath) === 0){
$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
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]);
}
@ -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{
if(!mb_check_encoding($string, '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\Internet;
use pocketmine\utils\InternetException;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use Webmozart\PathUtil\Path;
use function fgets;
@ -69,7 +70,7 @@ class SetupWizard{
}
$this->message("Please select a language");
foreach($langs as $short => $native){
foreach(Utils::stringifyKeys($langs) as $short => $native){
$this->writeLine(" $native => $short");
}

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