PM4 LET'S GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO

Merge branch 'master' into stable
This commit is contained in:
Dylan K. Taylor 2021-12-01 22:19:37 +00:00
commit 59de045ecb
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
1703 changed files with 82878 additions and 80038 deletions

View File

@ -43,30 +43,7 @@ jobs:
echo ::set-output name=BUILD_NUMBER::$BUILD_NUMBER
- name: Minify BedrockData JSON files
run: php src/pocketmine/resources/vanilla/.minify_json.php
- name: Run preprocessor
run: |
PM_PREPROCESSOR_PATH="$GITHUB_WORKSPACE/build/preprocessor"
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path=src --multisize || (echo "Preprocessor exited with code $?" && exit 1)
#dump the diff of preprocessor replacements to a patch in case it has bugs
git diff > preprocessor_diff.patch
VENDOR_PM="$GITHUB_WORKSPACE/vendor"
VENDOR_PM_BACKUP="$GITHUB_WORKSPACE/vendor-before-preprocess"
cp -r "$VENDOR_PM" "$VENDOR_PM_BACKUP"
for f in $(ls $VENDOR_PM/pocketmine); do
echo "Processing directory \"$f\"..."
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path="$VENDOR_PM/pocketmine/$f/src" --multisize || (echo "Preprocessor exited with code $?" && exit 1)
echo "Checking for changes in \"$f\"..."
DIFF=$(git diff --no-index "$VENDOR_PM_BACKUP/pocketmine/$f" "$VENDOR_PM/pocketmine/$f" || true)
if [ "$DIFF" != "" ]; then
PATCH="$GITHUB_WORKSPACE/preprocessor_diff_$f.patch"
echo "$DIFF" > "$PATCH"
echo "Generated patch file \"$PATCH\""
else
echo "No diff generated for \"$f\" (preprocessor made no changes)"
fi
done
run: php vendor/pocketmine/bedrock-data/.minify_json.php
- name: Build PocketMine-MP.phar
run: php -dphar.readonly=0 build/server-phar.php --git ${{ github.sha }} --build ${{ steps.build-number.outputs.BUILD_NUMBER }}
@ -74,10 +51,10 @@ jobs:
- name: Get PocketMine-MP release version
id: get-pm-version
run: |
echo ::set-output name=PM_VERSION::$(php -r 'require "vendor/autoload.php"; echo \pocketmine\BASE_VERSION;')
echo ::set-output name=PM_VERSION::$(php -r 'require "vendor/autoload.php"; echo \pocketmine\VersionInfo::BASE_VERSION;')
echo ::set-output name=MCPE_VERSION::$(php -r 'require "vendor/autoload.php"; echo \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK;')
echo ::set-output name=PM_VERSION_SHORT::$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\BASE_VERSION); array_pop($v); echo implode(".", $v);')
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\BASE_VERSION);')
echo ::set-output name=PM_VERSION_SHORT::$(php -r 'require "vendor/autoload.php"; $v = explode(".", \pocketmine\VersionInfo::BASE_VERSION); array_pop($v); echo implode(".", $v);')
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
- name: Generate build info
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json
@ -104,11 +81,3 @@ jobs:
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
Please see the [changelogs](/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
- name: Upload preprocessor diffs
uses: actions/upload-artifact@v2
if: always()
with:
name: preprocessor_diffs
path: ${{ github.workspace }}/preprocessor_diff*.patch

View File

@ -139,8 +139,8 @@ jobs:
- name: Run integration tests
run: ./tests/travis.sh -t4
preprocessor:
name: Preprocessor tests
codegen:
name: Generated Code consistency checks
needs: build-php
runs-on: ${{ matrix.image }}
strategy:
@ -151,8 +151,6 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@e232f72a4330a07aae8418e8aa56b64efcdda636
@ -176,37 +174,16 @@ jobs:
- name: Install Composer dependencies
run: php composer.phar install --no-dev --prefer-dist --no-interaction
- name: Run preprocessor
- name: Regenerate registry annotations
run: php build/generate-registry-annotations.php src
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Verify code is unchanged
run: |
PM_PREPROCESSOR_PATH="$GITHUB_WORKSPACE/build/preprocessor"
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path=src --multisize || (echo "Preprocessor exited with code $?" && exit 1)
#dump the diff of preprocessor replacements to a patch in case it has bugs
git diff > preprocessor_diff.patch
VENDOR_PM="$GITHUB_WORKSPACE/vendor"
VENDOR_PM_BACKUP="$GITHUB_WORKSPACE/vendor-before-preprocess"
cp -r "$VENDOR_PM" "$VENDOR_PM_BACKUP"
for f in $(ls $VENDOR_PM/pocketmine); do
echo "Processing directory \"$f\"..."
php "$PM_PREPROCESSOR_PATH/PreProcessor.php" --path="$VENDOR_PM/pocketmine/$f/src" --multisize || (echo "Preprocessor exited with code $?" && exit 1)
echo "Checking for changes in \"$f\"..."
DIFF=$(git diff --no-index "$VENDOR_PM_BACKUP/pocketmine/$f" "$VENDOR_PM/pocketmine/$f" || true)
if [ "$DIFF" != "" ]; then
PATCH="$GITHUB_WORKSPACE/preprocessor_diff_$f.patch"
echo "$DIFF" > "$PATCH"
echo "Generated patch file \"$PATCH\""
else
echo "No diff generated for \"$f\" (preprocessor made no changes)"
fi
done
- name: Upload preprocessor diffs
uses: actions/upload-artifact@v2
if: always()
with:
name: preprocessor_diffs_${{ matrix.php }}_${{ matrix.image }}
path: ${{ github.workspace }}/preprocessor_diff*.patch
git diff
git diff --quiet
codestyle:
name: Code Style checks

3
.gitignore vendored
View File

@ -1,5 +1,7 @@
players/*
worlds/*
world_conversion_backups/*
backups/*
plugin_data/*
plugins/*
bin*/*
@ -10,6 +12,7 @@ crashdumps/*
*.phar
server.properties
/pocketmine.yml
/plugin_list.yml
memory_dumps/*
resource_packs/
server.lock

9
.gitmodules vendored
View File

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

View File

@ -4,9 +4,8 @@ $finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/src')
->in(__DIR__ . '/build')
->in(__DIR__ . '/tests')
->in(__DIR__ . '/tools')
->notPath('plugins/DevTools')
->notPath('preprocessor')
->notContains('#ifndef COMPILE') //preprocessor will break if these are changed
->notName('PocketMine.php');
return (new PhpCsFixer\Config)

View File

@ -14,20 +14,15 @@ Because PocketMine-MP requires several non-standard PHP extensions and configura
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
## Setting up environment
1. `git clone --recursive https://github.com/pmmp/PocketMine-MP.git`
1. `git clone https://github.com/pmmp/PocketMine-MP.git`
2. `composer install`
## Checking out a different branch to build
1. `git checkout <branch to checkout>`
2. `git submodule update --init`
3. Re-run `composer install` to synchronize dependencies.
2. Re-run `composer install` to synchronize dependencies.
## Optimizing for release builds
1. Add the flags `--no-dev --classmap-authoritative` to your `composer install` command. This will reduce build size and improve autoloading speed.
2. Preprocess the source code by running `build/preprocessor/PreProcessor.php`. Usage instructions are provided in `build/preprocessor/README.md`.
### Note
Preprocessor requires that the `cpp` (c preprocessor) is available in your PATH.
## Building `PocketMine-MP.phar`
Run `composer make-server` using your preferred PHP binary. It'll drop a `PocketMine-MP.phar` into the current working directory.
@ -41,4 +36,4 @@ Fatal error: Uncaught BadMethodCallException: unable to create temporary file in
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
Run `src/pocketmine/PocketMine.php` using your preferred PHP binary.
Run `src/PocketMine.php` using your preferred PHP binary.

View File

@ -30,10 +30,10 @@ if(count($argv) !== 5){
echo json_encode([
"php_version" => sprintf("%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
"base_version" => \pocketmine\BASE_VERSION,
"base_version" => \pocketmine\VersionInfo::BASE_VERSION,
"build" => (int) $argv[4],
"is_dev" => \pocketmine\IS_DEVELOPMENT_BUILD,
"channel" => \pocketmine\BUILD_CHANNEL,
"is_dev" => \pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD,
"channel" => \pocketmine\VersionInfo::BUILD_CHANNEL,
"git_commit" => $argv[1],
"mcpe_version" => \pocketmine\network\mcpe\protocol\ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"date" => time(), //TODO: maybe we should embed this in VersionInfo?

View File

@ -0,0 +1,183 @@
<?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\build\generate_known_translation_apis;
use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use function array_map;
use function count;
use function dirname;
use function file_put_contents;
use function fwrite;
use function implode;
use function is_numeric;
use function ksort;
use function ob_get_clean;
use function ob_start;
use function parse_ini_file;
use function preg_match_all;
use function str_replace;
use function strtoupper;
use const INI_SCANNER_RAW;
use const SORT_STRING;
use const STDERR;
require dirname(__DIR__) . '/vendor/autoload.php';
function constantify(string $permissionName) : string{
return strtoupper(str_replace([".", "-"], "_", $permissionName));
}
function functionify(string $permissionName) : string{
return str_replace([".", "-"], "_", $permissionName);
}
const SHARED_HEADER = <<<'HEADER'
<?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\lang;
HEADER;
/**
* @param string[] $languageDefinitions
* @phpstan-param array<string, string> $languageDefinitions
*/
function generate_known_translation_keys(array $languageDefinitions) : void{
ob_start();
echo SHARED_HEADER;
echo <<<'HEADER'
/**
* 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.
*/
final class KnownTranslationKeys{
HEADER;
ksort($languageDefinitions, SORT_STRING);
foreach(Utils::stringifyKeys($languageDefinitions) as $k => $_){
echo "\tpublic const ";
echo constantify($k);
echo " = \"" . $k . "\";\n";
}
echo "}\n";
file_put_contents(dirname(__DIR__) . '/src/lang/KnownTranslationKeys.php', ob_get_clean());
echo "Done generating KnownTranslationKeys.\n";
}
/**
* @param string[] $languageDefinitions
* @phpstan-param array<string, string> $languageDefinitions
*/
function generate_known_translation_factory(array $languageDefinitions) : void{
ob_start();
echo SHARED_HEADER;
echo <<<'HEADER'
/**
* This class contains factory methods 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.
*/
final class KnownTranslationFactory{
HEADER;
ksort($languageDefinitions, SORT_STRING);
$parameterRegex = '/{%(.+?)}/';
$translationContainerClass = (new \ReflectionClass(Translatable::class))->getShortName();
foreach(Utils::stringifyKeys($languageDefinitions) as $key => $value){
$parameters = [];
if(preg_match_all($parameterRegex, $value, $matches) > 0){
foreach($matches[1] as $parameterName){
if(is_numeric($parameterName)){
$parameters[$parameterName] = "param$parameterName";
}else{
$parameters[$parameterName] = $parameterName;
}
}
}
echo "\tpublic static function " .
functionify($key) .
"(" . implode(", ", array_map(fn(string $paramName) => "$translationContainerClass|string \$$paramName", $parameters)) . ") : $translationContainerClass{\n";
echo "\t\treturn new $translationContainerClass(KnownTranslationKeys::" . constantify($key) . ", [";
foreach($parameters as $parameterKey => $parameterName){
echo "\n\t\t\t";
if(!is_numeric($parameterKey)){
echo "\"$parameterKey\"";
}else{
echo $parameterKey;
}
echo " => \$$parameterName,";
}
if(count($parameters) !== 0){
echo "\n\t\t";
}
echo "]);\n\t}\n\n";
}
echo "}\n";
file_put_contents(dirname(__DIR__) . '/src/lang/KnownTranslationFactory.php', ob_get_clean());
echo "Done generating KnownTranslationFactory.\n";
}
$lang = parse_ini_file(Path::join(\pocketmine\LOCALE_DATA_PATH, "eng.ini"), false, INI_SCANNER_RAW);
if($lang === false){
fwrite(STDERR, "Missing language files!\n");
exit(1);
}
generate_known_translation_keys($lang);
generate_known_translation_factory($lang);

View File

@ -0,0 +1,119 @@
<?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\build\update_registry_annotations;
use function basename;
use function class_exists;
use function count;
use function dirname;
use function file_get_contents;
use function file_put_contents;
use function implode;
use function ksort;
use function mb_strtoupper;
use function preg_match;
use function sprintf;
use function str_replace;
use function substr;
use const SORT_STRING;
if(count($argv) !== 2){
die("Provide a path to process");
}
/**
* @param object[] $members
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
$selfName = basename(__FILE__);
$lines = ["/**"];
$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
$lines[] = " * This must be regenerated whenever registry members are added, removed or changed.";
$lines[] = " * @see build/$selfName";
$lines[] = " * @generate-registry-docblock";
$lines[] = " *";
static $lineTmpl = " * @method static %2\$s %s()";
$memberLines = [];
foreach($members as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false and $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();
}
if($reflect === false){
$typehint = "object";
}elseif($reflect->getNamespaceName() === $namespaceName){
$typehint = $reflect->getShortName();
}else{
$typehint = '\\' . $reflect->getName();
}
$accessor = mb_strtoupper($name);
$memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint);
}
ksort($memberLines, SORT_STRING);
foreach($memberLines as $line){
$lines[] = $line;
}
$lines[] = " */";
return implode("\n", $lines);
}
require dirname(__DIR__) . '/vendor/autoload.php';
foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
if(substr($file, -4) !== ".php"){
continue;
}
$contents = file_get_contents($file);
if($contents === false){
throw new \RuntimeException("Failed to get contents of $file");
}
if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
continue;
}
$shortClassName = basename($file, ".php");
$className = $matches[1] . "\\" . $shortClassName;
if(!class_exists($className)){
continue;
}
$reflect = new \ReflectionClass($className);
$docComment = $reflect->getDocComment();
if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){
continue;
}
echo "Found registry in $file\n";
$replacement = generateMethodAnnotations($matches[1], $className::getAll());
$newContents = str_replace($docComment, $replacement, $contents);
if($newContents !== $contents){
echo "Writing changed file $file\n";
file_put_contents($file, $newContents);
}else{
echo "No changes made to file $file\n";
}
}

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\build\make_release;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use pocketmine\VersionInfo;
use function array_keys;
use function array_map;
use function dirname;
@ -40,8 +42,6 @@ use function sprintf;
use function str_pad;
use function strlen;
use function system;
use const pocketmine\BASE_VERSION;
use const pocketmine\BUILD_CHANNEL;
use const STDERR;
use const STDIN;
use const STDOUT;
@ -77,7 +77,7 @@ const ACCEPTED_OPTS = [
function main() : void{
$filteredOpts = [];
foreach(getopt("", ["current:", "next:", "channel:", "help"]) as $optName => $optValue){
foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help"])) as $optName => $optValue){
if($optName === "help"){
fwrite(STDOUT, "Options:\n");
@ -97,7 +97,7 @@ function main() : void{
if(isset($filteredOpts["current"])){
$currentVer = new VersionString($filteredOpts["current"]);
}else{
$currentVer = new VersionString(BASE_VERSION);
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
}
if(isset($filteredOpts["next"])){
$nextVer = new VersionString($filteredOpts["next"]);
@ -109,7 +109,7 @@ function main() : void{
$currentVer->getPatch() + 1
));
}
$channel = $filteredOpts["channel"] ?? BUILD_CHANNEL;
$channel = $filteredOpts["channel"] ?? VersionInfo::BUILD_CHANNEL;
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
echo "$currentVer will be published on release channel \"$channel\".\n";
@ -121,7 +121,7 @@ function main() : void{
echo "error: no changelog changes detected; aborting\n";
exit(1);
}
$versionInfoPath = dirname(__DIR__) . '/src/pocketmine/VersionInfo.php';
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
system('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"');
system('git tag ' . $currentVer->getBaseVersion());

@ -1 +0,0 @@
Subproject commit 1b9304de614090296a0906e60587789065a4cd6a

View File

@ -150,6 +150,7 @@ function main() : void{
$opts["out"] ?? getcwd() . DIRECTORY_SEPARATOR . "PocketMine-MP.phar",
dirname(__DIR__) . DIRECTORY_SEPARATOR,
[
'resources',
'src',
'vendor'
],
@ -168,7 +169,7 @@ if(!is_readable($tmpDir) or !is_writable($tmpDir)){
exit(1);
}
require("phar://" . __FILE__ . "/src/pocketmine/PocketMine.php");
require("phar://" . __FILE__ . "/src/PocketMine.php");
__HALT_COMPILER();
STUB
,

1767
changelogs/4.0-beta.md Normal file

File diff suppressed because it is too large Load Diff

1522
changelogs/4.0.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,17 @@
"php": "^8.0",
"php-64bit": "*",
"ext-chunkutils2": "^0.3.1",
"ext-crypto": "^0.3.1",
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-gmp": "*",
"ext-hash": "*",
"ext-igbinary": "^3.0.1",
"ext-json": "*",
"ext-leveldb": "^0.2.1 || ^0.3.0",
"ext-mbstring": "*",
"ext-morton": "^0.1.0",
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
@ -27,16 +32,25 @@
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "^1.1",
"pocketmine/binaryutils": "^0.1.9",
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-data": "^1.5.0+bedrock-1.18.0",
"pocketmine/bedrock-protocol": "^7.0.0+bedrock-1.18.0",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.1.0",
"pocketmine/log": "^0.2.0",
"pocketmine/log-pthreads": "^0.1.0",
"pocketmine/math": "^0.2.0",
"pocketmine/nbt": "^0.2.18",
"pocketmine/raklib": "^0.12.7",
"pocketmine/snooze": "^0.1.0",
"pocketmine/spl": "^0.4.0"
"pocketmine/classloader": "^0.2.0",
"pocketmine/color": "^0.2.0",
"pocketmine/errorhandler": "^0.3.0",
"pocketmine/locale-data": "^2.0.16",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/math": "^0.4.0",
"pocketmine/nbt": "^0.3.0",
"pocketmine/raklib": "^0.14.2",
"pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0",
"ramsey/uuid": "^4.1",
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.2.0",
@ -46,17 +60,16 @@
},
"autoload": {
"psr-4": {
"": ["src"]
"pocketmine\\": "src/"
},
"files": [
"src/pocketmine/CoreConstants.php",
"src/pocketmine/GlobalConstants.php",
"src/pocketmine/VersionInfo.php"
"src/CoreConstants.php"
]
},
"autoload-dev": {
"psr-4": {
"pocketmine\\": "tests/phpunit/"
"pocketmine\\": "tests/phpunit/",
"pocketmine\\phpstan\\rules\\": "tests/phpstan/rules"
}
},
"config": {
@ -66,10 +79,16 @@
"sort-packages": true
},
"scripts": {
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/DevTools/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/ConsoleScript.php --make tests/plugins/DevTools --out plugins/DevTools.phar",
"make-server": [
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
"@php -dphar.readonly=0 build/server-phar.php"
],
"update-registry-annotations": [
"@php build/generate-registry-annotations.php src"
],
"update-translation-apis": [
"@php build/generate-known-translation-apis.php"
]
}
}

1218
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,20 @@
includes:
- tests/phpstan/configs/actual-problems.neon
- tests/phpstan/configs/gc-hacks.neon
- tests/phpstan/configs/impossible-generics.neon
- tests/phpstan/configs/php-bugs.neon
- tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/phpunit-wiring-tests.neon
- tests/phpstan/configs/runtime-type-checks.neon
- tests/phpstan/configs/spl-fixed-array-sucks.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
rules:
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
parameters:
level: 9
checkMissingCallableSignature: true
@ -19,26 +24,27 @@ parameters:
scanDirectories:
- build
- tests/plugins/TesterPlugin
- tools
scanFiles:
- src/pocketmine/PocketMine.php
- src/PocketMine.php
paths:
- build
- src
- tests/phpstan/rules
- tests/phpunit
- tests/plugins/TesterPlugin
- tools
excludePaths:
analyseAndScan:
- build/php
- build/preprocessor
analyse:
- src/pocketmine/block/StoneSlab.php #overrides STONE constant
- src/pocketmine/item/Potion.php #overrides WATER constant
dynamicConstantNames:
- pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD
- pocketmine\DEBUG
- pocketmine\IS_DEVELOPMENT_BUILD
stubFiles:
- tests/phpstan/stubs/chunkutils.stub
- tests/phpstan/stubs/JsonMapper.stub
- tests/phpstan/stubs/leveldb.stub
- tests/phpstan/stubs/phpasn1.stub
- tests/phpstan/stubs/pthreads.stub
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
staticReflectionClassNamePatterns:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
#This configuration file allows you to control which plugins are loaded on your server.
#List behaviour
# - blacklist: Only plugins which ARE NOT listed will load.
# - whitelist: Only plugins which ARE listed will load.
mode: blacklist
#List names of plugins here.
plugins: []

View File

@ -92,6 +92,9 @@ network:
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be
#fragmented or split into smaller parts. Clients can request MTU sizes up to but not more than this number.
max-mtu-size: 1492
#Enable encryption of Minecraft network traffic. This has an impact on performance, but prevents hackers from stealing sessions and pretending to be other players.
#DO NOT DISABLE THIS unless you understand the risks involved.
enable-encryption: true
debug:
#If > 1, it will show debug messages in the console
@ -103,13 +106,10 @@ player:
#If true, checks that joining players' Xbox user ID (XUID) match what was previously recorded.
#This also prevents non-XBL players using XBL players' usernames to steal their data on servers with xbox-auth=off.
verify-xuid: true
anti-cheat:
#If false, will try to prevent speed and noclip cheats. May cause movement issues.
allow-movement-cheats: true
level-settings:
#The default format that worlds will use when created
default-format: pmanvil
default-format: leveldb
chunk-sending:
#To change server normal render distance, change view-distance in server.properties.
@ -123,8 +123,9 @@ chunk-ticking:
per-tick: 40
#Radius of chunks around a player to tick
tick-radius: 3
light-updates: false
clear-tick-list: true
#Number of blocks inside ticking areas' subchunks that get ticked every tick. Higher values will accelerate events
#like tree and plant growth, but at a higher performance cost.
blocks-per-subchunk-per-tick: 3
#IDs of blocks not to perform random ticking on.
disable-block-ticking:
#- 2 # grass
@ -154,7 +155,6 @@ auto-updater:
enabled: true
on-update:
warn-console: true
warn-ops: true
#Can be development, alpha, beta or stable.
preferred-channel: stable
#If using a development version, it will suggest changing the channel
@ -180,7 +180,8 @@ worlds:
#Example:
#world:
# seed: 404
# generator: FLAT:2;7,59x1,3x3,2;1;decoration(treecount=80 grasscount=45)
# generator: FLAT
# preset: 2;bedrock,59xstone,3xdirt,grass;1
plugins:
#Setting this to true will cause the legacy structure to be used where plugin data is placed inside the --plugins dir.

View File

@ -33,5 +33,8 @@ if(defined('pocketmine\_CORE_CONSTANTS_INCLUDED')){
}
define('pocketmine\_CORE_CONSTANTS_INCLUDED', true);
define('pocketmine\PATH', dirname(__DIR__, 2) . '/');
define('pocketmine\RESOURCE_PATH', __DIR__ . '/resources/');
define('pocketmine\PATH', dirname(__DIR__) . '/');
define('pocketmine\RESOURCE_PATH', dirname(__DIR__) . '/resources/');
define('pocketmine\BEDROCK_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/bedrock-data/');
define('pocketmine\LOCALE_DATA_PATH', dirname(__DIR__) . '/vendor/pocketmine/locale-data/');
define('pocketmine\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__) . '/vendor/autoload.php');

View File

@ -24,12 +24,14 @@ declare(strict_types=1);
namespace pocketmine;
use pocketmine\event\server\LowMemoryEvent;
use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\scheduler\DumpWorkerMemoryTask;
use pocketmine\scheduler\GarbageCollectionTask;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Process;
use pocketmine\utils\Utils;
use Webmozart\PathUtil\Path;
use function arsort;
use function count;
use function fclose;
@ -43,9 +45,10 @@ use function gc_enable;
use function gc_mem_caches;
use function get_class;
use function get_declared_classes;
use function implode;
use function get_defined_functions;
use function ini_get;
use function ini_set;
use function intdiv;
use function is_array;
use function is_object;
use function is_resource;
@ -67,70 +70,54 @@ use const SORT_NUMERIC;
class MemoryManager{
/** @var Server */
private $server;
private Server $server;
/** @var int */
private $memoryLimit;
/** @var int */
private $globalMemoryLimit;
/** @var int */
private $checkRate;
/** @var int */
private $checkTicker = 0;
/** @var bool */
private $lowMemory = false;
private int $memoryLimit;
private int $globalMemoryLimit;
private int $checkRate;
private int $checkTicker = 0;
private bool $lowMemory = false;
/** @var bool */
private $continuousTrigger = true;
/** @var int */
private $continuousTriggerRate;
/** @var int */
private $continuousTriggerCount = 0;
/** @var int */
private $continuousTriggerTicker = 0;
private bool $continuousTrigger = true;
private int $continuousTriggerRate;
private int $continuousTriggerCount = 0;
private int $continuousTriggerTicker = 0;
/** @var int */
private $garbageCollectionPeriod;
/** @var int */
private $garbageCollectionTicker = 0;
/** @var bool */
private $garbageCollectionTrigger;
/** @var bool */
private $garbageCollectionAsync;
private int $garbageCollectionPeriod;
private int $garbageCollectionTicker = 0;
private bool $garbageCollectionTrigger;
private bool $garbageCollectionAsync;
/** @var int */
private $lowMemChunkRadiusOverride;
/** @var bool */
private $lowMemChunkGC;
private int $lowMemChunkRadiusOverride;
private bool $lowMemChunkGC;
/** @var bool */
private $lowMemDisableChunkCache;
/** @var bool */
private $lowMemClearWorldCache;
private bool $lowMemDisableChunkCache;
private bool $lowMemClearWorldCache;
/** @var bool */
private $dumpWorkers = true;
private bool $dumpWorkers = true;
private \Logger $logger;
public function __construct(Server $server){
$this->server = $server;
$this->logger = new \PrefixedLogger($server->getLogger(), "Memory Manager");
$this->init();
$this->init($server->getConfigGroup());
}
private function init() : void{
$this->memoryLimit = ((int) $this->server->getProperty("memory.main-limit", 0)) * 1024 * 1024;
private function init(ServerConfigGroup $config) : void{
$this->memoryLimit = $config->getPropertyInt("memory.main-limit", 0) * 1024 * 1024;
$defaultMemory = 1024;
if(preg_match("/([0-9]+)([KMGkmg])/", $this->server->getConfigString("memory-limit", ""), $matches) > 0){
if(preg_match("/([0-9]+)([KMGkmg])/", $config->getConfigString("memory-limit", ""), $matches) > 0){
$m = (int) $matches[1];
if($m <= 0){
$defaultMemory = 0;
}else{
switch(mb_strtoupper($matches[2])){
case "K":
$defaultMemory = $m / 1024;
$defaultMemory = intdiv($m, 1024);
break;
case "M":
$defaultMemory = $m;
@ -145,7 +132,7 @@ class MemoryManager{
}
}
$hardLimit = ((int) $this->server->getProperty("memory.main-hard-limit", $defaultMemory));
$hardLimit = $config->getPropertyInt("memory.main-hard-limit", $defaultMemory);
if($hardLimit <= 0){
ini_set("memory_limit", '-1');
@ -153,22 +140,22 @@ class MemoryManager{
ini_set("memory_limit", $hardLimit . "M");
}
$this->globalMemoryLimit = ((int) $this->server->getProperty("memory.global-limit", 0)) * 1024 * 1024;
$this->checkRate = (int) $this->server->getProperty("memory.check-rate", 20);
$this->continuousTrigger = (bool) $this->server->getProperty("memory.continuous-trigger", true);
$this->continuousTriggerRate = (int) $this->server->getProperty("memory.continuous-trigger-rate", 30);
$this->globalMemoryLimit = $config->getPropertyInt("memory.global-limit", 0) * 1024 * 1024;
$this->checkRate = $config->getPropertyInt("memory.check-rate", 20);
$this->continuousTrigger = $config->getPropertyBool("memory.continuous-trigger", true);
$this->continuousTriggerRate = $config->getPropertyInt("memory.continuous-trigger-rate", 30);
$this->garbageCollectionPeriod = (int) $this->server->getProperty("memory.garbage-collection.period", 36000);
$this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true);
$this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true);
$this->garbageCollectionPeriod = $config->getPropertyInt("memory.garbage-collection.period", 36000);
$this->garbageCollectionTrigger = $config->getPropertyBool("memory.garbage-collection.low-memory-trigger", true);
$this->garbageCollectionAsync = $config->getPropertyBool("memory.garbage-collection.collect-async-worker", true);
$this->lowMemChunkRadiusOverride = (int) $this->server->getProperty("memory.max-chunks.chunk-radius", 4);
$this->lowMemChunkGC = (bool) $this->server->getProperty("memory.max-chunks.trigger-chunk-collect", true);
$this->lowMemChunkRadiusOverride = $config->getPropertyInt("memory.max-chunks.chunk-radius", 4);
$this->lowMemChunkGC = $config->getPropertyBool("memory.max-chunks.trigger-chunk-collect", true);
$this->lowMemDisableChunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true);
$this->lowMemClearWorldCache = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true);
$this->lowMemDisableChunkCache = $config->getPropertyBool("memory.world-caches.disable-chunk-cache", true);
$this->lowMemClearWorldCache = $config->getPropertyBool("memory.world-caches.low-memory-trigger", true);
$this->dumpWorkers = (bool) $this->server->getProperty("memory.memory-dump.dump-async-worker", true);
$this->dumpWorkers = $config->getPropertyBool("memory.memory-dump.dump-async-worker", true);
gc_enable();
}
@ -176,6 +163,10 @@ class MemoryManager{
return $this->lowMemory;
}
public function getGlobalMemoryLimit() : int{
return $this->globalMemoryLimit;
}
public function canUseChunkCache() : bool{
return !$this->lowMemory or !$this->lowMemDisableChunkCache;
}
@ -189,21 +180,20 @@ class MemoryManager{
/**
* Triggers garbage collection and cache cleanup to try and free memory.
*
* @return void
*/
public function trigger(int $memory, int $limit, bool $global = false, int $triggerCount = 0){
$this->server->getLogger()->debug(sprintf("[Memory Manager] %sLow memory triggered, limit %gMB, using %gMB",
public function trigger(int $memory, int $limit, bool $global = false, int $triggerCount = 0) : void{
$this->logger->debug(sprintf("%sLow memory triggered, limit %gMB, using %gMB",
$global ? "Global " : "", round(($limit / 1024) / 1024, 2), round(($memory / 1024) / 1024, 2)));
if($this->lowMemClearWorldCache){
foreach($this->server->getLevels() as $level){
$level->clearCache(true);
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->clearCache(true);
}
ChunkCache::pruneCaches();
}
if($this->lowMemChunkGC){
foreach($this->server->getLevels() as $level){
$level->doChunkGarbageCollection();
foreach($this->server->getWorldManager()->getWorlds() as $world){
$world->doChunkGarbageCollection();
}
}
@ -215,16 +205,14 @@ class MemoryManager{
$cycles = $this->triggerGarbageCollector();
}
$this->server->getLogger()->debug(sprintf("[Memory Manager] Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2)));
$this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2)));
}
/**
* Called every tick to update the memory manager state.
*
* @return void
*/
public function check(){
Timings::$memoryManagerTimer->startTiming();
public function check() : void{
Timings::$memoryManager->startTiming();
if(($this->memoryLimit > 0 or $this->globalMemoryLimit > 0) and ++$this->checkTicker >= $this->checkRate){
$this->checkTicker = 0;
@ -257,16 +245,16 @@ class MemoryManager{
$this->triggerGarbageCollector();
}
Timings::$memoryManagerTimer->stopTiming();
Timings::$memoryManager->stopTiming();
}
public function triggerGarbageCollector() : int{
Timings::$garbageCollectorTimer->startTiming();
Timings::$garbageCollector->startTiming();
if($this->garbageCollectionAsync){
$pool = $this->server->getAsyncPool();
if(($w = $pool->shutdownUnusedWorkers()) > 0){
$this->server->getLogger()->debug("Shut down $w idle async pool workers");
$this->logger->debug("Shut down $w idle async pool workers");
}
foreach($pool->getRunningWorkers() as $i){
$pool->submitTaskToWorker(new GarbageCollectionTask(), $i);
@ -276,19 +264,18 @@ class MemoryManager{
$cycles = gc_collect_cycles();
gc_mem_caches();
Timings::$garbageCollectorTimer->stopTiming();
Timings::$garbageCollector->stopTiming();
return $cycles;
}
/**
* Dumps the server memory into the specified output folder.
*
* @return void
*/
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
$this->server->getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $this->server->getLogger());
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize) : void{
$logger = new \PrefixedLogger($this->server->getLogger(), "Memory Dump");
$logger->notice("After the memory dump is done, the server might crash");
self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
if($this->dumpWorkers){
$pool = $this->server->getAsyncPool();
@ -302,11 +289,8 @@ class MemoryManager{
* Static memory dumper accessible from any thread.
*
* @param mixed $startingObject
*
* @return void
* @throws \ReflectionException
*/
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger){
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
$hardLimit = ini_get('memory_limit');
if($hardLimit === false) throw new AssumptionFailedError("memory_limit INI directive should always exist");
ini_set('memory_limit', '-1');
@ -316,9 +300,7 @@ class MemoryManager{
mkdir($outputFolder, 0777, true);
}
$obData = fopen($outputFolder . "/objects.js", "wb+");
$data = [];
$obData = fopen(Path::join($outputFolder, "objects.js"), "wb+");
$objects = [];
@ -329,6 +311,9 @@ class MemoryManager{
$staticProperties = [];
$staticCount = 0;
$functionStaticVars = [];
$functionStaticVarsCount = 0;
foreach(get_declared_classes() as $className){
$reflection = new \ReflectionClass($className);
$staticProperties[$className] = [];
@ -348,10 +333,24 @@ class MemoryManager{
if(count($staticProperties[$className]) === 0){
unset($staticProperties[$className]);
}
foreach($reflection->getMethods() as $method){
if($method->getDeclaringClass()->getName() !== $reflection->getName()){
continue;
}
$methodStatics = [];
foreach($method->getStaticVariables() as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
$functionStaticVars[$className . "::" . $method->getName()] = $methodStatics;
$functionStaticVarsCount += count($functionStaticVars);
}
}
}
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("[Dump] Wrote $staticCount static properties");
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("Wrote $staticCount static properties");
$globalVariables = [];
$globalCount = 0;
@ -368,7 +367,7 @@ class MemoryManager{
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
foreach(Utils::stringifyKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
@ -377,8 +376,23 @@ class MemoryManager{
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
file_put_contents($outputFolder . "/globalVariables.js", json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("[Dump] Wrote $globalCount global variables");
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("Wrote $globalCount global variables");
foreach(get_defined_functions()["user"] as $function){
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach($reflect->getStaticVariables() as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
$functionStaticVars[$function] = $vars;
$functionStaticVarsCount += count($vars);
}
}
file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
$logger->info("Wrote $functionStaticVarsCount function static variables");
$data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
@ -398,60 +412,64 @@ class MemoryManager{
}
$objects[$hash] = true;
$reflection = new \ReflectionObject($object);
$info = [
"information" => "$hash@$className",
"properties" => []
];
if($object instanceof \Closure){
$info["definition"] = Utils::getNiceClosureName($object);
$info["referencedVars"] = [];
$reflect = new \ReflectionFunction($object);
if(($closureThis = $reflect->getClosureThis()) !== null){
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(($parent = $reflection->getParentClass()) !== false){
$info["parent"] = $parent->getName();
}
foreach($reflect->getStaticVariables() as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
$reflection = new \ReflectionObject($object);
if(count($reflection->getInterfaceNames()) > 0){
$info["implements"] = implode(", ", $reflection->getInterfaceNames());
}
$info["properties"] = [];
for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
foreach($reflection->getProperties() as $property){
if($property->isStatic()){
continue;
}
$name = $property->getName();
if($reflection !== $original){
if($property->isPrivate()){
$name = $reflection->getName() . ":" . $name;
}else{
for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
foreach($reflection->getProperties() as $property){
if($property->isStatic()){
continue;
}
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
$name = $property->getName();
if($reflection !== $original){
if($property->isPrivate()){
$name = $reflection->getName() . ":" . $name;
}else{
continue;
}
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}
}
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
}
}while($continue);
$logger->info("[Dump] Wrote " . count($objects) . " objects");
$logger->info("Wrote " . count($objects) . " objects");
fclose($obData);
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
arsort($instanceCounts, SORT_NUMERIC);
file_put_contents($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));
$logger->info("[Dump] Finished!");
$logger->info("Finished!");
ini_set('memory_limit', $hardLimit);
gc_enable();
@ -479,7 +497,7 @@ class MemoryManager{
++$refCounts[$hash];
$data = "(object) $hash@" . get_class($from);
$data = "(object) $hash";
}elseif(is_array($from)){
if($recursion >= 5){
return "(error) ARRAY RECURSION LIMIT REACHED";

View File

@ -24,15 +24,23 @@ declare(strict_types=1);
namespace pocketmine {
use Composer\InstalledVersions;
use pocketmine\utils\Git;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\thread\ThreadManager;
use pocketmine\utils\Filesystem;
use pocketmine\utils\MainLogger;
use pocketmine\utils\Process;
use pocketmine\utils\ServerKiller;
use pocketmine\utils\Terminal;
use pocketmine\utils\Timezone;
use pocketmine\utils\Utils;
use pocketmine\utils\VersionString;
use pocketmine\wizard\SetupWizard;
use Webmozart\PathUtil\Path;
use function defined;
use function extension_loaded;
use function phpversion;
use function preg_match;
use function preg_quote;
use function strpos;
use function version_compare;
require_once __DIR__ . '/VersionInfo.php';
@ -76,11 +84,16 @@ namespace pocketmine {
$extensions = [
"chunkutils2" => "PocketMine ChunkUtils v2",
"curl" => "cURL",
"crypto" => "php-crypto",
"ctype" => "ctype",
"date" => "Date",
"gmp" => "GMP",
"hash" => "Hash",
"igbinary" => "igbinary",
"json" => "JSON",
"leveldb" => "LevelDB",
"mbstring" => "Multibyte String",
"morton" => "morton",
"openssl" => "OpenSSL",
"pcre" => "PCRE",
"phar" => "Phar",
@ -114,15 +127,18 @@ namespace pocketmine {
if(version_compare($leveldb_version, "0.2.1") < 0){
$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");
$wantedVersionLock = "0.3";
$wantedVersionMin = "$wantedVersionLock.0";
if($chunkutils2_version !== false && (
version_compare($chunkutils2_version, $wantedVersionMin) < 0 ||
preg_match("/^" . preg_quote($wantedVersionLock, "/") . "\.\d+(?:-dev)?$/", $chunkutils2_version) === 0 //lock in at ^0.2, optionally at a patch release
)){
version_compare($chunkutils2_version, $wantedVersionMin) < 0 ||
preg_match("/^" . preg_quote($wantedVersionLock, "/") . "\.\d+(?:-dev)?$/", $chunkutils2_version) === 0 //lock in at ^0.2, optionally at a patch release
)){
$messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version.";
}
@ -130,11 +146,14 @@ namespace pocketmine {
$messages[] = "The native PocketMine extension is no longer supported.";
}
if(!defined('AF_INET6')){
$messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support.";
}
return $messages;
}
/**
* @param \Logger $logger
* @return void
*/
function emit_performance_warnings(\Logger $logger){
@ -144,9 +163,6 @@ namespace pocketmine {
if(extension_loaded("xdebug")){
$logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
}
if(!extension_loaded("pocketmine_chunkutils")){
$logger->warning("ChunkUtils extension is missing. Anvil-format worlds will experience degraded performance.");
}
if(((int) ini_get('zend.assertions')) !== -1){
$logger->warning("Debugging assertions are enabled. This may degrade performance. To disable them, set `zend.assertions = -1` in php.ini.");
}
@ -206,48 +222,18 @@ JIT_WARNING
error_reporting(-1);
set_ini_entries();
$opts = getopt("", ["bootstrap:"]);
if(isset($opts["bootstrap"])){
$bootstrap = ($real = realpath($opts["bootstrap"])) !== false ? $real : $opts["bootstrap"];
}else{
$bootstrap = dirname(__FILE__, 3) . '/vendor/autoload.php';
}
if($bootstrap === false or !is_file($bootstrap)){
$bootstrap = dirname(__FILE__, 2) . '/vendor/autoload.php';
if(!is_file($bootstrap)){
critical_error("Composer autoloader not found at " . $bootstrap);
critical_error("Please install/update Composer dependencies or use provided builds.");
exit(1);
}
define('pocketmine\COMPOSER_AUTOLOADER_PATH', $bootstrap);
require_once(\pocketmine\COMPOSER_AUTOLOADER_PATH);
set_error_handler([Utils::class, 'errorExceptionHandler']);
$gitHash = str_repeat("00", 20);
$buildNumber = 0;
if(\Phar::running(true) === ""){
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
}
if(isset($meta["build"]) && is_int($meta["build"])){
$buildNumber = $meta["build"];
}
}
define('pocketmine\GIT_COMMIT', $gitHash);
define('pocketmine\BUILD_NUMBER', $buildNumber);
$version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER);
define('pocketmine\VERSION', $version->getFullVersion(true));
require_once($bootstrap);
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){
$currentGitHash = explode("-", \pocketmine\GIT_COMMIT)[0];
//we can't verify dependency versions if we were installed without using git
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
if($currentGitHash !== $composerGitHash){
critical_error("Composer dependencies and/or autoloader are out of sync.");
critical_error("- Current revision is $currentGitHash");
@ -257,38 +243,31 @@ JIT_WARNING
exit(1);
}
}
if(extension_loaded('parallel')){
\parallel\bootstrap(\pocketmine\COMPOSER_AUTOLOADER_PATH);
}
ErrorToExceptionHandler::set();
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR);
define('pocketmine\PLUGIN_PATH', isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR);
$dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR;
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : realpath(getcwd()) . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR;
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
if(!file_exists(\pocketmine\DATA)){
mkdir(\pocketmine\DATA, 0777, true);
if(!file_exists($dataPath)){
mkdir($dataPath, 0777, true);
}
$lockFile = fopen(\pocketmine\DATA . 'server.lock', "a+b");
if($lockFile === false){
critical_error("Unable to open server.lock file. Please check that the current user has read/write permissions to it.");
exit(1);
}
define('pocketmine\LOCK_FILE', $lockFile);
if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the
//other server wrote its PID and released exclusive lock before we get our lock
flock(\pocketmine\LOCK_FILE, LOCK_SH);
$pid = stream_get_contents(\pocketmine\LOCK_FILE);
critical_error("Another " . \pocketmine\NAME . " instance (PID $pid) is already using this folder (" . realpath(\pocketmine\DATA) . ").");
$lockFilePath = Path::join($dataPath, 'server.lock');
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
ftruncate(\pocketmine\LOCK_FILE, 0);
fwrite(\pocketmine\LOCK_FILE, (string) getmypid());
fflush(\pocketmine\LOCK_FILE);
flock(\pocketmine\LOCK_FILE, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading
//Logger has a dependency on timezone
$tzError = Timezone::init();
Timezone::init();
if(isset($opts["enable-ansi"])){
Terminal::init(true);
@ -298,36 +277,28 @@ JIT_WARNING
Terminal::init();
}
$logger = new MainLogger(\pocketmine\DATA . "server.log");
$logger->registerStatic();
foreach($tzError as $e){
$logger->warning($e);
}
unset($tzError);
$logger = new MainLogger(Path::join($dataPath, "server.log"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
\GlobalLogger::set($logger);
emit_performance_warnings($logger);
$exitCode = 0;
do{
if(!file_exists(\pocketmine\DATA . "server.properties") and !isset($opts["no-wizard"])){
$installer = new SetupWizard();
if(!file_exists(Path::join($dataPath, "server.properties")) and !isset($opts["no-wizard"])){
$installer = new SetupWizard($dataPath);
if(!$installer->run()){
$exitCode = -1;
break;
}
}
//TODO: move this to a Server field
define('pocketmine\START_TIME', microtime(true));
/*
* We now use the Composer autoloader, but this autoloader is still for loading plugins.
*/
$autoloader = new \BaseClassLoader();
$autoloader->register(false);
new Server($autoloader, $logger, \pocketmine\DATA, \pocketmine\PLUGIN_PATH);
new Server($autoloader, $logger, $dataPath, $pluginPath);
$logger->info("Stopping other threads");
@ -337,22 +308,15 @@ JIT_WARNING
if(ThreadManager::getInstance()->stopAll() > 0){
$logger->debug("Some threads could not be stopped, performing a force-kill");
Process::kill(Process::pid());
Process::kill(Process::pid(), true);
}
}while(false);
$logger->shutdown();
$logger->join();
$logger->shutdownLogWriterThread();
echo Terminal::$FORMAT_RESET . PHP_EOL;
if(!flock(\pocketmine\LOCK_FILE, LOCK_UN)){
critical_error("Failed to release the server.lock file.");
}
if(!fclose(\pocketmine\LOCK_FILE)){
critical_error("Could not close server.lock resource.");
}
Filesystem::releaseLockFile($lockFilePath);
exit($exitCode);
}

1816
src/Server.php Normal file

File diff suppressed because it is too large Load Diff

146
src/ServerConfigGroup.php Normal file
View File

@ -0,0 +1,146 @@
<?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 pocketmine\utils\Config;
use function array_key_exists;
use function getopt;
use function is_bool;
use function is_int;
use function is_string;
use function strtolower;
final class ServerConfigGroup{
/** @var Config */
private $pocketmineYml;
/** @var Config */
private $serverProperties;
/**
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private $propertyCache = [];
public function __construct(Config $pocketmineYml, Config $serverProperties){
$this->pocketmineYml = $pocketmineYml;
$this->serverProperties = $serverProperties;
}
/**
* @param mixed $defaultValue
*
* @return mixed
*/
public function getProperty(string $variable, $defaultValue = null){
if(!array_key_exists($variable, $this->propertyCache)){
$v = getopt("", ["$variable::"]);
if(isset($v[$variable])){
$this->propertyCache[$variable] = $v[$variable];
}else{
$this->propertyCache[$variable] = $this->pocketmineYml->getNested($variable);
}
}
return $this->propertyCache[$variable] ?? $defaultValue;
}
public function getPropertyBool(string $variable, bool $defaultValue) : bool{
return (bool) $this->getProperty($variable, $defaultValue);
}
public function getPropertyInt(string $variable, int $defaultValue) : int{
return (int) $this->getProperty($variable, $defaultValue);
}
public function getPropertyString(string $variable, string $defaultValue) : string{
return (string) $this->getProperty($variable, $defaultValue);
}
public function getConfigString(string $variable, string $defaultValue = "") : string{
$v = getopt("", ["$variable::"]);
if(isset($v[$variable])){
return (string) $v[$variable];
}
return $this->serverProperties->exists($variable) ? (string) $this->serverProperties->get($variable) : $defaultValue;
}
public function setConfigString(string $variable, string $value) : void{
$this->serverProperties->set($variable, $value);
}
public function getConfigInt(string $variable, int $defaultValue = 0) : int{
$v = getopt("", ["$variable::"]);
if(isset($v[$variable])){
return (int) $v[$variable];
}
return $this->serverProperties->exists($variable) ? (int) $this->serverProperties->get($variable) : $defaultValue;
}
public function setConfigInt(string $variable, int $value) : void{
$this->serverProperties->set($variable, $value);
}
public function getConfigBool(string $variable, bool $defaultValue = false) : bool{
$v = getopt("", ["$variable::"]);
if(isset($v[$variable])){
$value = $v[$variable];
}else{
$value = $this->serverProperties->exists($variable) ? $this->serverProperties->get($variable) : $defaultValue;
}
if(is_bool($value)){
return $value;
}
if(is_int($value)){
return $value !== 0;
}
if(is_string($value)){
switch(strtolower($value)){
case "on":
case "true":
case "1":
case "yes":
return true;
}
}
return false;
}
public function setConfigBool(string $variable, bool $value) : void{
$this->serverProperties->set($variable, $value ? "1" : "0");
}
public function save() : void{
if($this->serverProperties->hasChanged()){
$this->serverProperties->save();
}
if($this->pocketmineYml->hasChanged()){
$this->pocketmineYml->save();
}
}
}

91
src/VersionInfo.php Normal file
View File

@ -0,0 +1,91 @@
<?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 pocketmine\utils\Git;
use pocketmine\utils\VersionString;
use function is_array;
use function is_int;
use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.0.0-BETA16";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "beta";
private function __construct(){
//NOOP
}
/** @var string|null */
private static $gitHash = null;
public static function GIT_HASH() : string{
if(self::$gitHash === null){
$gitHash = str_repeat("00", 20);
if(\Phar::running(true) === ""){
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
$phar = new \Phar(\Phar::running(false));
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
}
}
self::$gitHash = $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 */
private static $fullVersion = null;
public static function VERSION() : VersionString{
if(self::$fullVersion === null){
self::$fullVersion = new VersionString(self::BASE_VERSION, self::IS_DEVELOPMENT_BUILD, self::BUILD_NUMBER());
}
return self::$fullVersion;
}
}

View File

@ -23,13 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
class ActivatorRail extends RedstoneRail{
use pocketmine\block\utils\RailPoweredByRedstoneTrait;
protected $id = self::ACTIVATOR_RAIL;
public function getName() : string{
return "Activator Rail";
}
class ActivatorRail extends StraightOnlyRail{
use RailPoweredByRedstoneTrait;
//TODO
}

View File

@ -23,29 +23,33 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
class Bedrock extends Solid{
/**
* Air block
*/
class Air extends Transparent{
protected $id = self::BEDROCK;
public function __construct(int $meta = 0){
$this->meta = $meta;
public function canBeFlowedInto() : bool{
return true;
}
public function getName() : string{
return "Bedrock";
public function canBeReplaced() : bool{
return true;
}
public function getHardness() : float{
return -1;
}
public function getBlastResistance() : float{
return 18000000;
}
public function isBreakable(Item $item) : bool{
public function canBePlaced() : bool{
return false;
}
public function isSolid() : bool{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
}

97
src/block/Anvil.php Normal file
View File

@ -0,0 +1,97 @@
<?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\inventory\AnvilInventory;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Anvil extends Transparent implements Fallable{
use FallableTrait;
use HorizontalFacingTrait;
private int $damage = 0;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) | ($this->damage << 2);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x3);
$this->damage = BlockDataSerializer::readBoundedInt("damage", $stateMeta >> 2, 0, 2);
}
public function getStateBitmask() : int{
return 0b1111;
}
protected function writeStateToItemMeta() : int{
return $this->damage << 2;
}
public function getDamage() : int{ return $this->damage; }
/** @return $this */
public function setDamage(int $damage) : self{
if($damage < 0 || $damage > 2){
throw new \InvalidArgumentException("Damage must be in range 0-2");
}
$this->damage = $damage;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->squash(Facing::axis(Facing::rotateY($this->facing, false)), 1 / 8)];
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$player->setCurrentWindow(new AnvilInventory($this->position));
}
return true;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$this->facing = Facing::rotateY($player->getHorizontalFacing(), true);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function tickFalling() : ?Block{
return null;
}
}

240
src/block/Bamboo.php Normal file
View File

@ -0,0 +1,240 @@
<?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\utils\BlockDataSerializer;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Bamboo as ItemBamboo;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function count;
use function gmp_add;
use function gmp_and;
use function gmp_intval;
use function gmp_mul;
use function gmp_xor;
use function min;
use function mt_rand;
use const PHP_INT_MAX;
class Bamboo extends Transparent{
public const NO_LEAVES = 0;
public const SMALL_LEAVES = 1;
public const LARGE_LEAVES = 2;
protected bool $thick = false; //age in PC, but this is 0/1
protected bool $ready = false;
protected int $leafSize = self::NO_LEAVES;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->thick = ($stateMeta & BlockLegacyMetadata::BAMBOO_FLAG_THICK) !== 0;
$this->leafSize = BlockDataSerializer::readBoundedInt("leafSize", ($stateMeta >> BlockLegacyMetadata::BAMBOO_LEAF_SIZE_SHIFT) & BlockLegacyMetadata::BAMBOO_LEAF_SIZE_MASK, self::NO_LEAVES, self::LARGE_LEAVES);
$this->ready = ($stateMeta & BlockLegacyMetadata::BAMBOO_FLAG_READY) !== 0;
}
public function writeStateToMeta() : int{
return ($this->thick ? BlockLegacyMetadata::BAMBOO_FLAG_THICK : 0) | ($this->leafSize << BlockLegacyMetadata::BAMBOO_LEAF_SIZE_SHIFT) | ($this->ready ? BlockLegacyMetadata::BAMBOO_FLAG_READY : 0);
}
public function getStateBitmask() : int{
return 0b1111;
}
public function isThick() : bool{ return $this->thick; }
/** @return $this */
public function setThick(bool $thick) : self{
$this->thick = $thick;
return $this;
}
public function isReady() : bool{ return $this->ready; }
/** @return $this */
public function setReady(bool $ready) : self{
$this->ready = $ready;
return $this;
}
public function getLeafSize() : int{ return $this->leafSize; }
/** @return $this */
public function setLeafSize(int $leafSize) : self{
$this->leafSize = $leafSize;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//this places the BB at the northwest corner, not the center
$inset = 1 - (($this->thick ? 3 : 2) / 16);
return [AxisAlignedBB::one()->trim(Facing::SOUTH, $inset)->trim(Facing::EAST, $inset)];
}
private static function getOffsetSeed(int $x, int $y, int $z) : int{
$p1 = gmp_mul($z, 0x6ebfff5);
$p2 = gmp_mul($x, 0x2fc20f);
$p3 = $y;
$xord = gmp_xor(gmp_xor($p1, $p2), $p3);
$fullResult = gmp_mul(gmp_add(gmp_mul($xord, 0x285b825), 0xb), $xord);
return gmp_intval(gmp_and($fullResult, 0xffffffff));
}
private static function getMaxHeight(int $x, int $z) : int{
return 12 + (self::getOffsetSeed($x, 0, $z) % 5);
}
public function getModelPositionOffset() : ?Vector3{
$seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ());
$retX = (($seed % 12) + 1) / 16;
$retZ = ((($seed >> 8) % 12) + 1) / 16;
return new Vector3($retX, 0, $retZ);
}
private function canBeSupportedBy(Block $block) : bool{
//TODO: tags would be better for this
return
$block instanceof Dirt ||
$block instanceof Grass ||
$block instanceof Gravel ||
$block instanceof Sand ||
$block instanceof Mycelium ||
$block instanceof Podzol;
}
private function seekToTop() : Bamboo{
$world = $this->position->getWorld();
$top = $this;
while(($next = $world->getBlock($top->position->up())) instanceof Bamboo && $next->isSameType($this)){
$top = $next;
}
return $top;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer){
$top = $this->seekToTop();
if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2), $player)){
$item->pop();
return true;
}
}elseif($item instanceof ItemBamboo){
if($this->seekToTop()->grow(PHP_INT_MAX, 1, $player)){
$item->pop();
return true;
}
}
return false;
}
public function onNearbyBlockChange() : void{
$below = $this->position->getWorld()->getBlock($this->position->down());
if(!$this->canBeSupportedBy($below) and !$below->isSameType($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false;
}
$height = 1;
while($world->getBlock($this->position->subtract(0, $height, 0))->isSameType($this)){
if(++$height >= $maxHeight){
return false;
}
}
$newHeight = $height + $growAmount;
$stemBlock = (clone $this)->setReady(false)->setLeafSize(self::NO_LEAVES);
if($newHeight >= 4 && !$stemBlock->isThick()){ //don't change it to false if height is less, because it might have been chopped
$stemBlock = $stemBlock->setThick(true);
}
$smallLeavesBlock = (clone $stemBlock)->setLeafSize(self::SMALL_LEAVES);
$bigLeavesBlock = (clone $stemBlock)->setLeafSize(self::LARGE_LEAVES);
$newBlocks = [];
if($newHeight === 2){
$newBlocks[] = $smallLeavesBlock;
}elseif($newHeight === 3){
$newBlocks[] = $smallLeavesBlock;
$newBlocks[] = $smallLeavesBlock;
}elseif($newHeight === 4){
$newBlocks[] = $bigLeavesBlock;
$newBlocks[] = $smallLeavesBlock;
$newBlocks[] = $stemBlock;
$newBlocks[] = $stemBlock;
}elseif($newHeight > 4){
$newBlocks[] = $bigLeavesBlock;
$newBlocks[] = $bigLeavesBlock;
$newBlocks[] = $smallLeavesBlock;
for($i = 0, $max = min($growAmount, $newHeight - count($newBlocks)); $i < $max; ++$i){
$newBlocks[] = $stemBlock; //to replace the bottom blocks that currently have leaves
}
}
$tx = new BlockTransaction($this->position->getWorld());
foreach($newBlocks as $idx => $newBlock){
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
}
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
if($ev->isCancelled()){
return false;
}
return $tx->apply();
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($this->ready){
$this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1, null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){
$this->ready = true;
$world->setBlock($this->position, $this);
}
}
}

130
src/block/BambooSapling.php Normal file
View File

@ -0,0 +1,130 @@
<?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\event\block\StructureGrowEvent;
use pocketmine\item\Bamboo as ItemBamboo;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class BambooSapling extends Flowable{
private bool $ready = false;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->ready = ($stateMeta & BlockLegacyMetadata::SAPLING_FLAG_READY) !== 0;
}
protected function writeStateToMeta() : int{
return $this->ready ? BlockLegacyMetadata::SAPLING_FLAG_READY : 0;
}
public function getStateBitmask() : int{ return 0b1000; }
public function isReady() : bool{ return $this->ready; }
/** @return $this */
public function setReady(bool $ready) : self{
$this->ready = $ready;
return $this;
}
private function canBeSupportedBy(Block $block) : bool{
//TODO: tags would be better for this
return
$block instanceof Dirt ||
$block instanceof Grass ||
$block instanceof Gravel ||
$block instanceof Sand ||
$block instanceof Mycelium ||
$block instanceof Podzol;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedBy($blockReplace->position->getWorld()->getBlock($blockReplace->position->down()))){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer || $item instanceof ItemBamboo){
if($this->grow($player)){
$item->pop();
return true;
}
}
return false;
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedBy($this->position->getWorld()->getBlock($this->position->down()))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function grow(?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
return false;
}
$tx = new BlockTransaction($world);
$bamboo = VanillaBlocks::BAMBOO();
$tx->addBlock($this->position, $bamboo)
->addBlock($this->position->up(), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES));
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
if($ev->isCancelled()){
return false;
}
return $tx->apply();
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
$world = $this->position->getWorld();
if($this->ready){
$this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow(null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){
$this->ready = true;
$world->setBlock($this->position, $this);
}
}
public function asItem() : Item{
return VanillaBlocks::BAMBOO()->asItem();
}
}

102
src/block/Barrel.php Normal file
View File

@ -0,0 +1,102 @@
<?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\Barrel as TileBarrel;
use pocketmine\block\utils\AnyFacingTrait;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function abs;
class Barrel extends Opaque{
use AnyFacingTrait;
protected bool $open = false;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeFacing($this->facing) | ($this->open ? BlockLegacyMetadata::BARREL_FLAG_OPEN : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readFacing($stateMeta & 0x07);
$this->open = ($stateMeta & BlockLegacyMetadata::BARREL_FLAG_OPEN) === BlockLegacyMetadata::BARREL_FLAG_OPEN;
}
public function getStateBitmask() : int{
return 0b1111;
}
public function isOpen() : bool{
return $this->open;
}
/** @return $this */
public function setOpen(bool $open) : Barrel{
$this->open = $open;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
if(abs($player->getPosition()->getX() - $this->position->getX()) < 2 && abs($player->getPosition()->getZ() - $this->position->getZ()) < 2){
$y = $player->getEyePos()->getY();
if($y - $this->position->getY() > 2){
$this->facing = Facing::UP;
}elseif($this->position->getY() - $y > 0){
$this->facing = Facing::DOWN;
}else{
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
}else{
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$barrel = $this->position->getWorld()->getTile($this->position);
if($barrel instanceof TileBarrel){
if(!$barrel->canOpenWith($item->getCustomName())){
return true;
}
$player->setCurrentWindow($barrel->getInventory());
}
}
return true;
}
public function getFuelTime() : int{
return 300;
}
}

147
src/block/BaseBanner.php Normal file
View File

@ -0,0 +1,147 @@
<?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\Banner as TileBanner;
use pocketmine\block\utils\BannerPatternLayer;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\data\bedrock\DyeColorIdMap;
use pocketmine\item\Banner as ItemBanner;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function array_filter;
use function assert;
use function count;
abstract class BaseBanner extends Transparent{
use ColoredTrait;
/**
* @var BannerPatternLayer[]
* @phpstan-var list<BannerPatternLayer>
*/
protected array $patterns = [];
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::BLACK();
parent::__construct($idInfo, $name, $breakInfo);
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBanner){
$this->color = $tile->getBaseColor();
$this->setPatterns($tile->getPatterns());
}
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
$tile = $this->position->getWorld()->getTile($this->position);
assert($tile instanceof TileBanner);
$tile->setBaseColor($this->color);
$tile->setPatterns($this->patterns);
}
public function isSolid() : bool{
return false;
}
public function getMaxStackSize() : int{
return 16;
}
/**
* @return BannerPatternLayer[]
* @phpstan-return list<BannerPatternLayer>
*/
public function getPatterns() : array{
return $this->patterns;
}
/**
* @param BannerPatternLayer[] $patterns
*
* @phpstan-param list<BannerPatternLayer> $patterns
* @return $this
*/
public function setPatterns(array $patterns) : self{
$checked = array_filter($patterns, fn($v) => $v instanceof BannerPatternLayer);
if(count($checked) !== count($patterns)){
throw new \TypeError("Deque must only contain " . BannerPatternLayer::class . " objects");
}
$this->patterns = $checked;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof ItemBanner){
$this->color = $item->getColor();
$this->setPatterns($item->getPatterns());
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
abstract protected function getSupportingFace() : int;
public function onNearbyBlockChange() : void{
if($this->getSide($this->getSupportingFace())->getId() === BlockLegacyIds::AIR){
$this->position->getWorld()->useBreakOn($this->position);
}
}
protected function writeStateToItemMeta() : int{
return DyeColorIdMap::getInstance()->toInvertedId($this->color);
}
public function getDropsForCompatibleTool(Item $item) : array{
$drop = $this->asItem();
if($drop instanceof ItemBanner and count($this->patterns) > 0){
$drop->setPatterns($this->patterns);
}
return [$drop];
}
public function getPickedItem(bool $addUserData = false) : Item{
$result = $this->asItem();
if($addUserData and $result instanceof ItemBanner and count($this->patterns) > 0){
$result->setPatterns($this->patterns);
}
return $result;
}
}

85
src/block/BaseCoral.php Normal file
View File

@ -0,0 +1,85 @@
<?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\utils\CoralType;
use pocketmine\item\Item;
abstract class BaseCoral extends Transparent{
protected CoralType $coralType;
protected bool $dead = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
parent::__construct($idInfo, $name, $breakInfo);
$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{
if(!$this->dead){
$world = $this->position->getWorld();
$hasWater = false;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
$hasWater = true;
break;
}
}
//TODO: check water inside the block itself (not supported on the API yet)
if(!$hasWater){
$world->setBlock($this->position, $this->setDead(true));
}
}
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function isSolid() : bool{ return false; }
protected function recalculateCollisionBoxes() : array{ return []; }
}

234
src/block/BaseRail.php Normal file
View File

@ -0,0 +1,234 @@
<?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\utils\RailConnectionInfo;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function array_reverse;
use function array_search;
use function array_shift;
use function count;
use function in_array;
abstract class BaseRail extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$blockReplace->getSide(Facing::DOWN)->isTransparent()){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onPostPlace() : void{
$this->tryReconnect();
}
/**
* @param int[] $connections
* @param int[][] $lookup
* @phpstan-param array<int, list<int>> $lookup
*/
protected static function searchState(array $connections, array $lookup) : ?int{
$shape = array_search($connections, $lookup, true);
if($shape === false){
$shape = array_search(array_reverse($connections), $lookup, true);
}
return $shape === false ? null : $shape;
}
/**
* Sets the rail shape according to the given connections, if a shape matches.
*
* @param int[] $connections
*
* @throws \InvalidArgumentException if no shape matches the given connections
*/
abstract protected function setShapeFromConnections(array $connections) : void;
/**
* Returns the connection directions of this rail (depending on the current block state)
*
* @return int[]
*/
abstract protected function getCurrentShapeConnections() : array;
/**
* Returns all the directions this rail is already connected in.
*
* @return int[]
*/
private function getConnectedDirections() : array{
/** @var int[] $connections */
$connections = [];
/** @var int $connection */
foreach($this->getCurrentShapeConnections() as $connection){
$other = $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND);
$otherConnection = Facing::opposite($connection & ~RailConnectionInfo::FLAG_ASCEND);
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0){
$other = $other->getSide(Facing::UP);
}elseif(!($other instanceof BaseRail)){ //check for rail sloping up to meet this one
$other = $other->getSide(Facing::DOWN);
$otherConnection |= RailConnectionInfo::FLAG_ASCEND;
}
if(
$other instanceof BaseRail and
in_array($otherConnection, $other->getCurrentShapeConnections(), true)
){
$connections[] = $connection;
}
}
return $connections;
}
/**
* @param int[] $constraints
*
* @return true[]
* @phpstan-return array<int, true>
*/
private function getPossibleConnectionDirections(array $constraints) : array{
switch(count($constraints)){
case 0:
//No constraints, can connect in any direction
$possible = [
Facing::NORTH => true,
Facing::SOUTH => true,
Facing::WEST => true,
Facing::EAST => true
];
foreach($possible as $p => $_){
$possible[$p | RailConnectionInfo::FLAG_ASCEND] = true;
}
return $possible;
case 1:
return $this->getPossibleConnectionDirectionsOneConstraint(array_shift($constraints));
case 2:
return [];
default:
throw new \InvalidArgumentException("Expected at most 2 constraints, got " . count($constraints));
}
}
/**
* @return true[]
* @phpstan-return array<int, true>
*/
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
$opposite = Facing::opposite($constraint & ~RailConnectionInfo::FLAG_ASCEND);
$possible = [$opposite => true];
if(($constraint & RailConnectionInfo::FLAG_ASCEND) === 0){
//We can slope the other way if this connection isn't already a slope
$possible[$opposite | RailConnectionInfo::FLAG_ASCEND] = true;
}
return $possible;
}
private function tryReconnect() : void{
$thisConnections = $this->getConnectedDirections();
$changed = false;
do{
$possible = $this->getPossibleConnectionDirections($thisConnections);
$continue = false;
foreach($possible as $thisSide => $_){
$otherSide = Facing::opposite($thisSide & ~RailConnectionInfo::FLAG_ASCEND);
$other = $this->getSide($thisSide & ~RailConnectionInfo::FLAG_ASCEND);
if(($thisSide & RailConnectionInfo::FLAG_ASCEND) !== 0){
$other = $other->getSide(Facing::UP);
}elseif(!($other instanceof BaseRail)){ //check if other rails can slope up to meet this one
$other = $other->getSide(Facing::DOWN);
$otherSide |= RailConnectionInfo::FLAG_ASCEND;
}
if(!($other instanceof BaseRail) or count($otherConnections = $other->getConnectedDirections()) >= 2){
//we can only connect to a rail that has less than 2 connections
continue;
}
$otherPossible = $other->getPossibleConnectionDirections($otherConnections);
if(isset($otherPossible[$otherSide])){
$otherConnections[] = $otherSide;
$other->setConnections($otherConnections);
$this->position->getWorld()->setBlock($other->position, $other);
$changed = true;
$thisConnections[] = $thisSide;
$continue = count($thisConnections) < 2;
break; //force recomputing possible directions, since this connection could invalidate others
}
}
}while($continue);
if($changed){
$this->setConnections($thisConnections);
$this->position->getWorld()->setBlock($this->position, $this);
}
}
/**
* @param int[] $connections
*/
private function setConnections(array $connections) : void{
if(count($connections) === 1){
$connections[] = Facing::opposite($connections[0] & ~RailConnectionInfo::FLAG_ASCEND);
}elseif(count($connections) !== 2){
throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections));
}
$this->setShapeFromConnections($connections);
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->isTransparent()){
$this->position->getWorld()->useBreakOn($this->position);
}else{
foreach($this->getCurrentShapeConnections() as $connection){
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 and $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->isTransparent()){
$this->position->getWorld()->useBreakOn($this->position);
break;
}
}
}
}
}

139
src/block/BaseSign.php Normal file
View File

@ -0,0 +1,139 @@
<?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\Sign as TileSign;
use pocketmine\block\utils\SignText;
use pocketmine\event\block\SignChangeEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\TextFormat;
use pocketmine\world\BlockTransaction;
use function array_map;
use function assert;
use function strlen;
abstract class BaseSign extends Transparent{
//TODO: conditionally useless properties, find a way to fix
protected SignText $text;
protected ?int $editorEntityRuntimeId = null;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
parent::__construct($idInfo, $name, $breakInfo);
$this->text = new SignText();
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileSign){
$this->text = $tile->getText();
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
}
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
$tile = $this->position->getWorld()->getTile($this->position);
assert($tile instanceof TileSign);
$tile->setText($this->text);
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
}
public function isSolid() : bool{
return false;
}
public function getMaxStackSize() : int{
return 16;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
abstract protected function getSupportingFace() : int;
public function onNearbyBlockChange() : void{
if($this->getSide($this->getSupportingFace())->getId() === BlockLegacyIds::AIR){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$this->editorEntityRuntimeId = $player->getId();
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
/**
* Returns an object containing information about the sign text.
*/
public function getText() : SignText{
return $this->text;
}
/** @return $this */
public function setText(SignText $text) : self{
$this->text = $text;
return $this;
}
/**
* Called by the player controller (network session) to update the sign text, firing events as appropriate.
*
* @return bool if the sign update was successful.
* @throws \UnexpectedValueException if the text payload is too large
*/
public function updateText(Player $author, SignText $text) : bool{
$size = 0;
foreach($text->getLines() as $line){
$size += strlen($line);
}
if($size > 1000){
throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 1000)");
}
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
return TextFormat::clean($line, false);
}, $text->getLines())));
if($this->editorEntityRuntimeId === null || $this->editorEntityRuntimeId !== $author->getId()){
$ev->cancel();
}
$ev->call();
if(!$ev->isCancelled()){
$this->setText($ev->getNewText());
$this->position->getWorld()->setBlock($this->position, $this);
return true;
}
return false;
}
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
class StillLava extends Lava{
final class Beacon extends Transparent{
protected $id = self::STILL_LAVA;
public function getName() : string{
return "Still Lava";
public function getLightLevel() : int{
return 15;
}
//TODO
}

219
src/block/Bed.php Normal file
View File

@ -0,0 +1,219 @@
<?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\Bed as TileBed;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\data\bedrock\DyeColorIdMap;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\TextFormat;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
class Bed extends Transparent{
use ColoredTrait;
use HorizontalFacingTrait;
protected bool $occupied = false;
protected bool $head = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::RED();
parent::__construct($idInfo, $name, $breakInfo);
}
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) |
($this->occupied ? BlockLegacyMetadata::BED_FLAG_OCCUPIED : 0) |
($this->head ? BlockLegacyMetadata::BED_FLAG_HEAD : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->occupied = ($stateMeta & BlockLegacyMetadata::BED_FLAG_OCCUPIED) !== 0;
$this->head = ($stateMeta & BlockLegacyMetadata::BED_FLAG_HEAD) !== 0;
}
public function getStateBitmask() : int{
return 0b1111;
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
//read extra state information from the tile - this is an ugly hack
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBed){
$this->color = $tile->getColor();
}
}
public function writeStateToWorld() : void{
parent::writeStateToWorld();
//extra block properties storage hack
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBed){
$tile->setColor($this->color);
}
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)];
}
public function isHeadPart() : bool{
return $this->head;
}
/** @return $this */
public function setHead(bool $head) : self{
$this->head = $head;
return $this;
}
public function isOccupied() : bool{
return $this->occupied;
}
/** @return $this */
public function setOccupied(bool $occupied = true) : self{
$this->occupied = $occupied;
return $this;
}
private function getOtherHalfSide() : int{
return $this->head ? Facing::opposite($this->facing) : $this->facing;
}
public function getOtherHalf() : ?Bed{
$other = $this->getSide($this->getOtherHalfSide());
if($other instanceof Bed and $other->head !== $this->head and $other->facing === $this->facing){
return $other;
}
return null;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$other = $this->getOtherHalf();
$playerPos = $player->getPosition();
if($other === null){
$player->sendMessage(TextFormat::GRAY . "This bed is incomplete");
return true;
}elseif($playerPos->distanceSquared($this->position) > 4 and $playerPos->distanceSquared($other->position) > 4){
$player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY));
return true;
}
$time = $this->position->getWorld()->getTimeOfDay();
$isNight = ($time >= World::TIME_NIGHT and $time < World::TIME_SUNRISE);
if(!$isNight){
$player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY));
return true;
}
$b = ($this->isHeadPart() ? $this : $other);
if($b->isOccupied()){
$player->sendMessage(KnownTranslationFactory::tile_bed_occupied()->prefix(TextFormat::GRAY));
return true;
}
$player->sleepOn($b->position);
}
return true;
}
public function onNearbyBlockChange() : void{
if(($other = $this->getOtherHalf()) !== null and $other->occupied !== $this->occupied){
$this->occupied = $other->occupied;
$this->position->getWorld()->setBlock($this->position, $this);
}
}
public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && $entity->isSneaking()){
return null;
}
$entity->fallDistance *= 0.5;
return $entity->getMotion()->y * -3 / 4; // 2/3 in Java, according to the wiki
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if(!$down->isTransparent()){
$this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH;
$next = $this->getSide($this->getOtherHalfSide());
if($next->canBeReplaced() and !$next->getSide(Facing::DOWN)->isTransparent()){
$nextState = clone $this;
$nextState->head = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($next->position, $nextState);
return true;
}
}
return false;
}
public function getDrops(Item $item) : array{
if($this->head){
return parent::getDrops($item);
}
return [];
}
protected function writeStateToItemMeta() : int{
return DyeColorIdMap::getInstance()->toId($this->color);
}
public function getAffectedBlocks() : array{
if(($other = $this->getOtherHalf()) !== null){
return [$this, $other];
}
return parent::getAffectedBlocks();
}
}

View File

@ -23,33 +23,29 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\TieredTool;
class Bedrock extends Opaque{
class Netherrack extends Solid{
private bool $burnsForever = false;
protected $id = self::NETHERRACK;
public function __construct(int $meta = 0){
$this->meta = $meta;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->burnsForever = ($stateMeta & BlockLegacyMetadata::BEDROCK_FLAG_INFINIBURN) !== 0;
}
public function getName() : string{
return "Netherrack";
protected function writeStateToMeta() : int{
return $this->burnsForever ? BlockLegacyMetadata::BEDROCK_FLAG_INFINIBURN : 0;
}
public function getHardness() : float{
return 0.4;
}
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}
public function getToolHarvestLevel() : int{
return TieredTool::TIER_WOODEN;
public function getStateBitmask() : int{
return 0b1;
}
public function burnsForever() : bool{
return true;
return $this->burnsForever;
}
/** @return $this */
public function setBurnsForever(bool $burnsForever) : self{
$this->burnsForever = $burnsForever;
return $this;
}
}

48
src/block/Beetroot.php Normal file
View File

@ -0,0 +1,48 @@
<?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\VanillaItems;
use function mt_rand;
class Beetroot extends Crops{
public function getDropsForCompatibleTool(Item $item) : array{
if($this->age >= 7){
return [
VanillaItems::BEETROOT(),
VanillaItems::BEETROOT_SEEDS()->setCount(mt_rand(0, 3))
];
}
return [
VanillaItems::BEETROOT_SEEDS()
];
}
public function getPickedItem(bool $addUserData = false) : Item{
return VanillaItems::BEETROOT_SEEDS();
}
}

160
src/block/Bell.php Normal file
View File

@ -0,0 +1,160 @@
<?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\Bell as TileBell;
use pocketmine\block\utils\BellAttachmentType;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\BellRingSound;
final class Bell extends Transparent{
use HorizontalFacingTrait;
private BellAttachmentType $attachmentType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->attachmentType = BellAttachmentType::FLOOR();
parent::__construct($idInfo, $name, $breakInfo);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->setFacing(BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03));
$attachmentType = [
BlockLegacyMetadata::BELL_ATTACHMENT_FLOOR => BellAttachmentType::FLOOR(),
BlockLegacyMetadata::BELL_ATTACHMENT_CEILING => BellAttachmentType::CEILING(),
BlockLegacyMetadata::BELL_ATTACHMENT_ONE_WALL => BellAttachmentType::ONE_WALL(),
BlockLegacyMetadata::BELL_ATTACHMENT_TWO_WALLS => BellAttachmentType::TWO_WALLS()
][($stateMeta >> 2) & 0b11] ?? null;
if($attachmentType === null){
throw new InvalidBlockStateException("No such attachment type");
}
$this->setAttachmentType($attachmentType);
}
public function writeStateToMeta() : int{
$attachmentTypeMeta = [
BellAttachmentType::FLOOR()->id() => BlockLegacyMetadata::BELL_ATTACHMENT_FLOOR,
BellAttachmentType::CEILING()->id() => BlockLegacyMetadata::BELL_ATTACHMENT_CEILING,
BellAttachmentType::ONE_WALL()->id() => BlockLegacyMetadata::BELL_ATTACHMENT_ONE_WALL,
BellAttachmentType::TWO_WALLS()->id() => BlockLegacyMetadata::BELL_ATTACHMENT_TWO_WALLS
][$this->getAttachmentType()->id()] ?? null;
if($attachmentTypeMeta === null){
throw new AssumptionFailedError("Mapping should cover all cases");
}
return BlockDataSerializer::writeLegacyHorizontalFacing($this->getFacing()) | ($attachmentTypeMeta << 2);
}
public function getStateBitmask() : int{
return 0b1111;
}
public function getAttachmentType() : BellAttachmentType{ return $this->attachmentType; }
/** @return $this */
public function setAttachmentType(BellAttachmentType $attachmentType) : self{
$this->attachmentType = $attachmentType;
return $this;
}
private function canBeSupportedBy(Block $block) : bool{
//TODO: this isn't the actual logic, but it's the closest approximation we can support for now
return $block->isSolid();
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()))){
return false;
}
if($player !== null){
$this->setFacing(Facing::opposite($player->getHorizontalFacing()));
}
$this->setAttachmentType(BellAttachmentType::FLOOR());
}elseif($face === Facing::DOWN){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()))){
return false;
}
$this->setAttachmentType(BellAttachmentType::CEILING());
}else{
$this->setFacing($face);
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))))){
$this->setAttachmentType(BellAttachmentType::ONE_WALL());
}else{
return false;
}
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)))){
$this->setAttachmentType(BellAttachmentType::TWO_WALLS());
}
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(
($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP))) ||
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))) ||
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))) ||
($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))))
){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$faceHit = Facing::opposite($player->getHorizontalFacing());
if($this->attachmentType->equals(BellAttachmentType::CEILING())){
$this->ring($faceHit);
}
if($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)){
$this->ring($faceHit);
}
if(
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
){
$this->ring($faceHit);
}
}
return true;
}
public function ring(int $faceHit) : void{
$this->position->getWorld()->addSound($this->position, new BellRingSound());
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBell){
$this->position->getWorld()->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
}
}
}

