mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-14 13:25:11 +00:00
Compare commits
315 Commits
Author | SHA1 | Date | |
---|---|---|---|
0ee6cdb058 | |||
97d6a79b25 | |||
8b5e4c1c16 | |||
214a5ddc15 | |||
9f7dfe3355 | |||
9b55d18393 | |||
31465525e3 | |||
74613b9b09 | |||
1cefe24414 | |||
8bf85d4a18 | |||
b5e6dec0c6 | |||
a3306914cc | |||
3b32ea1b0b | |||
7ec32f981e | |||
b0c87e9d06 | |||
99996b62d6 | |||
1cb6d9f5af | |||
0a9b52618d | |||
7ae6425d05 | |||
b5cfab497d | |||
774e23137e | |||
43bc3c7b25 | |||
eb62dc3294 | |||
279056fe2f | |||
cd233b123b | |||
64dd5e3bf6 | |||
4e5cc57560 | |||
95263795a8 | |||
2db86d151f | |||
f0358b09b7 | |||
80a432d9ff | |||
ec9f9a469f | |||
d450264e1c | |||
5e92f55d35 | |||
58c1bfe5d2 | |||
641d35a30f | |||
642630a4d2 | |||
d79e6354a0 | |||
ff63983de4 | |||
588215044e | |||
5c1e9a35a9 | |||
aad9f5fb45 | |||
a8dca190c6 | |||
e5f5fe80f9 | |||
36ab34df29 | |||
dda8ff18b1 | |||
6562335120 | |||
0c463a8721 | |||
a66f966b08 | |||
cca22046ab | |||
d19f0bf7be | |||
ff2391e74a | |||
39e10da88d | |||
beed6efd4e | |||
e5bc4deb12 | |||
2fcff13578 | |||
b4b8ef1c6b | |||
9650b7f03a | |||
a3502a711d | |||
83ddcce987 | |||
732dac6fc1 | |||
d5e3636908 | |||
ef100b248b | |||
d03bbb0426 | |||
93e661aa4e | |||
50efcf7424 | |||
a7ac6070dc | |||
069062f122 | |||
bf7014e0ec | |||
824ed0a56a | |||
b3ccf41307 | |||
a39938e6b6 | |||
d5bf88acc0 | |||
2d0602d19f | |||
3a2a23b236 | |||
1a8c8af523 | |||
1e9d83f014 | |||
6153a2ac70 | |||
ed452b9ccd | |||
c19880e045 | |||
cdbdcb5d67 | |||
29301614e8 | |||
2fdc46c165 | |||
bfd1b2c635 | |||
1671405cd0 | |||
fe982c697b | |||
1572b31b8d | |||
b6f6671a81 | |||
6da467b142 | |||
44af519cd6 | |||
fb31e6085e | |||
e4548da173 | |||
0d5287bf0b | |||
a9361b3f8b | |||
6e4c62744e | |||
9a0ead6deb | |||
d74824c8d5 | |||
d4eb73abe9 | |||
7864294336 | |||
2a910c1cc2 | |||
cd04a3db2e | |||
572def9245 | |||
20f5bed926 | |||
14d17a9546 | |||
b74c092d9b | |||
7bcc663b60 | |||
b3bda788d9 | |||
2cc8a56e68 | |||
57deb60355 | |||
92e47b98f8 | |||
b84c110819 | |||
4fadb63f67 | |||
c83f0896ac | |||
0d29a138fb | |||
421379fc77 | |||
293cea7714 | |||
15645759e9 | |||
6ae7cb288e | |||
7df2719fce | |||
10b8dcfdd1 | |||
c1fbac412e | |||
3feaa18f6c | |||
41970feb57 | |||
cd4bb91676 | |||
2be527060f | |||
6f68c6d8a0 | |||
ac16378410 | |||
1f9dfa77bf | |||
0c7f8470b9 | |||
fc56c041f3 | |||
d6bbf8217d | |||
22486dd75e | |||
37ec1193ea | |||
bda0ca23b4 | |||
b21cd82e94 | |||
1c7b1e9e5d | |||
b87e4d8bd3 | |||
86a2f8e360 | |||
def2f8c145 | |||
ed7c95549d | |||
cfb0cad7e0 | |||
83e5b0adb6 | |||
4650a3bb22 | |||
5e5661de75 | |||
e2f1b10165 | |||
455f9fa92e | |||
dec188d4ad | |||
2db3498891 | |||
a7dfa0907c | |||
f448b2e685 | |||
6a0c54f850 | |||
77a18d0aea | |||
140a809c40 | |||
cb7c136035 | |||
83a136a176 | |||
3f7d8a3777 | |||
3c55db531d | |||
93d4475111 | |||
e4fc523251 | |||
7804172846 | |||
7d29ac8293 | |||
481bda8cd5 | |||
d1c75da14b | |||
89e29448ee | |||
66e70e5b0e | |||
785dc71256 | |||
d459afaa54 | |||
db586233da | |||
23e98a30f5 | |||
5bc7ca6569 | |||
f39d2a9be3 | |||
47d98af6ac | |||
9f97654f6f | |||
82ba7903c8 | |||
441b06f6c7 | |||
112974758e | |||
313850eec4 | |||
a82923acb4 | |||
67887afd6b | |||
3d03bb1301 | |||
c063198b89 | |||
f3ca6de1eb | |||
88eafdd614 | |||
6dd5fec4ea | |||
6866c86d39 | |||
a735a69870 | |||
a0ea74c08f | |||
ca4b8a5827 | |||
f88c4d9a8c | |||
66cd156d80 | |||
222049927a | |||
d72e947d15 | |||
770cca2efa | |||
033dac3d16 | |||
1ee02d7e02 | |||
85678aa356 | |||
1d253bc8c2 | |||
973a56ab28 | |||
9e0b4621be | |||
ffb3af3e0d | |||
b3f03d7ae6 | |||
2585160ca2 | |||
14d3e6c7d5 | |||
65ec318c30 | |||
a25cb3741a | |||
b7a15b6e01 | |||
456439566b | |||
5b89833d5c | |||
fb25e05416 | |||
78b5be8dd0 | |||
0a92e91a30 | |||
b3a13a2f21 | |||
08b9495bce | |||
5779622235 | |||
c16893cbac | |||
7f175b47e6 | |||
0e73ffe555 | |||
1ffd38b37b | |||
bd13f39156 | |||
0c446c276c | |||
0284e65f60 | |||
b0d787b3d3 | |||
65e3ed43d5 | |||
75eba9c9ed | |||
b5a049d1fe | |||
bd9fcffe62 | |||
feffbc2c5b | |||
53b51c99b4 | |||
5cb77c8365 | |||
bf8befc40b | |||
f75ca312cc | |||
d144832928 | |||
709a869045 | |||
ac056044ce | |||
fc8434308b | |||
5426b41447 | |||
af2babec23 | |||
5d5366a7c8 | |||
717ab1989a | |||
83db186b6a | |||
6a4e5aba8b | |||
c13170a00b | |||
98778052bb | |||
e86e8254a8 | |||
1b852ac290 | |||
10b799fadb | |||
bc5008334a | |||
d6af2b12f4 | |||
ad2d59923c | |||
792c1b62b7 | |||
e90abecf38 | |||
575dd47db7 | |||
e4a5defabb | |||
c9626c610b | |||
8fb7fff6b9 | |||
5c8d8ff61f | |||
99b55f7427 | |||
dce8bd6d21 | |||
8fa81242d6 | |||
2f4a9469b6 | |||
4d34885b15 | |||
c5b2488fc1 | |||
d62df585f2 | |||
19d7c2b552 | |||
f7ab0a3b92 | |||
036e06e889 | |||
9343a0b800 | |||
14b4644b03 | |||
464b65b25c | |||
15586ed80e | |||
0f8ad8ecf7 | |||
82b9afef77 | |||
2fc84f6c67 | |||
566f5935a3 | |||
44e4dabf6e | |||
8acc535218 | |||
e9a1cb7ce5 | |||
a21419d120 | |||
c2b599166c | |||
df7a1fcba6 | |||
d77a95e4af | |||
5c72807b16 | |||
5c6927e16c | |||
9abbb85a93 | |||
554182b2cb | |||
d669a6f0c7 | |||
16ed16722a | |||
42f9336f7a | |||
5d9f783037 | |||
01ca14c314 | |||
608c6ed6db | |||
c26631d06d | |||
b75bc61a64 | |||
3724479be3 | |||
eb916fe43d | |||
5e3b3a0700 | |||
e10a624444 | |||
2940547026 | |||
24e72ec109 | |||
dbc0b9634b | |||
040516054f | |||
4e3964ffce | |||
93254523e6 | |||
e00f8e3a32 | |||
e2855aadff | |||
4d6ec66270 | |||
f1a63098bd | |||
4b1052022c | |||
5a8983dd81 | |||
38651fde74 | |||
63ee03a7be | |||
088a2e478c | |||
35fd71eddf | |||
be168beba0 | |||
4b73bedd57 |
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,,}")
|
run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}")
|
||||||
|
|
||||||
- name: Build image for tag
|
- name: Build image for tag
|
||||||
uses: docker/build-push-action@v3.1.0
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for major tag
|
- name: Build image for major tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.1.0
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for minor tag
|
- name: Build image for minor tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.1.0
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
context: ./pocketmine-mp
|
||||||
@ -85,7 +85,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build image for latest tag
|
- name: Build image for latest tag
|
||||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||||
uses: docker/build-push-action@v3.1.0
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
context: ./pocketmine-mp
|
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.22.0
|
||||||
|
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 }}
|
8
.github/workflows/draft-release.yml
vendored
8
.github/workflows/draft-release.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.21.0
|
uses: shivammathur/setup-php@2.22.0
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
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);')
|
echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);')
|
||||||
|
|
||||||
- name: Generate build info
|
- name: Generate build info
|
||||||
run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ 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
|
- name: Upload release artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@ -69,7 +69,7 @@ jobs:
|
|||||||
${{ github.workspace }}/build_info.json
|
${{ github.workspace }}/build_info.json
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: ncipollo/release-action@v1.10.0
|
uses: ncipollo/release-action@v1.12.0
|
||||||
with:
|
with:
|
||||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
|
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
@ -80,4 +80,4 @@ jobs:
|
|||||||
body: |
|
body: |
|
||||||
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
|
**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:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.18]
|
php: [8.0.23, 8.1.10]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Build and prepare PHP cache
|
- name: Build and prepare PHP cache
|
||||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
@ -31,13 +31,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.18]
|
php: [8.0.23, 8.1.10]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
@ -69,13 +69,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.18]
|
php: [8.0.23, 8.1.10]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
@ -107,7 +107,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.18]
|
php: [8.0.23, 8.1.10]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -115,7 +115,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
@ -147,13 +147,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.18]
|
php: [8.0.23, 8.1.10]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440
|
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
@ -195,10 +195,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP and tools
|
- name: Setup PHP and tools
|
||||||
uses: shivammathur/setup-php@2.21.0
|
uses: shivammathur/setup-php@2.22.0
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.0
|
||||||
tools: php-cs-fixer:3.2
|
tools: php-cs-fixer:3.11
|
||||||
|
|
||||||
- name: Run PHP-CS-Fixer
|
- name: Run PHP-CS-Fixer
|
||||||
run: php-cs-fixer fix --dry-run --diff --ansi
|
run: php-cs-fixer fix --dry-run --diff --ansi
|
||||||
|
2
.github/workflows/support.yml
vendored
2
.github/workflows/support.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
support:
|
support:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/support-requests@v2
|
- uses: dessant/support-requests@v3
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
support-label: "Support request"
|
support-label: "Support request"
|
||||||
|
3
.github/workflows/update-php-versions.php
vendored
3
.github/workflows/update-php-versions.php
vendored
@ -22,7 +22,8 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
const VERSIONS = [
|
const VERSIONS = [
|
||||||
"8.0"
|
"8.0",
|
||||||
|
"8.1"
|
||||||
];
|
];
|
||||||
|
|
||||||
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
|
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
|
||||||
|
5
.idea/codeStyles/Project.xml
generated
5
.idea/codeStyles/Project.xml
generated
@ -62,6 +62,11 @@
|
|||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Shell Script">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="neon">
|
<codeStyleSettings language="neon">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
@ -66,10 +66,17 @@ BODY,
|
|||||||
],
|
],
|
||||||
'indentation_type' => true,
|
'indentation_type' => true,
|
||||||
'logical_operators' => true,
|
'logical_operators' => true,
|
||||||
|
'native_constant_invocation' => [
|
||||||
|
'scope' => 'namespaced'
|
||||||
|
],
|
||||||
'native_function_invocation' => [
|
'native_function_invocation' => [
|
||||||
'scope' => 'namespaced',
|
'scope' => 'namespaced',
|
||||||
'include' => ['@all'],
|
'include' => ['@all'],
|
||||||
],
|
],
|
||||||
|
'new_with_braces' => [
|
||||||
|
'named_class' => true,
|
||||||
|
'anonymous_class' => false,
|
||||||
|
],
|
||||||
'no_closing_tag' => true,
|
'no_closing_tag' => true,
|
||||||
'no_empty_phpdoc' => true,
|
'no_empty_phpdoc' => true,
|
||||||
'no_extra_blank_lines' => true,
|
'no_extra_blank_lines' => true,
|
||||||
@ -88,6 +95,12 @@ BODY,
|
|||||||
],
|
],
|
||||||
'sort_algorithm' => 'alpha'
|
'sort_algorithm' => 'alpha'
|
||||||
],
|
],
|
||||||
|
'phpdoc_align' => [
|
||||||
|
'align' => 'vertical',
|
||||||
|
'tags' => [
|
||||||
|
'param',
|
||||||
|
]
|
||||||
|
],
|
||||||
'phpdoc_line_span' => [
|
'phpdoc_line_span' => [
|
||||||
'property' => 'single',
|
'property' => 'single',
|
||||||
'method' => null,
|
'method' => null,
|
||||||
|
@ -18,6 +18,32 @@ Larger contributions like feature additions should be preceded by a [Change Prop
|
|||||||
## Other things you'll need
|
## Other things you'll need
|
||||||
- [git](https://git-scm.com/)
|
- [git](https://git-scm.com/)
|
||||||
|
|
||||||
|
## Choosing a target branch
|
||||||
|
PocketMine-MP has three primary branches of development.
|
||||||
|
|
||||||
|
| Type of change | `stable` | `next-minor` | `next-major` |
|
||||||
|
|:---------------|:--------:|:------------:|:------------:|
|
||||||
|
| Bug fixes | ✔️ | ✔️ | ✔️ |
|
||||||
|
| Improvements to API docs | ✔️ | ✔️ | ✔️ |
|
||||||
|
| Cleaning up code | ❌ | ✔️ | ✔️ |
|
||||||
|
| Changing code formatting or style | ❌ | ✔️ | ✔️ |
|
||||||
|
| Addition of new core features | ❌ | 🟡 Only if non-disruptive | ✔️ |
|
||||||
|
| Changing core behaviour (e.g. making something use threads) | ❌ | ✔️ | ✔️ |
|
||||||
|
| Addition of new configuration options | ❌ | 🟡 Only if optional | ✔️ |
|
||||||
|
| Addition of new API classes, methods or constants | ❌ | ✔️ | ✔️ |
|
||||||
|
| Deprecating API classes, methods or constants | ❌ | ✔️ | ✔️ |
|
||||||
|
| Adding optional parameters to an API method | ❌ | ✔️ | ✔️ |
|
||||||
|
| Changing API behaviour | ❌ | 🟡 Only if backwards-compatible | ✔️ |
|
||||||
|
| Removal of API | ❌ | ❌ | ✔️ |
|
||||||
|
| Backwards-incompatible API change (e.g. renaming a method) | ❌ | ❌ | ✔️ |
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- **Non-disruptive** means that usage should not be significantly altered by the change.
|
||||||
|
- Examples of **non-disruptive** changes include adding new commands, or gameplay features like blocks and items.
|
||||||
|
- Examples of **disruptive** changes include changing the way the server is run, world format changes (since those require downtime for the user to convert their world).
|
||||||
|
- **API** includes all public and protected classes, functions and constants (unless marked as `@internal`).
|
||||||
|
- Private members are not part of the API, **unless in a trait**.
|
||||||
|
|
||||||
## Making a pull request
|
## Making a pull request
|
||||||
The basic procedure to create a pull request is:
|
The basic procedure to create a pull request is:
|
||||||
1. [Fork the repository on GitHub](https://github.com/pmmp/PocketMine-MP/fork). This gives you your own copy of the repository to make changes to.
|
1. [Fork the repository on GitHub](https://github.com/pmmp/PocketMine-MP/fork). This gives you your own copy of the repository to make changes to.
|
||||||
|
@ -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.**
|
**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
|
- Version of PocketMine-MP
|
||||||
- Detailed description of the vulnerability (e.g. how to exploit it, what the effects are)
|
- 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.
|
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';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
if(count($argv) !== 5){
|
if(count($argv) !== 6){
|
||||||
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number>");
|
fwrite(STDERR, "required args: <git hash> <tag name> <github repo (owner/name)> <build number> <github actions run ID>\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,4 +40,5 @@ echo json_encode([
|
|||||||
"details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]",
|
"details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]",
|
||||||
"download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar",
|
"download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar",
|
||||||
"source_url" => "https://github.com/$argv[3]/tree/$argv[2]",
|
"source_url" => "https://github.com/$argv[3]/tree/$argv[2]",
|
||||||
|
"build_log_url" => "https://github.com/$argv[3]/actions/runs/$argv[5]",
|
||||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n";
|
||||||
|
@ -25,7 +25,7 @@ namespace pocketmine\build\generate_known_translation_apis;
|
|||||||
|
|
||||||
use pocketmine\lang\Translatable;
|
use pocketmine\lang\Translatable;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use Webmozart\PathUtil\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function count;
|
use function count;
|
||||||
use function dirname;
|
use function dirname;
|
||||||
|
@ -39,6 +39,7 @@ use function sprintf;
|
|||||||
use function str_replace;
|
use function str_replace;
|
||||||
use function substr;
|
use function substr;
|
||||||
use const SORT_STRING;
|
use const SORT_STRING;
|
||||||
|
use const STDERR;
|
||||||
|
|
||||||
if(count($argv) !== 2){
|
if(count($argv) !== 2){
|
||||||
fwrite(STDERR, "Provide a path to process\n");
|
fwrite(STDERR, "Provide a path to process\n");
|
||||||
|
Submodule build/php updated: f292501a70...27cc1fcdfa
@ -40,12 +40,13 @@ use function rtrim;
|
|||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function str_replace;
|
use function str_replace;
|
||||||
use function unlink;
|
use function unlink;
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $strings
|
* @param string[] $strings
|
||||||
*
|
*
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
|
53
changelogs/4.10.md
Normal file
53
changelogs/4.10.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
**For Minecraft: Bedrock Edition 1.19.40**
|
||||||
|
|
||||||
|
### 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.10.0
|
||||||
|
Released 26th October 2022.
|
||||||
|
|
||||||
|
## General
|
||||||
|
- Added support for Minecraft: Bedrock Edition 1.19.40.
|
||||||
|
- Removed support for older versions.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed incorrect command descriptions showing in `/help` when multiple commands use the same name. Previously, the most recently registered command would show, even though it wouldn't actually be invoked.
|
||||||
|
- Fixed splash potions affecting players in spectator mode.
|
||||||
|
- Fixed `World->addParticle()` sending particles to players who couldn't possibly see them when a list of targets was used.
|
||||||
|
- Fixed `World->addSound()` sending sounds to players who couldn't possibly hear them when a list of targets was used.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- Improved type information available for various API methods in `World`.
|
||||||
|
|
||||||
|
# 4.10.1
|
||||||
|
Released 7th November 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed spawning in the void if spawn terrain in a world is solid at the default spawn position.
|
||||||
|
- Fixed totems of undying activating when the player has 1 HP remaining.
|
||||||
|
- Fixed durable items such as tools becoming unbreakable when in stacks larger than 1. Now, the durability correctly resets when the tool breaks.
|
||||||
|
- TPS below 12 now correctly shows as red in `/status`. Previously, it showed as orange due to a condition ordering bug.
|
||||||
|
- Improved handling of missing arguments in user-defined `pocketmine.yml` command aliases. Previously, missing arguments would be filled with an empty string, which caused a variety of unexpected behaviour.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Added validation for the array given to `BaseInventory->setContents()` to ensure that it contains only `Item` instances.
|
||||||
|
- Silenced `PlayerAuthInputPacket` spam when the session is in the "spawn response" state.
|
||||||
|
- Updated to PHPStan 1.9.
|
||||||
|
|
||||||
|
# 4.10.2
|
||||||
|
Released 25th November 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed crashes on macOS and Linux when using console colours without the `TERM` environment variable set.
|
||||||
|
- Fixed crashdumps not being generated when error messages contained invalid UTF-8 characters.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- Clarified documentation of caching behaviour for `Internet::getIP()`.
|
||||||
|
- Added and improved documentation for many `Inventory` methods.
|
||||||
|
- Rewritten documentation for `PlayerCreationEvent` with warnings and more detail.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Non-arrow projectile damage is now unscaled. Scaling according to velocity is only applied to arrows. This currently doesn't cause any observable change in behaviour, but is required for future additions.
|
92
changelogs/4.11-beta.md
Normal file
92
changelogs/4.11-beta.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
**For Minecraft: Bedrock Edition 1.19.40**
|
||||||
|
|
||||||
|
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
|
||||||
|
|
||||||
|
### 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.11.0-BETA1
|
||||||
|
Released 7th November 2022.
|
||||||
|
|
||||||
|
## General
|
||||||
|
- Packet receive timings have now been split into two subcategories - Decode and Handle.
|
||||||
|
- Console command entry can now be disabled via the `console.enable-input` setting in `pocketmine.yml`.
|
||||||
|
- Best suited for headless servers (e.g. in a Docker container) where the console will never be used anyway.
|
||||||
|
- Disabling the console reader slightly reduces memory usage, because console reading currently requires an additional subprocess.
|
||||||
|
- Console command output now appears on the terminal only, and is not written to the log file.
|
||||||
|
- The output from console commands now appears with a `Command output |` prefix, instead of as a log message.
|
||||||
|
- Introduced validation for the `--data` and `--plugins` command line options.
|
||||||
|
- Encrypted resource packs are now supported, by means of adding a `.key` file alongside the pack in the `resource_packs` folder.
|
||||||
|
- e.g. `MyEncryptedPack.zip` -> `MyEncryptedPack.zip.key`
|
||||||
|
|
||||||
|
## Gameplay
|
||||||
|
- Fixed supporting blocks of dead bush to be in line with vanilla.
|
||||||
|
- Sugarcane can now be grown using bonemeal on any part of the sugarcane. Previously, it only worked when used on the bottom block.
|
||||||
|
- Fixed modifier values for Instant Damage and Regeneration effects.
|
||||||
|
|
||||||
|
## API
|
||||||
|
### General
|
||||||
|
- Plugins are now always disabled before their dependencies, to ensure that they are able to shutdown properly (e.g. a core plugin depending on a database plugin may want to save data to a DB during `onDisable()`).
|
||||||
|
- [`webmozart/path-util`](https://packagist.org/packages/webmozart/path-util) has been deprecated, and will be dropped in favour of [`symfony/filesystem`](https://packagist.org/packages/symfony/filesystem) in PM5.
|
||||||
|
- To prepare for this change, simply replace any usage of `Webmozart\PathUtil\Path` with `Symfony\Component\Filesystem\Path`, which is available as a dependency in this release.
|
||||||
|
|
||||||
|
### `pocketmine`
|
||||||
|
- The following API methods are now deprecated:
|
||||||
|
- `Server->getPlayerByPrefix()`
|
||||||
|
|
||||||
|
### `pocketmine\entity`
|
||||||
|
- `EntitySpawnEvent` and `ItemSpawnEvent` are now fired on the first tick after the entity is added to the world. Previously, these events were called directly from the entity constructor, making it impossible to get properties like velocity which are often set after the entity is created.
|
||||||
|
- The following API methods are now deprecated:
|
||||||
|
- `Living->hasLineOfSight()`
|
||||||
|
|
||||||
|
### `pocketmine\item`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public Armor->clearCustomColor() : $this`
|
||||||
|
|
||||||
|
### `pocketmine\inventory\transaction`
|
||||||
|
- Introduced a `TransactionBuilder` class. This makes it less of a hassle to build an `InventoryTransaction` server-side, since the regular `Inventory` API methods can be used, rather than having to manually create `SlotChangeAction`s.
|
||||||
|
|
||||||
|
### `pocketmine\player`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public Player->sendToastNotification(string $title, string $body) : void` - makes a grey box appear at the top of the player's screen containing the specified message
|
||||||
|
|
||||||
|
### `pocketmine\utils`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public static TextFormat::addBase(string $baseFormat, string $string) : string` - used for coloured log messages, changes the base formatting of a string by inserting the given formatting codes after every RESET code
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Improved performance of `ContainerTrait` dropping items on block destroy. (24e72ec109c1442b09558df89b6833cf2f2e0ec7)
|
||||||
|
- Avoid repeated calls to `Position->getWorld()` (use local variables). (2940547026db40ce76deb46e992870de3ead79ad)
|
||||||
|
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. (e90abecf38d9c57635fa0497514bba7e546a2469)
|
||||||
|
- Console polling is now done on the main thread (no longer a performance concern).
|
||||||
|
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie.
|
||||||
|
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged.
|
||||||
|
- `DelegateInventory` now uses `WeakReference` to track its inventory listener. This allows the delegate to be reused.
|
||||||
|
|
||||||
|
# 4.11.0-BETA2
|
||||||
|
Released 13th November 2022.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
- The `chunk-ticking.per-tick` setting is now deprecated, and will be removed in a future release.
|
||||||
|
- The functionality of this setting has been removed, since it caused more problems than it solved.
|
||||||
|
- Setting it to zero will still disable chunk ticking (for now), but this should now be done by setting `chunk-ticking.tick-radius` to `0` instead.
|
||||||
|
|
||||||
|
## Gameplay
|
||||||
|
- Improved chunk random ticking:
|
||||||
|
- Removed the limit on chunks ticked per tick, and its associated config option is no longer respected.
|
||||||
|
- This change significantly improves crop and plant growth with large numbers of players, but may cause higher CPU usage.
|
||||||
|
- This limit was causing a linear decrease in chunk ticking speed with larger numbers of players, leading to worsened gameplay experience.
|
||||||
|
- Every chunk within the configured tick radius of a player will be ticked. Previously, chunks were randomly selected from the radius.
|
||||||
|
- Implemented Darkness effect.
|
||||||
|
|
||||||
|
## API
|
||||||
|
### `pocketmine\world`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public World->getChunkTickRadius() : int` - returns the world's simulation radius
|
||||||
|
- `public World->setChunkTickRadius(int $radius) : void` - sets the world's simulation radius
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Non-arrow projectile damage is now unscaled. Scaling according to velocity is only applied to arrows. This currently doesn't cause any observable change in behaviour, but is required for future additions.
|
106
changelogs/4.11.md
Normal file
106
changelogs/4.11.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
**For Minecraft: Bedrock Edition 1.19.40**
|
||||||
|
|
||||||
|
This is a minor feature release for PocketMine-MP, introducing some new features and improvements.
|
||||||
|
|
||||||
|
### 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.11.0
|
||||||
|
Released 25th November 2022.
|
||||||
|
|
||||||
|
## General
|
||||||
|
- Packet receive timings have now been split into two subcategories - Decode and Handle.
|
||||||
|
- Console command entry can now be disabled via the `console.enable-input` setting in `pocketmine.yml`.
|
||||||
|
- Best suited for headless servers (e.g. in a Docker container) where the console will never be used anyway.
|
||||||
|
- Disabling the console reader slightly reduces memory usage, because console reading currently requires an additional subprocess.
|
||||||
|
- Console command output now appears on the terminal only, and is not written to the log file.
|
||||||
|
- The output from console commands now appears with a `Command output |` prefix, instead of as a log message.
|
||||||
|
- User-defined `pocketmine.yml` custom commands now use a generic description which makes clear the command is config-defined.
|
||||||
|
- Introduced validation for the `--data` and `--plugins` command line options.
|
||||||
|
- Encrypted resource packs are now supported, by means of adding a `.key` file alongside the pack in the `resource_packs` folder.
|
||||||
|
- e.g. `MyEncryptedPack.zip` -> `MyEncryptedPack.zip.key`
|
||||||
|
- The file must contain the raw key bytes, and must not end with a newline.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
- The `chunk-ticking.per-tick` setting is now deprecated, and will be removed in a future release.
|
||||||
|
- The functionality of this setting has been removed, since it caused more problems than it solved.
|
||||||
|
- Setting it to zero will still disable chunk ticking (for now), but this should now be done by setting `chunk-ticking.tick-radius` to `0` instead.
|
||||||
|
|
||||||
|
## Gameplay
|
||||||
|
- Fixed supporting blocks of dead bush to be in line with vanilla.
|
||||||
|
- Sugarcane can now be grown using bonemeal on any part of the sugarcane. Previously, it only worked when used on the bottom block.
|
||||||
|
- Fixed missing sounds when adding, rotating, or removing items in item frames.
|
||||||
|
- Fixed modifier values for Instant Damage and Regeneration effects.
|
||||||
|
- Implemented Darkness effect.
|
||||||
|
- Improved chunk random ticking:
|
||||||
|
- Removed the limit on chunks ticked per tick, and its associated config option is no longer respected.
|
||||||
|
- This change significantly improves crop and plant growth with large numbers of players.
|
||||||
|
- This limit was causing a linear decrease in chunk ticking speed with larger numbers of players, leading to worsened gameplay experience.
|
||||||
|
- **Warning: This change will result in increased CPU usage if players are spread over a very large area.**
|
||||||
|
- Every chunk within the configured tick radius of a player will be ticked. Previously, chunks were randomly selected from the radius.
|
||||||
|
|
||||||
|
## API
|
||||||
|
### General
|
||||||
|
- Plugins are now always disabled before their dependencies, to ensure that they are able to shutdown properly (e.g. a core plugin depending on a database plugin may want to save data to a DB during `onDisable()`).
|
||||||
|
- [`webmozart/path-util`](https://packagist.org/packages/webmozart/path-util) has been deprecated, and will be dropped in favour of [`symfony/filesystem`](https://packagist.org/packages/symfony/filesystem) in PM5.
|
||||||
|
- To prepare for this change, simply replace any usage of `Webmozart\PathUtil\Path` with `Symfony\Component\Filesystem\Path`, which is available as a dependency in this release.
|
||||||
|
|
||||||
|
### `pocketmine`
|
||||||
|
- The following API methods are now deprecated:
|
||||||
|
- `Server->getPlayerByPrefix()`
|
||||||
|
|
||||||
|
### `pocketmine\entity`
|
||||||
|
- `EntitySpawnEvent` and `ItemSpawnEvent` are now fired on the first tick after the entity is added to the world. Previously, these events were called directly from the entity constructor, making it impossible to get properties like velocity which are often set after the entity is created.
|
||||||
|
- The following API methods are now deprecated:
|
||||||
|
- `Living->hasLineOfSight()`
|
||||||
|
|
||||||
|
### `pocketmine\event\block`
|
||||||
|
- The following new classes have been added:
|
||||||
|
- `BlockDeathEvent` - event called when coral or coral blocks die due to lack of water
|
||||||
|
|
||||||
|
### `pocketmine\item`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public Armor->clearCustomColor() : $this`
|
||||||
|
|
||||||
|
### `pocketmine\inventory\transaction`
|
||||||
|
- Introduced a `TransactionBuilder` class. This makes it less of a hassle to build an `InventoryTransaction` server-side, since the regular `Inventory` API methods can be used, rather than having to manually create `SlotChangeAction`s.
|
||||||
|
|
||||||
|
### `pocketmine\lang`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public Language->getAll() : array<string, string>`
|
||||||
|
|
||||||
|
### `pocketmine\player`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public Player->sendToastNotification(string $title, string $body) : void` - makes a grey box appear at the top of the player's screen containing the specified message
|
||||||
|
|
||||||
|
### `pocketmine\utils`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public static TextFormat::addBase(string $baseFormat, string $string) : string` - used for coloured log messages, changes the base formatting of a string by inserting the given formatting codes after every RESET code
|
||||||
|
|
||||||
|
### `pocketmine\world`
|
||||||
|
- The following new API methods have been added:
|
||||||
|
- `public World->getChunkTickRadius() : int` - returns the world's simulation radius
|
||||||
|
- `public World->setChunkTickRadius(int $radius) : void` - sets the world's simulation radius
|
||||||
|
|
||||||
|
### `pocketmine\world\sound`
|
||||||
|
- The following new classes have been added:
|
||||||
|
- `ItemFrameAddItemSound`
|
||||||
|
- `ItemFrameRemoveItemSound`
|
||||||
|
- `ItemFrameRotateItemSound`
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Improved performance of `ContainerTrait` dropping items on block destroy. ([link](https://github.com/pmmp/PocketMine-MP/commits/24e72ec109c1442b09558df89b6833cf2f2e0ec7))
|
||||||
|
- Avoid repeated calls to `Position->getWorld()` (use local variables). ([link](https://github.com/pmmp/PocketMine-MP/commit/2940547026db40ce76deb46e992870de3ead79ad))
|
||||||
|
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. ([link](https://github.com/pmmp/PocketMine-MP/commit/e90abecf38d9c57635fa0497514bba7e546a2469))
|
||||||
|
- Inventories are now mapped on a per-slot basis. This means that more than one inventory can be mapped to the same window ID, which is necessary for correctly handling "UI" inventories like crafting tables.
|
||||||
|
- `InventoryManager->getWindow(int $windowId) : ?Inventory` is replaced by `locateWindowAndSlot` (see below).
|
||||||
|
- Added `InventoryManager->locateWindowAndSlot(int $windowId, int $netSlotId) : array{Inventory, int}` - accepts a window ID and absolute slot ID, and returns the associated inventory and the slot relative to the inventory's own start (for use with `getItem()` etc.).
|
||||||
|
- Slot offset mapping for "UI" inventories is now handled in `InventoryManager->createComplexSlotMapping()` instead of in `TypeConverter`.
|
||||||
|
- Console polling is now done on the main thread (no longer a performance concern). ([link](https://github.com/pmmp/PocketMine-MP/commit/b3f03d7ae645de67a54b7300c09b94eeca16298e))
|
||||||
|
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie. ([link](https://github.com/pmmp/PocketMine-MP/commit/2585160ca2c4df5758b8b980331307402ff9f0fb))
|
||||||
|
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged. ([link](https://github.com/pmmp/PocketMine-MP/commit/83e5b0adb6fa0dddec377182bb1c7945ac8f7820))
|
||||||
|
- `DelegateInventory` now uses `WeakReference` to track its inventory listener. This allows the delegate to be reused. ([link](https://github.com/pmmp/PocketMine-MP/commit/3feaa18f6c10c3a99c0deca75f57ec2d74b92ab4))
|
||||||
|
- Non-arrow projectile damage is now unscaled. Scaling according to velocity is only applied to arrows. This currently doesn't cause any observable change in behaviour, but is required for future additions.
|
32
changelogs/4.12.md
Normal file
32
changelogs/4.12.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
**For Minecraft: Bedrock Edition 1.19.50**
|
||||||
|
|
||||||
|
### 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.12.0
|
||||||
|
Released 30th November 2022.
|
||||||
|
|
||||||
|
## General
|
||||||
|
- Added support for Minecraft: Bedrock Edition 1.19.50.
|
||||||
|
- Removed support for older versions.
|
||||||
|
|
||||||
|
# 4.12.1
|
||||||
|
Released 4th December 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed items glitching when dragging a stack of items across the crafting grid (desync issues).
|
||||||
|
|
||||||
|
# 4.12.2
|
||||||
|
Released 15th December 2022.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Folder used for plugins (optionally specified by `--plugins`) is no longer required to be writable.
|
||||||
|
- Fixed broken writable check for server data folder (`is_writable()` broken on NFS and similar filesystems).
|
||||||
|
- `Filesystem::createLockFile()` exceptions now include more information about why the lock file could not be created.
|
||||||
|
- Fixed client-side item predictions not being rolled back when cancelling events such as `PlayerItemUseEvent`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Updated BedrockProtocol to [17.1.0](https://github.com/pmmp/BedrockProtocol/releases/tag/17.1.0+bedrock-1.19.50). This adds some missing `LevelSoundEvent` constants and fixes the values for `ContainerUIIds`.
|
@ -29,3 +29,14 @@ Released 22nd July 2022.
|
|||||||
- Fixed incorrect fire ticks when being set on fire by lava (8 seconds in Bedrock instead of 15).
|
- 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.
|
- `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.
|
- 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.
|
36
changelogs/4.9.md
Normal file
36
changelogs/4.9.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
**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.
|
||||||
|
|
||||||
|
# 4.9.1
|
||||||
|
Released 11th October 2022.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- Added and improved documentation for many API methods in `Player` and `Block`.
|
||||||
|
- Added missing `@internal` tag for `TaskHandler->setNextRun()`, `TaskHandler->remove()` and `TaskHandler->run()`.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Flight state is now locked by the server in spectator mode. This prevents any attempt by the client to toggle flight mode.
|
||||||
|
- Fixed entity health exceeding its max health after the expiry of Health Boost effect.
|
||||||
|
- Fixed burp sound not being played when a player eats food.
|
||||||
|
- Fixed placement conditions for mushrooms - they can now only be placed when the light level at the target is <= 12, or on podzol or mycelium.
|
||||||
|
- Fixed sign text appearing to change colour and/or glow when using dye on a sign - since this feature is not yet implemented, no change should occur.
|
||||||
|
- Fixed players drowning when sprint-swimming on the surface of water.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Added more detailed debug logging during the player login sequence.
|
||||||
|
- Silenced debug spam during `PreSpawnPacketHandler`, considerably reducing debug noise when players join.
|
||||||
|
- Fixed an edge case in `InventoryManager->removeWindow()`. This bug didn't have any effect on stable versions, but caused a `next-minor` development version to crash.
|
||||||
|
- `Item`s returned by event getters are now cloned if modifying the result will have no useful side effects.
|
||||||
|
- Updated `pocketmine/bedrock-data` to [`1.11.1`](https://github.com/pmmp/BedrockData/tree/1.11.1%2Bbedrock-1.19.30), which reduces bandwidth consumption during logins by not sending useless biome generation data.
|
@ -34,14 +34,14 @@
|
|||||||
"adhocore/json-comment": "^1.1",
|
"adhocore/json-comment": "^1.1",
|
||||||
"fgrosse/phpasn1": "^2.3",
|
"fgrosse/phpasn1": "^2.3",
|
||||||
"netresearch/jsonmapper": "^4.0",
|
"netresearch/jsonmapper": "^4.0",
|
||||||
"pocketmine/bedrock-data": "~1.9.0+bedrock-1.19.10",
|
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
|
||||||
"pocketmine/bedrock-protocol": "~11.0.0+bedrock-1.19.10",
|
"pocketmine/bedrock-protocol": "~17.1.0+bedrock-1.19.50",
|
||||||
"pocketmine/binaryutils": "^0.2.1",
|
"pocketmine/binaryutils": "^0.2.1",
|
||||||
"pocketmine/callback-validator": "^1.0.2",
|
"pocketmine/callback-validator": "^1.0.2",
|
||||||
"pocketmine/classloader": "^0.2.0",
|
"pocketmine/classloader": "^0.2.0",
|
||||||
"pocketmine/color": "^0.2.0",
|
"pocketmine/color": "^0.2.0",
|
||||||
"pocketmine/errorhandler": "^0.6.0",
|
"pocketmine/errorhandler": "^0.6.0",
|
||||||
"pocketmine/locale-data": "~2.8.0",
|
"pocketmine/locale-data": "~2.11.0",
|
||||||
"pocketmine/log": "^0.4.0",
|
"pocketmine/log": "^0.4.0",
|
||||||
"pocketmine/log-pthreads": "^0.4.0",
|
"pocketmine/log-pthreads": "^0.4.0",
|
||||||
"pocketmine/math": "^0.4.0",
|
"pocketmine/math": "^0.4.0",
|
||||||
@ -50,10 +50,11 @@
|
|||||||
"pocketmine/raklib-ipc": "^0.1.0",
|
"pocketmine/raklib-ipc": "^0.1.0",
|
||||||
"pocketmine/snooze": "^0.3.0",
|
"pocketmine/snooze": "^0.3.0",
|
||||||
"ramsey/uuid": "^4.1",
|
"ramsey/uuid": "^4.1",
|
||||||
|
"symfony/filesystem": "^5.4",
|
||||||
"webmozart/path-util": "^2.3"
|
"webmozart/path-util": "^2.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "1.8.0",
|
"phpstan/phpstan": "1.9.3",
|
||||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||||
"phpunit/phpunit": "^9.2"
|
"phpunit/phpunit": "^9.2"
|
||||||
|
841
composer.lock
generated
841
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
|||||||
includes:
|
includes:
|
||||||
|
- tests/phpstan/analyse-for-current-php-version.neon.php
|
||||||
- tests/phpstan/configs/actual-problems.neon
|
- tests/phpstan/configs/actual-problems.neon
|
||||||
- tests/phpstan/configs/gc-hacks.neon
|
- tests/phpstan/configs/gc-hacks.neon
|
||||||
- tests/phpstan/configs/impossible-generics.neon
|
- tests/phpstan/configs/impossible-generics.neon
|
||||||
|
@ -119,8 +119,6 @@ chunk-sending:
|
|||||||
spawn-radius: 4
|
spawn-radius: 4
|
||||||
|
|
||||||
chunk-ticking:
|
chunk-ticking:
|
||||||
#Max amount of chunks processed each tick
|
|
||||||
per-tick: 40
|
|
||||||
#Radius of chunks around a player to tick
|
#Radius of chunks around a player to tick
|
||||||
tick-radius: 3
|
tick-radius: 3
|
||||||
#Number of blocks inside ticking areas' subchunks that get ticked every tick. Higher values will accelerate events
|
#Number of blocks inside ticking areas' subchunks that get ticked every tick. Higher values will accelerate events
|
||||||
@ -168,6 +166,9 @@ timings:
|
|||||||
host: timings.pmmp.io
|
host: timings.pmmp.io
|
||||||
|
|
||||||
console:
|
console:
|
||||||
|
#Whether to accept commands via the console. If disabled, anything typed on the console will be ignored.
|
||||||
|
#Useful to save resources on headless servers where the console is never used (e.g. hosted server, Docker, etc.)
|
||||||
|
enable-input: true
|
||||||
#Choose whether to enable server stats reporting on the console title.
|
#Choose whether to enable server stats reporting on the console title.
|
||||||
#NOTE: The title ticker will be disabled regardless if console colours are not enabled.
|
#NOTE: The title ticker will be disabled regardless if console colours are not enabled.
|
||||||
title-tick: true
|
title-tick: true
|
||||||
|
@ -10,3 +10,4 @@ resource_stack:
|
|||||||
# - natural.zip
|
# - natural.zip
|
||||||
# - vanilla.zip
|
# - vanilla.zip
|
||||||
#If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here.
|
#If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here.
|
||||||
|
#To specify a resource encryption key, put the key in the <resource>.key file alongside the resource pack. Example: vanilla.zip.key
|
||||||
|
@ -30,7 +30,7 @@ use pocketmine\scheduler\GarbageCollectionTask;
|
|||||||
use pocketmine\timings\Timings;
|
use pocketmine\timings\Timings;
|
||||||
use pocketmine\utils\Process;
|
use pocketmine\utils\Process;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use Webmozart\PathUtil\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
use function arsort;
|
use function arsort;
|
||||||
use function count;
|
use function count;
|
||||||
use function fclose;
|
use function fclose;
|
||||||
@ -286,7 +286,7 @@ class MemoryManager{
|
|||||||
/**
|
/**
|
||||||
* Static memory dumper accessible from any thread.
|
* Static memory dumper accessible from any thread.
|
||||||
*
|
*
|
||||||
* @param mixed $startingObject
|
* @param mixed $startingObject
|
||||||
*/
|
*/
|
||||||
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
|
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
|
||||||
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
|
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
|
||||||
@ -398,7 +398,7 @@ class MemoryManager{
|
|||||||
|
|
||||||
do{
|
do{
|
||||||
$continue = false;
|
$continue = false;
|
||||||
foreach($objects as $hash => $object){
|
foreach(Utils::stringifyKeys($objects) as $hash => $object){
|
||||||
if(!is_object($object)){
|
if(!is_object($object)){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -480,9 +480,14 @@ class MemoryManager{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $from
|
* @param mixed $from
|
||||||
* @param object[] $objects reference parameter
|
* @param object[] $objects reference parameter
|
||||||
* @param int[] $refCounts reference parameter
|
* @param int[] $refCounts reference parameter
|
||||||
*
|
*
|
||||||
|
* @phpstan-param array<string, object> $objects
|
||||||
|
* @phpstan-param array<string, int> $refCounts
|
||||||
|
* @phpstan-param-out array<string, object> $objects
|
||||||
|
* @phpstan-param-out array<string, int> $refCounts
|
||||||
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
private static function continueDump($from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
private static function continueDump($from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||||
|
@ -34,15 +34,19 @@ namespace pocketmine {
|
|||||||
use pocketmine\utils\Timezone;
|
use pocketmine\utils\Timezone;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use pocketmine\wizard\SetupWizard;
|
use pocketmine\wizard\SetupWizard;
|
||||||
use Webmozart\PathUtil\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
use function defined;
|
use function defined;
|
||||||
use function extension_loaded;
|
use function extension_loaded;
|
||||||
|
use function function_exists;
|
||||||
use function getcwd;
|
use function getcwd;
|
||||||
|
use function is_dir;
|
||||||
|
use function mkdir;
|
||||||
use function phpversion;
|
use function phpversion;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function preg_quote;
|
use function preg_quote;
|
||||||
use function realpath;
|
use function realpath;
|
||||||
use function version_compare;
|
use function version_compare;
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
require_once __DIR__ . '/VersionInfo.php';
|
require_once __DIR__ . '/VersionInfo.php';
|
||||||
|
|
||||||
@ -160,7 +164,7 @@ namespace pocketmine {
|
|||||||
if(PHP_DEBUG !== 0){
|
if(PHP_DEBUG !== 0){
|
||||||
$logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
|
$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.");
|
$logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
|
||||||
}
|
}
|
||||||
if(((int) ini_get('zend.assertions')) !== -1){
|
if(((int) ini_get('zend.assertions')) !== -1){
|
||||||
@ -176,10 +180,10 @@ namespace pocketmine {
|
|||||||
|
|
||||||
|
|
||||||
--------------------------------------- ! WARNING ! ---------------------------------------
|
--------------------------------------- ! 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.
|
HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs.
|
||||||
Proceed with caution.
|
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.
|
To turn off JIT, change `opcache.jit` to `0` in your php.ini file.
|
||||||
-------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -200,6 +204,22 @@ JIT_WARNING
|
|||||||
ini_set('assert.exception', '1');
|
ini_set('assert.exception', '1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getopt_string(string $opt) : ?string{
|
||||||
|
$opts = getopt("", ["$opt:"]);
|
||||||
|
if(isset($opts[$opt])){
|
||||||
|
if(is_string($opts[$opt])){
|
||||||
|
return $opts[$opt];
|
||||||
|
}
|
||||||
|
if(is_array($opts[$opt])){
|
||||||
|
critical_error("Cannot specify --$opt multiple times");
|
||||||
|
}else{
|
||||||
|
critical_error("Missing value for --$opt");
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -251,27 +271,42 @@ JIT_WARNING
|
|||||||
|
|
||||||
ErrorToExceptionHandler::set();
|
ErrorToExceptionHandler::set();
|
||||||
|
|
||||||
$opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-ansi", "disable-ansi"]);
|
|
||||||
|
|
||||||
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
|
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
|
||||||
$dataPath = isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR;
|
$dataPath = getopt_string("data") ?? $cwd;
|
||||||
$pluginPath = isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : $cwd . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR;
|
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
|
||||||
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
|
||||||
|
|
||||||
if(!file_exists($dataPath)){
|
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
|
||||||
mkdir($dataPath, 0777, true);
|
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
//this has to be done after we're sure the data path exists
|
||||||
|
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
$lockFilePath = Path::join($dataPath, 'server.lock');
|
$lockFilePath = Path::join($dataPath, 'server.lock');
|
||||||
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
|
try{
|
||||||
|
$pid = Filesystem::createLockFile($lockFilePath);
|
||||||
|
}catch(\InvalidArgumentException $e){
|
||||||
|
critical_error($e->getMessage());
|
||||||
|
critical_error("Please ensure that there is enough space on the disk and that the current user has read/write permissions to the selected data directory $dataPath.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if($pid !== null){
|
||||||
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
|
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
|
||||||
critical_error("Please stop the other server first before running a new one.");
|
critical_error("Please stop the other server first before running a new one.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
|
||||||
|
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
//Logger has a dependency on timezone
|
//Logger has a dependency on timezone
|
||||||
Timezone::init();
|
Timezone::init();
|
||||||
|
|
||||||
|
$opts = getopt("", ["no-wizard", "enable-ansi", "disable-ansi"]);
|
||||||
if(isset($opts["enable-ansi"])){
|
if(isset($opts["enable-ansi"])){
|
||||||
Terminal::init(true);
|
Terminal::init(true);
|
||||||
}elseif(isset($opts["disable-ansi"])){
|
}elseif(isset($opts["disable-ansi"])){
|
||||||
|
@ -31,7 +31,7 @@ use pocketmine\command\Command;
|
|||||||
use pocketmine\command\CommandSender;
|
use pocketmine\command\CommandSender;
|
||||||
use pocketmine\command\SimpleCommandMap;
|
use pocketmine\command\SimpleCommandMap;
|
||||||
use pocketmine\console\ConsoleCommandSender;
|
use pocketmine\console\ConsoleCommandSender;
|
||||||
use pocketmine\console\ConsoleReaderThread;
|
use pocketmine\console\ConsoleReaderChildProcessDaemon;
|
||||||
use pocketmine\crafting\CraftingManager;
|
use pocketmine\crafting\CraftingManager;
|
||||||
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
||||||
use pocketmine\crash\CrashDump;
|
use pocketmine\crash\CrashDump;
|
||||||
@ -88,12 +88,12 @@ use pocketmine\promise\PromiseResolver;
|
|||||||
use pocketmine\resourcepacks\ResourcePackManager;
|
use pocketmine\resourcepacks\ResourcePackManager;
|
||||||
use pocketmine\scheduler\AsyncPool;
|
use pocketmine\scheduler\AsyncPool;
|
||||||
use pocketmine\snooze\SleeperHandler;
|
use pocketmine\snooze\SleeperHandler;
|
||||||
use pocketmine\snooze\SleeperNotifier;
|
|
||||||
use pocketmine\stats\SendUsageTask;
|
use pocketmine\stats\SendUsageTask;
|
||||||
use pocketmine\timings\Timings;
|
use pocketmine\timings\Timings;
|
||||||
use pocketmine\timings\TimingsHandler;
|
use pocketmine\timings\TimingsHandler;
|
||||||
use pocketmine\updater\UpdateChecker;
|
use pocketmine\updater\UpdateChecker;
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
|
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||||
use pocketmine\utils\Config;
|
use pocketmine\utils\Config;
|
||||||
use pocketmine\utils\Filesystem;
|
use pocketmine\utils\Filesystem;
|
||||||
use pocketmine\utils\Internet;
|
use pocketmine\utils\Internet;
|
||||||
@ -115,7 +115,7 @@ use pocketmine\world\World;
|
|||||||
use pocketmine\world\WorldCreationOptions;
|
use pocketmine\world\WorldCreationOptions;
|
||||||
use pocketmine\world\WorldManager;
|
use pocketmine\world\WorldManager;
|
||||||
use Ramsey\Uuid\UuidInterface;
|
use Ramsey\Uuid\UuidInterface;
|
||||||
use Webmozart\PathUtil\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
use function array_sum;
|
use function array_sum;
|
||||||
use function base64_encode;
|
use function base64_encode;
|
||||||
use function cli_set_process_title;
|
use function cli_set_process_title;
|
||||||
@ -132,6 +132,7 @@ use function get_class;
|
|||||||
use function ini_set;
|
use function ini_set;
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function is_dir;
|
use function is_dir;
|
||||||
|
use function is_int;
|
||||||
use function is_object;
|
use function is_object;
|
||||||
use function is_resource;
|
use function is_resource;
|
||||||
use function is_string;
|
use function is_string;
|
||||||
@ -165,7 +166,6 @@ use function zlib_encode;
|
|||||||
use const DIRECTORY_SEPARATOR;
|
use const DIRECTORY_SEPARATOR;
|
||||||
use const PHP_EOL;
|
use const PHP_EOL;
|
||||||
use const PHP_INT_MAX;
|
use const PHP_INT_MAX;
|
||||||
use const PTHREADS_INHERIT_NONE;
|
|
||||||
use const ZLIB_ENCODING_GZIP;
|
use const ZLIB_ENCODING_GZIP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,7 +225,8 @@ class Server{
|
|||||||
|
|
||||||
private MemoryManager $memoryManager;
|
private MemoryManager $memoryManager;
|
||||||
|
|
||||||
private ConsoleReaderThread $console;
|
private ?ConsoleReaderChildProcessDaemon $console = null;
|
||||||
|
private ?ConsoleCommandSender $consoleSender = null;
|
||||||
|
|
||||||
private SimpleCommandMap $commandMap;
|
private SimpleCommandMap $commandMap;
|
||||||
|
|
||||||
@ -570,6 +571,7 @@ class Server{
|
|||||||
$playerPos = null;
|
$playerPos = null;
|
||||||
$spawn = $world->getSpawnLocation();
|
$spawn = $world->getSpawnLocation();
|
||||||
}
|
}
|
||||||
|
/** @phpstan-var PromiseResolver<Player> $playerPromiseResolver */
|
||||||
$playerPromiseResolver = new PromiseResolver();
|
$playerPromiseResolver = new PromiseResolver();
|
||||||
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
$world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion(
|
||||||
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
|
function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{
|
||||||
@ -607,6 +609,10 @@ class Server{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated This method's results are unpredictable. The string "Steve" will return the player named "SteveJobs",
|
||||||
|
* until another player named "SteveJ" joins the server, at which point it will return that player instead. Prefer
|
||||||
|
* filtering the results of {@link Server::getOnlinePlayers()} yourself.
|
||||||
|
*
|
||||||
* Returns an online player whose name begins with or equals the given string (case insensitive).
|
* Returns an online player whose name begins with or equals the given string (case insensitive).
|
||||||
* The closest match will be returned, or null if there are no online matches.
|
* The closest match will be returned, or null if there are no online matches.
|
||||||
*
|
*
|
||||||
@ -1043,22 +1049,14 @@ class Server{
|
|||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
||||||
|
|
||||||
//TODO: move console parts to a separate component
|
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
||||||
$consoleSender = new ConsoleCommandSender($this, $this->language);
|
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder);
|
||||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender);
|
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder);
|
||||||
$this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $consoleSender);
|
|
||||||
|
|
||||||
$consoleNotifier = new SleeperNotifier();
|
//TODO: move console parts to a separate component
|
||||||
$commandBuffer = new \Threaded();
|
if($this->configGroup->getPropertyBool("console.enable-input", true)){
|
||||||
$this->console = new ConsoleReaderThread($commandBuffer, $consoleNotifier);
|
$this->console = new ConsoleReaderChildProcessDaemon($this->logger);
|
||||||
$this->tickSleeper->addNotifier($consoleNotifier, function() use ($commandBuffer, $consoleSender) : void{
|
}
|
||||||
Timings::$serverCommand->startTiming();
|
|
||||||
while(($line = $commandBuffer->shift()) !== null){
|
|
||||||
$this->dispatchCommand($consoleSender, (string) $line);
|
|
||||||
}
|
|
||||||
Timings::$serverCommand->stopTiming();
|
|
||||||
});
|
|
||||||
$this->console->start(PTHREADS_INHERIT_NONE);
|
|
||||||
|
|
||||||
$this->tickProcessor();
|
$this->tickProcessor();
|
||||||
$this->forceShutdown();
|
$this->forceShutdown();
|
||||||
@ -1270,7 +1268,7 @@ class Server{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CommandSender[]|null $recipients
|
* @param CommandSender[]|null $recipients
|
||||||
*/
|
*/
|
||||||
public function broadcastMessage(Translatable|string $message, ?array $recipients = null) : int{
|
public function broadcastMessage(Translatable|string $message, ?array $recipients = null) : int{
|
||||||
$recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS);
|
$recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS);
|
||||||
@ -1323,9 +1321,9 @@ class Server{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
|
||||||
* @param int $stay Duration in ticks to stay on screen for
|
* @param int $stay Duration in ticks to stay on screen for
|
||||||
* @param int $fadeOut Duration in ticks for fade-out.
|
* @param int $fadeOut Duration in ticks for fade-out.
|
||||||
* @param Player[]|null $recipients
|
* @param Player[]|null $recipients
|
||||||
*/
|
*/
|
||||||
public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, ?array $recipients = null) : int{
|
public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, ?array $recipients = null) : int{
|
||||||
@ -1511,7 +1509,7 @@ class Server{
|
|||||||
$this->configGroup->save();
|
$this->configGroup->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($this->console)){
|
if($this->console !== null){
|
||||||
$this->getLogger()->debug("Closing console");
|
$this->getLogger()->debug("Closing console");
|
||||||
$this->console->quit();
|
$this->console->quit();
|
||||||
}
|
}
|
||||||
@ -1650,12 +1648,14 @@ class Server{
|
|||||||
], 10, [], $postUrlError);
|
], 10, [], $postUrlError);
|
||||||
|
|
||||||
if($reply !== null && is_object($data = json_decode($reply->getBody()))){
|
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;
|
$reportId = $data->crashId;
|
||||||
$reportUrl = $data->crashUrl;
|
$reportUrl = $data->crashUrl;
|
||||||
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
|
$this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId)));
|
||||||
}elseif(isset($data->error)){
|
}elseif(isset($data->error) && is_string($data->error)){
|
||||||
$this->logger->emergency("Automatic crash report submission failed: $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{
|
}else{
|
||||||
$this->logger->emergency("Failed to communicate with crash archive: $postUrlError");
|
$this->logger->emergency("Failed to communicate with crash archive: $postUrlError");
|
||||||
@ -1716,7 +1716,7 @@ class Server{
|
|||||||
$session = $player->getNetworkSession();
|
$session = $player->getNetworkSession();
|
||||||
$position = $player->getPosition();
|
$position = $player->getPosition();
|
||||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
|
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn(
|
||||||
TextFormat::AQUA . $player->getName() . TextFormat::WHITE,
|
TextFormat::AQUA . $player->getName() . TextFormat::RESET,
|
||||||
$session->getIp(),
|
$session->getIp(),
|
||||||
(string) $session->getPort(),
|
(string) $session->getPort(),
|
||||||
(string) $player->getId(),
|
(string) $player->getId(),
|
||||||
@ -1853,6 +1853,15 @@ class Server{
|
|||||||
|
|
||||||
$this->getMemoryManager()->check();
|
$this->getMemoryManager()->check();
|
||||||
|
|
||||||
|
if($this->console !== null){
|
||||||
|
Timings::$serverCommand->startTiming();
|
||||||
|
while(($line = $this->console->readLine()) !== null){
|
||||||
|
$this->consoleSender ??= new ConsoleCommandSender($this, $this->language);
|
||||||
|
$this->dispatchCommand($this->consoleSender, $line);
|
||||||
|
}
|
||||||
|
Timings::$serverCommand->stopTiming();
|
||||||
|
}
|
||||||
|
|
||||||
Timings::$serverTick->stopTiming();
|
Timings::$serverTick->stopTiming();
|
||||||
|
|
||||||
$now = microtime(true);
|
$now = microtime(true);
|
||||||
|
@ -44,7 +44,7 @@ final class ServerConfigGroup{
|
|||||||
){}
|
){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $defaultValue
|
* @param mixed $defaultValue
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
|||||||
|
|
||||||
final class VersionInfo{
|
final class VersionInfo{
|
||||||
public const NAME = "PocketMine-MP";
|
public const NAME = "PocketMine-MP";
|
||||||
public const BASE_VERSION = "4.6.1";
|
public const BASE_VERSION = "4.12.2";
|
||||||
public const IS_DEVELOPMENT_BUILD = false;
|
public const IS_DEVELOPMENT_BUILD = false;
|
||||||
public const BUILD_CHANNEL = "stable";
|
public const BUILD_CHANNEL = "stable";
|
||||||
|
|
||||||
|
@ -164,9 +164,10 @@ class Bamboo extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$below = $this->position->getWorld()->getBlock($this->position->down());
|
$world = $this->position->getWorld();
|
||||||
|
$below = $world->getBlock($this->position->down());
|
||||||
if(!$this->canBeSupportedBy($below) && !$below->isSameType($this)){
|
if(!$this->canBeSupportedBy($below) && !$below->isSameType($this)){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +213,7 @@ class Bamboo extends Transparent{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$tx = new BlockTransaction($this->position->getWorld());
|
$tx = new BlockTransaction($world);
|
||||||
foreach($newBlocks as $idx => $newBlock){
|
foreach($newBlocks as $idx => $newBlock){
|
||||||
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
|
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,9 @@ final class BambooSapling extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
if(!$this->canBeSupportedBy($this->position->getWorld()->getBlock($this->position->down()))){
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
|
||||||
|
$world->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ abstract class BaseBanner extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BannerPatternLayer[] $patterns
|
* @param BannerPatternLayer[] $patterns
|
||||||
*
|
*
|
||||||
* @phpstan-param list<BannerPatternLayer> $patterns
|
* @phpstan-param list<BannerPatternLayer> $patterns
|
||||||
* @return $this
|
* @return $this
|
||||||
|
@ -26,6 +26,7 @@ namespace pocketmine\block;
|
|||||||
use pocketmine\block\utils\CoralType;
|
use pocketmine\block\utils\CoralType;
|
||||||
use pocketmine\block\utils\CoralTypeTrait;
|
use pocketmine\block\utils\CoralTypeTrait;
|
||||||
use pocketmine\block\utils\SupportType;
|
use pocketmine\block\utils\SupportType;
|
||||||
|
use pocketmine\event\block\BlockDeathEvent;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
|
|
||||||
abstract class BaseCoral extends Transparent{
|
abstract class BaseCoral extends Transparent{
|
||||||
@ -50,7 +51,11 @@ abstract class BaseCoral extends Transparent{
|
|||||||
|
|
||||||
//TODO: check water inside the block itself (not supported on the API yet)
|
//TODO: check water inside the block itself (not supported on the API yet)
|
||||||
if(!$hasWater){
|
if(!$hasWater){
|
||||||
$world->setBlock($this->position, $this->setDead(true));
|
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||||
|
$ev->call();
|
||||||
|
if(!$ev->isCancelled()){
|
||||||
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,7 @@ abstract class BaseRail extends Flowable{
|
|||||||
$thisConnections = $this->getConnectedDirections();
|
$thisConnections = $this->getConnectedDirections();
|
||||||
$changed = false;
|
$changed = false;
|
||||||
|
|
||||||
|
$world = $this->position->getWorld();
|
||||||
do{
|
do{
|
||||||
$possible = $this->getPossibleConnectionDirections($thisConnections);
|
$possible = $this->getPossibleConnectionDirections($thisConnections);
|
||||||
$continue = false;
|
$continue = false;
|
||||||
@ -189,7 +190,7 @@ abstract class BaseRail extends Flowable{
|
|||||||
if(isset($otherPossible[$otherSide])){
|
if(isset($otherPossible[$otherSide])){
|
||||||
$otherConnections[] = $otherSide;
|
$otherConnections[] = $otherSide;
|
||||||
$other->setConnections($otherConnections);
|
$other->setConnections($otherConnections);
|
||||||
$this->position->getWorld()->setBlock($other->position, $other);
|
$world->setBlock($other->position, $other);
|
||||||
|
|
||||||
$changed = true;
|
$changed = true;
|
||||||
$thisConnections[] = $thisSide;
|
$thisConnections[] = $thisSide;
|
||||||
@ -202,7 +203,7 @@ abstract class BaseRail extends Flowable{
|
|||||||
|
|
||||||
if($changed){
|
if($changed){
|
||||||
$this->setConnections($thisConnections);
|
$this->setConnections($thisConnections);
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,12 +221,13 @@ abstract class BaseRail extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(!$this->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
if(!$this->getSide(Facing::DOWN)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}else{
|
}else{
|
||||||
foreach($this->getCurrentShapeConnections() as $connection){
|
foreach($this->getCurrentShapeConnections() as $connection){
|
||||||
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,10 +179,11 @@ final class Bell extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function ring(int $faceHit) : void{
|
public function ring(int $faceHit) : void{
|
||||||
$this->position->getWorld()->addSound($this->position, new BellRingSound());
|
$world = $this->position->getWorld();
|
||||||
$tile = $this->position->getWorld()->getTile($this->position);
|
$world->addSound($this->position, new BellRingSound());
|
||||||
|
$tile = $world->getTile($this->position);
|
||||||
if($tile instanceof TileBell){
|
if($tile instanceof TileBell){
|
||||||
$this->position->getWorld()->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
|
$world->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class Block{
|
|||||||
protected ?array $collisionBoxes = null;
|
protected ?array $collisionBoxes = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name English name of the block type (TODO: implement translations)
|
* @param string $name English name of the block type (TODO: implement translations)
|
||||||
*/
|
*/
|
||||||
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
|
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
|
||||||
if(($idInfo->getVariant() & $this->getStateBitmask()) !== 0){
|
if(($idInfo->getVariant() & $this->getStateBitmask()) !== 0){
|
||||||
@ -78,25 +78,47 @@ class Block{
|
|||||||
$this->position = clone $this->position;
|
$this->position = clone $this->position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object containing information about how to identify and store this block type, such as its legacy
|
||||||
|
* numeric ID(s), tile type (if any), and legacy variant metadata.
|
||||||
|
*/
|
||||||
public function getIdInfo() : BlockIdentifier{
|
public function getIdInfo() : BlockIdentifier{
|
||||||
return $this->idInfo;
|
return $this->idInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the printable English name of the block.
|
||||||
|
*/
|
||||||
public function getName() : string{
|
public function getName() : string{
|
||||||
return $this->fallbackName;
|
return $this->fallbackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*
|
||||||
|
* Returns the legacy numeric Minecraft block ID.
|
||||||
|
*/
|
||||||
public function getId() : int{
|
public function getId() : int{
|
||||||
return $this->idInfo->getBlockId();
|
return $this->idInfo->getBlockId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
*
|
||||||
|
* Returns the full blockstate ID of this block. This is a compact way of representing a blockstate used to store
|
||||||
|
* blocks in chunks at runtime.
|
||||||
|
*
|
||||||
|
* This ID can be used to later obtain a copy of this block using {@link BlockFactory::get()}.
|
||||||
*/
|
*/
|
||||||
public function getFullId() : int{
|
public function getFullId() : int{
|
||||||
return ($this->getId() << self::INTERNAL_METADATA_BITS) | $this->getMeta();
|
return ($this->getId() << self::INTERNAL_METADATA_BITS) | $this->getMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the block as an item.
|
||||||
|
* State information such as facing, powered/unpowered, open/closed, etc., is discarded.
|
||||||
|
* Type information such as colour, wood type, etc. is preserved.
|
||||||
|
*/
|
||||||
public function asItem() : Item{
|
public function asItem() : Item{
|
||||||
return ItemFactory::getInstance()->get(
|
return ItemFactory::getInstance()->get(
|
||||||
$this->idInfo->getItemId(),
|
$this->idInfo->getItemId(),
|
||||||
@ -104,6 +126,12 @@ class Block{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*
|
||||||
|
* Returns the legacy Minecraft block meta value. This is a mixed-purpose value, which is used to store different
|
||||||
|
* things for different blocks.
|
||||||
|
*/
|
||||||
public function getMeta() : int{
|
public function getMeta() : int{
|
||||||
$stateMeta = $this->writeStateToMeta();
|
$stateMeta = $this->writeStateToMeta();
|
||||||
assert(($stateMeta & ~$this->getStateBitmask()) === 0);
|
assert(($stateMeta & ~$this->getStateBitmask()) === 0);
|
||||||
@ -116,6 +144,7 @@ class Block{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a bitmask used to extract state bits from block metadata.
|
* Returns a bitmask used to extract state bits from block metadata.
|
||||||
|
* This is used to remove unwanted information from the legacy meta value when getting the block as an item.
|
||||||
*/
|
*/
|
||||||
public function getStateBitmask() : int{
|
public function getStateBitmask() : int{
|
||||||
return 0;
|
return 0;
|
||||||
@ -143,11 +172,18 @@ class Block{
|
|||||||
$this->collisionBoxes = null;
|
$this->collisionBoxes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes information about the block into the world. This writes the blockstate ID into the chunk, and creates
|
||||||
|
* and/or removes tiles as necessary.
|
||||||
|
*
|
||||||
|
* Note: Do not call this directly. Pass the block to {@link World::setBlock()} instead.
|
||||||
|
*/
|
||||||
public function writeStateToWorld() : void{
|
public function writeStateToWorld() : void{
|
||||||
$this->position->getWorld()->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
|
$world = $this->position->getWorld();
|
||||||
|
$world->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId());
|
||||||
|
|
||||||
$tileType = $this->idInfo->getTileClass();
|
$tileType = $this->idInfo->getTileClass();
|
||||||
$oldTile = $this->position->getWorld()->getTile($this->position);
|
$oldTile = $world->getTile($this->position);
|
||||||
if($oldTile !== null){
|
if($oldTile !== null){
|
||||||
if($tileType === null || !($oldTile instanceof $tileType)){
|
if($tileType === null || !($oldTile instanceof $tileType)){
|
||||||
$oldTile->close();
|
$oldTile->close();
|
||||||
@ -161,13 +197,13 @@ class Block{
|
|||||||
* @var Tile $tile
|
* @var Tile $tile
|
||||||
* @see Tile::__construct()
|
* @see Tile::__construct()
|
||||||
*/
|
*/
|
||||||
$tile = new $tileType($this->position->getWorld(), $this->position->asVector3());
|
$tile = new $tileType($world, $this->position->asVector3());
|
||||||
$this->position->getWorld()->addTile($tile);
|
$world->addTile($tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a type ID that identifies this type of block. This does not include information like facing, colour,
|
* Returns a type ID that identifies this type of block. This does not include information like facing, open/closed,
|
||||||
* powered/unpowered, etc.
|
* powered/unpowered, etc.
|
||||||
*/
|
*/
|
||||||
public function getTypeId() : int{
|
public function getTypeId() : int{
|
||||||
@ -198,22 +234,36 @@ class Block{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this block can be replaced by another block placed in the same position.
|
||||||
|
*/
|
||||||
public function canBeReplaced() : bool{
|
public function canBeReplaced() : bool{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this block can replace the given block in the given placement conditions.
|
||||||
|
* This is used to allow slabs of the same type to combine into double slabs.
|
||||||
|
*/
|
||||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||||
return $blockReplace->canBeReplaced();
|
return $blockReplace->canBeReplaced();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Places the Block, using block space and block target, and side. Returns if the block has been placed.
|
* Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block
|
||||||
|
* itself, but may be multiple blocks in some cases (such as doors).
|
||||||
|
*
|
||||||
|
* @return bool whether the placement should go ahead
|
||||||
*/
|
*/
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
$tx->addBlock($blockReplace->position, $this);
|
$tx->addBlock($blockReplace->position, $this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately after the block has been placed in the world. Since placement uses a block transaction, some
|
||||||
|
* things may not be possible until after the transaction has been executed.
|
||||||
|
*/
|
||||||
public function onPostPlace() : void{
|
public function onPostPlace() : void{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -229,10 +279,11 @@ class Block{
|
|||||||
* Do the actions needed so the block is broken with the Item
|
* Do the actions needed so the block is broken with the Item
|
||||||
*/
|
*/
|
||||||
public function onBreak(Item $item, ?Player $player = null) : bool{
|
public function onBreak(Item $item, ?Player $player = null) : bool{
|
||||||
if(($t = $this->position->getWorld()->getTile($this->position)) !== null){
|
$world = $this->position->getWorld();
|
||||||
|
if(($t = $world->getTile($this->position)) !== null){
|
||||||
$t->onBlockDestroyed();
|
$t->onBlockDestroyed();
|
||||||
}
|
}
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,7 +303,7 @@ class Block{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this block is randomly updated due to chunk ticking.
|
* Called when this block is randomly updated due to chunk ticking.
|
||||||
* WARNING: This will not be called if ticksRandomly() does not return true!
|
* WARNING: This will not be called if {@link Block::ticksRandomly()} does not return true!
|
||||||
*/
|
*/
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
|
|
||||||
@ -273,8 +324,7 @@ class Block{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this block is attacked (left-clicked). This is called when a player left-clicks the block to try and
|
* Called when this block is attacked (left-clicked) by a player attempting to start breaking it in survival.
|
||||||
* start to break it in survival mode.
|
|
||||||
*
|
*
|
||||||
* @return bool if an action took place, prevents starting to break the block if true.
|
* @return bool if an action took place, prevents starting to break the block if true.
|
||||||
*/
|
*/
|
||||||
@ -282,11 +332,19 @@ class Block{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a multiplier applied to the velocity of entities moving on top of this block. A higher value will make
|
||||||
|
* the block more slippery (like ice).
|
||||||
|
*
|
||||||
|
* @return float 0.0-1.0
|
||||||
|
*/
|
||||||
public function getFrictionFactor() : float{
|
public function getFrictionFactor() : float{
|
||||||
return 0.6;
|
return 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns the amount of light emitted by this block.
|
||||||
|
*
|
||||||
* @return int 0-15
|
* @return int 0-15
|
||||||
*/
|
*/
|
||||||
public function getLightLevel() : int{
|
public function getLightLevel() : int{
|
||||||
@ -329,10 +387,6 @@ class Block{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasEntityCollision() : bool{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether entities can climb up this block.
|
* Returns whether entities can climb up this block.
|
||||||
*/
|
*/
|
||||||
@ -340,10 +394,6 @@ class Block{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final public function getPosition() : Position{
|
final public function getPosition() : Position{
|
||||||
return $this->position;
|
return $this->position;
|
||||||
}
|
}
|
||||||
@ -426,6 +476,7 @@ class Block{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the item that players will equip when middle-clicking on this block.
|
* Returns the item that players will equip when middle-clicking on this block.
|
||||||
|
* If addUserData is true, additional data may be added, such as banner patterns, chest contents, etc.
|
||||||
*/
|
*/
|
||||||
public function getPickedItem(bool $addUserData = false) : Item{
|
public function getPickedItem(bool $addUserData = false) : Item{
|
||||||
$item = $this->asItem();
|
$item = $this->asItem();
|
||||||
@ -549,7 +600,7 @@ class Block{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for collision against an AxisAlignedBB
|
* Returns whether any of the block's collision boxes intersect with the given AxisAlignedBB.
|
||||||
*/
|
*/
|
||||||
public function collidesWithBB(AxisAlignedBB $bb) : bool{
|
public function collidesWithBB(AxisAlignedBB $bb) : bool{
|
||||||
foreach($this->getCollisionBoxes() as $bb2){
|
foreach($this->getCollisionBoxes() as $bb2){
|
||||||
@ -561,10 +612,21 @@ class Block{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the block has actions to be executed when an entity enters its cell (full cube space).
|
||||||
|
*
|
||||||
|
* @see Block::onEntityInside()
|
||||||
|
*/
|
||||||
|
public function hasEntityCollision() : bool{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting
|
* Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting
|
||||||
* with the collision box or bounding box.
|
* with the collision box or bounding box.
|
||||||
*
|
*
|
||||||
|
* WARNING: This will not be called if {@link Block::hasEntityCollision()} returns false.
|
||||||
|
*
|
||||||
* @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive
|
* @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive
|
||||||
* being ignited), this should return false.
|
* being ignited), this should return false.
|
||||||
*/
|
*/
|
||||||
@ -572,6 +634,19 @@ class Block{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a direction vector describing which way an entity intersecting this block should be pushed.
|
||||||
|
* This is used by liquids to push entities in liquid currents.
|
||||||
|
*
|
||||||
|
* The returned vector is summed with vectors from every other block the entity is intersecting, and normalized to
|
||||||
|
* produce a final direction vector.
|
||||||
|
*
|
||||||
|
* WARNING: This will not be called if {@link Block::hasEntityCollision()} does not return true!
|
||||||
|
*/
|
||||||
|
public function addVelocityToEntity(Entity $entity) : ?Vector3{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an entity lands on this block (usually due to falling).
|
* Called when an entity lands on this block (usually due to falling).
|
||||||
* @return float|null The new vertical velocity of the entity, or null if unchanged.
|
* @return float|null The new vertical velocity of the entity, or null if unchanged.
|
||||||
@ -581,6 +656,13 @@ class Block{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns an array of collision bounding boxes for this block.
|
||||||
|
* These are used for:
|
||||||
|
* - entity movement collision checks (to ensure entities can't clip through blocks)
|
||||||
|
* - projectile flight paths
|
||||||
|
* - block placement (to ensure the player can't place blocks inside itself or another entity)
|
||||||
|
* - anti-cheat checks in plugins
|
||||||
|
*
|
||||||
* @return AxisAlignedBB[]
|
* @return AxisAlignedBB[]
|
||||||
*/
|
*/
|
||||||
final public function getCollisionBoxes() : array{
|
final public function getCollisionBoxes() : array{
|
||||||
@ -611,6 +693,10 @@ class Block{
|
|||||||
return [AxisAlignedBB::one()];
|
return [AxisAlignedBB::one()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of support that the block can provide on the given face. This is used to determine whether
|
||||||
|
* blocks placed on the given face can be supported by this block.
|
||||||
|
*/
|
||||||
public function getSupportType(int $facing) : SupportType{
|
public function getSupportType(int $facing) : SupportType{
|
||||||
return SupportType::FULL();
|
return SupportType::FULL();
|
||||||
}
|
}
|
||||||
@ -621,6 +707,10 @@ class Block{
|
|||||||
return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
|
return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a ray trace along the line between the two positions using the block's collision boxes.
|
||||||
|
* Returns the intersection point closest to pos1, or null if no intersection occurred.
|
||||||
|
*/
|
||||||
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
|
public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{
|
||||||
$bbs = $this->getCollisionBoxes();
|
$bbs = $this->getCollisionBoxes();
|
||||||
if(count($bbs) === 0){
|
if(count($bbs) === 0){
|
||||||
|
@ -523,12 +523,6 @@ class BlockFactory{
|
|||||||
$this->registerAllMeta(...$leaves);
|
$this->registerAllMeta(...$leaves);
|
||||||
$this->registerAllMeta(...$allSidedLogs);
|
$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());
|
$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::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));
|
$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));
|
$this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS, 0), "Smooth Sandstone Stairs", $sandstoneBreakInfo));
|
||||||
$sandstones = [];
|
$sandstones = [];
|
||||||
$redSandstones = [];
|
$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);
|
$sandstones[] = new Opaque(new BID(Ids::SANDSTONE, $variant), $prefix . "Sandstone", $sandstoneBreakInfo);
|
||||||
$redSandstones[] = new Opaque(new BID(Ids::RED_SANDSTONE, $variant), $prefix . "Red Sandstone", $sandstoneBreakInfo);
|
$redSandstones[] = new Opaque(new BID(Ids::RED_SANDSTONE, $variant), $prefix . "Red Sandstone", $sandstoneBreakInfo);
|
||||||
}
|
}
|
||||||
@ -969,7 +968,7 @@ class BlockFactory{
|
|||||||
* NOTE: If you are registering a new block type, you will need to add it to the creative inventory yourself - it
|
* NOTE: If you are registering a new block type, you will need to add it to the creative inventory yourself - it
|
||||||
* will not automatically appear there.
|
* will not automatically appear there.
|
||||||
*
|
*
|
||||||
* @param bool $override Whether to override existing registrations
|
* @param bool $override Whether to override existing registrations
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException if something attempted to override an already-registered block without specifying the
|
* @throws \RuntimeException if something attempted to override an already-registered block without specifying the
|
||||||
* $override parameter.
|
* $override parameter.
|
||||||
|
@ -130,10 +130,11 @@ class BrewingStand extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onScheduledUpdate() : void{
|
public function onScheduledUpdate() : void{
|
||||||
$brewing = $this->position->getWorld()->getTile($this->position);
|
$world = $this->position->getWorld();
|
||||||
|
$brewing = $world->getTile($this->position);
|
||||||
if($brewing instanceof TileBrewingStand){
|
if($brewing instanceof TileBrewingStand){
|
||||||
if($brewing->onUpdate()){
|
if($brewing->onUpdate()){
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
|
$world->scheduleDelayedBlockUpdate($this->position, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$changed = false;
|
$changed = false;
|
||||||
@ -146,7 +147,7 @@ class BrewingStand extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($changed){
|
if($changed){
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,9 +73,10 @@ abstract class Button extends Flowable{
|
|||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if(!$this->pressed){
|
if(!$this->pressed){
|
||||||
$this->pressed = true;
|
$this->pressed = true;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
|
$world->setBlock($this->position, $this);
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
|
$world->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
|
||||||
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -84,8 +85,9 @@ abstract class Button extends Flowable{
|
|||||||
public function onScheduledUpdate() : void{
|
public function onScheduledUpdate() : void{
|
||||||
if($this->pressed){
|
if($this->pressed){
|
||||||
$this->pressed = false;
|
$this->pressed = false;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
|
$world->setBlock($this->position, $this);
|
||||||
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class Cactus extends Transparent{
|
|||||||
* @return AxisAlignedBB[]
|
* @return AxisAlignedBB[]
|
||||||
*/
|
*/
|
||||||
protected function recalculateCollisionBoxes() : array{
|
protected function recalculateCollisionBoxes() : array{
|
||||||
static $shrinkSize = 1 / 16;
|
$shrinkSize = 1 / 16;
|
||||||
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
|
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,13 +88,14 @@ class Cactus extends Transparent{
|
|||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$down = $this->getSide(Facing::DOWN);
|
$down = $this->getSide(Facing::DOWN);
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($down->getId() !== BlockLegacyIds::SAND && !$down->isSameType($this)){
|
if($down->getId() !== BlockLegacyIds::SAND && !$down->isSameType($this)){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}else{
|
}else{
|
||||||
foreach(Facing::HORIZONTAL as $side){
|
foreach(Facing::HORIZONTAL as $side){
|
||||||
$b = $this->getSide($side);
|
$b = $this->getSide($side);
|
||||||
if($b->isSolid()){
|
if($b->isSolid()){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,28 +108,29 @@ class Cactus extends Transparent{
|
|||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($this->age === self::MAX_AGE){
|
if($this->age === self::MAX_AGE){
|
||||||
for($y = 1; $y < 3; ++$y){
|
for($y = 1; $y < 3; ++$y){
|
||||||
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
if(!$world->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$b = $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
$b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
||||||
if($b->getId() === BlockLegacyIds::AIR){
|
if($b->getId() === BlockLegacyIds::AIR){
|
||||||
$ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
|
$ev = new BlockGrowEvent($b, VanillaBlocks::CACTUS());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if($ev->isCancelled()){
|
if($ev->isCancelled()){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
|
$world->setBlock($b->position, $ev->getNewState());
|
||||||
}else{
|
}else{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->age = 0;
|
$this->age = 0;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}else{
|
}else{
|
||||||
++$this->age;
|
++$this->age;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,13 +51,13 @@ class Chest extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onPostPlace() : void{
|
public function onPostPlace() : void{
|
||||||
$tile = $this->position->getWorld()->getTile($this->position);
|
$world = $this->position->getWorld();
|
||||||
|
$tile = $world->getTile($this->position);
|
||||||
if($tile instanceof TileChest){
|
if($tile instanceof TileChest){
|
||||||
foreach([false, true] as $clockwise){
|
foreach([false, true] as $clockwise){
|
||||||
$side = Facing::rotateY($this->facing, $clockwise);
|
$side = Facing::rotateY($this->facing, $clockwise);
|
||||||
$c = $this->getSide($side);
|
$c = $this->getSide($side);
|
||||||
if($c instanceof Chest && $c->isSameType($this) && $c->facing === $this->facing){
|
if($c instanceof Chest && $c->isSameType($this) && $c->facing === $this->facing){
|
||||||
$world = $this->position->getWorld();
|
|
||||||
$pair = $world->getTile($c->position);
|
$pair = $world->getTile($c->position);
|
||||||
if($pair instanceof TileChest && !$pair->isPaired()){
|
if($pair instanceof TileChest && !$pair->isPaired()){
|
||||||
[$left, $right] = $clockwise ? [$c, $this] : [$this, $c];
|
[$left, $right] = $clockwise ? [$c, $this] : [$this, $c];
|
||||||
|
@ -27,6 +27,7 @@ use pocketmine\block\utils\CoralType;
|
|||||||
use pocketmine\block\utils\CoralTypeTrait;
|
use pocketmine\block\utils\CoralTypeTrait;
|
||||||
use pocketmine\block\utils\InvalidBlockStateException;
|
use pocketmine\block\utils\InvalidBlockStateException;
|
||||||
use pocketmine\data\bedrock\CoralTypeIdMap;
|
use pocketmine\data\bedrock\CoralTypeIdMap;
|
||||||
|
use pocketmine\event\block\BlockDeathEvent;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
|
|
||||||
@ -77,7 +78,11 @@ final class CoralBlock extends Opaque{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!$hasWater){
|
if(!$hasWater){
|
||||||
$world->setBlock($this->position, $this->setDead(true));
|
$ev = new BlockDeathEvent($this, $this->setDead(true));
|
||||||
|
$ev->call();
|
||||||
|
if(!$ev->isCancelled()){
|
||||||
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,21 +100,23 @@ class DaylightSensor extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onScheduledUpdate() : void{
|
public function onScheduledUpdate() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
$signalStrength = $this->recalculateSignalStrength();
|
$signalStrength = $this->recalculateSignalStrength();
|
||||||
if($this->signalStrength !== $signalStrength){
|
if($this->signalStrength !== $signalStrength){
|
||||||
$this->signalStrength = $signalStrength;
|
$this->signalStrength = $signalStrength;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 20);
|
$world->scheduleDelayedBlockUpdate($this->position, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function recalculateSignalStrength() : int{
|
private function recalculateSignalStrength() : int{
|
||||||
$lightLevel = $this->position->getWorld()->getRealBlockSkyLightAt($this->position->x, $this->position->y, $this->position->z);
|
$world = $this->position->getWorld();
|
||||||
|
$lightLevel = $world->getRealBlockSkyLightAt($this->position->x, $this->position->y, $this->position->z);
|
||||||
if($this->inverted){
|
if($this->inverted){
|
||||||
return 15 - $lightLevel;
|
return 15 - $lightLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sunAngle = $this->position->getWorld()->getSunAnglePercentage();
|
$sunAngle = $world->getSunAnglePercentage();
|
||||||
return max(0, (int) round($lightLevel * cos(($sunAngle + ((($sunAngle < 0.5 ? 0 : 1) - $sunAngle) / 5)) * 2 * M_PI)));
|
return max(0, (int) round($lightLevel * cos(($sunAngle + ((($sunAngle < 0.5 ? 0 : 1) - $sunAngle) / 5)) * 2 * M_PI)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ use function mt_rand;
|
|||||||
class DeadBush extends Flowable{
|
class DeadBush extends Flowable{
|
||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if(!$this->getSide(Facing::DOWN)->isTransparent()){
|
if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class DeadBush extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
if($this->getSide(Facing::DOWN)->isTransparent()){
|
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,4 +64,14 @@ class DeadBush extends Flowable{
|
|||||||
public function getFlammability() : int{
|
public function getFlammability() : int{
|
||||||
return 100;
|
return 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canBeSupportedBy(Block $block) : bool{
|
||||||
|
$blockId = $block->getId();
|
||||||
|
return $blockId === BlockLegacyIds::SAND
|
||||||
|
|| $blockId === BlockLegacyIds::PODZOL
|
||||||
|
|| $blockId === BlockLegacyIds::MYCELIUM
|
||||||
|
|| $blockId === BlockLegacyIds::DIRT
|
||||||
|
|| $blockId === BlockLegacyIds::HARDENED_CLAY
|
||||||
|
|| $blockId === BlockLegacyIds::STAINED_HARDENED_CLAY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,9 @@ class Dirt extends Opaque{
|
|||||||
$item->applyDamage(1);
|
$item->applyDamage(1);
|
||||||
|
|
||||||
$newBlock = $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND();
|
$newBlock = $this->coarse ? VanillaBlocks::DIRT() : VanillaBlocks::FARMLAND();
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||||
|
$world->setBlock($this->position, $newBlock);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -163,13 +163,14 @@ class Door extends Transparent{
|
|||||||
$this->open = !$this->open;
|
$this->open = !$this->open;
|
||||||
|
|
||||||
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
|
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($other instanceof Door && $other->isSameType($this)){
|
if($other instanceof Door && $other->isSameType($this)){
|
||||||
$other->open = $this->open;
|
$other->open = $this->open;
|
||||||
$this->position->getWorld()->setBlock($other->position, $other);
|
$world->setBlock($other->position, $other);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
$world->addSound($this->position, new DoorSound());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,9 @@ class DragonEgg extends Transparent implements Fallable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function teleport() : void{
|
public function teleport() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
for($tries = 0; $tries < 16; ++$tries){
|
for($tries = 0; $tries < 16; ++$tries){
|
||||||
$block = $this->position->getWorld()->getBlockAt(
|
$block = $world->getBlockAt(
|
||||||
$this->position->x + mt_rand(-16, 16),
|
$this->position->x + mt_rand(-16, 16),
|
||||||
max(World::Y_MIN, min(World::Y_MAX - 1, $this->position->y + mt_rand(-8, 8))),
|
max(World::Y_MIN, min(World::Y_MAX - 1, $this->position->y + mt_rand(-8, 8))),
|
||||||
$this->position->z + mt_rand(-16, 16)
|
$this->position->z + mt_rand(-16, 16)
|
||||||
@ -76,9 +77,9 @@ class DragonEgg extends Transparent implements Fallable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$blockPos = $ev->getTo();
|
$blockPos = $ev->getTo();
|
||||||
$this->position->getWorld()->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
|
$world->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
$this->position->getWorld()->setBlock($blockPos, $this);
|
$world->setBlock($blockPos, $this);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,16 +78,17 @@ class Farmland extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(!$this->canHydrate()){
|
if(!$this->canHydrate()){
|
||||||
if($this->wetness > 0){
|
if($this->wetness > 0){
|
||||||
$this->wetness--;
|
$this->wetness--;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
$world->setBlock($this->position, $this, false);
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::DIRT());
|
$world->setBlock($this->position, VanillaBlocks::DIRT());
|
||||||
}
|
}
|
||||||
}elseif($this->wetness < self::MAX_WETNESS){
|
}elseif($this->wetness < self::MAX_WETNESS){
|
||||||
$this->wetness = self::MAX_WETNESS;
|
$this->wetness = self::MAX_WETNESS;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
$world->setBlock($this->position, $this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +117,9 @@ class FenceGate extends Transparent{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
$world->setBlock($this->position, $this);
|
||||||
|
$world->addSound($this->position, new DoorSound());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,10 +100,11 @@ class Fire extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($this->getSide(Facing::DOWN)->isTransparent() && !$this->hasAdjacentFlammableBlocks()){
|
if($this->getSide(Facing::DOWN)->isTransparent() && !$this->hasAdjacentFlammableBlocks()){
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,11 +137,12 @@ class Fire extends Flowable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($result !== null){
|
if($result !== null){
|
||||||
$this->position->getWorld()->setBlock($this->position, $result);
|
$world->setBlock($this->position, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||||
|
|
||||||
if($canSpread){
|
if($canSpread){
|
||||||
$this->burnBlocksAround();
|
$this->burnBlocksAround();
|
||||||
@ -181,7 +183,8 @@ class Fire extends Flowable{
|
|||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$block->onIncinerate();
|
$block->onIncinerate();
|
||||||
|
|
||||||
if($this->position->getWorld()->getBlock($block->getPosition())->isSameState($block)){
|
$world = $this->position->getWorld();
|
||||||
|
if($world->getBlock($block->getPosition())->isSameState($block)){
|
||||||
$spreadedFire = false;
|
$spreadedFire = false;
|
||||||
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
|
if(mt_rand(0, $this->age + 9) < 5){ //TODO: check rain
|
||||||
$fire = clone $this;
|
$fire = clone $this;
|
||||||
@ -189,7 +192,7 @@ class Fire extends Flowable{
|
|||||||
$spreadedFire = $this->spreadBlock($block, $fire);
|
$spreadedFire = $this->spreadBlock($block, $fire);
|
||||||
}
|
}
|
||||||
if(!$spreadedFire){
|
if(!$spreadedFire){
|
||||||
$this->position->getWorld()->setBlock($block->position, VanillaBlocks::AIR());
|
$world->setBlock($block->position, VanillaBlocks::AIR());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ namespace pocketmine\block;
|
|||||||
use pocketmine\block\utils\SupportType;
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\math\AxisAlignedBB;
|
use pocketmine\math\AxisAlignedBB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
|
||||||
|
* collision boxes, and can't provide support for other blocks.
|
||||||
|
*/
|
||||||
abstract class Flowable extends Transparent{
|
abstract class Flowable extends Transparent{
|
||||||
|
|
||||||
public function canBeFlowedInto() : bool{
|
public function canBeFlowedInto() : bool{
|
||||||
|
@ -122,6 +122,7 @@ class FlowerPot extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
$plant = $item->getBlock();
|
$plant = $item->getBlock();
|
||||||
if($this->plant !== null){
|
if($this->plant !== null){
|
||||||
if($this->isValidPlant($plant)){
|
if($this->isValidPlant($plant)){
|
||||||
@ -137,16 +138,16 @@ class FlowerPot extends Flowable{
|
|||||||
$removedItems = $player->getInventory()->addItem(...$removedItems);
|
$removedItems = $player->getInventory()->addItem(...$removedItems);
|
||||||
}
|
}
|
||||||
foreach($removedItems as $drops){
|
foreach($removedItems as $drops){
|
||||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), $drops);
|
$world->dropItem($this->position->add(0.5, 0.5, 0.5), $drops);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setPlant(null);
|
$this->setPlant(null);
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
return true;
|
return true;
|
||||||
}elseif($this->isValidPlant($plant)){
|
}elseif($this->isValidPlant($plant)){
|
||||||
$this->setPlant($plant);
|
$this->setPlant($plant);
|
||||||
$item->pop();
|
$item->pop();
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -56,16 +56,18 @@ class FrostedIce extends Ice{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(!$this->checkAdjacentBlocks(2)){
|
if(!$this->checkAdjacentBlocks(2)){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if((!$this->checkAdjacentBlocks(4) || mt_rand(0, 2) === 0) &&
|
if((!$this->checkAdjacentBlocks(4) || mt_rand(0, 2) === 0) &&
|
||||||
$this->position->getWorld()->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){
|
$world->getHighestAdjacentFullLightAt($this->position->x, $this->position->y, $this->position->z) >= 12 - $this->age){
|
||||||
if($this->tryMelt()){
|
if($this->tryMelt()){
|
||||||
foreach($this->getAllSides() as $block){
|
foreach($this->getAllSides() as $block){
|
||||||
if($block instanceof FrostedIce){
|
if($block instanceof FrostedIce){
|
||||||
@ -74,7 +76,7 @@ class FrostedIce extends Ice{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,18 +108,19 @@ class FrostedIce extends Ice{
|
|||||||
* @return bool Whether the ice was destroyed.
|
* @return bool Whether the ice was destroyed.
|
||||||
*/
|
*/
|
||||||
private function tryMelt() : bool{
|
private function tryMelt() : bool{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($this->age >= self::MAX_AGE){
|
if($this->age >= self::MAX_AGE){
|
||||||
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->age++;
|
$this->age++;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,12 +83,13 @@ class Furnace extends Opaque{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onScheduledUpdate() : void{
|
public function onScheduledUpdate() : void{
|
||||||
$furnace = $this->position->getWorld()->getTile($this->position);
|
$world = $this->position->getWorld();
|
||||||
|
$furnace = $world->getTile($this->position);
|
||||||
if($furnace instanceof TileFurnace && $furnace->onUpdate()){
|
if($furnace instanceof TileFurnace && $furnace->onUpdate()){
|
||||||
if(mt_rand(1, 60) === 1){ //in vanilla this is between 1 and 5 seconds; try to average about 3
|
if(mt_rand(1, 60) === 1){ //in vanilla this is between 1 and 5 seconds; try to average about 3
|
||||||
$this->position->getWorld()->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
|
$world->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
|
||||||
}
|
}
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
|
$world->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,13 +53,14 @@ class Grass extends Opaque{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
$lightAbove = $this->position->getWorld()->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
|
$world = $this->position->getWorld();
|
||||||
if($lightAbove < 4 && $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
|
$lightAbove = $world->getFullLightAt($this->position->x, $this->position->y + 1, $this->position->z);
|
||||||
|
if($lightAbove < 4 && $world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)->getLightFilter() >= 2){
|
||||||
//grass dies
|
//grass dies
|
||||||
$ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
|
$ev = new BlockSpreadEvent($this, $this, VanillaBlocks::DIRT());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState(), false);
|
$world->setBlock($this->position, $ev->getNewState(), false);
|
||||||
}
|
}
|
||||||
}elseif($lightAbove >= 9){
|
}elseif($lightAbove >= 9){
|
||||||
//try grass spread
|
//try grass spread
|
||||||
@ -68,12 +69,12 @@ class Grass extends Opaque{
|
|||||||
$y = mt_rand($this->position->y - 3, $this->position->y + 1);
|
$y = mt_rand($this->position->y - 3, $this->position->y + 1);
|
||||||
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
||||||
|
|
||||||
$b = $this->position->getWorld()->getBlockAt($x, $y, $z);
|
$b = $world->getBlockAt($x, $y, $z);
|
||||||
if(
|
if(
|
||||||
!($b instanceof Dirt) ||
|
!($b instanceof Dirt) ||
|
||||||
$b->isCoarse() ||
|
$b->isCoarse() ||
|
||||||
$this->position->getWorld()->getFullLightAt($x, $y + 1, $z) < 4 ||
|
$world->getFullLightAt($x, $y + 1, $z) < 4 ||
|
||||||
$this->position->getWorld()->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
|
$world->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
|
||||||
){
|
){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ class Grass extends Opaque{
|
|||||||
$ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS());
|
$ev = new BlockSpreadEvent($b, $this, VanillaBlocks::GRASS());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState(), false);
|
$world->setBlock($b->position, $ev->getNewState(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,23 +92,24 @@ class Grass extends Opaque{
|
|||||||
if($face !== Facing::UP){
|
if($face !== Facing::UP){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($item instanceof Fertilizer){
|
if($item instanceof Fertilizer){
|
||||||
$item->pop();
|
$item->pop();
|
||||||
TallGrassObject::growGrass($this->position->getWorld(), $this->position, new Random(mt_rand()), 8, 2);
|
TallGrassObject::growGrass($world, $this->position, new Random(mt_rand()), 8, 2);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}elseif($item instanceof Hoe){
|
}elseif($item instanceof Hoe){
|
||||||
$item->applyDamage(1);
|
$item->applyDamage(1);
|
||||||
$newBlock = VanillaBlocks::FARMLAND();
|
$newBlock = VanillaBlocks::FARMLAND();
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
$world->setBlock($this->position, $newBlock);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getId() === BlockLegacyIds::AIR){
|
}elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getId() === BlockLegacyIds::AIR){
|
||||||
$item->applyDamage(1);
|
$item->applyDamage(1);
|
||||||
$newBlock = VanillaBlocks::GRASS_PATH();
|
$newBlock = VanillaBlocks::GRASS_PATH();
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
|
||||||
$this->position->getWorld()->setBlock($this->position, $newBlock);
|
$world->setBlock($this->position, $newBlock);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -51,11 +51,12 @@ class Ice extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if($this->position->getWorld()->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
|
$world = $this->position->getWorld();
|
||||||
|
if($world->getHighestAdjacentBlockLight($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||||
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
$ev = new BlockMeltEvent($this, VanillaBlocks::WATER());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,9 @@ use pocketmine\math\Facing;
|
|||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
|
use pocketmine\world\sound\ItemFrameAddItemSound;
|
||||||
|
use pocketmine\world\sound\ItemFrameRemoveItemSound;
|
||||||
|
use pocketmine\world\sound\ItemFrameRotateItemSound;
|
||||||
use function is_infinite;
|
use function is_infinite;
|
||||||
use function is_nan;
|
use function is_nan;
|
||||||
use function lcg_value;
|
use function lcg_value;
|
||||||
@ -136,8 +139,12 @@ class ItemFrame extends Flowable{
|
|||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($this->framedItem !== null){
|
if($this->framedItem !== null){
|
||||||
$this->itemRotation = ($this->itemRotation + 1) % self::ROTATIONS;
|
$this->itemRotation = ($this->itemRotation + 1) % self::ROTATIONS;
|
||||||
|
|
||||||
|
$this->position->getWorld()->addSound($this->position, new ItemFrameRotateItemSound());
|
||||||
}elseif(!$item->isNull()){
|
}elseif(!$item->isNull()){
|
||||||
$this->framedItem = $item->pop();
|
$this->framedItem = $item->pop();
|
||||||
|
|
||||||
|
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
|
||||||
}else{
|
}else{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -151,11 +158,13 @@ class ItemFrame extends Flowable{
|
|||||||
if($this->framedItem === null){
|
if($this->framedItem === null){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(lcg_value() <= $this->itemDropChance){
|
if(lcg_value() <= $this->itemDropChance){
|
||||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
|
||||||
|
$world->addSound($this->position, new ItemFrameRemoveItemSound());
|
||||||
}
|
}
|
||||||
$this->setFramedItem(null);
|
$this->setFramedItem(null);
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ class Leaves extends Transparent{
|
|||||||
/**
|
/**
|
||||||
* @param true[] $visited reference parameter
|
* @param true[] $visited reference parameter
|
||||||
* @phpstan-param array<int, true> $visited
|
* @phpstan-param array<int, true> $visited
|
||||||
|
* @phpstan-param-out array<int, true> $visited
|
||||||
*/
|
*/
|
||||||
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
|
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
|
||||||
$index = World::blockHash($pos->x, $pos->y, $pos->z);
|
$index = World::blockHash($pos->x, $pos->y, $pos->z);
|
||||||
@ -123,11 +124,12 @@ class Leaves extends Transparent{
|
|||||||
if(!$this->noDecay && $this->checkDecay){
|
if(!$this->noDecay && $this->checkDecay){
|
||||||
$ev = new LeavesDecayEvent($this);
|
$ev = new LeavesDecayEvent($this);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($ev->isCancelled() || $this->findLog($this->position)){
|
if($ev->isCancelled() || $this->findLog($this->position)){
|
||||||
$this->checkDecay = false;
|
$this->checkDecay = false;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this, false);
|
$world->setBlock($this->position, $this, false);
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,8 +128,9 @@ class Lectern extends Transparent{
|
|||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($this->book === null && $item instanceof WritableBookBase){
|
if($this->book === null && $item instanceof WritableBookBase){
|
||||||
$this->position->getWorld()->setBlock($this->position, $this->setBook($item));
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound($this->position, new LecternPlaceBookSound());
|
$world->setBlock($this->position, $this->setBook($item));
|
||||||
|
$world->addSound($this->position, new LecternPlaceBookSound());
|
||||||
$item->pop();
|
$item->pop();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -137,8 +138,9 @@ class Lectern extends Transparent{
|
|||||||
|
|
||||||
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
|
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
|
||||||
if($this->book !== null){
|
if($this->book !== null){
|
||||||
$this->position->getWorld()->dropItem($this->position->up(), $this->book);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->setBlock($this->position, $this->setBook(null));
|
$world->dropItem($this->position->up(), $this->book);
|
||||||
|
$world->setBlock($this->position, $this->setBook(null));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -152,12 +154,13 @@ class Lectern extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->viewedPage = $newPage;
|
$this->viewedPage = $newPage;
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(!$this->producingSignal){
|
if(!$this->producingSignal){
|
||||||
$this->producingSignal = true;
|
$this->producingSignal = true;
|
||||||
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
|
$world->scheduleDelayedBlockUpdate($this->position, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,6 @@ class Lever extends Flowable{
|
|||||||
5 => LeverFacing::UP_AXIS_Z(),
|
5 => LeverFacing::UP_AXIS_Z(),
|
||||||
6 => LeverFacing::UP_AXIS_X(),
|
6 => LeverFacing::UP_AXIS_X(),
|
||||||
7 => LeverFacing::DOWN_AXIS_Z(),
|
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;
|
$this->activated = ($stateMeta & BlockLegacyMetadata::LEVER_FLAG_POWERED) !== 0;
|
||||||
@ -128,8 +127,9 @@ class Lever extends Flowable{
|
|||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
$this->activated = !$this->activated;
|
$this->activated = !$this->activated;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound(
|
$world->setBlock($this->position, $this);
|
||||||
|
$world->addSound(
|
||||||
$this->position->add(0.5, 0.5, 0.5),
|
$this->position->add(0.5, 0.5, 0.5),
|
||||||
$this->activated ? new RedstonePowerOnSound() : new RedstonePowerOffSound()
|
$this->activated ? new RedstonePowerOnSound() : new RedstonePowerOffSound()
|
||||||
);
|
);
|
||||||
|
@ -329,7 +329,7 @@ abstract class Liquid extends Transparent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($adjacentDecay <= self::MAX_DECAY){
|
if($adjacentDecay <= self::MAX_DECAY){
|
||||||
$calculator = new MinimumCostFlowCalculator($this->position->getWorld(), $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
|
$calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), \Closure::fromCallable([$this, 'canFlowInto']));
|
||||||
foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
|
foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
|
||||||
$this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);
|
$this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);
|
||||||
}
|
}
|
||||||
@ -348,11 +348,12 @@ abstract class Liquid extends Transparent{
|
|||||||
$ev = new BlockSpreadEvent($block, $this, $new);
|
$ev = new BlockSpreadEvent($block, $this, $new);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($block->getId() !== BlockLegacyIds::AIR){
|
if($block->getId() !== BlockLegacyIds::AIR){
|
||||||
$this->position->getWorld()->useBreakOn($block->position);
|
$world->useBreakOn($block->position);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->position->getWorld()->setBlock($block->position, $ev->getNewState());
|
$world->setBlock($block->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -382,8 +383,9 @@ abstract class Liquid extends Transparent{
|
|||||||
$ev = new BlockFormEvent($this, $result);
|
$ev = new BlockFormEvent($this, $result);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
|
$world->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ use pocketmine\item\Item;
|
|||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use function mt_rand;
|
use function mt_rand;
|
||||||
|
|
||||||
class Melon extends Transparent{
|
class Melon extends Opaque{
|
||||||
|
|
||||||
public function getDropsForCompatibleTool(Item $item) : array{
|
public function getDropsForCompatibleTool(Item $item) : array{
|
||||||
return [
|
return [
|
||||||
|
@ -49,13 +49,14 @@ class Mycelium extends Opaque{
|
|||||||
$x = mt_rand($this->position->x - 1, $this->position->x + 1);
|
$x = mt_rand($this->position->x - 1, $this->position->x + 1);
|
||||||
$y = mt_rand($this->position->y - 2, $this->position->y + 2);
|
$y = mt_rand($this->position->y - 2, $this->position->y + 2);
|
||||||
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
|
||||||
$block = $this->position->getWorld()->getBlockAt($x, $y, $z);
|
$world = $this->position->getWorld();
|
||||||
|
$block = $world->getBlockAt($x, $y, $z);
|
||||||
if($block instanceof Dirt && !$block->isCoarse()){
|
if($block instanceof Dirt && !$block->isCoarse()){
|
||||||
if($block->getSide(Facing::UP) instanceof Transparent){
|
if($block->getSide(Facing::UP) instanceof Transparent){
|
||||||
$ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM());
|
$ev = new BlockSpreadEvent($block, $this, VanillaBlocks::MYCELIUM());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($block->position, $ev->getNewState());
|
$world->setBlock($block->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque blocks do not allow light to pass through. They are usually collidable full-cube blocks.
|
||||||
|
* Most blocks in Minecraft fall into this category.
|
||||||
|
*/
|
||||||
class Opaque extends Block{
|
class Opaque extends Block{
|
||||||
|
|
||||||
public function isSolid() : bool{
|
public function isSolid() : bool{
|
||||||
|
@ -36,8 +36,9 @@ class Pumpkin extends Opaque{
|
|||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($item instanceof Shears && in_array($face, Facing::HORIZONTAL, true)){
|
if($item instanceof Shears && in_array($face, Facing::HORIZONTAL, true)){
|
||||||
$item->applyDamage(1);
|
$item->applyDamage(1);
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
|
$world->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
|
||||||
|
$world->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -43,7 +43,11 @@ class RedMushroom extends Flowable{
|
|||||||
|
|
||||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
$down = $this->getSide(Facing::DOWN);
|
$down = $this->getSide(Facing::DOWN);
|
||||||
if(!$down->isTransparent()){
|
$position = $this->getPosition();
|
||||||
|
$lightLevel = $position->getWorld()->getFullLightAt($position->x, $position->y, $position->z);
|
||||||
|
$downId = $down->getId();
|
||||||
|
//TODO: nylium support
|
||||||
|
if(($lightLevel <= 12 && !$down->isTransparent()) || $downId === BlockLegacyIds::MYCELIUM || $downId === BlockLegacyIds::PODZOL){
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,12 +96,13 @@ class Sapling extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if($this->position->getWorld()->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 && mt_rand(1, 7) === 1){
|
$world = $this->position->getWorld();
|
||||||
|
if($world->getFullLightAt($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) >= 8 && mt_rand(1, 7) === 1){
|
||||||
if($this->ready){
|
if($this->ready){
|
||||||
$this->grow(null);
|
$this->grow(null);
|
||||||
}else{
|
}else{
|
||||||
$this->ready = true;
|
$this->ready = true;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,11 +111,12 @@ class SnowLayer extends Flowable implements Fallable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if($this->position->getWorld()->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
|
$world = $this->position->getWorld();
|
||||||
|
if($world->getBlockLightAt($this->position->x, $this->position->y, $this->position->z) >= 12){
|
||||||
$ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
|
$ev = new BlockMeltEvent($this, VanillaBlocks::AIR());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,13 +35,14 @@ abstract class Stem extends Crops{
|
|||||||
|
|
||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if(mt_rand(0, 2) === 1){
|
if(mt_rand(0, 2) === 1){
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($this->age < self::MAX_AGE){
|
if($this->age < self::MAX_AGE){
|
||||||
$block = clone $this;
|
$block = clone $this;
|
||||||
++$block->age;
|
++$block->age;
|
||||||
$ev = new BlockGrowEvent($this, $block);
|
$ev = new BlockGrowEvent($this, $block);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
$grow = $this->getPlant();
|
$grow = $this->getPlant();
|
||||||
@ -57,7 +58,7 @@ abstract class Stem extends Crops{
|
|||||||
$ev = new BlockGrowEvent($side, $grow);
|
$ev = new BlockGrowEvent($side, $grow);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($side->position, $ev->getNewState());
|
$world->setBlock($side->position, $ev->getNewState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ use pocketmine\math\Facing;
|
|||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
use pocketmine\world\BlockTransaction;
|
use pocketmine\world\BlockTransaction;
|
||||||
|
use pocketmine\world\Position;
|
||||||
|
|
||||||
class Sugarcane extends Flowable{
|
class Sugarcane extends Flowable{
|
||||||
public const MAX_AGE = 15;
|
public const MAX_AGE = 15;
|
||||||
@ -49,27 +50,37 @@ class Sugarcane extends Flowable{
|
|||||||
return 0b1111;
|
return 0b1111;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function grow() : bool{
|
private function seekToBottom() : Position{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
|
$bottom = $this->position;
|
||||||
|
while(($next = $world->getBlock($bottom->down()))->isSameType($this)){
|
||||||
|
$bottom = $next->position;
|
||||||
|
}
|
||||||
|
return $bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function grow(Position $pos) : bool{
|
||||||
$grew = false;
|
$grew = false;
|
||||||
|
$world = $pos->getWorld();
|
||||||
for($y = 1; $y < 3; ++$y){
|
for($y = 1; $y < 3; ++$y){
|
||||||
if(!$this->position->getWorld()->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){
|
if(!$world->isInWorld($pos->x, $pos->y + $y, $pos->z)){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$b = $this->position->getWorld()->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z);
|
$b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z);
|
||||||
if($b->getId() === BlockLegacyIds::AIR){
|
if($b->getId() === BlockLegacyIds::AIR){
|
||||||
$ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE());
|
$ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE());
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if($ev->isCancelled()){
|
if($ev->isCancelled()){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$this->position->getWorld()->setBlock($b->position, $ev->getNewState());
|
$world->setBlock($b->position, $ev->getNewState());
|
||||||
$grew = true;
|
$grew = true;
|
||||||
}else{
|
}elseif(!$b->isSameType($this)){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->age = 0;
|
$this->age = 0;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($pos, $this);
|
||||||
return $grew;
|
return $grew;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +97,7 @@ class Sugarcane extends Flowable{
|
|||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
if($item instanceof Fertilizer){
|
if($item instanceof Fertilizer){
|
||||||
if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){
|
if($this->grow($this->seekToBottom())){
|
||||||
$item->pop();
|
$item->pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +109,7 @@ class Sugarcane extends Flowable{
|
|||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$down = $this->getSide(Facing::DOWN);
|
$down = $this->getSide(Facing::DOWN);
|
||||||
if($down->isTransparent() && !$down->isSameType($this)){
|
if(!$this->isValidSupport($down)){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$this->position->getWorld()->useBreakOn($this->position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +121,7 @@ class Sugarcane extends Flowable{
|
|||||||
public function onRandomTick() : void{
|
public function onRandomTick() : void{
|
||||||
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
if(!$this->getSide(Facing::DOWN)->isSameType($this)){
|
||||||
if($this->age === self::MAX_AGE){
|
if($this->age === self::MAX_AGE){
|
||||||
$this->grow();
|
$this->grow($this->position);
|
||||||
}else{
|
}else{
|
||||||
++$this->age;
|
++$this->age;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$this->position->getWorld()->setBlock($this->position, $this);
|
||||||
@ -122,9 +133,10 @@ class Sugarcane extends Flowable{
|
|||||||
$down = $this->getSide(Facing::DOWN);
|
$down = $this->getSide(Facing::DOWN);
|
||||||
if($down->isSameType($this)){
|
if($down->isSameType($this)){
|
||||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}elseif($down->getId() === BlockLegacyIds::GRASS || $down->getId() === BlockLegacyIds::DIRT || $down->getId() === BlockLegacyIds::SAND || $down->getId() === BlockLegacyIds::PODZOL){
|
}elseif($this->isValidSupport($down)){
|
||||||
foreach(Facing::HORIZONTAL as $side){
|
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);
|
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,4 +144,15 @@ class Sugarcane extends Flowable{
|
|||||||
|
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ class SweetBerryBush extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){
|
if($this->age < self::STAGE_MATURE && $item instanceof Fertilizer){
|
||||||
$block = clone $this;
|
$block = clone $this;
|
||||||
$block->age++;
|
$block->age++;
|
||||||
@ -98,13 +99,13 @@ class SweetBerryBush extends Flowable{
|
|||||||
$ev->call();
|
$ev->call();
|
||||||
|
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$this->position->getWorld()->setBlock($this->position, $ev->getNewState());
|
$world->setBlock($this->position, $ev->getNewState());
|
||||||
$item->pop();
|
$item->pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
|
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
|
||||||
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
|
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
|
||||||
$this->position->getWorld()->dropItem($this->position, $this->asItem()->setCount($dropAmount));
|
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -110,11 +110,12 @@ class TNT extends Opaque{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function ignite(int $fuse = 80) : void{
|
public function ignite(int $fuse = 80) : void{
|
||||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
$world = $this->position->getWorld();
|
||||||
|
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
|
|
||||||
$mot = (new Random())->nextSignedFloat() * M_PI * 2;
|
$mot = (new Random())->nextSignedFloat() * M_PI * 2;
|
||||||
|
|
||||||
$tnt = new PrimedTNT(Location::fromObject($this->position->add(0.5, 0, 0.5), $this->position->getWorld()));
|
$tnt = new PrimedTNT(Location::fromObject($this->position->add(0.5, 0, 0.5), $world));
|
||||||
$tnt->setFuse($fuse);
|
$tnt->setFuse($fuse);
|
||||||
$tnt->setWorksUnderwater($this->worksUnderwater);
|
$tnt->setWorksUnderwater($this->worksUnderwater);
|
||||||
$tnt->setMotion(new Vector3(-sin($mot) * 0.02, 0.2, -cos($mot) * 0.02));
|
$tnt->setMotion(new Vector3(-sin($mot) * 0.02, 0.2, -cos($mot) * 0.02));
|
||||||
|
@ -29,6 +29,9 @@ use pocketmine\math\AxisAlignedBB;
|
|||||||
use pocketmine\math\Facing;
|
use pocketmine\math\Facing;
|
||||||
use function count;
|
use function count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thin blocks behave like glass panes. They connect to full-cube blocks horizontally adjacent to them if possible.
|
||||||
|
*/
|
||||||
class Thin extends Transparent{
|
class Thin extends Transparent{
|
||||||
/** @var bool[] facing => dummy */
|
/** @var bool[] facing => dummy */
|
||||||
protected array $connections = [];
|
protected array $connections = [];
|
||||||
|
@ -23,6 +23,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transparent blocks do not block any light from propagating through them.
|
||||||
|
*
|
||||||
|
* Note: This does **not** imply that the block is **visually** transparent. For example, chests allow light to pass
|
||||||
|
* through, but the player cannot see through them except at the edges.
|
||||||
|
*/
|
||||||
class Transparent extends Block{
|
class Transparent extends Block{
|
||||||
|
|
||||||
public function isTransparent() : bool{
|
public function isTransparent() : bool{
|
||||||
|
@ -96,8 +96,9 @@ class Trapdoor extends Transparent{
|
|||||||
|
|
||||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||||
$this->open = !$this->open;
|
$this->open = !$this->open;
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world = $this->position->getWorld();
|
||||||
$this->position->getWorld()->addSound($this->position, new DoorSound());
|
$world->setBlock($this->position, $this);
|
||||||
|
$world->addSound($this->position, new DoorSound());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ namespace pocketmine\block;
|
|||||||
|
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a block which is unrecognized or not implemented.
|
||||||
|
*/
|
||||||
class UnknownBlock extends Transparent{
|
class UnknownBlock extends Transparent{
|
||||||
|
|
||||||
public function __construct(BlockIdentifier $idInfo, BlockBreakInfo $breakInfo){
|
public function __construct(BlockIdentifier $idInfo, BlockBreakInfo $breakInfo){
|
||||||
|
@ -585,6 +585,7 @@ final class VanillaBlocks{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Block[]
|
* @return Block[]
|
||||||
|
* @phpstan-return array<string, Block>
|
||||||
*/
|
*/
|
||||||
public static function getAll() : array{
|
public static function getAll() : array{
|
||||||
//phpstan doesn't support generic traits yet :(
|
//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{
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,10 +141,11 @@ class Vine extends Flowable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($changed){
|
if($changed){
|
||||||
|
$world = $this->position->getWorld();
|
||||||
if(count($this->faces) === 0){
|
if(count($this->faces) === 0){
|
||||||
$this->position->getWorld()->useBreakOn($this->position);
|
$world->useBreakOn($this->position);
|
||||||
}else{
|
}else{
|
||||||
$this->position->getWorld()->setBlock($this->position, $this);
|
$world->setBlock($this->position, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,10 @@ class BarrelInventory extends SimpleInventory implements BlockInventory{
|
|||||||
|
|
||||||
protected function animateBlock(bool $isOpen) : void{
|
protected function animateBlock(bool $isOpen) : void{
|
||||||
$holder = $this->getHolder();
|
$holder = $this->getHolder();
|
||||||
$block = $holder->getWorld()->getBlock($holder);
|
$world = $holder->getWorld();
|
||||||
|
$block = $world->getBlock($holder);
|
||||||
if($block instanceof Barrel){
|
if($block instanceof Barrel){
|
||||||
$holder->getWorld()->setBlock($holder, $block->setOpen($isOpen));
|
$world->setBlock($holder, $block->setOpen($isOpen));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ class Banner extends Spawnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param BannerPatternLayer[] $patterns
|
* @param BannerPatternLayer[] $patterns
|
||||||
*
|
*
|
||||||
* @phpstan-param list<BannerPatternLayer> $patterns
|
* @phpstan-param list<BannerPatternLayer> $patterns
|
||||||
*/
|
*/
|
||||||
|
@ -96,8 +96,10 @@ trait ContainerTrait{
|
|||||||
$inv = $this->getRealInventory();
|
$inv = $this->getRealInventory();
|
||||||
$pos = $this->getPosition();
|
$pos = $this->getPosition();
|
||||||
|
|
||||||
|
$world = $pos->getWorld();
|
||||||
|
$dropPos = $pos->add(0.5, 0.5, 0.5);
|
||||||
foreach($inv->getContents() as $k => $item){
|
foreach($inv->getContents() as $k => $item){
|
||||||
$pos->getWorld()->dropItem($pos->add(0.5, 0.5, 0.5), $item);
|
$world->dropItem($dropPos, $item);
|
||||||
}
|
}
|
||||||
$inv->clearAll();
|
$inv->clearAll();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ use pocketmine\block\utils\SignText;
|
|||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
use pocketmine\nbt\tag\StringTag;
|
use pocketmine\nbt\tag\StringTag;
|
||||||
|
use pocketmine\utils\Binary;
|
||||||
use pocketmine\world\World;
|
use pocketmine\world\World;
|
||||||
use function array_pad;
|
use function array_pad;
|
||||||
use function array_slice;
|
use function array_slice;
|
||||||
@ -42,6 +43,13 @@ use function sprintf;
|
|||||||
class Sign extends Spawnable{
|
class Sign extends Spawnable{
|
||||||
public const TAG_TEXT_BLOB = "Text";
|
public const TAG_TEXT_BLOB = "Text";
|
||||||
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able
|
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able
|
||||||
|
public const TAG_TEXT_COLOR = "SignTextColor";
|
||||||
|
public const TAG_GLOWING_TEXT = "IgnoreLighting";
|
||||||
|
/**
|
||||||
|
* This tag is set to indicate that MCPE-117835 has been addressed in whatever version this sign was created.
|
||||||
|
* @see https://bugs.mojang.com/browse/MCPE-117835
|
||||||
|
*/
|
||||||
|
public const TAG_LEGACY_BUG_RESOLVE = "TextIgnoreLegacyBugResolved";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string[]
|
* @return string[]
|
||||||
@ -111,5 +119,11 @@ class Sign extends Spawnable{
|
|||||||
|
|
||||||
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
|
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
|
||||||
$nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
|
$nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines()));
|
||||||
|
|
||||||
|
//the following are not yet used by the server, but needed to roll back any changes to glowing state or colour
|
||||||
|
//if the client uses dye on the sign
|
||||||
|
$nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00));
|
||||||
|
$nbt->setByte(self::TAG_GLOWING_TEXT, 0);
|
||||||
|
$nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,15 @@ trait FallableTrait{
|
|||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$pos = $this->getPosition();
|
$pos = $this->getPosition();
|
||||||
$down = $pos->getWorld()->getBlock($pos->getSide(Facing::DOWN));
|
$world = $pos->getWorld();
|
||||||
|
$down = $world->getBlock($pos->getSide(Facing::DOWN));
|
||||||
if($down->canBeReplaced()){
|
if($down->canBeReplaced()){
|
||||||
$pos->getWorld()->setBlock($pos, VanillaBlocks::AIR());
|
$world->setBlock($pos, VanillaBlocks::AIR());
|
||||||
|
|
||||||
$block = $this;
|
$block = $this;
|
||||||
if(!($block instanceof Block)) throw new AssumptionFailedError(__TRAIT__ . " should only be used by Blocks");
|
if(!($block instanceof Block)) throw new AssumptionFailedError(__TRAIT__ . " should only be used by Blocks");
|
||||||
|
|
||||||
$fall = new FallingBlock(Location::fromObject($pos->add(0.5, 0, 0.5), $pos->getWorld()), $block);
|
$fall = new FallingBlock(Location::fromObject($pos->add(0.5, 0, 0.5), $world), $block);
|
||||||
$fall->spawnToAll();
|
$fall->spawnToAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ class SignText{
|
|||||||
private array $lines;
|
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 the array size is greater than 4
|
||||||
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
|
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
|
||||||
|
@ -27,13 +27,13 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\command;
|
namespace pocketmine\command;
|
||||||
|
|
||||||
use pocketmine\command\utils\CommandException;
|
use pocketmine\command\utils\CommandException;
|
||||||
use pocketmine\console\ConsoleCommandSender;
|
|
||||||
use pocketmine\lang\KnownTranslationFactory;
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
use pocketmine\lang\Translatable;
|
use pocketmine\lang\Translatable;
|
||||||
use pocketmine\permission\PermissionManager;
|
use pocketmine\permission\PermissionManager;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\timings\Timings;
|
use pocketmine\timings\Timings;
|
||||||
use pocketmine\timings\TimingsHandler;
|
use pocketmine\timings\TimingsHandler;
|
||||||
|
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||||
use pocketmine\utils\TextFormat;
|
use pocketmine\utils\TextFormat;
|
||||||
use function explode;
|
use function explode;
|
||||||
use function str_replace;
|
use function str_replace;
|
||||||
@ -227,12 +227,12 @@ abstract class Command{
|
|||||||
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
|
||||||
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
|
||||||
|
|
||||||
if($sendToSource && !($source instanceof ConsoleCommandSender)){
|
if($sendToSource){
|
||||||
$source->sendMessage($message);
|
$source->sendMessage($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($users as $user){
|
foreach($users as $user){
|
||||||
if($user instanceof ConsoleCommandSender){
|
if($user instanceof BroadcastLoggerForwarder){
|
||||||
$user->sendMessage($result);
|
$user->sendMessage($result);
|
||||||
}elseif($user !== $source){
|
}elseif($user !== $source){
|
||||||
$user->sendMessage($colored);
|
$user->sendMessage($colored);
|
||||||
|
@ -26,7 +26,7 @@ namespace pocketmine\command;
|
|||||||
interface CommandExecutor{
|
interface CommandExecutor{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $args
|
* @param string[] $args
|
||||||
*/
|
*/
|
||||||
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool;
|
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool;
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
|
|||||||
use pocketmine\lang\KnownTranslationFactory;
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\utils\TextFormat;
|
use pocketmine\utils\TextFormat;
|
||||||
use function array_map;
|
|
||||||
use function array_shift;
|
use function array_shift;
|
||||||
use function count;
|
use function count;
|
||||||
|
use function implode;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
use function strpos;
|
use function strpos;
|
||||||
@ -52,7 +52,7 @@ class FormattedCommandAlias extends Command{
|
|||||||
string $alias,
|
string $alias,
|
||||||
private array $formatStrings
|
private array $formatStrings
|
||||||
){
|
){
|
||||||
parent::__construct($alias);
|
parent::__construct($alias, KnownTranslationFactory::pocketmine_command_userDefined_description());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||||
@ -62,7 +62,20 @@ class FormattedCommandAlias extends Command{
|
|||||||
foreach($this->formatStrings as $formatString){
|
foreach($this->formatStrings as $formatString){
|
||||||
try{
|
try{
|
||||||
$formatArgs = CommandStringHelper::parseQuoteAware($formatString);
|
$formatArgs = CommandStringHelper::parseQuoteAware($formatString);
|
||||||
$commands[] = array_map(fn(string $formatArg) => $this->buildCommand($formatArg, $args), $formatArgs);
|
$unresolved = [];
|
||||||
|
$processedArgs = [];
|
||||||
|
foreach($formatArgs as $formatArg){
|
||||||
|
$processedArg = $this->buildCommand($formatArg, $args);
|
||||||
|
if($processedArg === null){
|
||||||
|
$unresolved[] = $formatArg;
|
||||||
|
}elseif(count($unresolved) !== 0){
|
||||||
|
//unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
|
||||||
|
throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
|
||||||
|
}else{
|
||||||
|
$processedArgs[] = $processedArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$commands[] = $processedArgs;
|
||||||
}catch(\InvalidArgumentException $e){
|
}catch(\InvalidArgumentException $e){
|
||||||
$sender->sendMessage(TextFormat::RED . $e->getMessage());
|
$sender->sendMessage(TextFormat::RED . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
@ -107,7 +120,7 @@ class FormattedCommandAlias extends Command{
|
|||||||
/**
|
/**
|
||||||
* @param string[] $args
|
* @param string[] $args
|
||||||
*/
|
*/
|
||||||
private function buildCommand(string $formatString, array $args) : string{
|
private function buildCommand(string $formatString, array $args) : ?string{
|
||||||
$index = 0;
|
$index = 0;
|
||||||
while(($index = strpos($formatString, '$', $index)) !== false){
|
while(($index = strpos($formatString, '$', $index)) !== false){
|
||||||
$start = $index;
|
$start = $index;
|
||||||
@ -129,6 +142,9 @@ class FormattedCommandAlias extends Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$replacement = self::buildReplacement($args, $position, $rest);
|
$replacement = self::buildReplacement($args, $position, $rest);
|
||||||
|
if($replacement === null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$end = $index + strlen($fullPlaceholder);
|
$end = $index + strlen($fullPlaceholder);
|
||||||
$formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
|
$formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
|
||||||
@ -143,9 +159,9 @@ class FormattedCommandAlias extends Command{
|
|||||||
* @param string[] $args
|
* @param string[] $args
|
||||||
* @phpstan-param list<string> $args
|
* @phpstan-param list<string> $args
|
||||||
*/
|
*/
|
||||||
private static function buildReplacement(array $args, int $position, bool $rest) : string{
|
private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
|
||||||
$replacement = "";
|
|
||||||
if($rest && $position < count($args)){
|
if($rest && $position < count($args)){
|
||||||
|
$replacement = "";
|
||||||
for($i = $position, $c = count($args); $i < $c; ++$i){
|
for($i = $position, $c = count($args); $i < $c; ++$i){
|
||||||
if($i !== $position){
|
if($i !== $position){
|
||||||
$replacement .= " ";
|
$replacement .= " ";
|
||||||
@ -153,11 +169,13 @@ class FormattedCommandAlias extends Command{
|
|||||||
|
|
||||||
$replacement .= $args[$i];
|
$replacement .= $args[$i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $replacement;
|
||||||
}elseif($position < count($args)){
|
}elseif($position < count($args)){
|
||||||
$replacement .= $args[$position];
|
return $args[$position];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $replacement;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,7 +139,7 @@ class SimpleCommandMap implements CommandMap{
|
|||||||
|
|
||||||
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool{
|
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool{
|
||||||
if($label === null){
|
if($label === null){
|
||||||
$label = $command->getName();
|
$label = $command->getLabel();
|
||||||
}
|
}
|
||||||
$label = trim($label);
|
$label = trim($label);
|
||||||
$fallbackPrefix = strtolower(trim($fallbackPrefix));
|
$fallbackPrefix = strtolower(trim($fallbackPrefix));
|
||||||
@ -272,10 +272,11 @@ class SimpleCommandMap implements CommandMap{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//These registered commands have absolute priority
|
//These registered commands have absolute priority
|
||||||
|
$lowerAlias = strtolower($alias);
|
||||||
if(count($targets) > 0){
|
if(count($targets) > 0){
|
||||||
$this->knownCommands[strtolower($alias)] = new FormattedCommandAlias(strtolower($alias), $targets);
|
$this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets);
|
||||||
}else{
|
}else{
|
||||||
unset($this->knownCommands[strtolower($alias)]);
|
unset($this->knownCommands[$lowerAlias]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace pocketmine\command\defaults;
|
|||||||
|
|
||||||
use pocketmine\command\CommandSender;
|
use pocketmine\command\CommandSender;
|
||||||
use pocketmine\permission\DefaultPermissionNames;
|
use pocketmine\permission\DefaultPermissionNames;
|
||||||
use Webmozart\PathUtil\Path;
|
use Symfony\Component\Filesystem\Path;
|
||||||
use function date;
|
use function date;
|
||||||
|
|
||||||
class DumpMemoryCommand extends VanillaCommand{
|
class DumpMemoryCommand extends VanillaCommand{
|
||||||
|
@ -63,7 +63,7 @@ class GarbageCollectorCommand extends VanillaCommand{
|
|||||||
|
|
||||||
$cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector();
|
$cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector();
|
||||||
|
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::WHITE, TextFormat::GREEN . " ----" . TextFormat::WHITE));
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::RESET, TextFormat::GREEN . " ----" . TextFormat::RESET));
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_chunks(TextFormat::RED . number_format($chunksCollected))->prefix(TextFormat::GOLD));
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_chunks(TextFormat::RED . number_format($chunksCollected))->prefix(TextFormat::GOLD));
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD));
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD));
|
||||||
|
|
||||||
|
@ -75,7 +75,11 @@ class GiveCommand extends VanillaCommand{
|
|||||||
if(!isset($args[2])){
|
if(!isset($args[2])){
|
||||||
$item->setCount($item->getMaxStackSize());
|
$item->setCount($item->getMaxStackSize());
|
||||||
}else{
|
}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])){
|
if(isset($args[3])){
|
||||||
|
@ -76,11 +76,10 @@ class HelpCommand extends VanillaCommand{
|
|||||||
$pageHeight = $sender->getScreenLineHeight();
|
$pageHeight = $sender->getScreenLineHeight();
|
||||||
|
|
||||||
if($commandName === ""){
|
if($commandName === ""){
|
||||||
/** @var Command[][] $commands */
|
|
||||||
$commands = [];
|
$commands = [];
|
||||||
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
|
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
|
||||||
if($command->testPermissionSilent($sender)){
|
if($command->testPermissionSilent($sender)){
|
||||||
$commands[$command->getName()] = $command;
|
$commands[$command->getLabel()] = $command;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ksort($commands, SORT_NATURAL | SORT_FLAG_CASE);
|
ksort($commands, SORT_NATURAL | SORT_FLAG_CASE);
|
||||||
@ -95,7 +94,7 @@ class HelpCommand extends VanillaCommand{
|
|||||||
foreach($commands[$pageNumber - 1] as $command){
|
foreach($commands[$pageNumber - 1] as $command){
|
||||||
$description = $command->getDescription();
|
$description = $command->getDescription();
|
||||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::WHITE . $descriptionString);
|
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,19 +105,19 @@ class HelpCommand extends VanillaCommand{
|
|||||||
$lang = $sender->getLanguage();
|
$lang = $sender->getLanguage();
|
||||||
$description = $cmd->getDescription();
|
$description = $cmd->getDescription();
|
||||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getName())
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::WHITE, TextFormat::YELLOW . " ---------"));
|
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::WHITE . $descriptionString)
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||||
->prefix(TextFormat::GOLD));
|
->prefix(TextFormat::GOLD));
|
||||||
|
|
||||||
$usage = $cmd->getUsage();
|
$usage = $cmd->getUsage();
|
||||||
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $usageString)))
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
|
||||||
->prefix(TextFormat::GOLD));
|
->prefix(TextFormat::GOLD));
|
||||||
|
|
||||||
$aliases = $cmd->getAliases();
|
$aliases = $cmd->getAliases();
|
||||||
sort($aliases, SORT_NATURAL);
|
sort($aliases, SORT_NATURAL);
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::WHITE . implode(", ", $aliases))
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
|
||||||
->prefix(TextFormat::GOLD));
|
->prefix(TextFormat::GOLD));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -52,7 +52,7 @@ class MeCommand extends VanillaCommand{
|
|||||||
throw new InvalidCommandSyntaxException();
|
throw new InvalidCommandSyntaxException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::WHITE . implode(" ", $args)));
|
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args)));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class PluginsCommand extends VanillaCommand{
|
|||||||
}, $sender->getServer()->getPluginManager()->getPlugins());
|
}, $sender->getServer()->getPluginManager()->getPlugins());
|
||||||
sort($list, SORT_STRING);
|
sort($list, SORT_STRING);
|
||||||
|
|
||||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::WHITE . ", ", $list)));
|
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::RESET . ", ", $list)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class StatusCommand extends VanillaCommand{
|
|||||||
$mUsage = Process::getAdvancedMemoryUsage();
|
$mUsage = Process::getAdvancedMemoryUsage();
|
||||||
|
|
||||||
$server = $sender->getServer();
|
$server = $sender->getServer();
|
||||||
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----");
|
$sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::RESET . "Server status" . TextFormat::GREEN . " ----");
|
||||||
|
|
||||||
$time = (int) (microtime(true) - $server->getStartTime());
|
$time = (int) (microtime(true) - $server->getStartTime());
|
||||||
|
|
||||||
@ -82,10 +82,10 @@ class StatusCommand extends VanillaCommand{
|
|||||||
$sender->sendMessage(TextFormat::GOLD . "Uptime: " . TextFormat::RED . $uptime);
|
$sender->sendMessage(TextFormat::GOLD . "Uptime: " . TextFormat::RED . $uptime);
|
||||||
|
|
||||||
$tpsColor = TextFormat::GREEN;
|
$tpsColor = TextFormat::GREEN;
|
||||||
if($server->getTicksPerSecond() < 17){
|
if($server->getTicksPerSecond() < 12){
|
||||||
$tpsColor = TextFormat::GOLD;
|
|
||||||
}elseif($server->getTicksPerSecond() < 12){
|
|
||||||
$tpsColor = TextFormat::RED;
|
$tpsColor = TextFormat::RED;
|
||||||
|
}elseif($server->getTicksPerSecond() < 17){
|
||||||
|
$tpsColor = TextFormat::GOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sender->sendMessage(TextFormat::GOLD . "Current TPS: {$tpsColor}{$server->getTicksPerSecond()} ({$server->getTickUsage()}%)");
|
$sender->sendMessage(TextFormat::GOLD . "Current TPS: {$tpsColor}{$server->getTicksPerSecond()} ({$server->getTickUsage()}%)");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user