Merge branch 'next-minor' into r13-world-support

This commit is contained in:
Dylan K. Taylor 2022-01-27 16:47:46 +00:00
commit f01887fb1e
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
524 changed files with 14889 additions and 6818 deletions

1
.gitattributes vendored
View File

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

View File

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

View File

@ -13,20 +13,14 @@ jobs:
strategy: strategy:
matrix: matrix:
image: [ubuntu-20.04] image: [ubuntu-20.04]
php: [8.0.9] php: [8.0.14]
steps: steps:
- uses: actions/checkout@v2 #needed for build.sh - name: Build and prepare PHP cache
- name: Check for PHP build cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
id: php-build-cache
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Compile PHP
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: ./tests/gh-actions/build.sh "${{ matrix.php }}"
phpstan: phpstan:
name: PHPStan analysis name: PHPStan analysis
@ -37,28 +31,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
image: [ubuntu-20.04] image: [ubuntu-20.04]
php: [8.0.9] php: [8.0.14]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -87,30 +69,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
image: [ubuntu-20.04] image: [ubuntu-20.04]
php: [8.0.9] php: [8.0.14]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup PHP
uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
with: with:
submodules: true php-version: ${{ matrix.php }}
install-path: "./bin"
- 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
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -139,30 +107,18 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
image: [ubuntu-20.04] image: [ubuntu-20.04]
php: [8.0.9] php: [8.0.14]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Restore PHP build cache - name: Setup PHP
id: php-build-cache uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
uses: actions/cache@v2
with: with:
path: "./bin" php-version: ${{ matrix.php }}
key: "php-build-generic-${{ matrix.php }}-${{ matrix.image }}-${{ hashFiles('./tests/gh-actions/build.sh') }}" install-path: "./bin"
- name: Kill build on PHP build cache miss (should never happen)
if: steps.php-build-cache.outputs.cache-hit != 'true'
run: exit 1
- name: Install cached PHP's dependencies
if: steps.php-build-cache.outputs.cache-hit == 'true'
run: ./tests/gh-actions/install-dependencies.sh
- name: Prefix PHP to PATH
run: echo "$(pwd)/bin/php7/bin" >> $GITHUB_PATH
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -191,30 +147,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
image: [ubuntu-20.04] image: [ubuntu-20.04]
php: [8.0.9] php: [8.0.14]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup PHP
uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
with: with:
submodules: true php-version: ${{ matrix.php }}
install-path: "./bin"
- 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
- name: Install Composer - name: Install Composer
run: curl -sS https://getcomposer.org/installer | php run: curl -sS https://getcomposer.org/installer | php
@ -238,11 +180,10 @@ jobs:
- name: Regenerate KnownTranslation APIs - name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php run: php build/generate-known-translation-apis.php
- name: Run git diff - name: Verify code is unchanged
run: git diff run: |
git diff
- name: Fail job if changes were made git diff --quiet
run: git diff --quiet
codestyle: codestyle:
name: Code Style checks name: Code Style checks
@ -254,10 +195,10 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.9.0 uses: shivammathur/setup-php@2.15.0
with: with:
php-version: 8.0 php-version: 8.0
tools: php-cs-fixer tools: php-cs-fixer:3.2
- name: Run PHP-CS-Fixer - name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff run: php-cs-fixer fix --dry-run --diff --ansi

View File

@ -22,8 +22,6 @@
declare(strict_types=1); declare(strict_types=1);
const VERSIONS = [ const VERSIONS = [
"7.3",
"7.4",
"8.0" "8.0"
]; ];

6
.gitmodules vendored
View File

@ -1,12 +1,6 @@
[submodule "resources/locale"]
path = resources/locale
url = https://github.com/pmmp/Language.git
[submodule "tests/plugins/DevTools"] [submodule "tests/plugins/DevTools"]
path = tests/plugins/DevTools path = tests/plugins/DevTools
url = https://github.com/pmmp/DevTools.git url = https://github.com/pmmp/DevTools.git
[submodule "build/php"] [submodule "build/php"]
path = build/php path = build/php
url = https://github.com/pmmp/php-build-scripts.git url = https://github.com/pmmp/php-build-scripts.git
[submodule "resources/vanilla"]
path = resources/vanilla
url = https://github.com/pmmp/BedrockData.git

View File

@ -18,6 +18,9 @@ return (new PhpCsFixer\Config)
'array_syntax' => [ 'array_syntax' => [
'syntax' => 'short' 'syntax' => 'short'
], ],
'binary_operator_spaces' => [
'default' => 'single_space'
],
'blank_line_after_namespace' => true, 'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true, 'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => [ 'blank_line_before_statement' => [
@ -33,12 +36,14 @@ return (new PhpCsFixer\Config)
], ],
'declare_strict_types' => true, 'declare_strict_types' => true,
'elseif' => true, 'elseif' => true,
'fully_qualified_strict_types' => true,
'global_namespace_import' => [ 'global_namespace_import' => [
'import_constants' => true, 'import_constants' => true,
'import_functions' => true, 'import_functions' => true,
'import_classes' => null, 'import_classes' => null,
], ],
'indentation_type' => true, 'indentation_type' => true,
'logical_operators' => true,
'native_function_invocation' => [ 'native_function_invocation' => [
'scope' => 'namespaced', 'scope' => 'namespaced',
'include' => ['@all'], 'include' => ['@all'],
@ -61,10 +66,20 @@ return (new PhpCsFixer\Config)
], ],
'sort_algorithm' => 'alpha' 'sort_algorithm' => 'alpha'
], ],
'phpdoc_line_span' => [
'property' => 'single',
'method' => null,
'const' => null
],
'phpdoc_trim' => true, 'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true,
'return_type_declaration' => [
'space_before' => 'one'
],
'single_blank_line_at_eof' => true,
'single_import_per_statement' => true, 'single_import_per_statement' => true,
'strict_param' => true, 'strict_param' => true,
'unary_operator_spaces' => true,
]) ])
->setFinder($finder) ->setFinder($finder)
->setIndent("\t") ->setIndent("\t")

View File

@ -2,25 +2,24 @@
## Pre-requisites ## Pre-requisites
- A bash shell (git bash is sufficient for Windows) - A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell - [`git`](https://git-scm.com) available in your shell
- PHP 7.4 or newer available in your shell - PHP 8.0 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell - [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries ## Custom PHP binaries
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries. Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-7.4-Aggregate) - [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate)
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php` - [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`. If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
## Setting up environment ## Setting up environment
1. `git clone --recursive https://github.com/pmmp/PocketMine-MP.git` 1. `git clone https://github.com/pmmp/PocketMine-MP.git`
2. `composer install` 2. `composer install`
## Checking out a different branch to build ## Checking out a different branch to build
1. `git checkout <branch to checkout>` 1. `git checkout <branch to checkout>`
2. `git submodule update --init` 2. Re-run `composer install` to synchronize dependencies.
3. Re-run `composer install` to synchronize dependencies.
## Optimizing for release builds ## Optimizing for release builds
1. Add the flags `--no-dev --classmap-authoritative` to your `composer install` command. This will reduce build size and improve autoloading speed. 1. Add the flags `--no-dev --classmap-authoritative` to your `composer install` command. This will reduce build size and improve autoloading speed.
@ -34,7 +33,7 @@ There is a bug in PHP that might cause an error which looks like this:
``` ```
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119 Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
``` ```
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 7.4.16 or 8.0.3. You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3.
## Running PocketMine-MP from source code ## Running PocketMine-MP from source code
Run `src/PocketMine.php` using your preferred PHP binary. Run `src/PocketMine.php` using your preferred PHP binary.

View File

@ -4,10 +4,13 @@
</p> </p>
<p align="center"> <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/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" /></a>
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img src="https://img.shields.io/github/v/tag/pmmp/PocketMine-MP?label=release&logo=github" alt="GitHub tag (latest semver)" /></a> <a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/pmmp/PocketMine-MP?label=release&sort=semver"></a>
<a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a> <a href="https://hub.docker.com/r/pmmp/pocketmine-mp"><img src="https://img.shields.io/docker/v/pmmp/pocketmine-mp?logo=docker&label=image" alt="Docker image version (latest semver)" /></a>
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a> <a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
<br>
<a href="https://github.com/pmmp/PocketMine-MP/releases"><img alt="GitHub all releases" src="https://img.shields.io/github/downloads/pmmp/PocketMine-MP/total?label=downloads%40total"></a>
<a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest by SemVer)" src="https://img.shields.io/github/downloads/pmmp/PocketMine-MP/latest/total?sort=semver"></a>
</p> </p>
## Getting started ## Getting started

View File