637
src/block/Block.php Normal file
View File

@ -0,0 +1,637 @@
<?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);
/**
* All Block classes are in here
*/
namespace pocketmine\block;
use pocketmine\block\tile\Spawnable;
use pocketmine\block\tile\Tile;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\entity\Entity;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\Chunk;
use pocketmine\world\Position;
use pocketmine\world\World;
use function assert;
use function count;
use function dechex;
use const PHP_INT_MAX;
class Block{
public const INTERNAL_METADATA_BITS = 4;
public const INTERNAL_METADATA_MASK = ~(~0 << self::INTERNAL_METADATA_BITS);
protected BlockIdentifier $idInfo;
protected string $fallbackName;
protected BlockBreakInfo $breakInfo;
protected Position $position;
/** @var AxisAlignedBB[]|null */
protected ?array $collisionBoxes = null;
/**
* @param string $name English name of the block type (TODO: implement translations)
*/
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
if(($idInfo->getVariant() & $this->getStateBitmask()) !== 0){
throw new \InvalidArgumentException("Variant 0x" . dechex($idInfo->getVariant()) . " collides with state bitmask 0x" . dechex($this->getStateBitmask()));
}
$this->idInfo = $idInfo;
$this->fallbackName = $name;
$this->breakInfo = $breakInfo;
$this->position = new Position(0, 0, 0, null);
}
public function __clone(){
$this->position = clone $this->position;
}
public function getIdInfo() : BlockIdentifier{
return $this->idInfo;
}
public function getName() : string{
return $this->fallbackName;
}
public function getId() : int{
return $this->idInfo->getBlockId();
}
/**
* @internal
*/
public function getFullId() : int{
return ($this->getId() << self::INTERNAL_METADATA_BITS) | $this->getMeta();
}
public function asItem() : Item{
return ItemFactory::getInstance()->get(
$this->idInfo->getItemId(),
$this->idInfo->getVariant() | $this->writeStateToItemMeta()
);
}
public function getMeta() : int{
$stateMeta = $this->writeStateToMeta();
assert(($stateMeta & ~$this->getStateBitmask()) === 0);
return $this->idInfo->getVariant() | $stateMeta;
}
protected function writeStateToItemMeta() : int{
return 0;
}
/**
* Returns a bitmask used to extract state bits from block metadata.
*/
public function getStateBitmask() : int{
return 0;
}
protected function writeStateToMeta() : int{
return 0;
}
/**
* @throws InvalidBlockStateException
*/
public function readStateFromData(int $id, int $stateMeta) : void{
//NOOP
}
/**
* Called when this block is created, set, or has a neighbouring block update, to re-detect dynamic properties which
* are not saved on the world.
*
* Clears any cached precomputed objects, such as bounding boxes. Remove any outdated precomputed things such as
* AABBs and force recalculation.
*/
public function readStateFromWorld() : void{
$this->collisionBoxes = null;
}
public function writeStateToWorld() : void{
$this->position->getWorld()->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
$tileType = $this->idInfo->getTileClass();
$oldTile = $this->position->getWorld()->getTile($this->position);
if($oldTile !== null){
if($tileType === null or !($oldTile instanceof $tileType)){
$oldTile->close();
$oldTile = null;
}elseif($oldTile instanceof Spawnable){
$oldTile->setDirty(); //destroy old network cache
}
}
if($oldTile === null and $tileType !== null){
/**
* @var Tile $tile
* @see Tile::__construct()
*/
$tile = new $tileType($this->position->getWorld(), $this->position->asVector3());
$this->position->getWorld()->addTile($tile);
}
}
/**
* Returns whether the given block has an equivalent type to this one. This compares base legacy ID and variant.
*
* 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.
*/
public function isSameType(Block $other) : bool{
return $this->idInfo->getBlockId() === $other->idInfo->getBlockId() and $this->idInfo->getVariant() === $other->idInfo->getVariant();
}
/**
* Returns whether the given block has the same type and properties as this block.
*/
public function isSameState(Block $other) : bool{
return $this->getFullId() === $other->getFullId();
}
/**
* AKA: Block->isPlaceable
*/
public function canBePlaced() : bool{
return true;
}
public function canBeReplaced() : bool{
return false;
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return $blockReplace->canBeReplaced();
}
/**
* Places the Block, using block space and block target, and side. Returns if the block has been placed.
*/
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$tx->addBlock($blockReplace->position, $this);
return true;
}
public function onPostPlace() : void{
}
/**
* Returns an object containing information about the destruction requirements of this block.
*/
public function getBreakInfo() : BlockBreakInfo{
return $this->breakInfo;
}
/**
* Do the actions needed so the block is broken with the Item
*/
public function onBreak(Item $item, ?Player $player = null) : bool{
if(($t = $this->position->getWorld()->getTile($this->position)) !== null){
$t->onBlockDestroyed();
}
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
return true;
}
/**
* Called when this block or a block immediately adjacent to it changes state.
*/
public function onNearbyBlockChange() : void{
}
/**
* Returns whether random block updates will be done on this block.
*/
public function ticksRandomly() : bool{
return false;
}
/**
* Called when this block is randomly updated due to chunk ticking.
* WARNING: This will not be called if ticksRandomly() does not return true!
*/
public function onRandomTick() : void{
}
/**
* Called when this block is updated by the delayed blockupdate scheduler in the world.
*/
public function onScheduledUpdate() : void{
}
/**
* Do actions when interacted by Item. Returns if it has done anything
*/
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
return false;
}
/**
* Called when this block is attacked (left-clicked). This is called when a player left-clicks the block to try and
* start to break it in survival mode.
*
* @return bool if an action took place, prevents starting to break the block if true.
*/
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
return false;
}
public function getFrictionFactor() : float{
return 0.6;
}
/**
* @return int 0-15
*/
public function getLightLevel() : int{
return 0;
}
/**
* Returns the amount of light this block will filter out when light passes through this block.
* This value is used in light spread calculation.
*
* @return int 0-15
*/
public function getLightFilter() : int{
return $this->isTransparent() ? 0 : 15;
}
/**
* Returns whether this block blocks direct sky light from passing through it. This is independent from the light
* filter value, which is used during propagation.
*
* In most cases, this is the same as isTransparent(); however, some special cases exist such as leaves and cobwebs,
* which don't have any additional effect on light propagation, but don't allow direct sky light to pass through.
*/
public function blocksDirectSkyLight() : bool{
return $this->getLightFilter() > 0;
}
public function isTransparent() : bool{
return false;
}
public function isSolid() : bool{
return true;
}
/**
* AKA: Block->isFlowable
*/
public function canBeFlowedInto() : bool{
return false;
}
public function hasEntityCollision() : bool{
return false;
}
/**
* Returns whether entities can climb up this block.
*/
public function canClimb() : bool{
return false;
}
public function addVelocityToEntity(Entity $entity) : ?Vector3{
return null;
}
final public function getPosition() : Position{
return $this->position;
}
/**
* @internal
*/
final public function position(World $world, int $x, int $y, int $z) : void{
$this->position = new Position($x, $y, $z, $world);
}
/**
* Returns an array of Item objects to be dropped
*
* @return Item[]
*/
public function getDrops(Item $item) : array{
if($this->breakInfo->isToolCompatible($item)){
if($this->isAffectedBySilkTouch() and $item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
return $this->getSilkTouchDrops($item);
}
return $this->getDropsForCompatibleTool($item);
}
return $this->getDropsForIncompatibleTool($item);
}
/**
* Returns an array of Items to be dropped when the block is broken using the correct tool type.
*
* @return Item[]
*/
public function getDropsForCompatibleTool(Item $item) : array{
return [$this->asItem()];
}
/**
* Returns the items dropped by this block when broken with an incorrect tool type (or tool with a too-low tier).
*
* @return Item[]
*/
public function getDropsForIncompatibleTool(Item $item) : array{
return [];
}
/**
* Returns an array of Items to be dropped when the block is broken using a compatible Silk Touch-enchanted tool.
*
* @return Item[]
*/
public function getSilkTouchDrops(Item $item) : array{
return [$this->asItem()];
}
/**
* Returns how much XP will be dropped by breaking this block with the given item.
*/
public function getXpDropForTool(Item $item) : int{
if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH()) or !$this->breakInfo->isToolCompatible($item)){
return 0;
}
return $this->getXpDropAmount();
}
/**
* Returns how much XP this block will drop when broken with an appropriate tool.
*/
protected function getXpDropAmount() : int{
return 0;
}
/**
* Returns whether Silk Touch enchanted tools will cause this block to drop as itself.
*/
public function isAffectedBySilkTouch() : bool{
return false;
}
/**
* Returns the item that players will equip when middle-clicking on this block.
*/
public function getPickedItem(bool $addUserData = false) : Item{
$item = $this->asItem();
if($addUserData){
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof Tile){
$nbt = $tile->getCleanedNBT();
if($nbt instanceof CompoundTag){
$item->setCustomBlockData($nbt);
$item->setLore(["+(DATA)"]);
}
}
}
return $item;
}
/**
* Returns the time in ticks which the block will fuel a furnace for.
*/
public function getFuelTime() : int{
return 0;
}
/**
* Returns the maximum number of this block that can fit into a single item stack.
*/
public function getMaxStackSize() : int{
return 64;
}
/**
* Returns the chance that the block will catch fire from nearby fire sources. Higher values lead to faster catching
* fire.
*/
public function getFlameEncouragement() : int{
return 0;
}
/**
* Returns the base flammability of this block. Higher values lead to the block burning away more quickly.
*/
public function getFlammability() : int{
return 0;
}
/**
* Returns whether fire lit on this block will burn indefinitely.
*/
public function burnsForever() : bool{
return false;
}
/**
* Returns whether this block can catch fire.
*/
public function isFlammable() : bool{
return $this->getFlammability() > 0;
}
/**
* Called when this block is burned away by being on fire.
*/
public function onIncinerate() : void{
}
/**
* Returns the Block on the side $side, works like Vector3::getSide()
*
* @return Block
*/
public function getSide(int $side, int $step = 1){
if($this->position->isValid()){
return $this->position->getWorld()->getBlock($this->position->getSide($side, $step));
}
throw new \LogicException("Block does not have a valid world");
}
/**
* Returns the 4 blocks on the horizontal axes around the block (north, south, east, west)
*
* @return Block[]|\Generator
* @phpstan-return \Generator<int, Block, void, void>
*/
public function getHorizontalSides() : \Generator{
$world = $this->position->getWorld();
foreach($this->position->sidesAroundAxis(Axis::Y) as $vector3){
yield $world->getBlock($vector3);
}
}
/**
* Returns the six blocks around this block.
*
* @return Block[]|\Generator
* @phpstan-return \Generator<int, Block, void, void>
*/
public function getAllSides() : \Generator{
$world = $this->position->getWorld();
foreach($this->position->sides() as $vector3){
yield $world->getBlock($vector3);
}
}
/**
* Returns a list of blocks that this block is part of. In most cases, only contains the block itself, but in cases
* such as double plants, beds and doors, will contain both halves.
*
* @return Block[]
*/
public function getAffectedBlocks() : array{
return [$this];
}
/**
* @return string
*/
public function __toString(){
return "Block[" . $this->getName() . "] (" . $this->getId() . ":" . $this->getMeta() . ")";
}
/**
* Checks for collision against an AxisAlignedBB
*/
public function collidesWithBB(AxisAlignedBB $bb) : bool{
foreach($this->getCollisionBoxes() as $bb2){
if($bb->intersectsWith($bb2)){
return true;
}
}
return false;
}
/**
* Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting
* with the collision box or bounding box.
*
* @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive
* being ignited), this should return false.
*/
public function onEntityInside(Entity $entity) : bool{
return true;
}
/**
* Called when an entity lands on this block (usually due to falling).
* @return float|null The new vertical velocity of the entity, or null if unchanged.
*/
public function onEntityLand(Entity $entity) : ?float{
return null;
}
/**
* @return AxisAlignedBB[]
*/
final public function getCollisionBoxes() : array{
if($this->collisionBoxes === null){
$this->collisionBoxes = $this->recalculateCollisionBoxes();
$extraOffset = $this->getModelPositionOffset();
$offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
foreach($this->collisionBoxes as $bb){
$bb->offset($offset->x, $offset->y, $offset->z);
}
}
return $this->collisionBoxes;
}
/**
* 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.
*/
public function getModelPositionOffset() : ?Vector3{
return null;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()];
}
public function isFullCube() : bool{
$bb = $this->getCollisionBoxes();
return count($bb) === 1 and $bb[0]->getAverageEdgeLength() >= 1 and $bb[0]->isCube();
}
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
$bbs = $this->getCollisionBoxes();
if(count($bbs) === 0){
return null;
}
/** @var RayTraceResult|null $currentHit */
$currentHit = null;
/** @var int|float $currentDistance */
$currentDistance = PHP_INT_MAX;
foreach($bbs as $bb){
$nextHit = $bb->calculateIntercept($pos1, $pos2);
if($nextHit === null){
continue;
}
$nextDistance = $nextHit->hitVector->distanceSquared($pos1);
if($nextDistance < $currentDistance){
$currentHit = $nextHit;
$currentDistance = $nextDistance;
}
}
return $currentHit;
}
}

