mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-10 11:38:08 +00:00
Compare commits
158 Commits
4.0.0-BETA
...
4.0.0-BETA
Author | SHA1 | Date | |
---|---|---|---|
6d62b06ce6 | |||
8be92d16fe | |||
8079ae341a | |||
ba295dc7dc | |||
38325c8573 | |||
f239b077b9 | |||
6f8f460a6c | |||
882df94bcb | |||
4a8ca603a1 | |||
52f0c4f3ed | |||
e2815eed60 | |||
932a88764c | |||
9540193766 | |||
cc23e0b7a1 | |||
1f9400f901 | |||
e5149756a8 | |||
bc18969a09 | |||
c19174a174 | |||
f95142f6b6 | |||
7ace24caab | |||
32f619ac49 | |||
1bb6ac4fb6 | |||
533d3aae8b | |||
52a891ba73 | |||
71b813d4f9 | |||
f2540a72ad | |||
03f13495b7 | |||
7e0f6c02a1 | |||
1bc7869f6e | |||
5556861000 | |||
7dd5d0b593 | |||
9338d42742 | |||
9346ecdc39 | |||
c023c02b6c | |||
bb7683158f | |||
fad96b77ce | |||
40575a6dcf | |||
40f8f042da | |||
0fe6038c41 | |||
adff561483 | |||
ad56392d95 | |||
472ffb28ff | |||
726c5652f7 | |||
b784a04e08 | |||
5c7125f190 | |||
eb0cf52d81 | |||
d8f0fd0a7e | |||
fb0eebc0dc | |||
020cd7b966 | |||
c37c261c0f | |||
2bb97d8904 | |||
d3878b2d57 | |||
cbe0f44c4f | |||
37622e02b8 | |||
ed8b4950a3 | |||
fc7d297f60 | |||
7b4ef293bd | |||
c72d66f370 | |||
3683884b9c | |||
37e8b1ee8c | |||
046dafc34f | |||
db135788b9 | |||
b34e6f53eb | |||
b4b954cc5f | |||
7210db25b0 | |||
4599913034 | |||
c48aa274e7 | |||
269231c228 | |||
4cad552909 | |||
f2d5455c5e | |||
65247b7248 | |||
2f408708f0 | |||
3dd03075cb | |||
82b5bca83e | |||
639867a640 | |||
d4a382d568 | |||
399824c31c | |||
ada469bc45 | |||
dc8243f88b | |||
7668171c56 | |||
e4754ab029 | |||
3276047497 | |||
49a8eff11e | |||
73592349cd | |||
635a9143de | |||
c3ec9c0948 | |||
09a2e006a8 | |||
fed59d3ebe | |||
c7beb0a702 | |||
5be429a8c4 | |||
ab002ca06d | |||
6efb1db107 | |||
6fdcfb01c8 | |||
1beec348f9 | |||
7306a2d939 | |||
4bf338f783 | |||
255ff63fda | |||
d72f6a3ac6 | |||
93a1e84ad9 | |||
c33f97ae41 | |||
cc4bb91fcb | |||
eb9012401b | |||
3b34268ed6 | |||
4c07078586 | |||
19a3efe893 | |||
a1ecdc27e5 | |||
f93b5be789 | |||
1fb60b5b3a | |||
08420c2556 | |||
18f5fb66bb | |||
a6f6b60bed | |||
c6c992a1f0 | |||
df39a1ca07 | |||
be6d1843de | |||
2b0b9bd8ed | |||
76dad46e13 | |||
eb3530b6e6 | |||
4131bcef08 | |||
b84f7c18ec | |||
45edb94607 | |||
6b316dc29a | |||
d9d37f7fa6 | |||
4cb6c7dc1e | |||
b392651354 | |||
f81c55ce6c | |||
002feacf8e | |||
b8523f7a18 | |||
640e88009b | |||
6cd272c9e1 | |||
566c57bcd3 | |||
3c754b079c | |||
dbf9a33160 | |||
07b1cff306 | |||
0989c77037 | |||
5107d0df4e | |||
579ef63663 | |||
8abc952c74 | |||
4c3a5fdd73 | |||
54f287feb6 | |||
84f8b3eb2d | |||
15fca84f3b | |||
c60144210f | |||
8ac999cbd4 | |||
4f8501ff34 | |||
2405e45b35 | |||
e0b07ff308 | |||
729f831b8f | |||
0356716e8e | |||
5c81b04813 | |||
1ebb206762 | |||
29e2d92098 | |||
ef82a2cd79 | |||
87031627bf | |||
f066199971 | |||
a0e9eec652 | |||
fa6a432d58 | |||
102277c636 | |||
f50f26d52e |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -4,6 +4,7 @@
|
||||
*.sh text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.properties text eol=lf
|
||||
*.neon text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
13
.github/workflows/draft-release.yml
vendored
13
.github/workflows/draft-release.yml
vendored
@ -35,17 +35,18 @@ jobs:
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
|
||||
|
||||
- name: Patch VersionInfo
|
||||
- name: Calculate build number
|
||||
id: build-number
|
||||
run: |
|
||||
BUILD_NUMBER=2000+$GITHUB_RUN_NUMBER #to stay above jenkins
|
||||
BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins
|
||||
echo "Build number: $BUILD_NUMBER"
|
||||
sed -i "s/const BUILD_NUMBER = 0/const BUILD_NUMBER = ${BUILD_NUMBER}/" src/VersionInfo.php
|
||||
echo ::set-output name=BUILD_NUMBER::$BUILD_NUMBER
|
||||
|
||||
- name: Minify BedrockData JSON files
|
||||
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 }}
|
||||
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }} --build ${{ steps.build-number.outputs.BUILD_NUMBER }}
|
||||
|
||||
- name: Get PocketMine-MP release version
|
||||
id: get-pm-version
|
||||
@ -56,7 +57,7 @@ jobs:
|
||||
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
|
||||
|
||||
- name: Generate build info
|
||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} > build_info.json
|
||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json
|
||||
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
|
102
.github/workflows/main.yml
vendored
102
.github/workflows/main.yml
vendored
@ -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
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -23,15 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
if(count($argv) !== 4){
|
||||
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)>");
|
||||
if(count($argv) !== 5){
|
||||
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number>");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
|
||||
"base_version" => \pocketmine\VersionInfo::BASE_VERSION,
|
||||
"build" => \pocketmine\VersionInfo::BUILD_NUMBER,
|
||||
"build" => (int) $argv[4],
|
||||
"is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD,
|
||||
"channel" => \pocketmine\VersionInfo::BUILD_CHANNEL,
|
||||
"git_commit" => $argv[1],
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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");
|
||||
|
||||
|
Submodule build/php updated: 58ea62d7ca...bd329dba08
@ -134,13 +134,18 @@ function main() : void{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$opts = getopt("", ["out:", "git:"]);
|
||||
$opts = getopt("", ["out:", "git:", "build:"]);
|
||||
if(isset($opts["git"])){
|
||||
$gitHash = $opts["git"];
|
||||
}else{
|
||||
$gitHash = Git::getRepositoryStatePretty(dirname(__DIR__));
|
||||
echo "Git hash detected as $gitHash" . PHP_EOL;
|
||||
}
|
||||
if(isset($opts["build"])){
|
||||
$build = (int) $opts["build"];
|
||||
}else{
|
||||
$build = 0;
|
||||
}
|
||||
foreach(buildPhar(
|
||||
$opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
|
||||
dirname(__DIR__) . DIRECTORY_SEPARATOR,
|
||||
@ -150,7 +155,8 @@ function main() : void{
|
||||
'vendor'
|
||||
],
|
||||
[
|
||||
'git' => $gitHash
|
||||
'git' => $gitHash,
|
||||
'build' => $build
|
||||
],
|
||||
<<<'STUB'
|
||||
<?php
|
||||
|
@ -21,3 +21,18 @@ Plugin developers should **only** update their required API to this version if y
|
||||
- Fixed crash in `Player->showPlayer()` when the target is not in the same world.
|
||||
- `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.
|
||||
|
||||
# 3.25.5
|
||||
- Protocol: Fixed incorrect encoding in `StructureSettings`
|
||||
- Fixed reading tags from non-docblock comments in script plugins.
|
||||
- Build number is now defined in phar metadata instead of being patched into the source code directly.
|
||||
|
||||
# 3.25.6
|
||||
- Fixed borked build number in release build of 3.25.5.
|
||||
|
@ -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,152 @@ 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.
|
||||
|
||||
# 4.0.0-BETA14
|
||||
Released 30th November 2021.
|
||||
|
||||
## General
|
||||
- The server will now log an EMERGENCY-level message when `forceShutdown()` is used for any other reason than a graceful shutdown.
|
||||
- The server will now attempt to translate invalid blocks to valid equivalents when loading chunks. This fixes many issues with `update!` blocks appearing in worlds, particularly ghost structures (these would appear when world editors previously erased some blocks by setting their IDs but not metadata).
|
||||
|
||||
## Fixes
|
||||
- Fixed `ConsoleReaderThread` spawning many zombie processes when running a server inside a Docker container.
|
||||
|
@ -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"
|
||||
@ -78,7 +79,7 @@
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
|
||||
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
|
||||
"make-server": [
|
||||
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
|
||||
"@php -dphar.readonly=0 build/server-phar.php"
|
||||
|
106
composer.lock
generated
106
composer.lock
generated
@ -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.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "a6e4eb22587e0014f6d658732cf633262723f8d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/a6e4eb22587e0014f6d658732cf633262723f8d3",
|
||||
"reference": "a6e4eb22587e0014f6d658732cf633262723f8d3",
|
||||
"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.20"
|
||||
},
|
||||
"time": "2021-11-25T20:56:12+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",
|
||||
|
@ -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
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1378,6 +1413,9 @@ class Server{
|
||||
echo "\x1b]0;\x07";
|
||||
}
|
||||
|
||||
if($this->isRunning){
|
||||
$this->logger->emergency("Forcing server shutdown");
|
||||
}
|
||||
try{
|
||||
if(!$this->isRunning()){
|
||||
$this->sendUsage(SendUsageTask::TYPE_CLOSE);
|
||||
@ -1476,6 +1514,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 +1549,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 +1563,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 +1572,7 @@ class Server{
|
||||
}
|
||||
}
|
||||
|
||||
if($dump->getData()["error"]["type"] === \ParseError::class){
|
||||
if($dump->getData()->error["type"] === \ParseError::class){
|
||||
$report = false;
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,14 @@ namespace pocketmine;
|
||||
|
||||
use pocketmine\utils\Git;
|
||||
use pocketmine\utils\VersionString;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
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-BETA14";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_NUMBER = 0;
|
||||
public const BUILD_CHANNEL = "beta";
|
||||
|
||||
private function __construct(){
|
||||
@ -61,12 +62,29 @@ final class VersionInfo{
|
||||
return self::$gitHash;
|
||||
}
|
||||
|
||||
private static ?int $buildNumber = null;
|
||||
|
||||
public static function BUILD_NUMBER() : int{
|
||||
if(self::$buildNumber === null){
|
||||
self::$buildNumber = 0;
|
||||
if(\Phar::running(true) !== ""){
|
||||
$phar = new \Phar(\Phar::running(false));
|
||||
$meta = $phar->getMetadata();
|
||||
if(is_array($meta) && isset($meta["build"]) && is_int($meta["build"])){
|
||||
self::$buildNumber = $meta["build"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$buildNumber;
|
||||
}
|
||||
|
||||
/** @var VersionString|null */
|
||||
private static $fullVersion = null;
|
||||
|
||||
public static function VERSION() : VersionString{
|
||||
if(self::$fullVersion === null){
|
||||
self::$fullVersion = new VersionString(self::BASE_VERSION, self::IS_DEVELOPMENT_BUILD, self::BUILD_NUMBER);
|
||||
self::$fullVersion = new VersionString(self::BASE_VERSION, self::IS_DEVELOPMENT_BUILD, self::BUILD_NUMBER());
|
||||
}
|
||||
return self::$fullVersion;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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[]
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
37
src/block/inventory/CraftingTableInventory.php
Normal file
37
src/block/inventory/CraftingTableInventory.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function fclose;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
@ -44,7 +45,9 @@ final class ConsoleReader{
|
||||
fclose($this->stdin);
|
||||
}
|
||||
|
||||
$this->stdin = fopen("php://stdin", "r");
|
||||
$stdin = fopen("php://stdin", "r");
|
||||
if($stdin === false) throw new AssumptionFailedError("Opening stdin should never fail");
|
||||
$this->stdin = $stdin;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,11 +63,10 @@ final class ConsoleReader{
|
||||
if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds
|
||||
return null;
|
||||
}elseif($count === false){ //stream error
|
||||
$this->initStdin();
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -46,7 +46,10 @@ if($socket === false){
|
||||
$consoleReader = new ConsoleReader();
|
||||
while(!feof($socket)){
|
||||
$line = $consoleReader->readLine();
|
||||
if($line !== null){
|
||||
fwrite($socket, $line . "\n");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ use function base64_encode;
|
||||
use function fgets;
|
||||
use function fopen;
|
||||
use function preg_replace;
|
||||
use function proc_close;
|
||||
use function proc_open;
|
||||
use function proc_terminate;
|
||||
use function sprintf;
|
||||
@ -116,6 +117,9 @@ final class ConsoleReaderThread extends Thread{
|
||||
|
||||
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
|
||||
if($command === ""){
|
||||
continue;
|
||||
}
|
||||
$buffer[] = $command;
|
||||
if($notifier !== null){
|
||||
$notifier->wakeupSleeper();
|
||||
@ -127,6 +131,7 @@ final class ConsoleReaderThread extends Thread{
|
||||
//gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in
|
||||
//the first place).
|
||||
proc_terminate($sub);
|
||||
proc_close($sub);
|
||||
stream_socket_shutdown($client, STREAM_SHUT_RDWR);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
84
src/crash/CrashDumpData.php
Normal file
84
src/crash/CrashDumpData.php
Normal 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;
|
||||
}
|
||||
}
|
46
src/crash/CrashDumpDataGeneral.php
Normal file
46
src/crash/CrashDumpDataGeneral.php
Normal 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,
|
||||
){}
|
||||
}
|
45
src/crash/CrashDumpDataPluginEntry.php
Normal file
45
src/crash/CrashDumpDataPluginEntry.php
Normal 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,
|
||||
){}
|
||||
}
|
103
src/crash/CrashDumpRenderer.php
Normal file
103
src/crash/CrashDumpRenderer.php
Normal 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);
|
||||
}
|
||||
}
|
28
src/data/SavedDataLoadingException.php
Normal file
28
src/data/SavedDataLoadingException.php
Normal 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{
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
73
src/entity/effect/StringToEffectParser.php
Normal file
73
src/entity/effect/StringToEffectParser.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
51
src/event/player/PlayerEmoteEvent.php
Normal file
51
src/event/player/PlayerEmoteEvent.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
36
src/inventory/PlayerCraftingInventory.php
Normal file
36
src/inventory/PlayerCraftingInventory.php
Normal 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; }
|
||||
}
|
@ -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;
|
||||
|
||||
|
28
src/inventory/TemporaryInventory.php
Normal file
28
src/inventory/TemporaryInventory.php
Normal 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{
|
||||
|
||||
}
|
@ -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");
|
||||
|
@ -252,6 +252,7 @@ class ItemFactory{
|
||||
$this->register(new Steak(new ItemIdentifier(ItemIds::STEAK, 0), "Steak"));
|
||||
$this->register(new Stick(new ItemIdentifier(ItemIds::STICK, 0), "Stick"));
|
||||
$this->register(new StringItem(new ItemIdentifier(ItemIds::STRING, 0), "String"));
|
||||
$this->register(new SweetBerries(new ItemIdentifier(ItemIds::SWEET_BERRIES, 0), "Sweet Berries"));
|
||||
$this->register(new Totem(new ItemIdentifier(ItemIds::TOTEM, 0), "Totem of Undying"));
|
||||
$this->register(new WheatSeeds(new ItemIdentifier(ItemIds::WHEAT_SEEDS, 0), "Wheat Seeds"));
|
||||
$this->register(new WritableBook(new ItemIdentifier(ItemIds::WRITABLE_BOOK, 0), "Book & Quill"));
|
||||
@ -327,7 +328,6 @@ class ItemFactory{
|
||||
//TODO: minecraft:shield
|
||||
//TODO: minecraft:sparkler
|
||||
//TODO: minecraft:spawn_egg
|
||||
$this->register(new SweetBerries(new ItemIdentifier(ItemIds::SWEET_BERRIES, 0), "Sweet Berries"));
|
||||
//TODO: minecraft:tnt_minecart
|
||||
//TODO: minecraft:trident
|
||||
//TODO: minecraft:turtle_helmet
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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[]
|
||||
*/
|
||||
|
66
src/item/enchantment/StringToEnchantmentParser.php
Normal file
66
src/item/enchantment/StringToEnchantmentParser.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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{
|
||||
|
@ -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){
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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"]);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user