@ -23,15 +23,15 @@ declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';
if(count($argv) !== 4){ if(count($argv) !== 5){
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)>"); fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number>");
exit(1); exit(1);
} }
echo json_encode([ echo json_encode([
"php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION), "php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
"base_version" => \pocketmine\VersionInfo::BASE_VERSION, "base_version" => \pocketmine\VersionInfo::BASE_VERSION,
"build" => \pocketmine\VersionInfo::BUILD_NUMBER, "build" => (int) $argv[4],
"is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD, "is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD,
"channel" => \pocketmine\VersionInfo::BUILD_CHANNEL, "channel" => \pocketmine\VersionInfo::BUILD_CHANNEL,
"git_commit" => $argv[1], "git_commit" => $argv[1],
@ -40,4 +40,4 @@ echo json_encode([
"details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]", "details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]",
"download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar", "download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar",
"source_url" => "https://github.com/$argv[3]/tree/$argv[2]", "source_url" => "https://github.com/$argv[3]/tree/$argv[2]",
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\build\generate_known_translation_apis; namespace pocketmine\build\generate_known_translation_apis;
use pocketmine\lang\Translatable; use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function array_map; use function array_map;
use function count; use function count;
@ -40,6 +41,7 @@ use function preg_match_all;
use function str_replace; use function str_replace;
use function strtoupper; use function strtoupper;
use const INI_SCANNER_RAW; use const INI_SCANNER_RAW;
use const SORT_NUMERIC;
use const SORT_STRING; use const SORT_STRING;
use const STDERR; use const STDERR;
@ -94,13 +96,15 @@ function generate_known_translation_keys(array $languageDefinitions) : void{
/** /**
* This class contains constants for all the translations known to PocketMine-MP as per the used version of pmmp/Language. * This class contains constants for all the translations known to PocketMine-MP as per the used version of pmmp/Language.
* This class is generated automatically, do NOT modify it by hand. * This class is generated automatically, do NOT modify it by hand.
*
* @internal
*/ */
final class KnownTranslationKeys{ final class KnownTranslationKeys{
HEADER; HEADER;
ksort($languageDefinitions, SORT_STRING); ksort($languageDefinitions, SORT_STRING);
foreach($languageDefinitions as $k => $_){ foreach(Utils::stringifyKeys($languageDefinitions) as $k => $_){
echo "\tpublic const "; echo "\tpublic const ";
echo constantify($k); echo constantify($k);
echo " = \"" . $k . "\";\n"; echo " = \"" . $k . "\";\n";
@ -126,6 +130,8 @@ function generate_known_translation_factory(array $languageDefinitions) : void{
* This class contains factory methods for all the translations known to PocketMine-MP as per the used version of * This class contains factory methods for all the translations known to PocketMine-MP as per the used version of
* pmmp/Language. * pmmp/Language.
* This class is generated automatically, do NOT modify it by hand. * This class is generated automatically, do NOT modify it by hand.
*
* @internal
*/ */
final class KnownTranslationFactory{ final class KnownTranslationFactory{
@ -135,17 +141,22 @@ HEADER;
$parameterRegex = '/{%(.+?)}/'; $parameterRegex = '/{%(.+?)}/';
$translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName(); $translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName();
foreach($languageDefinitions as $key => $value){ foreach(Utils::stringifyKeys($languageDefinitions) as $key => $value){
$parameters = []; $parameters = [];
$allParametersPositional = true;
if(preg_match_all($parameterRegex, $value, $matches) > 0){ if(preg_match_all($parameterRegex, $value, $matches) > 0){
foreach($matches[1] as $parameterName){ foreach($matches[1] as $parameterName){
if(is_numeric($parameterName)){ if(is_numeric($parameterName)){
$parameters[$parameterName] = "param$parameterName"; $parameters[$parameterName] = "param$parameterName";
}else{ }else{
$parameters[$parameterName] = $parameterName; $parameters[$parameterName] = $parameterName;
$allParametersPositional = false;
} }
} }
} }
if($allParametersPositional){
ksort($parameters, SORT_NUMERIC);
}
echo "\tpublic static function " . echo "\tpublic static function " .
functionify($key) . functionify($key) .
"(" . implode(", ", array_map(fn(string $paramName) => "$translationContainerClass|string \$$paramName", $parameters)) . ") : $translationContainerClass{\n"; "(" . implode(", ", array_map(fn(string $paramName) => "$translationContainerClass|string \$$paramName", $parameters)) . ") : $translationContainerClass{\n";
@ -172,7 +183,7 @@ HEADER;
echo "Done generating KnownTranslationFactory.\n"; 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){ if($lang === false){
fwrite(STDERR, "Missing language files!\n"); fwrite(STDERR, "Missing language files!\n");
exit(1); exit(1);

View File

@ -58,7 +58,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
$memberLines = []; $memberLines = [];
foreach($members as $name => $member){ foreach($members as $name => $member){
$reflect = new \ReflectionClass($member); $reflect = new \ReflectionClass($member);
while($reflect !== false and $reflect->isAnonymous()){ while($reflect !== false && $reflect->isAnonymous()){
$reflect = $reflect->getParentClass(); $reflect = $reflect->getParentClass();
} }
if($reflect === false){ if($reflect === false){
@ -91,7 +91,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
throw new \RuntimeException("Failed to get contents of $file"); throw new \RuntimeException("Failed to get contents of $file");
} }
if(preg_match("/^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/^((final|abstract)\s+)?class /m', $contents) !== 1){ if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
continue; continue;
} }
$shortClassName = basename($file, ".php"); $shortClassName = basename($file, ".php");
@ -101,7 +101,7 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
} }
$reflect = new \ReflectionClass($className); $reflect = new \ReflectionClass($className);
$docComment = $reflect->getDocComment(); $docComment = $reflect->getDocComment();
if($docComment === false || preg_match("/^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){ if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){
continue; continue;
} }
echo "Found registry in $file\n"; echo "Found registry in $file\n";
@ -116,4 +116,3 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
echo "No changes made to file $file\n"; echo "No changes made to file $file\n";
} }
} }

View File

@ -23,25 +23,34 @@ declare(strict_types=1);
namespace pocketmine\build\make_release; namespace pocketmine\build\make_release;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString; use pocketmine\utils\VersionString;
use pocketmine\VersionInfo; use pocketmine\VersionInfo;
use function count; use function array_keys;
use function array_map;
use function dirname; use function dirname;
use function fgets; use function fgets;
use function file_get_contents; use function file_get_contents;
use function file_put_contents; use function file_put_contents;
use function fwrite; use function fwrite;
use function getopt;
use function is_string;
use function max;
use function preg_replace; use function preg_replace;
use function sleep; use function sleep;
use function sprintf; use function sprintf;
use function str_pad;
use function strlen;
use function system; use function system;
use const STDERR; use const STDERR;
use const STDIN; use const STDIN;
use const STDOUT;
use const STR_PAD_LEFT;
require_once dirname(__DIR__) . '/vendor/autoload.php'; require_once dirname(__DIR__) . '/vendor/autoload.php';
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev, string $channel) : void{ function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev, string $channel) : void{
$versionInfo = file_get_contents($versionInfoPath); $versionInfo = Utils::assumeNotFalse(file_get_contents($versionInfoPath), $versionInfoPath . " should always exist");
$versionInfo = preg_replace( $versionInfo = preg_replace(
$pattern = '/^([\t ]*public )?const BASE_VERSION = "(\d+)\.(\d+)\.(\d+)(?:-(.*))?";$/m', $pattern = '/^([\t ]*public )?const BASE_VERSION = "(\d+)\.(\d+)\.(\d+)(?:-(.*))?";$/m',
'$1const BASE_VERSION = "' . $newVersion . '";', '$1const BASE_VERSION = "' . $newVersion . '";',
@ -60,22 +69,46 @@ function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev
file_put_contents($versionInfoPath, $versionInfo); file_put_contents($versionInfoPath, $versionInfo);
} }
/** const ACCEPTED_OPTS = [
* @param string[] $argv "current" => "Version to insert and tag",
* @phpstan-param list<string> $argv "next" => "Version to put in the file after tagging",
*/ "channel" => "Release channel to post this build into"
function main(array $argv) : void{ ];
if(count($argv) < 2){
fwrite(STDERR, "Arguments: <channel> [release version] [next version]\n"); function systemWrapper(string $command, string $errorMessage) : void{
system($command, $result);
if($result !== 0){
echo "error: $errorMessage; aborting\n";
exit(1); exit(1);
} }
if(isset($argv[2])){ }
$currentVer = new VersionString($argv[2]);
}else{ function main() : void{
$currentVer = VersionInfo::VERSION(); $filteredOpts = [];
foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
$maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS)));
foreach(ACCEPTED_OPTS as $acceptedName => $description){
fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n");
} }
if(isset($argv[3])){ exit(0);
$nextVer = new VersionString($argv[3]); }
if(!is_string($optValue)){
fwrite(STDERR, "--$optName expects exactly 1 value\n");
exit(1);
}
$filteredOpts[$optName] = $optValue;
}
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
if(isset($filteredOpts["next"])){
$nextVer = new VersionString($filteredOpts["next"]);
}else{ }else{
$nextVer = new VersionString(sprintf( $nextVer = new VersionString(sprintf(
"%u.%u.%u", "%u.%u.%u",
@ -84,26 +117,29 @@ function main(array $argv) : void{
$currentVer->getPatch() + 1 $currentVer->getPatch() + 1
)); ));
} }
$channel = $filteredOpts["channel"] ?? VersionInfo::BUILD_CHANNEL;
echo "About to tag version $currentVer. Next version will be $nextVer.\n"; echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
echo "please add appropriate notes to the changelog and press enter..."; echo "please add appropriate notes to the changelog and press enter...";
fgets(STDIN); fgets(STDIN);
system('git add "' . dirname(__DIR__) . '/changelogs"'); systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes");
system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result); system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result);
if($result === 0){ if($result === 0){
echo "error: no changelog changes detected; aborting\n"; echo "error: no changelog changes detected; aborting\n";
exit(1); exit(1);
} }
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php'; $versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $argv[1]); replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
system('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"'); systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit");
system('git tag ' . $currentVer->getBaseVersion()); systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag");
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, "");
system('git add "' . $versionInfoPath . '"'); replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
system('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"'); systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit");
systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit");
echo "pushing changes in 5 seconds\n"; echo "pushing changes in 5 seconds\n";
sleep(5); sleep(5);
system('git push origin HEAD ' . $currentVer->getBaseVersion()); systemWrapper('git push origin HEAD ' . $currentVer->getBaseVersion(), "failed to push changes to remote");
} }
main($argv); main();

@ -1 +1 @@
Subproject commit ad9cd1fdb47742038365ec8ec7e2ed61ba58a068 Subproject commit bd329dba08242b6ecb99ef7fbf9a0031dcbbb3ff

View File

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

14
changelogs/3.23.md Normal file
View File

@ -0,0 +1,14 @@
**For Minecraft: Bedrock Edition 1.17.30**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.23.0
- Added support for Minecraft: Bedrock Edition 1.17.30.
- Removed compatibility with earlier versions.
# 3.23.1
- Fixed broken build of 3.23.0.

12
changelogs/3.24.md Normal file
View File

@ -0,0 +1,12 @@
**For Minecraft: Bedrock Edition 1.17.30**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.24.0
- PHP 8.0 is now required as a minimum.
- Fixed stats reporting checking the wrong `pocketmine.yml` property.
- Fixed `Projectile->move()` not respecting the given `dx`/`dy`/`dz` and using its own motion instead.

38
changelogs/3.25.md Normal file
View File

@ -0,0 +1,38 @@
**For Minecraft: Bedrock Edition 1.17.40**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.25.0
- Added support for Minecraft: Bedrock Edition 1.17.40.
- Removed compatibility with earlier versions.
# 3.25.1
- Fixed autosave bug that caused unmodified chunks to be saved at least once (during the first autosave after they were loaded).
- `Entity->spawnTo()` now has an additional sanity check for matching worlds (might expose a few new errors in plugins).
- Fixed a missing field in `CraftRecipeAuto` item stack request type.
# 3.25.2
- Now analysed using level 9 on PHPStan 1.0.0.
- `ext-pthreads` v4.0.0 or newer is now required.
- 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.

32
changelogs/3.26.md Normal file
View File

@ -0,0 +1,32 @@
**For Minecraft: Bedrock Edition 1.18.0**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.26.0
- Added support for Minecraft: Bedrock Edition 1.18.0.
- Removed compatibility with earlier versions.
# 3.26.1
- Fixed a bug in chunk sending that caused double chests to not be paired, signs to be blank, and various other issues.
# 3.26.2
- Improved error messages shown by `start.cmd`, `start.sh` and `start.ps1` when the PHP binary was not found.
- The value of PHPRC is now shown when erroring out due to unsatisfied PHP requirements.
- Removed restriction on the range of valid channels for `auto-updater.channel` in `pocketmine.yml`.
# 3.26.3
- `PlayerExperienceChangeEvent->setNewProgress()` now performs range checks. This fixes the root of a very old and confusing crash bug which took several years to identify the cause of.
- Note that the defective plugin(s) which caused this problem will still cause a server crash, but the plugin responsible will now get blamed correctly.
# 3.26.4
- Fixed skins appearing black when using RTX resource packs.
- Fixed chunks containing furnaces in old worlds (pre-2017) being discarded as corrupted.
- This was caused by a strict corruption check detecting bad data created by a bug in PocketMine-MP that was fixed in 2017.
# 3.26.5
- Fixed several denial-of-service attack vectors related to writable book text length and encoding.
- Fixed several denial-of-service attack vectors related to skin data field lengths.

15
changelogs/3.27.md Normal file
View File

@ -0,0 +1,15 @@
**For Minecraft: Bedrock Edition 1.18.0**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 3.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 3.27.0
- Introduced support for protocol encryption.
- Encryption is enabled by default.
- Fixes login replay attacks.
- This may cause some performance degradation.
- Encryption can be disabled by setting `network.enable-encryption` to `false` in `pocketmine.yml`. DO NOT do this unless you understand the risks involved.
- An obsoletion notice has been added to the console during server startup.

1767
changelogs/4.0-beta.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

166
changelogs/4.1.md Normal file
View File

@ -0,0 +1,166 @@
**For Minecraft: Bedrock Edition 1.18.0**
### Note about API versions
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
Plugin developers should **only** update their required API to this version if you need the changes in this build.
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
# 4.1.0-BETA1
Released 22nd January 2022.
## General
- Game mode names (e.g. `survival`, `creative`) may now be used for the `gamemode` property in `server.properties`.
- Increased default maximum render distance to 16 chunks. Players with a render distance smaller than this will notice no difference.
- The setup wizard now prompts for a maximum render distance value.
- The setup wizard now prompts for an IPv6 port selection. Previously it would always use 19133.
- `chunk-ticking.disable-block-ticking` now accepts block names like those used in the `/give` command.
- The `/clear` command now behaves more like vanilla:
- The order of inventories is now the same as Bedrock.
- The cursor and offhand inventories are now cleared if necessary.
## Technical
- `PlayerAuthInputPacket` is now used instead of `MovePlayerPacket` for processing movements. This improves position and rotation accuracy.
- `&&` and `||` are now always used instead of `and` and `or`.
- New version of `pocketmine/errorhandler` is used by this version, adding support for `ErrorToExceptionHandler::trap()`. This enables reliably capturing `E_WARNING` and `E_NOTICE` from functions such as `yaml_parse()` and friends.
- New dependency versions are required by this version:
- `pocketmine/bedrock-protocol` has been updated from 7.1.0 to [7.3.0](https://github.com/pmmp/BedrockProtocol/releases/tag/7.3.0%2Bbedrock-1.18.0).
- `pocketmine/errorhandler` has been updated from 0.3.0 to [0.6.0](https://github.com/pmmp/ErrorHandler/releases/tag/0.6.0).
## API
### Block
- The following classes have been added:
- `Lectern`
- `Pumpkin`
- The following public API methods have been added:
- `Block->getTypeId() : int` - returns an integer which uniquely identifies the block type, ignoring things like facing, colour etc.
- `VanillaBlocks::LECTERN()`
### Entity
- The following classes have been added:
- `animation\ItemEntityStackSizeChangeAnimation`
- The following public API methods have been added:
- `object\ItemEntity->isMergeable(object\ItemEntity $other) : bool`
- `object\ItemEntity->setStackSize(int $size) : void`
- `object\ItemEntity->tryMergeInto(object\ItemEntity $other) : bool`
- `ExperienceManager->canAttractXpOrbs() : bool`
- `ExperienceManager->setCanAttractXpOrbs(bool $v = true) : void`
- `Entity->getSize() : EntitySizeInfo`
- `Living->isGliding() : bool`
- `Living->isSwimming() : bool`
- `Living->setGliding(bool $value = true) : void`
- `Living->setSwimming(bool $value = true) : void`
- The following protected API methods have been added:
- `Entity->getBlocksIntersected(float $inset) : \Generator<int, Block, void, void>`
### Event
- `BlockSpreadEvent` is now called when fire spreads to the positions of blocks it burns away.
- `BlockFormEvent` is now called when concrete powder turns into concrete due to contact with water.
- The following classes have been added:
- `BlockMeltEvent` - called when ice or snow melts
- `ChestPairEvent` - called when two chests try to form a pair
- `PlayerToggleGlideEvent` - called when a player starts or stops gliding
- `PlayerToggleSwimEvent` - called when a player starts or stops swimming
### Item
- The following public API methods have been added:
- `SplashPotion->getType() : PotionType`
- `VanillaItems::AIR()`
- The following API methods have been deprecated:
- `ItemFactory::air()` - use `VanillaItems::AIR()` instead
### Player
- The following public API methods have been added:
- `Player->hasBlockCollision() : bool`
- `Player->setHasBlockCollision(bool $value)` - allows controlling spectator-like no-clip behaviour without changing game mode
- `Player->toggleSwim(bool $swim) : bool` - called by the network system when the client tries to start/stop swimming
- `Player->toggleGlide(bool $glide) : bool` - called by the network system when the client tries to start/stop gliding
### Server
- The following public API constants have been added:
- `Server::DEFAULT_SERVER_NAME`
- `Server::DEFAULT_MAX_PLAYERS`
- `Server::DEFAULT_PORT_IPV4`
- `Server::DEFAULT_PORT_IPV6`
- `Server::DEFAULT_MAX_VIEW_DISTANCE`
### Utils
- Config parsing errors are now always represented by `ConfigLoadException` and include the path to the file in the message.
- Added `TextFormat::MINECOIN_GOLD`, and support for it to the various `TextFormat` methods.
- The following public API methods have been added:
- `Utils::assumeNotFalse()` - static analysis crutch to silence PHPStan errors without using `ignoreErrors` or `@phpstan-ignore-line`, which are both too coarse.
- The following public API properties have been added:
- `Terminal::$COLOR_MINECOIN_GOLD`
- The following classes have been added:
- `ConfigLoadException`
- Fixed `Random->nextSignedInt()` to actually return a signed int. Previously it would return any integer value between 0 and 4,294,957,295.
- Fixed `Random->nextSignedFloat()` to return a float between `-1.0` and `1.0`. Previously it would return any value between `0.0` and `2.0`.
- `VersionString->getNumber()` output is now structured differently to fix overflow issues caused by the old format.
### World
- The following classes have been added:
- `sound\ItemUseOnBlockSound`
- `sound\LecternPlaceBookSound`
## Gameplay
### Blocks
- Fire now spreads.
- Implemented lectern blocks.
- Added missing sounds for hoeing grass and dirt.
- Added missing sounds for using a shovel on grass to create grass path.
- Pumpkins can now be carved using shears.
### Items
- Dropped items of the same type now merge with each other.
### Misc
- Implemented player swimming.
# 4.1.0-BETA2
Released 27th January 2022.
## API
### Block
- The following API methods have been added:
- `utils\BrewingStandSlot->getSlotNumber() : int`
- `utils\FurnaceType->getCookSound() : Sound`
- The following API constants have been added:
- `tile\BrewingStand::BREW_TIME_TICKS`
### Crafting
- The following API methods have been added:
- `CraftingManager->getPotionContainerChangeRecipes() : array<int, array<string, PotionContainerChangeRecipe>>`
- `CraftingManager->getPotionTypeRecipes() : array<string, array<string, PotionTypeRecipe>>`
- `CraftingManager->registerPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void`
- `CraftingManager->registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void`
- The following classes have been added:
- `BrewingRecipe`
- `PotionContainerChangeRecipe`
- `PotionTypeRecipe`
### Event
- The following classes have been added:
- `BrewItemEvent` - called when a brewing stand finishes brewing potions; this is called up to 3 times (once for each brewing slot, as needed)
- `BrewingFuelUseEvent` - called when a brewing stand consumes blaze powder
- `PlayerViewDistanceChangeEvent` - called whenever a player alters their render distance or requests one for the first time when connecting
### World
#### Sound
- The following classes have been added:
- `BlastFurnaceSound` - the sound made by a blast furnace during smelting
- `FurnaceSound` - the sound made by a regular furnace during cooking or smelting
- `PotionFinishBrewingSound` - the sound made by a brewing stand when a potion finishes being brewed
- `SmokerSound` - the sound made by a smoker during cooking
## Gameplay
### Blocks
- Brewing stands can now be used for brewing potions.
- The visual appearance of a brewing stand now updates correctly when the contents of its inventory changes (adding/removing potions).
- Added missing sounds for furnace, blast furnace and smoker.
- Fixed ender chest not dropping itself when mined with a Silk Touch pickaxe.
- Cobwebs now drop themselves when mined using shears.
- The correct amount of fall damage is now taken when falling from a height onto hay bales.
- Fixed block updating bug introduced by beta1 which caused crops and other plants to never grow.
### Misc
- Added a workaround for client hitbox size bug after swimming which caused the player to be able to fit into one-block-tall gaps.

View File

@ -7,7 +7,7 @@
"require": { "require": {
"php": "^8.0", "php": "^8.0",
"php-64bit": "*", "php-64bit": "*",
"ext-chunkutils2": "^0.3.0", "ext-chunkutils2": "^0.3.1",
"ext-crypto": "^0.3.1", "ext-crypto": "^0.3.1",
"ext-ctype": "*", "ext-ctype": "*",
"ext-curl": "*", "ext-curl": "*",
@ -22,7 +22,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-pcre": "*", "ext-pcre": "*",
"ext-phar": "*", "ext-phar": "*",
"ext-pthreads": "~3.2.0", "ext-pthreads": "^4.0",
"ext-reflection": "*", "ext-reflection": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"ext-sockets": "*", "ext-sockets": "*",
@ -34,28 +34,28 @@
"adhocore/json-comment": "^1.1", "adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3", "fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0", "netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-protocol": "2.0.0+bedrock1.17.30", "pocketmine/bedrock-data": "~1.5.0+bedrock-1.18.0",
"pocketmine/bedrock-protocol": "~7.3.0+bedrock-1.18.0",
"pocketmine/binaryutils": "^0.2.1", "pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2", "pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "dev-master", "pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0", "pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0", "pocketmine/errorhandler": "^0.6.0",
"pocketmine/log": "^0.3.0", "pocketmine/locale-data": "~2.4.2",
"pocketmine/log-pthreads": "^0.2.0", "pocketmine/log": "^0.4.0",
"pocketmine/math": "^0.3.0", "pocketmine/log-pthreads": "^0.4.0",
"pocketmine/nbt": "^0.3.0", "pocketmine/math": "^0.4.0",
"pocketmine/raklib": "^0.14.0", "pocketmine/nbt": "^0.3.2",
"pocketmine/raklib": "^0.14.2",
"pocketmine/raklib-ipc": "^0.1.0", "pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0", "pocketmine/snooze": "^0.3.0",
"pocketmine/spl": "dev-master",
"ramsey/uuid": "^4.1", "ramsey/uuid": "^4.1",
"respect/validation": "^2.0",
"webmozart/path-util": "^2.3" "webmozart/path-util": "^2.3"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "0.12.98", "phpstan/phpstan": "1.3.3",
"phpstan/phpstan-phpunit": "^0.12.6", "phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^0.12.2", "phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.2" "phpunit/phpunit": "^9.2"
}, },
"autoload": { "autoload": {
@ -79,7 +79,7 @@
"sort-packages": true "sort-packages": true
}, },
"scripts": { "scripts": {
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar", "make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
"make-server": [ "make-server": [
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs", "@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
"@php -dphar.readonly=0 build/server-phar.php" "@php -dphar.readonly=0 build/server-phar.php"

779
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,9 @@
includes: includes:
- tests/phpstan/configs/actual-problems.neon - tests/phpstan/configs/actual-problems.neon
- tests/phpstan/configs/check-explicit-mixed-baseline.neon
- tests/phpstan/configs/gc-hacks.neon - tests/phpstan/configs/gc-hacks.neon
- tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/impossible-generics.neon
- tests/phpstan/configs/l7-baseline.neon
- tests/phpstan/configs/l8-baseline.neon
- tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/php-bugs.neon
- tests/phpstan/configs/phpstan-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/pthreads-bugs.neon
- tests/phpstan/configs/runtime-type-checks.neon - tests/phpstan/configs/runtime-type-checks.neon
- tests/phpstan/configs/spl-fixed-array-sucks.neon - tests/phpstan/configs/spl-fixed-array-sucks.neon
- vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/extension.neon
@ -16,11 +12,11 @@ includes:
rules: rules:
- pocketmine\phpstan\rules\DisallowEnumComparisonRule - pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule # - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
parameters: parameters:
level: 8 level: 9
checkExplicitMixed: true
checkMissingCallableSignature: true checkMissingCallableSignature: true
treatPhpDocTypesAsCertain: false treatPhpDocTypesAsCertain: false
bootstrapFiles: bootstrapFiles:
@ -38,6 +34,9 @@ parameters:
- tests/phpunit - tests/phpunit
- tests/plugins/TesterPlugin - tests/plugins/TesterPlugin
- tools - tools
excludePaths:
analyseAndScan:
- build/php
dynamicConstantNames: dynamicConstantNames:
- pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD - pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD
- pocketmine\DEBUG - pocketmine\DEBUG
@ -54,5 +53,5 @@ parameters:
#variadics don't work for this - mixed probably shouldn't work either, but for now it does #variadics don't work for this - mixed probably shouldn't work either, but for now it does
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that, #what we actually need is something that accepts an infinite number of parameters, but in the absence of that,
#we'll just fill it with 10 - it's very unlikely to encounter a callable with 10 parameters anyway. #we'll just fill it with 10 - it's very unlikely to encounter a callable with 10 parameters anyway.
anyCallable: 'callable(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed' anyCallable: 'callable(never, never, never, never, never, never, never, never, never, never) : mixed'
anyClosure: '\Closure(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed) : mixed' anyClosure: '\Closure(never, never, never, never, never, never, never, never, never, never) : mixed'

View File

@ -1,4 +1,691 @@
{ {
"-2": -2,
"-3": -3,
"-4": -4,
"-5": -5,
"-6": -6,
"-7": -7,
"-8": -8,
"-9": -9,
"-10": -10,
"-11": -11,
"-12": -12,
"-13": -13,
"-14": -14,
"-15": -15,
"-16": -16,
"-17": -17,
"-18": -18,
"-19": -19,
"-20": -20,
"-21": -21,
"-22": -22,
"-23": -23,
"-24": -24,
"-25": -25,
"-26": -26,
"-27": -27,
"-28": -28,
"-29": -29,
"-30": -30,
"-31": -31,
"-32": -32,
"-33": -33,
"-34": -34,
"-35": -35,
"-36": -36,
"-37": -37,
"-38": -38,
"-39": -39,
"-40": -40,
"-41": -41,
"-42": -42,
"-43": -43,
"-44": -44,
"-45": -45,
"-46": -46,
"-47": -47,
"-48": -48,
"-49": -49,
"-50": -50,
"-51": -51,
"-52": -52,
"-53": -53,
"-54": -54,
"-55": -55,
"-56": -56,
"-57": -57,
"-58": -58,
"-59": -59,
"-60": -60,
"-61": -61,
"-62": -62,
"-63": -63,
"-64": -64,
"-65": -65,
"-66": -66,
"-67": -67,
"-68": -68,
"-69": -69,
"-70": -70,
"-71": -71,
"-72": -72,
"-73": -73,
"-74": -74,
"-75": -75,
"-76": -76,
"-77": -77,
"-78": -78,
"-79": -79,
"-80": -80,
"-81": -81,
"-82": -82,
"-83": -83,
"-84": -84,
"-85": -85,
"-86": -86,
"-87": -87,
"-88": -88,
"-89": -89,
"-90": -90,
"-91": -91,
"-92": -92,
"-93": -93,
"-94": -94,
"-95": -95,
"-96": -96,
"-97": -97,
"-98": -98,
"-99": -99,
"-100": -100,
"-101": -101,
"-102": -102,
"-103": -103,
"-104": -104,
"-105": -105,
"-106": -106,
"-107": -107,
"-108": -108,
"-109": -109,
"-110": -110,
"-111": -111,
"-112": -112,
"-113": -113,
"-114": -114,
"-115": -115,
"-116": -116,
"-117": -117,
"-118": -118,
"-119": -119,
"-120": -120,
"-121": -121,
"-122": -122,
"-123": -123,
"-124": -124,
"-125": -125,
"-126": -126,
"-127": -127,
"-128": -128,
"-129": -129,
"-130": -130,
"-131": -131,
"-132": -132,
"-133": -133,
"-134": -134,
"-135": -135,
"-136": -136,
"-137": -137,
"-138": -138,
"-139": -139,
"-140": -140,
"-141": -141,
"-142": -142,
"-143": -143,
"-144": -144,
"-145": -145,
"-146": -146,
"-147": -147,
"-148": -148,
"-149": -149,
"-150": -150,
"-151": -151,
"-152": -152,
"-153": -153,
"-154": -154,
"-155": -155,
"-156": -156,
"-157": -157,
"-159": -159,
"-160": -160,
"-161": -161,
"-162": -162,
"-163": -163,
"-164": -164,
"-165": -165,
"-166": -166,
"-167": -167,
"-168": -168,
"-169": -169,
"-170": -170,
"-171": -171,
"-172": -172,
"-173": -173,
"-174": -174,
"-175": -175,
"-176": -176,
"-177": -177,
"-178": -178,
"-179": -179,
"-180": -180,
"-181": -181,
"-182": -182,
"-183": -183,
"-184": -184,
"-185": -185,
"-186": -186,
"-187": -187,
"-188": -188,
"-189": -189,
"-190": -190,
"-191": -191,
"-192": -192,
"-193": -193,
"-194": -194,
"-195": -195,
"-196": -196,
"-197": -197,
"-198": -198,
"-199": -199,
"-200": -200,
"-201": -201,
"-202": -202,
"-203": -203,
"-204": -204,
"-206": -206,
"-207": -207,
"-208": -208,
"-209": -209,
"-210": -210,
"-211": -211,
"-213": -213,
"-214": -214,
"0": 0,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"10": 10,
"11": 11,
"12": 12,
"13": 13,
"14": 14,
"15": 15,
"16": 16,
"17": 17,
"18": 18,
"19": 19,
"20": 20,
"21": 21,
"22": 22,
"23": 23,
"24": 24,
"25": 25,
"26": 26,
"27": 27,
"28": 28,
"29": 29,
"30": 30,
"31": 31,
"32": 32,
"33": 33,
"34": 34,
"35": 35,
"36": 36,
"37": 37,
"38": 38,
"39": 39,
"40": 40,
"41": 41,
"42": 42,
"43": 43,
"44": 44,
"45": 45,
"46": 46,
"47": 47,
"48": 48,
"49": 49,
"50": 50,
"51": 51,
"52": 52,
"53": 53,
"54": 54,
"55": 55,
"56": 56,
"57": 57,
"58": 58,
"59": 59,
"60": 60,
"61": 61,
"62": 62,
"63": 63,
"64": 64,
"65": 65,
"66": 66,
"67": 67,
"68": 68,
"69": 69,
"70": 70,
"71": 71,
"72": 72,
"73": 73,
"74": 74,
"75": 75,
"76": 76,
"77": 77,
"78": 78,
"79": 79,
"80": 80,
"81": 81,
"82": 82,
"83": 83,
"84": 84,
"85": 85,
"86": 86,
"87": 87,
"88": 88,
"89": 89,
"90": 90,
"91": 91,
"92": 92,
"93": 93,
"94": 94,
"95": 95,
"96": 96,
"97": 97,
"98": 98,
"99": 99,
"100": 100,
"101": 101,
"102": 102,
"103": 103,
"104": 104,
"105": 105,
"106": 106,
"107": 107,
"108": 108,
"109": 109,
"110": 110,
"111": 111,
"112": 112,
"113": 113,
"114": 114,
"115": 115,
"116": 116,
"117": 117,
"118": 118,
"119": 119,
"120": 120,
"121": 121,
"122": 122,
"123": 123,
"124": 124,
"125": 125,
"126": 126,
"127": 127,
"128": 128,
"129": 129,
"130": 130,
"131": 131,
"132": 132,
"133": 133,
"134": 134,
"135": 135,
"136": 136,
"137": 137,
"138": 138,
"139": 139,
"140": 140,
"141": 141,
"142": 142,
"143": 143,
"144": 144,
"145": 145,
"146": 146,
"147": 147,
"148": 148,
"149": 149,
"150": 150,
"151": 151,
"152": 152,
"153": 153,
"154": 154,
"155": 155,
"156": 156,
"157": 157,
"158": 158,
"159": 159,
"160": 160,
"161": 161,
"162": 162,
"163": 163,
"164": 164,
"165": 165,
"166": 166,
"167": 167,
"168": 168,
"169": 169,
"170": 170,
"171": 171,
"172": 172,
"173": 173,
"174": 174,
"175": 175,
"176": 176,
"177": 177,
"178": 178,
"179": 179,
"180": 180,
"181": 181,
"182": 182,
"183": 183,
"184": 184,
"185": 185,
"186": 186,
"187": 187,
"188": 188,
"189": 189,
"190": 190,
"191": 191,
"192": 192,
"193": 193,
"194": 194,
"195": 195,
"196": 196,
"197": 197,
"198": 198,
"199": 199,
"200": 200,
"201": 201,
"202": 202,
"203": 203,
"204": 204,
"205": 205,
"206": 206,
"207": 207,
"208": 208,
"209": 209,
"213": 213,
"214": 214,
"215": 215,
"216": 216,
"218": 218,
"219": 219,
"220": 220,
"221": 221,
"222": 222,
"223": 223,
"224": 224,
"225": 225,
"226": 226,
"227": 227,
"228": 228,
"229": 229,
"231": 231,
"232": 232,
"233": 233,
"234": 234,
"235": 235,
"236": 236,
"237": 237,
"238": 238,
"239": 239,
"240": 240,
"241": 241,
"243": 243,
"244": 244,
"245": 245,
"246": 246,
"247": 247,
"248": 248,
"249": 249,
"250": 250,
"251": 251,
"252": 252,
"253": 253,
"254": 254,
"255": 255,
"256": 256,
"257": 257,
"258": 258,
"259": 259,
"260": 260,
"261": 261,
"262": 262,
"263": 263,
"264": 264,
"265": 265,
"266": 266,
"267": 267,
"268": 268,
"269": 269,
"270": 270,
"271": 271,
"272": 272,
"273": 273,
"274": 274,
"275": 275,
"276": 276,
"277": 277,
"278": 278,
"279": 279,
"280": 280,
"281": 281,
"282": 282,
"283": 283,
"284": 284,
"285": 285,
"286": 286,
"287": 287,
"288": 288,
"289": 289,
"290": 290,
"291": 291,
"292": 292,
"293": 293,
"294": 294,
"295": 295,
"296": 296,
"297": 297,
"298": 298,
"299": 299,
"300": 300,
"301": 301,
"302": 302,
"303": 303,
"304": 304,
"305": 305,
"306": 306,
"307": 307,
"308": 308,
"309": 309,
"310": 310,
"311": 311,
"312": 312,
"313": 313,
"314": 314,
"315": 315,
"316": 316,
"317": 317,
"318": 318,
"319": 319,
"320": 320,
"321": 321,
"322": 322,
"323": 323,
"324": 324,
"325": 325,
"328": 328,
"329": 329,
"330": 330,
"331": 331,
"332": 332,
"333": 333,
"334": 334,
"335": 335,
"336": 336,
"337": 337,
"338": 338,
"339": 339,
"340": 340,
"341": 341,
"342": 342,
"344": 344,
"345": 345,
"346": 346,
"347": 347,
"348": 348,
"349": 349,
"350": 350,
"351": 351,
"352": 352,
"353": 353,
"354": 354,
"355": 355,
"356": 356,
"357": 357,
"358": 358,
"359": 359,
"360": 360,
"361": 361,
"362": 362,
"363": 363,
"364": 364,
"365": 365,
"366": 366,
"367": 367,
"368": 368,
"369": 369,
"370": 370,
"371": 371,
"372": 372,
"373": 373,
"374": 374,
"375": 375,
"376": 376,
"377": 377,
"378": 378,
"379": 379,
"380": 380,
"381": 381,
"382": 382,
"383": 383,
"384": 384,
"385": 385,
"386": 386,
"387": 387,
"388": 388,
"389": 389,
"390": 390,
"391": 391,
"392": 392,
"393": 393,
"394": 394,
"395": 395,
"396": 396,
"397": 397,
"398": 398,
"399": 399,
"400": 400,
"401": 401,
"402": 402,
"403": 403,
"404": 404,
"405": 405,
"406": 406,
"407": 407,
"408": 408,
"409": 409,
"410": 410,
"411": 411,
"412": 412,
"413": 413,
"414": 414,
"415": 415,
"416": 416,
"417": 417,
"418": 418,
"419": 419,
"420": 420,
"421": 421,
"422": 422,
"423": 423,
"424": 424,
"425": 425,
"426": 426,
"427": 427,
"428": 428,
"429": 429,
"430": 430,
"431": 431,
"432": 432,
"433": 433,
"434": 434,
"437": 437,
"438": 438,
"441": 441,
"442": 442,
"443": 443,
"444": 444,
"445": 445,
"446": 446,
"447": 447,
"448": 448,
"449": 449,
"450": 450,
"451": 451,
"452": 452,
"453": 453,
"455": 455,
"457": 457,
"458": 458,
"459": 459,
"460": 460,
"461": 461,
"462": 462,
"463": 463,
"464": 464,
"465": 465,
"466": 466,
"467": 467,
"468": 468,
"469": 469,
"470": 470,
"471": 471,
"472": 472,
"473": 473,
"474": 474,
"475": 475,
"476": 476,
"477": 477,
"499": 499,
"500": 500,
"501": 501,
"502": 502,
"503": 503,
"504": 504,
"505": 505,
"506": 506,
"507": 507,
"508": 508,
"509": 509,
"510": 510,
"511": 511,
"513": 513,
"acacia_button": -140, "acacia_button": -140,
"acacia_door": 430, "acacia_door": 430,
"acacia_door_block": 196, "acacia_door_block": 196,
@ -226,27 +913,16 @@
"egg": 344, "egg": 344,
"element_0": 36, "element_0": 36,
"element_1": -12, "element_1": -12,
"element_2": -13,
"element_3": -14,
"element_4": -15,
"element_5": -16,
"element_6": -17,
"element_7": -18,
"element_8": -19,
"element_9": -20,
"element_10": -21, "element_10": -21,
"element_100": -111,
"element_101": -112,
"element_102": -113,
"element_103": -114,
"element_104": -115,
"element_105": -116,
"element_106": -117,
"element_107": -118,
"element_108": -119,
"element_109": -120,
"element_11": -22, "element_11": -22,
"element_110": -121,
"element_111": -122,
"element_112": -123,
"element_113": -124,
"element_114": -125,
"element_115": -126,
"element_116": -127,
"element_117": -128,
"element_118": -129,
"element_12": -23, "element_12": -23,
"element_13": -24, "element_13": -24,
"element_14": -25, "element_14": -25,
@ -255,7 +931,6 @@
"element_17": -28, "element_17": -28,
"element_18": -29, "element_18": -29,
"element_19": -30, "element_19": -30,
"element_2": -13,
"element_20": -31, "element_20": -31,
"element_21": -32, "element_21": -32,
"element_22": -33, "element_22": -33,
@ -266,7 +941,6 @@
"element_27": -38, "element_27": -38,
"element_28": -39, "element_28": -39,
"element_29": -40, "element_29": -40,
"element_3": -14,
"element_30": -41, "element_30": -41,
"element_31": -42, "element_31": -42,
"element_32": -43, "element_32": -43,
@ -277,7 +951,6 @@
"element_37": -48, "element_37": -48,
"element_38": -49, "element_38": -49,
"element_39": -50, "element_39": -50,
"element_4": -15,
"element_40": -51, "element_40": -51,
"element_41": -52, "element_41": -52,
"element_42": -53, "element_42": -53,
@ -288,7 +961,6 @@
"element_47": -58, "element_47": -58,
"element_48": -59, "element_48": -59,
"element_49": -60, "element_49": -60,
"element_5": -16,
"element_50": -61, "element_50": -61,
"element_51": -62, "element_51": -62,
"element_52": -63, "element_52": -63,
@ -299,7 +971,6 @@
"element_57": -68, "element_57": -68,
"element_58": -69, "element_58": -69,
"element_59": -70, "element_59": -70,
"element_6": -17,
"element_60": -71, "element_60": -71,
"element_61": -72, "element_61": -72,
"element_62": -73, "element_62": -73,
@ -310,7 +981,6 @@
"element_67": -78, "element_67": -78,
"element_68": -79, "element_68": -79,
"element_69": -80, "element_69": -80,
"element_7": -18,
"element_70": -81, "element_70": -81,
"element_71": -82, "element_71": -82,
"element_72": -83, "element_72": -83,
@ -321,7 +991,6 @@
"element_77": -88, "element_77": -88,
"element_78": -89, "element_78": -89,
"element_79": -90, "element_79": -90,
"element_8": -19,
"element_80": -91, "element_80": -91,
"element_81": -92, "element_81": -92,
"element_82": -93, "element_82": -93,
@ -332,7 +1001,6 @@
"element_87": -98, "element_87": -98,
"element_88": -99, "element_88": -99,
"element_89": -100, "element_89": -100,
"element_9": -20,
"element_90": -101, "element_90": -101,
"element_91": -102, "element_91": -102,
"element_92": -103, "element_92": -103,
@ -343,6 +1011,25 @@
"element_97": -108, "element_97": -108,
"element_98": -109, "element_98": -109,
"element_99": -110, "element_99": -110,
"element_100": -111,
"element_101": -112,
"element_102": -113,
"element_103": -114,
"element_104": -115,
"element_105": -116,
"element_106": -117,
"element_107": -118,
"element_108": -119,
"element_109": -120,
"element_110": -121,
"element_111": -122,
"element_112": -123,
"element_113": -124,
"element_114": -125,
"element_115": -126,
"element_116": -127,
"element_117": -128,
"element_118": -129,
"elytra": 444, "elytra": 444,
"emerald": 388, "emerald": 388,
"emerald_block": 133, "emerald_block": 133,
@ -532,8 +1219,8 @@
"leather_pants": 300, "leather_pants": 300,
"leather_tunic": 299, "leather_tunic": 299,
"leave": 18, "leave": 18,
"leaves": 18,
"leave2": 161, "leave2": 161,
"leaves": 18,
"leaves2": 161, "leaves2": 161,
"lectern": -194, "lectern": -194,
"lever": 69, "lever": 69,

@ -1 +0,0 @@
Subproject commit 4a322da43eb9beef727bd36be3d6b641b8316591

View File

@ -108,7 +108,7 @@ player:
verify-xuid: true verify-xuid: true
level-settings: level-settings:
#The default format that levels will use when created #The default format that worlds will use when created
default-format: leveldb default-format: leveldb
chunk-sending: chunk-sending:
@ -128,7 +128,9 @@ chunk-ticking:
blocks-per-subchunk-per-tick: 3 blocks-per-subchunk-per-tick: 3
#IDs of blocks not to perform random ticking on. #IDs of blocks not to perform random ticking on.
disable-block-ticking: disable-block-ticking:
#- 2 # grass #- grass
#- ice
#- fire
chunk-generation: chunk-generation:
#Max. amount of chunks in the waiting queue to be populated #Max. amount of chunks in the waiting queue to be populated
@ -176,7 +178,7 @@ aliases:
#savestop: [save-all, stop] #savestop: [save-all, stop]
worlds: worlds:
#These settings will override the generator set in server.properties and allows loading multiple levels #These settings will override the generator set in server.properties and allows loading multiple worlds
#Example: #Example:
#world: #world:
# seed: 404 # seed: 404

@ -1 +0,0 @@
Subproject commit 19569dd729970e161a24b574b41c06a5e064ab81

View File

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

View File

@ -1,379 +0,0 @@
<?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;
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase;
use pocketmine\plugin\PluginManager;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
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;
use function php_uname;
use function phpinfo;
use function phpversion;
use function preg_replace;
use function sprintf;
use function str_split;
use function strpos;
use function substr;
use function 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 SORT_STRING;
class CrashDump{
/**
* Crashdump data format version, used by the crash archive to decide how to decode the crashdump
* This should be incremented when backwards incompatible changes are introduced, such as fields being removed or
* having their content changed, version format changing, etc.
* It is not necessary to increase this when adding new fields.
*/
private const FORMAT_VERSION = 4;
private const PLUGIN_INVOLVEMENT_NONE = "none";
private const PLUGIN_INVOLVEMENT_DIRECT = "direct";
private 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 = [];
/** @var string */
private $encodedData = "";
/** @var string */
private $path;
public function __construct(Server $server){
$this->time = microtime(true);
$this->server = $server;
$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->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());
}
$zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed");
$this->encodedData = $zlibEncoded;
foreach(str_split(base64_encode($this->encodedData), 76) as $line){
$this->addLine($line);
}
$this->addLine("===END CRASH DUMP===");
}
private function pluginsData() : void{
if($this->server->getPluginManager() instanceof PluginManager){
$this->addLine();
$this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
$plugins = $this->server->getPluginManager()->getPlugins();
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()));
}
}
}
private function extraData() : void{
global $argv;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
$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;
}
if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){
$this->data["pocketmine.yml"] = $pocketmineDotYml;
}else{
$this->data["pocketmine.yml"] = "";
}
}else{
$this->data["pocketmine.yml"] = "";
$this->data["server.properties"] = "";
$this->data["parameters"] = [];
}
$extensions = [];
foreach(get_loaded_extensions() as $ext){
$extensions[$ext] = phpversion($ext);
}
$this->data["extensions"] = $extensions;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
ob_start();
phpinfo();
$this->data["phpinfo"] = ob_get_contents();
ob_end_clean();
}
}
private function baseCrash() : void{
global $lastExceptionError, $lastError;
if(isset($lastExceptionError)){
$error = $lastExceptionError;
}else{
$error = error_get_last();
if($error === null){
throw new \RuntimeException("Crash error information missing - did something use exit()?");
}
$error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump
$error["fullFile"] = $error["file"];
$error["file"] = Filesystem::cleanPath($error["file"]);
try{
$error["type"] = ErrorTypeToStringMap::get($error["type"]);
}catch(\InvalidArgumentException $e){
//pass
}
if(($pos = strpos($error["message"], "\n")) !== false){
$error["message"] = substr($error["message"], 0, $pos);
}
}
if(isset($lastError)){
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$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["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"])){
continue; //PHP core
}
if($this->determinePluginFromFile($frame["file"], false)){
break;
}
}
}
$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->addLine();
$this->addLine("Backtrace:");
foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line);
}
$this->addLine();
}
private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{
$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;
}else{
$this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT;
}
if(file_exists($filePath)){
$reflection = new \ReflectionClass(PluginBase::class);
$file = $reflection->getProperty("file");
$file->setAccessible(true);
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());
break;
}
}
}
return true;
}
return false;
}
private function generalData() : void{
$version = VersionInfo::VERSION();
$composerLibraries = [];
foreach(InstalledVersions::getInstalledPackages() as $package){
$composerLibraries[$package] = sprintf(
"%s@%s",
InstalledVersions::getPrettyVersion($package) ?? "unknown",
InstalledVersions::getReference($package) ?? "unknown"
);
}
$this->data["general"] = [];
$this->data["general"]["name"] = $this->server->getName();
$this->data["general"]["base_version"] = 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);
}
}

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace pocketmine; namespace pocketmine;
use pocketmine\event\server\LowMemoryEvent; use pocketmine\event\server\LowMemoryEvent;
use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\scheduler\DumpWorkerMemoryTask; use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask; use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Process; use pocketmine\utils\Process;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
@ -64,6 +64,7 @@ use function sprintf;
use function strlen; use function strlen;
use function substr; use function substr;
use const JSON_PRETTY_PRINT; use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_SLASHES;
use const SORT_NUMERIC; use const SORT_NUMERIC;
@ -167,14 +168,14 @@ class MemoryManager{
} }
public function canUseChunkCache() : bool{ public function canUseChunkCache() : bool{
return !$this->lowMemory or !$this->lowMemDisableChunkCache; return !$this->lowMemory || !$this->lowMemDisableChunkCache;
} }
/** /**
* Returns the allowed chunk radius based on the current memory usage. * Returns the allowed chunk radius based on the current memory usage.
*/ */
public function getViewDistance(int $distance) : int{ public function getViewDistance(int $distance) : int{
return ($this->lowMemory and $this->lowMemChunkRadiusOverride > 0) ? min($this->lowMemChunkRadiusOverride, $distance) : $distance; return ($this->lowMemory && $this->lowMemChunkRadiusOverride > 0) ? min($this->lowMemChunkRadiusOverride, $distance) : $distance;
} }
/** /**
@ -187,6 +188,7 @@ class MemoryManager{
foreach($this->server->getWorldManager()->getWorlds() as $world){ foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->clearCache(true); $world->clearCache(true);
} }
ChunkCache::pruneCaches();
} }
if($this->lowMemChunkGC){ if($this->lowMemChunkGC){
@ -212,18 +214,18 @@ class MemoryManager{
public function check() : void{ public function check() : void{
Timings::$memoryManager->startTiming(); Timings::$memoryManager->startTiming();
if(($this->memoryLimit > 0 or $this->globalMemoryLimit > 0) and ++$this->checkTicker >= $this->checkRate){ if(($this->memoryLimit > 0 || $this->globalMemoryLimit > 0) && ++$this->checkTicker >= $this->checkRate){
$this->checkTicker = 0; $this->checkTicker = 0;
$memory = Process::getAdvancedMemoryUsage(); $memory = Process::getAdvancedMemoryUsage();
$trigger = false; $trigger = false;
if($this->memoryLimit > 0 and $memory[0] > $this->memoryLimit){ if($this->memoryLimit > 0 && $memory[0] > $this->memoryLimit){
$trigger = 0; $trigger = 0;
}elseif($this->globalMemoryLimit > 0 and $memory[1] > $this->globalMemoryLimit){ }elseif($this->globalMemoryLimit > 0 && $memory[1] > $this->globalMemoryLimit){
$trigger = 1; $trigger = 1;
} }
if($trigger !== false){ if($trigger !== false){
if($this->lowMemory and $this->continuousTrigger){ if($this->lowMemory && $this->continuousTrigger){
if(++$this->continuousTriggerTicker >= $this->continuousTriggerRate){ if(++$this->continuousTriggerTicker >= $this->continuousTriggerRate){
$this->continuousTriggerTicker = 0; $this->continuousTriggerTicker = 0;
$this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0, ++$this->continuousTriggerCount); $this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0, ++$this->continuousTriggerCount);
@ -238,7 +240,7 @@ class MemoryManager{
} }
} }
if($this->garbageCollectionPeriod > 0 and ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){ if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){
$this->garbageCollectionTicker = 0; $this->garbageCollectionTicker = 0;
$this->triggerGarbageCollector(); $this->triggerGarbageCollector();
} }
@ -289,8 +291,7 @@ class MemoryManager{
* @param mixed $startingObject * @param mixed $startingObject
*/ */
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{ public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
$hardLimit = ini_get('memory_limit'); $hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
if($hardLimit === false) throw new AssumptionFailedError("memory_limit INI directive should always exist");
ini_set('memory_limit', '-1'); ini_set('memory_limit', '-1');
gc_disable(); gc_disable();
@ -298,7 +299,7 @@ class MemoryManager{
mkdir($outputFolder, 0777, true); mkdir($outputFolder, 0777, true);
} }
$obData = fopen(Path::join($outputFolder, "objects.js"), "wb+"); $obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+"));
$objects = []; $objects = [];
@ -316,13 +317,16 @@ class MemoryManager{
$reflection = new \ReflectionClass($className); $reflection = new \ReflectionClass($className);
$staticProperties[$className] = []; $staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){ foreach($reflection->getProperties() as $property){
if(!$property->isStatic() or $property->getDeclaringClass()->getName() !== $className){ if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){
continue; continue;
} }
if(!$property->isPublic()){ if(!$property->isPublic()){
$property->setAccessible(true); $property->setAccessible(true);
} }
if(!$property->isInitialized()){
continue;
}
$staticCount++; $staticCount++;
$staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize); $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
@ -347,10 +351,9 @@ class MemoryManager{
} }
} }
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $staticCount static properties"); $logger->info("Wrote $staticCount static properties");
if(isset($GLOBALS)){ //This might be null if we're on a different thread
$globalVariables = []; $globalVariables = [];
$globalCount = 0; $globalCount = 0;
@ -366,7 +369,7 @@ class MemoryManager{
'_SESSION' => true '_SESSION' => true
]; ];
foreach($GLOBALS as $varName => $value){ foreach(Utils::stringifyKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){ if(isset($ignoredGlobals[$varName])){
continue; continue;
} }
@ -375,9 +378,8 @@ class MemoryManager{
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize); $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)); file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $globalCount global variables"); $logger->info("Wrote $globalCount global variables");
}
foreach(get_defined_functions()["user"] as $function){ foreach(get_defined_functions()["user"] as $function){
$reflect = new \ReflectionFunction($function); $reflect = new \ReflectionFunction($function);
@ -391,7 +393,7 @@ class MemoryManager{
$functionStaticVarsCount += count($vars); $functionStaticVarsCount += count($vars);
} }
} }
file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Wrote $functionStaticVarsCount function static variables"); $logger->info("Wrote $functionStaticVarsCount function static variables");
$data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize); $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
@ -448,13 +450,16 @@ class MemoryManager{
if(!$property->isPublic()){ if(!$property->isPublic()){
$property->setAccessible(true); $property->setAccessible(true);
} }
if(!$property->isInitialized($object)){
continue;
}
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize); $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
} }
} }
} }
fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES) . "\n"); fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
} }
}while($continue); }while($continue);
@ -463,11 +468,11 @@ class MemoryManager{
fclose($obData); fclose($obData);
file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
arsort($instanceCounts, SORT_NUMERIC); arsort($instanceCounts, SORT_NUMERIC);
file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$logger->info("Finished!"); $logger->info("Finished!");

View File

@ -32,13 +32,16 @@ namespace pocketmine {
use pocketmine\utils\ServerKiller; use pocketmine\utils\ServerKiller;
use pocketmine\utils\Terminal; use pocketmine\utils\Terminal;
use pocketmine\utils\Timezone; use pocketmine\utils\Timezone;
use pocketmine\utils\Utils;
use pocketmine\wizard\SetupWizard; use pocketmine\wizard\SetupWizard;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function defined;
use function extension_loaded; use function extension_loaded;
use function getcwd;
use function phpversion; use function phpversion;
use function preg_match; use function preg_match;
use function preg_quote; use function preg_quote;
use function strpos; use function realpath;
use function version_compare; use function version_compare;
require_once __DIR__ . '/VersionInfo.php'; require_once __DIR__ . '/VersionInfo.php';
@ -111,21 +114,22 @@ namespace pocketmine {
} }
} }
if(extension_loaded("pthreads")){ if(($pthreads_version = phpversion("pthreads")) !== false){
$pthreads_version = phpversion("pthreads");
if(substr_count($pthreads_version, ".") < 2){ if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version"; $pthreads_version = "0.$pthreads_version";
} }
if(version_compare($pthreads_version, "3.2.0") < 0){ if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") > 0){
$messages[] = "pthreads >= 3.2.0 is required, while you have $pthreads_version."; $messages[] = "pthreads ^4.0.0 is required, while you have $pthreads_version.";
} }
} }
if(extension_loaded("leveldb")){ if(($leveldb_version = phpversion("leveldb")) !== false){
$leveldb_version = phpversion("leveldb");
if(version_compare($leveldb_version, "0.2.1") < 0){ if(version_compare($leveldb_version, "0.2.1") < 0){
$messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version."; $messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
} }
if(!defined('LEVELDB_ZLIB_RAW_COMPRESSION')){
$messages[] = "Given version of php-leveldb doesn't support ZLIB_RAW compression (use https://github.com/pmmp/php-leveldb)";
}
} }
$chunkutils2_version = phpversion("chunkutils2"); $chunkutils2_version = phpversion("chunkutils2");
@ -142,6 +146,10 @@ namespace pocketmine {
$messages[] = "The native PocketMine extension is no longer supported."; $messages[] = "The native PocketMine extension is no longer supported.";
} }
if(!defined('AF_INET6')){
$messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support.";
}
return $messages; return $messages;
} }
@ -205,6 +213,8 @@ JIT_WARNING
} }
critical_error("PHP binary used: " . $binary); critical_error("PHP binary used: " . $binary);
critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none")); critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none"));
$phprc = getenv("PHPRC");
critical_error("Value of PHPRC environment variable: " . ($phprc === false ? "" : $phprc));
critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html."); critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
echo PHP_EOL; echo PHP_EOL;
exit(1); exit(1);
@ -214,20 +224,13 @@ JIT_WARNING
error_reporting(-1); error_reporting(-1);
set_ini_entries(); set_ini_entries();
$opts = getopt("", ["bootstrap:"]);
if(isset($opts["bootstrap"])){
$bootstrap = ($real = realpath($opts["bootstrap"])) !== false ? $real : $opts["bootstrap"];
}else{
$bootstrap = dirname(__FILE__, 2) . '/vendor/autoload.php'; $bootstrap = dirname(__FILE__, 2) . '/vendor/autoload.php';
} if(!is_file($bootstrap)){
if($bootstrap === false or !is_file($bootstrap)){
critical_error("Composer autoloader not found at " . $bootstrap); critical_error("Composer autoloader not found at " . $bootstrap);
critical_error("Please install/update Composer dependencies or use provided builds."); critical_error("Please install/update Composer dependencies or use provided builds.");
exit(1); exit(1);
} }
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap); require_once($bootstrap);
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp'); $composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){ if($composerGitHash !== null){
@ -250,8 +253,9 @@ JIT_WARNING
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]); $opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
$dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR; $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR; $dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR;
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR;
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX); Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
if(!file_exists($dataPath)){ if(!file_exists($dataPath)){
@ -283,7 +287,7 @@ JIT_WARNING
$exitCode = 0; $exitCode = 0;
do{ do{
if(!file_exists(Path::join($dataPath, "server.properties")) and !isset($opts["no-wizard"])){ if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts["no-wizard"])){
$installer = new SetupWizard($dataPath); $installer = new SetupWizard($dataPath);
if(!$installer->run()){ if(!$installer->run()){
$exitCode = -1; $exitCode = -1;
@ -307,7 +311,7 @@ JIT_WARNING
if(ThreadManager::getInstance()->stopAll() > 0){ if(ThreadManager::getInstance()->stopAll() > 0){
$logger->debug("Some threads could not be stopped, performing a force-kill"); $logger->debug("Some threads could not be stopped, performing a force-kill");
Process::kill(Process::pid()); Process::kill(Process::pid(), true);
} }
}while(false); }while(false);

View File

@ -34,12 +34,14 @@ use pocketmine\console\ConsoleCommandSender;
use pocketmine\console\ConsoleReaderThread; use pocketmine\console\ConsoleReaderThread;
use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crafting\CraftingManagerFromDataHelper;
use pocketmine\data\java\GameModeIdMap; use pocketmine\crash\CrashDump;
use pocketmine\crash\CrashDumpRenderer;
use pocketmine\entity\EntityDataHelper; use pocketmine\entity\EntityDataHelper;
use pocketmine\entity\Location; use pocketmine\entity\Location;
use pocketmine\event\HandlerListManager; use pocketmine\event\HandlerListManager;
use pocketmine\event\player\PlayerCreationEvent; use pocketmine\event\player\PlayerCreationEvent;
use pocketmine\event\player\PlayerDataSaveEvent; use pocketmine\event\player\PlayerDataSaveEvent;
use pocketmine\event\player\PlayerLoginEvent;
use pocketmine\event\server\CommandEvent; use pocketmine\event\server\CommandEvent;
use pocketmine\event\server\DataPacketSendEvent; use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\event\server\QueryRegenerateEvent; use pocketmine\event\server\QueryRegenerateEvent;
@ -63,6 +65,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\raklib\RakLibInterface; use pocketmine\network\mcpe\raklib\RakLibInterface;
use pocketmine\network\Network; use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\query\DedicatedQueryNetworkInterface; use pocketmine\network\query\DedicatedQueryNetworkInterface;
use pocketmine\network\query\QueryHandler; use pocketmine\network\query\QueryHandler;
use pocketmine\network\query\QueryInfo; use pocketmine\network\query\QueryInfo;
@ -80,6 +83,8 @@ use pocketmine\plugin\PluginGraylist;
use pocketmine\plugin\PluginManager; use pocketmine\plugin\PluginManager;
use pocketmine\plugin\PluginOwned; use pocketmine\plugin\PluginOwned;
use pocketmine\plugin\ScriptPluginLoader; use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool; use pocketmine\scheduler\AsyncPool;
use pocketmine\snooze\SleeperHandler; use pocketmine\snooze\SleeperHandler;
@ -96,7 +101,7 @@ use pocketmine\utils\MainLogger;
use pocketmine\utils\NotCloneable; use pocketmine\utils\NotCloneable;
use pocketmine\utils\NotSerializable; use pocketmine\utils\NotSerializable;
use pocketmine\utils\Process; use pocketmine\utils\Process;
use pocketmine\utils\Promise; use pocketmine\utils\SignalHandler;
use pocketmine\utils\Terminal; use pocketmine\utils\Terminal;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
@ -105,26 +110,29 @@ use pocketmine\world\format\io\WorldProviderManager;
use pocketmine\world\format\io\WritableWorldProviderManagerEntry; use pocketmine\world\format\io\WritableWorldProviderManagerEntry;
use pocketmine\world\generator\Generator; use pocketmine\world\generator\Generator;
use pocketmine\world\generator\GeneratorManager; use pocketmine\world\generator\GeneratorManager;
use pocketmine\world\generator\InvalidGeneratorOptionsException;
use pocketmine\world\World; use pocketmine\world\World;
use pocketmine\world\WorldCreationOptions; use pocketmine\world\WorldCreationOptions;
use pocketmine\world\WorldManager; use pocketmine\world\WorldManager;
use Ramsey\Uuid\UuidInterface; use Ramsey\Uuid\UuidInterface;
use Webmozart\PathUtil\Path; use Webmozart\PathUtil\Path;
use function array_shift;
use function array_sum; use function array_sum;
use function base64_encode; use function base64_encode;
use function cli_set_process_title; use function cli_set_process_title;
use function copy; use function copy;
use function count; use function count;
use function explode; use function date;
use function fclose;
use function file_exists; use function file_exists;
use function file_get_contents; use function file_get_contents;
use function file_put_contents; use function file_put_contents;
use function filemtime; use function filemtime;
use function fopen;
use function get_class; use function get_class;
use function implode;
use function ini_set; use function ini_set;
use function is_array; use function is_array;
use function is_dir;
use function is_resource;
use function is_string; use function is_string;
use function json_decode; use function json_decode;
use function max; use function max;
@ -169,6 +177,12 @@ class Server{
public const BROADCAST_CHANNEL_ADMINISTRATIVE = "pocketmine.broadcast.admin"; public const BROADCAST_CHANNEL_ADMINISTRATIVE = "pocketmine.broadcast.admin";
public const BROADCAST_CHANNEL_USERS = "pocketmine.broadcast.user"; public const BROADCAST_CHANNEL_USERS = "pocketmine.broadcast.user";
public const DEFAULT_SERVER_NAME = VersionInfo::NAME . " Server";
public const DEFAULT_MAX_PLAYERS = 20;
public const DEFAULT_PORT_IPV4 = 19132;
public const DEFAULT_PORT_IPV6 = 19133;
public const DEFAULT_MAX_VIEW_DISTANCE = 16;
private static ?Server $instance = null; private static ?Server $instance = null;
private SleeperHandler $tickSleeper; private SleeperHandler $tickSleeper;
@ -251,6 +265,8 @@ class Server{
/** @var Player[] */ /** @var Player[] */
private array $playerList = []; private array $playerList = [];
private SignalHandler $signalHandler;
/** /**
* @var CommandSender[][] * @var CommandSender[][]
* @phpstan-var array<string, array<int, CommandSender>> * @phpstan-var array<string, array<int, CommandSender>>
@ -313,11 +329,15 @@ class Server{
} }
public function getPort() : int{ public function getPort() : int{
return $this->configGroup->getConfigInt("server-port", 19132); return $this->configGroup->getConfigInt("server-port", self::DEFAULT_PORT_IPV4);
}
public function getPortV6() : int{
return $this->configGroup->getConfigInt("server-portv6", self::DEFAULT_PORT_IPV6);
} }
public function getViewDistance() : int{ public function getViewDistance() : int{
return max(2, $this->configGroup->getConfigInt("view-distance", 8)); return max(2, $this->configGroup->getConfigInt("view-distance", self::DEFAULT_MAX_VIEW_DISTANCE));
} }
/** /**
@ -332,12 +352,17 @@ class Server{
return $str !== "" ? $str : "0.0.0.0"; return $str !== "" ? $str : "0.0.0.0";
} }
public function getIpV6() : string{
$str = $this->configGroup->getConfigString("server-ipv6");
return $str !== "" ? $str : "::";
}
public function getServerUniqueId() : UuidInterface{ public function getServerUniqueId() : UuidInterface{
return $this->serverID; return $this->serverID;
} }
public function getGamemode() : GameMode{ public function getGamemode() : GameMode{
return GameModeIdMap::getInstance()->fromId($this->configGroup->getConfigInt("gamemode", 0)) ?? GameMode::SURVIVAL(); return GameMode::fromString($this->configGroup->getConfigString("gamemode", GameMode::SURVIVAL()->name())) ?? GameMode::SURVIVAL();
} }
public function getForceGamemode() : bool{ public function getForceGamemode() : bool{
@ -360,7 +385,7 @@ class Server{
} }
public function getMotd() : string{ public function getMotd() : string{
return $this->configGroup->getConfigString("motd", VersionInfo::NAME . " Server"); return $this->configGroup->getConfigString("motd", self::DEFAULT_SERVER_NAME);
} }
public function getLoader() : \DynamicClassLoader{ public function getLoader() : \DynamicClassLoader{
@ -517,9 +542,10 @@ class Server{
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
Timings::$syncPlayerDataSave->time(function() use ($name, $ev) : void{ Timings::$syncPlayerDataSave->time(function() use ($name, $ev) : void{
$nbt = new BigEndianNbtSerializer(); $nbt = new BigEndianNbtSerializer();
$contents = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot($ev->getSaveData())), ZLIB_ENCODING_GZIP), "zlib_encode() failed unexpectedly");
try{ try{
file_put_contents($this->getPlayerDataPath($name), zlib_encode($nbt->write(new TreeRoot($ev->getSaveData())), ZLIB_ENCODING_GZIP)); Filesystem::safeFilePutContents($this->getPlayerDataPath($name), $contents);
}catch(\ErrorException $e){ }catch(\RuntimeException $e){
$this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage()))); $this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
$this->logger->logException($e); $this->logger->logException($e);
} }
@ -535,7 +561,7 @@ class Server{
$ev->call(); $ev->call();
$class = $ev->getPlayerClass(); $class = $ev->getPlayerClass();
if($offlinePlayerData !== null and ($world = $this->worldManager->getWorldByName($offlinePlayerData->getString("Level", ""))) !== null){ if($offlinePlayerData !== null && ($world = $this->worldManager->getWorldByName($offlinePlayerData->getString("Level", ""))) !== null){
$playerPos = EntityDataHelper::parseLocation($offlinePlayerData, $world); $playerPos = EntityDataHelper::parseLocation($offlinePlayerData, $world);
$spawn = $playerPos->asVector3(); $spawn = $playerPos->asVector3();
}else{ }else{
@ -546,11 +572,11 @@ class Server{
$playerPos = null; $playerPos = null;
$spawn = $world->getSpawnLocation(); $spawn = $world->getSpawnLocation();
} }
$playerPromise = new Promise(); $playerPromiseResolver = new PromiseResolver();
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion( $world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
function() use ($playerPromise, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{ function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
if(!$session->isConnected()){ if(!$session->isConnected()){
$playerPromise->reject(); $playerPromiseResolver->reject();
return; return;
} }
@ -570,16 +596,16 @@ class Server{
if(!$player->hasPlayedBefore()){ if(!$player->hasPlayedBefore()){
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move $player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move
} }
$playerPromise->resolve($player); $playerPromiseResolver->resolve($player);
}, },
static function() use ($playerPromise, $session) : void{ static function() use ($playerPromiseResolver, $session) : void{
if($session->isConnected()){ if($session->isConnected()){
$session->disconnect("Spawn terrain generation failed"); $session->disconnect("Spawn terrain generation failed");
} }
$playerPromise->reject(); $playerPromiseResolver->reject();
} }
); );
return $playerPromise; return $playerPromiseResolver->getPromise();
} }
/** /**
@ -670,7 +696,13 @@ class Server{
} }
public function removeOp(string $name) : void{ public function removeOp(string $name) : void{
$this->operators->remove(strtolower($name)); $lowercaseName = strtolower($name);
foreach($this->operators->getAll() as $operatorName => $_){
$operatorName = (string) $operatorName;
if($lowercaseName === strtolower($operatorName)){
$this->operators->remove($operatorName);
}
}
if(($player = $this->getPlayerExact($name)) !== null){ if(($player = $this->getPlayerExact($name)) !== null){
$player->unsetBasePermission(DefaultPermissions::ROOT_OPERATOR); $player->unsetBasePermission(DefaultPermissions::ROOT_OPERATOR);
@ -689,7 +721,7 @@ class Server{
} }
public function isWhitelisted(string $name) : bool{ public function isWhitelisted(string $name) : bool{
return !$this->hasWhitelist() or $this->operators->exists($name, true) or $this->whitelist->exists($name, true); return !$this->hasWhitelist() || $this->operators->exists($name, true) || $this->whitelist->exists($name, true);
} }
public function isOp(string $name) : bool{ public function isOp(string $name) : bool{
@ -735,7 +767,7 @@ class Server{
public function __construct(\DynamicClassLoader $autoloader, \AttachableThreadedLogger $logger, string $dataPath, string $pluginPath){ public function __construct(\DynamicClassLoader $autoloader, \AttachableThreadedLogger $logger, string $dataPath, string $pluginPath){
if(self::$instance !== null){ if(self::$instance !== null){
throw new \InvalidStateException("Only one server instance can exist at once"); throw new \LogicException("Only one server instance can exist at once");
} }
self::$instance = $this; self::$instance = $this;
$this->startTime = microtime(true); $this->startTime = microtime(true);
@ -744,6 +776,11 @@ class Server{
$this->autoloader = $autoloader; $this->autoloader = $autoloader;
$this->logger = $logger; $this->logger = $logger;
$this->signalHandler = new SignalHandler(function() : void{
$this->logger->info("Received signal interrupt, stopping the server");
$this->shutdown();
});
try{ try{
foreach([ foreach([
$dataPath, $dataPath,
@ -762,7 +799,7 @@ class Server{
$this->logger->info("Loading server configuration"); $this->logger->info("Loading server configuration");
$pocketmineYmlPath = Path::join($this->dataPath, "pocketmine.yml"); $pocketmineYmlPath = Path::join($this->dataPath, "pocketmine.yml");
if(!file_exists($pocketmineYmlPath)){ if(!file_exists($pocketmineYmlPath)){
$content = file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "pocketmine.yml")); $content = Utils::assumeNotFalse(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "pocketmine.yml")), "Missing required resource file");
if(VersionInfo::IS_DEVELOPMENT_BUILD){ if(VersionInfo::IS_DEVELOPMENT_BUILD){
$content = str_replace("preferred-channel: stable", "preferred-channel: beta", $content); $content = str_replace("preferred-channel: stable", "preferred-channel: beta", $content);
} }
@ -772,11 +809,13 @@ class Server{
$this->configGroup = new ServerConfigGroup( $this->configGroup = new ServerConfigGroup(
new Config($pocketmineYmlPath, Config::YAML, []), new Config($pocketmineYmlPath, Config::YAML, []),
new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [ new Config(Path::join($this->dataPath, "server.properties"), Config::PROPERTIES, [
"motd" => VersionInfo::NAME . " Server", "motd" => self::DEFAULT_SERVER_NAME,
"server-port" => 19132, "server-port" => self::DEFAULT_PORT_IPV4,
"server-portv6" => self::DEFAULT_PORT_IPV6,
"enable-ipv6" => true,
"white-list" => false, "white-list" => false,
"max-players" => 20, "max-players" => self::DEFAULT_MAX_PLAYERS,
"gamemode" => 0, "gamemode" => GameMode::SURVIVAL()->name(),
"force-gamemode" => false, "force-gamemode" => false,
"hardcore" => false, "hardcore" => false,
"pvp" => true, "pvp" => true,
@ -787,7 +826,7 @@ class Server{
"level-type" => "DEFAULT", "level-type" => "DEFAULT",
"enable-query" => true, "enable-query" => true,
"auto-save" => true, "auto-save" => true,
"view-distance" => 8, "view-distance" => self::DEFAULT_MAX_VIEW_DISTANCE,
"xbox-auth" => true, "xbox-auth" => true,
"language" => "eng" "language" => "eng"
]) ])
@ -856,7 +895,7 @@ class Server{
} }
$netCompressionLevel = $this->configGroup->getPropertyInt("network.compression-level", 6); $netCompressionLevel = $this->configGroup->getPropertyInt("network.compression-level", 6);
if($netCompressionLevel < 1 or $netCompressionLevel > 9){ if($netCompressionLevel < 1 || $netCompressionLevel > 9){
$this->logger->warning("Invalid network compression level $netCompressionLevel set, setting to default 6"); $this->logger->warning("Invalid network compression level $netCompressionLevel set, setting to default 6");
$netCompressionLevel = 6; $netCompressionLevel = 6;
} }
@ -873,7 +912,7 @@ class Server{
$bannedTxt = Path::join($this->dataPath, "banned.txt"); $bannedTxt = Path::join($this->dataPath, "banned.txt");
$bannedPlayersTxt = Path::join($this->dataPath, "banned-players.txt"); $bannedPlayersTxt = Path::join($this->dataPath, "banned-players.txt");
if(file_exists($bannedTxt) and !file_exists($bannedPlayersTxt)){ if(file_exists($bannedTxt) && !file_exists($bannedPlayersTxt)){
@rename($bannedTxt, $bannedPlayersTxt); @rename($bannedTxt, $bannedPlayersTxt);
} }
@touch($bannedPlayersTxt); @touch($bannedPlayersTxt);
@ -884,7 +923,7 @@ class Server{
$this->banByIP = new BanList($bannedIpsTxt); $this->banByIP = new BanList($bannedIpsTxt);
$this->banByIP->load(); $this->banByIP->load();
$this->maxPlayers = $this->configGroup->getConfigInt("max-players", 20); $this->maxPlayers = $this->configGroup->getConfigInt("max-players", self::DEFAULT_MAX_PLAYERS);
$this->onlineMode = $this->configGroup->getConfigBool("xbox-auth", true); $this->onlineMode = $this->configGroup->getConfigBool("xbox-auth", true);
if($this->onlineMode){ if($this->onlineMode){
@ -895,7 +934,7 @@ class Server{
$this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled())); $this->logger->warning($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_authProperty_disabled()));
} }
if($this->configGroup->getConfigBool("hardcore", false) and $this->getDifficulty() < World::DIFFICULTY_HARD){ if($this->configGroup->getConfigBool("hardcore", false) && $this->getDifficulty() < World::DIFFICULTY_HARD){
$this->configGroup->setConfigInt("difficulty", World::DIFFICULTY_HARD); $this->configGroup->setConfigInt("difficulty", World::DIFFICULTY_HARD);
} }
@ -923,7 +962,7 @@ class Server{
$this->commandMap = new SimpleCommandMap($this); $this->commandMap = new SimpleCommandMap($this);
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "recipes.json")); $this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes.json"));
$this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger); $this->resourceManager = new ResourcePackManager(Path::join($this->getDataPath(), "resource_packs"), $this->logger);
@ -945,7 +984,7 @@ class Server{
$providerManager = new WorldProviderManager(); $providerManager = new WorldProviderManager();
if( if(
($format = $providerManager->getProviderByName($formatName = $this->configGroup->getPropertyString("level-settings.default-format", ""))) !== null and ($format = $providerManager->getProviderByName($formatName = $this->configGroup->getPropertyString("level-settings.default-format", ""))) !== null &&
$format instanceof WritableWorldProviderManagerEntry $format instanceof WritableWorldProviderManagerEntry
){ ){
$providerManager->setDefault($format); $providerManager->setDefault($format);
@ -966,91 +1005,17 @@ class Server{
$this->pluginManager->loadPlugins($this->pluginPath); $this->pluginManager->loadPlugins($this->pluginPath);
$this->enablePlugins(PluginEnableOrder::STARTUP()); $this->enablePlugins(PluginEnableOrder::STARTUP());
foreach((array) $this->configGroup->getProperty("worlds", []) as $name => $options){ if(!$this->startupPrepareWorlds()){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->worldManager->loadWorld($name, true)){
$creationOptions = WorldCreationOptions::create();
//TODO: error checking
if(isset($options["generator"])){
$generatorOptions = explode(":", $options["generator"]);
$creationOptions->setGeneratorClass(GeneratorManager::getInstance()->getGenerator(array_shift($generatorOptions)));
if(count($generatorOptions) > 0){
$creationOptions->setGeneratorOptions(implode(":", $generatorOptions));
}
}
if(isset($options["difficulty"]) && is_string($options["difficulty"])){
$creationOptions->setDifficulty(World::getDifficultyFromString($options["difficulty"]));
}
if(isset($options["preset"]) && is_string($options["preset"])){
$creationOptions->setGeneratorOptions($options["preset"]);
}
if(isset($options["seed"])){
$convertedSeed = Generator::convertSeed((string) ($options["seed"] ?? ""));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
}
$this->worldManager->generateWorld($name, $creationOptions);
}
}
if($this->worldManager->getDefaultWorld() === null){
$default = $this->configGroup->getConfigString("level-name", "world");
if(trim($default) == ""){
$this->getLogger()->warning("level-name cannot be null, using default");
$default = "world";
$this->configGroup->setConfigString("level-name", "world");
}
if(!$this->worldManager->loadWorld($default, true)){
$creationOptions = WorldCreationOptions::create()
->setGeneratorClass(GeneratorManager::getInstance()->getGenerator($this->configGroup->getConfigString("level-type")))
->setGeneratorOptions($this->configGroup->getConfigString("generator-settings"));
$convertedSeed = Generator::convertSeed($this->configGroup->getConfigString("level-seed"));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
$this->worldManager->generateWorld($default, $creationOptions);
}
$world = $this->worldManager->getWorldByName($default);
if($world === null){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->forceShutdown();
return; return;
} }
$this->worldManager->setDefaultWorld($world);
}
$this->enablePlugins(PluginEnableOrder::POSTWORLD()); $this->enablePlugins(PluginEnableOrder::POSTWORLD());
$useQuery = $this->configGroup->getConfigBool("enable-query", true); if(!$this->startupPrepareNetworkInterfaces()){
if(!$this->network->registerInterface(new RakLibInterface($this)) && $useQuery){ $this->forceShutdown();
//RakLib would normally handle the transport for Query packets return;
//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($this->getIp(), (string) $this->getPort())));
if($useQuery){
$this->network->registerRawPacketHandler(new QueryHandler($this));
} }
foreach($this->getIPBans()->getEntries() as $entry){ if($this->configGroup->getPropertyBool("anonymous-statistics.enabled", true)){
$this->network->blockAddress($entry->getName(), -1);
}
if($this->configGroup->getPropertyBool("network.upnp-forwarding", false)){
$this->network->registerInterface(new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
}
if($this->configGroup->getPropertyBool("settings.send-usage", true)){
$this->sendUsageTicker = 6000; $this->sendUsageTicker = 6000;
$this->sendUsage(SendUsageTask::TYPE_OPEN); $this->sendUsage(SendUsageTask::TYPE_OPEN);
} }
@ -1085,6 +1050,153 @@ class Server{
} }
} }
private function startupPrepareWorlds() : bool{
$getGenerator = function(string $generatorName, string $generatorOptions, string $worldName) : ?string{
$generatorEntry = GeneratorManager::getInstance()->getGenerator($generatorName);
if($generatorEntry === null){
$this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
$worldName,
KnownTranslationFactory::pocketmine_level_unknownGenerator($generatorName)
)));
return null;
}
try{
$generatorEntry->validateGeneratorOptions($generatorOptions);
}catch(InvalidGeneratorOptionsException $e){
$this->logger->error($this->language->translate(KnownTranslationFactory::pocketmine_level_generationError(
$worldName,
KnownTranslationFactory::pocketmine_level_invalidGeneratorOptions($generatorOptions, $generatorName, $e->getMessage())
)));
return null;
}
return $generatorEntry->getGeneratorClass();
};
foreach((array) $this->configGroup->getProperty("worlds", []) as $name => $options){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->worldManager->loadWorld($name, true) && !$this->worldManager->isWorldGenerated($name)){
$creationOptions = WorldCreationOptions::create();
//TODO: error checking
$generatorName = $options["generator"] ?? "default";
$generatorOptions = isset($options["preset"]) && is_string($options["preset"]) ? $options["preset"] : "";
$generatorClass = $getGenerator($generatorName, $generatorOptions, $name);
if($generatorClass === null){
continue;
}
$creationOptions->setGeneratorClass($generatorClass);
$creationOptions->setGeneratorOptions($generatorOptions);
if(isset($options["difficulty"]) && is_string($options["difficulty"])){
$creationOptions->setDifficulty(World::getDifficultyFromString($options["difficulty"]));
}
if(isset($options["seed"])){
$convertedSeed = Generator::convertSeed((string) ($options["seed"] ?? ""));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
}
$this->worldManager->generateWorld($name, $creationOptions);
}
}
if($this->worldManager->getDefaultWorld() === null){
$default = $this->configGroup->getConfigString("level-name", "world");
if(trim($default) == ""){
$this->getLogger()->warning("level-name cannot be null, using default");
$default = "world";
$this->configGroup->setConfigString("level-name", "world");
}
if(!$this->worldManager->loadWorld($default, true) && !$this->worldManager->isWorldGenerated($default)){
$generatorName = $this->configGroup->getConfigString("level-type");
$generatorOptions = $this->configGroup->getConfigString("generator-settings");
$generatorClass = $getGenerator($generatorName, $generatorOptions, $default);
if($generatorClass !== null){
$creationOptions = WorldCreationOptions::create()
->setGeneratorClass($generatorClass)
->setGeneratorOptions($generatorOptions);
$convertedSeed = Generator::convertSeed($this->configGroup->getConfigString("level-seed"));
if($convertedSeed !== null){
$creationOptions->setSeed($convertedSeed);
}
$this->worldManager->generateWorld($default, $creationOptions);
}
}
$world = $this->worldManager->getWorldByName($default);
if($world === null){
$this->getLogger()->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_level_defaultError()));
$this->forceShutdown();
return false;
}
$this->worldManager->setDefaultWorld($world);
}
return 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, $ip, $port, $ipV6));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$ip,
(string) $port,
$e->getMessage()
)));
return false;
}
if($rakLibRegistered){
$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;
}
if($useQuery){
$this->network->registerRawPacketHandler(new QueryHandler($this));
}
foreach($this->getIPBans()->getEntries() as $entry){
$this->network->blockAddress($entry->getName(), -1);
}
if($this->configGroup->getPropertyBool("network.upnp-forwarding", false)){
$this->network->registerInterface(new UPnPNetworkInterface($this->logger, Internet::getInternalIP(), $this->getPort()));
}
return true;
}
/** /**
* Subscribes to a particular message broadcast channel. * Subscribes to a particular message broadcast channel.
* The channel ID can be any arbitrary string. * The channel ID can be any arbitrary string.
@ -1109,7 +1221,7 @@ class Server{
* Unsubscribes from all broadcast channels. * Unsubscribes from all broadcast channels.
*/ */
public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{ public function unsubscribeFromAllBroadcastChannels(CommandSender $subscriber) : void{
foreach($this->broadcastSubscribers as $channelId => $recipients){ foreach(Utils::stringifyKeys($this->broadcastSubscribers) as $channelId => $recipients){
$this->unsubscribeFromBroadcastChannel($channelId, $subscriber); $this->unsubscribeFromBroadcastChannel($channelId, $subscriber);
} }
} }
@ -1269,7 +1381,7 @@ class Server{
public function enablePlugins(PluginEnableOrder $type) : void{ public function enablePlugins(PluginEnableOrder $type) : void{
foreach($this->pluginManager->getPlugins() as $plugin){ foreach($this->pluginManager->getPlugins() as $plugin){
if(!$plugin->isEnabled() and $plugin->getDescription()->getOrder()->equals($type)){ if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder()->equals($type)){
$this->pluginManager->enablePlugin($plugin); $this->pluginManager->enablePlugin($plugin);
} }
} }
@ -1293,20 +1405,17 @@ class Server{
$commandLine = $ev->getCommand(); $commandLine = $ev->getCommand();
} }
if($this->commandMap->dispatch($sender, $commandLine)){ return $this->commandMap->dispatch($sender, $commandLine);
return true;
}
$sender->sendMessage(KnownTranslationFactory::commands_generic_notFound()->prefix(TextFormat::RED));
return false;
} }
/** /**
* Shuts the server down correctly * Shuts the server down correctly
*/ */
public function shutdown() : void{ public function shutdown() : void{
if($this->isRunning){
$this->isRunning = false; $this->isRunning = false;
$this->signalHandler->unregister();
}
} }
public function forceShutdown() : void{ public function forceShutdown() : void{
@ -1318,6 +1427,9 @@ class Server{
echo "\x1b]0;\x07"; echo "\x1b]0;\x07";
} }
if($this->isRunning){
$this->logger->emergency("Forcing server shutdown");
}
try{ try{
if(!$this->isRunning()){ if(!$this->isRunning()){
$this->sendUsage(SendUsageTask::TYPE_CLOSE); $this->sendUsage(SendUsageTask::TYPE_CLOSE);
@ -1371,7 +1483,7 @@ class Server{
}catch(\Throwable $e){ }catch(\Throwable $e){
$this->logger->logException($e); $this->logger->logException($e);
$this->logger->emergency("Crashed while crashing, killing process"); $this->logger->emergency("Crashed while crashing, killing process");
@Process::kill(Process::pid()); @Process::kill(Process::pid(), true);
} }
} }
@ -1416,6 +1528,25 @@ class Server{
$this->crashDump(); $this->crashDump();
} }
private function writeCrashDumpFile(CrashDump $dump) : string{
$crashFolder = Path::join($this->getDataPath(), "crashdumps");
if(!is_dir($crashFolder)){
mkdir($crashFolder);
}
$crashDumpPath = Path::join($crashFolder, date("D_M_j-H.i.s-T_Y", (int) $dump->getData()->time) . ".log");
$fp = @fopen($crashDumpPath, "wb");
if(!is_resource($fp)){
throw new \RuntimeException("Unable to open new file to generate crashdump");
}
$writer = new CrashDumpRenderer($fp, $dump->getData());
$writer->renderHumanReadable();
$dump->encodeData($writer);
fclose($fp);
return $crashDumpPath;
}
public function crashDump() : void{ public function crashDump() : void{
while(@ob_end_flush()){} while(@ob_end_flush()){}
if(!$this->isRunning){ if(!$this->isRunning){
@ -1430,34 +1561,37 @@ class Server{
ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems ini_set("memory_limit", '-1'); //Fix error dump not dumped on memory problems
try{ try{
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create())); $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_create()));
$dump = new CrashDump($this); $dump = new CrashDump($this, $this->pluginManager ?? null);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($dump->getPath()))); $crashDumpPath = $this->writeCrashDumpFile($dump);
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_submit($crashDumpPath)));
if($this->configGroup->getPropertyBool("auto-report.enabled", true)){ if($this->configGroup->getPropertyBool("auto-report.enabled", true)){
$report = true; $report = true;
$stamp = Path::join($this->getDataPath(), "crashdumps", ".last_crash"); $stamp = Path::join($this->getDataPath(), "crashdumps", ".last_crash");
$crashInterval = 120; //2 minutes $crashInterval = 120; //2 minutes
if(file_exists($stamp) and !($report = (filemtime($stamp) + $crashInterval < time()))){ if(($lastReportTime = @filemtime($stamp)) !== false && $lastReportTime + $crashInterval >= time()){
$report = false;
$this->logger->debug("Not sending crashdump due to last crash less than $crashInterval seconds ago"); $this->logger->debug("Not sending crashdump due to last crash less than $crashInterval seconds ago");
} }
@touch($stamp); //update file timestamp @touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"]; $plugin = $dump->getData()->plugin;
if(is_string($plugin)){ if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin); $p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){ if($p instanceof Plugin && !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin"); $this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
$report = false; $report = false;
} }
} }
if($dump->getData()["error"]["type"] === \ParseError::class){ if($dump->getData()->error["type"] === \ParseError::class){
$report = false; $report = false;
} }
if(strrpos(VersionInfo::GIT_HASH(), "-dirty") !== false or VersionInfo::GIT_HASH() === str_repeat("00", 20)){ if(strrpos(VersionInfo::GIT_HASH(), "-dirty") !== false || VersionInfo::GIT_HASH() === str_repeat("00", 20)){
$this->logger->debug("Not sending crashdump due to locally modified"); $this->logger->debug("Not sending crashdump due to locally modified");
$report = false; //Don't send crashdumps for locally modified builds $report = false; //Don't send crashdumps for locally modified builds
} }
@ -1472,8 +1606,8 @@ class Server{
"reportPaste" => base64_encode($dump->getEncodedData()) "reportPaste" => base64_encode($dump->getEncodedData())
], 10, [], $postUrlError); ], 10, [], $postUrlError);
if($reply !== null and ($data = json_decode($reply->getBody())) !== null){ if($reply !== null && ($data = json_decode($reply->getBody())) !== null){
if(isset($data->crashId) and isset($data->crashUrl)){ if(isset($data->crashId) && isset($data->crashUrl)){
$reportId = $data->crashId; $reportId = $data->crashId;
$reportUrl = $data->crashUrl; $reportUrl = $data->crashUrl;
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId))); $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
@ -1501,7 +1635,7 @@ class Server{
echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL; echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
sleep($spacing); sleep($spacing);
} }
@Process::kill(Process::pid()); @Process::kill(Process::pid(), true);
exit(1); exit(1);
} }
@ -1527,7 +1661,28 @@ class Server{
} }
} }
public function addOnlinePlayer(Player $player) : void{ public function addOnlinePlayer(Player $player) : bool{
$ev = new PlayerLoginEvent($player, "Plugin reason");
$ev->call();
if($ev->isCancelled() || !$player->isConnected()){
$player->disconnect($ev->getKickMessage());
return false;
}
$session = $player->getNetworkSession();
$position = $player->getPosition();
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
TextFormat::AQUA . $player->getName() . TextFormat::WHITE,
$session->getIp(),
(string) $session->getPort(),
(string) $player->getId(),
$position->getWorld()->getDisplayName(),
(string) round($position->x, 4),
(string) round($position->y, 4),
(string) round($position->z, 4)
)));
foreach($this->playerList as $p){ foreach($this->playerList as $p){
$p->getNetworkSession()->onPlayerAdded($player); $p->getNetworkSession()->onPlayerAdded($player);
} }
@ -1537,6 +1692,8 @@ class Server{
if($this->sendUsageTicker > 0){ if($this->sendUsageTicker > 0){
$this->uniquePlayers[$rawUUID] = $rawUUID; $this->uniquePlayers[$rawUUID] = $rawUUID;
} }
return true;
} }
public function removeOnlinePlayer(Player $player) : void{ public function removeOnlinePlayer(Player $player) : void{
@ -1636,7 +1793,7 @@ class Server{
$this->network->getBandwidthTracker()->rotateAverageHistory(); $this->network->getBandwidthTracker()->rotateAverageHistory();
} }
if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){ if($this->sendUsageTicker > 0 && --$this->sendUsageTicker === 0){
$this->sendUsageTicker = 6000; $this->sendUsageTicker = 6000;
$this->sendUsage(SendUsageTask::TYPE_STATUS); $this->sendUsage(SendUsageTask::TYPE_STATUS);
} }