View File

@ -0,0 +1,148 @@
<?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 function get_class;
class BlockBreakInfo{
/**
* If the tool is the correct type and high enough harvest level (tool tier), base break time is hardness multiplied
* by this value.
*/
public const COMPATIBLE_TOOL_MULTIPLIER = 1.5;
/**
* If the tool is an incorrect type or too low harvest level (tool tier), base break time is hardness multiplied by
* this value.
*/
public const INCOMPATIBLE_TOOL_MULTIPLIER = 5.0;
private float $hardness;
private float $blastResistance;
private int $toolType;
private int $toolHarvestLevel;
/**
* @param float|null $blastResistance default 5x hardness
*/
public function __construct(float $hardness, int $toolType = BlockToolType::NONE, int $toolHarvestLevel = 0, ?float $blastResistance = null){
$this->hardness = $hardness;
$this->toolType = $toolType;
$this->toolHarvestLevel = $toolHarvestLevel;
$this->blastResistance = $blastResistance ?? $hardness * 5;
}
public static function instant(int $toolType = BlockToolType::NONE, int $toolHarvestLevel = 0) : self{
return new self(0.0, $toolType, $toolHarvestLevel, 0.0);
}
public static function indestructible(float $blastResistance = 18000000.0) : self{
return new self(-1.0, BlockToolType::NONE, 0, $blastResistance);
}
/**
* Returns a base value used to compute block break times.
*/
public function getHardness() : float{
return $this->hardness;
}
/**
* Returns whether the block can be broken at all.
*/
public function isBreakable() : bool{
return $this->hardness >= 0;
}
/**
* Returns whether this block can be instantly broken.
*/
public function breaksInstantly() : bool{
return $this->hardness == 0.0;
}
/**
* Returns the block's resistance to explosions. Usually 5x hardness.
*/
public function getBlastResistance() : float{
return $this->blastResistance;
}
public function getToolType() : int{
return $this->toolType;
}
/**
* Returns the level of tool required to harvest the block (for normal blocks). When the tool type matches the
* block's required tool type, the tool must have a harvest level greater than or equal to this value to be able to
* successfully harvest the block.
*
* If the block requires a specific minimum tier of tiered tool, the minimum tier required should be returned.
* Otherwise, 1 should be returned if a tool is required, 0 if not.
*
* @see Item::getBlockToolHarvestLevel()
*/
public function getToolHarvestLevel() : int{
return $this->toolHarvestLevel;
}
/**
* Returns whether the specified item is the proper tool to use for breaking this block. This checks tool type and
* harvest level requirement.
*
* In most cases this is also used to determine whether block drops should be created or not, except in some
* special cases such as vines.
*/
public function isToolCompatible(Item $tool) : bool{
if($this->hardness < 0){
return false;
}
return $this->toolType === BlockToolType::NONE or $this->toolHarvestLevel === 0 or (
($this->toolType & $tool->getBlockToolType()) !== 0 and $tool->getBlockToolHarvestLevel() >= $this->toolHarvestLevel);
}
/**
* Returns the seconds that this block takes to be broken using an specific Item
*
* @throws \InvalidArgumentException if the item efficiency is not a positive number
*/
public function getBreakTime(Item $item) : float{
$base = $this->hardness;
if($this->isToolCompatible($item)){
$base *= self::COMPATIBLE_TOOL_MULTIPLIER;
}else{
$base *= self::INCOMPATIBLE_TOOL_MULTIPLIER;
}
$efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0);
if($efficiency <= 0){
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
}
$base /= $efficiency;
return $base;
}
}

