mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 11:16:57 +00:00
Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
785dc71256 | |||
d459afaa54 | |||
db586233da | |||
23e98a30f5 | |||
5bc7ca6569 | |||
f39d2a9be3 | |||
47d98af6ac | |||
82ba7903c8 | |||
112974758e | |||
313850eec4 | |||
a82923acb4 | |||
67887afd6b | |||
3d03bb1301 | |||
c063198b89 | |||
f3ca6de1eb | |||
88eafdd614 | |||
6dd5fec4ea | |||
6866c86d39 | |||
a735a69870 | |||
a0ea74c08f | |||
ca4b8a5827 | |||
f88c4d9a8c | |||
66cd156d80 | |||
222049927a | |||
d72e947d15 | |||
770cca2efa | |||
033dac3d16 | |||
1ee02d7e02 | |||
85678aa356 | |||
1d253bc8c2 | |||
973a56ab28 | |||
9e0b4621be | |||
b7a15b6e01 | |||
456439566b | |||
fb25e05416 | |||
78b5be8dd0 | |||
0a92e91a30 | |||
b3a13a2f21 | |||
08b9495bce | |||
5779622235 | |||
7f175b47e6 | |||
0e73ffe555 | |||
1ffd38b37b | |||
bd13f39156 | |||
0c446c276c | |||
0284e65f60 | |||
b0d787b3d3 | |||
65e3ed43d5 | |||
75eba9c9ed | |||
b5a049d1fe | |||
bd9fcffe62 | |||
feffbc2c5b | |||
53b51c99b4 | |||
5cb77c8365 | |||
bf8befc40b | |||
f75ca312cc | |||
d144832928 | |||
709a869045 | |||
ac056044ce | |||
fc8434308b | |||
5426b41447 | |||
af2babec23 | |||
717ab1989a | |||
83db186b6a | |||
6a4e5aba8b | |||
c13170a00b | |||
98778052bb | |||
e86e8254a8 | |||
1b852ac290 | |||
10b799fadb | |||
bc5008334a | |||
575dd47db7 | |||
e4a5defabb | |||
c9626c610b | |||
8fb7fff6b9 | |||
5c8d8ff61f | |||
99b55f7427 | |||
dce8bd6d21 | |||
8fa81242d6 | |||
2f4a9469b6 | |||
c5b2488fc1 | |||
d62df585f2 | |||
19d7c2b552 | |||
036e06e889 | |||
9343a0b800 | |||
14b4644b03 | |||
464b65b25c | |||
15586ed80e | |||
0f8ad8ecf7 | |||
82b9afef77 | |||
2fc84f6c67 | |||
566f5935a3 | |||
44e4dabf6e | |||
8acc535218 | |||
e9a1cb7ce5 | |||
a21419d120 | |||
c2b599166c | |||
df7a1fcba6 | |||
d77a95e4af | |||
5c72807b16 | |||
5c6927e16c | |||
9abbb85a93 | |||
554182b2cb | |||
d669a6f0c7 | |||
5d9f783037 | |||
01ca14c314 | |||
608c6ed6db | |||
c26631d06d | |||
b75bc61a64 | |||
3724479be3 | |||
eb916fe43d | |||
5e3b3a0700 | |||
e10a624444 | |||
b20e04539d | |||
4852f8029a | |||
c4f85e526b | |||
6cee428287 | |||
bcba064d69 | |||
86647683bc | |||
64f0e58e60 | |||
62f21516d1 | |||
c553f7cf06 | |||
fec89b7803 | |||
2b61c025c2 | |||
c7133bc2e6 | |||
baf75089f5 | |||
705df7d508 | |||
75d7adfb2d | |||
9d535e2917 | |||
3ccd288afd | |||
06655bee78 | |||
0ad2985247 | |||
269b6ed16a | |||
f031c3c602 | |||
f3e09dd7d5 | |||
68e704bf97 | |||
9898577135 | |||
784d602600 | |||
15c99cfe77 | |||
d5fa0a2fc5 | |||
0da9260994 | |||
df2d1fd4f9 | |||
9f65fb5f90 | |||
caa4b78a3f | |||
14352a05bc | |||
bb5b52d998 | |||
5e22b70b6d | |||
02513818a9 | |||
d641812c52 | |||
a851496293 | |||
01a8bce2dd | |||
becbd562d6 | |||
82edb20e0c | |||
64a8c462f9 | |||
4ec97d0f7a | |||
016a80bb70 | |||
ce66a400a7 | |||
50776875bb |
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v3.0.0
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
100
.github/workflows/discord-release-embed.php
vendored
Normal file
100
.github/workflows/discord-release-embed.php
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\utils\Internet;
|
||||
use function dirname;
|
||||
use function fwrite;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const STDERR;
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* @phpstan-return array<string, mixed>
|
||||
*/
|
||||
function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl) : array{
|
||||
return [
|
||||
"embeds" => [
|
||||
[
|
||||
"title" => "New PocketMine-MP release: $version ($channel)",
|
||||
"description" => <<<DESCRIPTION
|
||||
$description
|
||||
|
||||
[Details]($detailsUrl) | [Source Code]($sourceUrl) | [Build Log]($buildLogUrl) | [Download]($pharDownloadUrl)
|
||||
DESCRIPTION,
|
||||
"url" => $detailsUrl,
|
||||
"color" => $channel === "stable" ? 0x57ab5a : 0xc69026
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if(count($argv) !== 5){
|
||||
fwrite(STDERR, "Required arguments: github repo, version, API token\n");
|
||||
exit(1);
|
||||
}
|
||||
[, $repo, $tagName, $token, $hookURL] = $argv;
|
||||
|
||||
$result = Internet::getURL('https://api.github.com/repos/' . $repo . '/releases/tags/' . $tagName, extraHeaders: [
|
||||
'Authorization: token ' . $token
|
||||
]);
|
||||
if($result === null){
|
||||
fwrite(STDERR, "failed to access GitHub API\n");
|
||||
return;
|
||||
}
|
||||
if($result->getCode() !== 200){
|
||||
fwrite(STDERR, "Error accessing GitHub API: " . $result->getCode() . "\n");
|
||||
fwrite(STDERR, $result->getBody() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$releaseInfoJson = json_decode($result->getBody(), true, JSON_THROW_ON_ERROR);
|
||||
if(!is_array($releaseInfoJson)){
|
||||
fwrite(STDERR, "Invalid release JSON returned from GitHub API\n");
|
||||
exit(1);
|
||||
}
|
||||
$buildInfoPath = 'https://github.com/' . $repo . '/releases/download/' . $tagName . '/build_info.json';
|
||||
|
||||
$buildInfoResult = Internet::getURL($buildInfoPath, extraHeaders: [
|
||||
'Authorization: token ' . $token
|
||||
]);
|
||||
if($buildInfoResult === null){
|
||||
fwrite(STDERR, "missing build_info.json\n");
|
||||
exit(1);
|
||||
}
|
||||
if($buildInfoResult->getCode() !== 200){
|
||||
fwrite(STDERR, "error accessing build_info.json: " . $buildInfoResult->getCode() . "\n");
|
||||
fwrite(STDERR, $buildInfoResult->getBody() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$buildInfoJson = json_decode($buildInfoResult->getBody(), true, JSON_THROW_ON_ERROR);
|
||||
if(!is_array($buildInfoJson)){
|
||||
fwrite(STDERR, "invalid build_info.json\n");
|
||||
exit(1);
|
||||
}
|
||||
$detailsUrl = $buildInfoJson["details_url"];
|
||||
$sourceUrl = $buildInfoJson["source_url"];
|
||||
$pharDownloadUrl = $buildInfoJson["download_url"];
|
||||
$buildLogUrl = $buildInfoJson["build_log_url"];
|
||||
|
||||
$description = $releaseInfoJson["body"];
|
||||
|
||||
$discordPayload = generateDiscordEmbed($buildInfoJson["base_version"], $buildInfoJson["channel"], $description, $detailsUrl, $sourceUrl, $pharDownloadUrl, $buildLogUrl);
|
||||
|
||||
$response = Internet::postURL(
|
||||
$hookURL,
|
||||
json_encode($discordPayload, JSON_THROW_ON_ERROR),
|
||||
extraHeaders: ['Content-Type: application/json']
|
||||
);
|
||||
if($response?->getCode() !== 204){
|
||||
fwrite(STDERR, "failed to send Discord webhook\n");
|
||||
fwrite(STDERR, $response?->getBody() ?? "no response body\n");
|
||||
exit(1);
|
||||
}
|
38
.github/workflows/discord-release-notify.yml
vendored
Normal file
38
.github/workflows/discord-release-notify.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Notify Discord webhook of release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
with:
|
||||
php-version: 8.0
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
|
||||
|
||||
- name: Get actual tag name
|
||||
id: tag-name
|
||||
run: echo ::set-output name=TAG_NAME::$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{')
|
||||
|
||||
- name: Run webhook post script
|
||||
run: php .github/workflows/discord-release-embed.php ${{ github.repository }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
6
.github/workflows/draft-release.yml
vendored
6
.github/workflows/draft-release.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.19.0
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
with:
|
||||
php-version: 8.0
|
||||
|
||||
@ -57,7 +57,7 @@ jobs:
|
||||
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
|
||||
|
||||
- name: Generate build info
|
||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json
|
||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} ${{ github.run_id }} > build_info.json
|
||||
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
@ -80,4 +80,4 @@ jobs:
|
||||
body: |
|
||||
**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.
|
||||
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
|
||||
|
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@ -13,11 +13,11 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.18]
|
||||
php: [8.0.23, 8.1.10]
|
||||
|
||||
steps:
|
||||
- name: Build and prepare PHP cache
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
@ -31,13 +31,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.18]
|
||||
php: [8.0.23, 8.1.10]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
@ -69,13 +69,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.18]
|
||||
php: [8.0.23, 8.1.10]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
@ -107,7 +107,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.18]
|
||||
php: [8.0.23, 8.1.10]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -115,7 +115,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
@ -147,13 +147,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.18]
|
||||
php: [8.0.23, 8.1.10]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
@ -195,10 +195,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.19.0
|
||||
uses: shivammathur/setup-php@2.21.2
|
||||
with:
|
||||
php-version: 8.0
|
||||
tools: php-cs-fixer:3.2
|
||||
tools: php-cs-fixer:3.11
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: php-cs-fixer fix --dry-run --diff --ansi
|
||||
|
5
.github/workflows/support.yml
vendored
5
.github/workflows/support.yml
vendored
@ -16,9 +16,10 @@ jobs:
|
||||
Hi, we only accept **bug reports** on this issue tracker, but this issue looks like a support request.
|
||||
|
||||
|
||||
Instead of creating a bug report, try the following:
|
||||
|
||||
Instead of creating an issue, try the following:
|
||||
|
||||
- Check our [Documentation](https://doc.pmmp.io) to see if you can find answers there
|
||||
|
||||
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG) or our [Forums](https://forums.pmmp.io)
|
||||
|
||||
|
||||
|
3
.github/workflows/update-php-versions.php
vendored
3
.github/workflows/update-php-versions.php
vendored
@ -22,7 +22,8 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
const VERSIONS = [
|
||||
"8.0"
|
||||
"8.0",
|
||||
"8.1"
|
||||
];
|
||||
|
||||
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
|
||||
|
@ -70,6 +70,10 @@ BODY,
|
||||
'scope' => 'namespaced',
|
||||
'include' => ['@all'],
|
||||
],
|
||||
'new_with_braces' => [
|
||||
'named_class' => true,
|
||||
'anonymous_class' => false,
|
||||
],
|
||||
'no_closing_tag' => true,
|
||||
'no_empty_phpdoc' => true,
|
||||
'no_extra_blank_lines' => true,
|
||||
|
@ -7,10 +7,11 @@ GitHub is public and anyone can see the issues you post on the issue tracker, in
|
||||
|
||||
**WARNING: You may put live servers at risk by reporting a vulnerability on the GitHub issue tracker.**
|
||||
|
||||
**Contact us** by sending an email to [**team@pmmp.io**](mailto:team@pmmp.io?subject=Security%20Vulnerability%20in%20PocketMine-MP). Include the following information:
|
||||
**Contact us** by sending an email to [**security@pmmp.io**](mailto:security@pmmp.io). Include the following information:
|
||||
|
||||
- Version of PocketMine-MP
|
||||
- Detailed description of the vulnerability (e.g. how to exploit it, what the effects are)
|
||||
- Your GitHub username, if you wish to be credited for reporting the problem in the security advisory
|
||||
|
||||
Please note that we can't guarantee a reply to every email.
|
||||
|
||||
|
@ -23,8 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
if(count($argv) !== 5){
|
||||
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number>");
|
||||
if(count($argv) !== 6){
|
||||
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number> <github actions run ID>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -40,4 +40,5 @@ echo json_encode([
|
||||
"details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]",
|
||||
"download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar",
|
||||
"source_url" => "https://github.com/$argv[3]/tree/$argv[2]",
|
||||
"build_log_url" => "https://github.com/$argv[3]/actions/runs/$argv[5]",
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
|
||||
|
@ -29,7 +29,9 @@ use function count;
|
||||
use function dirname;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function fwrite;
|
||||
use function implode;
|
||||
use function is_dir;
|
||||
use function ksort;
|
||||
use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
@ -39,7 +41,8 @@ use function substr;
|
||||
use const SORT_STRING;
|
||||
|
||||
if(count($argv) !== 2){
|
||||
die("Provide a path to process");
|
||||
fwrite(STDERR, "Provide a path to process\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,30 +83,24 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
/** @var string $file */
|
||||
foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
|
||||
if(substr($file, -4) !== ".php"){
|
||||
continue;
|
||||
}
|
||||
function processFile(string $file) : void{
|
||||
$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;
|
||||
return;
|
||||
}
|
||||
$shortClassName = basename($file, ".php");
|
||||
$className = $matches[1] . "\\" . $shortClassName;
|
||||
if(!class_exists($className)){
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
$reflect = new \ReflectionClass($className);
|
||||
$docComment = $reflect->getDocComment();
|
||||
if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
echo "Found registry in $file\n";
|
||||
|
||||
@ -117,3 +114,18 @@ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1],
|
||||
echo "No changes made to file $file\n";
|
||||
}
|
||||
}
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
if(is_dir($argv[1])){
|
||||
/** @var string $file */
|
||||
foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
|
||||
if(substr($file, -4) !== ".php"){
|
||||
continue;
|
||||
}
|
||||
|
||||
processFile($file);
|
||||
}
|
||||
}else{
|
||||
processFile($argv[1]);
|
||||
}
|
||||
|
Submodule build/php updated: 11103498ca...cf79c01722
28
changelogs/4.5.md
Normal file
28
changelogs/4.5.md
Normal file
@ -0,0 +1,28 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.0**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.5.0
|
||||
Released 7th June 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.0.
|
||||
- Removed support for older versions.
|
||||
|
||||
# 4.5.1
|
||||
Released 8th June 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed commands defined in `pocketmine.yml` `aliases` not passing the correct arguments.
|
||||
- Updated BedrockProtocol to fix command argument types displayed on client-side command suggestions.
|
||||
|
||||
# 4.5.2
|
||||
Released 29th June 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed terrain getting redrawn when flying in spectator mode (or when using `Player->setHasBlockCollision(false)`).
|
||||
- Fixed skulls with the `noDrops` flag set being treated as unknown blocks.
|
42
changelogs/4.6.md
Normal file
42
changelogs/4.6.md
Normal file
@ -0,0 +1,42 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.10**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.6.0
|
||||
Released 13th July 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.10.
|
||||
- Removed support for older versions.
|
||||
|
||||
# 4.6.1
|
||||
Released 22nd July 2022.
|
||||
|
||||
## Tools
|
||||
- `build/generate-registry-annotations.php` now supports processing single files (useful for PhpStorm file watchers).
|
||||
|
||||
## API
|
||||
- Updated documentation for `AsyncTask`.
|
||||
|
||||
## Fixes
|
||||
- Fixed incorrect items being displayed in item frames.
|
||||
- Fixed books not showing in lecterns.
|
||||
- Fixed incorrect damage interval of Wither status effect.
|
||||
- Fixed incorrect fire ticks when being set on fire by lava (8 seconds in Bedrock instead of 15).
|
||||
- `Entity->attack()` now cancels damage from `FIRE` and `FIRE_TICK` damage causes if the entity is fireproof.
|
||||
- Fixed inventory windows getting force-closed when the client attempts to use an enchanting table or anvil.
|
||||
|
||||
# 4.6.2
|
||||
Released 6th August 2022.
|
||||
|
||||
## Core
|
||||
- Improved server-side performance of `PlayerAuthInputPacket` handler.
|
||||
- Improved client-side performance of `FloatingTextParticle` by using an invisible falling block entity. This offered a roughly 5x performance improvement over using tiny invisible players in local testing.
|
||||
|
||||
## Fixes
|
||||
- Fixed assert failures and debug spam on debug Minecraft clients related to abilities in `AddPlayerPacket`.
|
||||
- Fixed crash in `ReversePriorityQueue` on PHP 8.1 by adding `#[ReturnTypeWillChange]` attribute.
|
46
changelogs/4.7.md
Normal file
46
changelogs/4.7.md
Normal file
@ -0,0 +1,46 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.20**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.7.0
|
||||
Released 9th August 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.20.
|
||||
- Removed support for older versions.
|
||||
|
||||
# 4.7.1
|
||||
Released 14th August 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed server crash when loading items from disk which have negative meta values.
|
||||
- Fixed Turtle Master potions not giving any effects.
|
||||
- Unimplemented items are no longer craftable.
|
||||
- Fixed incorrect items appearing in item frames (due to an obsolete workaround for 1.19.10).
|
||||
|
||||
# 4.7.2
|
||||
Released 16th August 2022.
|
||||
|
||||
## Fixes
|
||||
- Fixed crash when processing player skins with invalid geometry data.
|
||||
- Fixed spectator players being able to pick blocks using mousewheel click.
|
||||
- Improved supporting requirements for sugarcane.
|
||||
|
||||
# 4.7.3
|
||||
Released 22nd August 2022.
|
||||
|
||||
## General
|
||||
- Added complete translations for Spanish and Vietnamese.
|
||||
- All continuous integration (static analysis, unit tests, integration tests) are now performed on PHP 8.1 as well as 8.0.
|
||||
- InventoryTransaction now verifies that stack sizes of items after the transaction don't exceed the maximum stack size of the item type or the containing inventory.
|
||||
|
||||
## Fixes
|
||||
- Fixed Normal generator crash on PHP 8.1.
|
||||
- Fixed a race condition during async worker shutdown that could lead to tasks executing in the wrong order. This (very rarely) led to a crash in `PopulationTask` due to its preceding `GeneratorRegisterTask` not being executed.
|
||||
- Fixed `/give` accepting negative amounts or amounts larger than 32767 (vanilla max).
|
||||
- Fixed placement conditions for vines (no longer able to be placed on the side of cacti).
|
||||
- Fixed incorrect documentation of `SignText::__construct()`.
|
23
changelogs/4.8.md
Normal file
23
changelogs/4.8.md
Normal file
@ -0,0 +1,23 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.21**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.8.0
|
||||
Released 24th August 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.21.
|
||||
- Removed support for older versions.
|
||||
|
||||
# 4.8.1
|
||||
Released 26th August 2022.
|
||||
|
||||
## General
|
||||
- Crashdumps now include JIT mode information for use by the Crash Archive.
|
||||
|
||||
## Fixes
|
||||
- Fixed uninitialized offset error in `DyeColorIdMap` when given invalid dye color IDs.
|
14
changelogs/4.9.md
Normal file
14
changelogs/4.9.md
Normal file
@ -0,0 +1,14 @@
|
||||
**For Minecraft: Bedrock Edition 1.19.30**
|
||||
|
||||
### Note about API versions
|
||||
Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps.
|
||||
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||
|
||||
**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do.
|
||||
|
||||
# 4.9.0
|
||||
Released 20th September 2022.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.19.30.
|
||||
- Removed support for older versions.
|
@ -34,14 +34,14 @@
|
||||
"adhocore/json-comment": "^1.1",
|
||||
"fgrosse/phpasn1": "^2.3",
|
||||
"netresearch/jsonmapper": "^4.0",
|
||||
"pocketmine/bedrock-data": "~1.7.0+bedrock-1.18.30",
|
||||
"pocketmine/bedrock-protocol": "~9.0.0+bedrock-1.18.30",
|
||||
"pocketmine/bedrock-data": "~1.11.0+bedrock-1.19.30",
|
||||
"pocketmine/bedrock-protocol": "~13.0.0+bedrock-1.19.30",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/classloader": "^0.2.0",
|
||||
"pocketmine/color": "^0.2.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/locale-data": "~2.8.0",
|
||||
"pocketmine/locale-data": "~2.8.0 <2.8.9",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.4.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
@ -53,7 +53,7 @@
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.7.8",
|
||||
"phpstan/phpstan": "1.8.5",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
|
586
composer.lock
generated
586
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
includes:
|
||||
- tests/phpstan/analyse-for-current-php-version.neon.php
|
||||
- tests/phpstan/configs/actual-problems.neon
|
||||
- tests/phpstan/configs/gc-hacks.neon
|
||||
- tests/phpstan/configs/impossible-generics.neon
|
||||
|
@ -37,6 +37,7 @@ namespace pocketmine {
|
||||
use Webmozart\PathUtil\Path;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function function_exists;
|
||||
use function getcwd;
|
||||
use function phpversion;
|
||||
use function preg_match;
|
||||
@ -160,7 +161,7 @@ namespace pocketmine {
|
||||
if(PHP_DEBUG !== 0){
|
||||
$logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
|
||||
}
|
||||
if(extension_loaded("xdebug")){
|
||||
if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
|
||||
$logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
|
||||
}
|
||||
if(((int) ini_get('zend.assertions')) !== -1){
|
||||
@ -176,10 +177,10 @@ namespace pocketmine {
|
||||
|
||||
|
||||
--------------------------------------- ! WARNING ! ---------------------------------------
|
||||
You're using PHP 8.0 with JIT enabled. This provides significant performance improvements.
|
||||
You're using PHP with JIT enabled. This provides significant performance improvements.
|
||||
HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs.
|
||||
Proceed with caution.
|
||||
If you want to report any bugs, make sure to mention that you are using PHP 8.0 with JIT.
|
||||
If you want to report any bugs, make sure to mention that you have enabled PHP JIT.
|
||||
To turn off JIT, change `opcache.jit` to `0` in your php.ini file.
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -132,6 +132,7 @@ use function get_class;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
use function is_int;
|
||||
use function is_object;
|
||||
use function is_resource;
|
||||
use function is_string;
|
||||
@ -570,6 +571,7 @@ class Server{
|
||||
$playerPos = null;
|
||||
$spawn = $world->getSpawnLocation();
|
||||
}
|
||||
/** @phpstan-var PromiseResolver<Player> $playerPromiseResolver */
|
||||
$playerPromiseResolver = new PromiseResolver();
|
||||
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
|
||||
@ -1650,12 +1652,14 @@ class Server{
|
||||
], 10, [], $postUrlError);
|
||||
|
||||
if($reply !== null && is_object($data = json_decode($reply->getBody()))){
|
||||
if(isset($data->crashId) && isset($data->crashUrl)){
|
||||
if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){
|
||||
$reportId = $data->crashId;
|
||||
$reportUrl = $data->crashUrl;
|
||||
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
|
||||
}elseif(isset($data->error)){
|
||||
}elseif(isset($data->error) && is_string($data->error)){
|
||||
$this->logger->emergency("Automatic crash report submission failed: $data->error");
|
||||
}else{
|
||||
$this->logger->emergency("Invalid JSON response received from crash archive: " . $reply->getBody());
|
||||
}
|
||||
}else{
|
||||
$this->logger->emergency("Failed to communicate with crash archive: $postUrlError");
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.4.2";
|
||||
public const BASE_VERSION = "4.9.0";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -523,12 +523,6 @@ class BlockFactory{
|
||||
$this->registerAllMeta(...$leaves);
|
||||
$this->registerAllMeta(...$allSidedLogs);
|
||||
|
||||
static $sandstoneTypes = [
|
||||
Meta::SANDSTONE_NORMAL => "",
|
||||
Meta::SANDSTONE_CHISELED => "Chiseled ",
|
||||
Meta::SANDSTONE_CUT => "Cut ",
|
||||
Meta::SANDSTONE_SMOOTH => "Smooth "
|
||||
];
|
||||
$sandstoneBreakInfo = new BreakInfo(0.8, ToolType::PICKAXE, ToolTier::WOOD()->getHarvestLevel());
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::RED_SANDSTONE_STAIRS, 0), "Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_RED_SANDSTONE_STAIRS, 0), "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
@ -536,7 +530,12 @@ class BlockFactory{
|
||||
$this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS, 0), "Smooth Sandstone Stairs", $sandstoneBreakInfo));
|
||||
$sandstones = [];
|
||||
$redSandstones = [];
|
||||
foreach($sandstoneTypes as $variant => $prefix){
|
||||
foreach([
|
||||
Meta::SANDSTONE_NORMAL => "",
|
||||
Meta::SANDSTONE_CHISELED => "Chiseled ",
|
||||
Meta::SANDSTONE_CUT => "Cut ",
|
||||
Meta::SANDSTONE_SMOOTH => "Smooth "
|
||||
] as $variant => $prefix){
|
||||
$sandstones[] = new Opaque(new BID(Ids::SANDSTONE, $variant), $prefix . "Sandstone", $sandstoneBreakInfo);
|
||||
$redSandstones[] = new Opaque(new BID(Ids::RED_SANDSTONE, $variant), $prefix . "Red Sandstone", $sandstoneBreakInfo);
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class Cactus extends Transparent{
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
static $shrinkSize = 1 / 16;
|
||||
$shrinkSize = 1 / 16;
|
||||
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,8 @@ class Lava extends Liquid{
|
||||
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_LAVA, 4);
|
||||
$entity->attack($ev);
|
||||
|
||||
$ev = new EntityCombustByBlockEvent($this, $entity, 15);
|
||||
//in java burns entities for 15 seconds - seems to be a parity issue in bedrock
|
||||
$ev = new EntityCombustByBlockEvent($this, $entity, 8);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$entity->setOnFire($ev->getDuration());
|
||||
|
@ -69,7 +69,6 @@ class Lever extends Flowable{
|
||||
5 => LeverFacing::UP_AXIS_Z(),
|
||||
6 => LeverFacing::UP_AXIS_X(),
|
||||
7 => LeverFacing::DOWN_AXIS_Z(),
|
||||
default => throw new AssumptionFailedError("0x07 mask should make this impossible"), //phpstan doesn't understand :(
|
||||
};
|
||||
|
||||
$this->activated = ($stateMeta & BlockLegacyMetadata::LEVER_FLAG_POWERED) !== 0;
|
||||
|
@ -56,7 +56,8 @@ class Skull extends Flowable{
|
||||
}
|
||||
|
||||
public function readStateFromData(int $id, int $stateMeta) : void{
|
||||
$this->facing = $stateMeta === 1 ? Facing::UP : BlockDataSerializer::readHorizontalFacing($stateMeta);
|
||||
$facingMeta = $stateMeta & 0x7;
|
||||
$this->facing = $facingMeta === 1 ? Facing::UP : BlockDataSerializer::readHorizontalFacing($facingMeta);
|
||||
$this->noDrops = ($stateMeta & BlockLegacyMetadata::SKULL_FLAG_NO_DROPS) !== 0;
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ class Sugarcane extends Flowable{
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
if($down->isTransparent() && !$down->isSameType($this)){
|
||||
if(!$this->isValidSupport($down)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
@ -122,9 +122,10 @@ class Sugarcane extends Flowable{
|
||||
$down = $this->getSide(Facing::DOWN);
|
||||
if($down->isSameType($this)){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}elseif($down->getId() === BlockLegacyIds::GRASS || $down->getId() === BlockLegacyIds::DIRT || $down->getId() === BlockLegacyIds::SAND || $down->getId() === BlockLegacyIds::PODZOL){
|
||||
}elseif($this->isValidSupport($down)){
|
||||
foreach(Facing::HORIZONTAL as $side){
|
||||
if($down->getSide($side) instanceof Water){
|
||||
$sideBlock = $down->getSide($side);
|
||||
if($sideBlock instanceof Water || $sideBlock instanceof FrostedIce){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
}
|
||||
@ -132,4 +133,15 @@ class Sugarcane extends Flowable{
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isValidSupport(Block $block) : bool{
|
||||
$id = $block->getId();
|
||||
//TODO: rooted dirt, moss block
|
||||
return $block->isSameType($this)
|
||||
|| $id === BlockLegacyIds::GRASS
|
||||
|| $id === BlockLegacyIds::DIRT
|
||||
|| $id === BlockLegacyIds::PODZOL
|
||||
|| $id === BlockLegacyIds::MYCELIUM
|
||||
|| $id === BlockLegacyIds::SAND;
|
||||
}
|
||||
}
|
||||
|
@ -585,6 +585,7 @@ final class VanillaBlocks{
|
||||
|
||||
/**
|
||||
* @return Block[]
|
||||
* @phpstan-return array<string, Block>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@ -116,7 +116,7 @@ class Vine extends Flowable{
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$blockClicked->isSolid() || Facing::axis($face) === Axis::Y){
|
||||
if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block\utils;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\block\Fire;
|
||||
use pocketmine\block\Liquid;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\entity\object\FallingBlock;
|
||||
@ -50,7 +47,7 @@ trait FallableTrait{
|
||||
public function onNearbyBlockChange() : void{
|
||||
$pos = $this->getPosition();
|
||||
$down = $pos->getWorld()->getBlock($pos->getSide(Facing::DOWN));
|
||||
if($down->getId() === BlockLegacyIds::AIR || $down instanceof Liquid || $down instanceof Fire){
|
||||
if($down->canBeReplaced()){
|
||||
$pos->getWorld()->setBlock($pos, VanillaBlocks::AIR());
|
||||
|
||||
$block = $this;
|
||||
|
@ -39,7 +39,7 @@ class SignText{
|
||||
private array $lines;
|
||||
|
||||
/**
|
||||
* @param string[]|null $lines index-sensitive; omitting an index will leave it unchanged
|
||||
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
|
||||
*
|
||||
* @throws \InvalidArgumentException if the array size is greater than 4
|
||||
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
|
||||
|
@ -85,7 +85,7 @@ class FormattedCommandAlias extends Command{
|
||||
$target->timings->startTiming();
|
||||
|
||||
try{
|
||||
$target->execute($sender, $commandLabel, $args);
|
||||
$target->execute($sender, $commandLabel, $commandArgs);
|
||||
}catch(InvalidCommandSyntaxException $e){
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage())));
|
||||
}finally{
|
||||
|
@ -75,7 +75,11 @@ class GiveCommand extends VanillaCommand{
|
||||
if(!isset($args[2])){
|
||||
$item->setCount($item->getMaxStackSize());
|
||||
}else{
|
||||
$item->setCount((int) $args[2]);
|
||||
$count = $this->getBoundedInt($sender, $args[2], 1, 32767);
|
||||
if($count === null){
|
||||
return true;
|
||||
}
|
||||
$item->setCount($count);
|
||||
}
|
||||
|
||||
if(isset($args[3])){
|
||||
|
@ -32,9 +32,7 @@ use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function implode;
|
||||
use function opcache_get_status;
|
||||
use function sprintf;
|
||||
use function stripos;
|
||||
use function strtolower;
|
||||
@ -71,16 +69,10 @@ class VersionCommand extends VanillaCommand{
|
||||
));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(PHP_VERSION));
|
||||
|
||||
if(
|
||||
function_exists('opcache_get_status') &&
|
||||
($opcacheStatus = opcache_get_status(false)) !== false &&
|
||||
isset($opcacheStatus["jit"]["on"])
|
||||
){
|
||||
$jit = $opcacheStatus["jit"];
|
||||
if($jit["on"] === true){
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(
|
||||
sprintf("CRTO: %s%s%s%s", $jit["opt_flags"] >> 2, $jit["opt_flags"] & 0x03, $jit["kind"], $jit["opt_level"])
|
||||
);
|
||||
$jitMode = Utils::getOpcacheJitMode();
|
||||
if($jitMode !== null){
|
||||
if($jitMode !== 0){
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(sprintf("CRTO: %d", $jitMode));
|
||||
}else{
|
||||
$jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitDisabled();
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemFactory;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_map;
|
||||
@ -33,6 +34,23 @@ use function json_decode;
|
||||
|
||||
final class CraftingManagerFromDataHelper{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
*/
|
||||
private static function containsUnknownOutputs(array $items) : bool{
|
||||
$factory = ItemFactory::getInstance();
|
||||
foreach($items as $item){
|
||||
if($item->hasAnyDamageValue()){
|
||||
throw new \InvalidArgumentException("Recipe outputs must not have wildcard meta values");
|
||||
}
|
||||
if(!$factory->isRegistered($item->getId(), $item->getMeta())){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function make(string $filePath) : CraftingManager{
|
||||
$recipes = json_decode(Utils::assumeNotFalse(file_get_contents($filePath), "Missing required resource file"), true);
|
||||
if(!is_array($recipes)){
|
||||
@ -52,9 +70,13 @@ final class CraftingManagerFromDataHelper{
|
||||
if($recipeType === null){
|
||||
continue;
|
||||
}
|
||||
$output = array_map($itemDeserializerFunc, $recipe["output"]);
|
||||
if(self::containsUnknownOutputs($output)){
|
||||
continue;
|
||||
}
|
||||
$result->registerShapelessRecipe(new ShapelessRecipe(
|
||||
array_map($itemDeserializerFunc, $recipe["input"]),
|
||||
array_map($itemDeserializerFunc, $recipe["output"]),
|
||||
$output,
|
||||
$recipeType
|
||||
));
|
||||
}
|
||||
@ -62,10 +84,14 @@ final class CraftingManagerFromDataHelper{
|
||||
if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics
|
||||
continue;
|
||||
}
|
||||
$output = array_map($itemDeserializerFunc, $recipe["output"]);
|
||||
if(self::containsUnknownOutputs($output)){
|
||||
continue;
|
||||
}
|
||||
$result->registerShapedRecipe(new ShapedRecipe(
|
||||
$recipe["shape"],
|
||||
array_map($itemDeserializerFunc, $recipe["input"]),
|
||||
array_map($itemDeserializerFunc, $recipe["output"])
|
||||
$output
|
||||
));
|
||||
}
|
||||
foreach($recipes["smelting"] as $recipe){
|
||||
@ -79,19 +105,30 @@ final class CraftingManagerFromDataHelper{
|
||||
if($furnaceType === null){
|
||||
continue;
|
||||
}
|
||||
$output = Item::jsonDeserialize($recipe["output"]);
|
||||
if(self::containsUnknownOutputs([$output])){
|
||||
continue;
|
||||
}
|
||||
$result->getFurnaceRecipeManager($furnaceType)->register(new FurnaceRecipe(
|
||||
Item::jsonDeserialize($recipe["output"]),
|
||||
$output,
|
||||
Item::jsonDeserialize($recipe["input"]))
|
||||
);
|
||||
}
|
||||
foreach($recipes["potion_type"] as $recipe){
|
||||
$output = Item::jsonDeserialize($recipe["output"]);
|
||||
if(self::containsUnknownOutputs([$output])){
|
||||
continue;
|
||||
}
|
||||
$result->registerPotionTypeRecipe(new PotionTypeRecipe(
|
||||
Item::jsonDeserialize($recipe["input"]),
|
||||
Item::jsonDeserialize($recipe["ingredient"]),
|
||||
Item::jsonDeserialize($recipe["output"])
|
||||
$output
|
||||
));
|
||||
}
|
||||
foreach($recipes["potion_container_change"] as $recipe){
|
||||
if(!ItemFactory::getInstance()->isRegistered($recipe["output_item_id"])){
|
||||
continue;
|
||||
}
|
||||
$result->registerPotionContainerChangeRecipe(new PotionContainerChangeRecipe(
|
||||
$recipe["input_item_id"],
|
||||
Item::jsonDeserialize($recipe["ingredient"]),
|
||||
|
@ -164,6 +164,8 @@ class CrashDump{
|
||||
}
|
||||
$this->data->extensions = $extensions;
|
||||
|
||||
$this->data->jit_mode = Utils::getOpcacheJitMode();
|
||||
|
||||
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
|
||||
ob_start();
|
||||
phpinfo();
|
||||
|
@ -66,6 +66,8 @@ final class CrashDumpData implements \JsonSerializable{
|
||||
*/
|
||||
public array $extensions = [];
|
||||
|
||||
public ?int $jit_mode = null;
|
||||
|
||||
public string $phpinfo = "";
|
||||
|
||||
public CrashDumpDataGeneral $general;
|
||||
|
@ -74,7 +74,7 @@ final class DyeColorIdMap{
|
||||
}
|
||||
|
||||
public function fromId(int $id) : ?DyeColor{
|
||||
return $this->idToEnum[$id];
|
||||
return $this->idToEnum[$id] ?? null;
|
||||
}
|
||||
|
||||
public function fromInvertedId(int $id) : ?DyeColor{
|
||||
|
@ -522,6 +522,9 @@ abstract class Entity{
|
||||
}
|
||||
|
||||
public function attack(EntityDamageEvent $source) : void{
|
||||
if($this->isFireProof() && ($source->getCause() === EntityDamageEvent::CAUSE_FIRE || $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK)){
|
||||
$source->cancel();
|
||||
}
|
||||
$source->call();
|
||||
if($source->isCancelled()){
|
||||
return;
|
||||
@ -1461,8 +1464,9 @@ abstract class Entity{
|
||||
$this->location->pitch,
|
||||
$this->location->yaw,
|
||||
$this->location->yaw, //TODO: head yaw
|
||||
$this->location->yaw, //TODO: body yaw (wtf mojang?)
|
||||
array_map(function(Attribute $attr) : NetworkAttribute{
|
||||
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
|
||||
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
|
||||
}, $this->attributeMap->getAll()),
|
||||
$this->getAllNetworkData(),
|
||||
[] //TODO: entity links
|
||||
|
@ -227,7 +227,7 @@ final class EntityFactory{
|
||||
*/
|
||||
public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
|
||||
try{
|
||||
$saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier");
|
||||
$saveId = $nbt->getTag("identifier") ?? $nbt->getTag("id");
|
||||
$func = null;
|
||||
if($saveId instanceof StringTag){
|
||||
$func = $this->creationFuncs[$saveId->getValue()] ?? null;
|
||||
@ -248,7 +248,7 @@ final class EntityFactory{
|
||||
|
||||
public function injectSaveId(string $class, CompoundTag $saveData) : void{
|
||||
if(isset($this->saveNames[$class])){
|
||||
$saveData->setTag("id", new StringTag($this->saveNames[$class]));
|
||||
$saveData->setTag("identifier", new StringTag($this->saveNames[$class]));
|
||||
}else{
|
||||
throw new \InvalidArgumentException("Entity $class is not registered");
|
||||
}
|
||||
|
@ -237,11 +237,10 @@ class ExperienceManager{
|
||||
}
|
||||
|
||||
public function onPickupXp(int $xpValue) : void{
|
||||
static $mainHandIndex = -1;
|
||||
static $offHandIndex = -2;
|
||||
$mainHandIndex = -1;
|
||||
$offHandIndex = -2;
|
||||
|
||||
//TODO: replace this with a more generic equipment getting/setting interface
|
||||
/** @var Durable[] $equipment */
|
||||
$equipment = [];
|
||||
|
||||
if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){
|
||||
|
@ -48,9 +48,9 @@ use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
|
||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\DeviceOS;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
@ -58,11 +58,15 @@ use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\UpdateAbilitiesPacketLayer;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\world\sound\TotemUseSound;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function array_fill;
|
||||
use function array_filter;
|
||||
use function array_key_exists;
|
||||
use function array_merge;
|
||||
@ -471,7 +475,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
$player->getNetworkSession()->sendDataPacket(AddPlayerPacket::create(
|
||||
$this->getUniqueId(),
|
||||
$this->getName(),
|
||||
$this->getId(), //TODO: actor unique ID
|
||||
$this->getId(),
|
||||
"",
|
||||
$this->location->asVector3(),
|
||||
@ -482,7 +485,14 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($this->getInventory()->getItemInHand())),
|
||||
GameMode::SURVIVAL,
|
||||
$this->getAllNetworkData(),
|
||||
AdventureSettingsPacket::create(0, 0, 0, 0, 0, $this->getId()), //TODO
|
||||
UpdateAbilitiesPacket::create(CommandPermissions::NORMAL, PlayerPermissions::VISITOR, $this->getId() /* TODO: this should be unique ID */, [
|
||||
new UpdateAbilitiesPacketLayer(
|
||||
UpdateAbilitiesPacketLayer::LAYER_BASE,
|
||||
array_fill(0, UpdateAbilitiesPacketLayer::NUMBER_OF_ABILITIES, false),
|
||||
0.0,
|
||||
0.0
|
||||
)
|
||||
]),
|
||||
[], //TODO: entity links
|
||||
"", //device ID (we intentionally don't send this - secvuln)
|
||||
DeviceOS::UNKNOWN //we intentionally don't send this (secvuln)
|
||||
|
@ -28,7 +28,6 @@ use pocketmine\utils\Limits;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function json_encode;
|
||||
use function json_last_error_msg;
|
||||
use function strlen;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
@ -68,9 +67,10 @@ final class Skin{
|
||||
}
|
||||
|
||||
if($geometryData !== ""){
|
||||
$decodedGeometry = (new CommentedJsonDecoder())->decode($geometryData);
|
||||
if($decodedGeometry === false){
|
||||
throw new InvalidSkinException("Invalid geometry data (" . json_last_error_msg() . ")");
|
||||
try{
|
||||
$decodedGeometry = (new CommentedJsonDecoder())->decode($geometryData);
|
||||
}catch(\RuntimeException $e){
|
||||
throw new InvalidSkinException("Invalid geometry data: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -38,7 +38,6 @@ class Effect{
|
||||
* @param Translatable|string $name Translation key used for effect name
|
||||
* @param Color $color Color of bubbles given by this effect
|
||||
* @param bool $bad Whether the effect is harmful
|
||||
* @param int $defaultDuration
|
||||
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -85,14 +85,11 @@ class EffectManager{
|
||||
$index = spl_object_id($effectType);
|
||||
if(isset($this->effects[$index])){
|
||||
$effect = $this->effects[$index];
|
||||
$hasExpired = $effect->hasExpired();
|
||||
$ev = new EntityEffectRemoveEvent($this->entity, $effect);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
if($hasExpired && !$ev->getEffect()->hasExpired()){ //altered duration of an expired effect to make it not get removed
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($ev->getEffect(), true);
|
||||
}
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($ev->getEffect(), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ final class StringToEffectParser extends StringToTParser{
|
||||
use SingletonTrait;
|
||||
|
||||
private static function make() : self{
|
||||
$result = new self;
|
||||
$result = new self();
|
||||
|
||||
$result->register("absorption", fn() => VanillaEffects::ABSORPTION());
|
||||
$result->register("blindness", fn() => VanillaEffects::BLINDNESS());
|
||||
|
@ -101,6 +101,7 @@ final class VanillaEffects{
|
||||
|
||||
/**
|
||||
* @return Effect[]
|
||||
* @phpstan-return array<string, Effect>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@ -30,7 +30,7 @@ use pocketmine\event\entity\EntityDamageEvent;
|
||||
class WitherEffect extends Effect{
|
||||
|
||||
public function canTick(EffectInstance $instance) : bool{
|
||||
if(($interval = (50 >> $instance->getAmplifier())) > 0){
|
||||
if(($interval = (40 >> $instance->getAmplifier())) > 0){
|
||||
return ($instance->getDuration() % $interval) === 0;
|
||||
}
|
||||
return true;
|
||||
|
@ -186,6 +186,10 @@ class ItemEntity extends Entity{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canSaveWithChunk() : bool{
|
||||
return !$this->item->isNull() && parent::canSaveWithChunk();
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
$nbt->setTag("Item", $this->item->nbtSerialize());
|
||||
|
@ -31,7 +31,7 @@ class HandlerListManager{
|
||||
private static ?self $globalInstance = null;
|
||||
|
||||
public static function global() : self{
|
||||
return self::$globalInstance ?? (self::$globalInstance = new self);
|
||||
return self::$globalInstance ?? (self::$globalInstance = new self());
|
||||
}
|
||||
|
||||
/** @var HandlerList[] classname => HandlerList */
|
||||
|
@ -76,11 +76,13 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
abstract protected function internalSetContents(array $items) : void;
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void{
|
||||
if(count($items) > $this->getSize()){
|
||||
@ -132,6 +134,7 @@ abstract class BaseInventory implements Inventory{
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
public function first(Item $item, bool $exact = false) : int{
|
||||
$count = $exact ? $item->getCount() : max(1, $item->getCount());
|
||||
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
||||
|
@ -47,12 +47,20 @@ interface Inventory{
|
||||
public function setItem(int $index, Item $item) : void;
|
||||
|
||||
/**
|
||||
* Returns an array of all the itemstacks in the inventory, indexed by their slot number.
|
||||
* Empty slots are not included unless includeEmpty is true.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getContents(bool $includeEmpty = false) : array;
|
||||
|
||||
/**
|
||||
* Sets the contents of the inventory. Non-numeric offsets or offsets larger than the size of the inventory are
|
||||
* ignored.
|
||||
*
|
||||
* @param Item[] $items
|
||||
* @phpstan-param array<int, Item> $items
|
||||
*/
|
||||
public function setContents(array $items) : void;
|
||||
|
||||
@ -85,8 +93,10 @@ interface Inventory{
|
||||
/**
|
||||
* Will return all the Items that has the same id and metadata (if not null).
|
||||
* Won't check amount
|
||||
* The returned array is indexed by slot number.
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function all(Item $item) : array;
|
||||
|
||||
|
@ -58,6 +58,7 @@ class SimpleInventory extends BaseInventory{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getContents(bool $includeEmpty = false) : array{
|
||||
$contents = [];
|
||||
|
@ -42,6 +42,9 @@ class DropItemAction extends InventoryAction{
|
||||
if($this->targetItem->isNull()){
|
||||
throw new TransactionValidationException("Cannot drop an empty itemstack");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds item type max stack size");
|
||||
}
|
||||
}
|
||||
|
||||
public function onPreExecute(Player $source) : bool{
|
||||
|
@ -70,6 +70,12 @@ class SlotChangeAction extends InventoryAction{
|
||||
if(!$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){
|
||||
throw new TransactionValidationException("Slot does not contain expected original item");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds item type max stack size");
|
||||
}
|
||||
if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){
|
||||
throw new TransactionValidationException("Target item exceeds inventory max stack size");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,8 +100,9 @@ class Item implements \JsonSerializable{
|
||||
* Constructs a new Item type. This constructor should ONLY be used when constructing a new item TYPE to register
|
||||
* into the index.
|
||||
*
|
||||
* NOTE: This should NOT BE USED for creating items to set into an inventory. Use {@link ItemFactory#get} for that
|
||||
* NOTE: This should NOT BE USED for creating items to set into an inventory. Use VanillaItems for that
|
||||
* purpose.
|
||||
* @see VanillaItems
|
||||
*/
|
||||
public function __construct(ItemIdentifier $identifier, string $name = "Unknown"){
|
||||
$this->identifier = $identifier;
|
||||
|
@ -472,7 +472,7 @@ class ItemFactory{
|
||||
if(isset($this->list[$offset = self::getListOffset($id, $meta)])){
|
||||
$item = clone $this->list[$offset];
|
||||
}elseif(isset($this->list[$zero = self::getListOffset($id, 0)]) && $this->list[$zero] instanceof Durable){
|
||||
if($meta <= $this->list[$zero]->getMaxDurability()){
|
||||
if($meta >= 0 && $meta <= $this->list[$zero]->getMaxDurability()){
|
||||
$item = clone $this->list[$zero];
|
||||
$item->setDamage($meta);
|
||||
}else{
|
||||
|
@ -185,13 +185,16 @@ final class PotionType{
|
||||
new EffectInstance(VanillaEffects::WITHER(), 800, 1)
|
||||
]),
|
||||
new self("turtle_master", "Turtle Master", fn() => [
|
||||
//TODO
|
||||
new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 3),
|
||||
new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 2),
|
||||
]),
|
||||
new self("long_turtle_master", "Long Turtle Master", fn() => [
|
||||
//TODO
|
||||
new EffectInstance(VanillaEffects::SLOWNESS(), 40 * 20, 3),
|
||||
new EffectInstance(VanillaEffects::RESISTANCE(), 40 * 20, 2),
|
||||
]),
|
||||
new self("strong_turtle_master", "Strong Turtle Master", fn() => [
|
||||
//TODO
|
||||
new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 5),
|
||||
new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 3),
|
||||
]),
|
||||
new self("slow_falling", "Slow Falling", fn() => [
|
||||
//TODO
|
||||
|
@ -41,7 +41,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
use SingletonTrait;
|
||||
|
||||
private static function make() : self{
|
||||
$result = new self;
|
||||
$result = new self();
|
||||
|
||||
foreach(DyeColor::getAll() as $color){
|
||||
$prefix = fn(string $name) => $color->name() . "_" . $name;
|
||||
|
@ -384,6 +384,7 @@ final class VanillaItems{
|
||||
|
||||
/**
|
||||
* @return Item[]
|
||||
* @phpstan-return array<string, Item>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@ -35,7 +35,7 @@ final class StringToEnchantmentParser extends StringToTParser{
|
||||
use SingletonTrait;
|
||||
|
||||
private static function make() : self{
|
||||
$result = new self;
|
||||
$result = new self();
|
||||
|
||||
$result->register("blast_protection", fn() => VanillaEnchantments::BLAST_PROTECTION());
|
||||
$result->register("efficiency", fn() => VanillaEnchantments::EFFICIENCY());
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\utils\Utils;
|
||||
use Webmozart\PathUtil\Path;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function is_dir;
|
||||
@ -67,10 +68,14 @@ class Language{
|
||||
$result = [];
|
||||
|
||||
foreach($files as $file){
|
||||
$code = explode(".", $file)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings["language.name"])){
|
||||
$result[$code] = $strings["language.name"];
|
||||
try{
|
||||
$code = explode(".", $file)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings["language.name"])){
|
||||
$result[$code] = $strings["language.name"];
|
||||
}
|
||||
}catch(LanguageNotFoundException $e){
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +129,10 @@ class Language{
|
||||
protected static function loadLang(string $path, string $languageCode) : array{
|
||||
$file = Path::join($path, $languageCode . ".ini");
|
||||
if(file_exists($file)){
|
||||
return array_map('\stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files"));
|
||||
$strings = array_map('stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files"));
|
||||
if(count($strings) > 0){
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
|
||||
throw new LanguageNotFoundException("Language \"$languageCode\" not found");
|
||||
|
@ -30,6 +30,7 @@ use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\LevelChunkPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
|
||||
use pocketmine\network\mcpe\serializer\ChunkSerializer;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -71,7 +72,7 @@ class ChunkRequestTask extends AsyncTask{
|
||||
$subCount = ChunkSerializer::getSubChunkCount($chunk) + ChunkSerializer::LOWER_PADDING_SIZE;
|
||||
$encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
|
||||
$payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles);
|
||||
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create($this->chunkX, $this->chunkZ, $subCount, false, null, $payload))->getBuffer()));
|
||||
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
|
||||
}
|
||||
|
||||
public function onError() : void{
|
||||
|
@ -259,8 +259,10 @@ class InventoryManager{
|
||||
|
||||
public function onClientRemoveWindow(int $id) : void{
|
||||
if($id === $this->lastInventoryNetworkId){
|
||||
$this->remove($id);
|
||||
$this->player->removeCurrentWindow();
|
||||
if($id !== $this->pendingCloseWindowId){
|
||||
$this->remove($id);
|
||||
$this->player->removeCurrentWindow();
|
||||
}
|
||||
}else{
|
||||
$this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId");
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ use pocketmine\network\mcpe\handler\LoginPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\PacketHandler;
|
||||
use pocketmine\network\mcpe\handler\PreSpawnPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\SessionStartPacketHandler;
|
||||
use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
|
||||
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
@ -93,6 +93,7 @@ use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty;
|
||||
@ -100,9 +101,13 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\UpdateAbilitiesPacketLayer;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
|
||||
use pocketmine\network\NetworkSessionManager;
|
||||
use pocketmine\network\PacketHandlingException;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\permission\DefaultPermissions;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
@ -160,6 +165,7 @@ class NetworkSession{
|
||||
*/
|
||||
private \SplQueue $compressedQueue;
|
||||
private bool $forceAsyncCompression = true;
|
||||
private bool $enableCompression = false; //disabled until handshake completed
|
||||
|
||||
private PacketSerializerContext $packetSerializerContext;
|
||||
|
||||
@ -192,17 +198,10 @@ class NetworkSession{
|
||||
|
||||
$this->connectTime = time();
|
||||
|
||||
$this->setHandler(new LoginPacketHandler(
|
||||
$this->setHandler(new SessionStartPacketHandler(
|
||||
$this->server,
|
||||
$this,
|
||||
function(PlayerInfo $info) : void{
|
||||
$this->info = $info;
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
}
|
||||
fn() => $this->onSessionStartSuccess()
|
||||
));
|
||||
|
||||
$this->manager->add($this);
|
||||
@ -217,6 +216,24 @@ class NetworkSession{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
private function onSessionStartSuccess() : void{
|
||||
$this->logger->debug("Session start handshake completed, awaiting login packet");
|
||||
$this->flushSendBuffer(true);
|
||||
$this->enableCompression = true;
|
||||
$this->setHandler(new LoginPacketHandler(
|
||||
$this->server,
|
||||
$this,
|
||||
function(PlayerInfo $info) : void{
|
||||
$this->info = $info;
|
||||
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
|
||||
$this->logger->setPrefix($this->getLogPrefix());
|
||||
},
|
||||
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
|
||||
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
protected function createPlayer() : void{
|
||||
$this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion(
|
||||
\Closure::fromCallable([$this, 'onPlayerCreated']),
|
||||
@ -250,8 +267,8 @@ class NetworkSession{
|
||||
|
||||
$permissionHooks = $this->player->getPermissionRecalculationCallbacks();
|
||||
$permissionHooks->add($permHook = function() : void{
|
||||
$this->logger->debug("Syncing available commands and adventure settings due to permission recalculation");
|
||||
$this->syncAdventureSettings($this->player);
|
||||
$this->logger->debug("Syncing available commands and abilities/permissions due to permission recalculation");
|
||||
$this->syncAbilities($this->player);
|
||||
$this->syncAvailableCommands();
|
||||
});
|
||||
$this->disposeHooks->add(static function() use ($permissionHooks, $permHook) : void{
|
||||
@ -331,18 +348,22 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
Timings::$playerNetworkReceiveDecompress->startTiming();
|
||||
try{
|
||||
$stream = new PacketBatch($this->compressor->decompress($payload));
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
if($this->enableCompression){
|
||||
Timings::$playerNetworkReceiveDecompress->startTiming();
|
||||
try{
|
||||
$decompressed = $this->compressor->decompress($payload);
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
}
|
||||
}else{
|
||||
$decompressed = $payload;
|
||||
}
|
||||
|
||||
try{
|
||||
foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
||||
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
|
||||
if($packet === null){
|
||||
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
|
||||
throw new PacketHandlingException("Unknown packet received");
|
||||
@ -440,7 +461,14 @@ class NetworkSession{
|
||||
}elseif($this->forceAsyncCompression){
|
||||
$syncMode = false;
|
||||
}
|
||||
$promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode);
|
||||
|
||||
$batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer);
|
||||
if($this->enableCompression){
|
||||
$promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode);
|
||||
}else{
|
||||
$promise = new CompressBatchPromise();
|
||||
$promise->resolve($batch->getBuffer());
|
||||
}
|
||||
$this->sendBuffer = [];
|
||||
$this->queueCompressedNoBufferFlush($promise, $immediate);
|
||||
}
|
||||
@ -716,7 +744,7 @@ class NetworkSession{
|
||||
$this->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
|
||||
$this->player->sendData(null);
|
||||
|
||||
$this->syncAdventureSettings($this->player);
|
||||
$this->syncAbilities($this->player);
|
||||
$this->invManager->syncAll();
|
||||
$this->setHandler(new InGamePacketHandler($this->player, $this, $this->invManager));
|
||||
}
|
||||
@ -750,7 +778,7 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
public function syncViewAreaCenterPoint(Vector3 $newPos, int $viewDistance) : void{
|
||||
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16)); //blocks, not chunks >.>
|
||||
$this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16, [])); //blocks, not chunks >.>
|
||||
}
|
||||
|
||||
public function syncPlayerSpawnPoint(Position $newSpawn) : void{
|
||||
@ -766,37 +794,60 @@ class NetworkSession{
|
||||
public function syncGameMode(GameMode $mode, bool $isRollback = false) : void{
|
||||
$this->sendDataPacket(SetPlayerGameTypePacket::create(TypeConverter::getInstance()->coreGameModeToProtocol($mode)));
|
||||
if($this->player !== null){
|
||||
$this->syncAdventureSettings($this->player);
|
||||
$this->syncAbilities($this->player);
|
||||
$this->syncAdventureSettings(); //TODO: we might be able to do this with the abilities packet alone
|
||||
}
|
||||
if(!$isRollback && $this->invManager !== null){
|
||||
$this->invManager->syncCreative();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: make this less specialized
|
||||
*/
|
||||
public function syncAdventureSettings(Player $for) : void{
|
||||
public function syncAbilities(Player $for) : void{
|
||||
$isOp = $for->hasPermission(DefaultPermissions::ROOT_OPERATOR);
|
||||
$pk = AdventureSettingsPacket::create(
|
||||
0,
|
||||
$isOp ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL,
|
||||
0,
|
||||
|
||||
//ALL of these need to be set for the base layer, otherwise the client will cry
|
||||
$boolAbilities = [
|
||||
UpdateAbilitiesPacketLayer::ABILITY_ALLOW_FLIGHT => $for->getAllowFlight(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_FLYING => $for->isFlying(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_NO_CLIP => !$for->hasBlockCollision(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_OPERATOR => $isOp,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_TELEPORT => $for->hasPermission(DefaultPermissionNames::COMMAND_TELEPORT),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_INVULNERABLE => $for->isCreative(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_MUTED => false,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_WORLD_BUILDER => false,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_INFINITE_RESOURCES => !$for->hasFiniteResources(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_LIGHTNING => false,
|
||||
UpdateAbilitiesPacketLayer::ABILITY_BUILD => !$for->isSpectator(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_MINE => !$for->isSpectator(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_DOORS_AND_SWITCHES => !$for->isSpectator(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_OPEN_CONTAINERS => !$for->isSpectator(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_ATTACK_PLAYERS => !$for->isSpectator(),
|
||||
UpdateAbilitiesPacketLayer::ABILITY_ATTACK_MOBS => !$for->isSpectator(),
|
||||
];
|
||||
|
||||
$this->sendDataPacket(UpdateAbilitiesPacket::create(
|
||||
$isOp ? CommandPermissions::OPERATOR : CommandPermissions::NORMAL,
|
||||
$isOp ? PlayerPermissions::OPERATOR : PlayerPermissions::MEMBER,
|
||||
0,
|
||||
$for->getId()
|
||||
);
|
||||
$for->getId(),
|
||||
[
|
||||
//TODO: dynamic flying speed! FINALLY!!!!!!!!!!!!!!!!!
|
||||
new UpdateAbilitiesPacketLayer(UpdateAbilitiesPacketLayer::LAYER_BASE, $boolAbilities, 0.05, 0.1),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
$pk->setFlag(AdventureSettingsPacket::WORLD_IMMUTABLE, $for->isSpectator());
|
||||
$pk->setFlag(AdventureSettingsPacket::NO_PVP, $for->isSpectator());
|
||||
$pk->setFlag(AdventureSettingsPacket::AUTO_JUMP, $for->hasAutoJump());
|
||||
$pk->setFlag(AdventureSettingsPacket::ALLOW_FLIGHT, $for->getAllowFlight());
|
||||
$pk->setFlag(AdventureSettingsPacket::NO_CLIP, !$for->hasBlockCollision());
|
||||
$pk->setFlag(AdventureSettingsPacket::FLYING, $for->isFlying());
|
||||
|
||||
//TODO: permission flags
|
||||
|
||||
$this->sendDataPacket($pk);
|
||||
public function syncAdventureSettings() : void{
|
||||
if($this->player === null){
|
||||
throw new \LogicException("Cannot sync adventure settings for a player that is not yet created");
|
||||
}
|
||||
//everything except auto jump is handled via UpdateAbilitiesPacket
|
||||
$this->sendDataPacket(UpdateAdventureSettingsPacket::create(
|
||||
noAttackingMobs: false,
|
||||
noAttackingPlayers: false,
|
||||
worldImmutable: false,
|
||||
showNameTags: true,
|
||||
autoJump: $this->player->hasAutoJump()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -805,7 +856,7 @@ class NetworkSession{
|
||||
public function syncAttributes(Living $entity, array $attributes) : void{
|
||||
if(count($attributes) > 0){
|
||||
$this->sendDataPacket(UpdateAttributesPacket::create($entity->getId(), array_map(function(Attribute $attr) : NetworkAttribute{
|
||||
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue());
|
||||
return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []);
|
||||
}, $attributes), 0));
|
||||
}
|
||||
}
|
||||
|
2
src/network/mcpe/cache/ChunkCache.php
vendored
2
src/network/mcpe/cache/ChunkCache.php
vendored
@ -43,8 +43,6 @@ class ChunkCache implements ChunkListener{
|
||||
|
||||
/**
|
||||
* Fetches the ChunkCache instance for the given world. This lazily creates cache systems as needed.
|
||||
*
|
||||
* @return ChunkCache
|
||||
*/
|
||||
public static function getInstance(World $world, Compressor $compressor) : self{
|
||||
$worldId = spl_object_id($world);
|
||||
|
@ -48,7 +48,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
@ -115,7 +117,7 @@ class TypeConverter{
|
||||
|
||||
public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{
|
||||
if($itemStack->isNull()){
|
||||
return new RecipeIngredient(0, 0, 0);
|
||||
return new RecipeIngredient(null, 0);
|
||||
}
|
||||
if($itemStack->hasAnyDamageValue()){
|
||||
[$id, ] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), 0);
|
||||
@ -123,15 +125,25 @@ class TypeConverter{
|
||||
}else{
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta());
|
||||
}
|
||||
return new RecipeIngredient($id, $meta, $itemStack->getCount());
|
||||
return new RecipeIngredient(new IntIdMetaItemDescriptor($id, $meta), $itemStack->getCount());
|
||||
}
|
||||
|
||||
public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{
|
||||
if($ingredient->getId() === 0){
|
||||
$descriptor = $ingredient->getDescriptor();
|
||||
if($descriptor === null){
|
||||
return VanillaItems::AIR();
|
||||
}
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($ingredient->getId(), $ingredient->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
if($descriptor instanceof IntIdMetaItemDescriptor){
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($descriptor->getId(), $descriptor->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
}
|
||||
if($descriptor instanceof StringIdMetaItemDescriptor){
|
||||
$intId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($descriptor->getId());
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($intId, $descriptor->getMeta());
|
||||
return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount());
|
||||
}
|
||||
|
||||
throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack");
|
||||
}
|
||||
|
||||
public function coreItemStackToNet(Item $itemStack) : ItemStack{
|
||||
|
@ -51,7 +51,6 @@ use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\ActorEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\ActorPickRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\BlockPickRequestPacket;
|
||||
@ -80,6 +79,7 @@ use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestAbilityPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
|
||||
@ -114,6 +114,7 @@ use function count;
|
||||
use function fmod;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_bool;
|
||||
use function is_infinite;
|
||||
use function is_nan;
|
||||
use function json_decode;
|
||||
@ -143,6 +144,11 @@ class InGamePacketHandler extends PacketHandler{
|
||||
/** @var UseItemTransactionData|null */
|
||||
protected $lastRightClickData = null;
|
||||
|
||||
protected ?Vector3 $lastPlayerAuthInputPosition = null;
|
||||
protected ?float $lastPlayerAuthInputYaw = null;
|
||||
protected ?float $lastPlayerAuthInputPitch = null;
|
||||
protected ?int $lastPlayerAuthInputFlags = null;
|
||||
|
||||
/** @var bool */
|
||||
public $forceMoveSync = false;
|
||||
|
||||
@ -166,9 +172,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function resolveOnOffInputFlags(PlayerAuthInputPacket $packet, int $startFlag, int $stopFlag) : ?bool{
|
||||
$enabled = $packet->hasFlag($startFlag);
|
||||
if($enabled !== $packet->hasFlag($stopFlag)){
|
||||
private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{
|
||||
$enabled = ($inputFlags & (1 << $startFlag)) !== 0;
|
||||
$disabled = ($inputFlags & (1 << $stopFlag)) !== 0;
|
||||
if($enabled !== $disabled){
|
||||
return $enabled;
|
||||
}
|
||||
//neither flag was set, or both were set
|
||||
@ -177,51 +184,68 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{
|
||||
$rawPos = $packet->getPosition();
|
||||
foreach([$rawPos->x, $rawPos->y, $rawPos->z, $packet->getYaw(), $packet->getHeadYaw(), $packet->getPitch()] as $float){
|
||||
$rawYaw = $packet->getYaw();
|
||||
$rawPitch = $packet->getPitch();
|
||||
foreach([$rawPos->x, $rawPos->y, $rawPos->z, $rawYaw, $packet->getHeadYaw(), $rawPitch] as $float){
|
||||
if(is_infinite($float) || is_nan($float)){
|
||||
$this->session->getLogger()->debug("Invalid movement received, contains NAN/INF components");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$yaw = fmod($packet->getYaw(), 360);
|
||||
$pitch = fmod($packet->getPitch(), 360);
|
||||
if($yaw < 0){
|
||||
$yaw += 360;
|
||||
if($rawYaw !== $this->lastPlayerAuthInputYaw || $rawPitch !== $this->lastPlayerAuthInputPitch){
|
||||
$this->lastPlayerAuthInputYaw = $rawYaw;
|
||||
$this->lastPlayerAuthInputPitch = $rawPitch;
|
||||
|
||||
$yaw = fmod($rawYaw, 360);
|
||||
$pitch = fmod($rawPitch, 360);
|
||||
if($yaw < 0){
|
||||
$yaw += 360;
|
||||
}
|
||||
|
||||
$this->player->setRotation($yaw, $pitch);
|
||||
}
|
||||
|
||||
$this->player->setRotation($yaw, $pitch);
|
||||
|
||||
$curPos = $this->player->getLocation();
|
||||
$hasMoved = $this->lastPlayerAuthInputPosition === null || !$this->lastPlayerAuthInputPosition->equals($rawPos);
|
||||
$newPos = $rawPos->round(4)->subtract(0, 1.62, 0);
|
||||
|
||||
if($this->forceMoveSync && $newPos->distanceSquared($curPos) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks
|
||||
$this->session->getLogger()->debug("Got outdated pre-teleport movement, received " . $newPos . ", expected " . $curPos);
|
||||
//Still getting movements from before teleport, ignore them
|
||||
return false;
|
||||
if($this->forceMoveSync && $hasMoved){
|
||||
$curPos = $this->player->getLocation();
|
||||
|
||||
if($newPos->distanceSquared($curPos) > 1){ //Tolerate up to 1 block to avoid problems with client-sided physics when spawning in blocks
|
||||
$this->session->getLogger()->debug("Got outdated pre-teleport movement, received " . $newPos . ", expected " . $curPos);
|
||||
//Still getting movements from before teleport, ignore them
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock
|
||||
$this->forceMoveSync = false;
|
||||
}
|
||||
|
||||
// Once we get a movement within a reasonable distance, treat it as a teleport ACK and remove position lock
|
||||
$this->forceMoveSync = false;
|
||||
$inputFlags = $packet->getInputFlags();
|
||||
if($inputFlags !== $this->lastPlayerAuthInputFlags){
|
||||
$this->lastPlayerAuthInputFlags = $inputFlags;
|
||||
|
||||
$sneaking = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING);
|
||||
$sprinting = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING);
|
||||
$swimming = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING);
|
||||
$gliding = $this->resolveOnOffInputFlags($packet, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING);
|
||||
$mismatch =
|
||||
($sneaking !== null && !$this->player->toggleSneak($sneaking)) |
|
||||
($sprinting !== null && !$this->player->toggleSprint($sprinting)) |
|
||||
($swimming !== null && !$this->player->toggleSwim($swimming)) |
|
||||
($gliding !== null && !$this->player->toggleGlide($gliding));
|
||||
if((bool) $mismatch){
|
||||
$this->player->sendData([$this->player]);
|
||||
$sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING);
|
||||
$sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING);
|
||||
$swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING);
|
||||
$gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING);
|
||||
$mismatch =
|
||||
($sneaking !== null && !$this->player->toggleSneak($sneaking)) |
|
||||
($sprinting !== null && !$this->player->toggleSprint($sprinting)) |
|
||||
($swimming !== null && !$this->player->toggleSwim($swimming)) |
|
||||
($gliding !== null && !$this->player->toggleGlide($gliding));
|
||||
if((bool) $mismatch){
|
||||
$this->player->sendData([$this->player]);
|
||||
}
|
||||
|
||||
if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){
|
||||
$this->player->jump();
|
||||
}
|
||||
}
|
||||
|
||||
if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){
|
||||
$this->player->jump();
|
||||
}
|
||||
|
||||
if(!$this->forceMoveSync){
|
||||
if(!$this->forceMoveSync && $hasMoved){
|
||||
$this->lastPlayerAuthInputPosition = $rawPos;
|
||||
//TODO: this packet has WAYYYYY more useful information that we're not using
|
||||
$this->player->handleMovement($newPos);
|
||||
}
|
||||
@ -267,7 +291,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
|
||||
return $packet->actorRuntimeId === ActorEvent::EATING_ITEM;
|
||||
}
|
||||
$this->player->removeCurrentWindow();
|
||||
|
||||
switch($packet->eventId){
|
||||
case ActorEvent::EATING_ITEM: //TODO: ignore this and handle it server-side
|
||||
@ -610,6 +633,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK:
|
||||
//TODO: do we need to handle this?
|
||||
break;
|
||||
case PlayerAction::START_ITEM_USE_ON:
|
||||
case PlayerAction::STOP_ITEM_USE_ON:
|
||||
//TODO: this has no obvious use and seems only used for analytics in vanilla - ignore it
|
||||
break;
|
||||
default:
|
||||
$this->session->getLogger()->debug("Unhandled/unknown player action type " . $action);
|
||||
return false;
|
||||
@ -641,26 +668,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return true; //this is a broken useless packet, so we don't use it
|
||||
}
|
||||
|
||||
public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{
|
||||
if($packet->targetActorUniqueId !== $this->player->getId()){
|
||||
return false; //TODO: operators can change other people's permissions using this
|
||||
}
|
||||
|
||||
$handled = false;
|
||||
|
||||
$isFlying = $packet->getFlag(AdventureSettingsPacket::FLYING);
|
||||
if($isFlying !== $this->player->isFlying()){
|
||||
if(!$this->player->toggleFlight($isFlying)){
|
||||
$this->session->syncAdventureSettings($this->player);
|
||||
}
|
||||
$handled = true;
|
||||
}
|
||||
|
||||
//TODO: check for other changes
|
||||
|
||||
return $handled;
|
||||
}
|
||||
|
||||
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
|
||||
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
|
||||
if($pos->distanceSquared($this->player->getLocation()) > 10000){
|
||||
@ -878,7 +885,14 @@ class InGamePacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{
|
||||
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
|
||||
if($packet->cancelReason !== null){
|
||||
//TODO: make APIs for this to allow plugins to use this information
|
||||
return $this->player->onFormSubmit($packet->formId, null);
|
||||
}elseif($packet->formData !== null){
|
||||
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
|
||||
}else{
|
||||
throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -980,4 +994,22 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$this->player->emote($packet->getEmoteId());
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequestAbility(RequestAbilityPacket $packet) : bool{
|
||||
if($packet->getAbilityId() === RequestAbilityPacket::ABILITY_FLYING){
|
||||
$isFlying = $packet->getAbilityValue();
|
||||
if(!is_bool($isFlying)){
|
||||
throw new PacketHandlingException("Flying ability should always have a bool value");
|
||||
}
|
||||
if($isFlying !== $this->player->isFlying()){
|
||||
if(!$this->player->toggleFlight($isFlying)){
|
||||
$this->session->syncAbilities($this->player);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -165,13 +165,13 @@ class LoginPacketHandler extends PacketHandler{
|
||||
if(!is_array($claims["extraData"])){
|
||||
throw new PacketHandlingException("'extraData' key should be an array");
|
||||
}
|
||||
$mapper = new \JsonMapper;
|
||||
$mapper = new \JsonMapper();
|
||||
$mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
|
||||
$mapper->bExceptionOnMissingData = true;
|
||||
$mapper->bExceptionOnUndefinedProperty = true;
|
||||
try{
|
||||
/** @var AuthenticationData $extraData */
|
||||
$extraData = $mapper->map($claims["extraData"], new AuthenticationData);
|
||||
$extraData = $mapper->map($claims["extraData"], new AuthenticationData());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw PacketHandlingException::wrap($e);
|
||||
}
|
||||
@ -193,12 +193,12 @@ class LoginPacketHandler extends PacketHandler{
|
||||
throw PacketHandlingException::wrap($e);
|
||||
}
|
||||
|
||||
$mapper = new \JsonMapper;
|
||||
$mapper = new \JsonMapper();
|
||||
$mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
|
||||
$mapper->bExceptionOnMissingData = true;
|
||||
$mapper->bExceptionOnUndefinedProperty = true;
|
||||
try{
|
||||
$clientData = $mapper->map($clientDataClaims, new ClientData);
|
||||
$clientData = $mapper->map($clientDataClaims, new ClientData());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw PacketHandlingException::wrap($e);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\mcpe\cache\CraftingDataCache;
|
||||
use pocketmine\network\mcpe\cache\StaticPacketCache;
|
||||
use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary;
|
||||
@ -33,6 +34,7 @@ use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\BoolGameRule;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\Experiments;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSettings;
|
||||
@ -42,6 +44,7 @@ use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\VersionInfo;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
@ -82,6 +85,7 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
$this->player->getOffsetPosition($location),
|
||||
$location->pitch,
|
||||
$location->yaw,
|
||||
new CacheableNbt(CompoundTag::create()), //TODO: we don't care about this right now
|
||||
$levelSettings,
|
||||
"",
|
||||
$this->server->getMotd(),
|
||||
@ -93,16 +97,19 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
"",
|
||||
false,
|
||||
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
|
||||
Uuid::fromString(Uuid::NIL),
|
||||
false,
|
||||
[],
|
||||
0,
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries()
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
$this->session->syncAttributes($this->player, $this->player->getAttributeMap()->getAll());
|
||||
$this->session->syncAvailableCommands();
|
||||
$this->session->syncAdventureSettings($this->player);
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->onEntityEffectAdded($this->player, $effect, false);
|
||||
}
|
||||
|
76
src/network/mcpe/handler/SessionStartPacketHandler.php
Normal file
76
src/network/mcpe/handler/SessionStartPacketHandler.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?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\network\mcpe\handler;
|
||||
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\NetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\RequestNetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
use pocketmine\Server;
|
||||
|
||||
final class SessionStartPacketHandler extends PacketHandler{
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure() : void $onSuccess
|
||||
*/
|
||||
public function __construct(
|
||||
private Server $server,
|
||||
private NetworkSession $session,
|
||||
private \Closure $onSuccess
|
||||
){}
|
||||
|
||||
public function handleRequestNetworkSettings(RequestNetworkSettingsPacket $packet) : bool{
|
||||
$protocolVersion = $packet->getProtocolVersion();
|
||||
if(!$this->isCompatibleProtocol($protocolVersion)){
|
||||
$this->session->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
|
||||
|
||||
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
|
||||
$this->session->disconnect(
|
||||
$this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion)),
|
||||
false
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future
|
||||
$this->session->sendDataPacket(NetworkSettingsPacket::create(
|
||||
NetworkSettingsPacket::COMPRESS_EVERYTHING,
|
||||
CompressionAlgorithm::ZLIB,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
));
|
||||
($this->onSuccess)();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isCompatibleProtocol(int $protocolVersion) : bool{
|
||||
return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL;
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
* Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't
|
||||
* communicate. It's important that we check this to avoid catastrophes.
|
||||
*/
|
||||
private const MCPE_RAKNET_PROTOCOL_VERSION = 10;
|
||||
private const MCPE_RAKNET_PROTOCOL_VERSION = 11;
|
||||
|
||||
private const MCPE_RAKNET_PACKET_ID = "\xfe";
|
||||
|
||||
@ -84,8 +84,8 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
|
||||
$this->sleeper = new SleeperNotifier();
|
||||
|
||||
$mainToThreadBuffer = new \Threaded;
|
||||
$threadToMainBuffer = new \Threaded;
|
||||
$mainToThreadBuffer = new \Threaded();
|
||||
$threadToMainBuffer = new \Threaded();
|
||||
|
||||
$this->rakLib = new RakLibServer(
|
||||
$this->server->getLogger(),
|
||||
|
@ -31,7 +31,7 @@ class PermissionManager{
|
||||
|
||||
public static function getInstance() : PermissionManager{
|
||||
if(self::$instance === null){
|
||||
self::$instance = new self;
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
|
@ -421,7 +421,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
public function setAllowFlight(bool $value) : void{
|
||||
if($this->allowFlight !== $value){
|
||||
$this->allowFlight = $value;
|
||||
$this->getNetworkSession()->syncAdventureSettings($this);
|
||||
$this->getNetworkSession()->syncAbilities($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,7 +432,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
public function setHasBlockCollision(bool $value) : void{
|
||||
if($this->blockCollision !== $value){
|
||||
$this->blockCollision = $value;
|
||||
$this->getNetworkSession()->syncAdventureSettings($this);
|
||||
$this->getNetworkSession()->syncAbilities($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,7 +444,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
if($this->flying !== $value){
|
||||
$this->flying = $value;
|
||||
$this->resetFallDistance();
|
||||
$this->getNetworkSession()->syncAdventureSettings($this);
|
||||
$this->getNetworkSession()->syncAbilities($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +455,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
public function setAutoJump(bool $value) : void{
|
||||
if($this->autoJump !== $value){
|
||||
$this->autoJump = $value;
|
||||
$this->getNetworkSession()->syncAdventureSettings($this);
|
||||
$this->getNetworkSession()->syncAdventureSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1180,7 +1180,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return;
|
||||
}
|
||||
|
||||
$oldPos = $this->getLocation();
|
||||
$oldPos = $this->location;
|
||||
$distanceSquared = $newPos->distanceSquared($oldPos);
|
||||
|
||||
$revert = false;
|
||||
@ -1198,7 +1198,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* asking for help if you suffer the consequences of messing with this.
|
||||
*/
|
||||
$this->logger->debug("Moved too fast, reverting movement");
|
||||
$this->logger->debug("Old position: " . $this->location->asVector3() . ", new position: " . $newPos);
|
||||
$this->logger->debug("Old position: " . $oldPos->asVector3() . ", new position: " . $newPos);
|
||||
$revert = true;
|
||||
}elseif(!$this->getWorld()->isInLoadedTerrain($newPos)){
|
||||
$revert = true;
|
||||
@ -1206,9 +1206,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
|
||||
if(!$revert && $distanceSquared != 0){
|
||||
$dx = $newPos->x - $this->location->x;
|
||||
$dy = $newPos->y - $this->location->y;
|
||||
$dz = $newPos->z - $this->location->z;
|
||||
$dx = $newPos->x - $oldPos->x;
|
||||
$dy = $newPos->y - $oldPos->y;
|
||||
$dz = $newPos->z - $oldPos->z;
|
||||
|
||||
$this->move($dx, $dy, $dz);
|
||||
}
|
||||
@ -1558,7 +1558,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$ev = new PlayerBlockPickEvent($this, $block, $item);
|
||||
$existingSlot = $this->inventory->first($item);
|
||||
if($existingSlot === -1 && $this->hasFiniteResources()){
|
||||
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
|
||||
$ev->cancel();
|
||||
}
|
||||
$ev->call();
|
||||
@ -2339,6 +2339,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
parent::syncNetworkData($properties);
|
||||
|
||||
$properties->setGenericFlag(EntityMetadataFlags::ACTION, $this->startAction > -1);
|
||||
$properties->setGenericFlag(EntityMetadataFlags::HAS_COLLISION, $this->hasBlockCollision());
|
||||
|
||||
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
|
||||
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
|
||||
|
@ -163,7 +163,7 @@ class AsyncPool{
|
||||
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
|
||||
}
|
||||
|
||||
$task->progressUpdates = new \Threaded;
|
||||
$task->progressUpdates = new \Threaded();
|
||||
$task->setSubmitted();
|
||||
|
||||
$this->getWorker($worker)->stack($task);
|
||||
|
@ -33,19 +33,31 @@ use function spl_object_id;
|
||||
/**
|
||||
* Class used to run async tasks in other threads.
|
||||
*
|
||||
* An AsyncTask does not have its own thread. It is queued into an AsyncPool and executed if there is an async worker
|
||||
* with no AsyncTask running. Therefore, an AsyncTask SHOULD NOT execute for more than a few seconds. For tasks that
|
||||
* run for a long time or infinitely, start another thread instead.
|
||||
* An AsyncTask is run by a thread pool of reusable threads, and doesn't have its own dedicated thread. A thread is
|
||||
* usually chosen from the pool at random to run the task (though a specific thread in the pool may be selected
|
||||
* manually, if needed).
|
||||
* Reusing threads this way has a much lower performance cost than starting an entirely new thread for every operation.
|
||||
* AsyncTasks are therefore suitable for brief CPU-bound tasks, such as world generation, compression/decompression of
|
||||
* data, etc.
|
||||
*
|
||||
* AsyncTask SHOULD NOT be used for I/O-bound tasks, such as network I/O, file I/O, database I/O, etc. The server's
|
||||
* central AsyncPool is used for things like compressing network packets for sending, so using AsyncTask for I/O will
|
||||
* slow the whole server down, stall chunk loading, etc.
|
||||
*
|
||||
* An AsyncTask SHOULD NOT run for more than a few seconds. For tasks that run for a long time or indefinitely, create
|
||||
* a dedicated thread instead.
|
||||
*
|
||||
* The Server instance is not accessible inside {@link AsyncTask::onRun()}. It can only be accessed in the main server
|
||||
* thread, e.g. during {@link AsyncTask::onCompletion()} or {@link AsyncTask::onProgressUpdate()}. This means that
|
||||
* whatever you do in onRun() must be able to work without the Server instance.
|
||||
*
|
||||
* WARNING: Any non-Threaded objects WILL BE SERIALIZED when assigned to members of AsyncTasks or other Threaded object.
|
||||
* If later accessed from said Threaded object, you will be operating on a COPY OF THE OBJECT, NOT THE ORIGINAL OBJECT.
|
||||
* If you want to store non-serializable objects to access when the task completes, store them using
|
||||
* {@link AsyncTask::storeLocal}.
|
||||
*
|
||||
* WARNING: As of pthreads v3.1.6, arrays are converted to Volatile objects when assigned as members of Threaded objects.
|
||||
* WARNING: Arrays are converted to Volatile objects when assigned as members of Threaded objects.
|
||||
* Keep this in mind when using arrays stored as members of your AsyncTask.
|
||||
*
|
||||
* WARNING: Do not call PocketMine-MP API methods from other Threads!!
|
||||
*/
|
||||
abstract class AsyncTask extends \Threaded{
|
||||
/**
|
||||
|
@ -56,7 +56,9 @@ abstract class Worker extends \Worker{
|
||||
$this->isKilled = true;
|
||||
|
||||
if(!$this->isShutdown()){
|
||||
while($this->unstack() !== null);
|
||||
$this->synchronized(function() : void{
|
||||
while($this->unstack() !== null);
|
||||
});
|
||||
$this->notify();
|
||||
$this->shutdown();
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ trait EnumTrait{
|
||||
* This is overridden to change the return typehint.
|
||||
*
|
||||
* @return self[]
|
||||
* @phpstan-return array<string, self>
|
||||
*/
|
||||
public static function getAll() : array{
|
||||
//phpstan doesn't support generic traits yet :(
|
||||
|
@ -29,7 +29,10 @@ use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
|
||||
trait RegistryTrait{
|
||||
/** @var object[] */
|
||||
/**
|
||||
* @var object[]
|
||||
* @phpstan-var array<string, object>
|
||||
*/
|
||||
private static $members = null;
|
||||
|
||||
private static function verifyName(string $name) : void{
|
||||
@ -107,6 +110,7 @@ trait RegistryTrait{
|
||||
|
||||
/**
|
||||
* @return object[]
|
||||
* @phpstan-return array<string, object>
|
||||
*/
|
||||
private static function _registryGetAll() : array{
|
||||
self::checkInit();
|
||||
|
@ -38,6 +38,7 @@ class ReversePriorityQueue extends \SplPriorityQueue{
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function compare($priority1, $priority2){
|
||||
//TODO: this will crash if non-numeric priorities are used
|
||||
return (int) -($priority1 - $priority2);
|
||||
|
@ -28,7 +28,7 @@ trait SingletonTrait{
|
||||
private static $instance = null;
|
||||
|
||||
private static function make() : self{
|
||||
return new self;
|
||||
return new self();
|
||||
}
|
||||
|
||||
public static function getInstance() : self{
|
||||
|
@ -69,6 +69,7 @@ use function mb_check_encoding;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_contents;
|
||||
use function ob_start;
|
||||
use function opcache_get_status;
|
||||
use function ord;
|
||||
use function php_uname;
|
||||
use function phpversion;
|
||||
@ -108,6 +109,7 @@ final class Utils{
|
||||
|
||||
private static ?string $os = null;
|
||||
private static ?UuidInterface $serverUniqueId = null;
|
||||
private static ?int $cpuCores = null;
|
||||
|
||||
/**
|
||||
* Returns a readable identifier for the given Closure, including file and line.
|
||||
@ -295,14 +297,11 @@ final class Utils{
|
||||
}
|
||||
|
||||
public static function getCoreCount(bool $recalculate = false) : int{
|
||||
static $processors = 0;
|
||||
|
||||
if($processors > 0 && !$recalculate){
|
||||
return $processors;
|
||||
}else{
|
||||
$processors = 0;
|
||||
if(self::$cpuCores !== null && !$recalculate){
|
||||
return self::$cpuCores;
|
||||
}
|
||||
|
||||
$processors = 0;
|
||||
switch(Utils::getOS()){
|
||||
case Utils::OS_LINUX:
|
||||
case Utils::OS_ANDROID:
|
||||
@ -326,7 +325,7 @@ final class Utils{
|
||||
$processors = (int) getenv("NUMBER_OF_PROCESSORS");
|
||||
break;
|
||||
}
|
||||
return $processors;
|
||||
return self::$cpuCores = $processors;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -365,12 +364,6 @@ final class Utils{
|
||||
$ord -= 0x100;
|
||||
}
|
||||
$hash = 31 * $hash + $ord;
|
||||
while($hash > 0x7FFFFFFF){
|
||||
$hash -= 0x100000000;
|
||||
}
|
||||
while($hash < -0x80000000){
|
||||
$hash += 0x100000000;
|
||||
}
|
||||
$hash &= 0xFFFFFFFF;
|
||||
}
|
||||
return $hash;
|
||||
@ -486,8 +479,8 @@ final class Utils{
|
||||
*/
|
||||
public static function currentTrace(int $skipFrames = 0) : array{
|
||||
++$skipFrames; //omit this frame from trace, in addition to other skipped frames
|
||||
if(function_exists("xdebug_get_function_stack")){
|
||||
$trace = array_reverse(xdebug_get_function_stack());
|
||||
if(function_exists("xdebug_get_function_stack") && count($trace = @xdebug_get_function_stack()) !== 0){
|
||||
$trace = array_reverse($trace);
|
||||
}else{
|
||||
$e = new \Exception();
|
||||
$trace = $e->getTrace();
|
||||
@ -634,4 +627,30 @@ final class Utils{
|
||||
public static function checkLocationNotInfOrNaN(Location $location) : void{
|
||||
self::checkVector3NotInfOrNaN($location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an integer describing the current OPcache JIT setting.
|
||||
* @see https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
|
||||
*/
|
||||
public static function getOpcacheJitMode() : ?int{
|
||||
if(
|
||||
function_exists('opcache_get_status') &&
|
||||
($opcacheStatus = opcache_get_status(false)) !== false &&
|
||||
isset($opcacheStatus["jit"]["on"])
|
||||
){
|
||||
$jit = $opcacheStatus["jit"];
|
||||
if($jit["on"] === true){
|
||||
return (($jit["opt_flags"] >> 2) * 1000) +
|
||||
(($jit["opt_flags"] & 0x03) * 100) +
|
||||
($jit["kind"] * 10) +
|
||||
$jit["opt_level"];
|
||||
}
|
||||
|
||||
//jit available, but disabled
|
||||
return 0;
|
||||
}
|
||||
|
||||
//jit not available
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,8 @@ class World implements ChunkManager{
|
||||
|
||||
/** @var Entity[] */
|
||||
public $updateEntities = [];
|
||||
|
||||
private bool $inDynamicStateRecalculation = false;
|
||||
/** @var Block[][] */
|
||||
private array $blockCache = [];
|
||||
|
||||
@ -1536,17 +1538,15 @@ class World implements ChunkManager{
|
||||
|
||||
$block->position($this, $x, $y, $z);
|
||||
|
||||
static $dynamicStateRead = false;
|
||||
|
||||
if($dynamicStateRead){
|
||||
if($this->inDynamicStateRecalculation){
|
||||
//this call was generated by a parent getBlock() call calculating dynamic stateinfo
|
||||
//don't calculate dynamic state and don't add to block cache (since it won't have dynamic state calculated).
|
||||
//this ensures that it's impossible for dynamic state properties to recursively depend on each other.
|
||||
$addToCache = false;
|
||||
}else{
|
||||
$dynamicStateRead = true;
|
||||
$this->inDynamicStateRecalculation = true;
|
||||
$block->readStateFromWorld();
|
||||
$dynamicStateRead = false;
|
||||
$this->inDynamicStateRecalculation = false;
|
||||
}
|
||||
|
||||
if($addToCache && $relativeBlockHash !== null){
|
||||
@ -2492,7 +2492,7 @@ class World implements ChunkManager{
|
||||
continue;
|
||||
}
|
||||
if($entity === null){
|
||||
$saveIdTag = $nbt->getTag("id") ?? $nbt->getTag("identifier");
|
||||
$saveIdTag = $nbt->getTag("identifier") ?? $nbt->getTag("id");
|
||||
$saveId = "<unknown>";
|
||||
if($saveIdTag instanceof StringTag){
|
||||
$saveId = $saveIdTag->getValue();
|
||||
|
@ -48,7 +48,7 @@ final class WorldCreationOptions{
|
||||
}
|
||||
|
||||
public static function create() : self{
|
||||
return new self;
|
||||
return new self();
|
||||
}
|
||||
|
||||
/** @phpstan-return class-string<Generator> */
|
||||
|
@ -123,6 +123,7 @@ class Normal extends Generator{
|
||||
private function pickBiome(int $x, int $z) : Biome{
|
||||
$hash = $x * 2345803 ^ $z * 9236449 ^ $this->seed;
|
||||
$hash *= $hash + 223;
|
||||
$hash = (int) $hash;
|
||||
$xNoise = $hash >> 20 & 3;
|
||||
$zNoise = $hash >> 22 & 3;
|
||||
if($xNoise == 3){
|
||||
|
@ -23,25 +23,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\particle;
|
||||
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Skin;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
|
||||
use pocketmine\network\mcpe\protocol\AddPlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\AdventureSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerListPacket;
|
||||
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
|
||||
use pocketmine\network\mcpe\protocol\AddActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\RemoveActorPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\DeviceOS;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\ByteMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\FloatMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\IntMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\LongMetadataProperty;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function str_repeat;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\StringMetadataProperty;
|
||||
|
||||
class FloatingTextParticle implements Particle{
|
||||
//TODO: HACK!
|
||||
@ -94,39 +89,34 @@ class FloatingTextParticle implements Particle{
|
||||
}
|
||||
|
||||
if(!$this->invisible){
|
||||
$uuid = Uuid::uuid4();
|
||||
$name = $this->title . ($this->text !== "" ? "\n" . $this->text : "");
|
||||
|
||||
$p[] = PlayerListPacket::add([PlayerListEntry::createAdditionEntry($uuid, $this->entityId, $name, SkinAdapterSingleton::get()->toSkinData(new Skin("Standard_Custom", str_repeat("\x00", 8192))))]);
|
||||
|
||||
$actorFlags = (
|
||||
1 << EntityMetadataFlags::IMMOBILE
|
||||
);
|
||||
$actorMetadata = [
|
||||
EntityMetadataProperties::FLAGS => new LongMetadataProperty($actorFlags),
|
||||
EntityMetadataProperties::SCALE => new FloatMetadataProperty(0.01) //zero causes problems on debug builds
|
||||
EntityMetadataProperties::SCALE => new FloatMetadataProperty(0.01), //zero causes problems on debug builds
|
||||
EntityMetadataProperties::BOUNDING_BOX_WIDTH => new FloatMetadataProperty(0.0),
|
||||
EntityMetadataProperties::BOUNDING_BOX_HEIGHT => new FloatMetadataProperty(0.0),
|
||||
EntityMetadataProperties::NAMETAG => new StringMetadataProperty($name),
|
||||
EntityMetadataProperties::VARIANT => new IntMetadataProperty(RuntimeBlockMapping::getInstance()->toRuntimeId(VanillaBlocks::AIR()->getFullId())),
|
||||
EntityMetadataProperties::ALWAYS_SHOW_NAMETAG => new ByteMetadataProperty(1),
|
||||
];
|
||||
$p[] = AddPlayerPacket::create(
|
||||
$uuid,
|
||||
$name,
|
||||
$p[] = AddActorPacket::create(
|
||||
$this->entityId, //TODO: actor unique ID
|
||||
$this->entityId,
|
||||
"",
|
||||
$pos, //TODO: check offset
|
||||
EntityIds::FALLING_BLOCK,
|
||||
$pos, //TODO: check offset (0.49?)
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ItemStackWrapper::legacy(ItemStack::null()),
|
||||
GameMode::SURVIVAL,
|
||||
$actorMetadata,
|
||||
AdventureSettingsPacket::create(0, 0, 0, 0, 0, $this->entityId),
|
||||
0,
|
||||
[],
|
||||
"",
|
||||
DeviceOS::UNKNOWN
|
||||
$actorMetadata,
|
||||
[]
|
||||
);
|
||||
|
||||
$p[] = PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($uuid)]);
|
||||
}
|
||||
|
||||
return $p;
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\world\particle;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\convert\ItemTranslator;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\ParticleIds;
|
||||
|
||||
@ -32,6 +33,7 @@ class ItemBreakParticle implements Particle{
|
||||
public function __construct(private Item $item){}
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelEventPacket::standardParticle(ParticleIds::ITEM_BREAK, ($this->item->getId() << 16) | $this->item->getMeta(), $pos)];
|
||||
[$id, $meta] = ItemTranslator::getInstance()->toNetworkId($this->item->getId(), $this->item->getMeta());
|
||||
return [LevelEventPacket::standardParticle(ParticleIds::ITEM_BREAK, ($id << 16) | $meta, $pos)];
|
||||
}
|
||||
}
|
||||
|
32
tests/phpstan/analyse-for-current-php-version.neon.php
Normal file
32
tests/phpstan/analyse-for-current-php-version.neon.php
Normal 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);
|
||||
|
||||
/*
|
||||
* This hack works around a "feature" in PHPStan which locks in analysis version based on platform.php in composer.json
|
||||
* See https://github.com/phpstan/phpstan/issues/7701 for details
|
||||
*/
|
||||
return [
|
||||
'parameters' => [
|
||||
'phpVersion' => PHP_VERSION_ID
|
||||
]
|
||||
];
|
@ -131,7 +131,7 @@ parameters:
|
||||
path: ../../../src/block/DragonEgg.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int\\<0, max\\> given\\.$#"
|
||||
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int\\<0, 255\\> given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/DragonEgg.php
|
||||
|
||||
@ -335,21 +335,6 @@ parameters:
|
||||
count: 3
|
||||
path: ../../../src/block/Mycelium.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/RedMushroom.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/RedMushroom.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/RedMushroom.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#"
|
||||
count: 1
|
||||
@ -701,7 +686,7 @@ parameters:
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAdventureSettings\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#"
|
||||
message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAbilities\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#"
|
||||
count: 2
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
@ -790,18 +775,8 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginBase.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to string\\.$#"
|
||||
count: 5
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$data of static method pocketmine\\\\permission\\\\PermissionParser\\:\\:loadPermissions\\(\\) expects array\\<string, array\\<string, mixed\\>\\>, mixed given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
@ -810,11 +785,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$name of static method pocketmine\\\\plugin\\\\PluginEnableOrder\\:\\:fromString\\(\\) expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#"
|
||||
count: 1
|
||||
@ -825,26 +795,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$authors \\(array\\<string\\>\\) does not accept array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$authors \\(array\\<string\\>\\) does not accept array\\<mixed\\>\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$depend \\(array\\<string\\>\\) does not accept array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$loadBefore \\(array\\<string\\>\\) does not accept array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$main \\(string\\) does not accept mixed\\.$#"
|
||||
count: 1
|
||||
@ -855,16 +805,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$softDepend \\(array\\<string\\>\\) does not accept array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$srcNamespacePrefix \\(string\\) does not accept mixed\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method addChild\\(\\) on pocketmine\\\\permission\\\\Permission\\|null\\.$#"
|
||||
count: 4
|
||||
|
@ -10,8 +10,3 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/event/RegisteredListener.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\event\\\\RegisteredListener\\:\\:\\$handler type has no signature specified for Closure\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/event/RegisteredListener.php
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool\\)\\: mixed\\) does not accept Closure\\|null\\.$#"
|
||||
message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool=\\)\\: mixed\\) does not accept Closure\\|null\\.$#"
|
||||
count: 1
|
||||
path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php
|
||||
|
||||
|
@ -5,11 +5,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/block/BaseBanner.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\block\\\\tile\\\\TileFactory\\:\\:\\$saveNames \\(array\\<class\\-string\\<pocketmine\\\\block\\\\tile\\\\Tile\\>, string\\>\\) does not accept array\\<class\\-string\\<pocketmine\\\\block\\\\tile\\\\Tile\\>, bool\\|string\\>\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/tile/TileFactory.php
|
||||
|
||||
-
|
||||
message: "#^Comparison operation \"\\<\" between int\\<1, max\\> and 1 is always false\\.$#"
|
||||
count: 1
|
||||
@ -40,11 +35,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/handler/InGamePacketHandler.php
|
||||
|
||||
-
|
||||
message: "#^Negated boolean expression is always true\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/handler/InGamePacketHandler.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#"
|
||||
count: 1
|
||||
@ -65,6 +55,11 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
|
||||
-
|
||||
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
|
||||
-
|
||||
message: "#^Static property pocketmine\\\\scheduler\\\\AsyncTask\\:\\:\\$threadLocalStorage \\(ArrayObject\\<int, array\\<string, mixed\\>\\>\\|null\\) does not accept non\\-empty\\-array\\<int, non\\-empty\\-array\\<string, mixed\\>\\>\\|ArrayObject\\<int, array\\<string, mixed\\>\\>\\.$#"
|
||||
count: 1
|
||||
@ -81,12 +76,17 @@ parameters:
|
||||
path: ../../../src/thread/Worker.php
|
||||
|
||||
-
|
||||
message: "#^Dead catch \\- JsonException is never thrown in the try block\\.$#"
|
||||
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\world\\\\World\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/utils/Config.php
|
||||
path: ../../../src/world/WorldManager.php
|
||||
|
||||
-
|
||||
message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#"
|
||||
count: 2
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Casting to int something that's already int\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/world/generator/normal/Normal.php
|
||||
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\block;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use pocketmine\block\utils\BrewingStandSlot;
|
||||
use function array_values;
|
||||
use function count;
|
||||
|
||||
class BrewingStandTest extends TestCase{
|
||||
@ -33,7 +34,7 @@ class BrewingStandTest extends TestCase{
|
||||
* @phpstan-return \Generator<int, array{list<BrewingStandSlot>}, void, void>
|
||||
*/
|
||||
public function slotsProvider() : \Generator{
|
||||
yield [BrewingStandSlot::getAll()];
|
||||
yield [array_values(BrewingStandSlot::getAll())];
|
||||
yield [[BrewingStandSlot::EAST()]];
|
||||
yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]];
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -35,20 +35,32 @@ use function fopen;
|
||||
use function fwrite;
|
||||
use function getcwd;
|
||||
use function ksort;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
use const SORT_STRING;
|
||||
use const STDERR;
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
if(count($argv) > 2){
|
||||
fwrite(STDERR, "Required arguments: md|rst\n");
|
||||
exit(1);
|
||||
}
|
||||
$format = $argv[1] ?? "md";
|
||||
if($format !== "md" && $format !== "rst"){
|
||||
fwrite(STDERR, "Invalid format, expected either \"md\" or \"rst\"\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function markdownify(string $name) : string{
|
||||
return str_replace(['.', '`', ' '], ['', '', '-'], strtolower($name));
|
||||
}
|
||||
DefaultPermissions::registerCorePermissions();
|
||||
|
||||
$cwd = Utils::assumeNotFalse(getcwd());
|
||||
$output = Path::join($cwd, "core-permissions.md");
|
||||
$output = Path::join($cwd, "core-permissions.$format");
|
||||
echo "Writing output to $output\n";
|
||||
$doc = fopen($output, "wb");
|
||||
if($doc === false){
|
||||
@ -59,36 +71,92 @@ if($doc === false){
|
||||
$permissions = PermissionManager::getInstance()->getPermissions();
|
||||
ksort($permissions, SORT_STRING);
|
||||
|
||||
fwrite($doc, "# PocketMine-MP Core Permissions\n");
|
||||
fwrite($doc, "Generated from PocketMine-MP " . VersionInfo::VERSION()->getFullVersion() . "\n");
|
||||
$title = "List of " . VersionInfo::NAME . " core permissions";
|
||||
if($format === "md"){
|
||||
fwrite($doc, "# $title\n");
|
||||
}else{
|
||||
fwrite($doc, ".. _corepermissions:\n\n");
|
||||
fwrite($doc, "$title\n");
|
||||
fwrite($doc, str_repeat("=", strlen($title)) . "\n\n");
|
||||
}
|
||||
|
||||
fwrite($doc, "Generated from " . VersionInfo::NAME . " " . VersionInfo::VERSION()->getFullVersion() . "\n");
|
||||
fwrite($doc, "\n");
|
||||
fwrite($doc, "| Name | Description | Implied permissions |\n");
|
||||
fwrite($doc, "|:-----|:------------|:-------------------:|\n");
|
||||
if($format === "md"){
|
||||
fwrite($doc, "| Name | Description | Implied permissions |\n");
|
||||
fwrite($doc, "|:-----|:------------|:-------------------:|\n");
|
||||
}else{
|
||||
fwrite($doc, ".. list-table::\n");
|
||||
fwrite($doc, " :header-rows: 1\n\n");
|
||||
fwrite($doc, " * - Name\n");
|
||||
fwrite($doc, " - Description\n");
|
||||
fwrite($doc, " - Implied permissions\n");
|
||||
fwrite($doc, "\n");
|
||||
}
|
||||
foreach($permissions as $permission){
|
||||
$link = count($permission->getChildren()) === 0 ? "N/A" : "[Jump](#" . markdownify("Permissions implied by `" . $permission->getName() . "`") . ")";
|
||||
fwrite($doc, "| `" . $permission->getName() . "` | " . $permission->getDescription() . " | $link |\n");
|
||||
if($format === "md"){
|
||||
$link = count($permission->getChildren()) === 0 ? "N/A" : "[Jump](#" . markdownify("Permissions implied by `" . $permission->getName() . "`") . ")";
|
||||
fwrite($doc, "| `" . $permission->getName() . "` | " . $permission->getDescription() . " | $link |\n");
|
||||
}else{
|
||||
fwrite($doc, " * - ``" . $permission->getName() . "``\n");
|
||||
fwrite($doc, " - " . $permission->getDescription() . "\n");
|
||||
if(count($permission->getChildren()) === 0){
|
||||
fwrite($doc, " - N/A\n");
|
||||
}else{
|
||||
fwrite($doc, " - :ref:`Jump<permissions_implied_by_" . $permission->getName() . ">`\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwrite($doc, "\n\n");
|
||||
fwrite($doc, "## Implied permissions\n");
|
||||
fwrite($doc, "Some permissions automatically grant (or deny) other permissions by default when granted. These are referred to as **implied permissions**.<br>\n");
|
||||
fwrite($doc, "Permissions may imply permissions which in turn imply other permissions (e.g. `pocketmine.group.operator` implies `pocketmine.group.user`, which in turn implies `pocketmine.command.help`).<br>\n");
|
||||
fwrite($doc, "Implied permissions can be overridden by explicit permissions from elsewhere.<br>\n");
|
||||
fwrite($doc, "**Note:** When explicitly denied, implied permissions are inverted. This means that \"granted\" becomes \"denied\" and vice versa.\n");
|
||||
|
||||
$title = "Implied permissions";
|
||||
if($format === "md"){
|
||||
fwrite($doc, "## $title\n");
|
||||
}else{
|
||||
fwrite($doc, "$title\n");
|
||||
fwrite($doc, str_repeat("-", strlen($title)) . "\n\n");
|
||||
}
|
||||
$newline = $format === "md" ? "<br>\n" : "\n\n";
|
||||
$code = $format === "md" ? "`" : "``";
|
||||
fwrite($doc, "Some permissions automatically grant (or deny) other permissions by default when granted. These are referred to as **implied permissions**.$newline");
|
||||
fwrite($doc, "Permissions may imply permissions which in turn imply other permissions (e.g. {$code}pocketmine.group.operator{$code} implies {$code}pocketmine.group.user{$code}, which in turn implies {$code}pocketmine.command.help{$code}).$newline");
|
||||
fwrite($doc, "Implied permissions can be overridden by explicit permissions from elsewhere.$newline");
|
||||
fwrite($doc, "**Note:** When explicitly denied, implied permissions are inverted. This means that \"granted\" becomes \"denied\" and vice versa.$newline");
|
||||
fwrite($doc, "\n\n");
|
||||
foreach($permissions as $permission){
|
||||
if(count($permission->getChildren()) === 0){
|
||||
continue;
|
||||
}
|
||||
fwrite($doc, "### Permissions implied by `" . $permission->getName() . "`\n");
|
||||
$title = "Permissions implied by " . $code . $permission->getName() . $code;
|
||||
if($format === "md"){
|
||||
fwrite($doc, "### $title\n");
|
||||
}else{
|
||||
fwrite($doc, ".. _permissions_implied_by_" . $permission->getName() . ":\n\n");
|
||||
fwrite($doc, "$title\n");
|
||||
fwrite($doc, str_repeat("~", strlen($title)) . "\n\n");
|
||||
}
|
||||
fwrite($doc, "Users granted this permission will also be granted/denied the following permissions implicitly:\n\n");
|
||||
|
||||
fwrite($doc, "| Name | Type |\n");
|
||||
fwrite($doc, "|:-----|:----:|\n");
|
||||
$children = $permission->getChildren();
|
||||
ksort($children, SORT_STRING);
|
||||
foreach(Utils::stringifyKeys($children) as $childName => $isGranted){
|
||||
fwrite($doc, "| `$childName` | " . ($isGranted ? "Granted" : "Denied") . " |\n");
|
||||
if($format === "md"){
|
||||
fwrite($doc, "| Name | Type |\n");
|
||||
fwrite($doc, "|:-----|:----:|\n");
|
||||
$children = $permission->getChildren();
|
||||
ksort($children, SORT_STRING);
|
||||
foreach(Utils::stringifyKeys($children) as $childName => $isGranted){
|
||||
fwrite($doc, "| `$childName` | " . ($isGranted ? "Granted" : "Denied") . " |\n");
|
||||
}
|
||||
}else{
|
||||
fwrite($doc, ".. list-table::\n");
|
||||
fwrite($doc, " :header-rows: 1\n\n");
|
||||
fwrite($doc, " * - Name\n");
|
||||
fwrite($doc, " - Type\n");
|
||||
$children = $permission->getChildren();
|
||||
ksort($children, SORT_STRING);
|
||||
foreach(Utils::stringifyKeys($children) as $childName => $isGranted){
|
||||
fwrite($doc, " * - ``$childName``\n");
|
||||
fwrite($doc, " - " . ($isGranted ? "Granted" : "Denied") . "\n");
|
||||
}
|
||||
}
|
||||
fwrite($doc, "\n");
|
||||
}
|
||||
|
Reference in New Issue
Block a user