View File

@ -27,6 +27,8 @@ use pocketmine\utils\Config;
use function array_key_exists; use function array_key_exists;
use function getopt; use function getopt;
use function is_bool; use function is_bool;
use function is_int;
use function is_string;
use function strtolower; use function strtolower;
final class ServerConfigGroup{ final class ServerConfigGroup{
@ -110,10 +112,13 @@ final class ServerConfigGroup{
}else{ }else{
$value = $this->serverProperties->exists($variable) ? $this->serverProperties->get($variable) : $defaultValue; $value = $this->serverProperties->exists($variable) ? $this->serverProperties->get($variable) : $defaultValue;
} }
if(is_bool($value)){ if(is_bool($value)){
return $value; return $value;
} }
if(is_int($value)){
return $value !== 0;
}
if(is_string($value)){
switch(strtolower($value)){ switch(strtolower($value)){
case "on": case "on":
case "true": case "true":
@ -121,6 +126,7 @@ final class ServerConfigGroup{
case "yes": case "yes":
return true; return true;
} }
}
return false; return false;
} }

View File

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

View File

@ -115,7 +115,7 @@ class Bamboo extends Transparent{
return 12 + (self::getOffsetSeed($x, 0, $z) % 5); return 12 + (self::getOffsetSeed($x, 0, $z) % 5);
} }
public function getPositionOffset() : ?Vector3{ public function getModelPositionOffset() : ?Vector3{
$seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ()); $seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ());
$retX = (($seed % 12) + 1) / 16; $retX = (($seed % 12) + 1) / 16;
$retZ = ((($seed >> 8) % 12) + 1) / 16; $retZ = ((($seed >> 8) % 12) + 1) / 16;
@ -145,12 +145,12 @@ class Bamboo extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){ if($item instanceof Fertilizer){
$top = $this->seekToTop(); $top = $this->seekToTop();
if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2))){ if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2), $player)){
$item->pop(); $item->pop();
return true; return true;
} }
}elseif($item instanceof ItemBamboo){ }elseif($item instanceof ItemBamboo){
if($this->seekToTop()->grow(PHP_INT_MAX, 1)){ if($this->seekToTop()->grow(PHP_INT_MAX, 1, $player)){
$item->pop(); $item->pop();
return true; return true;
} }
@ -160,12 +160,12 @@ class Bamboo extends Transparent{
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
$below = $this->position->getWorld()->getBlock($this->position->down()); $below = $this->position->getWorld()->getBlock($this->position->down());
if(!$this->canBeSupportedBy($below) and !$below->isSameType($this)){ if(!$this->canBeSupportedBy($below) && !$below->isSameType($this)){
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
} }
} }
private function grow(int $maxHeight, int $growAmount) : bool{ private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld(); $world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){ if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false; return false;
@ -212,7 +212,7 @@ class Bamboo extends Transparent{
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock); $tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
} }
$ev = new StructureGrowEvent($this, $tx); $ev = new StructureGrowEvent($this, $tx, $player);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if($ev->isCancelled()){
return false; return false;
@ -229,7 +229,7 @@ class Bamboo extends Transparent{
$world = $this->position->getWorld(); $world = $this->position->getWorld();
if($this->ready){ if($this->ready){
$this->ready = false; $this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1)){ if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1, null)){
$world->setBlock($this->position, $this); $world->setBlock($this->position, $this);
} }
}elseif($world->getBlock($this->position->up())->canBeReplaced()){ }elseif($world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -73,7 +73,7 @@ final class BambooSapling extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer || $item instanceof ItemBamboo){ if($item instanceof Fertilizer || $item instanceof ItemBamboo){
if($this->grow()){ if($this->grow($player)){
$item->pop(); $item->pop();
return true; return true;
} }
@ -87,7 +87,7 @@ final class BambooSapling extends Flowable{
} }
} }
private function grow() : bool{ private function grow(?Player $player) : bool{
$world = $this->position->getWorld(); $world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){ if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false; return false;
@ -98,7 +98,7 @@ final class BambooSapling extends Flowable{
$tx->addBlock($this->position, $bamboo) $tx->addBlock($this->position, $bamboo)
->addBlock($this->position->up(), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES)); ->addBlock($this->position->up(), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES));
$ev = new StructureGrowEvent($this, $tx); $ev = new StructureGrowEvent($this, $tx, $player);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if($ev->isCancelled()){
return false; return false;
@ -115,7 +115,7 @@ final class BambooSapling extends Flowable{
$world = $this->position->getWorld(); $world = $this->position->getWorld();
if($this->ready){ if($this->ready){
$this->ready = false; $this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow()){ if($world->getFullLight($this->position) < 9 || !$this->grow(null)){
$world->setBlock($this->position, $this); $world->setBlock($this->position, $this);
} }
}elseif($world->getBlock($this->position->up())->canBeReplaced()){ }elseif($world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -130,7 +130,7 @@ abstract class BaseBanner extends Transparent{
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
$drop = $this->asItem(); $drop = $this->asItem();
if($drop instanceof ItemBanner and count($this->patterns) > 0){ if($drop instanceof ItemBanner && count($this->patterns) > 0){
$drop->setPatterns($this->patterns); $drop->setPatterns($this->patterns);
} }
@ -139,7 +139,7 @@ abstract class BaseBanner extends Transparent{
public function getPickedItem(bool $addUserData = false) : Item{ public function getPickedItem(bool $addUserData = false) : Item{
$result = $this->asItem(); $result = $this->asItem();
if($addUserData and $result instanceof ItemBanner and count($this->patterns) > 0){ if($addUserData && $result instanceof ItemBanner && count($this->patterns) > 0){
$result->setPatterns($this->patterns); $result->setPatterns($this->patterns);
} }
return $result; return $result;

View File

@ -24,34 +24,17 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\CoralType; use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\item\Item; use pocketmine\item\Item;
abstract class BaseCoral extends Transparent{ abstract class BaseCoral extends Transparent{
use CoralTypeTrait;
protected CoralType $coralType;
protected bool $dead = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){ public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
parent::__construct($idInfo, $name, $breakInfo); parent::__construct($idInfo, $name, $breakInfo);
$this->coralType = CoralType::TUBE(); $this->coralType = CoralType::TUBE();
} }
public function getCoralType() : CoralType{ return $this->coralType; }
/** @return $this */
public function setCoralType(CoralType $coralType) : self{
$this->coralType = $coralType;
return $this;
}
public function isDead() : bool{ return $this->dead; }
/** @return $this */
public function setDead(bool $dead) : self{
$this->dead = $dead;
return $this;
}
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(!$this->dead){ if(!$this->dead){
$world = $this->position->getWorld(); $world = $this->position->getWorld();

View File

@ -101,7 +101,7 @@ abstract class BaseRail extends Flowable{
} }
if( if(
$other instanceof BaseRail and $other instanceof BaseRail &&
in_array($otherConnection, $other->getCurrentShapeConnections(), true) in_array($otherConnection, $other->getCurrentShapeConnections(), true)
){ ){
$connections[] = $connection; $connections[] = $connection;
@ -179,7 +179,7 @@ abstract class BaseRail extends Flowable{
$otherSide |= RailConnectionInfo::FLAG_ASCEND; $otherSide |= RailConnectionInfo::FLAG_ASCEND;
} }
if(!($other instanceof BaseRail) or count($otherConnections = $other->getConnectedDirections()) >= 2){ if(!($other instanceof BaseRail) || count($otherConnections = $other->getConnectedDirections()) >= 2){
//we can only connect to a rail that has less than 2 connections //we can only connect to a rail that has less than 2 connections
continue; continue;
} }
@ -224,7 +224,7 @@ abstract class BaseRail extends Flowable{
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
}else{ }else{
foreach($this->getCurrentShapeConnections() as $connection){ foreach($this->getCurrentShapeConnections() as $connection){
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 and $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->isTransparent()){ if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->isTransparent()){
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
break; break;
} }

View File

@ -37,8 +37,6 @@ use function assert;
use function strlen; use function strlen;
abstract class BaseSign extends Transparent{ abstract class BaseSign extends Transparent{
//TODO: conditionally useless properties, find a way to fix
protected SignText $text; protected SignText $text;
protected ?int $editorEntityRuntimeId = null; protected ?int $editorEntityRuntimeId = null;

View File

@ -120,7 +120,7 @@ class Bed extends Transparent{
public function getOtherHalf() : ?Bed{ public function getOtherHalf() : ?Bed{
$other = $this->getSide($this->getOtherHalfSide()); $other = $this->getSide($this->getOtherHalfSide());
if($other instanceof Bed and $other->head !== $this->head and $other->facing === $this->facing){ if($other instanceof Bed && $other->head !== $this->head && $other->facing === $this->facing){
return $other; return $other;
} }
@ -135,17 +135,17 @@ class Bed extends Transparent{
$player->sendMessage(TextFormat::GRAY . "This bed is incomplete"); $player->sendMessage(TextFormat::GRAY . "This bed is incomplete");
return true; return true;
}elseif($playerPos->distanceSquared($this->position) > 4 and $playerPos->distanceSquared($other->position) > 4){ }elseif($playerPos->distanceSquared($this->position) > 4 && $playerPos->distanceSquared($other->position) > 4){
$player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY)); $player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY));
return true; return true;
} }
$time = $this->position->getWorld()->getTimeOfDay(); $time = $this->position->getWorld()->getTimeOfDay();
$isNight = ($time >= World::TIME_NIGHT and $time < World::TIME_SUNRISE); $isNight = ($time >= World::TIME_NIGHT && $time < World::TIME_SUNRISE);
if(!$isNight){ if(!$isNight){
$player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY)); $player->sendMessage(KnownTranslationFactory::tile_bed_noSleep()->prefix(TextFormat::GRAY));
return true; return true;
} }
@ -166,7 +166,7 @@ class Bed extends Transparent{
} }
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(($other = $this->getOtherHalf()) !== null and $other->occupied !== $this->occupied){ if(($other = $this->getOtherHalf()) !== null && $other->occupied !== $this->occupied){
$this->occupied = $other->occupied; $this->occupied = $other->occupied;
$this->position->getWorld()->setBlock($this->position, $this); $this->position->getWorld()->setBlock($this->position, $this);
} }
@ -186,7 +186,7 @@ class Bed extends Transparent{
$this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH; $this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH;
$next = $this->getSide($this->getOtherHalfSide()); $next = $this->getSide($this->getOtherHalfSide());
if($next->canBeReplaced() and !$next->getSide(Facing::DOWN)->isTransparent()){ if($next->canBeReplaced() && !$next->getSide(Facing::DOWN)->isTransparent()){
$nextState = clone $this; $nextState = clone $this;
$nextState->head = true; $nextState->head = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($next->position, $nextState); $tx->addBlock($blockReplace->position, $this)->addBlock($next->position, $nextState);

View File

@ -37,8 +37,6 @@ use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\BellRingSound; use pocketmine\world\sound\BellRingSound;
final class Bell extends Transparent{ final class Bell extends Transparent{
private const BELL_RINGING_REPEAT_TICKS = 20;
use HorizontalFacingTrait; use HorizontalFacingTrait;
private BellAttachmentType $attachmentType; private BellAttachmentType $attachmentType;

View File

@ -148,14 +148,14 @@ class Block{
$tileType = $this->idInfo->getTileClass(); $tileType = $this->idInfo->getTileClass();
$oldTile = $this->position->getWorld()->getTile($this->position); $oldTile = $this->position->getWorld()->getTile($this->position);
if($oldTile !== null){ if($oldTile !== null){
if($tileType === null or !($oldTile instanceof $tileType)){ if($tileType === null || !($oldTile instanceof $tileType)){
$oldTile->close(); $oldTile->close();
$oldTile = null; $oldTile = null;
}elseif($oldTile instanceof Spawnable){ }elseif($oldTile instanceof Spawnable){
$oldTile->setDirty(); //destroy old network cache $oldTile->setDirty(); //destroy old network cache
} }
} }
if($oldTile === null and $tileType !== null){ if($oldTile === null && $tileType !== null){
/** /**
* @var Tile $tile * @var Tile $tile
* @see Tile::__construct() * @see Tile::__construct()
@ -166,20 +166,28 @@ class Block{
} }
/** /**
* Returns whether the given block has an equivalent type to this one. This compares base legacy ID and variant. * Returns a type ID that identifies this type of block. This does not include information like facing, colour,
* powered/unpowered, etc.
*/
public function getTypeId() : int{
return ($this->idInfo->getBlockId() << Block::INTERNAL_METADATA_BITS) | $this->idInfo->getVariant();
}
/**
* Returns whether the given block has an equivalent type to this one. This compares the type IDs.
* *
* Note: This ignores additional IDs used to represent additional states. This means that, for example, a lit * Note: This ignores additional IDs used to represent additional states. This means that, for example, a lit
* furnace and unlit furnace are considered the same type. * furnace and unlit furnace are considered the same type.
*/ */
public function isSameType(Block $other) : bool{ public function isSameType(Block $other) : bool{
return $this->idInfo->getBlockId() === $other->idInfo->getBlockId() and $this->idInfo->getVariant() === $other->idInfo->getVariant(); return $this->getTypeId() === $other->getTypeId();
} }
/** /**
* Returns whether the given block has the same type and properties as this block. * Returns whether the given block has the same type and properties as this block.
*/ */
public function isSameState(Block $other) : bool{ public function isSameState(Block $other) : bool{
return $this->isSameType($other) and $this->writeStateToMeta() === $other->writeStateToMeta(); return $this->getFullId() === $other->getFullId();
} }
/** /**
@ -353,7 +361,7 @@ class Block{
*/ */
public function getDrops(Item $item) : array{ public function getDrops(Item $item) : array{
if($this->breakInfo->isToolCompatible($item)){ if($this->breakInfo->isToolCompatible($item)){
if($this->isAffectedBySilkTouch() and $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){ if($this->isAffectedBySilkTouch() && $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
return $this->getSilkTouchDrops($item); return $this->getSilkTouchDrops($item);
} }
@ -394,7 +402,7 @@ class Block{
* Returns how much XP will be dropped by breaking this block with the given item. * Returns how much XP will be dropped by breaking this block with the given item.
*/ */
public function getXpDropForTool(Item $item) : int{ public function getXpDropForTool(Item $item) : int{
if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) or !$this->breakInfo->isToolCompatible($item)){ if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) || !$this->breakInfo->isToolCompatible($item)){
return 0; return 0;
} }
@ -493,7 +501,7 @@ class Block{
return $this->position->getWorld()->getBlock($this->position->getSide($side, $step)); return $this->position->getWorld()->getBlock($this->position->getSide($side, $step));
} }
throw new \InvalidStateException("Block does not have a valid world"); throw new \LogicException("Block does not have a valid world");
} }
/** /**
@ -577,7 +585,7 @@ class Block{
final public function getCollisionBoxes() : array{ final public function getCollisionBoxes() : array{
if($this->collisionBoxes === null){ if($this->collisionBoxes === null){
$this->collisionBoxes = $this->recalculateCollisionBoxes(); $this->collisionBoxes = $this->recalculateCollisionBoxes();
$extraOffset = $this->getPositionOffset(); $extraOffset = $this->getModelPositionOffset();
$offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position; $offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
foreach($this->collisionBoxes as $bb){ foreach($this->collisionBoxes as $bb){
$bb->offset($offset->x, $offset->y, $offset->z); $bb->offset($offset->x, $offset->y, $offset->z);
@ -588,10 +596,10 @@ class Block{
} }
/** /**
* Returns an additional fractional vector to shift the block's effective position by based on the current position. * Returns an additional fractional vector to shift the block model's position by based on the current position.
* Used to randomize position of things like bamboo canes and tall grass. * Used to randomize position of things like bamboo canes and tall grass.
*/ */
public function getPositionOffset() : ?Vector3{ public function getModelPositionOffset() : ?Vector3{
return null; return null;
} }
@ -605,7 +613,7 @@ class Block{
public function isFullCube() : bool{ public function isFullCube() : bool{
$bb = $this->getCollisionBoxes(); $bb = $this->getCollisionBoxes();
return count($bb) === 1 and $bb[0]->getAverageEdgeLength() >= 1 and $bb[0]->isCube(); return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
} }
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{ public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{

View File

@ -119,8 +119,8 @@ class BlockBreakInfo{
return false; return false;
} }
return $this->toolType === BlockToolType::NONE or $this->toolHarvestLevel === 0 or ( return $this->toolType === BlockToolType::NONE || $this->toolHarvestLevel === 0 || (
($this->toolType & $tool->getBlockToolType()) !== 0 and $tool->getBlockToolHarvestLevel() >= $this->toolHarvestLevel); ($this->toolType & $tool->getBlockToolType()) !== 0 && $tool->getBlockToolHarvestLevel() >= $this->toolHarvestLevel);
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\tile\Tile; use pocketmine\block\tile\Tile;
use pocketmine\utils\Utils;
class BlockIdentifier{ class BlockIdentifier{
@ -40,6 +41,10 @@ class BlockIdentifier{
$this->blockId = $blockId; $this->blockId = $blockId;
$this->variant = $variant; $this->variant = $variant;
$this->itemId = $itemId; $this->itemId = $itemId;
if($tileClass !== null){
Utils::testValidInstance($tileClass, Tile::class);
}
$this->tileClass = $tileClass; $this->tileClass = $tileClass;
} }

View File

@ -142,6 +142,8 @@ final class BlockLegacyMetadata{
public const LEAVES_FLAG_NO_DECAY = 0x04; public const LEAVES_FLAG_NO_DECAY = 0x04;
public const LEAVES_FLAG_CHECK_DECAY = 0x08; public const LEAVES_FLAG_CHECK_DECAY = 0x08;
public const LECTERN_FLAG_POWERED = 0x04;
public const LEVER_FLAG_POWERED = 0x08; public const LEVER_FLAG_POWERED = 0x08;
public const LIQUID_FLAG_FALLING = 0x08; public const LIQUID_FLAG_FALLING = 0x08;

View File

@ -26,6 +26,9 @@ namespace pocketmine\block;
use pocketmine\block\tile\BrewingStand as TileBrewingStand; use pocketmine\block\tile\BrewingStand as TileBrewingStand;
use pocketmine\block\utils\BrewingStandSlot; use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use function array_key_exists; use function array_key_exists;
@ -67,6 +70,19 @@ class BrewingStand extends Transparent{
return 0b111; return 0b111;
} }
protected function recalculateCollisionBoxes() : array{
return [
//bottom slab part - in PC this is also inset on X/Z by 1/16, but Bedrock sucks
AxisAlignedBB::one()->trim(Facing::UP, 7 / 8),
//center post
AxisAlignedBB::one()
->squash(Axis::X, 7 / 16)
->squash(Axis::Z, 7 / 16)
->trim(Facing::UP, 1 / 8)
];
}
public function hasSlot(BrewingStandSlot $slot) : bool{ public function hasSlot(BrewingStandSlot $slot) : bool{
return array_key_exists($slot->id(), $this->slots); return array_key_exists($slot->id(), $this->slots);
} }
@ -100,7 +116,7 @@ class BrewingStand extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){ if($player instanceof Player){
$stand = $this->position->getWorld()->getTile($this->position); $stand = $this->position->getWorld()->getTile($this->position);
if($stand instanceof TileBrewingStand and $stand->canOpenWith($item->getCustomName())){ if($stand instanceof TileBrewingStand && $stand->canOpenWith($item->getCustomName())){
$player->setCurrentWindow($stand->getInventory()); $player->setCurrentWindow($stand->getInventory());
} }
} }
@ -109,6 +125,24 @@ class BrewingStand extends Transparent{
} }
public function onScheduledUpdate() : void{ public function onScheduledUpdate() : void{
//TODO $brewing = $this->position->getWorld()->getTile($this->position);
if($brewing instanceof TileBrewingStand){
if($brewing->onUpdate()){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
}
$changed = false;
foreach(BrewingStandSlot::getAll() as $slot){
$occupied = !$brewing->getInventory()->isSlotEmpty($slot->getSlotNumber());
if($occupied !== $this->hasSlot($slot)){
$this->setSlot($slot, $occupied);
$changed = true;
}
}
if($changed){
$this->position->getWorld()->setBlock($this->position, $this);
}
}
} }
} }

View File

@ -82,7 +82,7 @@ class Cactus extends Transparent{
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->getId() !== BlockLegacyIds::SAND and !$down->isSameType($this)){ if($down->getId() !== BlockLegacyIds::SAND && !$down->isSameType($this)){
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
}else{ }else{
foreach(Facing::HORIZONTAL as $side){ foreach(Facing::HORIZONTAL as $side){
@ -129,7 +129,7 @@ class Cactus extends Transparent{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->getId() === BlockLegacyIds::SAND or $down->isSameType($this)){ if($down->getId() === BlockLegacyIds::SAND || $down->isSameType($this)){
foreach(Facing::HORIZONTAL as $side){ foreach(Facing::HORIZONTAL as $side){
if($this->getSide($side)->isSolid()){ if($this->getSide($side)->isSolid()){
return false; return false;

View File

@ -94,8 +94,7 @@ class Cake extends Transparent implements FoodSource{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){ if($player !== null){
$player->consumeObject($this); return $player->consumeObject($this);
return true;
} }
return false; return false;

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\tile\Chest as TileChest;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\NormalHorizontalFacingInMetadataTrait; use pocketmine\block\utils\NormalHorizontalFacingInMetadataTrait;
use pocketmine\event\block\ChestPairEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
@ -47,14 +48,17 @@ class Chest extends Transparent{
public function onPostPlace() : void{ public function onPostPlace() : void{
$tile = $this->position->getWorld()->getTile($this->position); $tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileChest){ if($tile instanceof TileChest){
foreach([ foreach([false, true] as $clockwise){
Facing::rotateY($this->facing, true), $side = Facing::rotateY($this->facing, $clockwise);
Facing::rotateY($this->facing, false)
] as $side){
$c = $this->getSide($side); $c = $this->getSide($side);
if($c instanceof Chest and $c->isSameType($this) and $c->facing === $this->facing){ if($c instanceof Chest && $c->isSameType($this) && $c->facing === $this->facing){
$pair = $this->position->getWorld()->getTile($c->position); $world = $this->position->getWorld();
if($pair instanceof TileChest and !$pair->isPaired()){ $pair = $world->getTile($c->position);
if($pair instanceof TileChest && !$pair->isPaired()){
[$left, $right] = $clockwise ? [$c, $this] : [$this, $c];
$ev = new ChestPairEvent($left, $right);
$ev->call();
if(!$ev->isCancelled() && $world->getBlock($this->position)->isSameType($this) && $world->getBlock($c->position)->isSameType($c)){
$pair->pairWith($tile); $pair->pairWith($tile);
$tile->pairWith($pair); $tile->pairWith($pair);
break; break;
@ -63,6 +67,7 @@ class Chest extends Transparent{
} }
} }
} }
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){ if($player instanceof Player){
@ -70,8 +75,8 @@ class Chest extends Transparent{
$chest = $this->position->getWorld()->getTile($this->position); $chest = $this->position->getWorld()->getTile($this->position);
if($chest instanceof TileChest){ if($chest instanceof TileChest){
if( if(
!$this->getSide(Facing::UP)->isTransparent() or !$this->getSide(Facing::UP)->isTransparent() ||
(($pair = $chest->getPair()) !== null and !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) or (($pair = $chest->getPair()) !== null && !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) ||
!$chest->canOpenWith($item->getCustomName()) !$chest->canOpenWith($item->getCustomName())
){ ){
return true; return true;

View File

@ -39,6 +39,9 @@ class Cobweb extends Flowable{
} }
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0){
return [$this->asItem()];
}
return [ return [
VanillaItems::STRING() VanillaItems::STRING()
]; ];

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockDataSerializer; use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\TreeType; use pocketmine\block\utils\TreeType;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer; use pocketmine\item\Fertilizer;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
@ -85,7 +86,7 @@ class CocoaBlock extends Transparent{
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(Facing::axis($face) !== Axis::Y and $this->canAttachTo($blockClicked)){ if(Facing::axis($face) !== Axis::Y && $this->canAttachTo($blockClicked)){
$this->facing = $face; $this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }
@ -94,10 +95,7 @@ class CocoaBlock extends Transparent{
} }
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->age < 2 and $item instanceof Fertilizer){ if($item instanceof Fertilizer && $this->grow()){
$this->age++;
$this->position->getWorld()->setBlock($this->position, $this);
$item->pop(); $item->pop();
return true; return true;
@ -117,12 +115,25 @@ class CocoaBlock extends Transparent{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->age < 2 and mt_rand(1, 5) === 1){ if(mt_rand(1, 5) === 1){
$this->age++; $this->grow();
$this->position->getWorld()->setBlock($this->position, $this);
} }
} }
private function grow() : bool{
if($this->age < 2){
$block = clone $this;
$block->age++;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
return true;
}
}
return false;
}
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
return [ return [
VanillaItems::COCOA_BEANS()->setCount($this->age === 2 ? mt_rand(2, 3) : 1) VanillaItems::COCOA_BEANS()->setCount($this->age === 2 ? mt_rand(2, 3) : 1)

View File

@ -27,6 +27,7 @@ use pocketmine\block\utils\ColorInMetadataTrait;
use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\Fallable; use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\FallableTrait;
use pocketmine\event\block\BlockFormEvent;
use pocketmine\math\Facing; use pocketmine\math\Facing;
class ConcretePowder extends Opaque implements Fallable{ class ConcretePowder extends Opaque implements Fallable{
@ -42,7 +43,11 @@ class ConcretePowder extends Opaque implements Fallable{
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(($block = $this->checkAdjacentWater()) !== null){ if(($block = $this->checkAdjacentWater()) !== null){
$this->position->getWorld()->setBlock($this->position, $block); $ev = new BlockFormEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
}else{ }else{
$this->startFalling(); $this->startFalling();
} }

View File

@ -24,15 +24,14 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\CoralType; use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\utils\InvalidBlockStateException; use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\data\bedrock\CoralTypeIdMap; use pocketmine\data\bedrock\CoralTypeIdMap;
use pocketmine\item\Item; use pocketmine\item\Item;
use function mt_rand; use function mt_rand;
final class CoralBlock extends Opaque{ final class CoralBlock extends Opaque{
use CoralTypeTrait;
private CoralType $coralType;
private bool $dead = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){ public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->coralType = CoralType::TUBE(); $this->coralType = CoralType::TUBE();
@ -60,22 +59,6 @@ final class CoralBlock extends Opaque{
return 0b1111; return 0b1111;
} }
public function getCoralType() : CoralType{ return $this->coralType; }
/** @return $this */
public function setCoralType(CoralType $coralType) : self{
$this->coralType = $coralType;
return $this;
}
public function isDead() : bool{ return $this->dead; }
/** @return $this */
public function setDead(bool $dead) : self{
$this->dead = $dead;
return $this;
}
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(!$this->dead){ if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200)); $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));

View File

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

View File

@ -69,7 +69,7 @@ abstract class Crops extends Flowable{
} }
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->age < 7 and $item instanceof Fertilizer){ if($this->age < 7 && $item instanceof Fertilizer){
$block = clone $this; $block = clone $this;
$block->age += mt_rand(2, 5); $block->age += mt_rand(2, 5);
if($block->age > 7){ if($block->age > 7){
@ -80,9 +80,8 @@ abstract class Crops extends Flowable{
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState()); $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
$item->pop(); $item->pop();
}
return true; return true;
} }
@ -101,7 +100,7 @@ abstract class Crops extends Flowable{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->age < 7 and mt_rand(0, 2) === 1){ if($this->age < 7 && mt_rand(0, 2) === 1){
$block = clone $this; $block = clone $this;
++$block->age; ++$block->age;
$ev = new BlockGrowEvent($this, $block); $ev = new BlockGrowEvent($this, $block);

View File

@ -28,6 +28,7 @@ use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\world\sound\ItemUseOnBlockSound;
class Dirt extends Opaque{ class Dirt extends Opaque{
@ -58,9 +59,12 @@ class Dirt extends Opaque{
} }
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP and $item instanceof Hoe){ if($face === Facing::UP && $item instanceof Hoe){
$item->applyDamage(1); $item->applyDamage(1);
$this->position->getWorld()->setBlock($this->position, $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND());
$newBlock = $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND();
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$this->position->getWorld()->setBlock($this->position, $newBlock);
return true; return true;
} }

View File

@ -72,7 +72,7 @@ class Door extends Transparent{
//copy door properties from other half //copy door properties from other half
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door and $other->isSameType($this)){ if($other instanceof Door && $other->isSameType($this)){
if($this->top){ if($this->top){
$this->facing = $other->facing; $this->facing = $other->facing;
$this->open = $other->open; $this->open = $other->open;
@ -129,7 +129,7 @@ class Door extends Transparent{
if($face === Facing::UP){ if($face === Facing::UP){
$blockUp = $this->getSide(Facing::UP); $blockUp = $this->getSide(Facing::UP);
$blockDown = $this->getSide(Facing::DOWN); $blockDown = $this->getSide(Facing::DOWN);
if(!$blockUp->canBeReplaced() or $blockDown->isTransparent()){ if(!$blockUp->canBeReplaced() || $blockDown->isTransparent()){
return false; return false;
} }
@ -140,7 +140,7 @@ class Door extends Transparent{
$next = $this->getSide(Facing::rotateY($this->facing, false)); $next = $this->getSide(Facing::rotateY($this->facing, false));
$next2 = $this->getSide(Facing::rotateY($this->facing, true)); $next2 = $this->getSide(Facing::rotateY($this->facing, true));
if($next->isSameType($this) or (!$next2->isTransparent() and $next->isTransparent())){ //Door hinge if($next->isSameType($this) || (!$next2->isTransparent() && $next->isTransparent())){ //Door hinge
$this->hingeRight = true; $this->hingeRight = true;
} }
@ -158,7 +158,7 @@ class Door extends Transparent{
$this->open = !$this->open; $this->open = !$this->open;
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door and $other->isSameType($this)){ if($other instanceof Door && $other->isSameType($this)){
$other->open = $this->open; $other->open = $this->open;
$this->position->getWorld()->setBlock($other->position, $other); $this->position->getWorld()->setBlock($other->position, $other);
} }

View File

@ -55,7 +55,7 @@ class DoublePlant extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$id = $blockReplace->getSide(Facing::DOWN)->getId(); $id = $blockReplace->getSide(Facing::DOWN)->getId();
if(($id === BlockLegacyIds::GRASS or $id === BlockLegacyIds::DIRT) and $blockReplace->getSide(Facing::UP)->canBeReplaced()){ if(($id === BlockLegacyIds::GRASS || $id === BlockLegacyIds::DIRT) && $blockReplace->getSide(Facing::UP)->canBeReplaced()){
$top = clone $this; $top = clone $this;
$top->top = true; $top->top = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($blockReplace->position->getSide(Facing::UP), $top); $tx->addBlock($blockReplace->position, $this)->addBlock($blockReplace->position->getSide(Facing::UP), $top);
@ -72,14 +72,14 @@ class DoublePlant extends Flowable{
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP); $other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
return ( return (
$other instanceof DoublePlant and $other instanceof DoublePlant &&
$other->isSameType($this) and $other->isSameType($this) &&
$other->top !== $this->top $other->top !== $this->top
); );
} }
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(!$this->isValidHalfPlant() or (!$this->top and $this->getSide(Facing::DOWN)->isTransparent())){ if(!$this->isValidHalfPlant() || (!$this->top && $this->getSide(Facing::DOWN)->isTransparent())){
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
} }
} }

View File

@ -34,7 +34,7 @@ class DoubleTallGrass extends DoublePlant{
} }
public function getDropsForIncompatibleTool(Item $item) : array{ public function getDropsForIncompatibleTool(Item $item) : array{
if($this->top and mt_rand(0, 7) === 0){ if($this->top && mt_rand(0, 7) === 0){
return [VanillaItems::WHEAT_SEEDS()]; return [VanillaItems::WHEAT_SEEDS()];
} }
return []; return [];

View File

@ -46,7 +46,7 @@ class EndRod extends Flowable{
} }
public function readStateFromData(int $id, int $stateMeta) : void{ public function readStateFromData(int $id, int $stateMeta) : void{
if($stateMeta !== 0 and $stateMeta !== 1){ if($stateMeta !== 0 && $stateMeta !== 1){
$stateMeta ^= 1; $stateMeta ^= 1;
} }
@ -59,7 +59,7 @@ class EndRod extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->facing = $face; $this->facing = $face;
if($blockClicked instanceof EndRod and $blockClicked->facing === $this->facing){ if($blockClicked instanceof EndRod && $blockClicked->facing === $this->facing){
$this->facing = Facing::opposite($face); $this->facing = Facing::opposite($face);
} }

View File

@ -52,7 +52,7 @@ class EnderChest extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){ if($player instanceof Player){
$enderChest = $this->position->getWorld()->getTile($this->position); $enderChest = $this->position->getWorld()->getTile($this->position);
if($enderChest instanceof TileEnderChest and $this->getSide(Facing::UP)->isTransparent()){ if($enderChest instanceof TileEnderChest && $this->getSide(Facing::UP)->isTransparent()){
$enderChest->setViewerCount($enderChest->getViewerCount() + 1); $enderChest->setViewerCount($enderChest->getViewerCount() + 1);
$player->setCurrentWindow(new EnderChestInventory($this->position, $player->getEnderInventory())); $player->setCurrentWindow(new EnderChestInventory($this->position, $player->getEnderInventory()));
} }
@ -66,4 +66,8 @@ class EnderChest extends Transparent{
VanillaBlocks::OBSIDIAN()->asItem()->setCount(8) VanillaBlocks::OBSIDIAN()->asItem()->setCount(8)
]; ];
} }
public function isAffectedBySilkTouch() : bool{
return true;
}
} }

View File

@ -41,7 +41,7 @@ class Fence extends Transparent{
foreach(Facing::HORIZONTAL as $facing){ foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing); $block = $this->getSide($facing);
if($block instanceof static or $block instanceof FenceGate or ($block->isSolid() and !$block->isTransparent())){ if($block instanceof static || $block instanceof FenceGate || ($block->isSolid() && !$block->isTransparent())){
$this->connections[$facing] = true; $this->connections[$facing] = true;
}else{ }else{
unset($this->connections[$facing]); unset($this->connections[$facing]);
@ -61,7 +61,7 @@ class Fence extends Transparent{
$connectWest = isset($this->connections[Facing::WEST]); $connectWest = isset($this->connections[Facing::WEST]);
$connectEast = isset($this->connections[Facing::EAST]); $connectEast = isset($this->connections[Facing::EAST]);
if($connectWest or $connectEast){ if($connectWest || $connectEast){
//X axis (west/east) //X axis (west/east)
$bbs[] = AxisAlignedBB::one() $bbs[] = AxisAlignedBB::one()
->squash(Axis::Z, $inset) ->squash(Axis::Z, $inset)
@ -73,7 +73,7 @@ class Fence extends Transparent{
$connectNorth = isset($this->connections[Facing::NORTH]); $connectNorth = isset($this->connections[Facing::NORTH]);
$connectSouth = isset($this->connections[Facing::SOUTH]); $connectSouth = isset($this->connections[Facing::SOUTH]);
if($connectNorth or $connectSouth){ if($connectNorth || $connectSouth){
//Z axis (north/south) //Z axis (north/south)
$bbs[] = AxisAlignedBB::one() $bbs[] = AxisAlignedBB::one()
->squash(Axis::X, $inset) ->squash(Axis::X, $inset)

View File

@ -80,7 +80,7 @@ class FenceGate extends Transparent{
private function checkInWall() : bool{ private function checkInWall() : bool{
return ( return (
$this->getSide(Facing::rotateY($this->facing, false)) instanceof Wall or $this->getSide(Facing::rotateY($this->facing, false)) instanceof Wall ||
$this->getSide(Facing::rotateY($this->facing, true)) instanceof Wall $this->getSide(Facing::rotateY($this->facing, true)) instanceof Wall
); );
} }
@ -105,7 +105,7 @@ class FenceGate extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->open = !$this->open; $this->open = !$this->open;
if($this->open and $player !== null){ if($this->open && $player !== null){
$playerFacing = $player->getHorizontalFacing(); $playerFacing = $player->getHorizontalFacing();
if($playerFacing === Facing::opposite($this->facing)){ if($playerFacing === Facing::opposite($this->facing)){
$this->facing = $playerFacing; $this->facing = $playerFacing;

View File

@ -27,11 +27,16 @@ use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow; use pocketmine\entity\projectile\Arrow;
use pocketmine\event\block\BlockBurnEvent; use pocketmine\event\block\BlockBurnEvent;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\event\entity\EntityCombustByBlockEvent; use pocketmine\event\entity\EntityCombustByBlockEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent; use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\world\format\Chunk;
use pocketmine\world\World;
use function intdiv;
use function max;
use function min; use function min;
use function mt_rand; use function mt_rand;
@ -94,7 +99,7 @@ class Fire extends Flowable{
} }
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(!$this->getSide(Facing::DOWN)->isSolid() and !$this->hasAdjacentFlammableBlocks()){ if(!$this->getSide(Facing::DOWN)->isSolid() && !$this->hasAdjacentFlammableBlocks()){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR()); $this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
}else{ }else{
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40)); $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
@ -109,7 +114,7 @@ class Fire extends Flowable{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
$result = null; $result = null;
if($this->age < 15 and mt_rand(0, 2) === 0){ if($this->age < 15 && mt_rand(0, 2) === 0){
$this->age++; $this->age++;
$result = $this; $result = $this;
} }
@ -118,13 +123,13 @@ class Fire extends Flowable{
if(!$down->burnsForever()){ if(!$down->burnsForever()){
//TODO: check rain //TODO: check rain
if($this->age === 15){ if($this->age === 15){
if(!$down->isFlammable() and mt_rand(0, 3) === 3){ //1/4 chance to extinguish if(!$down->isFlammable() && mt_rand(0, 3) === 3){ //1/4 chance to extinguish
$canSpread = false; $canSpread = false;
$result = VanillaBlocks::AIR(); $result = VanillaBlocks::AIR();
} }
}elseif(!$this->hasAdjacentFlammableBlocks()){ }elseif(!$this->hasAdjacentFlammableBlocks()){
$canSpread = false; $canSpread = false;
if(!$down->isSolid() or $this->age > 3){ if(!$down->isSolid() || $this->age > 3){
$result = VanillaBlocks::AIR(); $result = VanillaBlocks::AIR();
} }
} }
@ -137,17 +142,8 @@ class Fire extends Flowable{
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40)); $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
if($canSpread){ if($canSpread){
//TODO: raise upper bound for chance in humid biomes $this->burnBlocksAround();
$this->spreadFire();
foreach($this->getHorizontalSides() as $side){
$this->burnBlock($side, 300);
}
//vanilla uses a 250 upper bound here, but I don't think they intended to increase the chance of incineration
$this->burnBlock($this->getSide(Facing::UP), 350);
$this->burnBlock($this->getSide(Facing::DOWN), 350);
//TODO: fire spread
} }
} }
@ -165,6 +161,18 @@ class Fire extends Flowable{
return false; return false;
} }
private function burnBlocksAround() : void{
//TODO: raise upper bound for chance in humid biomes
foreach($this->getHorizontalSides() as $side){
$this->burnBlock($side, 300);
}
//vanilla uses a 250 upper bound here, but I don't think they intended to increase the chance of incineration
$this->burnBlock($this->getSide(Facing::UP), 350);
$this->burnBlock($this->getSide(Facing::DOWN), 350);
}
private function burnBlock(Block $block, int $chanceBound) : void{ private function burnBlock(Block $block, int $chanceBound) : void{
if(mt_rand(0, $chanceBound) < $block->getFlammability()){ if(mt_rand(0, $chanceBound) < $block->getFlammability()){
$ev = new BlockBurnEvent($block, $this); $ev = new BlockBurnEvent($block, $this);
@ -172,14 +180,85 @@ class Fire extends Flowable{
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
$block->onIncinerate(); $block->onIncinerate();
$spreadedFire = false;
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
$fire = clone $this; $fire = clone $this;
$fire->age = min(15, $fire->age + (mt_rand(0, 4) >> 2)); $fire->age = min(15, $fire->age + (mt_rand(0, 4) >> 2));
$this->position->getWorld()->setBlock($block->position, $fire); $spreadedFire = $this->spreadBlock($block, $fire);
}else{ }
if(!$spreadedFire){
$this->position->getWorld()->setBlock($block->position, VanillaBlocks::AIR()); $this->position->getWorld()->setBlock($block->position, VanillaBlocks::AIR());
} }
} }
} }
} }
private function spreadFire() : void{
$world = $this->position->getWorld();
$difficultyChanceIncrease = $world->getDifficulty() * 7;
$ageDivisor = $this->age + 30;
for($y = -1; $y <= 4; ++$y){
$targetY = $y + (int) $this->position->y;
if($targetY < World::Y_MIN || $targetY >= World::Y_MAX){
continue;
}
//Higher blocks have a lower chance of catching fire
$randomBound = 100 + ($y > 1 ? ($y - 1) * 100 : 0);
for($z = -1; $z <= 1; ++$z){
$targetZ = $z + (int) $this->position->z;
for($x = -1; $x <= 1; ++$x){
if($x === 0 && $y === 0 && $z === 0){
continue;
}
$targetX = $x + (int) $this->position->x;
if(!$world->isInWorld($targetX, $targetY, $targetZ)){
continue;
}
if(!$world->isChunkLoaded($targetX >> Chunk::COORD_BIT_SIZE, $targetZ >> Chunk::COORD_BIT_SIZE)){
continue;
}
$block = $world->getBlockAt($targetX, $targetY, $targetZ);
if($block->getId() !== BlockLegacyIds::AIR){
continue;
}
//TODO: fire can't spread if it's raining in any horizontally adjacent block, or the current one
$encouragement = 0;
foreach($block->position->sides() as $vector3){
if($world->isInWorld($vector3->x, $vector3->y, $vector3->z)){
$encouragement = max($encouragement, $world->getBlockAt($vector3->x, $vector3->y, $vector3->z)->getFlameEncouragement());
}
}
if($encouragement <= 0){
continue;
}
$maxChance = intdiv($encouragement + 40 + $difficultyChanceIncrease, $ageDivisor);
//TODO: max chance is lowered by half in humid biomes
if($maxChance > 0 && mt_rand(0, $randomBound - 1) <= $maxChance){
$new = clone $this;
$new->age = min(15, $this->age + (mt_rand(0, 4) >> 2));
$this->spreadBlock($block, $new);
}
}
}
}
}
private function spreadBlock(Block $block, Block $newState) : bool{
$ev = new BlockSpreadEvent($block, $this, $newState);
$ev->call();
if(!$ev->isCancelled()){
$block->position->getWorld()->setBlock($block->position, $ev->getNewState());
return true;
}
return false;
}
} }

View File

@ -0,0 +1,30 @@
<?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;
class FletchingTable extends Opaque{
public function getFuelTime() : int{
return 300;
}
}

View File

@ -33,7 +33,7 @@ class Flower extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->getId() === BlockLegacyIds::GRASS or $down->getId() === BlockLegacyIds::DIRT or $down->getId() === BlockLegacyIds::FARMLAND){ if($down->getId() === BlockLegacyIds::GRASS || $down->getId() === BlockLegacyIds::DIRT || $down->getId() === BlockLegacyIds::FARMLAND){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }

View File

@ -69,7 +69,7 @@ class FlowerPot extends Flowable{
/** @return $this */ /** @return $this */
public function setPlant(?Block $plant) : self{ public function setPlant(?Block $plant) : self{
if($plant === null or $plant instanceof Air){ if($plant === null || $plant instanceof Air){
$this->plant = null; $this->plant = null;
}else{ }else{
$this->plant = clone $plant; $this->plant = clone $plant;
@ -83,12 +83,12 @@ class FlowerPot extends Flowable{
} }
return return
$block instanceof Cactus or $block instanceof Cactus ||
$block instanceof DeadBush or $block instanceof DeadBush ||
$block instanceof Flower or $block instanceof Flower ||
$block instanceof RedMushroom or $block instanceof RedMushroom ||
$block instanceof Sapling or $block instanceof Sapling ||
($block instanceof TallGrass and $block->getIdInfo()->getVariant() === BlockLegacyMetadata::TALLGRASS_FERN); //TODO: clean up ($block instanceof TallGrass && $block->getIdInfo()->getVariant() === BlockLegacyMetadata::TALLGRASS_FERN); //TODO: clean up
//TODO: bamboo //TODO: bamboo
} }

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\BlockDataSerializer; use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\event\block\BlockMeltEvent;
use function mt_rand; use function mt_rand;
class FrostedIce extends Ice{ class FrostedIce extends Ice{
@ -62,7 +63,7 @@ class FrostedIce extends Ice{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if((!$this->checkAdjacentBlocks(4) or mt_rand(0, 2) === 0) and if((!$this->checkAdjacentBlocks(4) || mt_rand(0, 2) === 0) &&
$this->position->getWorld()->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){ $this->position->getWorld()->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){
if($this->tryMelt()){ if($this->tryMelt()){
foreach($this->getAllSides() as $block){ foreach($this->getAllSides() as $block){
@ -84,11 +85,11 @@ class FrostedIce extends Ice{
$found = 0; $found = 0;
for($x = -1; $x <= 1; ++$x){ for($x = -1; $x <= 1; ++$x){
for($z = -1; $z <= 1; ++$z){ for($z = -1; $z <= 1; ++$z){
if($x === 0 and $z === 0){ if($x === 0 && $z === 0){
continue; continue;
} }
if( if(
$this->position->getWorld()->getBlockAt($this->position->x + $x, $this->position->y, $this->position->z + $z) instanceof FrostedIce and $this->position->getWorld()->getBlockAt($this->position->x + $x, $this->position->y, $this->position->z + $z) instanceof FrostedIce &&
++$found >= $requirement ++$found >= $requirement
){ ){
return true; return true;
@ -105,7 +106,11 @@ class FrostedIce extends Ice{
*/ */
private function tryMelt() : bool{ private function tryMelt() : bool{
if($this->age >= 3){ if($this->age >= 3){
$this->position->getWorld()->useBreakOn($this->position); $ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
return true; return true;
} }

View File

@ -29,6 +29,7 @@ use pocketmine\block\utils\NormalHorizontalFacingInMetadataTrait;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use function mt_rand;
class Furnace extends Opaque{ class Furnace extends Opaque{
use FacesOppositePlacingPlayerTrait; use FacesOppositePlacingPlayerTrait;
@ -73,7 +74,7 @@ class Furnace extends Opaque{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){ if($player instanceof Player){
$furnace = $this->position->getWorld()->getTile($this->position); $furnace = $this->position->getWorld()->getTile($this->position);
if($furnace instanceof TileFurnace and $furnace->canOpenWith($item->getCustomName())){ if($furnace instanceof TileFurnace && $furnace->canOpenWith($item->getCustomName())){
$player->setCurrentWindow($furnace->getInventory()); $player->setCurrentWindow($furnace->getInventory());
} }
} }
@ -83,7 +84,10 @@ class Furnace extends Opaque{
public function onScheduledUpdate() : void{ public function onScheduledUpdate() : void{
$furnace = $this->position->getWorld()->getTile($this->position); $furnace = $this->position->getWorld()->getTile($this->position);
if($furnace instanceof TileFurnace and $furnace->onUpdate()){ if($furnace instanceof TileFurnace && $furnace->onUpdate()){
if(mt_rand(1, 60) === 1){ //in vanilla this is between 1 and 5 seconds; try to average about 3
$this->position->getWorld()->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
}
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
} }
} }

View File

@ -33,6 +33,7 @@ use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Random; use pocketmine\utils\Random;
use pocketmine\world\generator\object\TallGrass as TallGrassObject; use pocketmine\world\generator\object\TallGrass as TallGrassObject;
use pocketmine\world\sound\ItemUseOnBlockSound;
use function mt_rand; use function mt_rand;
class Grass extends Opaque{ class Grass extends Opaque{
@ -53,7 +54,7 @@ class Grass extends Opaque{
public function onRandomTick() : void{ public function onRandomTick() : void{
$lightAbove = $this->position->getWorld()->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z); $lightAbove = $this->position->getWorld()->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
if($lightAbove < 4 and $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){ if($lightAbove < 4 && $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
//grass dies //grass dies
$ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT()); $ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
$ev->call(); $ev->call();
@ -69,9 +70,9 @@ class Grass extends Opaque{
$b = $this->position->getWorld()->getBlockAt($x, $y, $z); $b = $this->position->getWorld()->getBlockAt($x, $y, $z);
if( if(
!($b instanceof Dirt) or !($b instanceof Dirt) ||
$b->isCoarse() or $b->isCoarse() ||
$this->position->getWorld()->getFullLightAt($x, $y + 1, $z) < 4 or $this->position->getWorld()->getFullLightAt($x, $y + 1, $z) < 4 ||
$this->position->getWorld()->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2 $this->position->getWorld()->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
){ ){
continue; continue;
@ -97,12 +98,16 @@ class Grass extends Opaque{
return true; return true;
}elseif($item instanceof Hoe){ }elseif($item instanceof Hoe){
$item->applyDamage(1); $item->applyDamage(1);
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::FARMLAND()); $newBlock = VanillaBlocks::FARMLAND();
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$this->position->getWorld()->setBlock($this->position, $newBlock);
return true; return true;
}elseif($item instanceof Shovel and $this->getSide(Facing::UP)->getId() === BlockLegacyIds::AIR){ }elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getId() === BlockLegacyIds::AIR){
$item->applyDamage(1); $item->applyDamage(1);
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::GRASS_PATH()); $newBlock = VanillaBlocks::GRASS_PATH();
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$this->position->getWorld()->setBlock($this->position, $newBlock);
return true; return true;
} }

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\PillarRotationInMetadataTrait; use pocketmine\block\utils\PillarRotationInMetadataTrait;
use pocketmine\entity\Entity;
class HayBale extends Opaque{ class HayBale extends Opaque{
use PillarRotationInMetadataTrait; use PillarRotationInMetadataTrait;
@ -35,4 +36,9 @@ class HayBale extends Opaque{
public function getFlammability() : int{ public function getFlammability() : int{
return 20; return 20;
} }
public function onEntityLand(Entity $entity) : ?float{
$entity->fallDistance *= 0.2;
return null;
}
} }

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\event\block\BlockMeltEvent;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\player\Player; use pocketmine\player\Player;
@ -38,7 +39,7 @@ class Ice extends Transparent{
} }
public function onBreak(Item $item, ?Player $player = null) : bool{ public function onBreak(Item $item, ?Player $player = null) : bool{
if(($player === null or $player->isSurvival()) and !$item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){ if(($player === null || $player->isSurvival()) && !$item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::WATER()); $this->position->getWorld()->setBlock($this->position, VanillaBlocks::WATER());
return true; return true;
} }
@ -51,7 +52,11 @@ class Ice extends Transparent{
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->position->getWorld()->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){ if($this->position->getWorld()->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
$this->position->getWorld()->useBreakOn($this->position); $ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
} }
} }

View File

@ -86,7 +86,7 @@ class ItemFrame extends Flowable{
/** @return $this */ /** @return $this */
public function setFramedItem(?Item $item) : self{ public function setFramedItem(?Item $item) : self{
if($item === null or $item->isNull()){ if($item === null || $item->isNull()){
$this->framedItem = null; $this->framedItem = null;
$this->itemRotation = 0; $this->itemRotation = 0;
}else{ }else{
@ -161,7 +161,7 @@ class ItemFrame extends Flowable{
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::DOWN or $face === Facing::UP or !$blockClicked->isSolid()){ if($face === Facing::DOWN || $face === Facing::UP || !$blockClicked->isSolid()){
return false; return false;
} }
@ -172,7 +172,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item); $drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null and lcg_value() <= $this->itemDropChance){ if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
$drops[] = clone $this->framedItem; $drops[] = clone $this->framedItem;
} }

View File

@ -65,7 +65,7 @@ class Ladder extends Transparent{
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$blockClicked->isTransparent() and Facing::axis($face) !== Axis::Y){ if(!$blockClicked->isTransparent() && Facing::axis($face) !== Axis::Y){
$this->facing = $face; $this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }

View File

@ -77,11 +77,11 @@ class Lantern extends Transparent{
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->up())) and !$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->down()))){ if(!$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->up())) && !$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->down()))){
return false; return false;
} }
$this->hanging = ($face === Facing::DOWN or !$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->down()))); $this->hanging = ($face === Facing::DOWN || !$this->canAttachTo($this->position->getWorld()->getBlock($blockReplace->getPosition()->down())));
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }

View File

@ -91,8 +91,6 @@ class Lava extends Liquid{
} }
public function onEntityInside(Entity $entity) : bool{ public function onEntityInside(Entity $entity) : bool{
$entity->fallDistance *= 0.5;
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_LAVA, 4); $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_LAVA, 4);
$entity->attack($ev); $entity->attack($ev);

View File

@ -96,7 +96,7 @@ class Leaves extends Transparent{
return true; return true;
} }
if($block->getId() === $this->getId() and $distance <= 4){ if($block->getId() === $this->getId() && $distance <= 4){
foreach(Facing::ALL as $side){ foreach(Facing::ALL as $side){
if($this->findLog($pos->getSide($side), $visited, $distance + 1)){ if($this->findLog($pos->getSide($side), $visited, $distance + 1)){
return true; return true;
@ -108,7 +108,7 @@ class Leaves extends Transparent{
} }
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
if(!$this->noDecay and !$this->checkDecay){ if(!$this->noDecay && !$this->checkDecay){
$this->checkDecay = true; $this->checkDecay = true;
$this->position->getWorld()->setBlock($this->position, $this, false); $this->position->getWorld()->setBlock($this->position, $this, false);
} }
@ -119,10 +119,10 @@ class Leaves extends Transparent{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if(!$this->noDecay and $this->checkDecay){ if(!$this->noDecay && $this->checkDecay){
$ev = new LeavesDecayEvent($this); $ev = new LeavesDecayEvent($this);
$ev->call(); $ev->call();
if($ev->isCancelled() or $this->findLog($this->position)){ if($ev->isCancelled() || $this->findLog($this->position)){
$this->checkDecay = false; $this->checkDecay = false;
$this->position->getWorld()->setBlock($this->position, $this, false); $this->position->getWorld()->setBlock($this->position, $this, false);
}else{ }else{
@ -145,7 +145,7 @@ class Leaves extends Transparent{
if(mt_rand(1, 20) === 1){ //Saplings if(mt_rand(1, 20) === 1){ //Saplings
$drops[] = ItemFactory::getInstance()->get(ItemIds::SAPLING, $this->treeType->getMagicNumber()); $drops[] = ItemFactory::getInstance()->get(ItemIds::SAPLING, $this->treeType->getMagicNumber());
} }
if(($this->treeType->equals(TreeType::OAK()) or $this->treeType->equals(TreeType::DARK_OAK())) and mt_rand(1, 200) === 1){ //Apples if(($this->treeType->equals(TreeType::OAK()) || $this->treeType->equals(TreeType::DARK_OAK())) && mt_rand(1, 200) === 1){ //Apples
$drops[] = VanillaItems::APPLE(); $drops[] = VanillaItems::APPLE();
} }

166
src/block/Lectern.php Normal file
View File

@ -0,0 +1,166 @@
<?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;
use pocketmine\block\tile\Lectern as TileLectern;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\item\WritableBookBase;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\sound\LecternPlaceBookSound;
use function count;
class Lectern extends Transparent{
use FacesOppositePlacingPlayerTrait;
use HorizontalFacingTrait;
protected int $viewedPage = 0;
protected ?WritableBookBase $book = null;
protected bool $producingSignal = false;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->producingSignal = ($stateMeta & BlockLegacyMetadata::LECTERN_FLAG_POWERED) !== 0;
}
public function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) | ($this->producingSignal ? BlockLegacyMetadata::LECTERN_FLAG_POWERED : 0);
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileLectern){
$this->viewedPage = $tile->getViewedPage();
$this->book = $tile->getBook();
}
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileLectern){
$tile->setViewedPage($this->viewedPage);
$tile->setBook($this->book);
}
}
public function getStateBitmask() : int{
return 0b111;
}
public function getFlammability() : int{
return 30;
}
public function getDrops(Item $item) : array{
$drops = parent::getDrops($item);
if($this->book !== null){
$drops[] = clone $this->book;
}
return $drops;
}
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 0.1)];
}
public function isProducingSignal() : bool{ return $this->producingSignal; }
/** @return $this */
public function setProducingSignal(bool $producingSignal) : self{
$this->producingSignal = $producingSignal;
return $this;
}
public function getViewedPage() : int{
return $this->viewedPage;
}
/** @return $this */
public function setViewedPage(int $viewedPage) : self{
$this->viewedPage = $viewedPage;
return $this;
}
public function getBook() : ?WritableBookBase{
return $this->book !== null ? clone $this->book : null;
}
/** @return $this */
public function setBook(?WritableBookBase $book) : self{
$this->book = $book !== null && !$book->isNull() ? (clone $book)->setCount(1) : null;
$this->viewedPage = 0;
return $this;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->book === null && $item instanceof WritableBookBase){
$this->position->getWorld()->setBlock($this->position, $this->setBook($item));
$this->position->getWorld()->addSound($this->position, new LecternPlaceBookSound());
$item->pop();
}
return true;
}
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
if($this->book !== null){
$this->position->getWorld()->dropItem($this->position->up(), $this->book);
$this->position->getWorld()->setBlock($this->position, $this->setBook(null));
}
return false;
}
public function onPageTurn(int $newPage) : bool{
if($newPage === $this->viewedPage){
return true;
}
if($this->book === null || $newPage >= count($this->book->getPages()) || $newPage < 0){
return false;
}
$this->viewedPage = $newPage;
if(!$this->producingSignal){
$this->producingSignal = true;
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
}
$this->position->getWorld()->setBlock($this->position, $this);
return true;
}
public function onScheduledUpdate() : void{
if($this->producingSignal){
$this->producingSignal = false;
$this->position->getWorld()->setBlock($this->position, $this);
}
}
}

View File

@ -131,7 +131,7 @@ abstract class Liquid extends Transparent{
abstract public function getBucketEmptySound() : Sound; abstract public function getBucketEmptySound() : Sound;
public function isSource() : bool{ public function isSource() : bool{
return !$this->falling and $this->decay === 0; return !$this->falling && $this->decay === 0;
} }
/** /**
@ -154,7 +154,7 @@ abstract class Liquid extends Transparent{
} }
protected function getEffectiveFlowDecay(Block $block) : int{ protected function getEffectiveFlowDecay(Block $block) : int{
if(!($block instanceof Liquid) or !$block->isSameType($this)){ if(!($block instanceof Liquid) || !$block->isSameType($this)){
return -1; return -1;
} }
@ -279,7 +279,7 @@ abstract class Liquid extends Transparent{
$newDecay = $smallestFlowDecay + $multiplier; $newDecay = $smallestFlowDecay + $multiplier;
$falling = false; $falling = false;
if($newDecay >= 8 or $smallestFlowDecay < 0){ if($newDecay >= 8 || $smallestFlowDecay < 0){
$newDecay = -1; $newDecay = -1;
} }
@ -290,14 +290,14 @@ abstract class Liquid extends Transparent{
$minAdjacentSources = $this->getMinAdjacentSourcesToFormSource(); $minAdjacentSources = $this->getMinAdjacentSourcesToFormSource();
if($minAdjacentSources !== null && $this->adjacentSources >= $minAdjacentSources){ if($minAdjacentSources !== null && $this->adjacentSources >= $minAdjacentSources){
$bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z); $bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z);
if($bottomBlock->isSolid() or ($bottomBlock instanceof Liquid and $bottomBlock->isSameType($this) and $bottomBlock->isSource())){ if($bottomBlock->isSolid() || ($bottomBlock instanceof Liquid && $bottomBlock->isSameType($this) && $bottomBlock->isSource())){
$newDecay = 0; $newDecay = 0;
$falling = false; $falling = false;
} }
} }
if($falling !== $this->falling or (!$falling and $newDecay !== $this->decay)){ if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
if(!$falling and $newDecay < 0){ if(!$falling && $newDecay < 0){
$world->setBlock($this->position, VanillaBlocks::AIR()); $world->setBlock($this->position, VanillaBlocks::AIR());
return; return;
} }
@ -312,7 +312,7 @@ abstract class Liquid extends Transparent{
$this->flowIntoBlock($bottomBlock, 0, true); $this->flowIntoBlock($bottomBlock, 0, true);
if($this->isSource() or !$bottomBlock->canBeFlowedInto()){ if($this->isSource() || !$bottomBlock->canBeFlowedInto()){
if($this->falling){ if($this->falling){
$adjacentDecay = 1; //falling liquid behaves like source block $adjacentDecay = 1; //falling liquid behaves like source block
}else{ }else{
@ -331,7 +331,7 @@ abstract class Liquid extends Transparent{
} }
protected function flowIntoBlock(Block $block, int $newFlowDecay, bool $falling) : void{ protected function flowIntoBlock(Block $block, int $newFlowDecay, bool $falling) : void{
if($this->canFlowInto($block) and !($block instanceof Liquid)){ if($this->canFlowInto($block) && !($block instanceof Liquid)){
$new = clone $this; $new = clone $this;
$new->falling = $falling; $new->falling = $falling;
$new->decay = $falling ? 0 : $newFlowDecay; $new->decay = $falling ? 0 : $newFlowDecay;
@ -350,7 +350,7 @@ abstract class Liquid extends Transparent{
/** @phpstan-impure */ /** @phpstan-impure */
private function getSmallestFlowDecay(Block $block, int $decay) : int{ private function getSmallestFlowDecay(Block $block, int $decay) : int{
if(!($block instanceof Liquid) or !$block->isSameType($this)){ if(!($block instanceof Liquid) || !$block->isSameType($this)){
return $decay; return $decay;
} }
@ -381,8 +381,8 @@ abstract class Liquid extends Transparent{
protected function canFlowInto(Block $block) : bool{ protected function canFlowInto(Block $block) : bool{
return return
$this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) and $this->position->getWorld()->isInWorld($block->position->x, $block->position->y, $block->position->z) &&
$block->canBeFlowedInto() and $block->canBeFlowedInto() &&
!($block instanceof Liquid and $block->isSource()); //TODO: I think this should only be liquids of the same type !($block instanceof Liquid && $block->isSource()); //TODO: I think this should only be liquids of the same type
} }
} }

View File

@ -39,7 +39,7 @@ class Magma extends Opaque{
} }
public function onEntityInside(Entity $entity) : bool{ public function onEntityInside(Entity $entity) : bool{
if($entity instanceof Living and !$entity->isSneaking()){ if($entity instanceof Living && !$entity->isSneaking()){
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1); $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev); $entity->attack($ev);
} }

View File

@ -53,7 +53,7 @@ class NetherPortal extends Transparent{
* @return $this * @return $this
*/ */
public function setAxis(int $axis) : self{ public function setAxis(int $axis) : self{
if($axis !== Axis::X and $axis !== Axis::Z){ if($axis !== Axis::X && $axis !== Axis::Z){
throw new \InvalidArgumentException("Invalid axis"); throw new \InvalidArgumentException("Invalid axis");
} }
$this->axis = $axis; $this->axis = $axis;

View File

@ -79,7 +79,7 @@ class NetherWartPlant extends Flowable{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->age < 3 and mt_rand(0, 10) === 0){ //Still growing if($this->age < 3 && mt_rand(0, 10) === 0){ //Still growing
$block = clone $this; $block = clone $this;
$block->age++; $block->age++;
$ev = new BlockGrowEvent($this, $block); $ev = new BlockGrowEvent($this, $block);

View File

@ -59,7 +59,7 @@ class Note extends Opaque{
/** @return $this */ /** @return $this */
public function setPitch(int $pitch) : self{ public function setPitch(int $pitch) : self{
if($pitch < self::MIN_PITCH or $pitch > self::MAX_PITCH){ if($pitch < self::MIN_PITCH || $pitch > self::MAX_PITCH){
throw new \InvalidArgumentException("Pitch must be in range " . self::MIN_PITCH . " - " . self::MAX_PITCH); throw new \InvalidArgumentException("Pitch must be in range " . self::MIN_PITCH . " - " . self::MAX_PITCH);
} }
$this->pitch = $pitch; $this->pitch = $pitch;

View File

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

45
src/block/Pumpkin.php Normal file
View File

@ -0,0 +1,45 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\Shears;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function in_array;
class Pumpkin extends Opaque{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Shears && in_array($face, Facing::HORIZONTAL, true)){
$item->applyDamage(1);
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
return true;
}
return false;
}
}

View File

@ -57,7 +57,7 @@ class RedstoneComparator extends Flowable{
public function readStateFromData(int $id, int $stateMeta) : void{ public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03); $this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->isSubtractMode = ($stateMeta & BlockLegacyMetadata::REDSTONE_COMPARATOR_FLAG_SUBTRACT) !== 0; $this->isSubtractMode = ($stateMeta & BlockLegacyMetadata::REDSTONE_COMPARATOR_FLAG_SUBTRACT) !== 0;
$this->powered = ($id === $this->idInfoFlattened->getSecondId() or ($stateMeta & BlockLegacyMetadata::REDSTONE_COMPARATOR_FLAG_POWERED) !== 0); $this->powered = ($id === $this->idInfoFlattened->getSecondId() || ($stateMeta & BlockLegacyMetadata::REDSTONE_COMPARATOR_FLAG_POWERED) !== 0);
} }
public function writeStateToMeta() : int{ public function writeStateToMeta() : int{

View File

@ -68,7 +68,7 @@ class Sapling extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->getId() === BlockLegacyIds::GRASS or $down->getId() === BlockLegacyIds::DIRT or $down->getId() === BlockLegacyIds::FARMLAND){ if($down->getId() === BlockLegacyIds::GRASS || $down->getId() === BlockLegacyIds::DIRT || $down->getId() === BlockLegacyIds::FARMLAND){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }
@ -76,9 +76,7 @@ class Sapling extends Flowable{
} }
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){ if($item instanceof Fertilizer && $this->grow($player)){
$this->grow();
$item->pop(); $item->pop();
return true; return true;
@ -98,9 +96,9 @@ class Sapling extends Flowable{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->position->getWorld()->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 and mt_rand(1, 7) === 1){ if($this->position->getWorld()->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 && mt_rand(1, 7) === 1){
if($this->ready){ if($this->ready){
$this->grow(); $this->grow(null);
}else{ }else{
$this->ready = true; $this->ready = true;
$this->position->getWorld()->setBlock($this->position, $this); $this->position->getWorld()->setBlock($this->position, $this);
@ -108,21 +106,20 @@ class Sapling extends Flowable{
} }
} }
private function grow() : void{ private function grow(?Player $player) : bool{
$random = new Random(mt_rand()); $random = new Random(mt_rand());
$tree = TreeFactory::get($random, $this->treeType); $tree = TreeFactory::get($random, $this->treeType);
$transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random); $transaction = $tree?->getBlockTransaction($this->position->getWorld(), $this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ(), $random);
if($transaction === null){ if($transaction === null){
return; return false;
} }
$ev = new StructureGrowEvent($this, $transaction); $ev = new StructureGrowEvent($this, $transaction, $player);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if(!$ev->isCancelled()){
return; return $transaction->apply();
} }
return false;
$transaction->apply();
} }
public function getFuelTime() : int{ public function getFuelTime() : int{

View File

@ -83,12 +83,12 @@ class SeaPickle extends Transparent{
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
//TODO: proper placement logic (needs a supporting face below) //TODO: proper placement logic (needs a supporting face below)
return ($blockReplace instanceof SeaPickle and $blockReplace->count < 4) or parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock); return ($blockReplace instanceof SeaPickle && $blockReplace->count < 4) || parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->underwater = false; //TODO: implement this once we have new water logic in place $this->underwater = false; //TODO: implement this once we have new water logic in place
if($blockReplace instanceof SeaPickle and $blockReplace->count < 4){ if($blockReplace instanceof SeaPickle && $blockReplace->count < 4){
$this->count = $blockReplace->count + 1; $this->count = $blockReplace->count + 1;
} }

View File

@ -91,7 +91,7 @@ class ShulkerBox extends Opaque{
$shulker = $this->position->getWorld()->getTile($this->position); $shulker = $this->position->getWorld()->getTile($this->position);
if($shulker instanceof TileShulkerBox){ if($shulker instanceof TileShulkerBox){
if( if(
$this->getSide($this->facing)->getId() !== BlockLegacyIds::AIR or $this->getSide($this->facing)->getId() !== BlockLegacyIds::AIR ||
!$shulker->canOpenWith($item->getCustomName()) !$shulker->canOpenWith($item->getCustomName())
){ ){
return true; return true;

View File

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

View File

@ -90,11 +90,11 @@ class Slab extends Transparent{
return true; return true;
} }
if($blockReplace instanceof Slab and !$blockReplace->slabType->equals(SlabType::DOUBLE()) and $blockReplace->isSameType($this)){ if($blockReplace instanceof Slab && !$blockReplace->slabType->equals(SlabType::DOUBLE()) && $blockReplace->isSameType($this)){
if($blockReplace->slabType->equals(SlabType::TOP())){ //Trying to combine with top slab if($blockReplace->slabType->equals(SlabType::TOP())){ //Trying to combine with top slab
return $clickVector->y <= 0.5 or (!$isClickedBlock and $face === Facing::UP); return $clickVector->y <= 0.5 || (!$isClickedBlock && $face === Facing::UP);
}else{ }else{
return $clickVector->y >= 0.5 or (!$isClickedBlock and $face === Facing::DOWN); return $clickVector->y >= 0.5 || (!$isClickedBlock && $face === Facing::DOWN);
} }
} }
@ -102,9 +102,9 @@ class Slab extends Transparent{
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockReplace instanceof Slab and !$blockReplace->slabType->equals(SlabType::DOUBLE()) and $blockReplace->isSameType($this) and ( if($blockReplace instanceof Slab && !$blockReplace->slabType->equals(SlabType::DOUBLE()) && $blockReplace->isSameType($this) && (
($blockReplace->slabType->equals(SlabType::TOP()) and ($clickVector->y <= 0.5 or $face === Facing::UP)) or ($blockReplace->slabType->equals(SlabType::TOP()) && ($clickVector->y <= 0.5 || $face === Facing::UP)) ||
($blockReplace->slabType->equals(SlabType::BOTTOM()) and ($clickVector->y >= 0.5 or $face === Facing::DOWN)) ($blockReplace->slabType->equals(SlabType::BOTTOM()) && ($clickVector->y >= 0.5 || $face === Facing::DOWN))
)){ )){
//Clicked in empty half of existing slab //Clicked in empty half of existing slab
$this->slabType = SlabType::DOUBLE(); $this->slabType = SlabType::DOUBLE();

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockDataSerializer; use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\Fallable; use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait; use pocketmine\block\utils\FallableTrait;
use pocketmine\event\block\BlockMeltEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
@ -77,7 +78,7 @@ class SnowLayer extends Flowable implements Fallable{
} }
private function canBeSupportedBy(Block $b) : bool{ private function canBeSupportedBy(Block $b) : bool{
return $b->isSolid() or ($b instanceof SnowLayer and $b->isSameType($this) and $b->layers === 8); return $b->isSolid() || ($b instanceof SnowLayer && $b->isSameType($this) && $b->layers === 8);
} }
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@ -100,7 +101,11 @@ class SnowLayer extends Flowable implements Fallable{
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->position->getWorld()->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){ if($this->position->getWorld()->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR(), false); $ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
} }
} }

View File

@ -96,9 +96,9 @@ class Stair extends Transparent{
->trim(Facing::opposite($topStepFace), 0.5) ->trim(Facing::opposite($topStepFace), 0.5)
->trim(Facing::opposite($this->facing), 0.5); ->trim(Facing::opposite($this->facing), 0.5);
if($this->shape->equals(StairShape::OUTER_LEFT()) or $this->shape->equals(StairShape::OUTER_RIGHT())){ if($this->shape->equals(StairShape::OUTER_LEFT()) || $this->shape->equals(StairShape::OUTER_RIGHT())){
$topStep->trim(Facing::rotateY($this->facing, $this->shape->equals(StairShape::OUTER_LEFT())), 0.5); $topStep->trim(Facing::rotateY($this->facing, $this->shape->equals(StairShape::OUTER_LEFT())), 0.5);
}elseif($this->shape->equals(StairShape::INNER_LEFT()) or $this->shape->equals(StairShape::INNER_RIGHT())){ }elseif($this->shape->equals(StairShape::INNER_LEFT()) || $this->shape->equals(StairShape::INNER_RIGHT())){
//add an extra cube //add an extra cube
$bbs[] = AxisAlignedBB::one() $bbs[] = AxisAlignedBB::one()
->trim(Facing::opposite($topStepFace), 0.5) ->trim(Facing::opposite($topStepFace), 0.5)
@ -114,8 +114,8 @@ class Stair extends Transparent{
private function getPossibleCornerFacing(bool $oppositeFacing) : ?int{ private function getPossibleCornerFacing(bool $oppositeFacing) : ?int{
$side = $this->getSide($oppositeFacing ? Facing::opposite($this->facing) : $this->facing); $side = $this->getSide($oppositeFacing ? Facing::opposite($this->facing) : $this->facing);
return ( return (
$side instanceof Stair and $side instanceof Stair &&
$side->upsideDown === $this->upsideDown and $side->upsideDown === $this->upsideDown &&
Facing::axis($side->facing) !== Facing::axis($this->facing) //perpendicular Facing::axis($side->facing) !== Facing::axis($this->facing) //perpendicular
) ? $side->facing : null; ) ? $side->facing : null;
} }
@ -124,7 +124,7 @@ class Stair extends Transparent{
if($player !== null){ if($player !== null){
$this->facing = $player->getHorizontalFacing(); $this->facing = $player->getHorizontalFacing();
} }
$this->upsideDown = (($clickVector->y > 0.5 and $face !== Facing::UP) or $face === Facing::DOWN); $this->upsideDown = (($clickVector->y > 0.5 && $face !== Facing::UP) || $face === Facing::DOWN);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }

View File

@ -53,7 +53,7 @@ abstract class Stem extends Crops{
$side = $this->getSide(Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]); $side = $this->getSide(Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)]);
$d = $side->getSide(Facing::DOWN); $d = $side->getSide(Facing::DOWN);
if($side->getId() === BlockLegacyIds::AIR and ($d->getId() === BlockLegacyIds::FARMLAND or $d->getId() === BlockLegacyIds::GRASS or $d->getId() === BlockLegacyIds::DIRT)){ if($side->getId() === BlockLegacyIds::AIR && ($d->getId() === BlockLegacyIds::FARMLAND || $d->getId() === BlockLegacyIds::GRASS || $d->getId() === BlockLegacyIds::DIRT)){
$ev = new BlockGrowEvent($side, $grow); $ev = new BlockGrowEvent($side, $grow);
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){

View File

@ -48,7 +48,8 @@ class Sugarcane extends Flowable{
return 0b1111; return 0b1111;
} }
private function grow() : void{ private function grow() : bool{
$grew = false;
for($y = 1; $y < 3; ++$y){ for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){ if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break; break;
@ -61,12 +62,14 @@ class Sugarcane extends Flowable{
break; break;
} }
$this->position->getWorld()->setBlock($b->position, $ev->getNewState()); $this->position->getWorld()->setBlock($b->position, $ev->getNewState());
$grew = true;
}else{ }else{
break; break;
} }
} }
$this->age = 0; $this->age = 0;
$this->position->getWorld()->setBlock($this->position, $this); $this->position->getWorld()->setBlock($this->position, $this);
return $grew;
} }
public function getAge() : int{ return $this->age; } public function getAge() : int{ return $this->age; }
@ -82,11 +85,9 @@ class Sugarcane extends Flowable{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){ if($item instanceof Fertilizer){
if(!$this->getSide(Facing::DOWN)->isSameType($this)){ if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
$this->grow();
}
$item->pop(); $item->pop();
}
return true; return true;
} }
@ -96,7 +97,7 @@ class Sugarcane extends Flowable{
public function onNearbyBlockChange() : void{ public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->isTransparent() and !$down->isSameType($this)){ if($down->isTransparent() && !$down->isSameType($this)){
$this->position->getWorld()->useBreakOn($this->position); $this->position->getWorld()->useBreakOn($this->position);
} }
} }
@ -120,7 +121,7 @@ class Sugarcane extends Flowable{
$down = $this->getSide(Facing::DOWN); $down = $this->getSide(Facing::DOWN);
if($down->isSameType($this)){ if($down->isSameType($this)){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}elseif($down->getId() === BlockLegacyIds::GRASS or $down->getId() === BlockLegacyIds::DIRT or $down->getId() === BlockLegacyIds::SAND or $down->getId() === BlockLegacyIds::PODZOL){ }elseif($down->getId() === BlockLegacyIds::GRASS || $down->getId() === BlockLegacyIds::DIRT || $down->getId() === BlockLegacyIds::SAND || $down->getId() === BlockLegacyIds::PODZOL){
foreach(Facing::HORIZONTAL as $side){ foreach(Facing::HORIZONTAL as $side){
if($down->getSide($side) instanceof Water){ if($down->getSide($side) instanceof Water){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);

View File

@ -99,9 +99,9 @@ class SweetBerryBush extends Flowable{
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState()); $this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
} }
$item->pop();
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){ }elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES)); $this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount)); $this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));
@ -135,7 +135,7 @@ class SweetBerryBush extends Flowable{
} }
public function onRandomTick() : void{ public function onRandomTick() : void{
if($this->age < self::STAGE_MATURE and mt_rand(0, 2) === 1){ if($this->age < self::STAGE_MATURE && mt_rand(0, 2) === 1){
$block = clone $this; $block = clone $this;
++$block->age; ++$block->age;
$ev = new BlockGrowEvent($this, $block); $ev = new BlockGrowEvent($this, $block);

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