1067
src/block/BlockFactory.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
<?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\Tile;
class BlockIdentifier{
private int $blockId;
private int $variant;
private ?int $itemId;
/** @phpstan-var class-string<Tile>|null */
private ?string $tileClass;
/**
* @phpstan-param class-string<Tile>|null $tileClass
*/
public function __construct(int $blockId, int $variant, ?int $itemId = null, ?string $tileClass = null){
$this->blockId = $blockId;
$this->variant = $variant;
$this->itemId = $itemId;
$this->tileClass = $tileClass;
}
public function getBlockId() : int{
return $this->blockId;
}
/**
* @return int[]
*/
public function getAllBlockIds() : array{
return [$this->blockId];
}
public function getVariant() : int{
return $this->variant;
}
public function getItemId() : int{
return $this->itemId ?? ($this->blockId > 255 ? 255 - $this->blockId : $this->blockId);
}
/**
* @phpstan-return class-string<Tile>|null
*/
public function getTileClass() : ?string{
return $this->tileClass;
}
}

View File

@ -0,0 +1,59 @@
<?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 function count;
class BlockIdentifierFlattened extends BlockIdentifier{
/** @var int[] */
private array $additionalIds;
/**
* @param int[] $additionalIds
*/
public function __construct(int $blockId, array $additionalIds, int $variant, ?int $itemId = null, ?string $tileClass = null){
if(count($additionalIds) === 0){
throw new \InvalidArgumentException("Expected at least 1 additional ID");
}
parent::__construct($blockId, $variant, $itemId, $tileClass);
$this->additionalIds = $additionalIds;
}
public function getAdditionalId(int $index) : int{
if(!isset($this->additionalIds[$index])){
throw new \InvalidArgumentException("No such ID at index $index");
}
return $this->additionalIds[$index];
}
public function getSecondId() : int{
return $this->getAdditionalId(0);
}
public function getAllBlockIds() : array{
return [$this->getBlockId(), ...$this->additionalIds];
}
}

