mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-13 12:55:06 +00:00
Compare commits
122 Commits
5.25.2
...
repeated-p
Author | SHA1 | Date | |
---|---|---|---|
9946ea9e8a | |||
14b70c04ad | |||
56da492e48 | |||
035d2dec23 | |||
baafaed362 | |||
bf33a625c9 | |||
059f4ee7bf | |||
dd17adeaaf | |||
98f28f8b6d | |||
a554d2acf5 | |||
5527a0c6bf | |||
eee2e62d81 | |||
4d5c27a734 | |||
e1af2a4af1 | |||
f656d7d3d7 | |||
3636173d75 | |||
9606c0e0bb | |||
55123b36e1 | |||
94fb5d95b9 | |||
b5f236c019 | |||
7169f8e553 | |||
657e6c8130 | |||
647c2587a8 | |||
81d3017ad5 | |||
a37353c060 | |||
280911ec59 | |||
abb004fbc5 | |||
e0864e7ee8 | |||
dca37d5842 | |||
67f3bb9c52 | |||
acf4341d71 | |||
84bb9d2ab4 | |||
50f3fe2578 | |||
04de72e85e | |||
4bcef443f7 | |||
d90fc3415c | |||
e2e16a4ec5 | |||
134c7309c5 | |||
5e830c7320 | |||
c1cee1fc24 | |||
d789c75c00 | |||
6196b9c995 | |||
f2e7473629 | |||
6bf9a305de | |||
f8abcd5102 | |||
6f3506360e | |||
efaf9311b3 | |||
fe70b31881 | |||
cfafb584a8 | |||
ad6f7dfedb | |||
1ea5c060fd | |||
4a5c1e7540 | |||
2548422973 | |||
028815490e | |||
a74168953c | |||
f661443ec7 | |||
1073f372f8 | |||
835c383d4e | |||
d3f6c22996 | |||
6f3851be80 | |||
071c15d7de | |||
673b39e2a1 | |||
17faa19743 | |||
e88b81a4cb | |||
687112f4cd | |||
644c04bee4 | |||
c9e85603b0 | |||
d954772a20 | |||
c80a4d5b55 | |||
f416cb8902 | |||
f123df5e0d | |||
de26ebd124 | |||
6b04bb504f | |||
1c6a4bde86 | |||
c2f8e9365b | |||
4ef21fabab | |||
4407e585e4 | |||
463be36b72 | |||
8b57e9007a | |||
e03c586c86 | |||
802e373bf3 | |||
09acbfab4c | |||
7cfaf04b87 | |||
d9e0e51e14 | |||
069ecf007f | |||
341c7a03a9 | |||
7ae90dda5d | |||
73a4b076d6 | |||
00df508727 | |||
a6553097f4 | |||
afc4a3c7f1 | |||
ac7b5b3b13 | |||
7af5eb3da2 | |||
95284bc9de | |||
2291546610 | |||
aad2bce9e4 | |||
09f0ce458c | |||
50a1e59aa4 | |||
9382e6e5b3 | |||
afac178cf4 | |||
e2f5e3e73c | |||
19425e35ea | |||
a08b06d322 | |||
c876253f76 | |||
67272f8f2b | |||
9744bd7073 | |||
51cf6817b1 | |||
fd04894a7b | |||
d2d6a59c72 | |||
788ee9a008 | |||
246c1776df | |||
2670e81668 | |||
e29aa2f337 | |||
9b3b45258a | |||
ca4debbf08 | |||
03f98ee0a9 | |||
70368ea653 | |||
21ccd90147 | |||
0a9a45a126 | |||
88937f1785 | |||
e5a783cb9e | |||
70fb9bbdfd |
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -57,6 +57,9 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
## Version, OS and game info
|
||||
> [!WARNING]
|
||||
> "Latest" is not a valid version.
|
||||
> Failure to fill these fields with valid information may result in your issue being closed.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -12,6 +12,10 @@ updates:
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
- "version-update:semver-minor"
|
||||
|
||||
#since we lock this to exact versions, it causes conflicts with minor-next & major-next in composer.lock
|
||||
#better to just test updates to this locally anyway since almost every version breaks something
|
||||
- dependency-name: phpstan/phpstan
|
||||
groups:
|
||||
production-patch-updates:
|
||||
dependency-type: production
|
||||
|
32
.github/workflows/branch-sync-cron-trigger.yml
vendored
Normal file
32
.github/workflows/branch-sync-cron-trigger.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#Since GitHub automatically disables cron actions after 60 days of repo inactivity, we need the active repo (PM)
|
||||
#to trigger the branch merge workflow explicitly. This avoids the need for TOS-violating actions which we previously
|
||||
#used to keep the restricted action active, as the workflow depends on the activity of this repo anyway.
|
||||
|
||||
name: Trigger branch sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" #once per day so we don't spam merge commits on busy days
|
||||
workflow_dispatch: #for testing
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: Trigger branch sync RestrictedActions workflow
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
- name: Dispatch branch sync restricted action
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: pocketmine_mp_branch_sync
|
10
.github/workflows/build-docker-image.yml
vendored
10
.github/workflows/build-docker-image.yml
vendored
@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Update Docker Hub images
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.32.0
|
||||
uses: shivammathur/setup-php@2.33.0
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
6
.github/workflows/draft-release-pr-check.yml
vendored
6
.github/workflows/draft-release-pr-check.yml
vendored
@ -24,7 +24,7 @@ permissions:
|
||||
jobs:
|
||||
check-intent:
|
||||
name: Check release trigger
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
outputs:
|
||||
valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }}
|
||||
@ -43,13 +43,13 @@ jobs:
|
||||
#don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses
|
||||
if: needs.check-intent.outputs.valid == 'true'
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.32.0
|
||||
uses: shivammathur/setup-php@2.33.0
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
10
.github/workflows/draft-release.yml
vendored
10
.github/workflows/draft-release.yml
vendored
@ -23,7 +23,7 @@ env:
|
||||
jobs:
|
||||
skip:
|
||||
name: Check whether to ignore this tag
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
outputs:
|
||||
skip: ${{ steps.exists.outputs.exists == 'true' }}
|
||||
@ -54,12 +54,12 @@ jobs:
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.32.0
|
||||
uses: shivammathur/setup-php@2.33.0
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
|
||||
|
2
.github/workflows/main-php-matrix.yml
vendored
2
.github/workflows/main-php-matrix.yml
vendored
@ -15,7 +15,7 @@ on:
|
||||
type: number
|
||||
image:
|
||||
description: 'Runner image to use'
|
||||
default: 'ubuntu-20.04'
|
||||
default: 'ubuntu-22.04'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
|
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
codestyle:
|
||||
name: Code Style checks
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@ -28,10 +28,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.32.0
|
||||
uses: shivammathur/setup-php@2.33.0
|
||||
with:
|
||||
php-version: 8.2
|
||||
tools: php-cs-fixer:3.49
|
||||
php-version: 8.3
|
||||
tools: php-cs-fixer:3.75
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
shellcheck:
|
||||
name: ShellCheck
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
|
32
.github/workflows/pr-remove-waiting-label.yml
vendored
32
.github/workflows/pr-remove-waiting-label.yml
vendored
@ -15,19 +15,23 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const [owner, repo] = context.payload.repository.full_name.split('/');
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: context.payload.number,
|
||||
name: "Status: Waiting on Author",
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
//probably label wasn't set on the issue
|
||||
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
|
||||
} else {
|
||||
throw error;
|
||||
async function removeLabel(owner, repo, issue_number, name) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: issue_number,
|
||||
name: name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
//probably label wasn't set on the issue
|
||||
console.log('Failed to remove label ' + name + ' (probably label isn\'t on the PR): ' + error.message);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
const [owner, repo] = context.payload.repository.full_name.split('/');
|
||||
removeLabel(owner, repo, context.payload.number, "Status: Waiting on Author");
|
||||
removeLabel(owner, repo, context.payload.number, "Stale");
|
||||
|
5
.github/workflows/support.yml
vendored
5
.github/workflows/support.yml
vendored
@ -20,10 +20,7 @@ jobs:
|
||||
|
||||
- Check our [Documentation](https://doc.pmmp.io) to see if you can find answers there
|
||||
|
||||
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG) or our [Forums](https://forums.pmmp.io)
|
||||
|
||||
|
||||
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
|
||||
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG)
|
||||
|
||||
close-issue: true
|
||||
lock-issue: false
|
||||
|
2
.github/workflows/team-pr-auto-approve.yml
vendored
2
.github/workflows/team-pr-auto-approve.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
|
@ -6,6 +6,12 @@ $finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/tests')
|
||||
->in(__DIR__ . '/tools')
|
||||
->notPath('plugins/DevTools')
|
||||
//JsonMapper will break if the FQNs in the doc comments for these are shortened :(
|
||||
->notPath('crafting/json')
|
||||
->notPath('inventory/json')
|
||||
->notPath('data/bedrock/block/upgrade/model')
|
||||
->notPath('data/bedrock/item/upgrade/model')
|
||||
|
||||
->notName('PocketMine.php');
|
||||
|
||||
return (new PhpCsFixer\Config)
|
||||
|
@ -65,6 +65,8 @@ PocketMine-MP accepts community contributions! The following resources will be u
|
||||
* [Building and running PocketMine-MP from source](BUILDING.md)
|
||||
* [Contributing Guidelines](CONTRIBUTING.md)
|
||||
|
||||
New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase.
|
||||
|
||||
## Donate
|
||||
PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.
|
||||
|
||||
|
@ -31,8 +31,8 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var string[]|\Closure[] $options
|
||||
* @phpstan-var array<string, string|\Closure() : string> $options
|
||||
* @var string[]|Closure[] $options
|
||||
* @phpstan-var array<string, string|Closure() : string> $options
|
||||
*/
|
||||
$options = [
|
||||
"base_version" => VersionInfo::BASE_VERSION,
|
||||
|
71
changelogs/5.26.md
Normal file
71
changelogs/5.26.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 5.26.0
|
||||
Released 22nd March 2025.
|
||||
|
||||
This is a minor feature release focused on performance improvements.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Performance
|
||||
- Significantly improved performance of entity movement. Load testing with item entities showed a 3x increase in the number of entities supported without lag.
|
||||
- Significantly improved performance of on-ground checks for player movement. This still needs further work, but optimisations implemented in this version should improve performance substantially.
|
||||
- Updated `pocketmine/nbt` dependency with performance improvements to `TAG_Compound` and `TAG_List` comparison. This should improve performance of inventory-related actions.
|
||||
- `InventoryTransaction` now avoids useless item clones when processing transactions, which should improve performance of inventory-related actions.
|
||||
|
||||
## Dependencies
|
||||
- `pocketmine/bedrock-protocol` has been updated to `36.2.0`, which adds new functions to access some packet fields.
|
||||
- `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees.
|
||||
|
||||
## Gameplay
|
||||
- Block breaking animation speed now takes into account the following: jumping, being in water, haste, mining fatigue
|
||||
|
||||
## Tools
|
||||
- `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging.
|
||||
|
||||
## API
|
||||
### `pocketmine\block`
|
||||
- The following methods have been added:
|
||||
- `public RuntimeBlockStateRegistry->hasStateId(int $stateId) : bool` - checks whether the given state ID is registered
|
||||
|
||||
### `pocketmine\crafting`
|
||||
- The following methods have been deprecated:
|
||||
- `CraftingManager::sort()` - this was implicitly internal anyway
|
||||
|
||||
### `pocketmine\utils`
|
||||
- The following constants have been added:
|
||||
- `TextFormat::MATERIAL_RESIN`
|
||||
- The following static properties have been added:
|
||||
- `Terminal::$COLOR_MATERIAL_RESIN`
|
||||
|
||||
### `pocketmine\data\bedrock\block`
|
||||
- `BlockStateToObjectDeserializer` now permits overriding **deserializers** for Bedrock IDs. This may be useful to implement custom state handling, or to implement missing block variants (such as snow cauldron).
|
||||
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
|
||||
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
|
||||
- If you want to make a custom version of a vanilla block, create a custom type ID for it, exactly as you would for a regular custom block.
|
||||
- The following methods have been added:
|
||||
- `public BlockStateToObjectDeserializer->getDeserializerForId(string $id) : ?(\Closure(BlockStateReader) : Block)`
|
||||
|
||||
### `pocketmine\data\bedrock\item`
|
||||
- `ItemDeserializer` now permits overriding **deserializers** for Bedrock IDs. As above, this may be useful to implement custom data handling, or to implement missing variants of existing items.
|
||||
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
|
||||
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
|
||||
- As above, if you want to make a custom version of a vanilla item, create a custom type ID for it, exactly as you would for a regular custom item.
|
||||
- The following methods have been added:
|
||||
- `public ItemDeserializer->getDeserializerForId(string $id) : ?(\Closure(SavedItemData) : Item)`
|
||||
|
||||
## Internals
|
||||
- `new $class` is now banned on new internals code by a PHPStan rule. Closures or factory objects should be used instead for greater flexibility and better static analysis.
|
||||
- `CraftingManager` now uses a more stable hash function for recipe output filtering.
|
||||
- `ChunkCache` now accepts `int $dimensionId` in the constructor. This may be useful for plugins which implement the nether.
|
||||
- `RuntimeBlockStateRegistry` now precomputes basic collision info about known states for fast paths.
|
||||
- This permits specialization for common shapes like cubes and collisionless blocks, which allows skipping complex logic in entity movement calculation. This vastly improves performance.
|
||||
- Any block whose class overrides `readStateFromWorld()` or `getModelPositionOffset()` will *not* be optimised.
|
||||
- `Block->recalculateCollisionBoxes()` now has a hard requirement not to depend on anything other than available properties. It must not use `World` or its position.
|
||||
- This change was problematic for `ChorusPlant`, which used nearby blocks to calculate its collision boxes.
|
||||
- Blocks which need nearby blocks should override `readStateFromWorld()` and set dynamic state properties, similar to fences.
|
||||
- This design flaw will be corrected with a major change to `Block` internals currently in planning for a future major version.
|
||||
- `Block->getCollisionBoxes()` may not be called at all during gameplay for blocks with shapes determined to be simple, like cubes and collisionless blocks.
|
||||
- `BlockStateToObjectDeserializer` now checks if the returned blockstate is registered in `RuntimeBlockStateRegistry` to promote earlier error detection (instead of crashing in random code paths).
|
24
changelogs/5.27.md
Normal file
24
changelogs/5.27.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 5.27.0
|
||||
Released 27th March 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.70.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Interim releases
|
||||
If you're upgrading from 5.25.x directly to 5.27.0, please also read the following changelogs, as the interim releases contain important changes:
|
||||
- [5.26.0](https://github.com/pmmp/PocketMine-MP/blob/5.26.0/changelogs/5.26.md#5260) - Performance improvements and other internal improvements
|
||||
|
||||
## General
|
||||
- Aded support for Minecraft: Bedrock Edition 1.21.70.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
# 5.27.1
|
||||
Released 6th April 2025.
|
||||
|
||||
## Fixes
|
||||
- Updated RakLib to get ping timestamp handling fixes.
|
34
changelogs/5.28.md
Normal file
34
changelogs/5.28.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 5.28.0
|
||||
Released 9th May 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.80.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.80.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Fixes
|
||||
- `AvailableEnchantmentRegistry` now requires provided tags to always be `string`. Previously, this wasn't enforced, leading to random crashes in core code related to enchanting.
|
||||
- `Entity->setFireTicks()` and `Entity->setOnFire()` now truncate the fire time to the max value instead of throwing exceptions.
|
||||
|
||||
## Internals
|
||||
- Improved PHPStan error reporting for unsafe foreaches. Foreach on an array with implicit keys now generates different errors than foreach on an array with string keys.
|
||||
|
||||
# 5.28.1
|
||||
Released 17th May 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed errors when PlayStation players attempt to join due to null `TitleID`.
|
||||
|
||||
# 5.28.2
|
||||
Released 17th May 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed version constraints which were incorrectly updated during the 1.21.80 update. This led to an unnoticed failure to update BedrockProtocol in the previous patch release.
|
||||
- Actually fixed PlayStation issues this time
|
@ -34,29 +34,33 @@
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"netresearch/jsonmapper": "~v5.0.0",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-protocol": "~38.0.0+bedrock-1.21.80",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.24.0",
|
||||
"pocketmine/locale-data": "~2.25.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
"pocketmine/raklib": "~1.1.0",
|
||||
"pocketmine/nbt": "~1.1.0",
|
||||
"pocketmine/raklib": "~1.1.2",
|
||||
"pocketmine/raklib-ipc": "~1.0.0",
|
||||
"pocketmine/snooze": "^0.5.0",
|
||||
"ramsey/uuid": "~4.7.0",
|
||||
"symfony/filesystem": "~6.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "2.1.6",
|
||||
"phpstan/phpstan": "2.1.16",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^10.5.24"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-mbstring": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"pocketmine\\": "src/"
|
||||
|
364
composer.lock
generated
364
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bef9decc40d9f5bd82e1de2d151bd99f",
|
||||
"content-hash": "b106b34fbd6c8abdfd45931bcb18bb69",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -67,16 +67,16 @@
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.12.2",
|
||||
"version": "0.12.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/math.git",
|
||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40"
|
||||
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -115,7 +115,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/math/issues",
|
||||
"source": "https://github.com/brick/math/tree/0.12.2"
|
||||
"source": "https://github.com/brick/math/tree/0.12.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -123,7 +123,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-26T10:21:45+00:00"
|
||||
"time": "2025-02-28T13:11:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "netresearch/jsonmapper",
|
||||
@ -204,16 +204,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "4.0.0+bedrock-1.21.60",
|
||||
"version": "5.0.0+bedrock-1.21.80",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e"
|
||||
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080",
|
||||
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -224,9 +224,9 @@
|
||||
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockData/issues",
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60"
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.80"
|
||||
},
|
||||
"time": "2025-02-16T15:56:56+00:00"
|
||||
"time": "2025-05-09T14:15:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-item-upgrade-schema",
|
||||
@ -256,16 +256,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "36.0.0+bedrock-1.21.60",
|
||||
"version": "38.0.1+bedrock-1.21.80",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963"
|
||||
"reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f",
|
||||
"reference": "0c1c13e970a2e1ded1609d0b442b4fcfd24cd21f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -296,9 +296,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/38.0.1+bedrock-1.21.80"
|
||||
},
|
||||
"time": "2025-02-16T15:59:08+00:00"
|
||||
"time": "2025-05-17T11:56:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -471,16 +471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.24.0",
|
||||
"version": "2.25.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9"
|
||||
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd",
|
||||
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -488,9 +488,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.24.0"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.25.1"
|
||||
},
|
||||
"time": "2025-02-16T20:46:34+00:00"
|
||||
"time": "2025-04-16T11:15:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -576,16 +576,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d"
|
||||
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
|
||||
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
|
||||
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -612,22 +612,22 @@
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/NBT/issues",
|
||||
"source": "https://github.com/pmmp/NBT/tree/1.0.1"
|
||||
"source": "https://github.com/pmmp/NBT/tree/1.1.1"
|
||||
},
|
||||
"time": "2025-01-07T22:47:46+00:00"
|
||||
"time": "2025-03-09T01:46:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/RakLib.git",
|
||||
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97"
|
||||
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97",
|
||||
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97",
|
||||
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
|
||||
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -639,8 +639,8 @@
|
||||
"pocketmine/log": "^0.3.0 || ^0.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.1",
|
||||
"phpstan/phpstan-strict-rules": "^1.0"
|
||||
"phpstan/phpstan": "2.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -655,9 +655,9 @@
|
||||
"description": "A RakNet server implementation written in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/RakLib/issues",
|
||||
"source": "https://github.com/pmmp/RakLib/tree/1.1.1"
|
||||
"source": "https://github.com/pmmp/RakLib/tree/1.1.2"
|
||||
},
|
||||
"time": "2024-03-04T14:02:14+00:00"
|
||||
"time": "2025-04-06T03:38:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib-ipc",
|
||||
@ -742,16 +742,16 @@
|
||||
},
|
||||
{
|
||||
"name": "ramsey/collection",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/collection.git",
|
||||
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
|
||||
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
|
||||
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
|
||||
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
|
||||
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -759,25 +759,22 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/plugin-composer": "^5.3",
|
||||
"ergebnis/composer-normalize": "^2.28.3",
|
||||
"fakerphp/faker": "^1.21",
|
||||
"ergebnis/composer-normalize": "^2.45",
|
||||
"fakerphp/faker": "^1.24",
|
||||
"hamcrest/hamcrest-php": "^2.0",
|
||||
"jangregor/phpstan-prophecy": "^1.0",
|
||||
"mockery/mockery": "^1.5",
|
||||
"jangregor/phpstan-prophecy": "^2.1",
|
||||
"mockery/mockery": "^1.6",
|
||||
"php-parallel-lint/php-console-highlighter": "^1.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/extension-installer": "^1.2",
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpstan/phpstan-mockery": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.3",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psalm/plugin-mockery": "^1.1",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"ramsey/coding-standard": "^2.0.3",
|
||||
"ramsey/conventional-commits": "^1.3",
|
||||
"vimeo/psalm": "^5.4"
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||
"phpspec/prophecy-phpunit": "^2.3",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-mockery": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"ramsey/coding-standard": "^2.3",
|
||||
"ramsey/conventional-commits": "^1.6",
|
||||
"roave/security-advisories": "dev-latest"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@ -815,19 +812,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ramsey/collection/issues",
|
||||
"source": "https://github.com/ramsey/collection/tree/2.0.0"
|
||||
"source": "https://github.com/ramsey/collection/tree/2.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ramsey",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-31T21:50:55+00:00"
|
||||
"time": "2025-03-22T05:38:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
@ -986,180 +973,21 @@
|
||||
}
|
||||
],
|
||||
"time": "2024-10-25T15:07:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
|
||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
||||
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1198,7 +1026,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1206,7 +1034,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-12T12:17:51+00:00"
|
||||
"time": "2025-04-29T12:36:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
@ -1386,16 +1214,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.6",
|
||||
"version": "2.1.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c"
|
||||
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
|
||||
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
|
||||
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1440,20 +1268,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-19T15:46:42+00:00"
|
||||
"time": "2025-05-16T09:40:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
||||
"reference": "d09e152f403c843998d7a52b5d87040c937525dd"
|
||||
"reference": "6b92469f8a7995e626da3aa487099617b8dfa260"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd",
|
||||
"reference": "d09e152f403c843998d7a52b5d87040c937525dd",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260",
|
||||
"reference": "6b92469f8a7995e626da3aa487099617b8dfa260",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1464,7 +1292,9 @@
|
||||
"phpunit/phpunit": "<7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nikic/php-parser": "^5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
@ -1489,22 +1319,22 @@
|
||||
"description": "PHPUnit extensions and rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4"
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6"
|
||||
},
|
||||
"time": "2025-01-22T13:07:38+00:00"
|
||||
"time": "2025-03-26T12:47:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
|
||||
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
|
||||
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
|
||||
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1537,9 +1367,9 @@
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4"
|
||||
},
|
||||
"time": "2025-01-21T10:52:14+00:00"
|
||||
"time": "2025-03-18T11:42:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -1864,16 +1694,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.5.45",
|
||||
"version": "10.5.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8"
|
||||
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8",
|
||||
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d",
|
||||
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1883,7 +1713,7 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"myclabs/deep-copy": "^1.12.1",
|
||||
"myclabs/deep-copy": "^1.13.1",
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.1",
|
||||
@ -1945,7 +1775,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1956,12 +1786,20 @@
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://liberapay.com/sebastianbergmann",
|
||||
"type": "liberapay"
|
||||
},
|
||||
{
|
||||
"url": "https://thanks.dev/u/gh/sebastianbergmann",
|
||||
"type": "thanks_dev"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-06T16:08:12+00:00"
|
||||
"time": "2025-05-02T06:46:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
@ -2932,7 +2770,7 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
@ -2963,7 +2801,7 @@
|
||||
"ext-zlib": ">=1.2.11",
|
||||
"composer-runtime-api": "^2.0"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-dev": {},
|
||||
"platform-overrides": {
|
||||
"php": "8.1.0"
|
||||
},
|
||||
|
@ -11,10 +11,11 @@ includes:
|
||||
|
||||
rules:
|
||||
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
|
||||
- pocketmine\phpstan\rules\DisallowDynamicNewRule
|
||||
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
|
||||
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
|
||||
- pocketmine\phpstan\rules\ExplodeLimitRule
|
||||
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
|
||||
- pocketmine\phpstan\rules\UnsafeForeachRule
|
||||
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
|
||||
|
||||
parameters:
|
||||
|
@ -31,6 +31,7 @@ use function hrtime;
|
||||
use function max;
|
||||
use function min;
|
||||
use function number_format;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector,
|
||||
@ -48,6 +49,7 @@ final class GarbageCollectorManager{
|
||||
|
||||
private int $threshold = self::GC_THRESHOLD_DEFAULT;
|
||||
private int $collectionTimeTotalNs = 0;
|
||||
private int $runs = 0;
|
||||
|
||||
private \Logger $logger;
|
||||
private TimingsHandler $timings;
|
||||
@ -96,7 +98,16 @@ final class GarbageCollectorManager{
|
||||
|
||||
$time = $end - $start;
|
||||
$this->collectionTimeTotalNs += $time;
|
||||
$this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns");
|
||||
$this->runs++;
|
||||
$this->logger->info(sprintf(
|
||||
"Run #%d took %s ms (%s -> %s roots, %s cycles collected) - cumulative GC time: %s ms",
|
||||
$this->runs,
|
||||
number_format($time / 1_000_000, 2),
|
||||
$rootsBefore,
|
||||
$rootsAfter,
|
||||
$cycles,
|
||||
number_format($this->collectionTimeTotalNs / 1_000_000, 2)
|
||||
));
|
||||
|
||||
return $cycles;
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.25.2";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BASE_VERSION = "5.28.3";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
/**
|
||||
|
@ -73,7 +73,7 @@ class BlockBreakInfo{
|
||||
return new self(0.0, $toolType, $toolHarvestLevel, 0.0);
|
||||
}
|
||||
|
||||
public static function indestructible(float $blastResistance = 18000000.0) : self{
|
||||
public static function indestructible(float $blastResistance = 18000003.75) : self{
|
||||
return new self(-1.0, BlockToolType::NONE, 0, $blastResistance);
|
||||
}
|
||||
|
||||
|
@ -786,8 +786,9 @@ final class BlockTypeIds{
|
||||
public const RESIN_BRICKS = 10756;
|
||||
public const RESIN_CLUMP = 10757;
|
||||
public const CHISELED_RESIN_BRICKS = 10758;
|
||||
public const RESPAWN_ANCHOR = 10759;
|
||||
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10759;
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10760;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
||||
|
||||
|
@ -34,11 +34,16 @@ use function mt_rand;
|
||||
final class ChorusPlant extends Flowable{
|
||||
use StaticSupportTrait;
|
||||
|
||||
/**
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
*/
|
||||
protected array $connections = [];
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$bb = AxisAlignedBB::one();
|
||||
foreach($this->getAllSides() as $facing => $block){
|
||||
$id = $block->getTypeId();
|
||||
if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){
|
||||
foreach(Facing::ALL as $facing){
|
||||
if(!isset($this->connections[$facing])){
|
||||
$bb->trim($facing, 2 / 16);
|
||||
}
|
||||
}
|
||||
@ -46,6 +51,26 @@ final class ChorusPlant extends Flowable{
|
||||
return [$bb];
|
||||
}
|
||||
|
||||
public function readStateFromWorld() : Block{
|
||||
parent::readStateFromWorld();
|
||||
|
||||
$this->collisionBoxes = null;
|
||||
|
||||
foreach(Facing::ALL as $facing){
|
||||
$block = $this->getSide($facing);
|
||||
if(match($block->getTypeId()){
|
||||
BlockTypeIds::END_STONE, BlockTypeIds::CHORUS_FLOWER, $this->getTypeId() => true,
|
||||
default => false
|
||||
}){
|
||||
$this->connections[$facing] = true;
|
||||
}else{
|
||||
unset($this->connections[$facing]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
|
||||
}
|
||||
|
123
src/block/RespawnAnchor.php
Normal file
123
src/block/RespawnAnchor.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\event\block\BlockPreExplodeEvent;
|
||||
use pocketmine\event\player\PlayerRespawnAnchorUseEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemTypeIds;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\Explosion;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\sound\RespawnAnchorChargeSound;
|
||||
use pocketmine\world\sound\RespawnAnchorSetSpawnSound;
|
||||
|
||||
final class RespawnAnchor extends Opaque{
|
||||
private const MIN_CHARGES = 0;
|
||||
private const MAX_CHARGES = 4;
|
||||
|
||||
private int $charges = self::MIN_CHARGES;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->boundedIntAuto(self::MIN_CHARGES, self::MAX_CHARGES, $this->charges);
|
||||
}
|
||||
|
||||
public function getCharges() : int{
|
||||
return $this->charges;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function setCharges(int $charges) : self{
|
||||
if($charges < self::MIN_CHARGES || $charges > self::MAX_CHARGES){
|
||||
throw new \InvalidArgumentException("Charges must be between " . self::MIN_CHARGES . " and " . self::MAX_CHARGES . ", given: $charges");
|
||||
}
|
||||
$this->charges = $charges;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLightLevel() : int{
|
||||
return $this->charges > 0 ? ($this->charges * 4) - 1 : 0;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item->getTypeId() === ItemTypeIds::fromBlockTypeId(BlockTypeIds::GLOWSTONE) && $this->charges < self::MAX_CHARGES){
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setCharges($this->charges + 1));
|
||||
$this->position->getWorld()->addSound($this->position, new RespawnAnchorChargeSound());
|
||||
return true;
|
||||
}
|
||||
|
||||
if($this->charges > self::MIN_CHARGES){
|
||||
if($player === null){
|
||||
return false;
|
||||
}
|
||||
|
||||
$ev = new PlayerRespawnAnchorUseEvent($player, $this, PlayerRespawnAnchorUseEvent::ACTION_EXPLODE);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
||||
switch($ev->getAction()){
|
||||
case PlayerRespawnAnchorUseEvent::ACTION_EXPLODE:
|
||||
$this->explode($player);
|
||||
return false;
|
||||
|
||||
case PlayerRespawnAnchorUseEvent::ACTION_SET_SPAWN:
|
||||
if($player->getSpawn() !== null && $player->getSpawn()->equals($this->position)){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player->setSpawn($this->position);
|
||||
$this->position->getWorld()->addSound($this->position, new RespawnAnchorSetSpawnSound());
|
||||
$player->sendMessage(KnownTranslationFactory::tile_respawn_anchor_respawnSet()->prefix(TextFormat::GRAY));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function explode(?Player $player) : void{
|
||||
$ev = new BlockPreExplodeEvent($this, 5, $player);
|
||||
$ev->setIncendiary(true);
|
||||
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||
|
||||
$explosion = new Explosion(Position::fromObject($this->position->add(0.5, 0.5, 0.5), $this->position->getWorld()), $ev->getRadius(), $this);
|
||||
$explosion->setFireChance($ev->getFireChance());
|
||||
|
||||
if($ev->isBlockBreaking()){
|
||||
$explosion->explodeA();
|
||||
}
|
||||
$explosion->explodeB();
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ use pocketmine\block\BlockIdentifier as BID;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\world\light\LightUpdate;
|
||||
use function count;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
@ -40,6 +41,11 @@ use function min;
|
||||
class RuntimeBlockStateRegistry{
|
||||
use SingletonTrait;
|
||||
|
||||
public const COLLISION_CUSTOM = 0;
|
||||
public const COLLISION_CUBE = 1;
|
||||
public const COLLISION_NONE = 2;
|
||||
public const COLLISION_MAY_OVERFLOW = 3;
|
||||
|
||||
/**
|
||||
* @var Block[]
|
||||
* @phpstan-var array<int, Block>
|
||||
@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{
|
||||
*/
|
||||
public array $blastResistance = [];
|
||||
|
||||
/**
|
||||
* Map of state ID -> useful AABB info to avoid unnecessary block allocations
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
public array $collisionInfo = [];
|
||||
|
||||
public function __construct(){
|
||||
foreach(VanillaBlocks::getAll() as $block){
|
||||
$this->register($block);
|
||||
@ -100,6 +113,70 @@ class RuntimeBlockStateRegistry{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class method overrides a method in Block.
|
||||
* Used to determine if a block might need to disable fast path optimizations.
|
||||
*
|
||||
* @phpstan-param anyClosure $closure
|
||||
*/
|
||||
private static function overridesBlockMethod(\Closure $closure) : bool{
|
||||
$declarer = (new \ReflectionFunction($closure))->getClosureScopeClass();
|
||||
return $declarer !== null && $declarer->getName() !== Block::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* A big ugly hack to set up fast paths for handling collisions on blocks with common shapes.
|
||||
* The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity
|
||||
* collision box calculations to avoid complex logic and unnecessary block object allocations.
|
||||
* This hack allows significant performance improvements.
|
||||
*
|
||||
* TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a
|
||||
* major version. For now, this hack nets major performance wins.
|
||||
*/
|
||||
private static function calculateCollisionInfo(Block $block) : int{
|
||||
if(
|
||||
self::overridesBlockMethod($block->getModelPositionOffset(...)) ||
|
||||
self::overridesBlockMethod($block->readStateFromWorld(...))
|
||||
){
|
||||
//getModelPositionOffset() might cause AABBs to shift outside the cell
|
||||
//readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states
|
||||
//TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything
|
||||
//weird with their AABBs, but for now this is the best we can do.
|
||||
return self::COLLISION_MAY_OVERFLOW;
|
||||
}
|
||||
|
||||
//TODO: this could blow up if any recalculateCollisionBoxes() uses the world
|
||||
//it shouldn't, but that doesn't mean that custom blocks won't...
|
||||
$boxes = $block->getCollisionBoxes();
|
||||
if(count($boxes) === 0){
|
||||
return self::COLLISION_NONE;
|
||||
}
|
||||
|
||||
if(
|
||||
count($boxes) === 1 &&
|
||||
$boxes[0]->minX === 0.0 &&
|
||||
$boxes[0]->minY === 0.0 &&
|
||||
$boxes[0]->minZ === 0.0 &&
|
||||
$boxes[0]->maxX === 1.0 &&
|
||||
$boxes[0]->maxY === 1.0 &&
|
||||
$boxes[0]->maxZ === 1.0
|
||||
){
|
||||
return self::COLLISION_CUBE;
|
||||
}
|
||||
|
||||
foreach($boxes as $box){
|
||||
if(
|
||||
$box->minX < 0 || $box->maxX > 1 ||
|
||||
$box->minY < 0 || $box->maxY > 1 ||
|
||||
$box->minZ < 0 || $box->maxZ > 1
|
||||
){
|
||||
return self::COLLISION_MAY_OVERFLOW;
|
||||
}
|
||||
}
|
||||
|
||||
return self::COLLISION_CUSTOM;
|
||||
}
|
||||
|
||||
private function fillStaticArrays(int $index, Block $block) : void{
|
||||
$fullId = $block->getStateId();
|
||||
if($index !== $fullId){
|
||||
@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{
|
||||
if($block->blocksDirectSkyLight()){
|
||||
$this->blocksDirectSkyLight[$index] = true;
|
||||
}
|
||||
|
||||
$this->collisionInfo[$index] = self::calculateCollisionInfo($block);
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,6 +209,10 @@ class RuntimeBlockStateRegistry{
|
||||
return $block;
|
||||
}
|
||||
|
||||
public function hasStateId(int $stateId) : bool{
|
||||
return isset($this->fullList[$stateId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Block[]
|
||||
* @phpstan-return array<int, Block>
|
||||
|
@ -25,11 +25,12 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\CropGrowthHelper;
|
||||
use pocketmine\block\utils\FortuneDropHelper;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Facing;
|
||||
use function array_rand;
|
||||
use function mt_rand;
|
||||
|
||||
abstract class Stem extends Crops{
|
||||
protected int $facing = Facing::UP;
|
||||
@ -90,8 +91,10 @@ abstract class Stem extends Crops{
|
||||
}
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
//TODO: bit annoying we have to pass an Item instance here
|
||||
//this should not be affected by Fortune, but still follows a binomial distribution
|
||||
return [
|
||||
$this->asItem()->setCount(mt_rand(0, 2))
|
||||
$this->asItem()->setCount(FortuneDropHelper::binomial(VanillaItems::AIR(), 0, chance: ($this->age + 1) / 15))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -694,6 +694,7 @@ use function strtolower;
|
||||
* @method static Stair RESIN_BRICK_STAIRS()
|
||||
* @method static Wall RESIN_BRICK_WALL()
|
||||
* @method static ResinClump RESIN_CLUMP()
|
||||
* @method static RespawnAnchor RESPAWN_ANCHOR()
|
||||
* @method static DoublePlant ROSE_BUSH()
|
||||
* @method static Sand SAND()
|
||||
* @method static Opaque SANDSTONE()
|
||||
@ -859,7 +860,7 @@ final class VanillaBlocks{
|
||||
$railBreakInfo = new Info(new BreakInfo(0.7));
|
||||
self::register("activator_rail", fn(BID $id) => new ActivatorRail($id, "Activator Rail", $railBreakInfo));
|
||||
self::register("anvil", fn(BID $id) => new Anvil($id, "Anvil", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))));
|
||||
self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(2.0 /* 1.0 in PC */, ToolType::AXE) extends BreakInfo{
|
||||
self::register("bamboo", fn(BID $id) => new Bamboo($id, "Bamboo", new Info(new class(1.0, ToolType::AXE) extends BreakInfo{
|
||||
public function getBreakTime(Item $item) : float{
|
||||
if($item->getBlockToolType() === ToolType::SWORD){
|
||||
return 0.0;
|
||||
@ -867,7 +868,7 @@ final class VanillaBlocks{
|
||||
return parent::getBreakTime($item);
|
||||
}
|
||||
}, [Tags::POTTABLE_PLANTS])));
|
||||
self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(BreakInfo::instant())));
|
||||
self::register("bamboo_sapling", fn(BID $id) => new BambooSapling($id, "Bamboo Sapling", new Info(new BreakInfo(1.0))));
|
||||
|
||||
$bannerBreakInfo = new Info(BreakInfo::axe(1.0));
|
||||
self::register("banner", fn(BID $id) => new FloorBanner($id, "Banner", $bannerBreakInfo), TileBanner::class);
|
||||
@ -876,7 +877,7 @@ final class VanillaBlocks{
|
||||
self::register("barrier", fn(BID $id) => new Transparent($id, "Barrier", new Info(BreakInfo::indestructible())));
|
||||
self::register("beacon", fn(BID $id) => new Beacon($id, "Beacon", new Info(new BreakInfo(3.0))), TileBeacon::class);
|
||||
self::register("bed", fn(BID $id) => new Bed($id, "Bed Block", new Info(new BreakInfo(0.2))), TileBed::class);
|
||||
self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible())));
|
||||
self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible(18000000.0))));
|
||||
|
||||
self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant())));
|
||||
self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0))), TileBell::class);
|
||||
@ -913,7 +914,7 @@ final class VanillaBlocks{
|
||||
|
||||
self::register("cobweb", fn(BID $id) => new Cobweb($id, "Cobweb", new Info(new BreakInfo(4.0, ToolType::SWORD | ToolType::SHEARS, 1))));
|
||||
self::register("cocoa_pod", fn(BID $id) => new CocoaBlock($id, "Cocoa Block", new Info(BreakInfo::axe(0.2, null, 15.0))));
|
||||
self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(7.0, ToolTier::WOOD))));
|
||||
self::register("coral_block", fn(BID $id) => new CoralBlock($id, "Coral Block", new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0))));
|
||||
self::register("daylight_sensor", fn(BID $id) => new DaylightSensor($id, "Daylight Sensor", new Info(BreakInfo::axe(0.2))), TileDaylightSensor::class);
|
||||
self::register("dead_bush", fn(BID $id) => new DeadBush($id, "Dead Bush", new Info(BreakInfo::instant(ToolType::SHEARS, 1), [Tags::POTTABLE_PLANTS])));
|
||||
self::register("detector_rail", fn(BID $id) => new DetectorRail($id, "Detector Rail", $railBreakInfo));
|
||||
@ -930,15 +931,15 @@ final class VanillaBlocks{
|
||||
self::register("pitcher_plant", fn(BID $id) => new DoublePlant($id, "Pitcher Plant", new Info(BreakInfo::instant())));
|
||||
self::register("pitcher_crop", fn(BID $id) => new PitcherCrop($id, "Pitcher Crop", new Info(BreakInfo::instant())));
|
||||
self::register("double_pitcher_crop", fn(BID $id) => new DoublePitcherCrop($id, "Double Pitcher Crop", new Info(BreakInfo::instant())));
|
||||
self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD))));
|
||||
self::register("dragon_egg", fn(BID $id) => new DragonEgg($id, "Dragon Egg", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, blastResistance: 45.0))));
|
||||
self::register("dried_kelp", fn(BID $id) => new DriedKelp($id, "Dried Kelp Block", new Info(new BreakInfo(0.5, ToolType::NONE, 0, 12.5))));
|
||||
self::register("emerald", fn(BID $id) => new Opaque($id, "Emerald Block", new Info(BreakInfo::pickaxe(5.0, ToolTier::IRON, 30.0))));
|
||||
self::register("enchanting_table", fn(BID $id) => new EnchantingTable($id, "Enchanting Table", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 6000.0))), TileEnchantingTable::class);
|
||||
self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible())));
|
||||
self::register("end_portal_frame", fn(BID $id) => new EndPortalFrame($id, "End Portal Frame", new Info(BreakInfo::indestructible(18000000.0))));
|
||||
self::register("end_rod", fn(BID $id) => new EndRod($id, "End Rod", new Info(BreakInfo::instant())));
|
||||
self::register("end_stone", fn(BID $id) => new Opaque($id, "End Stone", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0))));
|
||||
|
||||
$endBrickBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0));
|
||||
$endBrickBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0));
|
||||
self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo));
|
||||
self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo));
|
||||
|
||||
@ -962,7 +963,7 @@ final class VanillaBlocks{
|
||||
self::register("torchflower", fn(BID $id) => new Flower($id, "Torchflower", $flowerTypeInfo));
|
||||
self::register("torchflower_crop", fn(BID $id) => new TorchflowerCrop($id, "Torchflower Crop", new Info(BreakInfo::instant())));
|
||||
self::register("flower_pot", fn(BID $id) => new FlowerPot($id, "Flower Pot", new Info(BreakInfo::instant())), TileFlowerPot::class);
|
||||
self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(2.5))));
|
||||
self::register("frosted_ice", fn(BID $id) => new FrostedIce($id, "Frosted Ice", new Info(BreakInfo::pickaxe(0.5))));
|
||||
self::register("furnace", fn(BID $id) => new Furnace($id, "Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::FURNACE), TileNormalFurnace::class);
|
||||
self::register("blast_furnace", fn(BID $id) => new Furnace($id, "Blast Furnace", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::BLAST_FURNACE), TileBlastFurnace::class);
|
||||
self::register("smoker", fn(BID $id) => new Furnace($id, "Smoker", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD)), FurnaceType::SMOKER), TileSmoker::class);
|
||||
@ -970,30 +971,28 @@ final class VanillaBlocks{
|
||||
$glassBreakInfo = new Info(new BreakInfo(0.3));
|
||||
self::register("glass", fn(BID $id) => new Glass($id, "Glass", $glassBreakInfo));
|
||||
self::register("glass_pane", fn(BID $id) => new GlassPane($id, "Glass Pane", $glassBreakInfo));
|
||||
self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND, 50.0))));
|
||||
self::register("glowing_obsidian", fn(BID $id) => new GlowingObsidian($id, "Glowing Obsidian", new Info(BreakInfo::pickaxe(35.0, ToolTier::DIAMOND, 6000.0))));
|
||||
self::register("glowstone", fn(BID $id) => new Glowstone($id, "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
|
||||
self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
|
||||
self::register("glow_lichen", fn(BID $id) => new GlowLichen($id, "Glow Lichen", new Info(BreakInfo::axe(0.2))));
|
||||
self::register("gold", fn(BID $id) => new Opaque($id, "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON, 30.0))));
|
||||
|
||||
$grassBreakInfo = BreakInfo::shovel(0.6);
|
||||
self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info($grassBreakInfo, [Tags::DIRT])));
|
||||
self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info($grassBreakInfo)));
|
||||
self::register("grass", fn(BID $id) => new Grass($id, "Grass", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
|
||||
self::register("grass_path", fn(BID $id) => new GrassPath($id, "Grass Path", new Info(BreakInfo::shovel(0.65))));
|
||||
self::register("gravel", fn(BID $id) => new Gravel($id, "Gravel", new Info(BreakInfo::shovel(0.6))));
|
||||
|
||||
$hardenedClayBreakInfo = new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0));
|
||||
self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", $hardenedClayBreakInfo));
|
||||
self::register("hardened_clay", fn(BID $id) => new HardenedClay($id, "Hardened Clay", new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 21.0))));
|
||||
|
||||
$hardenedGlassBreakInfo = new Info(new BreakInfo(10.0));
|
||||
self::register("hardened_glass", fn(BID $id) => new HardenedGlass($id, "Hardened Glass", $hardenedGlassBreakInfo));
|
||||
self::register("hardened_glass_pane", fn(BID $id) => new HardenedGlassPane($id, "Hardened Glass Pane", $hardenedGlassBreakInfo));
|
||||
self::register("hay_bale", fn(BID $id) => new HayBale($id, "Hay Bale", new Info(new BreakInfo(0.5))));
|
||||
self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 15.0))), TileHopper::class);
|
||||
self::register("hopper", fn(BID $id) => new Hopper($id, "Hopper", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 24.0))), TileHopper::class);
|
||||
self::register("ice", fn(BID $id) => new Ice($id, "Ice", new Info(BreakInfo::pickaxe(0.5))));
|
||||
|
||||
$updateBlockBreakInfo = new Info(new BreakInfo(1.0));
|
||||
self::register("info_update", fn(BID $id) => new Opaque($id, "update!", $updateBlockBreakInfo));
|
||||
self::register("info_update2", fn(BID $id) => new Opaque($id, "ate!upd", $updateBlockBreakInfo));
|
||||
self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible())));
|
||||
self::register("invisible_bedrock", fn(BID $id) => new Transparent($id, "Invisible Bedrock", new Info(BreakInfo::indestructible(18000000.0))));
|
||||
|
||||
$ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0));
|
||||
self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo));
|
||||
@ -1006,16 +1005,16 @@ final class VanillaBlocks{
|
||||
self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class);
|
||||
self::register("glowing_item_frame", fn(BID $id) => new ItemFrame($id, "Glow Item Frame", $itemFrameInfo), TileGlowingItemFrame::class);
|
||||
|
||||
self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
|
||||
self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(2.0, blastResistance: 30.0))), TileJukebox::class);
|
||||
self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4))));
|
||||
|
||||
$lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0));
|
||||
$lanternBreakInfo = new Info(BreakInfo::pickaxe(3.5));
|
||||
self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15));
|
||||
self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10));
|
||||
|
||||
self::register("lapis_lazuli", fn(BID $id) => new Opaque($id, "Lapis Lazuli Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE))));
|
||||
self::register("lava", fn(BID $id) => new Lava($id, "Lava", new Info(BreakInfo::indestructible(500.0))));
|
||||
self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.0))), TileLectern::class);
|
||||
self::register("lectern", fn(BID $id) => new Lectern($id, "Lectern", new Info(BreakInfo::axe(2.5))), TileLectern::class);
|
||||
self::register("lever", fn(BID $id) => new Lever($id, "Lever", new Info(new BreakInfo(0.5))));
|
||||
self::register("magma", fn(BID $id) => new Magma($id, "Magma Block", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
|
||||
self::register("melon", fn(BID $id) => new Melon($id, "Melon Block", new Info(BreakInfo::axe(1.0))));
|
||||
@ -1065,14 +1064,15 @@ final class VanillaBlocks{
|
||||
self::register("purpur_stairs", fn(BID $id) => new Stair($id, "Purpur Stairs", $purpurBreakInfo));
|
||||
|
||||
$quartzBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
|
||||
$smoothQuartzBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
self::register("quartz", fn(BID $id) => new Opaque($id, "Quartz Block", $quartzBreakInfo));
|
||||
self::register("chiseled_quartz", fn(BID $id) => new SimplePillar($id, "Chiseled Quartz Block", $quartzBreakInfo));
|
||||
self::register("quartz_pillar", fn(BID $id) => new SimplePillar($id, "Quartz Pillar", $quartzBreakInfo));
|
||||
self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $quartzBreakInfo));
|
||||
self::register("smooth_quartz", fn(BID $id) => new Opaque($id, "Smooth Quartz Block", $smoothQuartzBreakInfo));
|
||||
self::register("quartz_bricks", fn(BID $id) => new Opaque($id, "Quartz Bricks", $quartzBreakInfo));
|
||||
|
||||
self::register("quartz_stairs", fn(BID $id) => new Stair($id, "Quartz Stairs", $quartzBreakInfo));
|
||||
self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $quartzBreakInfo));
|
||||
self::register("smooth_quartz_stairs", fn(BID $id) => new Stair($id, "Smooth Quartz Stairs", $smoothQuartzBreakInfo));
|
||||
|
||||
self::register("rail", fn(BID $id) => new Rail($id, "Rail", $railBreakInfo));
|
||||
self::register("red_mushroom", fn(BID $id) => new RedMushroom($id, "Red Mushroom", new Info(BreakInfo::instant(), [Tags::POTTABLE_PLANTS])));
|
||||
@ -1127,13 +1127,13 @@ final class VanillaBlocks{
|
||||
$infestedStoneBreakInfo = new Info(BreakInfo::pickaxe(0.75));
|
||||
self::register("infested_stone", fn(BID $id) => new InfestedStone($id, "Infested Stone", $infestedStoneBreakInfo, $stone));
|
||||
self::register("infested_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Stone Brick", $infestedStoneBreakInfo, $stoneBrick));
|
||||
self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", $infestedStoneBreakInfo, $cobblestone));
|
||||
self::register("infested_cobblestone", fn(BID $id) => new InfestedStone($id, "Infested Cobblestone", new Info(BreakInfo::pickaxe(1.0, blastResistance: 3.75)), $cobblestone));
|
||||
self::register("infested_mossy_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Mossy Stone Brick", $infestedStoneBreakInfo, $mossyStoneBrick));
|
||||
self::register("infested_cracked_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Cracked Stone Brick", $infestedStoneBreakInfo, $crackedStoneBrick));
|
||||
self::register("infested_chiseled_stone_brick", fn(BID $id) => new InfestedStone($id, "Infested Chiseled Stone Brick", $infestedStoneBreakInfo, $chiseledStoneBrick));
|
||||
|
||||
self::register("stone_stairs", fn(BID $id) => new Stair($id, "Stone Stairs", $stoneBreakInfo));
|
||||
self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", $stoneBreakInfo));
|
||||
self::register("smooth_stone", fn(BID $id) => new Opaque($id, "Smooth Stone", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0))));
|
||||
self::register("andesite_stairs", fn(BID $id) => new Stair($id, "Andesite Stairs", $stoneBreakInfo));
|
||||
self::register("diorite_stairs", fn(BID $id) => new Stair($id, "Diorite Stairs", $stoneBreakInfo));
|
||||
self::register("granite_stairs", fn(BID $id) => new Stair($id, "Granite Stairs", $stoneBreakInfo));
|
||||
@ -1146,7 +1146,6 @@ final class VanillaBlocks{
|
||||
self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
|
||||
self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5))));
|
||||
|
||||
//TODO: in the future this won't be the same for all the types
|
||||
$stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
|
||||
self::register("brick_slab", fn(BID $id) => new Slab($id, "Brick", $stoneSlabBreakInfo));
|
||||
@ -1157,28 +1156,31 @@ final class VanillaBlocks{
|
||||
self::register("sandstone_slab", fn(BID $id) => new Slab($id, "Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("smooth_stone_slab", fn(BID $id) => new Slab($id, "Smooth Stone", $stoneSlabBreakInfo));
|
||||
self::register("stone_brick_slab", fn(BID $id) => new Slab($id, "Stone Brick", $stoneSlabBreakInfo));
|
||||
self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $stoneSlabBreakInfo));
|
||||
self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo));
|
||||
self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $stoneSlabBreakInfo));
|
||||
self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $stoneSlabBreakInfo));
|
||||
self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo));
|
||||
self::register("red_nether_brick_slab", fn(BID $id) => new Slab($id, "Red Nether Brick", $stoneSlabBreakInfo));
|
||||
self::register("red_sandstone_slab", fn(BID $id) => new Slab($id, "Red Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("smooth_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $stoneSlabBreakInfo));
|
||||
self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $stoneSlabBreakInfo));
|
||||
self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", $stoneSlabBreakInfo));
|
||||
self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $stoneSlabBreakInfo));
|
||||
self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $stoneSlabBreakInfo));
|
||||
self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $stoneSlabBreakInfo));
|
||||
self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $stoneSlabBreakInfo));
|
||||
self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("cut_red_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Red Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("cut_sandstone_slab", fn(BID $id) => new Slab($id, "Cut Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $stoneSlabBreakInfo));
|
||||
self::register("mossy_cobblestone_slab", fn(BID $id) => new Slab($id, "Mossy Cobblestone", $stoneSlabBreakInfo));
|
||||
self::register("purpur_slab", fn(BID $id) => new Slab($id, "Purpur", $stoneSlabBreakInfo));
|
||||
self::register("smooth_red_sandstone_slab", fn(BID $id) => new Slab($id, "Smooth Red Sandstone", $stoneSlabBreakInfo));
|
||||
self::register("smooth_quartz_slab", fn(BID $id) => new Slab($id, "Smooth Quartz", $stoneSlabBreakInfo));
|
||||
self::register("stone_slab", fn(BID $id) => new Slab($id, "Stone", $stoneSlabBreakInfo));
|
||||
|
||||
self::register("end_stone_brick_slab", fn(BID $id) => new Slab($id, "End Stone Brick", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 30.0))));
|
||||
|
||||
$lightStoneSlabBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
|
||||
self::register("dark_prismarine_slab", fn(BID $id) => new Slab($id, "Dark Prismarine", $lightStoneSlabBreakInfo));
|
||||
self::register("prismarine_slab", fn(BID $id) => new Slab($id, "Prismarine", $lightStoneSlabBreakInfo));
|
||||
self::register("prismarine_bricks_slab", fn(BID $id) => new Slab($id, "Prismarine Bricks", $lightStoneSlabBreakInfo));
|
||||
self::register("andesite_slab", fn(BID $id) => new Slab($id, "Andesite", $lightStoneSlabBreakInfo));
|
||||
self::register("diorite_slab", fn(BID $id) => new Slab($id, "Diorite", $lightStoneSlabBreakInfo));
|
||||
self::register("granite_slab", fn(BID $id) => new Slab($id, "Granite", $lightStoneSlabBreakInfo));
|
||||
self::register("polished_andesite_slab", fn(BID $id) => new Slab($id, "Polished Andesite", $lightStoneSlabBreakInfo));
|
||||
self::register("polished_diorite_slab", fn(BID $id) => new Slab($id, "Polished Diorite", $lightStoneSlabBreakInfo));
|
||||
self::register("polished_granite_slab", fn(BID $id) => new Slab($id, "Polished Granite", $lightStoneSlabBreakInfo));
|
||||
self::register("mossy_stone_brick_slab", fn(BID $id) => new Slab($id, "Mossy Stone Brick", $lightStoneSlabBreakInfo));
|
||||
|
||||
self::register("legacy_stonecutter", fn(BID $id) => new Opaque($id, "Legacy Stonecutter", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD))));
|
||||
self::register("sugarcane", fn(BID $id) => new Sugarcane($id, "Sugarcane", new Info(BreakInfo::instant())));
|
||||
self::register("sweet_berry_bush", fn(BID $id) => new SweetBerryBush($id, "Sweet Berry Bush", new Info(BreakInfo::instant())));
|
||||
@ -1237,25 +1239,26 @@ final class VanillaBlocks{
|
||||
}
|
||||
|
||||
$sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
|
||||
$smoothSandstoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
self::register("smooth_red_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Red Sandstone Stairs", $smoothSandstoneBreakInfo));
|
||||
self::register("red_sandstone", fn(BID $id) => new Opaque($id, "Red Sandstone", $sandstoneBreakInfo));
|
||||
self::register("chiseled_red_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Red Sandstone", $sandstoneBreakInfo));
|
||||
self::register("cut_red_sandstone", fn(BID $id) => new Opaque($id, "Cut Red Sandstone", $sandstoneBreakInfo));
|
||||
self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $sandstoneBreakInfo));
|
||||
self::register("smooth_red_sandstone", fn(BID $id) => new Opaque($id, "Smooth Red Sandstone", $smoothSandstoneBreakInfo));
|
||||
|
||||
self::register("sandstone_stairs", fn(BID $id) => new Stair($id, "Sandstone Stairs", $sandstoneBreakInfo));
|
||||
self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $sandstoneBreakInfo));
|
||||
self::register("smooth_sandstone_stairs", fn(BID $id) => new Stair($id, "Smooth Sandstone Stairs", $smoothSandstoneBreakInfo));
|
||||
self::register("sandstone", fn(BID $id) => new Opaque($id, "Sandstone", $sandstoneBreakInfo));
|
||||
self::register("chiseled_sandstone", fn(BID $id) => new Opaque($id, "Chiseled Sandstone", $sandstoneBreakInfo));
|
||||
self::register("cut_sandstone", fn(BID $id) => new Opaque($id, "Cut Sandstone", $sandstoneBreakInfo));
|
||||
self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $sandstoneBreakInfo));
|
||||
self::register("smooth_sandstone", fn(BID $id) => new Opaque($id, "Smooth Sandstone", $smoothSandstoneBreakInfo));
|
||||
|
||||
self::register("glazed_terracotta", fn(BID $id) => new GlazedTerracotta($id, "Glazed Terracotta", new Info(BreakInfo::pickaxe(1.4, ToolTier::WOOD))));
|
||||
self::register("dyed_shulker_box", fn(BID $id) => new DyedShulkerBox($id, "Dyed Shulker Box", $shulkerBoxBreakInfo), TileShulkerBox::class);
|
||||
self::register("stained_glass", fn(BID $id) => new StainedGlass($id, "Stained Glass", $glassBreakInfo));
|
||||
self::register("stained_glass_pane", fn(BID $id) => new StainedGlassPane($id, "Stained Glass Pane", $glassBreakInfo));
|
||||
self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", $hardenedClayBreakInfo));
|
||||
self::register("stained_clay", fn(BID $id) => new StainedHardenedClay($id, "Stained Clay", new Info(BreakInfo::pickaxe(1.25, ToolTier::WOOD, 6.25))));
|
||||
self::register("stained_hardened_glass", fn(BID $id) => new StainedHardenedGlass($id, "Stained Hardened Glass", $hardenedGlassBreakInfo));
|
||||
self::register("stained_hardened_glass_pane", fn(BID $id) => new StainedHardenedGlassPane($id, "Stained Hardened Glass Pane", $hardenedGlassBreakInfo));
|
||||
self::register("carpet", fn(BID $id) => new Carpet($id, "Carpet", new Info(new BreakInfo(0.1))));
|
||||
@ -1272,22 +1275,26 @@ final class VanillaBlocks{
|
||||
}
|
||||
})));
|
||||
|
||||
//TODO: in the future these won't all have the same hardness; they only do now because of the old metadata crap
|
||||
$wallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $wallBreakInfo));
|
||||
self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $wallBreakInfo));
|
||||
self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $wallBreakInfo));
|
||||
self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $wallBreakInfo));
|
||||
self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", $wallBreakInfo));
|
||||
self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $wallBreakInfo));
|
||||
self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $wallBreakInfo));
|
||||
self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $wallBreakInfo));
|
||||
self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $wallBreakInfo));
|
||||
self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $wallBreakInfo));
|
||||
self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $wallBreakInfo));
|
||||
self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $wallBreakInfo));
|
||||
self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $wallBreakInfo));
|
||||
self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $wallBreakInfo));
|
||||
self::register("end_stone_brick_wall", fn(BID $id) => new Wall($id, "End Stone Brick Wall", new Info(BreakInfo::pickaxe(3.0, ToolTier::WOOD, 45.0))));
|
||||
|
||||
$brickWallBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
self::register("cobblestone_wall", fn(BID $id) => new Wall($id, "Cobblestone Wall", $brickWallBreakInfo));
|
||||
self::register("brick_wall", fn(BID $id) => new Wall($id, "Brick Wall", $brickWallBreakInfo));
|
||||
self::register("mossy_cobblestone_wall", fn(BID $id) => new Wall($id, "Mossy Cobblestone Wall", $brickWallBreakInfo));
|
||||
self::register("nether_brick_wall", fn(BID $id) => new Wall($id, "Nether Brick Wall", $brickWallBreakInfo));
|
||||
self::register("red_nether_brick_wall", fn(BID $id) => new Wall($id, "Red Nether Brick Wall", $brickWallBreakInfo));
|
||||
|
||||
$stoneWallBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
|
||||
self::register("stone_brick_wall", fn(BID $id) => new Wall($id, "Stone Brick Wall", $stoneWallBreakInfo));
|
||||
self::register("mossy_stone_brick_wall", fn(BID $id) => new Wall($id, "Mossy Stone Brick Wall", $stoneWallBreakInfo));
|
||||
self::register("granite_wall", fn(BID $id) => new Wall($id, "Granite Wall", $stoneWallBreakInfo));
|
||||
self::register("diorite_wall", fn(BID $id) => new Wall($id, "Diorite Wall", $stoneWallBreakInfo));
|
||||
self::register("andesite_wall", fn(BID $id) => new Wall($id, "Andesite Wall", $stoneWallBreakInfo));
|
||||
self::register("prismarine_wall", fn(BID $id) => new Wall($id, "Prismarine Wall", $stoneWallBreakInfo));
|
||||
|
||||
$sandstoneWallBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD, 4.0));
|
||||
self::register("red_sandstone_wall", fn(BID $id) => new Wall($id, "Red Sandstone Wall", $sandstoneWallBreakInfo));
|
||||
self::register("sandstone_wall", fn(BID $id) => new Wall($id, "Sandstone Wall", $sandstoneWallBreakInfo));
|
||||
|
||||
self::registerElements();
|
||||
|
||||
@ -1320,8 +1327,8 @@ final class VanillaBlocks{
|
||||
self::register("mangrove_roots", fn(BID $id) => new MangroveRoots($id, "Mangrove Roots", new Info(BreakInfo::axe(0.7))));
|
||||
self::register("muddy_mangrove_roots", fn(BID $id) => new SimplePillar($id, "Muddy Mangrove Roots", new Info(BreakInfo::shovel(0.7), [Tags::MUD])));
|
||||
self::register("froglight", fn(BID $id) => new Froglight($id, "Froglight", new Info(new BreakInfo(0.3))));
|
||||
self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.6, ToolType::HOE))));
|
||||
self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 3600.0))) extends Opaque{
|
||||
self::register("sculk", fn(BID $id) => new Sculk($id, "Sculk", new Info(new BreakInfo(0.2, ToolType::HOE))));
|
||||
self::register("reinforced_deepslate", fn(BID $id) => new class($id, "Reinforced Deepslate", new Info(new BreakInfo(55.0, ToolType::NONE, 0, 6000.0))) extends Opaque{
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [];
|
||||
}
|
||||
@ -1537,7 +1544,7 @@ final class VanillaBlocks{
|
||||
self::register("lapis_lazuli_ore", fn(BID $id) => new LapisOre($id, "Lapis Lazuli Ore", $stoneOreBreakInfo(ToolTier::STONE)));
|
||||
self::register("redstone_ore", fn(BID $id) => new RedstoneOre($id, "Redstone Ore", $stoneOreBreakInfo(ToolTier::IRON)));
|
||||
|
||||
$deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier));
|
||||
$deepslateOreBreakInfo = fn(ToolTier $toolTier) => new Info(BreakInfo::pickaxe(4.5, $toolTier, 15.0));
|
||||
self::register("deepslate_coal_ore", fn(BID $id) => new CoalOre($id, "Deepslate Coal Ore", $deepslateOreBreakInfo(ToolTier::WOOD)));
|
||||
self::register("deepslate_copper_ore", fn(BID $id) => new CopperOre($id, "Deepslate Copper Ore", $deepslateOreBreakInfo(ToolTier::STONE)));
|
||||
self::register("deepslate_diamond_ore", fn(BID $id) => new DiamondOre($id, "Deepslate Diamond Ore", $deepslateOreBreakInfo(ToolTier::IRON)));
|
||||
@ -1581,10 +1588,10 @@ final class VanillaBlocks{
|
||||
//for some reason, slabs have weird hardness like the legacy ones
|
||||
$slabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
|
||||
self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 3600.0))) extends Opaque{
|
||||
self::register("ancient_debris", fn(BID $id) => new class($id, "Ancient Debris", new Info(BreakInfo::pickaxe(30, ToolTier::DIAMOND, 6000.0))) extends Opaque{
|
||||
public function isFireProofAsItem() : bool{ return true; }
|
||||
});
|
||||
$netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 3600.0));
|
||||
$netheriteBreakInfo = new Info(BreakInfo::pickaxe(50, ToolTier::DIAMOND, 6000.0));
|
||||
self::register("netherite", fn(BID $id) => new class($id, "Netherite Block", $netheriteBreakInfo) extends Opaque{
|
||||
public function isFireProofAsItem() : bool{ return true; }
|
||||
});
|
||||
@ -1602,14 +1609,14 @@ final class VanillaBlocks{
|
||||
|
||||
self::register("gilded_blackstone", fn(BID $id) => new GildedBlackstone($id, "Gilded Blackstone", $blackstoneBreakInfo));
|
||||
|
||||
//TODO: polished blackstone ought to have 2.0 hardness (as per java) but it's 1.5 in Bedrock (probably parity bug)
|
||||
$polishedBlackstoneBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
$prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : "");
|
||||
self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo));
|
||||
self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $polishedBlackstoneBreakInfo));
|
||||
self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
|
||||
self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5)), 20));
|
||||
self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo));
|
||||
self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
|
||||
self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
|
||||
self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $polishedBlackstoneBreakInfo));
|
||||
self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $polishedBlackstoneBreakInfo));
|
||||
self::register("chiseled_polished_blackstone", fn(BID $id) => new Opaque($id, "Chiseled Polished Blackstone", $blackstoneBreakInfo));
|
||||
|
||||
$prefix = fn(string $thing) => "Polished Blackstone Brick" . ($thing !== "" ? " $thing" : "");
|
||||
@ -1622,8 +1629,7 @@ final class VanillaBlocks{
|
||||
self::register("soul_torch", fn(BID $id) => new Torch($id, "Soul Torch", new Info(BreakInfo::instant())));
|
||||
self::register("soul_fire", fn(BID $id) => new SoulFire($id, "Soul Fire", new Info(BreakInfo::instant(), [Tags::FIRE])));
|
||||
|
||||
//TODO: soul soul ought to have 0.5 hardness (as per java) but it's 1.0 in Bedrock (probably parity bug)
|
||||
self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(1.0))));
|
||||
self::register("soul_soil", fn(BID $id) => new Opaque($id, "Soul Soil", new Info(BreakInfo::shovel(0.5))));
|
||||
|
||||
self::register("shroomlight", fn(BID $id) => new class($id, "Shroomlight", new Info(new BreakInfo(1.0, ToolType::HOE))) extends Opaque{
|
||||
public function getLightLevel() : int{ return 15; }
|
||||
@ -1641,7 +1647,9 @@ final class VanillaBlocks{
|
||||
self::register("crimson_roots", fn(BID $id) => new NetherRoots($id, "Crimson Roots", $netherRootsInfo));
|
||||
self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo));
|
||||
|
||||
self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
|
||||
self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
|
||||
|
||||
self::register("respawn_anchor", fn(BID $id) => new RespawnAnchor($id, "Respawn Anchor", new Info(BreakInfo::pickaxe(50.0, ToolTier::DIAMOND, 6000.0))));
|
||||
}
|
||||
|
||||
private static function registerBlocksR17() : void{
|
||||
@ -1659,7 +1667,7 @@ final class VanillaBlocks{
|
||||
self::register("raw_gold", fn(BID $id) => new Opaque($id, "Raw Gold Block", new Info(BreakInfo::pickaxe(5, ToolTier::IRON, 30.0))));
|
||||
self::register("raw_iron", fn(BID $id) => new Opaque($id, "Raw Iron Block", new Info(BreakInfo::pickaxe(5, ToolTier::STONE, 30.0))));
|
||||
|
||||
$deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 18.0));
|
||||
$deepslateBreakInfo = new Info(BreakInfo::pickaxe(3, ToolTier::WOOD, 30.0));
|
||||
self::register("deepslate", fn(BID $id) => new class($id, "Deepslate", $deepslateBreakInfo) extends SimplePillar{
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
return [VanillaBlocks::COBBLED_DEEPSLATE()->asItem()];
|
||||
@ -1671,29 +1679,29 @@ final class VanillaBlocks{
|
||||
});
|
||||
|
||||
//TODO: parity issue here - in Java this has a hardness of 3.0, but in bedrock it's 3.5
|
||||
self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0))));
|
||||
self::register("chiseled_deepslate", fn(BID $id) => new Opaque($id, "Chiseled Deepslate", new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0))));
|
||||
|
||||
$deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
|
||||
$deepslateBrickBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0));
|
||||
self::register("deepslate_bricks", fn(BID $id) => new Opaque($id, "Deepslate Bricks", $deepslateBrickBreakInfo));
|
||||
self::register("deepslate_brick_slab", fn(BID $id) => new Slab($id, "Deepslate Brick", $deepslateBrickBreakInfo));
|
||||
self::register("deepslate_brick_stairs", fn(BID $id) => new Stair($id, "Deepslate Brick Stairs", $deepslateBrickBreakInfo));
|
||||
self::register("deepslate_brick_wall", fn(BID $id) => new Wall($id, "Deepslate Brick Wall", $deepslateBrickBreakInfo));
|
||||
self::register("cracked_deepslate_bricks", fn(BID $id) => new Opaque($id, "Cracked Deepslate Bricks", $deepslateBrickBreakInfo));
|
||||
|
||||
$deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
|
||||
$deepslateTilesBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0));
|
||||
self::register("deepslate_tiles", fn(BID $id) => new Opaque($id, "Deepslate Tiles", $deepslateTilesBreakInfo));
|
||||
self::register("deepslate_tile_slab", fn(BID $id) => new Slab($id, "Deepslate Tile", $deepslateTilesBreakInfo));
|
||||
self::register("deepslate_tile_stairs", fn(BID $id) => new Stair($id, "Deepslate Tile Stairs", $deepslateTilesBreakInfo));
|
||||
self::register("deepslate_tile_wall", fn(BID $id) => new Wall($id, "Deepslate Tile Wall", $deepslateTilesBreakInfo));
|
||||
self::register("cracked_deepslate_tiles", fn(BID $id) => new Opaque($id, "Cracked Deepslate Tiles", $deepslateTilesBreakInfo));
|
||||
|
||||
$cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
|
||||
$cobbledDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0));
|
||||
self::register("cobbled_deepslate", fn(BID $id) => new Opaque($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
|
||||
self::register("cobbled_deepslate_slab", fn(BID $id) => new Slab($id, "Cobbled Deepslate", $cobbledDeepslateBreakInfo));
|
||||
self::register("cobbled_deepslate_stairs", fn(BID $id) => new Stair($id, "Cobbled Deepslate Stairs", $cobbledDeepslateBreakInfo));
|
||||
self::register("cobbled_deepslate_wall", fn(BID $id) => new Wall($id, "Cobbled Deepslate Wall", $cobbledDeepslateBreakInfo));
|
||||
|
||||
$polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 18.0));
|
||||
$polishedDeepslateBreakInfo = new Info(BreakInfo::pickaxe(3.5, ToolTier::WOOD, 30.0));
|
||||
self::register("polished_deepslate", fn(BID $id) => new Opaque($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
|
||||
self::register("polished_deepslate_slab", fn(BID $id) => new Slab($id, "Polished Deepslate", $polishedDeepslateBreakInfo));
|
||||
self::register("polished_deepslate_stairs", fn(BID $id) => new Stair($id, "Polished Deepslate Stairs", $polishedDeepslateBreakInfo));
|
||||
@ -1702,7 +1710,7 @@ final class VanillaBlocks{
|
||||
self::register("tinted_glass", fn(BID $id) => new TintedGlass($id, "Tinted Glass", new Info(new BreakInfo(0.3))));
|
||||
|
||||
//blast resistance should be 30 if we were matched with java :(
|
||||
$copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 18.0));
|
||||
$copperBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0));
|
||||
self::register("lightning_rod", fn(BID $id) => new LightningRod($id, "Lightning Rod", $copperBreakInfo));
|
||||
|
||||
self::register("copper", fn(BID $id) => new Copper($id, "Copper Block", $copperBreakInfo));
|
||||
@ -1730,8 +1738,8 @@ final class VanillaBlocks{
|
||||
self::register("cave_vines", fn(BID $id) => new CaveVines($id, "Cave Vines", new Info(BreakInfo::instant())));
|
||||
|
||||
self::register("small_dripleaf", fn(BID $id) => new SmallDripleaf($id, "Small Dripleaf", new Info(BreakInfo::instant(ToolType::SHEARS, toolHarvestLevel: 1))));
|
||||
self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(BreakInfo::instant())));
|
||||
self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(BreakInfo::instant())));
|
||||
self::register("big_dripleaf_head", fn(BID $id) => new BigDripleafHead($id, "Big Dripleaf", new Info(new BreakInfo(0.1))));
|
||||
self::register("big_dripleaf_stem", fn(BID $id) => new BigDripleafStem($id, "Big Dripleaf Stem", new Info(new BreakInfo(0.1))));
|
||||
}
|
||||
|
||||
private static function registerBlocksR18() : void{
|
||||
@ -1742,7 +1750,7 @@ final class VanillaBlocks{
|
||||
self::register("mud", fn(BID $id) => new Opaque($id, "Mud", new Info(BreakInfo::shovel(0.5), [Tags::MUD])));
|
||||
self::register("packed_mud", fn(BID $id) => new Opaque($id, "Packed Mud", new Info(BreakInfo::pickaxe(1.0, null, 15.0))));
|
||||
|
||||
$mudBricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
$mudBricksBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 15.0));
|
||||
|
||||
self::register("mud_bricks", fn(BID $id) => new Opaque($id, "Mud Bricks", $mudBricksBreakInfo));
|
||||
self::register("mud_brick_slab", fn(BID $id) => new Slab($id, "Mud Brick", $mudBricksBreakInfo));
|
||||
@ -1754,7 +1762,7 @@ final class VanillaBlocks{
|
||||
self::register("resin", fn(BID $id) => new Opaque($id, "Block of Resin", new Info(BreakInfo::instant())));
|
||||
self::register("resin_clump", fn(BID $id) => new ResinClump($id, "Resin Clump", new Info(BreakInfo::instant())));
|
||||
|
||||
$resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD));
|
||||
$resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
|
||||
self::register("resin_brick_slab", fn(BID $id) => new Slab($id, "Resin Brick", $resinBricksInfo));
|
||||
self::register("resin_brick_stairs", fn(BID $id) => new Stair($id, "Resin Brick Stairs", $resinBricksInfo));
|
||||
self::register("resin_brick_wall", fn(BID $id) => new Wall($id, "Resin Brick Wall", $resinBricksInfo));
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityExtinguishEvent;
|
||||
use pocketmine\world\sound\BucketEmptyWaterSound;
|
||||
use pocketmine\world\sound\BucketFillWaterSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
@ -53,7 +54,7 @@ class Water extends Liquid{
|
||||
public function onEntityInside(Entity $entity) : bool{
|
||||
$entity->resetFallDistance();
|
||||
if($entity->isOnFire()){
|
||||
$entity->extinguish();
|
||||
$entity->extinguish(EntityExtinguishEvent::CAUSE_WATER);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\tile\Cauldron as TileCauldron;
|
||||
use pocketmine\block\utils\DyeColor;
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\entity\EntityExtinguishEvent;
|
||||
use pocketmine\item\Armor;
|
||||
use pocketmine\item\Banner;
|
||||
use pocketmine\item\Dye;
|
||||
@ -183,7 +184,7 @@ final class WaterCauldron extends FillableCauldron{
|
||||
|
||||
public function onEntityInside(Entity $entity) : bool{
|
||||
if($entity->isOnFire()){
|
||||
$entity->extinguish();
|
||||
$entity->extinguish(EntityExtinguishEvent::CAUSE_WATER_CAULDRON);
|
||||
//TODO: particles
|
||||
|
||||
$this->position->getWorld()->setBlock($this->position, $this->withFillLevel($this->getFillLevel() - self::ENTITY_EXTINGUISH_USE_AMOUNT));
|
||||
|
@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use function get_class;
|
||||
|
||||
abstract class Spawnable extends Tile{
|
||||
/** @phpstan-var CacheableNbt<\pocketmine\nbt\tag\CompoundTag>|null */
|
||||
/** @phpstan-var CacheableNbt<CompoundTag>|null */
|
||||
private ?CacheableNbt $spawnCompoundCache = null;
|
||||
|
||||
/**
|
||||
@ -73,7 +73,7 @@ abstract class Spawnable extends Tile{
|
||||
* Returns encoded NBT (varint, little-endian) used to spawn this tile to clients. Uses cache where possible,
|
||||
* populates cache if it is null.
|
||||
*
|
||||
* @phpstan-return CacheableNbt<\pocketmine\nbt\tag\CompoundTag>
|
||||
* @phpstan-return CacheableNbt<CompoundTag>
|
||||
*/
|
||||
final public function getSerializedSpawnCompound() : CacheableNbt{
|
||||
if($this->spawnCompoundCache === null){
|
||||
|
@ -114,6 +114,13 @@ final class TileFactory{
|
||||
$this->saveNames[$className] = reset($saveNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param class-string<Tile> $class
|
||||
*/
|
||||
public function isRegistered(string $class) : bool{
|
||||
return isset($this->saveNames[$class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @throws SavedDataLoadingException
|
||||
|
@ -30,9 +30,15 @@ interface CopperMaterial{
|
||||
|
||||
public function getOxidation() : CopperOxidation;
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setOxidation(CopperOxidation $oxidation) : CopperMaterial;
|
||||
|
||||
public function isWaxed() : bool;
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setWaxed(bool $waxed) : CopperMaterial;
|
||||
}
|
||||
|
@ -29,8 +29,12 @@ use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\DestructorCallbackTrait;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function ksort;
|
||||
use function spl_object_id;
|
||||
use function usort;
|
||||
use const SORT_STRING;
|
||||
|
||||
class CraftingManager{
|
||||
use DestructorCallbackTrait;
|
||||
@ -100,6 +104,7 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* Function used to arrange Shapeless Recipe ingredient lists into a consistent order.
|
||||
* @deprecated
|
||||
*/
|
||||
public static function sort(Item $i1, Item $i2) : int{
|
||||
//Use spaceship operator to compare each property, then try the next one if they are equivalent.
|
||||
@ -108,45 +113,30 @@ class CraftingManager{
|
||||
return $retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
*/
|
||||
private static function pack(array $items) : array{
|
||||
$result = [];
|
||||
private static function hashOutput(Item $output) : string{
|
||||
$write = new BinaryStream();
|
||||
$write->putVarInt($output->getStateId());
|
||||
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
|
||||
|
||||
foreach($items as $item){
|
||||
foreach($result as $otherItem){
|
||||
if($item->canStackWith($otherItem)){
|
||||
$otherItem->setCount($otherItem->getCount() + $item->getCount());
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
//No matching item found
|
||||
$result[] = clone $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
return $write->getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $outputs
|
||||
*/
|
||||
private static function hashOutputs(array $outputs) : string{
|
||||
$outputs = self::pack($outputs);
|
||||
usort($outputs, [self::class, "sort"]);
|
||||
$result = new BinaryStream();
|
||||
if(count($outputs) === 1){
|
||||
return self::hashOutput(array_shift($outputs));
|
||||
}
|
||||
$unique = [];
|
||||
foreach($outputs as $o){
|
||||
//count is not written because the outputs might be from multiple repetitions of a single recipe
|
||||
//this reduces the accuracy of the hash, but it won't matter in most cases.
|
||||
$result->putVarInt($o->getStateId());
|
||||
$result->put((new LittleEndianNbtSerializer())->write(new TreeRoot($o->getNamedTag())));
|
||||
$hash = self::hashOutput($o);
|
||||
$unique[$hash] = $hash;
|
||||
}
|
||||
|
||||
return $result->getBuffer();
|
||||
ksort($unique, SORT_STRING);
|
||||
return implode("", $unique);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,6 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
|
||||
$this->shape = $shape;
|
||||
|
||||
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
|
||||
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
|
||||
if(!str_contains(implode($this->shape), $char)){
|
||||
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
|
||||
@ -105,6 +106,7 @@ class ShapedRecipe implements CraftingRecipe{
|
||||
$this->ingredientList[$char] = clone $i;
|
||||
}
|
||||
|
||||
Utils::validateArrayValueType($results, function(Item $_) : void{});
|
||||
$this->results = Utils::cloneObjectArray($results);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,9 @@ class ShapelessRecipe implements CraftingRecipe{
|
||||
if(count($ingredients) > 9){
|
||||
throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients");
|
||||
}
|
||||
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
|
||||
$this->ingredients = $ingredients;
|
||||
Utils::validateArrayValueType($results, function(Item $_) : void{});
|
||||
$this->results = Utils::cloneObjectArray($results);
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,7 @@ final class BedrockDataFiles{
|
||||
}
|
||||
|
||||
public const BANNER_PATTERNS_JSON = BEDROCK_DATA_PATH . '/banner_patterns.json';
|
||||
public const BIOME_DEFINITIONS_NBT = BEDROCK_DATA_PATH . '/biome_definitions.nbt';
|
||||
public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt';
|
||||
public const BIOME_DEFINITIONS_JSON = BEDROCK_DATA_PATH . '/biome_definitions.json';
|
||||
public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json';
|
||||
public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json';
|
||||
public const BLOCK_PROPERTIES_TABLE_JSON = BEDROCK_DATA_PATH . '/block_properties_table.json';
|
||||
|
66
src/data/bedrock/WorldDataVersions.php
Normal file
66
src/data/bedrock/WorldDataVersions.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock;
|
||||
|
||||
use pocketmine\world\format\io\leveldb\ChunkVersion;
|
||||
use pocketmine\world\format\io\leveldb\SubChunkVersion;
|
||||
|
||||
/**
|
||||
* All version infos related to current Minecraft data version support
|
||||
* These are mostly related to world storage but may also influence network stuff
|
||||
*/
|
||||
final class WorldDataVersions{
|
||||
/**
|
||||
* Bedrock version of the most recent backwards-incompatible change to blockstates.
|
||||
*
|
||||
* This is *NOT* the same as current game version. It should match the numbers in the
|
||||
* newest blockstate upgrade schema used in BedrockBlockUpgradeSchema.
|
||||
*/
|
||||
public const BLOCK_STATES =
|
||||
(1 << 24) | //major
|
||||
(21 << 16) | //minor
|
||||
(60 << 8) | //patch
|
||||
(33); //revision
|
||||
|
||||
public const CHUNK = ChunkVersion::v1_21_40;
|
||||
public const SUBCHUNK = SubChunkVersion::PALETTED_MULTI;
|
||||
|
||||
public const STORAGE = 10;
|
||||
|
||||
/**
|
||||
* Highest NetworkVersion of Bedrock worlds currently supported by PocketMine-MP.
|
||||
*
|
||||
* This may be lower than the current protocol version if PocketMine-MP does not yet support features of the newer
|
||||
* version. This allows the protocol to be updated independently of world format support.
|
||||
*/
|
||||
public const NETWORK = 800;
|
||||
|
||||
public const LAST_OPENED_IN = [
|
||||
1, //major
|
||||
21, //minor
|
||||
80, //patch
|
||||
3, //revision
|
||||
0 //is beta
|
||||
];
|
||||
}
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block;
|
||||
|
||||
use pocketmine\data\bedrock\WorldDataVersions;
|
||||
use pocketmine\nbt\NbtException;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
@ -36,17 +37,7 @@ use function implode;
|
||||
* Contains the common information found in a serialized blockstate.
|
||||
*/
|
||||
final class BlockStateData{
|
||||
/**
|
||||
* Bedrock version of the most recent backwards-incompatible change to blockstates.
|
||||
*
|
||||
* This is *not* the same as current game version. It should match the numbers in the
|
||||
* newest blockstate upgrade schema used in BedrockBlockUpgradeSchema.
|
||||
*/
|
||||
public const CURRENT_VERSION =
|
||||
(1 << 24) | //major
|
||||
(21 << 16) | //minor
|
||||
(60 << 8) | //patch
|
||||
(33); //revision
|
||||
public const CURRENT_VERSION = WorldDataVersions::BLOCK_STATES;
|
||||
|
||||
public const TAG_NAME = "name";
|
||||
public const TAG_STATES = "states";
|
||||
|
@ -113,6 +113,7 @@ final class BlockStateNames{
|
||||
public const RAIL_DATA_BIT = "rail_data_bit";
|
||||
public const RAIL_DIRECTION = "rail_direction";
|
||||
public const REDSTONE_SIGNAL = "redstone_signal";
|
||||
public const REHYDRATION_LEVEL = "rehydration_level";
|
||||
public const REPEATER_DELAY = "repeater_delay";
|
||||
public const RESPAWN_ANCHOR_CHARGE = "respawn_anchor_charge";
|
||||
public const ROTATION = "rotation";
|
||||
|
@ -175,7 +175,9 @@ final class BlockTypeNames{
|
||||
public const BUBBLE_CORAL_FAN = "minecraft:bubble_coral_fan";
|
||||
public const BUBBLE_CORAL_WALL_FAN = "minecraft:bubble_coral_wall_fan";
|
||||
public const BUDDING_AMETHYST = "minecraft:budding_amethyst";
|
||||
public const BUSH = "minecraft:bush";
|
||||
public const CACTUS = "minecraft:cactus";
|
||||
public const CACTUS_FLOWER = "minecraft:cactus_flower";
|
||||
public const CAKE = "minecraft:cake";
|
||||
public const CALCITE = "minecraft:calcite";
|
||||
public const CALIBRATED_SCULK_SENSOR = "minecraft:calibrated_sculk_sensor";
|
||||
@ -390,6 +392,7 @@ final class BlockTypeNames{
|
||||
public const DOUBLE_CUT_COPPER_SLAB = "minecraft:double_cut_copper_slab";
|
||||
public const DRAGON_EGG = "minecraft:dragon_egg";
|
||||
public const DRAGON_HEAD = "minecraft:dragon_head";
|
||||
public const DRIED_GHAST = "minecraft:dried_ghast";
|
||||
public const DRIED_KELP_BLOCK = "minecraft:dried_kelp_block";
|
||||
public const DRIPSTONE_BLOCK = "minecraft:dripstone_block";
|
||||
public const DROPPER = "minecraft:dropper";
|
||||
@ -545,6 +548,7 @@ final class BlockTypeNames{
|
||||
public const FIRE_CORAL_BLOCK = "minecraft:fire_coral_block";
|
||||
public const FIRE_CORAL_FAN = "minecraft:fire_coral_fan";
|
||||
public const FIRE_CORAL_WALL_FAN = "minecraft:fire_coral_wall_fan";
|
||||
public const FIREFLY_BUSH = "minecraft:firefly_bush";
|
||||
public const FLETCHING_TABLE = "minecraft:fletching_table";
|
||||
public const FLOWER_POT = "minecraft:flower_pot";
|
||||
public const FLOWERING_AZALEA = "minecraft:flowering_azalea";
|
||||
@ -685,6 +689,7 @@ final class BlockTypeNames{
|
||||
public const LARGE_AMETHYST_BUD = "minecraft:large_amethyst_bud";
|
||||
public const LARGE_FERN = "minecraft:large_fern";
|
||||
public const LAVA = "minecraft:lava";
|
||||
public const LEAF_LITTER = "minecraft:leaf_litter";
|
||||
public const LECTERN = "minecraft:lectern";
|
||||
public const LEVER = "minecraft:lever";
|
||||
public const LIGHT_BLOCK_0 = "minecraft:light_block_0";
|
||||
@ -1043,6 +1048,7 @@ final class BlockTypeNames{
|
||||
public const SEA_LANTERN = "minecraft:sea_lantern";
|
||||
public const SEA_PICKLE = "minecraft:sea_pickle";
|
||||
public const SEAGRASS = "minecraft:seagrass";
|
||||
public const SHORT_DRY_GRASS = "minecraft:short_dry_grass";
|
||||
public const SHORT_GRASS = "minecraft:short_grass";
|
||||
public const SHROOMLIGHT = "minecraft:shroomlight";
|
||||
public const SILVER_GLAZED_TERRACOTTA = "minecraft:silver_glazed_terracotta";
|
||||
@ -1140,6 +1146,7 @@ final class BlockTypeNames{
|
||||
public const SUSPICIOUS_GRAVEL = "minecraft:suspicious_gravel";
|
||||
public const SUSPICIOUS_SAND = "minecraft:suspicious_sand";
|
||||
public const SWEET_BERRY_BUSH = "minecraft:sweet_berry_bush";
|
||||
public const TALL_DRY_GRASS = "minecraft:tall_dry_grass";
|
||||
public const TALL_GRASS = "minecraft:tall_grass";
|
||||
public const TARGET = "minecraft:target";
|
||||
public const TINTED_GLASS = "minecraft:tinted_glass";
|
||||
@ -1267,6 +1274,7 @@ final class BlockTypeNames{
|
||||
public const WHITE_TERRACOTTA = "minecraft:white_terracotta";
|
||||
public const WHITE_TULIP = "minecraft:white_tulip";
|
||||
public const WHITE_WOOL = "minecraft:white_wool";
|
||||
public const WILDFLOWERS = "minecraft:wildflowers";
|
||||
public const WITHER_ROSE = "minecraft:wither_rose";
|
||||
public const WITHER_SKELETON_SKULL = "minecraft:wither_skeleton_skull";
|
||||
public const WOODEN_BUTTON = "minecraft:wooden_button";
|
||||
|
@ -122,6 +122,7 @@ use pocketmine\block\RedstoneRepeater;
|
||||
use pocketmine\block\RedstoneTorch;
|
||||
use pocketmine\block\RedstoneWire;
|
||||
use pocketmine\block\ResinClump;
|
||||
use pocketmine\block\RespawnAnchor;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\Sapling;
|
||||
use pocketmine\block\SeaPickle;
|
||||
@ -214,6 +215,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
$this->registerLeavesSerializers();
|
||||
$this->registerSaplingSerializers();
|
||||
$this->registerMobHeadSerializers();
|
||||
$this->registerCopperSerializers();
|
||||
$this->registerSimpleSerializers();
|
||||
$this->registerSerializers();
|
||||
}
|
||||
@ -224,6 +226,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
return $this->cache[$stateId] ??= $this->serializeBlock(RuntimeBlockStateRegistry::getInstance()->fromStateId($stateId));
|
||||
}
|
||||
|
||||
public function isRegistered(Block $block) : bool{
|
||||
return isset($this->serializers[$block->getTypeId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TBlockType of Block
|
||||
* @phpstan-param TBlockType $block
|
||||
@ -791,6 +797,178 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
})->writeFacingWithoutDown($block->getFacing()));
|
||||
}
|
||||
|
||||
private function registerCopperSerializers() : void{
|
||||
$this->map(Blocks::COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) :
|
||||
Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_CHISELED_COPPER,
|
||||
Ids::WAXED_EXPOSED_CHISELED_COPPER,
|
||||
Ids::WAXED_WEATHERED_CHISELED_COPPER,
|
||||
Ids::WAXED_OXIDIZED_CHISELED_COPPER
|
||||
) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::CHISELED_COPPER,
|
||||
Ids::EXPOSED_CHISELED_COPPER,
|
||||
Ids::WEATHERED_CHISELED_COPPER,
|
||||
Ids::OXIDIZED_CHISELED_COPPER
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_COPPER_GRATE,
|
||||
Ids::WAXED_EXPOSED_COPPER_GRATE,
|
||||
Ids::WAXED_WEATHERED_COPPER_GRATE,
|
||||
Ids::WAXED_OXIDIZED_COPPER_GRATE
|
||||
) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::COPPER_GRATE,
|
||||
Ids::EXPOSED_COPPER_GRATE,
|
||||
Ids::WEATHERED_COPPER_GRATE,
|
||||
Ids::OXIDIZED_COPPER_GRATE
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) :
|
||||
Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeSlab(
|
||||
$block,
|
||||
($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_CUT_COPPER_SLAB
|
||||
)
|
||||
),
|
||||
($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeStairs(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::CUT_COPPER_STAIRS,
|
||||
Ids::EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::OXIDIZED_CUT_COPPER_STAIRS
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Writer::create($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_COPPER_BULB,
|
||||
Ids::WAXED_EXPOSED_COPPER_BULB,
|
||||
Ids::WAXED_WEATHERED_COPPER_BULB,
|
||||
Ids::WAXED_OXIDIZED_COPPER_BULB) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::COPPER_BULB,
|
||||
Ids::EXPOSED_COPPER_BULB,
|
||||
Ids::WEATHERED_COPPER_BULB,
|
||||
Ids::OXIDIZED_COPPER_BULB
|
||||
))
|
||||
->writeBool(StateNames::LIT, $block->isLit())
|
||||
->writeBool(StateNames::POWERED_BIT, $block->isPowered());
|
||||
});
|
||||
$this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeDoor(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_COPPER_DOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_DOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_DOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_DOOR
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::COPPER_DOOR,
|
||||
Ids::EXPOSED_COPPER_DOOR,
|
||||
Ids::WEATHERED_COPPER_DOOR,
|
||||
Ids::OXIDIZED_COPPER_DOOR
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeTrapdoor(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::COPPER_TRAPDOOR,
|
||||
Ids::EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::OXIDIZED_COPPER_TRAPDOOR
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private function registerSimpleSerializers() : void{
|
||||
$this->mapSimple(Blocks::AIR(), Ids::AIR);
|
||||
$this->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK);
|
||||
@ -1265,175 +1443,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
$this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB);
|
||||
$this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS);
|
||||
$this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL)));
|
||||
$this->map(Blocks::COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) :
|
||||
Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_CHISELED_COPPER,
|
||||
Ids::WAXED_EXPOSED_CHISELED_COPPER,
|
||||
Ids::WAXED_WEATHERED_CHISELED_COPPER,
|
||||
Ids::WAXED_OXIDIZED_CHISELED_COPPER
|
||||
) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::CHISELED_COPPER,
|
||||
Ids::EXPOSED_CHISELED_COPPER,
|
||||
Ids::WEATHERED_CHISELED_COPPER,
|
||||
Ids::OXIDIZED_CHISELED_COPPER
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_COPPER_GRATE,
|
||||
Ids::WAXED_EXPOSED_COPPER_GRATE,
|
||||
Ids::WAXED_WEATHERED_COPPER_GRATE,
|
||||
Ids::WAXED_OXIDIZED_COPPER_GRATE
|
||||
) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::COPPER_GRATE,
|
||||
Ids::EXPOSED_COPPER_GRATE,
|
||||
Ids::WEATHERED_COPPER_GRATE,
|
||||
Ids::OXIDIZED_COPPER_GRATE
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) :
|
||||
Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeSlab(
|
||||
$block,
|
||||
($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_CUT_COPPER_SLAB
|
||||
)
|
||||
),
|
||||
($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeStairs(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::CUT_COPPER_STAIRS,
|
||||
Ids::EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::OXIDIZED_CUT_COPPER_STAIRS
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Writer::create($block->isWaxed() ?
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::WAXED_COPPER_BULB,
|
||||
Ids::WAXED_EXPOSED_COPPER_BULB,
|
||||
Ids::WAXED_WEATHERED_COPPER_BULB,
|
||||
Ids::WAXED_OXIDIZED_COPPER_BULB) :
|
||||
Helper::selectCopperId($oxidation,
|
||||
Ids::COPPER_BULB,
|
||||
Ids::EXPOSED_COPPER_BULB,
|
||||
Ids::WEATHERED_COPPER_BULB,
|
||||
Ids::OXIDIZED_COPPER_BULB
|
||||
))
|
||||
->writeBool(StateNames::LIT, $block->isLit())
|
||||
->writeBool(StateNames::POWERED_BIT, $block->isPowered());
|
||||
});
|
||||
$this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeDoor(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_COPPER_DOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_DOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_DOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_DOOR
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::COPPER_DOOR,
|
||||
Ids::EXPOSED_COPPER_DOOR,
|
||||
Ids::WEATHERED_COPPER_DOOR,
|
||||
Ids::OXIDIZED_COPPER_DOOR
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{
|
||||
$oxidation = $block->getOxidation();
|
||||
return Helper::encodeTrapdoor(
|
||||
$block,
|
||||
new Writer($block->isWaxed() ?
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::WAXED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR
|
||||
) :
|
||||
Helper::selectCopperId(
|
||||
$oxidation,
|
||||
Ids::COPPER_TRAPDOOR,
|
||||
Ids::EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::OXIDIZED_COPPER_TRAPDOOR
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
$this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{
|
||||
return Writer::create(Ids::COCOA)
|
||||
->writeInt(StateNames::AGE, $block->getAge())
|
||||
@ -1746,6 +1755,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
return Writer::create(Ids::RESIN_CLUMP)
|
||||
->writeFacingFlags($block->getFaces());
|
||||
});
|
||||
$this->map(Blocks::RESPAWN_ANCHOR(), function(RespawnAnchor $block) : Writer{
|
||||
return Writer::create(Ids::RESPAWN_ANCHOR)
|
||||
->writeInt(StateNames::RESPAWN_ANCHOR_CHARGE, $block->getCharges());
|
||||
});
|
||||
$this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH)));
|
||||
$this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
|
||||
$this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS);
|
||||
|
@ -126,7 +126,13 @@ final class BlockStateDeserializerHelper{
|
||||
->setOutputSignalStrength($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15));
|
||||
}
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
/**
|
||||
* @phpstan-template TDoor of Door
|
||||
* @phpstan-param TDoor $block
|
||||
* @phpstan-return TDoor
|
||||
*
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public static function decodeDoor(Door $block, BlockStateReader $in) : Door{
|
||||
//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
|
||||
return $block
|
||||
@ -237,18 +243,36 @@ final class BlockStateDeserializerHelper{
|
||||
return $block->setPressed($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15) !== 0);
|
||||
}
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
/**
|
||||
* @phpstan-template TSlab of Slab
|
||||
* @phpstan-param TSlab $block
|
||||
* @phpstan-return TSlab
|
||||
*
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public static function decodeSingleSlab(Slab $block, BlockStateReader $in) : Slab{
|
||||
return $block->setSlabType($in->readSlabPosition());
|
||||
}
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
/**
|
||||
* @phpstan-template TSlab of Slab
|
||||
* @phpstan-param TSlab $block
|
||||
* @phpstan-return TSlab
|
||||
*
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public static function decodeDoubleSlab(Slab $block, BlockStateReader $in) : Slab{
|
||||
$in->ignored(StateNames::MC_VERTICAL_HALF);
|
||||
return $block->setSlabType(SlabType::DOUBLE);
|
||||
}
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
/**
|
||||
* @phpstan-template TStair of Stair
|
||||
* @phpstan-param TStair $block
|
||||
* @phpstan-return TStair
|
||||
*
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public static function decodeStairs(Stair $block, BlockStateReader $in) : Stair{
|
||||
return $block
|
||||
->setUpsideDown($in->readBool(BlockStateNames::UPSIDE_DOWN_BIT))
|
||||
@ -265,7 +289,13 @@ final class BlockStateDeserializerHelper{
|
||||
->setFacing($facing === Facing::DOWN ? Facing::UP : $facing);
|
||||
}
|
||||
|
||||
/** @throws BlockStateDeserializeException */
|
||||
/**
|
||||
* @phpstan-template TTrapdoor of Trapdoor
|
||||
* @phpstan-param TTrapdoor $block
|
||||
* @phpstan-return TTrapdoor
|
||||
*
|
||||
* @throws BlockStateDeserializeException
|
||||
*/
|
||||
public static function decodeTrapdoor(Trapdoor $block, BlockStateReader $in) : Trapdoor{
|
||||
return $block
|
||||
->setFacing($in->read5MinusHorizontalFacing())
|
||||
|
@ -33,11 +33,13 @@ use pocketmine\block\DoublePitcherCrop;
|
||||
use pocketmine\block\Opaque;
|
||||
use pocketmine\block\PinkPetals;
|
||||
use pocketmine\block\PitcherCrop;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\Slab;
|
||||
use pocketmine\block\Stair;
|
||||
use pocketmine\block\SweetBerryBush;
|
||||
use pocketmine\block\utils\BrewingStandSlot;
|
||||
use pocketmine\block\utils\ChiseledBookshelfSlot;
|
||||
use pocketmine\block\utils\CopperMaterial;
|
||||
use pocketmine\block\utils\CopperOxidation;
|
||||
use pocketmine\block\utils\CoralType;
|
||||
use pocketmine\block\utils\DirtType;
|
||||
@ -59,6 +61,7 @@ use pocketmine\data\bedrock\block\convert\BlockStateDeserializerHelper as Helper
|
||||
use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader;
|
||||
use pocketmine\math\Axis;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function min;
|
||||
@ -87,6 +90,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
$this->registerSaplingDeserializers();
|
||||
$this->registerLightDeserializers();
|
||||
$this->registerMobHeadDeserializers();
|
||||
$this->registerCopperDeserializers();
|
||||
$this->registerSimpleDeserializers();
|
||||
$this->registerDeserializers();
|
||||
}
|
||||
@ -94,19 +98,37 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
public function deserialize(BlockStateData $stateData) : int{
|
||||
if(count($stateData->getStates()) === 0){
|
||||
//if a block has zero properties, we can keep a map of string ID -> internal blockstate ID
|
||||
return $this->simpleCache[$stateData->getName()] ??= $this->deserializeBlock($stateData)->getStateId();
|
||||
return $this->simpleCache[$stateData->getName()] ??= $this->deserializeToStateId($stateData);
|
||||
}
|
||||
|
||||
//we can't cache blocks that have properties - go ahead and deserialize the slow way
|
||||
return $this->deserializeBlock($stateData)->getStateId();
|
||||
return $this->deserializeToStateId($stateData);
|
||||
}
|
||||
|
||||
private function deserializeToStateId(BlockStateData $stateData) : int{
|
||||
$stateId = $this->deserializeBlock($stateData)->getStateId();
|
||||
//plugin devs seem to keep missing this and causing core crashes, so we need to verify this at the earliest
|
||||
//available opportunity
|
||||
if(!RuntimeBlockStateRegistry::getInstance()->hasStateId($stateId)){
|
||||
throw new \LogicException("State ID $stateId returned by deserializer for " . $stateData->getName() . " is not registered in RuntimeBlockStateRegistry");
|
||||
}
|
||||
return $stateId;
|
||||
}
|
||||
|
||||
/** @phpstan-param \Closure(Reader) : Block $c */
|
||||
public function map(string $id, \Closure $c) : void{
|
||||
if(array_key_exists($id, $this->deserializeFuncs)){
|
||||
throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\"");
|
||||
}
|
||||
$this->deserializeFuncs[$id] = $c;
|
||||
$this->simpleCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the existing data deserializer for the given ID, or null if none exists.
|
||||
* This may be useful if you need to override a deserializer, but still want to be able to fall back to the original.
|
||||
*
|
||||
* @phpstan-return ?\Closure(Reader) : Block
|
||||
*/
|
||||
public function getDeserializerForId(string $id) : ?\Closure{
|
||||
return $this->deserializeFuncs[$id] ?? null;
|
||||
}
|
||||
|
||||
/** @phpstan-param \Closure() : Block $getBlock */
|
||||
@ -715,6 +737,150 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(Reader) : (CopperMaterial&Block) $deserializer
|
||||
*/
|
||||
private function mapCopper(
|
||||
string $normalId,
|
||||
string $waxedNormalId,
|
||||
string $exposedId,
|
||||
string $waxedExposedId,
|
||||
string $weatheredId,
|
||||
string $waxedWeatheredId,
|
||||
string $oxidizedId,
|
||||
string $waxedOxidizedId,
|
||||
\Closure $deserializer
|
||||
) : void{
|
||||
foreach(Utils::stringifyKeys([
|
||||
$normalId => [CopperOxidation::NONE, false],
|
||||
$waxedNormalId => [CopperOxidation::NONE, true],
|
||||
$exposedId => [CopperOxidation::EXPOSED, false],
|
||||
$waxedExposedId => [CopperOxidation::EXPOSED, true],
|
||||
$weatheredId => [CopperOxidation::WEATHERED, false],
|
||||
$waxedWeatheredId => [CopperOxidation::WEATHERED, true],
|
||||
$oxidizedId => [CopperOxidation::OXIDIZED, false],
|
||||
$waxedOxidizedId => [CopperOxidation::OXIDIZED, true],
|
||||
]) as $id => [$oxidation, $waxed]){
|
||||
$this->map($id, fn(Reader $in) => $deserializer($in)->setOxidation($oxidation)->setWaxed($waxed));
|
||||
}
|
||||
}
|
||||
|
||||
private function registerCopperDeserializers() : void{
|
||||
$this->mapCopper(
|
||||
Ids::CUT_COPPER_SLAB,
|
||||
Ids::WAXED_CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB,
|
||||
fn(Reader $in) => Helper::decodeSingleSlab(Blocks::CUT_COPPER_SLAB(), $in)
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB,
|
||||
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB,
|
||||
fn(Reader $in) => Helper::decodeDoubleSlab(Blocks::CUT_COPPER_SLAB(), $in)
|
||||
);
|
||||
|
||||
$this->mapCopper(
|
||||
Ids::COPPER_BULB,
|
||||
Ids::WAXED_COPPER_BULB,
|
||||
Ids::EXPOSED_COPPER_BULB,
|
||||
Ids::WAXED_EXPOSED_COPPER_BULB,
|
||||
Ids::WEATHERED_COPPER_BULB,
|
||||
Ids::WAXED_WEATHERED_COPPER_BULB,
|
||||
Ids::OXIDIZED_COPPER_BULB,
|
||||
Ids::WAXED_OXIDIZED_COPPER_BULB,
|
||||
fn(Reader $in) => Blocks::COPPER_BULB()
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT))
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::COPPER_DOOR,
|
||||
Ids::WAXED_COPPER_DOOR,
|
||||
Ids::EXPOSED_COPPER_DOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_DOOR,
|
||||
Ids::WEATHERED_COPPER_DOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_DOOR,
|
||||
Ids::OXIDIZED_COPPER_DOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_DOOR,
|
||||
fn(Reader $in) => Helper::decodeDoor(Blocks::COPPER_DOOR(), $in)
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::COPPER_TRAPDOOR,
|
||||
Ids::WAXED_COPPER_TRAPDOOR,
|
||||
Ids::EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
|
||||
Ids::WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
|
||||
Ids::OXIDIZED_COPPER_TRAPDOOR,
|
||||
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR,
|
||||
fn(Reader $in) => Helper::decodeTrapdoor(Blocks::COPPER_TRAPDOOR(), $in)
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::COPPER_BLOCK,
|
||||
Ids::WAXED_COPPER,
|
||||
Ids::EXPOSED_COPPER,
|
||||
Ids::WAXED_EXPOSED_COPPER,
|
||||
Ids::WEATHERED_COPPER,
|
||||
Ids::WAXED_WEATHERED_COPPER,
|
||||
Ids::OXIDIZED_COPPER,
|
||||
Ids::WAXED_OXIDIZED_COPPER,
|
||||
fn(Reader $in) => Blocks::COPPER()
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::CHISELED_COPPER,
|
||||
Ids::WAXED_CHISELED_COPPER,
|
||||
Ids::EXPOSED_CHISELED_COPPER,
|
||||
Ids::WAXED_EXPOSED_CHISELED_COPPER,
|
||||
Ids::WEATHERED_CHISELED_COPPER,
|
||||
Ids::WAXED_WEATHERED_CHISELED_COPPER,
|
||||
Ids::OXIDIZED_CHISELED_COPPER,
|
||||
Ids::WAXED_OXIDIZED_CHISELED_COPPER,
|
||||
fn(Reader $in) => Blocks::CHISELED_COPPER()
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::COPPER_GRATE,
|
||||
Ids::WAXED_COPPER_GRATE,
|
||||
Ids::EXPOSED_COPPER_GRATE,
|
||||
Ids::WAXED_EXPOSED_COPPER_GRATE,
|
||||
Ids::WEATHERED_COPPER_GRATE,
|
||||
Ids::WAXED_WEATHERED_COPPER_GRATE,
|
||||
Ids::OXIDIZED_COPPER_GRATE,
|
||||
Ids::WAXED_OXIDIZED_COPPER_GRATE,
|
||||
fn(Reader $in) => Blocks::COPPER_GRATE()
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::CUT_COPPER,
|
||||
Ids::WAXED_CUT_COPPER,
|
||||
Ids::EXPOSED_CUT_COPPER,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER,
|
||||
Ids::WEATHERED_CUT_COPPER,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER,
|
||||
Ids::OXIDIZED_CUT_COPPER,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER,
|
||||
fn(Reader $in) => Blocks::CUT_COPPER()
|
||||
);
|
||||
$this->mapCopper(
|
||||
Ids::CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_CUT_COPPER_STAIRS,
|
||||
Ids::EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
|
||||
Ids::WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
|
||||
Ids::OXIDIZED_CUT_COPPER_STAIRS,
|
||||
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS,
|
||||
fn(Reader $in) => Helper::decodeStairs(Blocks::CUT_COPPER_STAIRS(), $in)
|
||||
);
|
||||
}
|
||||
|
||||
private function registerSimpleDeserializers() : void{
|
||||
$this->mapSimple(Ids::AIR, fn() => Blocks::AIR());
|
||||
$this->mapSimple(Ids::AMETHYST_BLOCK, fn() => Blocks::AMETHYST());
|
||||
@ -1220,18 +1386,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
$this->map(Ids::COMPOUND_CREATOR, fn(Reader $in) => Blocks::COMPOUND_CREATOR()
|
||||
->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
|
||||
);
|
||||
$this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE));
|
||||
$this->map(Ids::COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
|
||||
$this->map(Ids::COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
|
||||
$this->map(Ids::COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
|
||||
$this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
|
||||
$this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
|
||||
$this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
|
||||
$this->mapSlab(Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_RED_SANDSTONE_SLAB());
|
||||
$this->mapSlab(Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_SANDSTONE_SLAB());
|
||||
$this->mapSlab(Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB, fn() => Blocks::DARK_PRISMARINE_SLAB());
|
||||
@ -1286,19 +1440,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
return Blocks::ENDER_CHEST()
|
||||
->setFacing($in->readCardinalHorizontalFacing());
|
||||
});
|
||||
$this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
|
||||
$this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
|
||||
$this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
|
||||
$this->map(Ids::FARMLAND, function(Reader $in) : Block{
|
||||
return Blocks::FARMLAND()
|
||||
->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7));
|
||||
@ -1451,19 +1592,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
$this->mapSlab(Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB, fn() => Blocks::STONE_SLAB());
|
||||
$this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS());
|
||||
$this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis()));
|
||||
$this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
|
||||
$this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
|
||||
$this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
|
||||
$this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis()));
|
||||
$this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB());
|
||||
$this->map(Ids::PINK_PETALS, function(Reader $in) : Block{
|
||||
@ -1589,6 +1717,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
$this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS());
|
||||
$this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in));
|
||||
$this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags()));
|
||||
$this->map(Ids::RESPAWN_ANCHOR, function(Reader $in) : Block{
|
||||
return Blocks::RESPAWN_ANCHOR()
|
||||
->setCharges($in->readBoundedInt(StateNames::RESPAWN_ANCHOR_CHARGE, 0, 4));
|
||||
});
|
||||
$this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB());
|
||||
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
|
||||
$this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in));
|
||||
@ -1736,71 +1868,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
->setFacing($in->readHorizontalFacing());
|
||||
});
|
||||
$this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in));
|
||||
$this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE));
|
||||
$this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE));
|
||||
$this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
|
||||
$this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
|
||||
$this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
|
||||
$this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
|
||||
$this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
|
||||
$this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
|
||||
$this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
|
||||
$this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
|
||||
$this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
|
||||
$this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
|
||||
$this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
|
||||
$this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
|
||||
$this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
|
||||
$this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
|
||||
$this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
|
||||
$this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
|
||||
$this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
|
||||
$this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
|
||||
$this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
|
||||
$this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
|
||||
$this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
|
||||
$this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
|
||||
$this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{
|
||||
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
|
||||
->setLit($in->readBool(StateNames::LIT))
|
||||
->setPowered($in->readBool(StateNames::POWERED_BIT));
|
||||
});
|
||||
$this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
|
||||
$this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
|
||||
|
||||
$this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{
|
||||
return Blocks::WEEPING_VINES()
|
||||
->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25));
|
||||
|
@ -51,12 +51,19 @@ final class ItemDeserializer{
|
||||
* @phpstan-param \Closure(Data) : Item $deserializer
|
||||
*/
|
||||
public function map(string $id, \Closure $deserializer) : void{
|
||||
if(isset($this->deserializers[$id])){
|
||||
throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\"");
|
||||
}
|
||||
$this->deserializers[$id] = $deserializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the existing data deserializer for the given ID, or null if none exists.
|
||||
* This may be useful if you need to override a deserializer, but still want to be able to fall back to the original.
|
||||
*
|
||||
* @phpstan-return ?\Closure(Data) : Item
|
||||
*/
|
||||
public function getDeserializerForId(string $id) : ?\Closure{
|
||||
return $this->deserializers[$id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \Closure(Data) : Block $deserializer
|
||||
*/
|
||||
|
@ -68,6 +68,7 @@ final class ItemTypeNames{
|
||||
public const BIRCH_SIGN = "minecraft:birch_sign";
|
||||
public const BLACK_BUNDLE = "minecraft:black_bundle";
|
||||
public const BLACK_DYE = "minecraft:black_dye";
|
||||
public const BLACK_HARNESS = "minecraft:black_harness";
|
||||
public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd";
|
||||
public const BLAZE_POWDER = "minecraft:blaze_powder";
|
||||
public const BLAZE_ROD = "minecraft:blaze_rod";
|
||||
@ -75,6 +76,8 @@ final class ItemTypeNames{
|
||||
public const BLEACH = "minecraft:bleach";
|
||||
public const BLUE_BUNDLE = "minecraft:blue_bundle";
|
||||
public const BLUE_DYE = "minecraft:blue_dye";
|
||||
public const BLUE_EGG = "minecraft:blue_egg";
|
||||
public const BLUE_HARNESS = "minecraft:blue_harness";
|
||||
public const BOARD = "minecraft:board";
|
||||
public const BOAT = "minecraft:boat";
|
||||
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
|
||||
@ -93,6 +96,8 @@ final class ItemTypeNames{
|
||||
public const BRICK = "minecraft:brick";
|
||||
public const BROWN_BUNDLE = "minecraft:brown_bundle";
|
||||
public const BROWN_DYE = "minecraft:brown_dye";
|
||||
public const BROWN_EGG = "minecraft:brown_egg";
|
||||
public const BROWN_HARNESS = "minecraft:brown_harness";
|
||||
public const BRUSH = "minecraft:brush";
|
||||
public const BUCKET = "minecraft:bucket";
|
||||
public const BUNDLE = "minecraft:bundle";
|
||||
@ -164,6 +169,7 @@ final class ItemTypeNames{
|
||||
public const CROSSBOW = "minecraft:crossbow";
|
||||
public const CYAN_BUNDLE = "minecraft:cyan_bundle";
|
||||
public const CYAN_DYE = "minecraft:cyan_dye";
|
||||
public const CYAN_HARNESS = "minecraft:cyan_harness";
|
||||
public const DANGER_POTTERY_SHERD = "minecraft:danger_pottery_sherd";
|
||||
public const DARK_OAK_BOAT = "minecraft:dark_oak_boat";
|
||||
public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat";
|
||||
@ -263,12 +269,15 @@ final class ItemTypeNames{
|
||||
public const GOLDEN_SWORD = "minecraft:golden_sword";
|
||||
public const GRAY_BUNDLE = "minecraft:gray_bundle";
|
||||
public const GRAY_DYE = "minecraft:gray_dye";
|
||||
public const GRAY_HARNESS = "minecraft:gray_harness";
|
||||
public const GREEN_BUNDLE = "minecraft:green_bundle";
|
||||
public const GREEN_DYE = "minecraft:green_dye";
|
||||
public const GREEN_HARNESS = "minecraft:green_harness";
|
||||
public const GUARDIAN_SPAWN_EGG = "minecraft:guardian_spawn_egg";
|
||||
public const GUNPOWDER = "minecraft:gunpowder";
|
||||
public const GUSTER_BANNER_PATTERN = "minecraft:guster_banner_pattern";
|
||||
public const GUSTER_POTTERY_SHERD = "minecraft:guster_pottery_sherd";
|
||||
public const HAPPY_GHAST_SPAWN_EGG = "minecraft:happy_ghast_spawn_egg";
|
||||
public const HARD_STAINED_GLASS = "minecraft:hard_stained_glass";
|
||||
public const HARD_STAINED_GLASS_PANE = "minecraft:hard_stained_glass_pane";
|
||||
public const HEART_OF_THE_SEA = "minecraft:heart_of_the_sea";
|
||||
@ -319,10 +328,13 @@ final class ItemTypeNames{
|
||||
public const LIGHT_BLOCK = "minecraft:light_block";
|
||||
public const LIGHT_BLUE_BUNDLE = "minecraft:light_blue_bundle";
|
||||
public const LIGHT_BLUE_DYE = "minecraft:light_blue_dye";
|
||||
public const LIGHT_BLUE_HARNESS = "minecraft:light_blue_harness";
|
||||
public const LIGHT_GRAY_BUNDLE = "minecraft:light_gray_bundle";
|
||||
public const LIGHT_GRAY_DYE = "minecraft:light_gray_dye";
|
||||
public const LIGHT_GRAY_HARNESS = "minecraft:light_gray_harness";
|
||||
public const LIME_BUNDLE = "minecraft:lime_bundle";
|
||||
public const LIME_DYE = "minecraft:lime_dye";
|
||||
public const LIME_HARNESS = "minecraft:lime_harness";
|
||||
public const LINGERING_POTION = "minecraft:lingering_potion";
|
||||
public const LLAMA_SPAWN_EGG = "minecraft:llama_spawn_egg";
|
||||
public const LODESTONE_COMPASS = "minecraft:lodestone_compass";
|
||||
@ -331,6 +343,7 @@ final class ItemTypeNames{
|
||||
public const MACE = "minecraft:mace";
|
||||
public const MAGENTA_BUNDLE = "minecraft:magenta_bundle";
|
||||
public const MAGENTA_DYE = "minecraft:magenta_dye";
|
||||
public const MAGENTA_HARNESS = "minecraft:magenta_harness";
|
||||
public const MAGMA_CREAM = "minecraft:magma_cream";
|
||||
public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg";
|
||||
public const MANGROVE_BOAT = "minecraft:mangrove_boat";
|
||||
@ -398,6 +411,7 @@ final class ItemTypeNames{
|
||||
public const OMINOUS_TRIAL_KEY = "minecraft:ominous_trial_key";
|
||||
public const ORANGE_BUNDLE = "minecraft:orange_bundle";
|
||||
public const ORANGE_DYE = "minecraft:orange_dye";
|
||||
public const ORANGE_HARNESS = "minecraft:orange_harness";
|
||||
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
|
||||
public const PAINTING = "minecraft:painting";
|
||||
public const PALE_OAK_BOAT = "minecraft:pale_oak_boat";
|
||||
@ -417,6 +431,7 @@ final class ItemTypeNames{
|
||||
public const PILLAGER_SPAWN_EGG = "minecraft:pillager_spawn_egg";
|
||||
public const PINK_BUNDLE = "minecraft:pink_bundle";
|
||||
public const PINK_DYE = "minecraft:pink_dye";
|
||||
public const PINK_HARNESS = "minecraft:pink_harness";
|
||||
public const PITCHER_POD = "minecraft:pitcher_pod";
|
||||
public const PLANKS = "minecraft:planks";
|
||||
public const PLENTY_POTTERY_SHERD = "minecraft:plenty_pottery_sherd";
|
||||
@ -437,6 +452,7 @@ final class ItemTypeNames{
|
||||
public const PUMPKIN_SEEDS = "minecraft:pumpkin_seeds";
|
||||
public const PURPLE_BUNDLE = "minecraft:purple_bundle";
|
||||
public const PURPLE_DYE = "minecraft:purple_dye";
|
||||
public const PURPLE_HARNESS = "minecraft:purple_harness";
|
||||
public const QUARTZ = "minecraft:quartz";
|
||||
public const RABBIT = "minecraft:rabbit";
|
||||
public const RABBIT_FOOT = "minecraft:rabbit_foot";
|
||||
@ -453,6 +469,7 @@ final class ItemTypeNames{
|
||||
public const RED_BUNDLE = "minecraft:red_bundle";
|
||||
public const RED_DYE = "minecraft:red_dye";
|
||||
public const RED_FLOWER = "minecraft:red_flower";
|
||||
public const RED_HARNESS = "minecraft:red_harness";
|
||||
public const REDSTONE = "minecraft:redstone";
|
||||
public const REPEATER = "minecraft:repeater";
|
||||
public const RESIN_BRICK = "minecraft:resin_brick";
|
||||
@ -561,6 +578,7 @@ final class ItemTypeNames{
|
||||
public const WHEAT_SEEDS = "minecraft:wheat_seeds";
|
||||
public const WHITE_BUNDLE = "minecraft:white_bundle";
|
||||
public const WHITE_DYE = "minecraft:white_dye";
|
||||
public const WHITE_HARNESS = "minecraft:white_harness";
|
||||
public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template";
|
||||
public const WIND_CHARGE = "minecraft:wind_charge";
|
||||
public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg";
|
||||
@ -581,6 +599,7 @@ final class ItemTypeNames{
|
||||
public const WRITTEN_BOOK = "minecraft:written_book";
|
||||
public const YELLOW_BUNDLE = "minecraft:yellow_bundle";
|
||||
public const YELLOW_DYE = "minecraft:yellow_dye";
|
||||
public const YELLOW_HARNESS = "minecraft:yellow_harness";
|
||||
public const ZOGLIN_SPAWN_EGG = "minecraft:zoglin_spawn_egg";
|
||||
public const ZOMBIE_HORSE_SPAWN_EGG = "minecraft:zombie_horse_spawn_egg";
|
||||
public const ZOMBIE_PIGMAN_SPAWN_EGG = "minecraft:zombie_pigman_spawn_egg";
|
||||
|
@ -31,6 +31,7 @@ use pocketmine\block\Water;
|
||||
use pocketmine\entity\animation\Animation;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityDespawnEvent;
|
||||
use pocketmine\event\entity\EntityExtinguishEvent;
|
||||
use pocketmine\event\entity\EntityMotionEvent;
|
||||
use pocketmine\event\entity\EntityRegainHealthEvent;
|
||||
use pocketmine\event\entity\EntitySpawnEvent;
|
||||
@ -60,6 +61,7 @@ use pocketmine\player\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -76,6 +78,7 @@ use function floatval;
|
||||
use function floor;
|
||||
use function fmod;
|
||||
use function get_class;
|
||||
use function min;
|
||||
use function sin;
|
||||
use function spl_object_id;
|
||||
use const M_PI_2;
|
||||
@ -700,16 +703,26 @@ abstract class Entity{
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setFireTicks(int $fireTicks) : void{
|
||||
if($fireTicks < 0 || $fireTicks > 0x7fff){
|
||||
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
|
||||
if($fireTicks < 0){
|
||||
throw new \InvalidArgumentException("Fire ticks cannot be negative");
|
||||
}
|
||||
|
||||
//Since the max value is not externally obvious or intuitive, many plugins use this without being aware that
|
||||
//reasonably large values are not accepted. We even have such usages within PM itself. It doesn't make sense
|
||||
//to force all those calls to be aware of this limitation, as it's not a functional limit but a limitation of
|
||||
//the Mojang save format. Truncating this to the max acceptable value is the next best thing we can do.
|
||||
$fireTicks = min($fireTicks, Limits::INT16_MAX);
|
||||
|
||||
if(!$this->isFireProof()){
|
||||
$this->fireTicks = $fireTicks;
|
||||
$this->networkPropertiesDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function extinguish() : void{
|
||||
public function extinguish(int $cause = EntityExtinguishEvent::CAUSE_CUSTOM) : void{
|
||||
$ev = new EntityExtinguishEvent($this, $cause);
|
||||
$ev->call();
|
||||
|
||||
$this->fireTicks = 0;
|
||||
$this->networkPropertiesDirty = true;
|
||||
}
|
||||
@ -720,7 +733,7 @@ abstract class Entity{
|
||||
|
||||
protected function doOnFireTick(int $tickDiff = 1) : bool{
|
||||
if($this->isFireProof() && $this->isOnFire()){
|
||||
$this->extinguish();
|
||||
$this->extinguish(EntityExtinguishEvent::CAUSE_FIRE_PROOF);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -731,7 +744,7 @@ abstract class Entity{
|
||||
}
|
||||
|
||||
if(!$this->isOnFire()){
|
||||
$this->extinguish();
|
||||
$this->extinguish(EntityExtinguishEvent::CAUSE_TICKING);
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
@ -1178,12 +1191,14 @@ abstract class Entity{
|
||||
|
||||
$moveBB->offset(0, 0, $dz);
|
||||
|
||||
if($this->stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){
|
||||
$stepHeight = $this->getStepHeight();
|
||||
|
||||
if($stepHeight > 0 && $fallingFlag && ($wantedX !== $dx || $wantedZ !== $dz)){
|
||||
$cx = $dx;
|
||||
$cy = $dy;
|
||||
$cz = $dz;
|
||||
$dx = $wantedX;
|
||||
$dy = $this->stepHeight;
|
||||
$dy = $stepHeight;
|
||||
$dz = $wantedZ;
|
||||
|
||||
$stepBB = clone $this->boundingBox;
|
||||
@ -1253,6 +1268,14 @@ abstract class Entity{
|
||||
Timings::$entityMove->stopTiming();
|
||||
}
|
||||
|
||||
public function setStepHeight(float $stepHeight) : void{
|
||||
$this->stepHeight = $stepHeight;
|
||||
}
|
||||
|
||||
public function getStepHeight() : float{
|
||||
return $this->stepHeight;
|
||||
}
|
||||
|
||||
protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{
|
||||
$this->isCollidedVertically = $wantedY !== $dy;
|
||||
$this->isCollidedHorizontally = ($wantedX !== $dx || $wantedZ !== $dz);
|
||||
@ -1495,7 +1518,7 @@ abstract class Entity{
|
||||
$this->getId(), //TODO: actor unique ID
|
||||
$this->getId(),
|
||||
static::getNetworkTypeId(),
|
||||
$this->location->asVector3(),
|
||||
$this->getOffsetPosition($this->location->asVector3()),
|
||||
$this->getMotion(),
|
||||
$this->location->pitch,
|
||||
$this->location->yaw,
|
||||
|
@ -219,6 +219,13 @@ final class EntityFactory{
|
||||
$this->saveNames[$className] = reset($saveNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param class-string<Entity> $class
|
||||
*/
|
||||
public function isRegistered(string $class) : bool{
|
||||
return isset($this->saveNames[$class]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity from data stored on a chunk.
|
||||
*
|
||||
|
@ -38,6 +38,7 @@ use pocketmine\event\entity\EntityDamageByChildEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityDeathEvent;
|
||||
use pocketmine\event\entity\EntityFrostWalkerEvent;
|
||||
use pocketmine\inventory\ArmorInventory;
|
||||
use pocketmine\inventory\CallbackInventoryListener;
|
||||
use pocketmine\inventory\Inventory;
|
||||
@ -721,19 +722,30 @@ abstract class Living extends Entity{
|
||||
$y = $this->location->getFloorY() - 1;
|
||||
$baseZ = $this->location->getFloorZ();
|
||||
|
||||
$frostedIce = VanillaBlocks::FROSTED_ICE();
|
||||
$liquid = VanillaBlocks::WATER();
|
||||
$targetBlock = VanillaBlocks::FROSTED_ICE();
|
||||
if(EntityFrostWalkerEvent::hasHandlers()){
|
||||
$ev = new EntityFrostWalkerEvent($this, $radius, $liquid, $targetBlock);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return;
|
||||
}
|
||||
$radius = $ev->getRadius();
|
||||
$liquid = $ev->getLiquid();
|
||||
$targetBlock = $ev->getTargetBlock();
|
||||
}
|
||||
|
||||
for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
|
||||
for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
|
||||
$block = $world->getBlockAt($x, $y, $z);
|
||||
if(
|
||||
!$block instanceof Water ||
|
||||
!$block->isSource() ||
|
||||
!$block->isSameState($liquid) ||
|
||||
$world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
|
||||
count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
|
||||
){
|
||||
continue;
|
||||
}
|
||||
$world->setBlockAt($x, $y, $z, $frostedIce);
|
||||
$world->setBlockAt($x, $y, $z, $targetBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ class EndCrystal extends Entity implements Explosive{
|
||||
$ev = new EntityPreExplodeEvent($this, 6);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
|
||||
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this, $ev->getFireChance());
|
||||
if($ev->isBlockBreaking()){
|
||||
$explosion->explodeA();
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
//TODO: deal with underwater TNT (underwater TNT treats water as if it has a blast resistance of 0)
|
||||
$explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this);
|
||||
$explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this, $ev->getFireChance());
|
||||
if($ev->isBlockBreaking()){
|
||||
$explosion->explodeA();
|
||||
}
|
||||
|
122
src/event/block/BlockExplodeEvent.php
Normal file
122
src/event/block/BlockExplodeEvent.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\block;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
/**
|
||||
* Called when a block explodes, after explosion impact has been calculated.
|
||||
*
|
||||
* @see BlockPreExplodeEvent
|
||||
*/
|
||||
class BlockExplodeEvent extends BlockEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Block[] $blocks
|
||||
* @param Block[] $ignitions
|
||||
*/
|
||||
public function __construct(
|
||||
Block $block,
|
||||
private Position $position,
|
||||
private array $blocks,
|
||||
private float $yield,
|
||||
private array $ignitions
|
||||
){
|
||||
parent::__construct($block);
|
||||
|
||||
Utils::checkFloatNotInfOrNaN("yield", $yield);
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||
}
|
||||
}
|
||||
|
||||
public function getPosition() : Position{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage chance of drops from each block destroyed by the explosion.
|
||||
*
|
||||
* @return float 0-100
|
||||
*/
|
||||
public function getYield() : float{
|
||||
return $this->yield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the percentage chance of drops from each block destroyed by the explosion.
|
||||
*
|
||||
* @param float $yield 0-100
|
||||
*/
|
||||
public function setYield(float $yield) : void{
|
||||
Utils::checkFloatNotInfOrNaN("yield", $yield);
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||
}
|
||||
$this->yield = $yield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of blocks destroyed by the explosion.
|
||||
*
|
||||
* @return Block[]
|
||||
*/
|
||||
public function getAffectedBlocks() : array{
|
||||
return $this->blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blocks destroyed by the explosion.
|
||||
*
|
||||
* @param Block[] $blocks
|
||||
*/
|
||||
public function setAffectedBlocks(array $blocks) : void{
|
||||
Utils::validateArrayValueType($blocks, fn(Block $block) => null);
|
||||
$this->blocks = $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of affected blocks that will be replaced by fire.
|
||||
*
|
||||
* @return Block[]
|
||||
*/
|
||||
public function getIgnitions() : array{
|
||||
return $this->ignitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of blocks that will be replaced by fire.
|
||||
*
|
||||
* @param Block[] $ignitions
|
||||
*/
|
||||
public function setIgnitions(array $ignitions) : void{
|
||||
Utils::validateArrayValueType($ignitions, fn(Block $block) => null);
|
||||
$this->ignitions = $ignitions;
|
||||
}
|
||||
}
|
129
src/event/block/BlockPreExplodeEvent.php
Normal file
129
src/event/block/BlockPreExplodeEvent.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\block;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\Explosion;
|
||||
|
||||
/**
|
||||
* Called when a block wants to explode, before the explosion impact is calculated.
|
||||
* This allows changing the explosion force, fire chance and whether it will destroy blocks.
|
||||
*
|
||||
* @see BlockExplodeEvent
|
||||
*/
|
||||
class BlockPreExplodeEvent extends BlockEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
private bool $blockBreaking = true;
|
||||
|
||||
public function __construct(
|
||||
Block $block,
|
||||
private float $radius,
|
||||
private readonly ?Player $player = null,
|
||||
private float $fireChance = 0.0
|
||||
){
|
||||
Utils::checkFloatNotInfOrNaN("radius", $radius);
|
||||
if($radius <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||
}
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||
}
|
||||
parent::__construct($block);
|
||||
}
|
||||
|
||||
public function getRadius() : float{
|
||||
return $this->radius;
|
||||
}
|
||||
|
||||
public function setRadius(float $radius) : void{
|
||||
Utils::checkFloatNotInfOrNaN("radius", $radius);
|
||||
if($radius <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||
}
|
||||
$this->radius = $radius;
|
||||
}
|
||||
|
||||
public function isBlockBreaking() : bool{
|
||||
return $this->blockBreaking;
|
||||
}
|
||||
|
||||
public function setBlockBreaking(bool $affectsBlocks) : void{
|
||||
$this->blockBreaking = $affectsBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the explosion will create a fire.
|
||||
*/
|
||||
public function isIncendiary() : bool{
|
||||
return $this->fireChance > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the explosion will create a fire by filling fireChance with default values.
|
||||
*
|
||||
* If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0).
|
||||
*/
|
||||
public function setIncendiary(bool $incendiary) : void{
|
||||
if(!$incendiary){
|
||||
$this->fireChance = 0;
|
||||
}elseif($this->fireChance <= 0){
|
||||
$this->fireChance = Explosion::DEFAULT_FIRE_CHANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a chance between 0 and 1 of creating a fire.
|
||||
*/
|
||||
public function getFireChance() : float{
|
||||
return $this->fireChance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a chance between 0 and 1 of creating a fire.
|
||||
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||
*
|
||||
* @param float $fireChance 0 ... 1
|
||||
*/
|
||||
public function setFireChance(float $fireChance) : void{
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||
}
|
||||
$this->fireChance = $fireChance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the player who triggered the block explosion.
|
||||
* Returns null if the block was exploded by other means.
|
||||
*/
|
||||
public function getPlayer() : ?Player{
|
||||
return $this->player;
|
||||
}
|
||||
}
|
@ -43,13 +43,15 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
|
||||
/**
|
||||
* @param Block[] $blocks
|
||||
* @param float $yield 0-100
|
||||
* @param float $yield 0-100
|
||||
* @param Block[] $ignitions
|
||||
*/
|
||||
public function __construct(
|
||||
Entity $entity,
|
||||
protected Position $position,
|
||||
protected array $blocks,
|
||||
protected float $yield
|
||||
protected float $yield,
|
||||
private array $ignitions
|
||||
){
|
||||
$this->entity = $entity;
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
@ -98,4 +100,23 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
}
|
||||
$this->yield = $yield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of blocks that will be replaced by fire.
|
||||
*
|
||||
* @param Block[] $ignitions
|
||||
*/
|
||||
public function setIgnitions(array $ignitions) : void{
|
||||
Utils::validateArrayValueType($ignitions, fn(Block $block) => null);
|
||||
$this->ignitions = $ignitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of affected blocks that will be replaced by fire.
|
||||
*
|
||||
* @return Block[]
|
||||
*/
|
||||
public function getIgnitions() : array{
|
||||
return $this->ignitions;
|
||||
}
|
||||
}
|
||||
|
53
src/event/entity/EntityExtinguishEvent.php
Normal file
53
src/event/entity/EntityExtinguishEvent.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\entity;
|
||||
|
||||
use pocketmine\entity\Entity;
|
||||
|
||||
/**
|
||||
* Called when an entity on fire gets extinguished.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Entity>
|
||||
*/
|
||||
class EntityExtinguishEvent extends EntityEvent{
|
||||
public const CAUSE_CUSTOM = 0;
|
||||
public const CAUSE_WATER = 1;
|
||||
public const CAUSE_WATER_CAULDRON = 2;
|
||||
public const CAUSE_RESPAWN = 3;
|
||||
public const CAUSE_FIRE_PROOF = 4;
|
||||
public const CAUSE_TICKING = 5;
|
||||
public const CAUSE_RAIN = 6;
|
||||
public const CAUSE_POWDER_SNOW = 7;
|
||||
|
||||
public function __construct(
|
||||
Entity $entity,
|
||||
private int $cause
|
||||
){
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
public function getCause() : int{
|
||||
return $this->cause;
|
||||
}
|
||||
}
|
84
src/event/entity/EntityFrostWalkerEvent.php
Normal file
84
src/event/entity/EntityFrostWalkerEvent.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\entity;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\Liquid;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when an entity moves horizontally while wearing boots enchanted with Frost Walker.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<Living>
|
||||
*/
|
||||
class EntityFrostWalkerEvent extends EntityEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
public function __construct(
|
||||
Living $entity,
|
||||
private int $radius,
|
||||
private Liquid $liquid,
|
||||
private Block $targetBlock
|
||||
){
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
public function getRadius() : int{
|
||||
return $this->radius;
|
||||
}
|
||||
|
||||
public function setRadius(int $radius) : void{
|
||||
$this->radius = $radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the liquid that gets frozen
|
||||
*/
|
||||
public function getLiquid() : Liquid{
|
||||
return $this->liquid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the liquid that gets frozen
|
||||
*/
|
||||
public function setLiquid(Liquid $liquid) : void{
|
||||
$this->liquid = $liquid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block that replaces the liquid
|
||||
*/
|
||||
public function getTargetBlock() : Block{
|
||||
return $this->targetBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block that replaces the liquid
|
||||
*/
|
||||
public function setTargetBlock(Block $targetBlock) : void{
|
||||
$this->targetBlock = $targetBlock;
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ namespace pocketmine\event\entity;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\Explosion;
|
||||
|
||||
/**
|
||||
* Called when an entity decides to explode, before the explosion's impact is calculated.
|
||||
@ -42,11 +44,16 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
||||
|
||||
public function __construct(
|
||||
Entity $entity,
|
||||
protected float $radius
|
||||
protected float $radius,
|
||||
private float $fireChance = 0.0,
|
||||
){
|
||||
if($radius <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||
}
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be between 0 and 1.");
|
||||
}
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
@ -61,6 +68,47 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
||||
$this->radius = $radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the explosion will create a fire.
|
||||
*/
|
||||
public function isIncendiary() : bool{
|
||||
return $this->fireChance > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the explosion will create a fire by filling fireChance with default values.
|
||||
*
|
||||
* If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0).
|
||||
*/
|
||||
public function setIncendiary(bool $incendiary) : void{
|
||||
if(!$incendiary){
|
||||
$this->fireChance = 0;
|
||||
}elseif($this->fireChance <= 0){
|
||||
$this->fireChance = Explosion::DEFAULT_FIRE_CHANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a chance between 0 and 1 of creating a fire.
|
||||
*/
|
||||
public function getFireChance() : float{
|
||||
return $this->fireChance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a chance between 0 and 1 of creating a fire.
|
||||
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||
*
|
||||
* @param float $fireChance 0 ... 1
|
||||
*/
|
||||
public function setFireChance(float $fireChance) : void{
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be between 0 and 1.");
|
||||
}
|
||||
$this->fireChance = $fireChance;
|
||||
}
|
||||
|
||||
public function isBlockBreaking() : bool{
|
||||
return $this->blockBreaking;
|
||||
}
|
||||
|
56
src/event/player/PlayerRespawnAnchorUseEvent.php
Normal file
56
src/event/player/PlayerRespawnAnchorUseEvent.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
class PlayerRespawnAnchorUseEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
public const ACTION_EXPLODE = 0;
|
||||
public const ACTION_SET_SPAWN = 1;
|
||||
|
||||
public function __construct(
|
||||
Player $player,
|
||||
protected Block $block,
|
||||
private int $action = self::ACTION_EXPLODE
|
||||
){
|
||||
$this->player = $player;
|
||||
}
|
||||
|
||||
public function getBlock() : Block{
|
||||
return $this->block;
|
||||
}
|
||||
|
||||
public function getAction() : int{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function setAction(int $action) : void{
|
||||
$this->action = $action;
|
||||
}
|
||||
}
|
@ -144,8 +144,9 @@ class InventoryTransaction{
|
||||
$needItems = [];
|
||||
$haveItems = [];
|
||||
foreach($this->actions as $key => $action){
|
||||
if(!$action->getTargetItem()->isNull()){
|
||||
$needItems[] = $action->getTargetItem();
|
||||
$targetItem = $action->getTargetItem();
|
||||
if(!$targetItem->isNull()){
|
||||
$needItems[] = $targetItem;
|
||||
}
|
||||
|
||||
try{
|
||||
@ -154,8 +155,9 @@ class InventoryTransaction{
|
||||
throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
if(!$action->getSourceItem()->isNull()){
|
||||
$haveItems[] = $action->getSourceItem();
|
||||
$sourceItem = $action->getSourceItem();
|
||||
if(!$sourceItem->isNull()){
|
||||
$haveItems[] = $sourceItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -993,6 +993,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL());
|
||||
$result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS());
|
||||
$result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP());
|
||||
$result->registerBlock("respawn_anchor", fn() => Blocks::RESPAWN_ANCHOR());
|
||||
$result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
|
||||
$result->registerBlock("rose", fn() => Blocks::POPPY());
|
||||
$result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH());
|
||||
|
@ -28,6 +28,7 @@ use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_filter;
|
||||
use function array_values;
|
||||
use function count;
|
||||
@ -129,6 +130,7 @@ final class AvailableEnchantmentRegistry{
|
||||
if(!$this->isRegistered($enchantment)){
|
||||
throw new \LogicException("Cannot set primary item tags for non-registered enchantment");
|
||||
}
|
||||
Utils::validateArrayValueType($tags, fn(string $v) => 1);
|
||||
$this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags);
|
||||
}
|
||||
|
||||
@ -152,6 +154,7 @@ final class AvailableEnchantmentRegistry{
|
||||
if(!$this->isRegistered($enchantment)){
|
||||
throw new \LogicException("Cannot set secondary item tags for non-registered enchantment");
|
||||
}
|
||||
Utils::validateArrayValueType($tags, fn(string $v) => 1);
|
||||
$this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags);
|
||||
}
|
||||
|
||||
|
@ -3035,6 +3035,14 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::TILE_BED_TOOFAR, []);
|
||||
}
|
||||
|
||||
public static function tile_respawn_anchor_notValid() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::TILE_RESPAWN_ANCHOR_NOTVALID, []);
|
||||
}
|
||||
|
||||
public static function tile_respawn_anchor_respawnSet() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::TILE_RESPAWN_ANCHOR_RESPAWNSET, []);
|
||||
}
|
||||
|
||||
public static function view_distance() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::VIEW_DISTANCE, []);
|
||||
}
|
||||
|
@ -658,6 +658,8 @@ final class KnownTranslationKeys{
|
||||
public const TILE_BED_NOSLEEP = "tile.bed.noSleep";
|
||||
public const TILE_BED_OCCUPIED = "tile.bed.occupied";
|
||||
public const TILE_BED_TOOFAR = "tile.bed.tooFar";
|
||||
public const TILE_RESPAWN_ANCHOR_NOTVALID = "tile.respawn_anchor.notValid";
|
||||
public const TILE_RESPAWN_ANCHOR_RESPAWNSET = "tile.respawn_anchor.respawnSet";
|
||||
public const VIEW_DISTANCE = "view_distance";
|
||||
public const WELCOME_TO_POCKETMINE = "welcome_to_pocketmine";
|
||||
public const WHITELIST_ENABLE = "whitelist_enable";
|
||||
|
@ -58,6 +58,7 @@ use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundCloseFormPacket;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\DisconnectPacket;
|
||||
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
|
||||
use pocketmine\network\mcpe\protocol\ModalFormRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket;
|
||||
@ -109,6 +110,7 @@ use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
@ -174,7 +176,7 @@ class NetworkSession{
|
||||
*/
|
||||
private array $sendBufferAckPromises = [];
|
||||
|
||||
/** @phpstan-var \SplQueue<array{CompressBatchPromise|string, list<PromiseResolver<true>>}> */
|
||||
/** @phpstan-var \SplQueue<array{CompressBatchPromise|string, list<PromiseResolver<true>>, bool}> */
|
||||
private \SplQueue $compressedQueue;
|
||||
private bool $forceAsyncCompression = true;
|
||||
private bool $enableCompression = false; //disabled until handshake completed
|
||||
@ -194,6 +196,17 @@ class NetworkSession{
|
||||
*/
|
||||
private ObjectSet $disposeHooks;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<int, string>
|
||||
*/
|
||||
private array $repeatedPacketFilters = [];
|
||||
/**
|
||||
* @var int[]
|
||||
* @phpstan-var array<int, int>
|
||||
*/
|
||||
private array $repeatedPacketFilterStats = [];
|
||||
|
||||
public function __construct(
|
||||
private Server $server,
|
||||
private NetworkSessionManager $manager,
|
||||
@ -221,6 +234,8 @@ class NetworkSession{
|
||||
$this->onSessionStartSuccess(...)
|
||||
));
|
||||
|
||||
$this->addRepeatedPacketFilter(InventoryTransactionPacket::NETWORK_ID);
|
||||
|
||||
$this->manager->add($this);
|
||||
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_open()));
|
||||
}
|
||||
@ -235,7 +250,7 @@ class NetworkSession{
|
||||
|
||||
private function onSessionStartSuccess() : void{
|
||||
$this->logger->debug("Session start handshake completed, awaiting login packet");
|
||||
$this->flushSendBuffer(true);
|
||||
$this->flushGamePacketQueue();
|
||||
$this->enableCompression = true;
|
||||
$this->setHandler(new LoginPacketHandler(
|
||||
$this->server,
|
||||
@ -350,6 +365,44 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
public function addRepeatedPacketFilter(int $packetId) : void{
|
||||
$this->repeatedPacketFilters[$packetId] = "";
|
||||
$this->repeatedPacketFilterStats[$packetId] = 0;
|
||||
}
|
||||
|
||||
public function removeRepeatedPacketFilter(int $packetId) : void{
|
||||
unset($this->repeatedPacketFilters[$packetId]);
|
||||
unset($this->repeatedPacketFilterStats[$packetId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stats for repeated packet filters, indexed by packet ID.
|
||||
* The value is the number of times a packet was dropped due to being repeated.
|
||||
*
|
||||
* @return int[]
|
||||
* @phpstan-return array<int, int>
|
||||
*/
|
||||
public function getRepeatedPacketFilterStats() : array{
|
||||
return $this->repeatedPacketFilterStats;
|
||||
}
|
||||
|
||||
private function checkRepeatedPacketFilter(string $buffer) : bool{
|
||||
//TODO: would be great if we didn't repeat reading the ID inside PacketPool
|
||||
$dummy = 0;
|
||||
$packetId = Binary::readUnsignedVarInt($buffer, $dummy);
|
||||
|
||||
if(isset($this->repeatedPacketFilters[$packetId])){
|
||||
if($buffer === $this->repeatedPacketFilters[$packetId]){
|
||||
$this->repeatedPacketFilterStats[$packetId]++;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->repeatedPacketFilters[$packetId] = $buffer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PacketHandlingException
|
||||
*/
|
||||
@ -403,6 +456,10 @@ class NetworkSession{
|
||||
try{
|
||||
$stream = new BinaryStream($decompressed);
|
||||
foreach(PacketBatch::decodeRaw($stream) as $buffer){
|
||||
if($this->checkRepeatedPacketFilter($buffer)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->gamePacketLimiter->decrement();
|
||||
$packet = $this->packetPool->getPacket($buffer);
|
||||
if($packet === null){
|
||||
@ -529,7 +586,7 @@ class NetworkSession{
|
||||
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
|
||||
}
|
||||
if($immediate){
|
||||
$this->flushSendBuffer(true);
|
||||
$this->flushGamePacketQueue();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -577,14 +634,12 @@ class NetworkSession{
|
||||
$this->sendBuffer[] = $buffer;
|
||||
}
|
||||
|
||||
private function flushSendBuffer(bool $immediate = false) : void{
|
||||
private function flushGamePacketQueue() : void{
|
||||
if(count($this->sendBuffer) > 0){
|
||||
Timings::$playerNetworkSend->startTiming();
|
||||
try{
|
||||
$syncMode = null; //automatic
|
||||
if($immediate){
|
||||
$syncMode = true;
|
||||
}elseif($this->forceAsyncCompression){
|
||||
if($this->forceAsyncCompression){
|
||||
$syncMode = false;
|
||||
}
|
||||
|
||||
@ -599,7 +654,9 @@ class NetworkSession{
|
||||
$this->sendBuffer = [];
|
||||
$ackPromises = $this->sendBufferAckPromises;
|
||||
$this->sendBufferAckPromises = [];
|
||||
$this->queueCompressedNoBufferFlush($batch, $immediate, $ackPromises);
|
||||
//these packets were already potentially buffered for up to 50ms - make sure the transport layer doesn't
|
||||
//delay them any longer
|
||||
$this->queueCompressedNoGamePacketFlush($batch, networkFlush: true, ackPromises: $ackPromises);
|
||||
}finally{
|
||||
Timings::$playerNetworkSend->stopTiming();
|
||||
}
|
||||
@ -619,8 +676,10 @@ class NetworkSession{
|
||||
public function queueCompressed(CompressBatchPromise|string $payload, bool $immediate = false) : void{
|
||||
Timings::$playerNetworkSend->startTiming();
|
||||
try{
|
||||
$this->flushSendBuffer($immediate); //Maintain ordering if possible
|
||||
$this->queueCompressedNoBufferFlush($payload, $immediate);
|
||||
//if the next packet causes a flush, avoid unnecessarily flushing twice
|
||||
//however, if the next packet does *not* cause a flush, game packets should be flushed to avoid delays
|
||||
$this->flushGamePacketQueue();
|
||||
$this->queueCompressedNoGamePacketFlush($payload, $immediate);
|
||||
}finally{
|
||||
Timings::$playerNetworkSend->stopTiming();
|
||||
}
|
||||
@ -631,22 +690,13 @@ class NetworkSession{
|
||||
*
|
||||
* @phpstan-param list<PromiseResolver<true>> $ackPromises
|
||||
*/
|
||||
private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false, array $ackPromises = []) : void{
|
||||
private function queueCompressedNoGamePacketFlush(CompressBatchPromise|string $batch, bool $networkFlush = false, array $ackPromises = []) : void{
|
||||
Timings::$playerNetworkSend->startTiming();
|
||||
try{
|
||||
$this->compressedQueue->enqueue([$batch, $ackPromises, $networkFlush]);
|
||||
if(is_string($batch)){
|
||||
if($immediate){
|
||||
//Skips all queues
|
||||
$this->sendEncoded($batch, true, $ackPromises);
|
||||
}else{
|
||||
$this->compressedQueue->enqueue([$batch, $ackPromises]);
|
||||
$this->flushCompressedQueue();
|
||||
}
|
||||
}elseif($immediate){
|
||||
//Skips all queues
|
||||
$this->sendEncoded($batch->getResult(), true, $ackPromises);
|
||||
$this->flushCompressedQueue();
|
||||
}else{
|
||||
$this->compressedQueue->enqueue([$batch, $ackPromises]);
|
||||
$batch->onResolve(function() : void{
|
||||
if($this->connected){
|
||||
$this->flushCompressedQueue();
|
||||
@ -663,14 +713,14 @@ class NetworkSession{
|
||||
try{
|
||||
while(!$this->compressedQueue->isEmpty()){
|
||||
/** @var CompressBatchPromise|string $current */
|
||||
[$current, $ackPromises] = $this->compressedQueue->bottom();
|
||||
[$current, $ackPromises, $networkFlush] = $this->compressedQueue->bottom();
|
||||
if(is_string($current)){
|
||||
$this->compressedQueue->dequeue();
|
||||
$this->sendEncoded($current, false, $ackPromises);
|
||||
$this->sendEncoded($current, $networkFlush, $ackPromises);
|
||||
|
||||
}elseif($current->hasResult()){
|
||||
$this->compressedQueue->dequeue();
|
||||
$this->sendEncoded($current->getResult(), false, $ackPromises);
|
||||
$this->sendEncoded($current->getResult(), $networkFlush, $ackPromises);
|
||||
|
||||
}else{
|
||||
//can't send any more queued until this one is ready
|
||||
@ -710,7 +760,7 @@ class NetworkSession{
|
||||
$this->disconnectGuard = true;
|
||||
$func();
|
||||
$this->disconnectGuard = false;
|
||||
$this->flushSendBuffer(true);
|
||||
$this->flushGamePacketQueue();
|
||||
$this->sender->close("");
|
||||
foreach($this->disposeHooks as $callback){
|
||||
$callback();
|
||||
@ -1345,6 +1395,6 @@ class NetworkSession{
|
||||
Timings::$playerNetworkSendInventorySync->stopTiming();
|
||||
}
|
||||
|
||||
$this->flushSendBuffer();
|
||||
$this->flushGamePacketQueue();
|
||||
}
|
||||
}
|
||||
|
8
src/network/mcpe/cache/ChunkCache.php
vendored
8
src/network/mcpe/cache/ChunkCache.php
vendored
@ -88,9 +88,13 @@ class ChunkCache implements ChunkListener{
|
||||
private int $hits = 0;
|
||||
private int $misses = 0;
|
||||
|
||||
/**
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
*/
|
||||
private function __construct(
|
||||
private World $world,
|
||||
private Compressor $compressor
|
||||
private Compressor $compressor,
|
||||
private int $dimensionId = DimensionIds::OVERWORLD
|
||||
){}
|
||||
|
||||
private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{
|
||||
@ -109,7 +113,7 @@ class ChunkCache implements ChunkListener{
|
||||
new ChunkRequestTask(
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
DimensionIds::OVERWORLD, //TODO: not hardcode this
|
||||
$this->dimensionId,
|
||||
$chunk,
|
||||
$promise,
|
||||
$this->compressor
|
||||
|
63
src/network/mcpe/cache/StaticPacketCache.php
vendored
63
src/network/mcpe/cache/StaticPacketCache.php
vendored
@ -23,13 +23,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\cache;
|
||||
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\data\bedrock\BedrockDataFiles;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
|
||||
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\biome\BiomeDefinitionEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\biome\model\BiomeDefinitionEntryData;
|
||||
use function count;
|
||||
use function get_debug_type;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
|
||||
class StaticPacketCache{
|
||||
use SingletonTrait;
|
||||
@ -41,9 +50,61 @@ class StaticPacketCache{
|
||||
return new CacheableNbt((new NetworkNbtSerializer())->read(Filesystem::fileGetContents($filePath))->mustGetCompoundTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<BiomeDefinitionEntry>
|
||||
*/
|
||||
private static function loadBiomeDefinitionModel(string $filePath) : array{
|
||||
$biomeEntries = json_decode(Filesystem::fileGetContents($filePath), associative: true);
|
||||
if(!is_array($biomeEntries)){
|
||||
throw new SavedDataLoadingException("$filePath root should be an array, got " . get_debug_type($biomeEntries));
|
||||
}
|
||||
|
||||
$jsonMapper = new \JsonMapper();
|
||||
$jsonMapper->bExceptionOnMissingData = true;
|
||||
$jsonMapper->bStrictObjectTypeChecking = true;
|
||||
$jsonMapper->bEnforceMapType = false;
|
||||
|
||||
$entries = [];
|
||||
foreach(Utils::promoteKeys($biomeEntries) as $biomeName => $entry){
|
||||
if(!is_array($entry)){
|
||||
throw new SavedDataLoadingException("$filePath should be an array of objects, got " . get_debug_type($entry));
|
||||
}
|
||||
|
||||
try{
|
||||
$biomeDefinition = $jsonMapper->map($entry, new BiomeDefinitionEntryData());
|
||||
|
||||
$mapWaterColour = $biomeDefinition->mapWaterColour;
|
||||
$entries[] = new BiomeDefinitionEntry(
|
||||
(string) $biomeName,
|
||||
$biomeDefinition->id,
|
||||
$biomeDefinition->temperature,
|
||||
$biomeDefinition->downfall,
|
||||
$biomeDefinition->redSporeDensity,
|
||||
$biomeDefinition->blueSporeDensity,
|
||||
$biomeDefinition->ashDensity,
|
||||
$biomeDefinition->whiteAshDensity,
|
||||
$biomeDefinition->depth,
|
||||
$biomeDefinition->scale,
|
||||
new Color(
|
||||
$mapWaterColour->r,
|
||||
$mapWaterColour->g,
|
||||
$mapWaterColour->b,
|
||||
$mapWaterColour->a
|
||||
),
|
||||
$biomeDefinition->rain,
|
||||
count($biomeDefinition->tags) > 0 ? $biomeDefinition->tags : null,
|
||||
);
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw new \RuntimeException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private static function make() : self{
|
||||
return new self(
|
||||
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(BedrockDataFiles::BIOME_DEFINITIONS_NBT)),
|
||||
BiomeDefinitionListPacket::fromDefinitions(self::loadBiomeDefinitionModel(BedrockDataFiles::BIOME_DEFINITIONS_JSON)),
|
||||
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(BedrockDataFiles::ENTITY_IDENTIFIERS_NBT))
|
||||
);
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
|
||||
use pocketmine\network\mcpe\protocol\LabTablePacket;
|
||||
use pocketmine\network\mcpe\protocol\LecternUpdatePacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
|
||||
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
|
||||
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
|
||||
@ -74,7 +73,6 @@ use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\BitSet;
|
||||
@ -118,7 +116,6 @@ use function is_nan;
|
||||
use function json_decode;
|
||||
use function max;
|
||||
use function mb_strlen;
|
||||
use function microtime;
|
||||
use function sprintf;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
@ -130,9 +127,6 @@ use const JSON_THROW_ON_ERROR;
|
||||
class InGamePacketHandler extends PacketHandler{
|
||||
private const MAX_FORM_RESPONSE_DEPTH = 2; //modal/simple will be 1, custom forms 2 - they will never contain anything other than string|int|float|bool|null
|
||||
|
||||
protected float $lastRightClickTime = 0.0;
|
||||
protected ?UseItemTransactionData $lastRightClickData = null;
|
||||
|
||||
protected ?Vector3 $lastPlayerAuthInputPosition = null;
|
||||
protected ?float $lastPlayerAuthInputYaw = null;
|
||||
protected ?float $lastPlayerAuthInputPitch = null;
|
||||
@ -296,10 +290,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return $packetHandled;
|
||||
}
|
||||
|
||||
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
|
||||
return true; //useless leftover from 1.8
|
||||
}
|
||||
|
||||
public function handleActorEvent(ActorEventPacket $packet) : bool{
|
||||
if($packet->actorRuntimeId !== $this->player->getId()){
|
||||
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
|
||||
@ -477,27 +467,11 @@ class InGamePacketHandler extends PacketHandler{
|
||||
|
||||
switch($data->getActionType()){
|
||||
case UseItemTransactionData::ACTION_CLICK_BLOCK:
|
||||
//TODO: start hack for client spam bug
|
||||
$clickPos = $data->getClickPosition();
|
||||
$spamBug = ($this->lastRightClickData !== null &&
|
||||
microtime(true) - $this->lastRightClickTime < 0.1 && //100ms
|
||||
$this->lastRightClickData->getPlayerPosition()->distanceSquared($data->getPlayerPosition()) < 0.00001 &&
|
||||
$this->lastRightClickData->getBlockPosition()->equals($data->getBlockPosition()) &&
|
||||
$this->lastRightClickData->getClickPosition()->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
|
||||
);
|
||||
//get rid of continued spam if the player clicks and holds right-click
|
||||
$this->lastRightClickData = $data;
|
||||
$this->lastRightClickTime = microtime(true);
|
||||
if($spamBug){
|
||||
return true;
|
||||
}
|
||||
//TODO: end hack for client spam bug
|
||||
|
||||
self::validateFacing($data->getFace());
|
||||
|
||||
$blockPos = $data->getBlockPosition();
|
||||
$vBlockPos = new Vector3($blockPos->getX(), $blockPos->getY(), $blockPos->getZ());
|
||||
$this->player->interactBlock($vBlockPos, $data->getFace(), $clickPos);
|
||||
$this->player->interactBlock($vBlockPos, $data->getFace(), $data->getClickPosition());
|
||||
//always sync this in case plugins caused a different result than the client expected
|
||||
//we *could* try to enhance detection of plugin-altered behaviour, but this would require propagating
|
||||
//more information up the stack. For now I think this is good enough.
|
||||
@ -786,10 +760,6 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function handlePlayerInput(PlayerInputPacket $packet) : bool{
|
||||
return false; //TODO
|
||||
}
|
||||
|
||||
public function handleSetPlayerGameType(SetPlayerGameTypePacket $packet) : bool{
|
||||
$gameMode = $this->session->getTypeConverter()->protocolGameModeToCore($packet->gamemode);
|
||||
if($gameMode !== $this->player->getGamemode()){
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\player;
|
||||
use pocketmine\block\BaseSign;
|
||||
use pocketmine\block\Bed;
|
||||
use pocketmine\block\BlockTypeTags;
|
||||
use pocketmine\block\RespawnAnchor;
|
||||
use pocketmine\block\UnknownBlock;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\command\CommandSender;
|
||||
@ -45,6 +46,7 @@ use pocketmine\entity\projectile\Arrow;
|
||||
use pocketmine\entity\Skin;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityExtinguishEvent;
|
||||
use pocketmine\event\inventory\InventoryCloseEvent;
|
||||
use pocketmine\event\inventory\InventoryOpenEvent;
|
||||
use pocketmine\event\player\PlayerBedEnterEvent;
|
||||
@ -135,6 +137,7 @@ use pocketmine\world\sound\EntityAttackNoDamageSound;
|
||||
use pocketmine\world\sound\EntityAttackSound;
|
||||
use pocketmine\world\sound\FireExtinguishSound;
|
||||
use pocketmine\world\sound\ItemBreakSound;
|
||||
use pocketmine\world\sound\RespawnAnchorDepleteSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\YmlServerProperties;
|
||||
@ -2537,6 +2540,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
$this->logger->debug("Respawn position located, completing respawn");
|
||||
$ev = new PlayerRespawnEvent($this, $safeSpawn);
|
||||
$spawnPosition = $ev->getRespawnPosition();
|
||||
$spawnBlock = $spawnPosition->getWorld()->getBlock($spawnPosition);
|
||||
if($spawnBlock instanceof RespawnAnchor){
|
||||
if($spawnBlock->getCharges() > 0){
|
||||
$spawnPosition->getWorld()->setBlock($spawnPosition, $spawnBlock->setCharges($spawnBlock->getCharges() - 1));
|
||||
$spawnPosition->getWorld()->addSound($spawnPosition, new RespawnAnchorDepleteSound());
|
||||
}else{
|
||||
$defaultSpawn = $this->server->getWorldManager()->getDefaultWorld()?->getSpawnLocation();
|
||||
if($defaultSpawn !== null){
|
||||
$this->setSpawn($defaultSpawn);
|
||||
$ev->setRespawnPosition($defaultSpawn);
|
||||
$this->sendMessage(KnownTranslationFactory::tile_respawn_anchor_notValid()->prefix(TextFormat::GRAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
$ev->call();
|
||||
|
||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
|
||||
@ -2546,7 +2564,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$this->setSneaking(false);
|
||||
$this->setFlying(false);
|
||||
|
||||
$this->extinguish();
|
||||
$this->extinguish(EntityExtinguishEvent::CAUSE_RESPAWN);
|
||||
$this->setAirSupplyTicks($this->getMaxAirSupplyTicks());
|
||||
$this->deadTicks = 0;
|
||||
$this->noDamageTicks = 60;
|
||||
|
@ -25,6 +25,8 @@ namespace pocketmine\player;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\entity\animation\ArmSwingAnimation;
|
||||
use pocketmine\entity\effect\VanillaEffects;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelEventPacket;
|
||||
@ -65,11 +67,29 @@ final class SurvivalBlockBreakHandler{
|
||||
if(!$this->block->getBreakInfo()->isBreakable()){
|
||||
return 0.0;
|
||||
}
|
||||
//TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211)
|
||||
$breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20;
|
||||
|
||||
if(!$this->player->isOnGround() && !$this->player->isFlying()){
|
||||
$breakTimePerTick *= 5;
|
||||
}
|
||||
if($this->player->isUnderwater() && !$this->player->getArmorInventory()->getHelmet()->hasEnchantment(VanillaEnchantments::AQUA_AFFINITY())){
|
||||
$breakTimePerTick *= 5;
|
||||
}
|
||||
if($breakTimePerTick > 0){
|
||||
return 1 / $breakTimePerTick;
|
||||
$progressPerTick = 1 / $breakTimePerTick;
|
||||
|
||||
$haste = $this->player->getEffects()->get(VanillaEffects::HASTE());
|
||||
if($haste !== null){
|
||||
$hasteLevel = $haste->getEffectLevel();
|
||||
$progressPerTick *= (1 + 0.2 * $hasteLevel) * (1.2 ** $hasteLevel);
|
||||
}
|
||||
|
||||
$miningFatigue = $this->player->getEffects()->get(VanillaEffects::MINING_FATIGUE());
|
||||
if($miningFatigue !== null){
|
||||
$miningFatigueLevel = $miningFatigue->getEffectLevel();
|
||||
$progressPerTick *= 0.21 ** $miningFatigueLevel;
|
||||
}
|
||||
|
||||
return $progressPerTick;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@ -82,7 +102,10 @@ final class SurvivalBlockBreakHandler{
|
||||
$newBreakSpeed = $this->calculateBreakProgressPerTick();
|
||||
if(abs($newBreakSpeed - $this->breakSpeed) > 0.0001){
|
||||
$this->breakSpeed = $newBreakSpeed;
|
||||
//TODO: sync with client
|
||||
$this->player->getWorld()->broadcastPacketToViewers(
|
||||
$this->blockPos,
|
||||
LevelEventPacket::create(LevelEvent::BLOCK_BREAK_SPEED, (int) (65535 * $this->breakSpeed), $this->blockPos)
|
||||
);
|
||||
}
|
||||
|
||||
$this->breakProgress += $this->breakSpeed;
|
||||
|
@ -94,7 +94,17 @@ trait CommonThreadPartsTrait{
|
||||
}
|
||||
}
|
||||
|
||||
public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; }
|
||||
public function getCrashInfo() : ?ThreadCrashInfo{
|
||||
//TODO: Joining a crashed worker might be a bit sus, but we need to make sure the thread's shutdown
|
||||
//handler has run before we try to collect the crash info. As of 6.1.1, pmmpthread sets isTerminated=true
|
||||
//*before* the shutdown handler is invoked, so we might land here before the crash info has been set.
|
||||
//In the future this should probably be fixed by running the shutdown handlers before setting isTerminated,
|
||||
//but this workaround should be good enough for now.
|
||||
if($this->isTerminated() && !$this->isJoined()){
|
||||
$this->join();
|
||||
}
|
||||
return $this->crashInfo;
|
||||
}
|
||||
|
||||
public function start(int $options = NativeThread::INHERIT_NONE) : bool{
|
||||
ThreadManager::getInstance()->add($this);
|
||||
|
@ -84,6 +84,6 @@ final class ThreadCrashInfo extends ThreadSafe{
|
||||
public function getThreadName() : string{ return $this->threadName; }
|
||||
|
||||
public function makePrettyMessage() : string{
|
||||
return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
|
||||
return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type, $this->message, Filesystem::cleanPath($this->file), $this->line);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\VersionInfo;
|
||||
use function array_merge;
|
||||
use function curl_close;
|
||||
use function curl_error;
|
||||
use function curl_exec;
|
||||
use function curl_getinfo;
|
||||
@ -217,34 +216,30 @@ class Internet{
|
||||
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . VersionInfo::NAME . "/" . VersionInfo::VERSION()->getFullVersion(true)], $extraHeaders),
|
||||
CURLOPT_HEADER => true
|
||||
]);
|
||||
try{
|
||||
$raw = curl_exec($ch);
|
||||
if($raw === false){
|
||||
throw new InternetException(curl_error($ch));
|
||||
}
|
||||
if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set");
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$rawHeaders = substr($raw, 0, $headerSize);
|
||||
$body = substr($raw, $headerSize);
|
||||
$headers = [];
|
||||
//TODO: explore if we can set these limits lower
|
||||
foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){
|
||||
$headerGroup = [];
|
||||
foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){
|
||||
$nameValue = explode(":", $line, 2);
|
||||
if(isset($nameValue[1])){
|
||||
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
|
||||
}
|
||||
}
|
||||
$headers[] = $headerGroup;
|
||||
}
|
||||
if($onSuccess !== null){
|
||||
$onSuccess($ch);
|
||||
}
|
||||
return new InternetRequestResult($headers, $body, $httpCode);
|
||||
}finally{
|
||||
curl_close($ch);
|
||||
$raw = curl_exec($ch);
|
||||
if($raw === false){
|
||||
throw new InternetException(curl_error($ch));
|
||||
}
|
||||
if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set");
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$rawHeaders = substr($raw, 0, $headerSize);
|
||||
$body = substr($raw, $headerSize);
|
||||
$headers = [];
|
||||
//TODO: explore if we can set these limits lower
|
||||
foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){
|
||||
$headerGroup = [];
|
||||
foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){
|
||||
$nameValue = explode(":", $line, 2);
|
||||
if(isset($nameValue[1])){
|
||||
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
|
||||
}
|
||||
}
|
||||
$headers[] = $headerGroup;
|
||||
}
|
||||
if($onSuccess !== null){
|
||||
$onSuccess($ch);
|
||||
}
|
||||
return new InternetRequestResult($headers, $body, $httpCode);
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ abstract class Terminal{
|
||||
public static string $COLOR_MATERIAL_DIAMOND = "";
|
||||
public static string $COLOR_MATERIAL_LAPIS = "";
|
||||
public static string $COLOR_MATERIAL_AMETHYST = "";
|
||||
public static string $COLOR_MATERIAL_RESIN = "";
|
||||
|
||||
private static ?bool $formattingCodes = null;
|
||||
|
||||
@ -131,6 +132,7 @@ abstract class Terminal{
|
||||
self::$COLOR_MATERIAL_DIAMOND = $color(37);
|
||||
self::$COLOR_MATERIAL_LAPIS = $color(24);
|
||||
self::$COLOR_MATERIAL_AMETHYST = $color(98);
|
||||
self::$COLOR_MATERIAL_RESIN = $color(208);
|
||||
}
|
||||
|
||||
protected static function getEscapeCodes() : void{
|
||||
@ -174,11 +176,12 @@ abstract class Terminal{
|
||||
self::$COLOR_MATERIAL_DIAMOND = $colors >= 256 ? $setaf(37) : $setaf(14);
|
||||
self::$COLOR_MATERIAL_LAPIS = $colors >= 256 ? $setaf(24) : $setaf(12);
|
||||
self::$COLOR_MATERIAL_AMETHYST = $colors >= 256 ? $setaf(98) : $setaf(13);
|
||||
self::$COLOR_MATERIAL_RESIN = $colors >= 256 ? $setaf(208) : $setaf(11);
|
||||
}else{
|
||||
self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = self::$COLOR_MATERIAL_NETHERITE = $setaf(0);
|
||||
self::$COLOR_RED = self::$COLOR_DARK_RED = self::$COLOR_MATERIAL_REDSTONE = self::$COLOR_MATERIAL_COPPER = $setaf(1);
|
||||
self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = self::$COLOR_MATERIAL_EMERALD = $setaf(2);
|
||||
self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = $setaf(3);
|
||||
self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = self::$COLOR_MATERIAL_RESIN = $setaf(3);
|
||||
self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = self::$COLOR_MATERIAL_LAPIS = $setaf(4);
|
||||
self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = self::$COLOR_MATERIAL_AMETHYST = $setaf(5);
|
||||
self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = self::$COLOR_MATERIAL_DIAMOND = $setaf(6);
|
||||
@ -253,6 +256,7 @@ abstract class Terminal{
|
||||
TextFormat::MATERIAL_DIAMOND => Terminal::$COLOR_MATERIAL_DIAMOND,
|
||||
TextFormat::MATERIAL_LAPIS => Terminal::$COLOR_MATERIAL_LAPIS,
|
||||
TextFormat::MATERIAL_AMETHYST => Terminal::$COLOR_MATERIAL_AMETHYST,
|
||||
TextFormat::MATERIAL_RESIN => Terminal::$COLOR_MATERIAL_RESIN,
|
||||
default => $token,
|
||||
};
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ abstract class TextFormat{
|
||||
public const MATERIAL_DIAMOND = TextFormat::ESCAPE . "s";
|
||||
public const MATERIAL_LAPIS = TextFormat::ESCAPE . "t";
|
||||
public const MATERIAL_AMETHYST = TextFormat::ESCAPE . "u";
|
||||
public const MATERIAL_RESIN = TextFormat::ESCAPE . "v";
|
||||
|
||||
public const COLORS = [
|
||||
self::BLACK => self::BLACK,
|
||||
@ -102,6 +103,7 @@ abstract class TextFormat{
|
||||
self::MATERIAL_DIAMOND => self::MATERIAL_DIAMOND,
|
||||
self::MATERIAL_LAPIS => self::MATERIAL_LAPIS,
|
||||
self::MATERIAL_AMETHYST => self::MATERIAL_AMETHYST,
|
||||
self::MATERIAL_RESIN => self::MATERIAL_RESIN,
|
||||
];
|
||||
|
||||
public const OBFUSCATED = TextFormat::ESCAPE . "k";
|
||||
@ -150,7 +152,7 @@ abstract class TextFormat{
|
||||
* @return string[]
|
||||
*/
|
||||
public static function tokenize(string $string) : array{
|
||||
$result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-u])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||||
$result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-v])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||||
if($result === false) throw self::makePcreError();
|
||||
return $result;
|
||||
}
|
||||
@ -164,7 +166,7 @@ abstract class TextFormat{
|
||||
$string = mb_scrub($string, 'UTF-8');
|
||||
$string = self::preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console)
|
||||
if($removeFormat){
|
||||
$string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-u]/u", "", $string));
|
||||
$string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-v]/u", "", $string));
|
||||
}
|
||||
return str_replace("\x1b", "", self::preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string));
|
||||
}
|
||||
@ -175,7 +177,7 @@ abstract class TextFormat{
|
||||
* @param string $placeholder default "&"
|
||||
*/
|
||||
public static function colorize(string $string, string $placeholder = "&") : string{
|
||||
return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-u])/u', TextFormat::ESCAPE . '$1', $string);
|
||||
return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-v])/u', TextFormat::ESCAPE . '$1', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,6 +254,7 @@ abstract class TextFormat{
|
||||
TextFormat::MATERIAL_DIAMOND => "color:#2cb9a8",
|
||||
TextFormat::MATERIAL_LAPIS => "color:#20487a",
|
||||
TextFormat::MATERIAL_AMETHYST => "color:#9a5cc5",
|
||||
TextFormat::MATERIAL_RESIN => "color:#fc7812",
|
||||
TextFormat::BOLD => "font-weight:bold",
|
||||
TextFormat::ITALIC => "font-style:italic",
|
||||
default => null
|
||||
|
@ -584,7 +584,7 @@ final class Utils{
|
||||
/**
|
||||
* @phpstan-template TMemberType
|
||||
* @phpstan-param array<mixed, TMemberType> $array
|
||||
* @phpstan-param \Closure(TMemberType) : void $validator
|
||||
* @phpstan-param \Closure(TMemberType) : mixed $validator
|
||||
*/
|
||||
public static function validateArrayValueType(array $array, \Closure $validator) : void{
|
||||
foreach(Utils::promoteKeys($array) as $k => $v){
|
||||
|
@ -26,16 +26,20 @@ namespace pocketmine\world;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\TNT;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\block\BlockExplodeEvent;
|
||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityExplodeEvent;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
use pocketmine\world\particle\HugeExplodeSeedParticle;
|
||||
use pocketmine\world\sound\ExplodeSound;
|
||||
@ -48,25 +52,36 @@ use function mt_rand;
|
||||
use function sqrt;
|
||||
|
||||
class Explosion{
|
||||
public const DEFAULT_FIRE_CHANCE = 1.0 / 3.0;
|
||||
|
||||
private int $rays = 16;
|
||||
public World $world;
|
||||
|
||||
/** @var Block[] */
|
||||
/**
|
||||
* @var Block[]
|
||||
* @phpstan-var array<int, Block>
|
||||
*/
|
||||
public array $affectedBlocks = [];
|
||||
public float $stepLen = 0.3;
|
||||
/** @var Block[] */
|
||||
private array $fireIgnitions = [];
|
||||
|
||||
private SubChunkExplorer $subChunkExplorer;
|
||||
|
||||
public function __construct(
|
||||
public Position $source,
|
||||
public float $radius,
|
||||
private Entity|Block|null $what = null
|
||||
private Entity|Block|null $what = null,
|
||||
private float $fireChance = 0.0
|
||||
){
|
||||
if(!$this->source->isValid()){
|
||||
throw new \InvalidArgumentException("Position does not have a valid world");
|
||||
}
|
||||
$this->world = $this->source->getWorld();
|
||||
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||
}
|
||||
if($radius <= 0){
|
||||
throw new \InvalidArgumentException("Explosion radius must be greater than 0, got $radius");
|
||||
}
|
||||
@ -85,6 +100,7 @@ class Explosion{
|
||||
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
||||
|
||||
$mRays = $this->rays - 1;
|
||||
$incendiary = $this->fireChance > 0;
|
||||
for($i = 0; $i < $this->rays; ++$i){
|
||||
for($j = 0; $j < $this->rays; ++$j){
|
||||
for($k = 0; $k < $this->rays; ++$k){
|
||||
@ -127,7 +143,12 @@ class Explosion{
|
||||
$_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false);
|
||||
foreach($_block->getAffectedBlocks() as $_affectedBlock){
|
||||
$_affectedBlockPos = $_affectedBlock->getPosition();
|
||||
$this->affectedBlocks[World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z)] = $_affectedBlock;
|
||||
$posHash = World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z);
|
||||
$this->affectedBlocks[$posHash] = $_affectedBlock;
|
||||
|
||||
if($incendiary && Utils::getRandomFloat() <= $this->fireChance){
|
||||
$this->fireIgnitions[$posHash] = $_affectedBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,13 +171,32 @@ class Explosion{
|
||||
$yield = min(100, (1 / $this->radius) * 100);
|
||||
|
||||
if($this->what instanceof Entity){
|
||||
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield);
|
||||
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield, $this->fireIgnitions);
|
||||
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}
|
||||
|
||||
$yield = $ev->getYield();
|
||||
$this->affectedBlocks = $ev->getBlockList();
|
||||
$this->fireIgnitions = $ev->getIgnitions();
|
||||
}elseif($this->what instanceof Block){
|
||||
$ev = new BlockExplodeEvent(
|
||||
$this->what,
|
||||
$this->source,
|
||||
$this->affectedBlocks,
|
||||
$yield,
|
||||
$this->fireIgnitions,
|
||||
);
|
||||
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}else{
|
||||
$yield = $ev->getYield();
|
||||
$this->affectedBlocks = $ev->getBlockList();
|
||||
$this->affectedBlocks = $ev->getAffectedBlocks();
|
||||
$this->fireIgnitions = $ev->getIgnitions();
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,8 +238,9 @@ class Explosion{
|
||||
|
||||
$air = VanillaItems::AIR();
|
||||
$airBlock = VanillaBlocks::AIR();
|
||||
$fireBlock = VanillaBlocks::FIRE();
|
||||
|
||||
foreach($this->affectedBlocks as $block){
|
||||
foreach($this->affectedBlocks as $hash => $block){
|
||||
$pos = $block->getPosition();
|
||||
if($block instanceof TNT){
|
||||
$block->ignite(mt_rand(10, 30));
|
||||
@ -212,7 +253,13 @@ class Explosion{
|
||||
if(($t = $this->world->getTileAt($pos->x, $pos->y, $pos->z)) !== null){
|
||||
$t->onBlockDestroyed(); //needed to create drops for inventories
|
||||
}
|
||||
$this->world->setBlockAt($pos->x, $pos->y, $pos->z, $airBlock);
|
||||
$targetBlock =
|
||||
isset($this->fireIgnitions[$hash]) &&
|
||||
$block->getSide(Facing::DOWN)->getSupportType(Facing::UP) === SupportType::FULL ?
|
||||
$fireBlock :
|
||||
$airBlock;
|
||||
|
||||
$this->world->setBlockAt($pos->x, $pos->y, $pos->z, $targetBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,4 +268,18 @@ class Explosion{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a chance between 0 and 1 of creating a fire.
|
||||
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||
*
|
||||
* @param float $fireChance 0 ... 1
|
||||
*/
|
||||
public function setFireChance(float $fireChance) : void{
|
||||
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||
}
|
||||
$this->fireChance = $fireChance;
|
||||
}
|
||||
}
|
||||
|
@ -93,9 +93,11 @@ use pocketmine\world\format\io\GlobalBlockStateHandlers;
|
||||
use pocketmine\world\format\io\WritableWorldProvider;
|
||||
use pocketmine\world\format\LightArray;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
use pocketmine\world\generator\executor\AsyncGeneratorExecutor;
|
||||
use pocketmine\world\generator\executor\GeneratorExecutor;
|
||||
use pocketmine\world\generator\executor\GeneratorExecutorSetupParameters;
|
||||
use pocketmine\world\generator\executor\SyncGeneratorExecutor;
|
||||
use pocketmine\world\generator\GeneratorManager;
|
||||
use pocketmine\world\generator\GeneratorRegisterTask;
|
||||
use pocketmine\world\generator\GeneratorUnregisterTask;
|
||||
use pocketmine\world\generator\PopulationTask;
|
||||
use pocketmine\world\light\BlockLightUpdate;
|
||||
use pocketmine\world\light\LightPopulationTask;
|
||||
@ -336,11 +338,7 @@ class World implements ChunkManager{
|
||||
*/
|
||||
private array $chunkPopulationRequestQueueIndex = [];
|
||||
|
||||
/**
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
*/
|
||||
private array $generatorRegisteredWorkers = [];
|
||||
private readonly GeneratorExecutor $generatorExecutor;
|
||||
|
||||
private bool $autoSave = true;
|
||||
|
||||
@ -360,9 +358,6 @@ class World implements ChunkManager{
|
||||
|
||||
private bool $doingTick = false;
|
||||
|
||||
/** @phpstan-var class-string<\pocketmine\world\generator\Generator> */
|
||||
private string $generator;
|
||||
|
||||
private bool $unloaded = false;
|
||||
/**
|
||||
* @var \Closure[]
|
||||
@ -375,6 +370,8 @@ class World implements ChunkManager{
|
||||
|
||||
private \Logger $logger;
|
||||
|
||||
private RuntimeBlockStateRegistry $blockStateRegistry;
|
||||
|
||||
/**
|
||||
* @phpstan-return ChunkPosHash
|
||||
*/
|
||||
@ -488,6 +485,7 @@ class World implements ChunkManager{
|
||||
$this->displayName = $this->provider->getWorldData()->getName();
|
||||
$this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName");
|
||||
|
||||
$this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance();
|
||||
$this->minY = $this->provider->getWorldMinY();
|
||||
$this->maxY = $this->provider->getWorldMaxY();
|
||||
|
||||
@ -495,7 +493,23 @@ class World implements ChunkManager{
|
||||
$generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ??
|
||||
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
|
||||
$generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions());
|
||||
$this->generator = $generator->getGeneratorClass();
|
||||
|
||||
$executorSetupParameters = new GeneratorExecutorSetupParameters(
|
||||
worldMinY: $this->minY,
|
||||
worldMaxY: $this->maxY,
|
||||
generatorSeed: $this->getSeed(),
|
||||
generatorClass: $generator->getGeneratorClass(),
|
||||
generatorSettings: $this->provider->getWorldData()->getGeneratorOptions()
|
||||
);
|
||||
$this->generatorExecutor = $generator->isFast() ?
|
||||
new SyncGeneratorExecutor($executorSetupParameters) :
|
||||
new AsyncGeneratorExecutor(
|
||||
$this->logger,
|
||||
$this->workerPool,
|
||||
$executorSetupParameters,
|
||||
$this->worldId
|
||||
);
|
||||
|
||||
$this->chunkPopulationRequestQueue = new \SplQueue();
|
||||
$this->addOnUnloadCallback(function() : void{
|
||||
$this->logger->debug("Cancelling unfulfilled generation requests");
|
||||
@ -531,17 +545,6 @@ class World implements ChunkManager{
|
||||
$this->initRandomTickBlocksFromConfig($cfg);
|
||||
|
||||
$this->timings = new WorldTimings($this);
|
||||
|
||||
$this->workerPool->addWorkerStartHook($workerStartHook = function(int $workerId) : void{
|
||||
if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){
|
||||
$this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered");
|
||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||
}
|
||||
});
|
||||
$workerPool = $this->workerPool;
|
||||
$this->addOnUnloadCallback(static function() use ($workerPool, $workerStartHook) : void{
|
||||
$workerPool->removeWorkerStartHook($workerStartHook);
|
||||
});
|
||||
}
|
||||
|
||||
private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{
|
||||
@ -559,7 +562,7 @@ class World implements ChunkManager{
|
||||
}catch(BlockStateDeserializeException){
|
||||
continue;
|
||||
}
|
||||
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
|
||||
$block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
|
||||
}else{
|
||||
//TODO: we probably ought to log an error here
|
||||
continue;
|
||||
@ -570,7 +573,7 @@ class World implements ChunkManager{
|
||||
}
|
||||
}
|
||||
|
||||
foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){
|
||||
foreach($this->blockStateRegistry->getAllKnownStates() as $state){
|
||||
$dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null;
|
||||
if($dontTickName === null && $state->ticksRandomly()){
|
||||
$this->randomTickBlocks[$state->getStateId()] = true;
|
||||
@ -582,21 +585,6 @@ class World implements ChunkManager{
|
||||
return $this->tickRateTime;
|
||||
}
|
||||
|
||||
public function registerGeneratorToWorker(int $worker) : void{
|
||||
$this->logger->debug("Registering generator on worker $worker");
|
||||
$this->workerPool->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getWorldData()->getGeneratorOptions()), $worker);
|
||||
$this->generatorRegisteredWorkers[$worker] = true;
|
||||
}
|
||||
|
||||
public function unregisterGenerator() : void{
|
||||
foreach($this->workerPool->getRunningWorkers() as $i){
|
||||
if(isset($this->generatorRegisteredWorkers[$i])){
|
||||
$this->workerPool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
|
||||
}
|
||||
}
|
||||
$this->generatorRegisteredWorkers = [];
|
||||
}
|
||||
|
||||
public function getServer() : Server{
|
||||
return $this->server;
|
||||
}
|
||||
@ -654,7 +642,7 @@ class World implements ChunkManager{
|
||||
|
||||
$this->save();
|
||||
|
||||
$this->unregisterGenerator();
|
||||
$this->generatorExecutor->shutdown();
|
||||
|
||||
$this->provider->close();
|
||||
$this->blockCache = [];
|
||||
@ -1394,7 +1382,7 @@ class World implements ChunkManager{
|
||||
$entity->onRandomUpdate();
|
||||
}
|
||||
|
||||
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
||||
$blockFactory = $this->blockStateRegistry;
|
||||
foreach($chunk->getSubChunks() as $Y => $subChunk){
|
||||
if(!$subChunk->isEmptyFast()){
|
||||
$k = 0;
|
||||
@ -1528,24 +1516,48 @@ class World implements ChunkManager{
|
||||
|
||||
$collides = [];
|
||||
|
||||
$collisionInfo = $this->blockStateRegistry->collisionInfo;
|
||||
if($targetFirst){
|
||||
for($z = $minZ; $z <= $maxZ; ++$z){
|
||||
$zOverflow = $z === $minZ || $z === $maxZ;
|
||||
for($x = $minX; $x <= $maxX; ++$x){
|
||||
$zxOverflow = $zOverflow || $x === $minX || $x === $maxX;
|
||||
for($y = $minY; $y <= $maxY; ++$y){
|
||||
$block = $this->getBlockAt($x, $y, $z);
|
||||
if($block->collidesWithBB($bb)){
|
||||
return [$block];
|
||||
$overflow = $zxOverflow || $y === $minY || $y === $maxY;
|
||||
|
||||
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
|
||||
if($overflow ?
|
||||
$stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) :
|
||||
match ($stateCollisionInfo) {
|
||||
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
|
||||
RuntimeBlockStateRegistry::COLLISION_NONE => false,
|
||||
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
|
||||
}
|
||||
){
|
||||
return [$this->getBlockAt($x, $y, $z)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//TODO: duplicated code :( this way is better for performance though
|
||||
for($z = $minZ; $z <= $maxZ; ++$z){
|
||||
$zOverflow = $z === $minZ || $z === $maxZ;
|
||||
for($x = $minX; $x <= $maxX; ++$x){
|
||||
$zxOverflow = $zOverflow || $x === $minX || $x === $maxX;
|
||||
for($y = $minY; $y <= $maxY; ++$y){
|
||||
$block = $this->getBlockAt($x, $y, $z);
|
||||
if($block->collidesWithBB($bb)){
|
||||
$collides[] = $block;
|
||||
$overflow = $zxOverflow || $y === $minY || $y === $maxY;
|
||||
|
||||
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
|
||||
if($overflow ?
|
||||
$stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) :
|
||||
match ($stateCollisionInfo) {
|
||||
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
|
||||
RuntimeBlockStateRegistry::COLLISION_NONE => false,
|
||||
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
|
||||
}
|
||||
){
|
||||
$collides[] = $this->getBlockAt($x, $y, $z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1555,24 +1567,64 @@ class World implements ChunkManager{
|
||||
return $collides;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $collisionInfo
|
||||
* @phpstan-param array<int, int> $collisionInfo
|
||||
*/
|
||||
private function getBlockCollisionInfo(int $x, int $y, int $z, array $collisionInfo) : int{
|
||||
if(!$this->isInWorld($x, $y, $z)){
|
||||
return RuntimeBlockStateRegistry::COLLISION_NONE;
|
||||
}
|
||||
$chunk = $this->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
|
||||
if($chunk === null){
|
||||
return RuntimeBlockStateRegistry::COLLISION_NONE;
|
||||
}
|
||||
$stateId = $chunk
|
||||
->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)
|
||||
->getBlockStateId(
|
||||
$x & SubChunk::COORD_MASK,
|
||||
$y & SubChunk::COORD_MASK,
|
||||
$z & SubChunk::COORD_MASK
|
||||
);
|
||||
return $collisionInfo[$stateId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all block AABBs which overlap the full block area at the given coordinates.
|
||||
* This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences.
|
||||
* Larger AABBs (>= 2 blocks on any axis) are not accounted for.
|
||||
*
|
||||
* @param int[] $collisionInfo
|
||||
* @phpstan-param array<int, int> $collisionInfo
|
||||
*
|
||||
* @return AxisAlignedBB[]
|
||||
* @phpstan-return list<AxisAlignedBB>
|
||||
*/
|
||||
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{
|
||||
$block = $this->getBlockAt($x, $y, $z);
|
||||
$boxes = $block->getCollisionBoxes();
|
||||
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{
|
||||
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
|
||||
$boxes = match($stateCollisionInfo){
|
||||
RuntimeBlockStateRegistry::COLLISION_NONE => [],
|
||||
RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)],
|
||||
default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes()
|
||||
};
|
||||
|
||||
$cellBB = AxisAlignedBB::one()->offset($x, $y, $z);
|
||||
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
|
||||
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
|
||||
foreach($extraBoxes as $extraBox){
|
||||
if($extraBox->intersectsWith($cellBB)){
|
||||
$boxes[] = $extraBox;
|
||||
//overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case
|
||||
if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){
|
||||
$cellBB = null;
|
||||
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
|
||||
$offsetX = $x + $dx;
|
||||
$offsetY = $y + $dy;
|
||||
$offsetZ = $z + $dz;
|
||||
$stateCollisionInfo = $this->getBlockCollisionInfo($offsetX, $offsetY, $offsetZ, $collisionInfo);
|
||||
if($stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){
|
||||
//avoid allocating this unless it's needed
|
||||
$cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z);
|
||||
$extraBoxes = $this->getBlockAt($offsetX, $offsetY, $offsetZ)->getCollisionBoxes();
|
||||
foreach($extraBoxes as $extraBox){
|
||||
if($extraBox->intersectsWith($cellBB)){
|
||||
$boxes[] = $extraBox;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1594,13 +1646,15 @@ class World implements ChunkManager{
|
||||
|
||||
$collides = [];
|
||||
|
||||
$collisionInfo = $this->blockStateRegistry->collisionInfo;
|
||||
|
||||
for($z = $minZ; $z <= $maxZ; ++$z){
|
||||
for($x = $minX; $x <= $maxX; ++$x){
|
||||
$chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
|
||||
for($y = $minY; $y <= $maxY; ++$y){
|
||||
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
|
||||
|
||||
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z);
|
||||
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z, $collisionInfo);
|
||||
|
||||
foreach($boxes as $blockBB){
|
||||
if($blockBB->intersectsWith($bb)){
|
||||
@ -1795,7 +1849,7 @@ class World implements ChunkManager{
|
||||
return;
|
||||
}
|
||||
|
||||
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
||||
$blockFactory = $this->blockStateRegistry;
|
||||
$this->timings->doBlockSkyLightUpdates->startTiming();
|
||||
if($this->skyLightUpdate === null){
|
||||
$this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight);
|
||||
@ -1914,7 +1968,7 @@ class World implements ChunkManager{
|
||||
|
||||
$chunk = $this->chunks[$chunkHash] ?? null;
|
||||
if($chunk !== null){
|
||||
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
|
||||
$block = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
|
||||
}else{
|
||||
$addToCache = false;
|
||||
$block = VanillaBlocks::AIR();
|
||||
@ -1978,6 +2032,15 @@ class World implements ChunkManager{
|
||||
throw new WorldException("Cannot set a block in un-generated terrain");
|
||||
}
|
||||
|
||||
//TODO: this computes state ID twice (we do it again in writeStateToWorld()). Not great for performance :(
|
||||
$stateId = $block->getStateId();
|
||||
if(!$this->blockStateRegistry->hasStateId($stateId)){
|
||||
throw new \LogicException("Block state ID not known to RuntimeBlockStateRegistry (probably not registered)");
|
||||
}
|
||||
if(!GlobalBlockStateHandlers::getSerializer()->isRegistered($block)){
|
||||
throw new \LogicException("Block not registered with GlobalBlockStateHandlers serializer");
|
||||
}
|
||||
|
||||
$this->timings->setBlock->startTiming();
|
||||
|
||||
$this->unlockChunk($chunkX, $chunkZ, null);
|
||||
@ -2573,7 +2636,7 @@ class World implements ChunkManager{
|
||||
$localY = $tilePosition->getFloorY();
|
||||
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
|
||||
|
||||
$newBlock = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
|
||||
$newBlock = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
|
||||
$expectedTileClass = $newBlock->getIdInfo()->getTileClass();
|
||||
if(
|
||||
$expectedTileClass === null || //new block doesn't expect a tile
|
||||
@ -2700,6 +2763,11 @@ class World implements ChunkManager{
|
||||
throw new AssumptionFailedError("Found two different entities sharing entity ID " . $entity->getId());
|
||||
}
|
||||
}
|
||||
if(!EntityFactory::getInstance()->isRegistered($entity::class) && !$entity instanceof Player){
|
||||
//canSaveWithChunk is mutable, so that means it could be toggled after adding the entity and cause a crash
|
||||
//later on. Better we just force all entities to have a save ID, even if it might not be needed.
|
||||
throw new \LogicException("Entity " . $entity::class . " is not registered for a save ID in EntityFactory");
|
||||
}
|
||||
$pos = $entity->getPosition()->asVector3();
|
||||
$this->entitiesByChunk[World::chunkHash($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE)][$entity->getId()] = $entity;
|
||||
$this->entityLastKnownPositions[$entity->getId()] = $pos;
|
||||
@ -2801,6 +2869,9 @@ class World implements ChunkManager{
|
||||
if(!$this->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ())){
|
||||
throw new \InvalidArgumentException("Tile position is outside the world bounds");
|
||||
}
|
||||
if(!TileFactory::getInstance()->isRegistered($tile::class)){
|
||||
throw new \LogicException("Tile " . $tile::class . " is not registered for a save ID in TileFactory");
|
||||
}
|
||||
|
||||
$chunkX = $pos->getFloorX() >> Chunk::COORD_BIT_SIZE;
|
||||
$chunkZ = $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE;
|
||||
@ -2911,6 +2982,8 @@ class World implements ChunkManager{
|
||||
if(count($chunkData->getEntityNBT()) !== 0){
|
||||
$this->timings->syncChunkLoadEntities->startTiming();
|
||||
$entityFactory = EntityFactory::getInstance();
|
||||
|
||||
$deletedEntities = [];
|
||||
foreach($chunkData->getEntityNBT() as $k => $nbt){
|
||||
try{
|
||||
$entity = $entityFactory->createFromData($this, $nbt);
|
||||
@ -2927,18 +3000,23 @@ class World implements ChunkManager{
|
||||
}elseif($saveIdTag instanceof IntTag){ //legacy MCPE format
|
||||
$saveId = "legacy(" . $saveIdTag->getValue() . ")";
|
||||
}
|
||||
$logger->warning("Deleted unknown entity type $saveId");
|
||||
$deletedEntities[$saveId] = ($deletedEntities[$saveId] ?? 0) + 1;
|
||||
}
|
||||
//TODO: we can't prevent entities getting added to unloaded chunks if they were saved in the wrong place
|
||||
//here, because entities currently add themselves to the world
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($deletedEntities) as $saveId => $count){
|
||||
$logger->warning("Deleted unknown entity type $saveId x$count");
|
||||
}
|
||||
$this->timings->syncChunkLoadEntities->stopTiming();
|
||||
}
|
||||
|
||||
if(count($chunkData->getTileNBT()) !== 0){
|
||||
$this->timings->syncChunkLoadTileEntities->startTiming();
|
||||
$tileFactory = TileFactory::getInstance();
|
||||
|
||||
$deletedTiles = [];
|
||||
foreach($chunkData->getTileNBT() as $k => $nbt){
|
||||
try{
|
||||
$tile = $tileFactory->createFromData($this, $nbt);
|
||||
@ -2948,7 +3026,8 @@ class World implements ChunkManager{
|
||||
continue;
|
||||
}
|
||||
if($tile === null){
|
||||
$logger->warning("Deleted unknown tile entity type " . $nbt->getString("id", "<unknown>"));
|
||||
$saveId = $nbt->getString("id", "<unknown>");
|
||||
$deletedTiles[$saveId] = ($deletedTiles[$saveId] ?? 0) + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -2964,6 +3043,10 @@ class World implements ChunkManager{
|
||||
}
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($deletedTiles) as $saveId => $count){
|
||||
$logger->warning("Deleted unknown tile entity type $saveId x$count");
|
||||
}
|
||||
|
||||
$this->timings->syncChunkLoadTileEntities->stopTiming();
|
||||
}
|
||||
}
|
||||
@ -3400,8 +3483,8 @@ class World implements ChunkManager{
|
||||
|
||||
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
|
||||
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
|
||||
$task = new PopulationTask(
|
||||
$this->worldId,
|
||||
|
||||
$this->generatorExecutor->populate(
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
$centerChunk,
|
||||
@ -3414,15 +3497,6 @@ class World implements ChunkManager{
|
||||
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
|
||||
}
|
||||
);
|
||||
$workerId = $this->workerPool->selectWorker();
|
||||
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
|
||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||
}
|
||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->registerGeneratorToWorker($workerId);
|
||||
}
|
||||
$this->workerPool->submitTaskToWorker($task, $workerId);
|
||||
|
||||
return $resolver->getPromise();
|
||||
}finally{
|
||||
|
69
src/world/biome/model/BiomeDefinitionEntryData.php
Normal file
69
src/world/biome/model/BiomeDefinitionEntryData.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\biome\model;
|
||||
|
||||
/**
|
||||
* Model for loading biome definition entries data from JSON.
|
||||
*/
|
||||
final class BiomeDefinitionEntryData{
|
||||
/** @required */
|
||||
public ?int $id;
|
||||
|
||||
/** @required */
|
||||
public float $temperature;
|
||||
|
||||
/** @required */
|
||||
public float $downfall;
|
||||
|
||||
/** @required */
|
||||
public float $redSporeDensity;
|
||||
|
||||
/** @required */
|
||||
public float $blueSporeDensity;
|
||||
|
||||
/** @required */
|
||||
public float $ashDensity;
|
||||
|
||||
/** @required */
|
||||
public float $whiteAshDensity;
|
||||
|
||||
/** @required */
|
||||
public float $depth;
|
||||
|
||||
/** @required */
|
||||
public float $scale;
|
||||
|
||||
/** @required */
|
||||
public ColorData $mapWaterColour;
|
||||
|
||||
/** @required */
|
||||
public bool $rain;
|
||||
|
||||
/**
|
||||
* @required
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
public array $tags;
|
||||
}
|
41
src/world/biome/model/ColorData.php
Normal file
41
src/world/biome/model/ColorData.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\biome\model;
|
||||
|
||||
/**
|
||||
* Model for loading color data from JSON.
|
||||
*/
|
||||
final class ColorData{
|
||||
/** @required */
|
||||
public int $r;
|
||||
|
||||
/** @required */
|
||||
public int $g;
|
||||
|
||||
/** @required */
|
||||
public int $b;
|
||||
|
||||
/** @required */
|
||||
public int $a;
|
||||
}
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\format\io\data;
|
||||
|
||||
use pocketmine\data\bedrock\WorldDataVersions;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
@ -50,15 +51,9 @@ use function time;
|
||||
|
||||
class BedrockWorldData extends BaseNbtWorldData{
|
||||
|
||||
public const CURRENT_STORAGE_VERSION = 10;
|
||||
public const CURRENT_STORAGE_NETWORK_VERSION = 776;
|
||||
public const CURRENT_CLIENT_VERSION_TARGET = [
|
||||
1, //major
|
||||
21, //minor
|
||||
60, //patch
|
||||
33, //revision
|
||||
0 //is beta
|
||||
];
|
||||
public const CURRENT_STORAGE_VERSION = WorldDataVersions::STORAGE;
|
||||
public const CURRENT_STORAGE_NETWORK_VERSION = WorldDataVersions::NETWORK;
|
||||
public const CURRENT_CLIENT_VERSION_TARGET = WorldDataVersions::LAST_OPENED_IN;
|
||||
|
||||
public const GENERATOR_LIMITED = 0;
|
||||
public const GENERATOR_INFINITE = 1;
|
||||
|
@ -27,6 +27,7 @@ use pocketmine\block\Block;
|
||||
use pocketmine\data\bedrock\BiomeIds;
|
||||
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
|
||||
use pocketmine\data\bedrock\block\convert\UnsupportedBlockStateException;
|
||||
use pocketmine\data\bedrock\WorldDataVersions;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\NbtDataException;
|
||||
@ -35,6 +36,7 @@ use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\VersionInfo;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\BaseWorldProvider;
|
||||
@ -78,8 +80,8 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
|
||||
protected const ENTRY_FLAT_WORLD_LAYERS = "game_flatworldlayers";
|
||||
|
||||
protected const CURRENT_LEVEL_CHUNK_VERSION = ChunkVersion::v1_21_40;
|
||||
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = SubChunkVersion::PALETTED_MULTI;
|
||||
protected const CURRENT_LEVEL_CHUNK_VERSION = WorldDataVersions::CHUNK;
|
||||
protected const CURRENT_LEVEL_SUBCHUNK_VERSION = WorldDataVersions::SUBCHUNK;
|
||||
|
||||
private const CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET = 4;
|
||||
|
||||
@ -203,23 +205,29 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
||||
$blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
//while not ideal, this is not a fatal error
|
||||
$blockDecodeErrors[] = "Palette offset $i / Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$errorMessage = "Upgrade error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$blockDecodeErrors[$errorMessage][] = $i;
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
continue;
|
||||
}
|
||||
try{
|
||||
$palette[] = $this->blockStateDeserializer->deserialize($blockStateData);
|
||||
}catch(UnsupportedBlockStateException $e){
|
||||
$blockDecodeErrors[] = "Palette offset $i / " . $e->getMessage();
|
||||
$blockDecodeErrors[$e->getMessage()][] = $i;
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
}catch(BlockStateDeserializeException $e){
|
||||
$blockDecodeErrors[] = "Palette offset $i / Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$errorMessage = "Deserialize error: " . $e->getMessage() . ", NBT: " . $blockStateNbt->toString();
|
||||
$blockDecodeErrors[$errorMessage][] = $i;
|
||||
$palette[] = $this->blockStateDeserializer->deserialize(GlobalBlockStateHandlers::getUnknownBlockStateData());
|
||||
}
|
||||
}
|
||||
|
||||
if(count($blockDecodeErrors) > 0){
|
||||
$logger->error("Errors decoding blocks:\n - " . implode("\n - ", $blockDecodeErrors));
|
||||
$finalErrors = [];
|
||||
foreach(Utils::promoteKeys($blockDecodeErrors) as $errorMessage => $paletteOffsets){
|
||||
$finalErrors[] = "$errorMessage (palette offsets: " . implode(", ", $paletteOffsets) . ")";
|
||||
}
|
||||
$logger->error("Errors decoding blocks:\n - " . implode("\n - ", $finalErrors));
|
||||
}
|
||||
|
||||
//TODO: exceptions
|
||||
|
@ -50,7 +50,7 @@ final class GeneratorManager{
|
||||
}catch(InvalidGeneratorOptionsException $e){
|
||||
return $e;
|
||||
}
|
||||
});
|
||||
}, fast: true);
|
||||
$this->addGenerator(Normal::class, "normal", fn() => null);
|
||||
$this->addAlias("normal", "default");
|
||||
$this->addGenerator(Nether::class, "nether", fn() => null);
|
||||
@ -62,6 +62,7 @@ final class GeneratorManager{
|
||||
* @param string $name Alias for this generator type that can be written in configs
|
||||
* @param \Closure $presetValidator Callback to validate generator options for new worlds
|
||||
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
||||
* @param bool $fast Whether this generator is fast enough to run without async tasks
|
||||
*
|
||||
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
|
||||
*
|
||||
@ -69,7 +70,7 @@ final class GeneratorManager{
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false) : void{
|
||||
public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false, bool $fast = false) : void{
|
||||
Utils::testValidInstance($class, Generator::class);
|
||||
|
||||
$name = strtolower($name);
|
||||
@ -77,7 +78,7 @@ final class GeneratorManager{
|
||||
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
|
||||
}
|
||||
|
||||
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator);
|
||||
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator, $fast);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,12 +31,15 @@ final class GeneratorManagerEntry{
|
||||
*/
|
||||
public function __construct(
|
||||
private string $generatorClass,
|
||||
private \Closure $presetValidator
|
||||
private \Closure $presetValidator,
|
||||
private readonly bool $fast
|
||||
){}
|
||||
|
||||
/** @phpstan-return class-string<Generator> */
|
||||
public function getGeneratorClass() : string{ return $this->generatorClass; }
|
||||
|
||||
public function isFast() : bool{ return $this->fast; }
|
||||
|
||||
/**
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
|
@ -27,13 +27,18 @@ use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\FastChunkSerializer;
|
||||
use pocketmine\world\SimpleChunkManager;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\world\generator\executor\ThreadLocalGeneratorContext;
|
||||
use function array_map;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* TODO: this should be moved to the executor namespace, but plugins have unfortunately used it directly due to the
|
||||
* difficulty of regenerating chunks. This should be addressed in the future.
|
||||
* For the remainder of PM5, we can't relocate this class.
|
||||
*
|
||||
* @phpstan-type OnCompletion \Closure(Chunk $centerChunk, array<int, Chunk> $adjacentChunks) : void
|
||||
*/
|
||||
class PopulationTask extends AsyncTask{
|
||||
@ -71,8 +76,6 @@ class PopulationTask extends AsyncTask{
|
||||
if($context === null){
|
||||
throw new AssumptionFailedError("Generator context should have been initialized before any PopulationTask execution");
|
||||
}
|
||||
$generator = $context->getGenerator();
|
||||
$manager = new SimpleChunkManager($context->getWorldMinY(), $context->getWorldMaxY());
|
||||
|
||||
$chunk = $this->chunk !== null ? FastChunkSerializer::deserializeTerrain($this->chunk) : null;
|
||||
|
||||
@ -93,21 +96,15 @@ class PopulationTask extends AsyncTask{
|
||||
$serialChunks
|
||||
);
|
||||
|
||||
self::setOrGenerateChunk($manager, $generator, $this->chunkX, $this->chunkZ, $chunk);
|
||||
|
||||
$resultChunks = []; //this is just to keep phpstan's type inference happy
|
||||
foreach($chunks as $relativeChunkHash => $c){
|
||||
World::getXZ($relativeChunkHash, $relativeX, $relativeZ);
|
||||
$resultChunks[$relativeChunkHash] = self::setOrGenerateChunk($manager, $generator, $this->chunkX + $relativeX, $this->chunkZ + $relativeZ, $c);
|
||||
}
|
||||
$chunks = $resultChunks;
|
||||
|
||||
$generator->populateChunk($manager, $this->chunkX, $this->chunkZ);
|
||||
$chunk = $manager->getChunk($this->chunkX, $this->chunkZ);
|
||||
if($chunk === null){
|
||||
throw new AssumptionFailedError("We just generated this chunk, so it must exist");
|
||||
}
|
||||
$chunk->setPopulated();
|
||||
[$chunk, $chunks] = PopulationUtils::populateChunkWithAdjacents(
|
||||
$context->getWorldMinY(),
|
||||
$context->getWorldMaxY(),
|
||||
$context->getGenerator(),
|
||||
$this->chunkX,
|
||||
$this->chunkZ,
|
||||
$chunk,
|
||||
$chunks
|
||||
);
|
||||
|
||||
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
|
||||
|
||||
@ -118,18 +115,6 @@ class PopulationTask extends AsyncTask{
|
||||
$this->adjacentChunks = igbinary_serialize($serialChunks) ?? throw new AssumptionFailedError("igbinary_serialize() returned null");
|
||||
}
|
||||
|
||||
private static function setOrGenerateChunk(SimpleChunkManager $manager, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $chunk) : Chunk{
|
||||
$manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], false));
|
||||
if($chunk === null){
|
||||
$generator->generateChunk($manager, $chunkX, $chunkZ);
|
||||
$chunk = $manager->getChunk($chunkX, $chunkZ);
|
||||
if($chunk === null){
|
||||
throw new AssumptionFailedError("We just set this chunk, so it must exist");
|
||||
}
|
||||
}
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
/**
|
||||
* @var \Closure $onCompletion
|
||||
|
74
src/world/generator/PopulationUtils.php
Normal file
74
src/world/generator/PopulationUtils.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\SimpleChunkManager;
|
||||
use pocketmine\world\World;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class PopulationUtils{
|
||||
|
||||
private static function setOrGenerateChunk(SimpleChunkManager $manager, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $chunk) : Chunk{
|
||||
$manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], false));
|
||||
if($chunk === null){
|
||||
$generator->generateChunk($manager, $chunkX, $chunkZ);
|
||||
$chunk = $manager->getChunk($chunkX, $chunkZ);
|
||||
if($chunk === null){
|
||||
throw new AssumptionFailedError("We just set this chunk, so it must exist");
|
||||
}
|
||||
}
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Chunk[]|null[] $adjacentChunks
|
||||
* @phpstan-param array<int, Chunk|null> $adjacentChunks
|
||||
*
|
||||
* @return Chunk[]|Chunk[][]
|
||||
* @phpstan-return array{Chunk, array<int, Chunk>}
|
||||
*/
|
||||
public static function populateChunkWithAdjacents(int $minY, int $maxY, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks) : array{
|
||||
$manager = new SimpleChunkManager($minY, $maxY);
|
||||
self::setOrGenerateChunk($manager, $generator, $chunkX, $chunkZ, $centerChunk);
|
||||
|
||||
$resultChunks = []; //this is just to keep phpstan's type inference happy
|
||||
foreach($adjacentChunks as $relativeChunkHash => $c){
|
||||
World::getXZ($relativeChunkHash, $relativeX, $relativeZ);
|
||||
$resultChunks[$relativeChunkHash] = self::setOrGenerateChunk($manager, $generator, $chunkX + $relativeX, $chunkZ + $relativeZ, $c);
|
||||
}
|
||||
$adjacentChunks = $resultChunks;
|
||||
|
||||
$generator->populateChunk($manager, $chunkX, $chunkZ);
|
||||
$centerChunk = $manager->getChunk($chunkX, $chunkZ);
|
||||
if($centerChunk === null){
|
||||
throw new AssumptionFailedError("We just generated this chunk, so it must exist");
|
||||
}
|
||||
$centerChunk->setPopulated();
|
||||
return [$centerChunk, $adjacentChunks];
|
||||
}
|
||||
}
|
106
src/world/generator/executor/AsyncGeneratorExecutor.php
Normal file
106
src/world/generator/executor/AsyncGeneratorExecutor.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\generator\PopulationTask;
|
||||
use function array_key_exists;
|
||||
|
||||
final class AsyncGeneratorExecutor implements GeneratorExecutor{
|
||||
private static int $nextAsyncContextId = 1;
|
||||
|
||||
private readonly \Logger $logger;
|
||||
|
||||
/** @phpstan-var \Closure(int) : void */
|
||||
private readonly \Closure $workerStartHook;
|
||||
|
||||
private readonly int $asyncContextId;
|
||||
|
||||
/**
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
*/
|
||||
private array $generatorRegisteredWorkers = [];
|
||||
|
||||
public function __construct(
|
||||
\Logger $logger,
|
||||
private readonly AsyncPool $workerPool,
|
||||
private readonly GeneratorExecutorSetupParameters $setupParameters,
|
||||
int $asyncContextId = null
|
||||
){
|
||||
$this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor");
|
||||
|
||||
//TODO: we only allow setting this for PM5 because of PopulationTask uses in plugins
|
||||
$this->asyncContextId = $asyncContextId ?? self::$nextAsyncContextId++;
|
||||
|
||||
$this->workerStartHook = function(int $workerId) : void{
|
||||
if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){
|
||||
$this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered");
|
||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||
}
|
||||
};
|
||||
$this->workerPool->addWorkerStartHook($this->workerStartHook);
|
||||
}
|
||||
|
||||
private function registerGeneratorToWorker(int $worker) : void{
|
||||
$this->logger->debug("Registering generator on worker $worker");
|
||||
$this->workerPool->submitTaskToWorker(new AsyncGeneratorRegisterTask($this->setupParameters, $this->asyncContextId), $worker);
|
||||
$this->generatorRegisteredWorkers[$worker] = true;
|
||||
}
|
||||
|
||||
private function unregisterGenerator() : void{
|
||||
foreach($this->workerPool->getRunningWorkers() as $i){
|
||||
if(isset($this->generatorRegisteredWorkers[$i])){
|
||||
$this->workerPool->submitTaskToWorker(new AsyncGeneratorUnregisterTask($this->asyncContextId), $i);
|
||||
}
|
||||
}
|
||||
$this->generatorRegisteredWorkers = [];
|
||||
}
|
||||
|
||||
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{
|
||||
$task = new PopulationTask(
|
||||
$this->asyncContextId,
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
$centerChunk,
|
||||
$adjacentChunks,
|
||||
$onCompletion
|
||||
);
|
||||
$workerId = $this->workerPool->selectWorker();
|
||||
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
|
||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||
}
|
||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||
$this->registerGeneratorToWorker($workerId);
|
||||
}
|
||||
$this->workerPool->submitTaskToWorker($task, $workerId);
|
||||
}
|
||||
|
||||
public function shutdown() : void{
|
||||
$this->unregisterGenerator();
|
||||
$this->workerPool->removeWorkerStartHook($this->workerStartHook);
|
||||
}
|
||||
}
|
@ -21,37 +21,20 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class GeneratorRegisterTask extends AsyncTask{
|
||||
public int $seed;
|
||||
public int $worldId;
|
||||
public int $worldMinY;
|
||||
public int $worldMaxY;
|
||||
class AsyncGeneratorRegisterTask extends AsyncTask{
|
||||
|
||||
/**
|
||||
* @phpstan-param class-string<Generator> $generatorClass
|
||||
*/
|
||||
public function __construct(
|
||||
World $world,
|
||||
public string $generatorClass,
|
||||
public string $generatorSettings
|
||||
){
|
||||
$this->seed = $world->getSeed();
|
||||
$this->worldId = $world->getId();
|
||||
$this->worldMinY = $world->getMinY();
|
||||
$this->worldMaxY = $world->getMaxY();
|
||||
}
|
||||
private readonly GeneratorExecutorSetupParameters $setupParameters,
|
||||
private readonly int $contextId
|
||||
){}
|
||||
|
||||
public function onRun() : void{
|
||||
/**
|
||||
* @var Generator $generator
|
||||
* @see Generator::__construct()
|
||||
*/
|
||||
$generator = new $this->generatorClass($this->seed, $this->generatorSettings);
|
||||
ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId);
|
||||
$setupParameters = $this->setupParameters;
|
||||
$generator = $setupParameters->createGenerator();
|
||||
ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $setupParameters->worldMinY, $setupParameters->worldMaxY), $this->contextId);
|
||||
}
|
||||
}
|
@ -21,19 +21,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class GeneratorUnregisterTask extends AsyncTask{
|
||||
public int $worldId;
|
||||
|
||||
public function __construct(World $world){
|
||||
$this->worldId = $world->getId();
|
||||
}
|
||||
class AsyncGeneratorUnregisterTask extends AsyncTask{
|
||||
public function __construct(
|
||||
private readonly int $contextId
|
||||
){}
|
||||
|
||||
public function onRun() : void{
|
||||
ThreadLocalGeneratorContext::unregister($this->worldId);
|
||||
ThreadLocalGeneratorContext::unregister($this->contextId);
|
||||
}
|
||||
}
|
38
src/world/generator/executor/GeneratorExecutor.php
Normal file
38
src/world/generator/executor/GeneratorExecutor.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\world\format\Chunk;
|
||||
|
||||
interface GeneratorExecutor{
|
||||
/**
|
||||
* @param Chunk[]|null[] $adjacentChunks
|
||||
* @phpstan-param array<int, Chunk|null> $adjacentChunks
|
||||
* @phpstan-param \Closure(Chunk $centerChunk, array<int, Chunk> $adjacentChunks) : void $onCompletion
|
||||
*/
|
||||
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void;
|
||||
|
||||
public function shutdown() : void;
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pmmp\thread\ThreadSafe;
|
||||
use pocketmine\world\generator\Generator;
|
||||
|
||||
final class GeneratorExecutorSetupParameters extends ThreadSafe{
|
||||
|
||||
/**
|
||||
* @phpstan-param class-string<covariant \pocketmine\world\generator\Generator> $generatorClass
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $worldMinY,
|
||||
public readonly int $worldMaxY,
|
||||
public readonly int $generatorSeed,
|
||||
public readonly string $generatorClass,
|
||||
public readonly string $generatorSettings,
|
||||
){}
|
||||
|
||||
public function createGenerator() : Generator{
|
||||
/**
|
||||
* @var Generator $generator
|
||||
* @see Generator::__construct()
|
||||
*/
|
||||
$generator = new $this->generatorClass($this->generatorSeed, $this->generatorSettings);
|
||||
return $generator;
|
||||
}
|
||||
}
|
61
src/world/generator/executor/SyncGeneratorExecutor.php
Normal file
61
src/world/generator/executor/SyncGeneratorExecutor.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\generator\Generator;
|
||||
use pocketmine\world\generator\PopulationUtils;
|
||||
|
||||
final class SyncGeneratorExecutor implements GeneratorExecutor{
|
||||
|
||||
private readonly Generator $generator;
|
||||
private readonly int $worldMinY;
|
||||
private readonly int $worldMaxY;
|
||||
|
||||
public function __construct(
|
||||
GeneratorExecutorSetupParameters $setupParameters
|
||||
){
|
||||
$this->generator = $setupParameters->createGenerator();
|
||||
$this->worldMinY = $setupParameters->worldMinY;
|
||||
$this->worldMaxY = $setupParameters->worldMaxY;
|
||||
}
|
||||
|
||||
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{
|
||||
[$centerChunk, $adjacentChunks] = PopulationUtils::populateChunkWithAdjacents(
|
||||
$this->worldMinY,
|
||||
$this->worldMaxY,
|
||||
$this->generator,
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
$centerChunk,
|
||||
$adjacentChunks
|
||||
);
|
||||
|
||||
$onCompletion($centerChunk, $adjacentChunks);
|
||||
}
|
||||
|
||||
public function shutdown() : void{
|
||||
//NOOP
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\generator;
|
||||
namespace pocketmine\world\generator\executor;
|
||||
|
||||
use pocketmine\world\generator\Generator;
|
||||
|
||||
/**
|
||||
* Manages thread-local caches for generators and the things needed to support them
|
@ -39,7 +39,8 @@ class EntityAttackNoDamageSound implements Sound{
|
||||
-1,
|
||||
"minecraft:player",
|
||||
false,
|
||||
false
|
||||
false,
|
||||
-1
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,8 @@ class EntityAttackSound implements Sound{
|
||||
-1,
|
||||
"minecraft:player",
|
||||
false,
|
||||
false
|
||||
false,
|
||||
-1
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ class EntityLandSound implements Sound{
|
||||
TypeConverter::getInstance()->getBlockTranslator()->internalIdToNetworkId($this->blockLandedOn->getStateId()),
|
||||
$this->entity::getNetworkTypeId(),
|
||||
false, //TODO: does isBaby have any relevance here?
|
||||
false
|
||||
false,
|
||||
$this->entity->getId()
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ class EntityLongFallSound implements Sound{
|
||||
-1,
|
||||
$this->entity::getNetworkTypeId(),
|
||||
false, //TODO: is isBaby relevant here?
|
||||
false
|
||||
false,
|
||||
$this->entity->getId()
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ class EntityShortFallSound implements Sound{
|
||||
-1,
|
||||
$this->entity::getNetworkTypeId(),
|
||||
false, //TODO: does isBaby have any relevance here?
|
||||
false
|
||||
false,
|
||||
-1
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
35
src/world/sound/RespawnAnchorChargeSound.php
Normal file
35
src/world/sound/RespawnAnchorChargeSound.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\sound;
|
||||
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||
|
||||
class RespawnAnchorChargeSound implements Sound{
|
||||
|
||||
public function encode(Vector3 $pos) : array{
|
||||
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::RESPAWN_ANCHOR_CHARGE, $pos, false)];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user