View File

@ -0,0 +1,248 @@
<?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\BlockIdentifier as BID;
use pocketmine\block\BlockLegacyIds as Ids;
use pocketmine\block\tile\Sign as TileSign;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\TreeType;
use pocketmine\item\ItemIds;
use pocketmine\utils\AssumptionFailedError;
final class BlockLegacyIdHelper{
public static function getWoodenFloorSignIdentifier(TreeType $treeType) : BID{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BID(Ids::SIGN_POST, 0, ItemIds::SIGN, TileSign::class);
case TreeType::SPRUCE()->id():
return new BID(Ids::SPRUCE_STANDING_SIGN, 0, ItemIds::SPRUCE_SIGN, TileSign::class);
case TreeType::BIRCH()->id():
return new BID(Ids::BIRCH_STANDING_SIGN, 0, ItemIds::BIRCH_SIGN, TileSign::class);
case TreeType::JUNGLE()->id():
return new BID(Ids::JUNGLE_STANDING_SIGN, 0, ItemIds::JUNGLE_SIGN, TileSign::class);
case TreeType::ACACIA()->id():
return new BID(Ids::ACACIA_STANDING_SIGN,0, ItemIds::ACACIA_SIGN, TileSign::class);
case TreeType::DARK_OAK()->id():
return new BID(Ids::DARKOAK_STANDING_SIGN, 0, ItemIds::DARKOAK_SIGN, TileSign::class);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenWallSignIdentifier(TreeType $treeType) : BID{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BID(Ids::WALL_SIGN, 0, ItemIds::SIGN, TileSign::class);
case TreeType::SPRUCE()->id():
return new BID(Ids::SPRUCE_WALL_SIGN, 0, ItemIds::SPRUCE_SIGN, TileSign::class);
case TreeType::BIRCH()->id():
return new BID(Ids::BIRCH_WALL_SIGN, 0, ItemIds::BIRCH_SIGN, TileSign::class);
case TreeType::JUNGLE()->id():
return new BID(Ids::JUNGLE_WALL_SIGN, 0, ItemIds::JUNGLE_SIGN, TileSign::class);
case TreeType::ACACIA()->id():
return new BID(Ids::ACACIA_WALL_SIGN, 0, ItemIds::ACACIA_SIGN, TileSign::class);
case TreeType::DARK_OAK()->id():
return new BID(Ids::DARKOAK_WALL_SIGN, 0, ItemIds::DARKOAK_SIGN, TileSign::class);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenTrapdoorIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::WOODEN_TRAPDOOR, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::SPRUCE_TRAPDOOR, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::BIRCH_TRAPDOOR, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::JUNGLE_TRAPDOOR, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::ACACIA_TRAPDOOR, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::DARK_OAK_TRAPDOOR, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenButtonIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::WOODEN_BUTTON, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::SPRUCE_BUTTON, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::BIRCH_BUTTON, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::JUNGLE_BUTTON, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::ACACIA_BUTTON, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::DARK_OAK_BUTTON, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenPressurePlateIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::WOODEN_PRESSURE_PLATE, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::SPRUCE_PRESSURE_PLATE, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::BIRCH_PRESSURE_PLATE, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::JUNGLE_PRESSURE_PLATE, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::ACACIA_PRESSURE_PLATE, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::DARK_OAK_PRESSURE_PLATE, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenDoorIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BID(Ids::OAK_DOOR_BLOCK, 0, ItemIds::OAK_DOOR);
case TreeType::SPRUCE()->id():
return new BID(Ids::SPRUCE_DOOR_BLOCK, 0, ItemIds::SPRUCE_DOOR);
case TreeType::BIRCH()->id():
return new BID(Ids::BIRCH_DOOR_BLOCK, 0, ItemIds::BIRCH_DOOR);
case TreeType::JUNGLE()->id():
return new BID(Ids::JUNGLE_DOOR_BLOCK, 0, ItemIds::JUNGLE_DOOR);
case TreeType::ACACIA()->id():
return new BID(Ids::ACACIA_DOOR_BLOCK, 0, ItemIds::ACACIA_DOOR);
case TreeType::DARK_OAK()->id():
return new BID(Ids::DARK_OAK_DOOR_BLOCK, 0, ItemIds::DARK_OAK_DOOR);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenFenceIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::OAK_FENCE_GATE, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::SPRUCE_FENCE_GATE, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::BIRCH_FENCE_GATE, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::JUNGLE_FENCE_GATE, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::ACACIA_FENCE_GATE, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::DARK_OAK_FENCE_GATE, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getWoodenStairsIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::OAK_STAIRS, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::SPRUCE_STAIRS, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::BIRCH_STAIRS, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::JUNGLE_STAIRS, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::ACACIA_STAIRS, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::DARK_OAK_STAIRS, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getStrippedLogIdentifier(TreeType $treeType) : BlockIdentifier{
switch($treeType->id()){
case TreeType::OAK()->id():
return new BlockIdentifier(Ids::STRIPPED_OAK_LOG, 0);
case TreeType::SPRUCE()->id():
return new BlockIdentifier(Ids::STRIPPED_SPRUCE_LOG, 0);
case TreeType::BIRCH()->id():
return new BlockIdentifier(Ids::STRIPPED_BIRCH_LOG, 0);
case TreeType::JUNGLE()->id():
return new BlockIdentifier(Ids::STRIPPED_JUNGLE_LOG, 0);
case TreeType::ACACIA()->id():
return new BlockIdentifier(Ids::STRIPPED_ACACIA_LOG, 0);
case TreeType::DARK_OAK()->id():
return new BlockIdentifier(Ids::STRIPPED_DARK_OAK_LOG, 0);
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
public static function getGlazedTerracottaIdentifier(DyeColor $color) : BlockIdentifier{
switch($color->id()){
case DyeColor::WHITE()->id():
return new BlockIdentifier(Ids::WHITE_GLAZED_TERRACOTTA, 0);
case DyeColor::ORANGE()->id():
return new BlockIdentifier(Ids::ORANGE_GLAZED_TERRACOTTA, 0);
case DyeColor::MAGENTA()->id():
return new BlockIdentifier(Ids::MAGENTA_GLAZED_TERRACOTTA, 0);
case DyeColor::LIGHT_BLUE()->id():
return new BlockIdentifier(Ids::LIGHT_BLUE_GLAZED_TERRACOTTA, 0);
case DyeColor::YELLOW()->id():
return new BlockIdentifier(Ids::YELLOW_GLAZED_TERRACOTTA, 0);
case DyeColor::LIME()->id():
return new BlockIdentifier(Ids::LIME_GLAZED_TERRACOTTA, 0);
case DyeColor::PINK()->id():
return new BlockIdentifier(Ids::PINK_GLAZED_TERRACOTTA, 0);
case DyeColor::GRAY()->id():
return new BlockIdentifier(Ids::GRAY_GLAZED_TERRACOTTA, 0);
case DyeColor::LIGHT_GRAY()->id():
return new BlockIdentifier(Ids::SILVER_GLAZED_TERRACOTTA, 0);
case DyeColor::CYAN()->id():
return new BlockIdentifier(Ids::CYAN_GLAZED_TERRACOTTA, 0);
case DyeColor::PURPLE()->id():
return new BlockIdentifier(Ids::PURPLE_GLAZED_TERRACOTTA, 0);
case DyeColor::BLUE()->id():
return new BlockIdentifier(Ids::BLUE_GLAZED_TERRACOTTA, 0);
case DyeColor::BROWN()->id():
return new BlockIdentifier(Ids::BROWN_GLAZED_TERRACOTTA, 0);
case DyeColor::GREEN()->id():
return new BlockIdentifier(Ids::GREEN_GLAZED_TERRACOTTA, 0);
case DyeColor::RED()->id():
return new BlockIdentifier(Ids::RED_GLAZED_TERRACOTTA, 0);
case DyeColor::BLACK()->id():
return new BlockIdentifier(Ids::BLACK_GLAZED_TERRACOTTA, 0);
}
throw new AssumptionFailedError("Switch should cover all colours");
}
public static function getStoneSlabIdentifier(int $stoneSlabId, int $meta) : BlockIdentifierFlattened{
$id = [
1 => [Ids::STONE_SLAB, Ids::DOUBLE_STONE_SLAB],
2 => [Ids::STONE_SLAB2, Ids::DOUBLE_STONE_SLAB2],
3 => [Ids::STONE_SLAB3, Ids::DOUBLE_STONE_SLAB3],
4 => [Ids::STONE_SLAB4, Ids::DOUBLE_STONE_SLAB4]
][$stoneSlabId] ?? null;
if($id === null){
throw new \InvalidArgumentException("Stone slab type should be 1, 2, 3 or 4");
}
return new BlockIdentifierFlattened($id[0], [$id[1]], $meta);
}
}

View File

@ -23,7 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
interface BlockIds{
final class BlockLegacyIds{
private function __construct(){
//NOOP
}
public const AIR = 0;
public const STONE = 1;
@ -42,7 +46,7 @@ interface BlockIds{
public const GOLD_ORE = 14;
public const IRON_ORE = 15;
public const COAL_ORE = 16;
public const LOG = 17, WOOD = 17;
public const LOG = 17;
public const LEAVES = 18;
public const SPONGE = 19;
public const GLASS = 20;
@ -61,7 +65,7 @@ interface BlockIds{
public const PISTON = 33;
public const PISTONARMCOLLISION = 34, PISTON_ARM_COLLISION = 34;
public const WOOL = 35;
public const ELEMENT_0 = 36;
public const DANDELION = 37, YELLOW_FLOWER = 37;
public const POPPY = 38, RED_FLOWER = 38;
public const BROWN_MUSHROOM = 39;
@ -187,11 +191,11 @@ interface BlockIds{
public const STAINED_CLAY = 159, STAINED_HARDENED_CLAY = 159, TERRACOTTA = 159;
public const STAINED_GLASS_PANE = 160;
public const LEAVES2 = 161;
public const LOG2 = 162, WOOD2 = 162;
public const LOG2 = 162;
public const ACACIA_STAIRS = 163;
public const DARK_OAK_STAIRS = 164;
public const SLIME = 165, SLIME_BLOCK = 165;
public const GLOW_STICK = 166;
public const IRON_TRAPDOOR = 167;
public const PRISMARINE = 168;
public const SEALANTERN = 169, SEA_LANTERN = 169;
@ -215,7 +219,9 @@ interface BlockIds{
public const ACACIA_FENCE_GATE = 187;
public const REPEATING_COMMAND_BLOCK = 188;
public const CHAIN_COMMAND_BLOCK = 189;
public const HARD_GLASS_PANE = 190;
public const HARD_STAINED_GLASS_PANE = 191;
public const CHEMICAL_HEAT = 192;
public const SPRUCE_DOOR_BLOCK = 193;
public const BIRCH_DOOR_BLOCK = 194;
public const JUNGLE_DOOR_BLOCK = 195;
@ -225,9 +231,9 @@ interface BlockIds{
public const FRAME_BLOCK = 199, ITEM_FRAME_BLOCK = 199;
public const CHORUS_FLOWER = 200;
public const PURPUR_BLOCK = 201;
public const COLORED_TORCH_RG = 202;
public const PURPUR_STAIRS = 203;
public const COLORED_TORCH_BP = 204;
public const UNDYED_SHULKER_BOX = 205;
public const END_BRICKS = 206;
public const FROSTED_ICE = 207;
@ -259,7 +265,8 @@ interface BlockIds{
public const BLACK_GLAZED_TERRACOTTA = 235;
public const CONCRETE = 236;
public const CONCRETEPOWDER = 237, CONCRETE_POWDER = 237;
public const CHEMISTRY_TABLE = 238;
public const UNDERWATER_TORCH = 239;
public const CHORUS_PLANT = 240;
public const STAINED_GLASS = 241;
@ -273,7 +280,222 @@ interface BlockIds{
public const MOVINGBLOCK = 250, MOVING_BLOCK = 250;
public const OBSERVER = 251;
public const STRUCTURE_BLOCK = 252;
public const HARD_GLASS = 253;
public const HARD_STAINED_GLASS = 254;
public const RESERVED6 = 255;
public const PRISMARINE_STAIRS = 257;
public const DARK_PRISMARINE_STAIRS = 258;
public const PRISMARINE_BRICKS_STAIRS = 259;
public const STRIPPED_SPRUCE_LOG = 260;
public const STRIPPED_BIRCH_LOG = 261;
public const STRIPPED_JUNGLE_LOG = 262;
public const STRIPPED_ACACIA_LOG = 263;
public const STRIPPED_DARK_OAK_LOG = 264;
public const STRIPPED_OAK_LOG = 265;
public const BLUE_ICE = 266;
public const ELEMENT_1 = 267;
public const ELEMENT_2 = 268;
public const ELEMENT_3 = 269;
public const ELEMENT_4 = 270;
public const ELEMENT_5 = 271;
public const ELEMENT_6 = 272;
public const ELEMENT_7 = 273;
public const ELEMENT_8 = 274;
public const ELEMENT_9 = 275;
public const ELEMENT_10 = 276;
public const ELEMENT_11 = 277;
public const ELEMENT_12 = 278;
public const ELEMENT_13 = 279;
public const ELEMENT_14 = 280;
public const ELEMENT_15 = 281;
public const ELEMENT_16 = 282;
public const ELEMENT_17 = 283;
public const ELEMENT_18 = 284;
public const ELEMENT_19 = 285;
public const ELEMENT_20 = 286;
public const ELEMENT_21 = 287;
public const ELEMENT_22 = 288;
public const ELEMENT_23 = 289;
public const ELEMENT_24 = 290;
public const ELEMENT_25 = 291;
public const ELEMENT_26 = 292;
public const ELEMENT_27 = 293;
public const ELEMENT_28 = 294;
public const ELEMENT_29 = 295;
public const ELEMENT_30 = 296;
public const ELEMENT_31 = 297;
public const ELEMENT_32 = 298;
public const ELEMENT_33 = 299;
public const ELEMENT_34 = 300;
public const ELEMENT_35 = 301;
public const ELEMENT_36 = 302;
public const ELEMENT_37 = 303;
public const ELEMENT_38 = 304;
public const ELEMENT_39 = 305;
public const ELEMENT_40 = 306;
public const ELEMENT_41 = 307;
public const ELEMENT_42 = 308;
public const ELEMENT_43 = 309;
public const ELEMENT_44 = 310;
public const ELEMENT_45 = 311;
public const ELEMENT_46 = 312;
public const ELEMENT_47 = 313;
public const ELEMENT_48 = 314;
public const ELEMENT_49 = 315;
public const ELEMENT_50 = 316;
public const ELEMENT_51 = 317;
public const ELEMENT_52 = 318;
public const ELEMENT_53 = 319;
public const ELEMENT_54 = 320;
public const ELEMENT_55 = 321;
public const ELEMENT_56 = 322;
public const ELEMENT_57 = 323;
public const ELEMENT_58 = 324;
public const ELEMENT_59 = 325;
public const ELEMENT_60 = 326;
public const ELEMENT_61 = 327;
public const ELEMENT_62 = 328;
public const ELEMENT_63 = 329;
public const ELEMENT_64 = 330;
public const ELEMENT_65 = 331;
public const ELEMENT_66 = 332;
public const ELEMENT_67 = 333;
public const ELEMENT_68 = 334;
public const ELEMENT_69 = 335;
public const ELEMENT_70 = 336;
public const ELEMENT_71 = 337;
public const ELEMENT_72 = 338;
public const ELEMENT_73 = 339;
public const ELEMENT_74 = 340;
public const ELEMENT_75 = 341;
public const ELEMENT_76 = 342;
public const ELEMENT_77 = 343;
public const ELEMENT_78 = 344;
public const ELEMENT_79 = 345;
public const ELEMENT_80 = 346;
public const ELEMENT_81 = 347;
public const ELEMENT_82 = 348;
public const ELEMENT_83 = 349;
public const ELEMENT_84 = 350;
public const ELEMENT_85 = 351;
public const ELEMENT_86 = 352;
public const ELEMENT_87 = 353;
public const ELEMENT_88 = 354;
public const ELEMENT_89 = 355;
public const ELEMENT_90 = 356;
public const ELEMENT_91 = 357;
public const ELEMENT_92 = 358;
public const ELEMENT_93 = 359;
public const ELEMENT_94 = 360;
public const ELEMENT_95 = 361;
public const ELEMENT_96 = 362;
public const ELEMENT_97 = 363;
public const ELEMENT_98 = 364;
public const ELEMENT_99 = 365;
public const ELEMENT_100 = 366;
public const ELEMENT_101 = 367;
public const ELEMENT_102 = 368;
public const ELEMENT_103 = 369;
public const ELEMENT_104 = 370;
public const ELEMENT_105 = 371;
public const ELEMENT_106 = 372;
public const ELEMENT_107 = 373;
public const ELEMENT_108 = 374;
public const ELEMENT_109 = 375;
public const ELEMENT_110 = 376;
public const ELEMENT_111 = 377;
public const ELEMENT_112 = 378;
public const ELEMENT_113 = 379;
public const ELEMENT_114 = 380;
public const ELEMENT_115 = 381;
public const ELEMENT_116 = 382;
public const ELEMENT_117 = 383;
public const ELEMENT_118 = 384;
public const SEAGRASS = 385;
public const CORAL = 386;
public const CORAL_BLOCK = 387;
public const CORAL_FAN = 388;
public const CORAL_FAN_DEAD = 389;
public const CORAL_FAN_HANG = 390;
public const CORAL_FAN_HANG2 = 391;
public const CORAL_FAN_HANG3 = 392;
public const KELP = 393;
public const DRIED_KELP_BLOCK = 394;
public const ACACIA_BUTTON = 395;
public const BIRCH_BUTTON = 396;
public const DARK_OAK_BUTTON = 397;
public const JUNGLE_BUTTON = 398;
public const SPRUCE_BUTTON = 399;
public const ACACIA_TRAPDOOR = 400;
public const BIRCH_TRAPDOOR = 401;
public const DARK_OAK_TRAPDOOR = 402;
public const JUNGLE_TRAPDOOR = 403;
public const SPRUCE_TRAPDOOR = 404;
public const ACACIA_PRESSURE_PLATE = 405;
public const BIRCH_PRESSURE_PLATE = 406;
public const DARK_OAK_PRESSURE_PLATE = 407;
public const JUNGLE_PRESSURE_PLATE = 408;
public const SPRUCE_PRESSURE_PLATE = 409;
public const CARVED_PUMPKIN = 410;
public const SEA_PICKLE = 411;
public const CONDUIT = 412;
public const TURTLE_EGG = 414;
public const BUBBLE_COLUMN = 415;
public const BARRIER = 416;
public const STONE_SLAB3 = 417;
public const BAMBOO = 418;
public const BAMBOO_SAPLING = 419;
public const SCAFFOLDING = 420;
public const STONE_SLAB4 = 421;
public const DOUBLE_STONE_SLAB3 = 422;
public const DOUBLE_STONE_SLAB4 = 423;
public const GRANITE_STAIRS = 424;
public const DIORITE_STAIRS = 425;
public const ANDESITE_STAIRS = 426;
public const POLISHED_GRANITE_STAIRS = 427;
public const POLISHED_DIORITE_STAIRS = 428;
public const POLISHED_ANDESITE_STAIRS = 429;
public const MOSSY_STONE_BRICK_STAIRS = 430;
public const SMOOTH_RED_SANDSTONE_STAIRS = 431;
public const SMOOTH_SANDSTONE_STAIRS = 432;
public const END_BRICK_STAIRS = 433;
public const MOSSY_COBBLESTONE_STAIRS = 434;
public const NORMAL_STONE_STAIRS = 435;
public const SPRUCE_STANDING_SIGN = 436;
public const SPRUCE_WALL_SIGN = 437;
public const SMOOTH_STONE = 438;
public const RED_NETHER_BRICK_STAIRS = 439;
public const SMOOTH_QUARTZ_STAIRS = 440;
public const BIRCH_STANDING_SIGN = 441;
public const BIRCH_WALL_SIGN = 442;
public const JUNGLE_STANDING_SIGN = 443;
public const JUNGLE_WALL_SIGN = 444;
public const ACACIA_STANDING_SIGN = 445;
public const ACACIA_WALL_SIGN = 446;
public const DARKOAK_STANDING_SIGN = 447;
public const DARKOAK_WALL_SIGN = 448;
public const LECTERN = 449;
public const GRINDSTONE = 450;
public const BLAST_FURNACE = 451;
public const STONECUTTER_BLOCK = 452;
public const SMOKER = 453;
public const LIT_SMOKER = 454;
public const CARTOGRAPHY_TABLE = 455;
public const FLETCHING_TABLE = 456;
public const SMITHING_TABLE = 457;
public const BARREL = 458;
public const LOOM = 459;
public const BELL = 461;
public const SWEET_BERRY_BUSH = 462;
public const LANTERN = 463;
public const CAMPFIRE = 464;
public const LAVA_CAULDRON = 465;
public const JIGSAW = 466;
public const WOOD = 467;
public const COMPOSTER = 468;
public const LIT_BLAST_FURNACE = 469;
}

View File

@ -0,0 +1,299 @@
<?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;
/**
* Constants for legacy metadata for various blocks.
*/
final class BlockLegacyMetadata{
private function __construct(){
//NOOP
}
public const ANVIL_NORMAL = 0;
public const ANVIL_SLIGHTLY_DAMAGED = 4;
public const ANVIL_VERY_DAMAGED = 8;
public const BAMBOO_FLAG_THICK = 0x01;
public const BAMBOO_FLAG_READY = 0x08;
public const BAMBOO_LEAF_SIZE_SHIFT = 1;
public const BAMBOO_LEAF_SIZE_MASK = 0x03;
public const BARREL_FLAG_OPEN = 0x08;
public const BED_FLAG_HEAD = 0x08;
public const BED_FLAG_OCCUPIED = 0x04;
public const BEDROCK_FLAG_INFINIBURN = 0x01;
public const BELL_ATTACHMENT_FLOOR = 0;
public const BELL_ATTACHMENT_CEILING = 1;
public const BELL_ATTACHMENT_ONE_WALL = 2;
public const BELL_ATTACHMENT_TWO_WALLS = 3;
public const BREWING_STAND_FLAG_EAST = 0x01;
public const BREWING_STAND_FLAG_SOUTHWEST = 0x02;
public const BREWING_STAND_FLAG_NORTHWEST = 0x04;
public const BUTTON_FLAG_POWERED = 0x08;
public const CHEMISTRY_COMPOUND_CREATOR = 0;
public const CHEMISTRY_MATERIAL_REDUCER = 4;
public const CHEMISTRY_ELEMENT_CONSTRUCTOR = 8;
public const CHEMISTRY_LAB_TABLE = 12;
public const COLORED_TORCH_BP_BLUE = 0;
public const COLORED_TORCH_BP_PURPLE = 8;
public const COLORED_TORCH_RG_RED = 0;
public const COLORED_TORCH_RG_GREEN = 8;
public const CORAL_BLOCK_FLAG_DEAD = 0x8;
public const CORAL_FAN_EAST_WEST = 0;
public const CORAL_FAN_NORTH_SOUTH = 1;
public const CORAL_FAN_TYPE_MASK = 0x7;
public const CORAL_FAN_HANG_FLAG_DEAD = 0x2;
public const CORAL_FAN_HANG_TUBE = 0;
public const CORAL_FAN_HANG_BRAIN = 1;
public const CORAL_FAN_HANG2_BUBBLE = 0;
public const CORAL_FAN_HANG2_FIRE = 1;
public const CORAL_FAN_HANG3_HORN = 0;
public const CORAL_FAN_HANG_TYPE_MASK = 0x1;
public const CORAL_VARIANT_TUBE = 0;
public const CORAL_VARIANT_BRAIN = 1;
public const CORAL_VARIANT_BUBBLE = 2;
public const CORAL_VARIANT_FIRE = 3;
public const CORAL_VARIANT_HORN = 4;
public const DIRT_FLAG_COARSE = 0x1;
public const DOOR_FLAG_TOP = 0x08;
public const DOOR_BOTTOM_FLAG_OPEN = 0x04;
public const DOOR_TOP_FLAG_RIGHT = 0x01;
public const DOOR_TOP_FLAG_POWERED = 0x02;
public const DOUBLE_PLANT_SUNFLOWER = 0;
public const DOUBLE_PLANT_LILAC = 1;
public const DOUBLE_PLANT_TALLGRASS = 2;
public const DOUBLE_PLANT_LARGE_FERN = 3;
public const DOUBLE_PLANT_ROSE_BUSH = 4;
public const DOUBLE_PLANT_PEONY = 5;
public const DOUBLE_PLANT_FLAG_TOP = 0x08;
public const END_PORTAL_FRAME_FLAG_EYE = 0x04;
public const FENCE_GATE_FLAG_OPEN = 0x04;
public const FENCE_GATE_FLAG_IN_WALL = 0x08;
public const FLOWER_POPPY = 0;
public const FLOWER_BLUE_ORCHID = 1;
public const FLOWER_ALLIUM = 2;
public const FLOWER_AZURE_BLUET = 3;
public const FLOWER_RED_TULIP = 4;
public const FLOWER_ORANGE_TULIP = 5;
public const FLOWER_WHITE_TULIP = 6;
public const FLOWER_PINK_TULIP = 7;
public const FLOWER_OXEYE_DAISY = 8;
public const FLOWER_CORNFLOWER = 9;
public const FLOWER_LILY_OF_THE_VALLEY = 10;
public const FLOWER_POT_FLAG_OCCUPIED = 0x01;
public const HOPPER_FLAG_POWERED = 0x08;
public const INFESTED_STONE = 0;
public const INFESTED_COBBLESTONE = 1;
public const INFESTED_STONE_BRICK = 2;
public const INFESTED_STONE_BRICK_MOSSY = 3;
public const INFESTED_STONE_BRICK_CRACKED = 4;
public const INFESTED_STONE_BRICK_CHISELED = 5;
public const ITEM_FRAME_FLAG_HAS_MAP = 0x04;
public const LANTERN_FLAG_HANGING = 0x01;
public const LEAVES_FLAG_NO_DECAY = 0x04;
public const LEAVES_FLAG_CHECK_DECAY = 0x08;
public const LEVER_FLAG_POWERED = 0x08;
public const LIQUID_FLAG_FALLING = 0x08;
public const MUSHROOM_BLOCK_ALL_PORES = 0;
public const MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER = 1;
public const MUSHROOM_BLOCK_CAP_NORTH_SIDE = 2;
public const MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER = 3;
public const MUSHROOM_BLOCK_CAP_WEST_SIDE = 4;
public const MUSHROOM_BLOCK_CAP_TOP_ONLY = 5;
public const MUSHROOM_BLOCK_CAP_EAST_SIDE = 6;
public const MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER = 7;
public const MUSHROOM_BLOCK_CAP_SOUTH_SIDE = 8;
public const MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER = 9;
public const MUSHROOM_BLOCK_STEM = 10;
//11, 12 and 13 appear the same as 0
public const MUSHROOM_BLOCK_ALL_CAP = 14;
public const MUSHROOM_BLOCK_ALL_STEM = 15;
public const NETHER_PORTAL_AXIS_X = 1;
public const NETHER_PORTAL_AXIS_Z = 2;
public const NETHER_REACTOR_INACTIVE = 0;
public const NETHER_REACTOR_ACTIVE = 1;
public const NETHER_REACTOR_USED = 2;
public const PRESSURE_PLATE_FLAG_POWERED = 0x01;
public const PRISMARINE_NORMAL = 0;
public const PRISMARINE_DARK = 1;
public const PRISMARINE_BRICKS = 2;
public const PURPUR_NORMAL = 0;
public const PURPUR_PILLAR = 2;
public const QUARTZ_NORMAL = 0;
public const QUARTZ_CHISELED = 1;
public const QUARTZ_PILLAR = 2;
public const QUARTZ_SMOOTH = 3;
public const RAIL_STRAIGHT_NORTH_SOUTH = 0;
public const RAIL_STRAIGHT_EAST_WEST = 1;
public const RAIL_ASCENDING_EAST = 2;
public const RAIL_ASCENDING_WEST = 3;
public const RAIL_ASCENDING_NORTH = 4;
public const RAIL_ASCENDING_SOUTH = 5;
public const RAIL_CURVE_SOUTHEAST = 6;
public const RAIL_CURVE_SOUTHWEST = 7;
public const RAIL_CURVE_NORTHWEST = 8;
public const RAIL_CURVE_NORTHEAST = 9;
public const REDSTONE_COMPARATOR_FLAG_SUBTRACT = 0x04;
public const REDSTONE_COMPARATOR_FLAG_POWERED = 0x08;
public const REDSTONE_RAIL_FLAG_POWERED = 0x08;
public const SANDSTONE_NORMAL = 0;
public const SANDSTONE_CHISELED = 1;
public const SANDSTONE_CUT = 2;
public const SANDSTONE_SMOOTH = 3;
public const SAPLING_FLAG_READY = 0x08;
public const SEA_PICKLE_FLAG_NOT_UNDERWATER = 0x04;
public const SKULL_FLAG_NO_DROPS = 0x08;
public const SLAB_FLAG_UPPER = 0x08;
public const SPONGE_FLAG_WET = 0x01;
public const STAIR_FLAG_UPSIDE_DOWN = 0x04;
public const STONE_NORMAL = 0;
public const STONE_GRANITE = 1;
public const STONE_POLISHED_GRANITE = 2;
public const STONE_DIORITE = 3;
public const STONE_POLISHED_DIORITE = 4;
public const STONE_ANDESITE = 5;
public const STONE_POLISHED_ANDESITE = 6;
public const STONE_BRICK_NORMAL = 0;
public const STONE_BRICK_MOSSY = 1;
public const STONE_BRICK_CRACKED = 2;
public const STONE_BRICK_CHISELED = 3;
public const STONE_SLAB_SMOOTH_STONE = 0;
public const STONE_SLAB_SANDSTONE = 1;
public const STONE_SLAB_FAKE_WOODEN = 2;
public const STONE_SLAB_COBBLESTONE = 3;
public const STONE_SLAB_BRICK = 4;
public const STONE_SLAB_STONE_BRICK = 5;
public const STONE_SLAB_QUARTZ = 6;
public const STONE_SLAB_NETHER_BRICK = 7;
public const STONE_SLAB2_RED_SANDSTONE = 0;
public const STONE_SLAB2_PURPUR = 1;
public const STONE_SLAB2_PRISMARINE = 2;
public const STONE_SLAB2_DARK_PRISMARINE = 3;
public const STONE_SLAB2_PRISMARINE_BRICKS = 4;
public const STONE_SLAB2_MOSSY_COBBLESTONE = 5;
public const STONE_SLAB2_SMOOTH_SANDSTONE = 6;
public const STONE_SLAB2_RED_NETHER_BRICK = 7;
public const STONE_SLAB3_END_STONE_BRICK = 0;
public const STONE_SLAB3_SMOOTH_RED_SANDSTONE = 1;
public const STONE_SLAB3_POLISHED_ANDESITE = 2;
public const STONE_SLAB3_ANDESITE = 3;
public const STONE_SLAB3_DIORITE = 4;
public const STONE_SLAB3_POLISHED_DIORITE = 5;
public const STONE_SLAB3_GRANITE = 6;
public const STONE_SLAB3_POLISHED_GRANITE = 7;
public const STONE_SLAB4_MOSSY_STONE_BRICK = 0;
public const STONE_SLAB4_SMOOTH_QUARTZ = 1;
public const STONE_SLAB4_STONE = 2;
public const STONE_SLAB4_CUT_SANDSTONE = 3;
public const STONE_SLAB4_CUT_RED_SANDSTONE = 4;
public const TALLGRASS_NORMAL = 1;
public const TALLGRASS_FERN = 2;
public const TNT_FLAG_UNSTABLE = 0x01;
public const TNT_FLAG_UNDERWATER = 0x02;
public const TRAPDOOR_FLAG_UPPER = 0x04;
public const TRAPDOOR_FLAG_OPEN = 0x08;
public const TRIPWIRE_FLAG_TRIGGERED = 0x01;
public const TRIPWIRE_FLAG_SUSPENDED = 0x02;
public const TRIPWIRE_FLAG_CONNECTED = 0x04;
public const TRIPWIRE_FLAG_DISARMED = 0x08;
public const TRIPWIRE_HOOK_FLAG_CONNECTED = 0x04;
public const TRIPWIRE_HOOK_FLAG_POWERED = 0x08;
public const VINE_FLAG_SOUTH = 0x01;
public const VINE_FLAG_WEST = 0x02;
public const VINE_FLAG_NORTH = 0x04;
public const VINE_FLAG_EAST = 0x08;
public const WALL_COBBLESTONE = 0;
public const WALL_MOSSY_COBBLESTONE = 1;
public const WALL_GRANITE = 2;
public const WALL_DIORITE = 3;
public const WALL_ANDESITE = 4;
public const WALL_SANDSTONE = 5;
public const WALL_BRICK = 6;
public const WALL_STONE_BRICK = 7;
public const WALL_MOSSY_STONE_BRICK = 8;
public const WALL_NETHER_BRICK = 9;
public const WALL_END_STONE_BRICK = 10;
public const WALL_PRISMARINE = 11;
public const WALL_RED_SANDSTONE = 12;
public const WALL_RED_NETHER_BRICK = 13;
public const WOOD_FLAG_STRIPPED = 0x8;
}

View File

@ -27,14 +27,18 @@ namespace pocketmine\block;
* Types of tools that can be used to break blocks
* Blocks may allow multiple tool types by combining these bitflags
*/
interface BlockToolType{
final class BlockToolType{
public const TYPE_NONE = 0;
public const TYPE_SWORD = 1 << 0;
public const TYPE_SHOVEL = 1 << 1;
public const TYPE_PICKAXE = 1 << 2;
public const TYPE_AXE = 1 << 3;
public const TYPE_SHEARS = 1 << 4;
public const TYPE_HOE = 1 << 5;
private function __construct(){
//NOOP
}
public const NONE = 0;
public const SWORD = 1 << 0;
public const SHOVEL = 1 << 1;
public const PICKAXE = 1 << 2;
public const AXE = 1 << 3;
public const SHEARS = 1 << 4;
public const HOE = 1 << 5;
}

View File

@ -23,15 +23,19 @@ declare(strict_types=1);
namespace pocketmine\block;
class Furnace extends BurningFurnace{
use pocketmine\item\Item;
protected $id = self::FURNACE;
public function getName() : string{
return "Furnace";
}
class BlueIce extends Opaque{
public function getLightLevel() : int{
return 0;
return 1;
}
public function getFrictionFactor() : float{
return 0.99;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
}

30
src/block/BoneBlock.php Normal file
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;
use pocketmine\block\utils\PillarRotationInMetadataTrait;
class BoneBlock extends Opaque{
use PillarRotationInMetadataTrait;
}

View File

@ -24,34 +24,20 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\VanillaItems;
class Bookshelf extends Solid{
protected $id = self::BOOKSHELF;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getName() : string{
return "Bookshelf";
}
public function getHardness() : float{
return 1.5;
}
public function getToolType() : int{
return BlockToolType::TYPE_AXE;
}
class Bookshelf extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
ItemFactory::get(Item::BOOK, 0, 3)
VanillaItems::BOOK()->setCount(3)
];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function getFuelTime() : int{
return 300;
}

114
src/block/BrewingStand.php Normal file
View File

@ -0,0 +1,114 @@
<?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\BrewingStand as TileBrewingStand;
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function array_key_exists;
class BrewingStand extends Transparent{
/**
* @var BrewingStandSlot[]
* @phpstan-var array<int, BrewingStandSlot>
*/
protected array $slots = [];
protected function writeStateToMeta() : int{
$flags = 0;
foreach([
BlockLegacyMetadata::BREWING_STAND_FLAG_EAST => BrewingStandSlot::EAST(),
BlockLegacyMetadata::BREWING_STAND_FLAG_NORTHWEST => BrewingStandSlot::NORTHWEST(),
BlockLegacyMetadata::BREWING_STAND_FLAG_SOUTHWEST => BrewingStandSlot::SOUTHWEST(),
] as $flag => $slot){
$flags |= (array_key_exists($slot->id(), $this->slots) ? $flag : 0);
}
return $flags;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->slots = [];
foreach([
BlockLegacyMetadata::BREWING_STAND_FLAG_EAST => BrewingStandSlot::EAST(),
BlockLegacyMetadata::BREWING_STAND_FLAG_NORTHWEST => BrewingStandSlot::NORTHWEST(),
BlockLegacyMetadata::BREWING_STAND_FLAG_SOUTHWEST => BrewingStandSlot::SOUTHWEST(),
] as $flag => $slot){
if(($stateMeta & $flag) !== 0){
$this->slots[$slot->id()] = $slot;
}
}
}
public function getStateBitmask() : int{
return 0b111;
}
public function hasSlot(BrewingStandSlot $slot) : bool{
return array_key_exists($slot->id(), $this->slots);
}
public function setSlot(BrewingStandSlot $slot, bool $occupied) : self{
if($occupied){
$this->slots[$slot->id()] = $slot;
}else{
unset($this->slots[$slot->id()]);
}
return $this;
}
/**
* @return BrewingStandSlot[]
* @phpstan-return array<int, BrewingStandSlot>
*/
public function getSlots() : array{
return $this->slots;
}
/** @param BrewingStandSlot[] $slots */
public function setSlots(array $slots) : self{
$this->slots = [];
foreach($slots as $slot){
$this->slots[$slot->id()] = $slot;
}
return $this;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$stand = $this->position->getWorld()->getTile($this->position);
if($stand instanceof TileBrewingStand and $stand->canOpenWith($item->getCustomName())){
$player->setCurrentWindow($stand->getInventory());
}
}
return true;
}
public function onScheduledUpdate() : void{
//TODO
}
}

View File

@ -25,12 +25,6 @@ namespace pocketmine\block;
class BrownMushroom extends RedMushroom{
protected $id = self::BROWN_MUSHROOM;
public function getName() : string{
return "Brown Mushroom";
}
public function getLightLevel() : int{
return 1;
}

View File

@ -28,15 +28,9 @@ use function mt_rand;
class BrownMushroomBlock extends RedMushroomBlock{
protected $id = Block::BROWN_MUSHROOM_BLOCK;
public function getName() : string{
return "Brown Mushroom Block";
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
Item::get(Item::BROWN_MUSHROOM, 0, mt_rand(0, 2))
VanillaBlocks::BROWN_MUSHROOM()->asItem()->setCount(mt_rand(0, 2))
];
}
}

89
src/block/Button.php Normal file
View File

@ -0,0 +1,89 @@
<?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\utils\AnyFacingTrait;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\RedstonePowerOffSound;
use pocketmine\world\sound\RedstonePowerOnSound;
abstract class Button extends Flowable{
use AnyFacingTrait;
protected bool $pressed = false;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeFacing($this->facing) | ($this->pressed ? BlockLegacyMetadata::BUTTON_FLAG_POWERED : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
//TODO: in PC it's (6 - facing) for every meta except 0 (down)
$this->facing = BlockDataSerializer::readFacing($stateMeta & 0x07);
$this->pressed = ($stateMeta & BlockLegacyMetadata::BUTTON_FLAG_POWERED) !== 0;
}
public function getStateBitmask() : int{
return 0b1111;
}
public function isPressed() : bool{ return $this->pressed; }
/** @return $this */
public function setPressed(bool $pressed) : self{
$this->pressed = $pressed;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
//TODO: check valid target block
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
abstract protected function getActivationTime() : int;
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->pressed){
$this->pressed = true;
$this->position->getWorld()->setBlock($this->position, $this);
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
}
return true;
}
public function onScheduledUpdate() : void{
if($this->pressed){
$this->pressed = false;
$this->position->getWorld()->setBlock($this->position, $this);
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
}
}
}

144
src/block/Cactus.php Normal file
View File

@ -0,0 +1,144 @@
<?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\utils\BlockDataSerializer;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Cactus extends Transparent{
protected int $age = 0;
protected function writeStateToMeta() : int{
return $this->age;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 15);
}
public function getStateBitmask() : int{
return 0b1111;
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 15){
throw new \InvalidArgumentException("Age must be in range 0-15");
}
$this->age = $age;
return $this;
}
public function hasEntityCollision() : bool{
return true;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
static $shrinkSize = 1 / 16;
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
}
public function onEntityInside(Entity $entity) : bool{
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_CONTACT, 1);
$entity->attack($ev);
return true;
}
public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN);
if($down->getId() !== BlockLegacyIds::SAND and !$down->isSameType($this)){
$this->position->getWorld()->useBreakOn($this->position);
}else{
foreach(Facing::HORIZONTAL as $side){
$b = $this->getSide($side);
if($b->isSolid()){
$this->position->getWorld()->useBreakOn($this->position);
break;
}
}
}
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
if($this->age === 15){
for($y = 1; $y < 3; ++$y){
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
break;
}
$b = $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
if($b->getId() === BlockLegacyIds::AIR){
$ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
$ev->call();
if($ev->isCancelled()){
break;
}
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
}else{
break;
}
}
$this->age = 0;
$this->position->getWorld()->setBlock($this->position, $this);
}else{
++$this->age;
$this->position->getWorld()->setBlock($this->position, $this);
}
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getId() === BlockLegacyIds::SAND or $down->isSameType($this)){
foreach(Facing::HORIZONTAL as $side){
if($this->getSide($side)->isSolid()){
return false;
}
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
}

137
src/block/Cake.php Normal file
View File

@ -0,0 +1,137 @@
<?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\utils\BlockDataSerializer;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\FoodSource;
use pocketmine\entity\Living;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Cake extends Transparent implements FoodSource{
protected int $bites = 0;
protected function writeStateToMeta() : int{
return $this->bites;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->bites = BlockDataSerializer::readBoundedInt("bites", $stateMeta, 0, 6);
}
public function getStateBitmask() : int{
return 0b111;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()
->contract(1 / 16, 0, 1 / 16)
->trim(Facing::UP, 0.5)
->trim(Facing::WEST, $this->bites / 8)
];
}
public function getBites() : int{ return $this->bites; }
/** @return $this */
public function setBites(int $bites) : self{
if($bites < 0 || $bites > 6){
throw new \InvalidArgumentException("Bites must be in range 0-6");
}
$this->bites = $bites;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getId() !== BlockLegacyIds::AIR){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getId() === BlockLegacyIds::AIR){ //Replace with common break method
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
}
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
return $player->consumeObject($this);
}
return false;
}
public function getFoodRestore() : int{
return 2;
}
public function getSaturationRestore() : float{
return 0.4;
}
public function requiresHunger() : bool{
return true;
}
/**
* @return Block
*/
public function getResidue(){
$clone = clone $this;
$clone->bites++;
if($clone->bites > 6){
$clone = VanillaBlocks::AIR();
}
return $clone;
}
/**
* @return EffectInstance[]
*/
public function getAdditionalEffects() : array{
return [];
}
public function onConsume(Living $consumer) : void{
$this->position->getWorld()->setBlock($this->position, $this->getResidue());
}
}

View File

@ -23,58 +23,46 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColorBlockMetaHelper;
use pocketmine\block\utils\ColorInMetadataTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Carpet extends Flowable{
use ColorInMetadataTrait;
protected $id = self::CARPET;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getHardness() : float{
return 0.1;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $breakInfo);
}
public function isSolid() : bool{
return true;
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Carpet";
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
}
protected function recalculateBoundingBox() : ?AxisAlignedBB{
return new AxisAlignedBB(
$this->x,
$this->y,
$this->z,
$this->x + 1,
$this->y + 0.0625,
$this->z + 1
);
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
$down = $this->getSide(Vector3::SIDE_DOWN);
if($down->getId() !== self::AIR){
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
return true;
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getId() !== BlockLegacyIds::AIR){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR){
$this->getLevelNonNull()->useBreakOn($this);
if($this->getSide(Facing::DOWN)->getId() === BlockLegacyIds::AIR){
$this->position->getWorld()->useBreakOn($this->position);
}
}

View File

@ -24,28 +24,18 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Carrot extends Crops{
protected $id = self::CARROT_BLOCK;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getName() : string{
return "Carrot Block";
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
ItemFactory::get(Item::CARROT, 0, $this->meta >= 0x07 ? mt_rand(1, 4) : 1)
VanillaItems::CARROT()->setCount($this->age >= 7 ? mt_rand(1, 4) : 1)
];
}
public function getPickedItem() : Item{
return ItemFactory::get(Item::CARROT);
public function getPickedItem(bool $addUserData = false) : Item{
return VanillaItems::CARROT();
}
}

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\block\utils\BlockDataSerializer;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
class CarvedPumpkin extends Opaque{
use FacesOppositePlacingPlayerTrait;
use HorizontalFacingTrait;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
}
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing);
}
public function getStateBitmask() : int{
return 0b11;
}
}

View File

@ -0,0 +1,32 @@
<?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;
final class ChemicalHeat extends Transparent{
/*
* TODO: this block causes melting of nearby ice and snow within 2 blocks taxicab distance.
* Since it doesn't emit any light, the mechanics of this block's behaviour are not currently clear.
*/
}

View File

@ -23,43 +23,32 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\PillarRotationHelper;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\player\Player;
class HayBale extends Solid{
final class ChemistryTable extends Opaque{
use FacesOppositePlacingPlayerTrait;
use HorizontalFacingTrait;
protected $id = self::HAY_BALE;
public function __construct(int $meta = 0){
$this->meta = $meta;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = Facing::opposite(BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x3));
}
public function getName() : string{
return "Hay Bale";
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing(Facing::opposite($this->facing));
}
public function getHardness() : float{
return 0.5;
public function getStateBitmask() : int{
return 0b0011;
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
$this->meta = PillarRotationHelper::getMetaFromFace($this->meta, $face);
$this->getLevelNonNull()->setBlock($blockReplace, $this, true, true);
return true;
}
public function getVariantBitmask() : int{
return 0x03;
}
public function getFlameEncouragement() : int{
return 60;
}
public function getFlammability() : int{
return 20;
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
//TODO
return false;
}
}

90
src/block/Chest.php Normal file
View File

@ -0,0 +1,90 @@
<?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\Chest as TileChest;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\NormalHorizontalFacingInMetadataTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class Chest extends Transparent{
use FacesOppositePlacingPlayerTrait;
use NormalHorizontalFacingInMetadataTrait;
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//these are slightly bigger than in PC
return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)];
}
public function onPostPlace() : void{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileChest){
foreach([
Facing::rotateY($this->facing, true),
Facing::rotateY($this->facing, false)
] as $side){
$c = $this->getSide($side);
if($c instanceof Chest and $c->isSameType($this) and $c->facing === $this->facing){
$pair = $this->position->getWorld()->getTile($c->position);
if($pair instanceof TileChest and !$pair->isPaired()){
$pair->pairWith($tile);
$tile->pairWith($pair);
break;
}
}
}
}
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$chest = $this->position->getWorld()->getTile($this->position);
if($chest instanceof TileChest){
if(
!$this->getSide(Facing::UP)->isTransparent() or
(($pair = $chest->getPair()) !== null and !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) or
!$chest->canOpenWith($item->getCustomName())
){
return true;
}
$player->setCurrentWindow($chest->getInventory());
}
}
return true;
}
public function getFuelTime() : int{
return 300;
}
}

View File

@ -23,18 +23,18 @@ declare(strict_types=1);
namespace pocketmine\block;
class Wood2 extends Wood{
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
public const ACACIA = 0;
public const DARK_OAK = 1;
class Clay extends Opaque{
protected $id = self::WOOD2;
public function getName() : string{
static $names = [
0 => "Acacia Wood",
1 => "Dark Oak Wood"
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::CLAY()->setCount(4)
];
return $names[$this->getVariant()] ?? "Unknown";
}
public function isAffectedBySilkTouch() : bool{
return true;
}
}

View File

@ -21,16 +21,19 @@
declare(strict_types=1);
namespace pocketmine\item;
namespace pocketmine\block;
class Boat extends Item{
public function __construct(int $meta = 0){
parent::__construct(self::BOAT, $meta, "Boat");
}
class Coal extends Opaque{
public function getFuelTime() : int{
return 1200; //400 in PC
return 16000;
}
//TODO
public function getFlameEncouragement() : int{
return 5;
}
public function getFlammability() : int{
return 5;
}
}

View File

@ -24,27 +24,22 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\VanillaItems;
use function mt_rand;
class Tripwire extends Flowable{
protected $id = self::TRIPWIRE;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getName() : string{
return "Tripwire";
}
class CoalOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
ItemFactory::get(Item::STRING)
VanillaItems::COAL()
];
}
public function isAffectedBySilkTouch() : bool{
return false;
return true;
}
protected function getXpDropAmount() : int{
return mt_rand(0, 2);
}
}

54
src/block/Cobweb.php Normal file
View File

@ -0,0 +1,54 @@
<?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\entity\Entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
class Cobweb extends Flowable{
public function hasEntityCollision() : bool{
return true;
}
public function onEntityInside(Entity $entity) : bool{
$entity->resetFallDistance();
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::STRING()
];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function blocksDirectSkyLight() : bool{
return true;
}
}

146
src/block/CocoaBlock.php Normal file
View File

@ -0,0 +1,146 @@
<?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\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\TreeType;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class CocoaBlock extends Transparent{
use HorizontalFacingTrait;
protected int $age = 0;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing(Facing::opposite($this->facing)) | ($this->age << 2);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = Facing::opposite(BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03));
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta >> 2, 0, 2);
}
public function getStateBitmask() : int{
return 0b1111;
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 2){
throw new \InvalidArgumentException("Age must be in range 0-2");
}
$this->age = $age;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()
->squash(Facing::axis(Facing::rotateY($this->facing, true)), (6 - $this->age) / 16) //sides
->trim(Facing::DOWN, (7 - $this->age * 2) / 16)
->trim(Facing::UP, 0.25)
->trim(Facing::opposite($this->facing), 1 / 16) //gap between log and pod
->trim($this->facing, (11 - $this->age * 2) / 16) //outward face
];
}
private function canAttachTo(Block $block) : bool{
return $block instanceof Wood && $block->getTreeType()->equals(TreeType::JUNGLE());
}
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)){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($item instanceof Fertilizer && $this->grow()){
$item->pop();
return true;
}
return false;
}
public function onNearbyBlockChange() : void{
if(!$this->canAttachTo($this->getSide(Facing::opposite($this->facing)))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
if(mt_rand(1, 5) === 1){
$this->grow();
}
}
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{
return [
VanillaItems::COCOA_BEANS()->setCount($this->age === 2 ? mt_rand(2, 3) : 1)
];
}
public function getPickedItem(bool $addUserData = false) : Item{
return VanillaItems::COCOA_BEANS();
}
}

36
src/block/Concrete.php Normal file
View File

@ -0,0 +1,36 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColorInMetadataTrait;
use pocketmine\block\utils\DyeColor;
class Concrete extends Opaque{
use ColorInMetadataTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $breakInfo);
}
}

View File

@ -23,33 +23,28 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColorBlockMetaHelper;
use pocketmine\block\utils\ColorInMetadataTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\math\Facing;
class ConcretePowder extends Fallable{
protected $id = self::CONCRETE_POWDER;
public function __construct(int $meta = 0){
$this->meta = $meta;
class ConcretePowder extends Opaque implements Fallable{
use ColorInMetadataTrait;
use FallableTrait {
onNearbyBlockChange as protected startFalling;
}
public function getName() : string{
return ColorBlockMetaHelper::getColorFromMeta($this->getVariant()) . " Concrete Powder";
}
public function getHardness() : float{
return 0.5;
}
public function getToolType() : int{
return BlockToolType::TYPE_SHOVEL;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $breakInfo);
}
public function onNearbyBlockChange() : void{
if(($block = $this->checkAdjacentWater()) !== null){
$this->level->setBlock($this, $block);
$this->position->getWorld()->setBlock($this->position, $block);
}else{
parent::onNearbyBlockChange();
$this->startFalling();
}
}
@ -58,9 +53,12 @@ class ConcretePowder extends Fallable{
}
private function checkAdjacentWater() : ?Block{
for($i = 1; $i < 6; ++$i){ //Do not check underneath
foreach(Facing::ALL as $i){
if($i === Facing::DOWN){
continue;
}
if($this->getSide($i) instanceof Water){
return BlockFactory::get(Block::CONCRETE, $this->meta);
return VanillaBlocks::CONCRETE()->setColor($this->color);
}
}

82
src/block/Coral.php Normal file
View File

@ -0,0 +1,82 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\data\bedrock\CoralTypeIdMap;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class Coral extends BaseCoral{
public function readStateFromData(int $id, int $stateMeta) : void{
$coralType = CoralTypeIdMap::getInstance()->fromId($stateMeta);
if($coralType === null){
throw new InvalidBlockStateException("No such coral type");
}
$this->coralType = $coralType;
}
public function writeStateToMeta() : int{
return CoralTypeIdMap::getInstance()->toId($this->coralType);
}
protected function writeStateToItemMeta() : int{
return $this->writeStateToMeta();
}
public function getStateBitmask() : int{
return 0b0111;
}
public function readStateFromWorld() : void{
//TODO: this hack ensures correct state of coral plants, because they don't retain their dead flag in metadata
$world = $this->position->getWorld();
$this->dead = true;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
$this->dead = false;
break;
}
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$tx->fetchBlock($blockReplace->getPosition()->down())->isSolid()){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->down())->isSolid()){
$world->useBreakOn($this->position);
}else{
parent::onNearbyBlockChange();
}
}
}

109
src/block/CoralBlock.php Normal file
View File

@ -0,0 +1,109 @@
<?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\utils\CoralType;
use pocketmine\block\utils\InvalidBlockStateException;
use pocketmine\data\bedrock\CoralTypeIdMap;
use pocketmine\item\Item;
use function mt_rand;
final class CoralBlock extends Opaque{
private CoralType $coralType;
private bool $dead = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->coralType = CoralType::TUBE();
parent::__construct($idInfo, $name, $breakInfo);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$coralType = CoralTypeIdMap::getInstance()->fromId($stateMeta & 0x7);
if($coralType === null){
throw new InvalidBlockStateException("No such coral type");
}
$this->coralType = $coralType;
$this->dead = ($stateMeta & BlockLegacyMetadata::CORAL_BLOCK_FLAG_DEAD) !== 0;
}
protected function writeStateToMeta() : int{
return ($this->dead ? BlockLegacyMetadata::CORAL_BLOCK_FLAG_DEAD : 0) | CoralTypeIdMap::getInstance()->toId($this->coralType);
}
protected function writeStateToItemMeta() : int{
return $this->writeStateToMeta();
}
public function getStateBitmask() : int{
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{
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));
}
}
public function onScheduledUpdate() : void{
if(!$this->dead){
$world = $this->position->getWorld();
$hasWater = false;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
$hasWater = true;
break;
}
}
if(!$hasWater){
$world->setBlock($this->position, $this->setDead(true));
}
}
}
public function getDropsForCompatibleTool(Item $item) : array{
return [$this->setDead(true)->asItem()];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
}

View File

@ -23,29 +23,22 @@ declare(strict_types=1);
namespace pocketmine\block;
class NoteBlock extends Solid{
use pocketmine\block\inventory\CraftingTableInventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
protected $id = self::NOTE_BLOCK;
class CraftingTable extends Opaque{
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$player->setCurrentWindow(new CraftingTableInventory($this->position));
}
public function getName() : string{
return "Note Block";
return true;
}
public function getFuelTime() : int{
return 300;
}
public function getHardness() : float{
return 0.8;
}
public function getToolType() : int{
return BlockToolType::TYPE_AXE;
}
//TODO
}

113
src/block/Crops.php Normal file
View File

@ -0,0 +1,113 @@
<?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\utils\BlockDataSerializer;
use pocketmine\event\block\BlockGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
abstract class Crops extends Flowable{
protected int $age = 0;
protected function writeStateToMeta() : int{
return $this->age;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 7);
}
public function getStateBitmask() : int{
return 0b111;
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 7){
throw new \InvalidArgumentException("Age must be in range 0-7");
}
$this->age = $age;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockReplace->getSide(Facing::DOWN)->getId() === BlockLegacyIds::FARMLAND){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->age < 7 and $item instanceof Fertilizer){
$block = clone $this;
$block->age += mt_rand(2, 5);
if($block->age > 7){
$block->age = 7;
}
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
$item->pop();
}
return true;
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getId() !== BlockLegacyIds::FARMLAND){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
if($this->age < 7 and mt_rand(0, 2) === 1){
$block = clone $this;
++$block->age;
$ev = new BlockGrowEvent($this, $block);
$ev->call();
if(!$ev->isCancelled()){
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
}
}
}
}

View File

@ -0,0 +1,117 @@
<?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\utils\AnalogRedstoneSignalEmitterTrait;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function cos;
use function max;
use function round;
use const M_PI;
class DaylightSensor extends Transparent{
use AnalogRedstoneSignalEmitterTrait;
protected BlockIdentifierFlattened $idInfoFlattened;
protected bool $inverted = false;
public function __construct(BlockIdentifierFlattened $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->idInfoFlattened = $idInfo;
parent::__construct($idInfo, $name, $breakInfo);
}
public function getId() : int{
return $this->inverted ? $this->idInfoFlattened->getSecondId() : parent::getId();
}
protected function writeStateToMeta() : int{
return $this->signalStrength;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->signalStrength = BlockDataSerializer::readBoundedInt("signalStrength", $stateMeta, 0, 15);
$this->inverted = $id === $this->idInfoFlattened->getSecondId();
}
public function getStateBitmask() : int{
return 0b1111;
}
public function isInverted() : bool{
return $this->inverted;
}
/**
* @return $this
*/
public function setInverted(bool $inverted = true) : self{
$this->inverted = $inverted;
return $this;
}
public function getFuelTime() : int{
return 300;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 10 / 16)];
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->inverted = !$this->inverted;
$this->signalStrength = $this->recalculateSignalStrength();
$this->position->getWorld()->setBlock($this->position, $this);
return true;
}
public function onScheduledUpdate() : void{
$signalStrength = $this->recalculateSignalStrength();
if($this->signalStrength !== $signalStrength){
$this->signalStrength = $signalStrength;
$this->position->getWorld()->setBlock($this->position, $this);
}
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 20);
}
private function recalculateSignalStrength() : int{
$lightLevel = $this->position->getWorld()->getRealBlockSkyLightAt($this->position->x, $this->position->y, $this->position->z);
if($this->inverted){
return 15 - $lightLevel;
}
$sunAngle = $this->position->getWorld()->getSunAnglePercentage();
return max(0, (int) round($lightLevel * cos(($sunAngle + ((($sunAngle < 0.5 ? 0 : 1) - $sunAngle) / 5)) * 2 * M_PI)));
}
//TODO
}

View File

@ -24,53 +24,37 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class DeadBush extends Flowable{
protected $id = self::DEAD_BUSH;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getName() : string{
return "Dead Bush";
}
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
if(!$this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
return parent::place($item, $blockReplace, $blockClicked, $face, $clickVector, $player);
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->getSide(Facing::DOWN)->isTransparent()){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){
$this->getLevelNonNull()->useBreakOn($this);
if($this->getSide(Facing::DOWN)->isTransparent()){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function getToolType() : int{
return BlockToolType::TYPE_SHEARS;
public function getDropsForIncompatibleTool(Item $item) : array{
return [
VanillaItems::STICK()->setCount(mt_rand(0, 2))
];
}
public function getToolHarvestLevel() : int{
return 1;
}
public function getDrops(Item $item) : array{
if(!$this->isCompatibleWithTool($item)){
return [
ItemFactory::get(Item::STICK, 0, mt_rand(0, 2))
];
}
return parent::getDrops($item);
public function isAffectedBySilkTouch() : bool{
return true;
}
public function getFlameEncouragement() : int{

View File

@ -0,0 +1,51 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
class DetectorRail extends StraightOnlyRail{
protected bool $activated = false;
public function isActivated() : bool{ return $this->activated; }
/** @return $this */
public function setActivated(bool $activated) : self{
$this->activated = $activated;
return $this;
}
public function readStateFromData(int $id, int $stateMeta) : void{
parent::readStateFromData($id, $stateMeta & ~BlockLegacyMetadata::REDSTONE_RAIL_FLAG_POWERED);
$this->activated = ($stateMeta & BlockLegacyMetadata::REDSTONE_RAIL_FLAG_POWERED) !== 0;
}
protected function writeStateToMeta() : int{
return parent::writeStateToMeta() | ($this->activated ? BlockLegacyMetadata::REDSTONE_RAIL_FLAG_POWERED : 0);
}
public function getStateBitmask() : int{
return 0b1111;
}
//TODO
}

View File

@ -24,31 +24,22 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
class RedMushroomBlock extends Solid{
protected $id = Block::RED_MUSHROOM_BLOCK;
public function __construct(int $meta = 0){
$this->meta = $meta;
}
public function getName() : string{
return "Red Mushroom Block";
}
public function getHardness() : float{
return 0.2;
}
public function getToolType() : int{
return BlockToolType::TYPE_AXE;
}
class DiamondOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
Item::get(Item::RED_MUSHROOM, 0, mt_rand(0, 2))
VanillaItems::DIAMOND()
];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
protected function getXpDropAmount() : int{
return mt_rand(3, 7);
}
}

70
src/block/Dirt.php Normal file
View File

@ -0,0 +1,70 @@
<?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\Hoe;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class Dirt extends Opaque{
protected bool $coarse = false;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->coarse = ($stateMeta & BlockLegacyMetadata::DIRT_FLAG_COARSE) !== 0;
}
protected function writeStateToMeta() : int{
return $this->coarse ? BlockLegacyMetadata::DIRT_FLAG_COARSE : 0;
}
protected function writeStateToItemMeta() : int{
return $this->writeStateToMeta();
}
public function getStateBitmask() : int{
return 0b1;
}
public function isCoarse() : bool{ return $this->coarse; }
/** @return $this */
public function setCoarse(bool $coarse) : self{
$this->coarse = $coarse;
return $this;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP and $item instanceof Hoe){
$item->applyDamage(1);
$this->position->getWorld()->setBlock($this->position, $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND());
return true;
}
return false;
}
}

187
src/block/Door.php Normal file
View File

@ -0,0 +1,187 @@
<?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\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\DoorSound;
class Door extends Transparent{
use HorizontalFacingTrait;
use PoweredByRedstoneTrait;
protected bool $top = false;
protected bool $hingeRight = false;
protected bool $open = false;
protected function writeStateToMeta() : int{
if($this->top){
return BlockLegacyMetadata::DOOR_FLAG_TOP |
($this->hingeRight ? BlockLegacyMetadata::DOOR_TOP_FLAG_RIGHT : 0) |
($this->powered ? BlockLegacyMetadata::DOOR_TOP_FLAG_POWERED : 0);
}
return BlockDataSerializer::writeLegacyHorizontalFacing(Facing::rotateY($this->facing, true)) | ($this->open ? BlockLegacyMetadata::DOOR_BOTTOM_FLAG_OPEN : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->top = ($stateMeta & BlockLegacyMetadata::DOOR_FLAG_TOP) !== 0;
if($this->top){
$this->hingeRight = ($stateMeta & BlockLegacyMetadata::DOOR_TOP_FLAG_RIGHT) !== 0;
$this->powered = ($stateMeta & BlockLegacyMetadata::DOOR_TOP_FLAG_POWERED) !== 0;
}else{
$this->facing = Facing::rotateY(BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03), false);
$this->open = ($stateMeta & BlockLegacyMetadata::DOOR_BOTTOM_FLAG_OPEN) !== 0;
}
}
public function getStateBitmask() : int{
return 0b1111;
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
//copy door properties from other half
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door and $other->isSameType($this)){
if($this->top){
$this->facing = $other->facing;
$this->open = $other->open;
}else{
$this->hingeRight = $other->hingeRight;
$this->powered = $other->powered;
}
}
}
public function isTop() : bool{ return $this->top; }
/** @return $this */
public function setTop(bool $top) : self{
$this->top = $top;
return $this;
}
public function isHingeRight() : bool{ return $this->hingeRight; }
/** @return $this */
public function setHingeRight(bool $hingeRight) : self{
$this->hingeRight = $hingeRight;
return $this;
}
public function isOpen() : bool{ return $this->open; }
/** @return $this */
public function setOpen(bool $open) : self{
$this->open = $open;
return $this;
}
public function isSolid() : bool{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//TODO: doors are 0.1825 blocks thick, instead of 0.1875 like JE (https://bugs.mojang.com/browse/MCPE-19214)
return [AxisAlignedBB::one()->trim($this->open ? Facing::rotateY($this->facing, !$this->hingeRight) : $this->facing, 327 / 400)];
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getId() === BlockLegacyIds::AIR){ //Replace with common break method
$this->position->getWorld()->useBreakOn($this->position); //this will delete both halves if they exist
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP){
$blockUp = $this->getSide(Facing::UP);
$blockDown = $this->getSide(Facing::DOWN);
if(!$blockUp->canBeReplaced() or $blockDown->isTransparent()){
return false;
}
if($player !== null){
$this->facing = $player->getHorizontalFacing();
}
$next = $this->getSide(Facing::rotateY($this->facing, false));
$next2 = $this->getSide(Facing::rotateY($this->facing, true));
if($next->isSameType($this) or (!$next2->isTransparent() and $next->isTransparent())){ //Door hinge
$this->hingeRight = true;
}
$topHalf = clone $this;
$topHalf->top = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($blockUp->position, $topHalf);
return true;
}
return false;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->open = !$this->open;
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door and $other->isSameType($this)){
$other->open = $this->open;
$this->position->getWorld()->setBlock($other->position, $other);
}
$this->position->getWorld()->setBlock($this->position, $this);
$this->position->getWorld()->addSound($this->position, new DoorSound());
return true;
}
public function getDrops(Item $item) : array{
if(!$this->top){
return parent::getDrops($item);
}
return [];
}
public function getAffectedBlocks() : array{
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other->isSameType($this)){
return [$this, $other];
}
return parent::getAffectedBlocks();
}
}

106
src/block/DoublePlant.php Normal file
View File

@ -0,0 +1,106 @@
<?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\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class DoublePlant extends Flowable{
protected bool $top = false;
protected function writeStateToMeta() : int{
return ($this->top ? BlockLegacyMetadata::DOUBLE_PLANT_FLAG_TOP : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->top = ($stateMeta & BlockLegacyMetadata::DOUBLE_PLANT_FLAG_TOP) !== 0;
}
public function getStateBitmask() : int{
return 0b1000;
}
public function isTop() : bool{ return $this->top; }
/** @return $this */
public function setTop(bool $top) : self{
$this->top = $top;
return $this;
}
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();
if(($id === BlockLegacyIds::GRASS or $id === BlockLegacyIds::DIRT) and $blockReplace->getSide(Facing::UP)->canBeReplaced()){
$top = clone $this;
$top->top = true;
$tx->addBlock($blockReplace->position, $this)->addBlock($blockReplace->position->getSide(Facing::UP), $top);
return true;
}
return false;
}
/**
* Returns whether this double-plant has a corresponding other half.
*/
public function isValidHalfPlant() : bool{
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
return (
$other instanceof DoublePlant and
$other->isSameType($this) and
$other->top !== $this->top
);
}
public function onNearbyBlockChange() : void{
if(!$this->isValidHalfPlant() or (!$this->top and $this->getSide(Facing::DOWN)->isTransparent())){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function getDrops(Item $item) : array{
return $this->top ? parent::getDrops($item) : [];
}
public function getAffectedBlocks() : array{
if($this->isValidHalfPlant()){
return [$this, $this->getSide($this->top ? Facing::DOWN : Facing::UP)];
}
return parent::getAffectedBlocks();
}
public function getFlameEncouragement() : int{
return 60;
}
public function getFlammability() : int{
return 100;
}
}

View File

@ -23,17 +23,20 @@ declare(strict_types=1);
namespace pocketmine\block;
class WallBanner extends StandingBanner{
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use function mt_rand;
protected $id = self::WALL_BANNER;
class DoubleTallGrass extends DoublePlant{
public function getName() : string{
return "Wall Banner";
public function canBeReplaced() : bool{
return true;
}
public function onNearbyBlockChange() : void{
if($this->getSide($this->meta ^ 0x01)->getId() === self::AIR){
$this->getLevelNonNull()->useBreakOn($this);
public function getDropsForIncompatibleTool(Item $item) : array{
if($this->top and mt_rand(0, 7) === 0){
return [VanillaItems::WHEAT_SEEDS()];
}
return [];
}
}

85
src/block/DragonEgg.php Normal file
View File

@ -0,0 +1,85 @@
<?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\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\event\block\BlockTeleportEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\player\GameMode;
use pocketmine\player\Player;
use pocketmine\world\particle\DragonEggTeleportParticle;
use pocketmine\world\World;
use function max;
use function min;
use function mt_rand;
class DragonEgg extends Transparent implements Fallable{
use FallableTrait;
public function getLightLevel() : int{
return 1;
}
public function tickFalling() : ?Block{
return null;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->teleport();
return true;
}
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
if($player !== null && !$player->getGamemode()->equals(GameMode::CREATIVE())){
$this->teleport();
return true;
}
return false;
}
public function teleport() : void{
for($tries = 0; $tries < 16; ++$tries){
$block = $this->position->getWorld()->getBlockAt(
$this->position->x + mt_rand(-16, 16),
max(World::Y_MIN, min(World::Y_MAX - 1, $this->position->y + mt_rand(-8, 8))),
$this->position->z + mt_rand(-16, 16)
);
if($block instanceof Air){
$ev = new BlockTeleportEvent($this, $block->position);
$ev->call();
if($ev->isCancelled()){
break;
}
$blockPos = $ev->getTo();
$this->position->getWorld()->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
$this->position->getWorld()->setBlock($blockPos, $this);
break;
}
}
}
}

View File

@ -23,15 +23,17 @@ declare(strict_types=1);
namespace pocketmine\block;
class LitRedstoneLamp extends RedstoneLamp{
class DriedKelp extends Opaque{
protected $id = self::LIT_REDSTONE_LAMP;
public function getName() : string{
return "Lit Redstone Lamp";
public function getFlameEncouragement() : int{
return 30;
}
public function getLightLevel() : int{
return 15;
public function getFlammability() : int{
return 60;
}
public function getFuelTime() : int{
return 4000;
}
}

View File

@ -0,0 +1,36 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColorInMetadataTrait;
use pocketmine\block\utils\DyeColor;
final class DyedShulkerBox extends ShulkerBox{
use ColorInMetadataTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $breakInfo);
}
}

View File

@ -23,33 +23,28 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\TieredTool;
class Element extends Opaque{
class Bricks extends Solid{
private int $atomicWeight;
private int $group;
private string $symbol;
protected $id = self::BRICK_BLOCK;
public function __construct(int $meta = 0){
$this->meta = $meta;
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo, string $symbol, int $atomicWeight, int $group){
parent::__construct($idInfo, $name, $breakInfo);
$this->atomicWeight = $atomicWeight;
$this->group = $group;
$this->symbol = $symbol;
}
public function getHardness() : float{
return 2;
public function getAtomicWeight() : int{
return $this->atomicWeight;
}
public function getBlastResistance() : float{
return 30;
public function getGroup() : int{
return $this->group;
}
public function getToolType() : int{
return BlockToolType::TYPE_PICKAXE;
}
public function getToolHarvestLevel() : int{
return TieredTool::TIER_WOODEN;
}
public function getName() : string{
return "Bricks";
public function getSymbol() : string{
return $this->symbol;
}
}

45
src/block/EmeraldOre.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\VanillaItems;
use function mt_rand;
class EmeraldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaItems::EMERALD()
];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
protected function getXpDropAmount() : int{
return mt_rand(3, 7);
}
}

View File

@ -23,29 +23,29 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\inventory\EnchantInventory;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\Player;
use pocketmine\player\Player;
abstract class Button extends Flowable{
class EnchantingTable extends Transparent{
public function __construct(int $meta = 0){
$this->meta = $meta;
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 0.25)];
}
public function getVariantBitmask() : int{
return 0;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
//TODO lock
public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{
//TODO: check valid target block
$this->meta = $face;
$player->setCurrentWindow(new EnchantInventory($this->position));
}
return $this->level->setBlock($this, $this, true, true);
}
public function onActivate(Item $item, Player $player = null) : bool{
//TODO
return true;
}
}

View File

@ -0,0 +1,69 @@
<?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\utils\BlockDataSerializer;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
class EndPortalFrame extends Opaque{
use FacesOppositePlacingPlayerTrait;
use HorizontalFacingTrait;
protected bool $eye = false;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) | ($this->eye ? BlockLegacyMetadata::END_PORTAL_FRAME_FLAG_EYE : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->eye = ($stateMeta & BlockLegacyMetadata::END_PORTAL_FRAME_FLAG_EYE) !== 0;
}
public function getStateBitmask() : int{
return 0b111;
}
public function hasEye() : bool{ return $this->eye; }
/** @return $this */
public function setEye(bool $eye) : self{
$this->eye = $eye;
return $this;
}
public function getLightLevel() : int{
return 1;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 3 / 16)];
}
}

92
src/block/EndRod.php Normal file
View File

@ -0,0 +1,92 @@
<?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\utils\AnyFacingTrait;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class EndRod extends Flowable{
use AnyFacingTrait;
protected function writeStateToMeta() : int{
$result = BlockDataSerializer::writeFacing($this->facing);
if(Facing::axis($this->facing) !== Axis::Y){
$result ^= 1; //TODO: in PC this is always the same as facing, just PE is stupid
}
return $result;
}
public function readStateFromData(int $id, int $stateMeta) : void{
if($stateMeta !== 0 and $stateMeta !== 1){
$stateMeta ^= 1;
}
$this->facing = BlockDataSerializer::readFacing($stateMeta);
}
public function getStateBitmask() : int{
return 0b111;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->facing = $face;
if($blockClicked instanceof EndRod and $blockClicked->facing === $this->facing){
$this->facing = Facing::opposite($face);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function isSolid() : bool{
return true;
}
public function getLightLevel() : int{
return 14;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$myAxis = Facing::axis($this->facing);
$bb = AxisAlignedBB::one();
foreach([Axis::Y, Axis::Z, Axis::X] as $axis){
if($axis === $myAxis){
continue;
}
$bb->squash($axis, 6 / 16);
}
return [$bb];
}
}

69
src/block/EnderChest.php Normal file
View File

@ -0,0 +1,69 @@
<?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\inventory\EnderChestInventory;
use pocketmine\block\tile\EnderChest as TileEnderChest;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\NormalHorizontalFacingInMetadataTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class EnderChest extends Transparent{
use FacesOppositePlacingPlayerTrait;
use NormalHorizontalFacingInMetadataTrait;
public function getLightLevel() : int{
return 7;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//these are slightly bigger than in PC
return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)];
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player instanceof Player){
$enderChest = $this->position->getWorld()->getTile($this->position);
if($enderChest instanceof TileEnderChest and $this->getSide(Facing::UP)->isTransparent()){
$enderChest->setViewerCount($enderChest->getViewerCount() + 1);
$player->setCurrentWindow(new EnderChestInventory($this->position, $player->getEnderInventory()));
}
}
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaBlocks::OBSIDIAN()->asItem()->setCount(8)
];
}
}

130
src/block/Farmland.php Normal file
View File

@ -0,0 +1,130 @@
<?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\utils\BlockDataSerializer;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use function lcg_value;
class Farmland extends Transparent{
protected int $wetness = 0; //"moisture" blockstate property in PC
protected function writeStateToMeta() : int{
return $this->wetness;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->wetness = BlockDataSerializer::readBoundedInt("wetness", $stateMeta, 0, 7);
}
public function getStateBitmask() : int{
return 0b111;
}
public function getWetness() : int{ return $this->wetness; }
/** @return $this */
public function setWetness(int $wetness) : self{
if($wetness < 0 || $wetness > 7){
throw new \InvalidArgumentException("Wetness must be in range 0-7");
}
$this->wetness = $wetness;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()]; //TODO: this should be trimmed at the top by 1/16, but MCPE currently treats them as a full block (https://bugs.mojang.com/browse/MCPE-12109)
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::UP)->isSolid()){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
}
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
if(!$this->canHydrate()){
if($this->wetness > 0){
$this->wetness--;
$this->position->getWorld()->setBlock($this->position, $this, false);
}else{
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
}
}elseif($this->wetness < 7){
$this->wetness = 7;
$this->position->getWorld()->setBlock($this->position, $this, false);
}
}
public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){
$this->getPosition()->getWorld()->setBlock($this->getPosition(), VanillaBlocks::DIRT());
}
}
return null;
}
protected function canHydrate() : bool{
//TODO: check rain
$start = $this->position->add(-4, 0, -4);
$end = $this->position->add(4, 1, 4);
for($y = $start->y; $y <= $end->y; ++$y){
for($z = $start->z; $z <= $end->z; ++$z){
for($x = $start->x; $x <= $end->x; ++$x){
if($this->position->getWorld()->getBlockAt($x, $y, $z) instanceof Water){
return true;
}
}
}
}
return false;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [
VanillaBlocks::DIRT()->asItem()
];
}
public function getPickedItem(bool $addUserData = false) : Item{
return VanillaBlocks::DIRT()->asItem();
}
}

96
src/block/Fence.php Normal file
View File

@ -0,0 +1,96 @@
<?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\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use function count;
class Fence extends Transparent{
/** @var bool[] facing => dummy */
protected array $connections = [];
public function getThickness() : float{
return 0.25;
}
public function readStateFromWorld() : void{
parent::readStateFromWorld();
foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing);
if($block instanceof static or $block instanceof FenceGate or ($block->isSolid() and !$block->isTransparent())){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);
}
}
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$inset = 0.5 - $this->getThickness() / 2;
/** @var AxisAlignedBB[] $bbs */
$bbs = [];
$connectWest = isset($this->connections[Facing::WEST]);
$connectEast = isset($this->connections[Facing::EAST]);
if($connectWest or $connectEast){
//X axis (west/east)
$bbs[] = AxisAlignedBB::one()
->squash(Axis::Z, $inset)
->extend(Facing::UP, 0.5)
->trim(Facing::WEST, $connectWest ? 0 : $inset)
->trim(Facing::EAST, $connectEast ? 0 : $inset);
}
$connectNorth = isset($this->connections[Facing::NORTH]);
$connectSouth = isset($this->connections[Facing::SOUTH]);
if($connectNorth or $connectSouth){
//Z axis (north/south)
$bbs[] = AxisAlignedBB::one()
->squash(Axis::X, $inset)
->extend(Facing::UP, 0.5)
->trim(Facing::NORTH, $connectNorth ? 0 : $inset)
->trim(Facing::SOUTH, $connectSouth ? 0 : $inset);
}
if(count($bbs) === 0){
//centre post AABB (only needed if not connected on any axis - other BBs overlapping will do this if any connections are made)
return [
AxisAlignedBB::one()
->extend(Facing::UP, 0.5)
->contract($inset, 0, $inset)
];
}
return $bbs;
}
}

131
src/block/FenceGate.php Normal file
View File

@ -0,0 +1,131 @@
<?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\utils\BlockDataSerializer;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\DoorSound;
class FenceGate extends Transparent{
use HorizontalFacingTrait;
protected bool $open = false;
protected bool $inWall = false;
protected function writeStateToMeta() : int{
return BlockDataSerializer::writeLegacyHorizontalFacing($this->facing) |
($this->open ? BlockLegacyMetadata::FENCE_GATE_FLAG_OPEN : 0) |
($this->inWall ? BlockLegacyMetadata::FENCE_GATE_FLAG_IN_WALL : 0);
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->facing = BlockDataSerializer::readLegacyHorizontalFacing($stateMeta & 0x03);
$this->open = ($stateMeta & BlockLegacyMetadata::FENCE_GATE_FLAG_OPEN) !== 0;
$this->inWall = ($stateMeta & BlockLegacyMetadata::FENCE_GATE_FLAG_IN_WALL) !== 0;
}
public function getStateBitmask() : int{
return 0b1111;
}
public function isOpen() : bool{ return $this->open; }
/** @return $this */
public function setOpen(bool $open) : self{
$this->open = $open;
return $this;
}
public function isInWall() : bool{ return $this->inWall; }
/** @return $this */
public function setInWall(bool $inWall) : self{
$this->inWall = $inWall;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return $this->open ? [] : [AxisAlignedBB::one()->extend(Facing::UP, 0.5)->squash(Facing::axis($this->facing), 6 / 16)];
}
private function checkInWall() : bool{
return (
$this->getSide(Facing::rotateY($this->facing, false)) instanceof Wall or
$this->getSide(Facing::rotateY($this->facing, true)) instanceof Wall
);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$this->facing = $player->getHorizontalFacing();
}
$this->inWall = $this->checkInWall();
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$inWall = $this->checkInWall();
if($inWall !== $this->inWall){
$this->inWall = $inWall;
$this->position->getWorld()->setBlock($this->position, $this);
}
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->open = !$this->open;
if($this->open and $player !== null){
$playerFacing = $player->getHorizontalFacing();
if($playerFacing === Facing::opposite($this->facing)){
$this->facing = $playerFacing;
}
}
$this->position->getWorld()->setBlock($this->position, $this);
$this->position->getWorld()->addSound($this->position, new DoorSound());
return true;
}
public function getFuelTime() : int{
return 300;
}
public function getFlameEncouragement() : int{
return 5;
}
public function getFlammability() : int{
return 20;
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockDataSerializer;
use pocketmine\entity\Entity;
use pocketmine\entity\projectile\Arrow;
use pocketmine\event\block\BlockBurnEvent;
@ -30,50 +31,62 @@ use pocketmine\event\entity\EntityCombustByBlockEvent;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\math\Facing;
use function min;
use function mt_rand;
class Fire extends Flowable{
protected $id = self::FIRE;
protected int $age = 0;
public function __construct(int $meta = 0){
$this->meta = $meta;
protected function writeStateToMeta() : int{
return $this->age;
}
public function readStateFromData(int $id, int $stateMeta) : void{
$this->age = BlockDataSerializer::readBoundedInt("age", $stateMeta, 0, 15);
}
public function getStateBitmask() : int{
return 0b1111;
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > 15){
throw new \InvalidArgumentException("Age must be in range 0-15");
}
$this->age = $age;
return $this;
}
public function hasEntityCollision() : bool{
return true;
}
public function getName() : string{
return "Fire Block";
}
public function getLightLevel() : int{
return 15;
}
public function isBreakable(Item $item) : bool{
return false;
}
public function canBeReplaced() : bool{
return true;
}
public function onEntityCollide(Entity $entity) : void{
public function onEntityInside(Entity $entity) : bool{
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev);
$ev = new EntityCombustByBlockEvent($this, $entity, 8);
if($entity instanceof Arrow){
$ev->setCancelled();
$ev->cancel();
}
$ev->call();
if(!$ev->isCancelled()){
$entity->setOnFire($ev->getDuration());
}
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
@ -81,10 +94,10 @@ class Fire extends Flowable{
}
public function onNearbyBlockChange() : void{
if(!$this->getSide(Vector3::SIDE_DOWN)->isSolid() and !$this->hasAdjacentFlammableBlocks()){
$this->getLevelNonNull()->setBlock($this, BlockFactory::get(Block::AIR), true);
if(!$this->getSide(Facing::DOWN)->isSolid() and !$this->hasAdjacentFlammableBlocks()){
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
}else{
$this->level->scheduleDelayedBlockUpdate($this, mt_rand(30, 40));
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
}
}
@ -93,35 +106,35 @@ class Fire extends Flowable{
}
public function onRandomTick() : void{
$down = $this->getSide(Vector3::SIDE_DOWN);
$down = $this->getSide(Facing::DOWN);
$result = null;
if($this->meta < 15 and mt_rand(0, 2) === 0){
$this->meta++;
if($this->age < 15 and mt_rand(0, 2) === 0){
$this->age++;
$result = $this;
}
$canSpread = true;
if(!$down->burnsForever()){
//TODO: check rain
if($this->meta === 15){
if($this->age === 15){
if(!$down->isFlammable() and mt_rand(0, 3) === 3){ //1/4 chance to extinguish
$canSpread = false;
$result = BlockFactory::get(Block::AIR);
$result = VanillaBlocks::AIR();
}
}elseif(!$this->hasAdjacentFlammableBlocks()){
$canSpread = false;
if(!$down->isSolid() or $this->meta > 3){ //fire older than 3, or without a solid block below
$result = BlockFactory::get(Block::AIR);
if(!$down->isSolid() or $this->age > 3){
$result = VanillaBlocks::AIR();
}
}
}
if($result !== null){
$this->level->setBlock($this, $result);
$this->position->getWorld()->setBlock($this->position, $result);
}
$this->level->scheduleDelayedBlockUpdate($this, mt_rand(30, 40));
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
if($canSpread){
//TODO: raise upper bound for chance in humid biomes
@ -131,8 +144,8 @@ class Fire extends Flowable{
}
//vanilla uses a 250 upper bound here, but I don't think they intended to increase the chance of incineration
$this->burnBlock($this->getSide(Vector3::SIDE_UP), 350);
$this->burnBlock($this->getSide(Vector3::SIDE_DOWN), 350);
$this->burnBlock($this->getSide(Facing::UP), 350);
$this->burnBlock($this->getSide(Facing::DOWN), 350);
//TODO: fire spread
}
@ -143,8 +156,8 @@ class Fire extends Flowable{
}
private function hasAdjacentFlammableBlocks() : bool{
for($i = 0; $i <= 5; ++$i){
if($this->getSide($i)->isFlammable()){
foreach(Facing::ALL as $face){
if($this->getSide($face)->isFlammable()){
return true;
}
}
@ -159,10 +172,12 @@ class Fire extends Flowable{
if(!$ev->isCancelled()){
$block->onIncinerate();
if(mt_rand(0, $this->meta + 9) < 5){ //TODO: check rain
$this->level->setBlock($block, BlockFactory::get(Block::FIRE, min(15, $this->meta + (mt_rand(0, 4) >> 2))));
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
$fire = clone $this;
$fire->age = min(15, $fire->age + (mt_rand(0, 4) >> 2));
$this->position->getWorld()->setBlock($block->position, $fire);
}else{
$this->level->setBlock($block, BlockFactory::get(Block::AIR));
$this->position->getWorld()->setBlock($block->position, VanillaBlocks::AIR());
}
}
}

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;
}
}

62
src/block/FloorBanner.php Normal file
View File

@ -0,0 +1,62 @@
<?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\utils\SignLikeRotationTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class FloorBanner extends BaseBanner{
use SignLikeRotationTrait;
public function readStateFromData(int $id, int $stateMeta) : void{
$this->rotation = $stateMeta;
}
protected function writeStateToMeta() : int{
return $this->rotation;
}
public function getStateBitmask() : int{
return 0b1111;
}
protected function getSupportingFace() : int{
return Facing::DOWN;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face !== Facing::UP){
return false;
}
if($player !== null){
$this->rotation = self::getRotationFromYaw($player->getLocation()->getYaw());
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
}

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