mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-13 12:55:06 +00:00
Compare commits
78 Commits
5.24.0
...
feat/anvil
Author | SHA1 | Date | |
---|---|---|---|
084feaef53 | |||
eaab4b35d2 | |||
f346799920 | |||
11135c2fd8 | |||
b4cb09fe5e | |||
a1695a52d5 | |||
51d45bedce | |||
ac7b5b3b13 | |||
7af5eb3da2 | |||
95284bc9de | |||
2291546610 | |||
aad2bce9e4 | |||
09f0ce458c | |||
50a1e59aa4 | |||
e8824a36b9 | |||
b1e63e544f | |||
9e9f8a4870 | |||
d0d84d4c51 | |||
9382e6e5b3 | |||
e3e0c14275 | |||
afac178cf4 | |||
e2f5e3e73c | |||
3a2d0d77d1 | |||
32b98dcbde | |||
092ea07d51 | |||
706f391068 | |||
7c654271a8 | |||
19425e35ea | |||
3df2bdb879 | |||
1fed9f6cb5 | |||
3050af0bc0 | |||
a08b06d322 | |||
c876253f76 | |||
67272f8f2b | |||
77be5f8e25 | |||
9744bd7073 | |||
51cf6817b1 | |||
fd04894a7b | |||
d2d6a59c72 | |||
788ee9a008 | |||
34f801ee3c | |||
d96ef21c4d | |||
246c1776df | |||
03e4b53ac4 | |||
91ac64783f | |||
9402a20ee3 | |||
2670e81668 | |||
e29aa2f337 | |||
5ef89200c6 | |||
9b3b45258a | |||
ca4debbf08 | |||
39e69276a1 | |||
94797e3afa | |||
03f98ee0a9 | |||
70368ea653 | |||
9d6a0cc738 | |||
21ccd90147 | |||
0a9a45a126 | |||
88937f1785 | |||
07987890a0 | |||
e5a783cb9e | |||
70fb9bbdfd | |||
f60120a75e | |||
fc86d3a44e | |||
b3f0ed23ca | |||
5b9dc2c275 | |||
947c8a0621 | |||
c77a72f15a | |||
b9df798796 | |||
7cfb6eea51 | |||
1cc809c332 | |||
b1a773ceb8 | |||
654b44447e | |||
804731d87f | |||
726e2cba23 | |||
54f746fc11 | |||
44c3e03598 | |||
e4f979dadf |
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
uses: docker/build-push-action@v6.15.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.10.0
|
||||
uses: docker/build-push-action@v6.15.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.10.0
|
||||
uses: docker/build-push-action@v6.15.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.10.0
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
4
.github/workflows/draft-release.yml
vendored
4
.github/workflows/draft-release.yml
vendored
@ -165,7 +165,7 @@ jobs:
|
||||
${{ github.workspace }}/core-permissions.rst
|
||||
|
||||
- name: Create draft release
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
uses: ncipollo/release-action@v1.16.0
|
||||
id: create-draft
|
||||
with:
|
||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
|
||||
@ -182,6 +182,8 @@ jobs:
|
||||
|
||||
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
|
||||
|
||||
:warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.**
|
||||
|
||||
- name: Post draft release URL on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
|
@ -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.
|
||||
|
||||
|
@ -36,7 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*/
|
||||
$options = [
|
||||
"base_version" => VersionInfo::BASE_VERSION,
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0],
|
||||
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
|
||||
"changelog_file_name" => function() : string{
|
||||
|
@ -28,6 +28,7 @@ use function dirname;
|
||||
use function fclose;
|
||||
use function fopen;
|
||||
use function fwrite;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function scandir;
|
||||
use function str_replace;
|
||||
@ -59,7 +60,7 @@ foreach($files as $file){
|
||||
continue;
|
||||
}
|
||||
$path = Path::join(BEDROCK_DATA_PATH, $file);
|
||||
if(!is_file($path)){
|
||||
if(!is_file($path) && !is_dir($path)){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -67,6 +68,7 @@ foreach($files as $file){
|
||||
'README.md',
|
||||
'LICENSE',
|
||||
'composer.json',
|
||||
'.github'
|
||||
] as $ignored){
|
||||
if($file === $ignored){
|
||||
continue 2;
|
||||
|
Submodule build/php updated: ae946949c5...1549433797
52
changelogs/5.25.md
Normal file
52
changelogs/5.25.md
Normal file
@ -0,0 +1,52 @@
|
||||
# 5.25.0
|
||||
Released 16th February 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.60. It also includes some minor API additions supporting new features in this version.
|
||||
|
||||
**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.60.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Documentation
|
||||
- Fixed the documentation of `Utils::getOS()`. It now refers to the `Utils::OS_*` constants instead of a list of hardcoded strings.
|
||||
|
||||
## API
|
||||
### `pocketmine\inventory`
|
||||
This release allows plugins to decide which creative tab they want to add their new items to.
|
||||
It also allows creating new collapsible groups of items, and modifying or removing existing ones.
|
||||
|
||||
- The following new methods have been added:
|
||||
- `public CreativeInventory->getAllEntries() : list<CreativeInventoryEntry>` - returns an array of objects, each containing a creative item and information about its category and collapsible group (if applicable).
|
||||
- `public CreativeInventory->getEntry(int $index) : ?CreativeInventoryEntry` - returns the creative item with the specified identifier, or `null` if not found
|
||||
- The following methods have signature changes:
|
||||
- `CreativeInventory->add()` now accepts two additional optional parameters: `CreativeCategory $category, ?CreativeGroup $group`. If not specified, the item will be added to the Items tab without a group.
|
||||
- The following new classes have been added:
|
||||
- `CreativeCategory` - enum of possible creative inventory categories (each has its own tab on the GUI)
|
||||
- `CreativeGroup` - contains information about a collapsible group of creative items, including tooltip text and icon
|
||||
- `CreativeInventoryEntry` - contains information about a creative inventory item, including its category and collapsible group (if applicable)
|
||||
|
||||
## Internals
|
||||
- `CreativeContentPacket` is no longer fully cached due to the requirement for translation context during construction. The individual items are still cached (which is the most expensive part), but the packet itself is now constructed on demand, and group entries are constructed on the fly. This may affect performance, but this has not been investigated.
|
||||
- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files.
|
||||
- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category.
|
||||
- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases.
|
||||
|
||||
# 5.25.1
|
||||
Released 26th February 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero.
|
||||
- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.)
|
||||
- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join.
|
||||
|
||||
# 5.25.2
|
||||
Released 4th March 2025.
|
||||
|
||||
## Fixes
|
||||
- Added limits to various `explode()` calls.
|
@ -33,18 +33,18 @@
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"netresearch/jsonmapper": "~v5.0.0",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60",
|
||||
"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.22.0",
|
||||
"pocketmine/locale-data": "~2.24.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.1.0",
|
||||
"pocketmine/raklib": "~1.1.0",
|
||||
"pocketmine/raklib-ipc": "~1.0.0",
|
||||
"pocketmine/snooze": "^0.5.0",
|
||||
@ -52,7 +52,7 @@
|
||||
"symfony/filesystem": "~6.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "2.1.1",
|
||||
"phpstan/phpstan": "2.1.6",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^10.5.24"
|
||||
|
156
composer.lock
generated
156
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": "994ccffe45f066768542019f6f9d237b",
|
||||
"content-hash": "2a56fc6dee1dac2ade34d965aa49dc82",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -67,16 +67,16 @@
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.12.1",
|
||||
"version": "0.12.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/math.git",
|
||||
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
|
||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
|
||||
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||
"reference": "901eddb1e45a8e0f689302e40af871c181ecbe40",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -85,7 +85,7 @@
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"vimeo/psalm": "5.16.0"
|
||||
"vimeo/psalm": "6.8.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -115,7 +115,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/math/issues",
|
||||
"source": "https://github.com/brick/math/tree/0.12.1"
|
||||
"source": "https://github.com/brick/math/tree/0.12.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -123,7 +123,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-29T23:19:16+00:00"
|
||||
"time": "2025-02-26T10:21:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "netresearch/jsonmapper",
|
||||
@ -178,16 +178,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-block-upgrade-schema",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -198,22 +198,22 @@
|
||||
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0"
|
||||
},
|
||||
"time": "2024-11-03T14:13:50+00:00"
|
||||
"time": "2025-02-11T17:41:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "2.15.0+bedrock-1.21.50",
|
||||
"version": "4.0.0+bedrock-1.21.60",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"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.50"
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60"
|
||||
},
|
||||
"time": "2024-12-04T12:59:12+00:00"
|
||||
"time": "2025-02-16T15:56:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-item-upgrade-schema",
|
||||
@ -256,16 +256,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "35.0.3+bedrock-1.21.50",
|
||||
"version": "36.0.0+bedrock-1.21.60",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c"
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c4d62581cb62d29ec426914c6b4d7e0ff835da9c",
|
||||
"reference": "c4d62581cb62d29ec426914c6b4d7e0ff835da9c",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"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/35.0.3+bedrock-1.21.50"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60"
|
||||
},
|
||||
"time": "2025-01-07T23:06:29+00:00"
|
||||
"time": "2025-02-16T15:59:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -471,16 +471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.22.1",
|
||||
"version": "2.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49"
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/fa4e377c437391cfcfdedd08eea3a848eabd1b49",
|
||||
"reference": "fa4e377c437391cfcfdedd08eea3a848eabd1b49",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"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.22.1"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.24.0"
|
||||
},
|
||||
"time": "2024-12-06T14:44:17+00:00"
|
||||
"time": "2025-02-16T20:46:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -576,16 +576,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/nbt",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/NBT.git",
|
||||
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d"
|
||||
"reference": "cfd53a86166b851786967fc560cdb372e66fde96"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
|
||||
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
|
||||
"url": "https://api.github.com/repos/pmmp/NBT/zipball/cfd53a86166b851786967fc560cdb372e66fde96",
|
||||
"reference": "cfd53a86166b851786967fc560cdb372e66fde96",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -612,9 +612,9 @@
|
||||
"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.0"
|
||||
},
|
||||
"time": "2025-01-07T22:47:46+00:00"
|
||||
"time": "2025-02-01T21:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
@ -1013,8 +1013,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -1092,8 +1092,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -1150,16 +1150,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.12.1",
|
||||
"version": "1.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
|
||||
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1198,7 +1198,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1206,20 +1206,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-08T17:47:46+00:00"
|
||||
"time": "2025-02-12T12:17:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.3.1",
|
||||
"version": "v5.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
|
||||
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1262,9 +1262,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
|
||||
},
|
||||
"time": "2024-10-08T18:51:32+00:00"
|
||||
"time": "2024-12-30T11:07:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@ -1386,16 +1386,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7"
|
||||
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
|
||||
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
|
||||
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1440,20 +1440,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-05T16:43:48+00:00"
|
||||
"time": "2025-02-19T15:46:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
||||
"reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18"
|
||||
"reference": "d09e152f403c843998d7a52b5d87040c937525dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e32ac656788a5bf3dedda89e6a2cad5643bf1a18",
|
||||
"reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd",
|
||||
"reference": "d09e152f403c843998d7a52b5d87040c937525dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1489,22 +1489,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.3"
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4"
|
||||
},
|
||||
"time": "2024-12-19T09:14:43+00:00"
|
||||
"time": "2025-01-22T13:07:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3"
|
||||
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3",
|
||||
"reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
|
||||
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1537,9 +1537,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.1"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
|
||||
},
|
||||
"time": "2024-12-12T20:21:10+00:00"
|
||||
"time": "2025-01-21T10:52:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -1864,16 +1864,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.5.38",
|
||||
"version": "10.5.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
|
||||
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
|
||||
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8",
|
||||
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1883,7 +1883,7 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"myclabs/deep-copy": "^1.12.0",
|
||||
"myclabs/deep-copy": "^1.12.1",
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.1",
|
||||
@ -1945,7 +1945,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.38"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1961,7 +1961,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-28T13:06:21+00:00"
|
||||
"time": "2025-02-06T16:08:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -11,8 +11,10 @@ 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\ThreadedSupportedTypesRule
|
||||
|
||||
|
@ -264,7 +264,7 @@ JIT_WARNING
|
||||
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
|
||||
if($composerGitHash !== null){
|
||||
//we can't verify dependency versions if we were installed without using git
|
||||
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
|
||||
$currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
|
||||
if($currentGitHash !== $composerGitHash){
|
||||
critical_error("Composer dependencies and/or autoloader are out of sync.");
|
||||
critical_error("- Current revision is $currentGitHash");
|
||||
|
@ -36,6 +36,7 @@ use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
||||
use pocketmine\crash\CrashDump;
|
||||
use pocketmine\crash\CrashDumpRenderer;
|
||||
use pocketmine\data\bedrock\BedrockDataFiles;
|
||||
use pocketmine\entity\EntityDataHelper;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\event\HandlerListManager;
|
||||
@ -698,7 +699,7 @@ class Server{
|
||||
|
||||
public function removeOp(string $name) : void{
|
||||
$lowercaseName = strtolower($name);
|
||||
foreach($this->operators->getAll() as $operatorName => $_){
|
||||
foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){
|
||||
$operatorName = (string) $operatorName;
|
||||
if($lowercaseName === strtolower($operatorName)){
|
||||
$this->operators->remove($operatorName);
|
||||
@ -1005,7 +1006,7 @@ class Server{
|
||||
|
||||
$this->commandMap = new SimpleCommandMap($this);
|
||||
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes"));
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES);
|
||||
|
||||
$this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger);
|
||||
|
||||
|
@ -31,8 +31,8 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.24.0";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BASE_VERSION = "5.25.3";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
/**
|
||||
|
@ -154,7 +154,7 @@ class BlockBreakInfo{
|
||||
|
||||
$efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0);
|
||||
if($efficiency <= 0){
|
||||
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
|
||||
throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency");
|
||||
}
|
||||
|
||||
$base /= $efficiency;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\block\inventory;
|
||||
|
||||
use pocketmine\inventory\SimpleInventory;
|
||||
use pocketmine\inventory\TemporaryInventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
class AnvilInventory extends SimpleInventory implements BlockInventory, TemporaryInventory{
|
||||
@ -37,4 +38,12 @@ class AnvilInventory extends SimpleInventory implements BlockInventory, Temporar
|
||||
$this->holder = $holder;
|
||||
parent::__construct(2);
|
||||
}
|
||||
|
||||
public function getInput() : Item {
|
||||
return $this->getItem(self::SLOT_INPUT);
|
||||
}
|
||||
|
||||
public function getMaterial() : Item {
|
||||
return $this->getItem(self::SLOT_MATERIAL);
|
||||
}
|
||||
}
|
||||
|
@ -62,9 +62,10 @@ class Sign extends Spawnable{
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @deprecated
|
||||
*/
|
||||
public static function fixTextBlob(string $blob) : array{
|
||||
return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
|
||||
return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4);
|
||||
}
|
||||
|
||||
protected SignText $text;
|
||||
|
66
src/block/utils/AnvilHelper.php
Normal file
66
src/block/utils/AnvilHelper.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\block\utils;
|
||||
|
||||
use pocketmine\crafting\AnvilCraftResult;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\Server;
|
||||
|
||||
final class AnvilHelper{
|
||||
private const COST_LIMIT = 39;
|
||||
|
||||
/**
|
||||
* Attempts to calculate the result of an anvil operation.
|
||||
*
|
||||
* Returns null if the operation can't do anything.
|
||||
*/
|
||||
public static function calculateResult(Item $base, Item $material, ?string $customName, bool $isCreative) : ?AnvilCraftResult{
|
||||
|
||||
$recipe = Server::getInstance()->getCraftingManager()->matchAnvilRecipe($base, $material);
|
||||
if($recipe === null){
|
||||
return null;
|
||||
}
|
||||
$result = $recipe->getResultFor($base, $material);
|
||||
|
||||
if($result !== null){
|
||||
$resultItem = $result->getOutput();
|
||||
$xpCost = $result->getXpCost();
|
||||
if(($customName === null || $customName === "") && $resultItem->hasCustomName()){
|
||||
$xpCost++;
|
||||
$resultItem->clearCustomName();
|
||||
}elseif($customName !== null && $resultItem->getCustomName() !== $customName){
|
||||
$xpCost++;
|
||||
$resultItem->setCustomName($customName);
|
||||
}
|
||||
|
||||
$result = new AnvilCraftResult($xpCost, $resultItem, $result->getSacrificeResult());
|
||||
}
|
||||
|
||||
if($result === null || $result->getXpCost() <= 0 || ($result->getXpCost() > self::COST_LIMIT && !$isCreative)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@ class SignText{
|
||||
* @throws \InvalidArgumentException if the text is not valid UTF-8
|
||||
*/
|
||||
public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{
|
||||
return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
|
||||
return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,7 @@ use function array_values;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function str_replace;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
abstract class Command{
|
||||
|
||||
@ -113,7 +114,7 @@ abstract class Command{
|
||||
}
|
||||
|
||||
public function setPermission(?string $permission) : void{
|
||||
$this->setPermissions($permission === null ? [] : explode(";", $permission));
|
||||
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
|
||||
}
|
||||
|
||||
public function testPermission(CommandSender $target, ?string $permission = null) : bool{
|
||||
|
@ -39,6 +39,7 @@ use function ksort;
|
||||
use function min;
|
||||
use function sort;
|
||||
use function strtolower;
|
||||
use const PHP_INT_MAX;
|
||||
use const SORT_FLAG_CASE;
|
||||
use const SORT_NATURAL;
|
||||
|
||||
@ -108,7 +109,7 @@ class HelpCommand extends VanillaCommand{
|
||||
|
||||
$usage = $cmd->getUsage();
|
||||
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$aliases = $cmd->getAliases();
|
||||
|
@ -219,7 +219,11 @@ class ParticleCommand extends VanillaCommand{
|
||||
break;
|
||||
case "blockdust":
|
||||
if($data !== null){
|
||||
$d = explode("_", $data);
|
||||
//to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts
|
||||
//this allows the 4th argument to be processed normally if given without forcing it to also consume
|
||||
//any unexpected parts
|
||||
//we probably ought to error in this case, but this will do for now
|
||||
$d = explode("_", $data, limit: 5);
|
||||
if(count($d) >= 3){
|
||||
return new DustParticle(new Color(
|
||||
((int) $d[0]) & 0xff,
|
||||
|
@ -62,7 +62,7 @@ class ConsoleCommandSender implements CommandSender{
|
||||
$message = $this->getLanguage()->translate($message);
|
||||
}
|
||||
|
||||
foreach(explode("\n", trim($message)) as $line){
|
||||
foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){
|
||||
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
|
||||
}
|
||||
}
|
||||
|
66
src/crafting/AnvilCraftResult.php
Normal file
66
src/crafting/AnvilCraftResult.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\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* This class is here to hold the result of an anvil crafting process.
|
||||
*/
|
||||
final class AnvilCraftResult{
|
||||
/**
|
||||
* @param Item|null $sacrificeResult If the given item is considered as null (count <= 0), the value will be set to null.
|
||||
*/
|
||||
public function __construct(
|
||||
private int $xpCost,
|
||||
private Item $output,
|
||||
private ?Item $sacrificeResult
|
||||
){
|
||||
if($this->sacrificeResult !== null && $this->sacrificeResult->isNull()){
|
||||
$this->sacrificeResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent the amount of experience points required to craft the output item.
|
||||
*/
|
||||
public function getXpCost() : int{
|
||||
return $this->xpCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent the item given as output of the crafting process.
|
||||
*/
|
||||
public function getOutput() : Item{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* This result has to be null if the sacrifice slot need to be emptied.
|
||||
* If not null, it represent the item that will be left in the sacrifice slot after the crafting process.
|
||||
*/
|
||||
public function getSacrificeResult() : ?Item{
|
||||
return $this->sacrificeResult;
|
||||
}
|
||||
}
|
78
src/crafting/AnvilCraftingManagerDataFiller.php
Normal file
78
src/crafting/AnvilCraftingManagerDataFiller.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?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\crafting;
|
||||
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\ToolTier;
|
||||
use pocketmine\item\VanillaArmorMaterials;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
|
||||
final class AnvilCraftingManagerDataFiller{
|
||||
public static function fillData(CraftingManager $manager) : CraftingManager{
|
||||
foreach([
|
||||
[
|
||||
VanillaItems::DIAMOND(),
|
||||
[VanillaArmorMaterials::DIAMOND(), ToolTier::DIAMOND]
|
||||
], [
|
||||
VanillaItems::GOLD_INGOT(),
|
||||
[VanillaArmorMaterials::GOLD(), ToolTier::GOLD]
|
||||
], [
|
||||
VanillaItems::IRON_INGOT(),
|
||||
[VanillaArmorMaterials::IRON(), ToolTier::IRON]
|
||||
], [
|
||||
VanillaItems::NETHERITE_INGOT(),
|
||||
[VanillaArmorMaterials::NETHERITE(), ToolTier::NETHERITE]
|
||||
], [
|
||||
VanillaItems::SCUTE(),
|
||||
[VanillaArmorMaterials::TURTLE(), null]
|
||||
], [
|
||||
VanillaItems::LEATHER(),
|
||||
[VanillaArmorMaterials::LEATHER(), null]
|
||||
]
|
||||
] as [$item, [$armorMaterial, $toolTier]]){
|
||||
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
|
||||
new ArmorRecipeIngredient($armorMaterial),
|
||||
new ExactRecipeIngredient($item)
|
||||
));
|
||||
if($toolTier !== null){
|
||||
$manager->registerAnvilRecipe(new MaterialRepairRecipe(
|
||||
new TieredToolRecipeIngredient($toolTier),
|
||||
new ExactRecipeIngredient($item)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
foreach(VanillaItems::getAll() as $item){
|
||||
if($item instanceof Durable){
|
||||
$itemId = GlobalItemDataHandlers::getSerializer()->serializeType($item)->getName();
|
||||
$manager->registerAnvilRecipe(new ItemSelfCombineRecipe(
|
||||
new MetaWildcardRecipeIngredient($itemId)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $manager;
|
||||
}
|
||||
}
|
30
src/crafting/AnvilRecipe.php
Normal file
30
src/crafting/AnvilRecipe.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
interface AnvilRecipe{
|
||||
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult;
|
||||
}
|
50
src/crafting/ArmorRecipeIngredient.php
Normal file
50
src/crafting/ArmorRecipeIngredient.php
Normal file
@ -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\crafting;
|
||||
|
||||
use pocketmine\item\Armor;
|
||||
use pocketmine\item\ArmorMaterial;
|
||||
use pocketmine\item\Item;
|
||||
use function spl_object_id;
|
||||
|
||||
class ArmorRecipeIngredient implements RecipeIngredient{
|
||||
public function __construct(
|
||||
private ArmorMaterial $material
|
||||
){
|
||||
}
|
||||
|
||||
public function getMaterial() : ArmorMaterial{ return $this->material; }
|
||||
|
||||
public function accepts(Item $item) : bool{
|
||||
if($item->getCount() < 1){
|
||||
return false;
|
||||
}
|
||||
|
||||
return $item instanceof Armor && $item->getMaterial() === $this->material;
|
||||
}
|
||||
|
||||
public function __toString() : string{
|
||||
return "ArmorRecipeIngredient(ArmorMaterial@" . spl_object_id($this->material) . ")";
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -76,6 +80,18 @@ class CraftingManager{
|
||||
*/
|
||||
private array $brewingRecipeCache = [];
|
||||
|
||||
/**
|
||||
* @var AnvilRecipe[]
|
||||
* @phpstan-var list<AnvilRecipe>
|
||||
*/
|
||||
private array $anvilRecipes = [];
|
||||
|
||||
/**
|
||||
* @var AnvilRecipe[][]
|
||||
* @phpstan-var array<int, array<int, AnvilRecipe>>
|
||||
*/
|
||||
private array $anvilRecipeCache = [];
|
||||
|
||||
/** @phpstan-var ObjectSet<\Closure() : void> */
|
||||
private ObjectSet $recipeRegisteredCallbacks;
|
||||
|
||||
@ -100,6 +116,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 +125,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,6 +199,14 @@ class CraftingManager{
|
||||
return $this->potionContainerChangeRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AnvilRecipe[][]
|
||||
* @phpstan-return list<AnvilRecipe>
|
||||
*/
|
||||
public function getAnvilRecipes() : array{
|
||||
return $this->anvilRecipes;
|
||||
}
|
||||
|
||||
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
|
||||
$this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
|
||||
$this->craftingRecipeIndex[] = $recipe;
|
||||
@ -231,6 +241,14 @@ class CraftingManager{
|
||||
}
|
||||
}
|
||||
|
||||
public function registerAnvilRecipe(AnvilRecipe $recipe) : void{
|
||||
$this->anvilRecipes[] = $recipe;
|
||||
|
||||
foreach($this->recipeRegisteredCallbacks as $callback){
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item[] $outputs
|
||||
*/
|
||||
@ -304,4 +322,21 @@ class CraftingManager{
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function matchAnvilRecipe(Item $input, Item $material) : ?AnvilRecipe{
|
||||
$inputHash = $input->getStateId();
|
||||
$materialHash = $material->getStateId();
|
||||
$cached = $this->anvilRecipeCache[$inputHash][$materialHash] ?? null;
|
||||
if($cached !== null){
|
||||
return $cached;
|
||||
}
|
||||
|
||||
foreach($this->anvilRecipes as $recipe){
|
||||
if($recipe->getResultFor($input, $material) !== null){
|
||||
return $this->anvilRecipeCache[$inputHash][$materialHash] = $recipe;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -185,6 +185,7 @@ final class CraftingManagerFromDataHelper{
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*
|
||||
* @return object[]
|
||||
*
|
||||
* @phpstan-template TRecipeData of object
|
||||
@ -333,6 +334,8 @@ final class CraftingManagerFromDataHelper{
|
||||
));
|
||||
}
|
||||
|
||||
$result = AnvilCraftingManagerDataFiller::fillData($result);
|
||||
|
||||
//TODO: smithing
|
||||
|
||||
return $result;
|
||||
|
123
src/crafting/ItemCombineRecipe.php
Normal file
123
src/crafting/ItemCombineRecipe.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\crafting;
|
||||
|
||||
use pocketmine\inventory\transaction\TransactionValidationException;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\EnchantedBook;
|
||||
use pocketmine\item\enchantment\AvailableEnchantmentRegistry;
|
||||
use pocketmine\item\enchantment\EnchantmentInstance;
|
||||
use pocketmine\item\enchantment\Rarity;
|
||||
use pocketmine\item\Item;
|
||||
use function floor;
|
||||
use function max;
|
||||
use function min;
|
||||
|
||||
abstract class ItemCombineRecipe implements AnvilRecipe{
|
||||
abstract protected function validate(Item $input, Item $material) : bool;
|
||||
|
||||
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult{
|
||||
if($this->validate($input, $material)){
|
||||
$result = (clone $input);
|
||||
$xpCost = 0;
|
||||
if($result instanceof Durable && $material instanceof Durable && $this->repair($result, $material)){
|
||||
// The two items are compatible for repair
|
||||
$xpCost = 2;
|
||||
}
|
||||
|
||||
// combining enchantments
|
||||
foreach($material->getEnchantments() as $instance){
|
||||
$enchantment = $instance->getType();
|
||||
$level = $instance->getLevel();
|
||||
if(!AvailableEnchantmentRegistry::getInstance()->isAvailableForItem($enchantment, $input)){
|
||||
continue;
|
||||
}
|
||||
if(($targetEnchantment = $input->getEnchantment($enchantment)) !== null){
|
||||
// Enchant already present on the target item
|
||||
$targetLevel = $targetEnchantment->getLevel();
|
||||
$newLevel = ($targetLevel === $level ? $targetLevel + 1 : max($targetLevel, $level));
|
||||
$level = min($newLevel, $enchantment->getMaxLevel());
|
||||
$instance = new EnchantmentInstance($enchantment, $level);
|
||||
}else{
|
||||
// Check if the enchantment is compatible with the existing enchantments
|
||||
foreach($input->getEnchantments() as $testedInstance){
|
||||
$testedEnchantment = $testedInstance->getType();
|
||||
if(!$testedEnchantment->isCompatibleWith($enchantment)){
|
||||
$xpCost++;
|
||||
//TODO: XP COST
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$costAddition = match ($enchantment->getRarity()) {
|
||||
Rarity::COMMON => 1,
|
||||
Rarity::UNCOMMON => 2,
|
||||
Rarity::RARE => 4,
|
||||
Rarity::MYTHIC => 8,
|
||||
default => throw new TransactionValidationException("Invalid rarity " . $enchantment->getRarity() . " found")
|
||||
};
|
||||
|
||||
if($material instanceof EnchantedBook){
|
||||
// Enchanted books are half as expensive to combine
|
||||
$costAddition = max(1, $costAddition / 2);
|
||||
}
|
||||
$levelDifference = $instance->getLevel() - $input->getEnchantmentLevel($instance->getType());
|
||||
$xpCost += (int) floor($costAddition * $levelDifference);
|
||||
$result->addEnchantment($instance);
|
||||
|
||||
$xpCost += 2 ** $input->getAnvilRepairCost() - 1;
|
||||
$xpCost += 2 ** $material->getAnvilRepairCost() - 1;
|
||||
$result->setAnvilRepairCost(
|
||||
max($result->getAnvilRepairCost(), $material->getAnvilRepairCost()) + 1
|
||||
);
|
||||
}
|
||||
|
||||
if($xpCost !== 0){
|
||||
return new AnvilCraftResult($xpCost, $result, null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function repair(Durable $result, Durable $material) : bool{
|
||||
$damage = $result->getDamage();
|
||||
if($damage === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
$baseMaxDurability = $result->getMaxDurability();
|
||||
$baseDurability = $baseMaxDurability - $damage;
|
||||
$materialDurability = $material->getMaxDurability() - $material->getDamage();
|
||||
$addDurability = (int) ($baseMaxDurability * 12 / 100);
|
||||
|
||||
$result->setDamage($baseMaxDurability - min(
|
||||
$baseMaxDurability,
|
||||
$baseDurability + $materialDurability + $addDurability
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
41
src/crafting/ItemDifferentCombineRecipe.php
Normal file
41
src/crafting/ItemDifferentCombineRecipe.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\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* Represent a recipe that repair an item with a material in an anvil.
|
||||
*/
|
||||
class ItemDifferentCombineRecipe extends ItemCombineRecipe{
|
||||
public function __construct(
|
||||
private RecipeIngredient $base,
|
||||
private RecipeIngredient $material
|
||||
){
|
||||
}
|
||||
|
||||
protected function validate(Item $input, Item $material) : bool{
|
||||
return $this->base->accepts($input) && $this->material->accepts($material);
|
||||
}
|
||||
}
|
44
src/crafting/ItemSelfCombineRecipe.php
Normal file
44
src/crafting/ItemSelfCombineRecipe.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
/**
|
||||
* Represent a recipe that repair an item with a material in an anvil.
|
||||
*/
|
||||
class ItemSelfCombineRecipe extends ItemCombineRecipe{
|
||||
/**
|
||||
* @param RecipeIngredient $target The item that will be concerned by the combinaison.
|
||||
* The input and material have to be accepted by this ingredient to be able to combine them.
|
||||
*/
|
||||
public function __construct(
|
||||
private RecipeIngredient $target
|
||||
){
|
||||
}
|
||||
|
||||
protected function validate(Item $input, Item $material) : bool{
|
||||
return $this->target->accepts($input) && $this->target->accepts($material);
|
||||
}
|
||||
}
|
69
src/crafting/MaterialRepairRecipe.php
Normal file
69
src/crafting/MaterialRepairRecipe.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\crafting;
|
||||
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
use function ceil;
|
||||
use function floor;
|
||||
use function max;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
* Represent a recipe that repair an item with a material in an anvil.
|
||||
*/
|
||||
class MaterialRepairRecipe implements AnvilRecipe{
|
||||
public function __construct(
|
||||
private RecipeIngredient $input,
|
||||
private RecipeIngredient $material
|
||||
){
|
||||
}
|
||||
|
||||
public function getInput() : RecipeIngredient{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
public function getMaterial() : RecipeIngredient{
|
||||
return $this->material;
|
||||
}
|
||||
|
||||
public function getResultFor(Item $input, Item $material) : ?AnvilCraftResult{
|
||||
if($this->input->accepts($input) && $this->material->accepts($material) && $input instanceof Durable){
|
||||
$damage = $input->getDamage();
|
||||
if($damage !== 0){
|
||||
$quarter = min($damage, (int) floor($input->getMaxDurability() / 4));
|
||||
$numberRepair = min($material->getCount(), (int) ceil($damage / $quarter));
|
||||
$damage -= $quarter * $numberRepair;
|
||||
|
||||
return new AnvilCraftResult(
|
||||
$numberRepair,
|
||||
(clone $input)->setDamage(max(0, $damage)),
|
||||
(clone $material)->setCount($material->getCount() - $numberRepair)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
49
src/crafting/TieredToolRecipeIngredient.php
Normal file
49
src/crafting/TieredToolRecipeIngredient.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?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\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\TieredTool;
|
||||
use pocketmine\item\ToolTier;
|
||||
|
||||
class TieredToolRecipeIngredient implements RecipeIngredient{
|
||||
public function __construct(
|
||||
private ToolTier $tier
|
||||
){
|
||||
}
|
||||
|
||||
public function getTier() : ToolTier{ return $this->tier; }
|
||||
|
||||
public function accepts(Item $item) : bool{
|
||||
if($item->getCount() < 1){
|
||||
return false;
|
||||
}
|
||||
|
||||
return $item instanceof TieredTool && $item->getTier() === $this->tier;
|
||||
}
|
||||
|
||||
public function __toString() : string{
|
||||
return "TieredToolRecipeIngredient(" . $this->tier->name . ")";
|
||||
}
|
||||
}
|
@ -39,14 +39,16 @@ final class BedrockDataFiles{
|
||||
public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json';
|
||||
public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt';
|
||||
public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json';
|
||||
public const CREATIVEITEMS_JSON = BEDROCK_DATA_PATH . '/creativeitems.json';
|
||||
public const CREATIVE = BEDROCK_DATA_PATH . '/creative';
|
||||
public const ENTITY_ID_MAP_JSON = BEDROCK_DATA_PATH . '/entity_id_map.json';
|
||||
public const ENTITY_IDENTIFIERS_NBT = BEDROCK_DATA_PATH . '/entity_identifiers.nbt';
|
||||
public const ENUMS = BEDROCK_DATA_PATH . '/enums';
|
||||
public const ENUMS_PY = BEDROCK_DATA_PATH . '/enums.py';
|
||||
public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json';
|
||||
public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json';
|
||||
public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json';
|
||||
public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json';
|
||||
public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin';
|
||||
public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json';
|
||||
public const RECIPES = BEDROCK_DATA_PATH . '/recipes';
|
||||
public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ final class BlockStateData{
|
||||
public const CURRENT_VERSION =
|
||||
(1 << 24) | //major
|
||||
(21 << 16) | //minor
|
||||
(40 << 8) | //patch
|
||||
(1); //revision
|
||||
(60 << 8) | //patch
|
||||
(33); //revision
|
||||
|
||||
public const TAG_NAME = "name";
|
||||
public const TAG_STATES = "states";
|
||||
|
@ -59,6 +59,7 @@ final class BlockStateNames{
|
||||
public const COVERED_BIT = "covered_bit";
|
||||
public const CRACKED_STATE = "cracked_state";
|
||||
public const CRAFTING = "crafting";
|
||||
public const CREAKING_HEART_STATE = "creaking_heart_state";
|
||||
public const DEAD_BIT = "dead_bit";
|
||||
public const DEPRECATED = "deprecated";
|
||||
public const DIRECTION = "direction";
|
||||
|
@ -56,6 +56,10 @@ final class BlockStateStringValues{
|
||||
public const CRACKED_STATE_MAX_CRACKED = "max_cracked";
|
||||
public const CRACKED_STATE_NO_CRACKS = "no_cracks";
|
||||
|
||||
public const CREAKING_HEART_STATE_AWAKE = "awake";
|
||||
public const CREAKING_HEART_STATE_DORMANT = "dormant";
|
||||
public const CREAKING_HEART_STATE_UPROOTED = "uprooted";
|
||||
|
||||
public const DRIPSTONE_THICKNESS_BASE = "base";
|
||||
public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum";
|
||||
public const DRIPSTONE_THICKNESS_MERGE = "merge";
|
||||
|
@ -131,7 +131,8 @@ final class BlockStateDeserializerHelper{
|
||||
//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
|
||||
return $block
|
||||
->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT))
|
||||
->setFacing(Facing::rotateY($in->readLegacyHorizontalFacing(), false))
|
||||
//a door facing "east" is actually facing north - thanks mojang
|
||||
->setFacing(Facing::rotateY($in->readCardinalHorizontalFacing(), clockwise: false))
|
||||
->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT))
|
||||
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
|
||||
}
|
||||
@ -145,7 +146,7 @@ final class BlockStateDeserializerHelper{
|
||||
/** @throws BlockStateDeserializeException */
|
||||
public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{
|
||||
return $block
|
||||
->setFacing($in->readLegacyHorizontalFacing())
|
||||
->setFacing($in->readCardinalHorizontalFacing())
|
||||
->setInWall($in->readBool(BlockStateNames::IN_WALL_BIT))
|
||||
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
|
||||
}
|
||||
|
@ -100,7 +100,8 @@ final class BlockStateSerializerHelper{
|
||||
public static function encodeDoor(Door $block, Writer $out) : Writer{
|
||||
return $out
|
||||
->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
|
||||
->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true))
|
||||
//a door facing north is encoded as "east"
|
||||
->writeCardinalHorizontalFacing(Facing::rotateY($block->getFacing(), clockwise: true))
|
||||
->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight())
|
||||
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
|
||||
}
|
||||
@ -112,7 +113,7 @@ final class BlockStateSerializerHelper{
|
||||
|
||||
public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{
|
||||
return $out
|
||||
->writeLegacyHorizontalFacing($block->getFacing())
|
||||
->writeCardinalHorizontalFacing($block->getFacing())
|
||||
->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall())
|
||||
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
|
||||
}
|
||||
|
@ -103,10 +103,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
|
||||
/** @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 */
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -513,6 +513,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
AbilitiesLayer::LAYER_BASE,
|
||||
array_fill(0, AbilitiesLayer::NUMBER_OF_ABILITIES, false),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
)
|
||||
])),
|
||||
|
86
src/event/player/PlayerUseAnvilEvent.php
Normal file
86
src/event/player/PlayerUseAnvilEvent.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?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\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
/**
|
||||
* Called when a player uses an anvil (renaming, repairing, combining items).
|
||||
* This event is called once per action even if multiple tasks are performed at once.
|
||||
*/
|
||||
class PlayerUseAnvilEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
public function __construct(
|
||||
Player $player,
|
||||
private Item $baseItem,
|
||||
private ?Item $materialItem,
|
||||
private Item $resultItem,
|
||||
private ?string $customName,
|
||||
private int $xpCost
|
||||
){
|
||||
$this->player = $player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that the player is using as the base item (left slot).
|
||||
*/
|
||||
public function getBaseItem() : Item{
|
||||
return $this->baseItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that the player is using as the material item (right slot), or null if there is no material item
|
||||
* (e.g. when renaming an item).
|
||||
*/
|
||||
public function getMaterialItem() : ?Item{
|
||||
return $this->materialItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item that the player will receive as a result of the anvil operation.
|
||||
*/
|
||||
public function getResultItem() : Item{
|
||||
return $this->resultItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the custom name that the player is setting on the item, or null if the player is not renaming the item.
|
||||
*
|
||||
* This value is defined when the base item is already renamed.
|
||||
*/
|
||||
public function getCustomName() : ?string{
|
||||
return $this->customName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of XP levels that the player will spend on this anvil operation.
|
||||
*/
|
||||
public function getXpCost() : int{
|
||||
return $this->xpCost;
|
||||
}
|
||||
}
|
34
src/inventory/CreativeCategory.php
Normal file
34
src/inventory/CreativeCategory.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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\inventory;
|
||||
|
||||
/**
|
||||
* Available tabs in the creative inventory that an item can be displayed in.
|
||||
*/
|
||||
enum CreativeCategory{
|
||||
case CONSTRUCTION;
|
||||
case NATURE;
|
||||
case EQUIPMENT;
|
||||
case ITEMS;
|
||||
}
|
51
src/inventory/CreativeGroup.php
Normal file
51
src/inventory/CreativeGroup.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\lang\Translatable;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Info for an item group in the creative inventory menu.
|
||||
*/
|
||||
final class CreativeGroup{
|
||||
/**
|
||||
* @param Translatable|string $name Tooltip shown to the player on hover
|
||||
* @param Item $icon Item shown when the group is collapsed
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Translatable|string $name,
|
||||
private readonly Item $icon
|
||||
){
|
||||
$nameLength = $name instanceof Translatable ? strlen($name->getText()) : strlen($name);
|
||||
if($nameLength === 0){
|
||||
throw new \InvalidArgumentException("Creative group name cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() : Translatable|string{ return $this->name; }
|
||||
|
||||
public function getIcon() : Item{ return clone $this->icon; }
|
||||
}
|
@ -24,21 +24,24 @@ declare(strict_types=1);
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\crafting\CraftingManagerFromDataHelper;
|
||||
use pocketmine\crafting\json\ItemStackData;
|
||||
use pocketmine\data\bedrock\BedrockDataFiles;
|
||||
use pocketmine\inventory\json\CreativeGroupData;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\utils\DestructorCallbackTrait;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
|
||||
final class CreativeInventory{
|
||||
use SingletonTrait;
|
||||
use DestructorCallbackTrait;
|
||||
|
||||
/**
|
||||
* @var Item[]
|
||||
* @phpstan-var array<int, Item>
|
||||
* @var CreativeInventoryEntry[]
|
||||
* @phpstan-var array<int, CreativeInventoryEntry>
|
||||
*/
|
||||
private array $creative = [];
|
||||
|
||||
@ -47,17 +50,32 @@ final class CreativeInventory{
|
||||
|
||||
private function __construct(){
|
||||
$this->contentChangedCallbacks = new ObjectSet();
|
||||
$creativeItems = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile(
|
||||
BedrockDataFiles::CREATIVEITEMS_JSON,
|
||||
ItemStackData::class
|
||||
|
||||
foreach([
|
||||
"construction" => CreativeCategory::CONSTRUCTION,
|
||||
"nature" => CreativeCategory::NATURE,
|
||||
"equipment" => CreativeCategory::EQUIPMENT,
|
||||
"items" => CreativeCategory::ITEMS,
|
||||
] as $categoryId => $categoryEnum){
|
||||
$groups = CraftingManagerFromDataHelper::loadJsonArrayOfObjectsFile(
|
||||
Path::join(BedrockDataFiles::CREATIVE, $categoryId . ".json"),
|
||||
CreativeGroupData::class
|
||||
);
|
||||
foreach($creativeItems as $data){
|
||||
$item = CraftingManagerFromDataHelper::deserializeItemStack($data);
|
||||
if($item === null){
|
||||
//unknown item
|
||||
continue;
|
||||
|
||||
foreach($groups as $groupData){
|
||||
$icon = $groupData->group_icon === null ? null : CraftingManagerFromDataHelper::deserializeItemStack($groupData->group_icon);
|
||||
|
||||
$group = $icon === null ? null : new CreativeGroup(
|
||||
new Translatable($groupData->group_name),
|
||||
$icon
|
||||
);
|
||||
|
||||
$items = array_filter(array_map(static fn($itemStack) => CraftingManagerFromDataHelper::deserializeItemStack($itemStack), $groupData->items));
|
||||
|
||||
foreach($items as $item){
|
||||
$this->add($item, $categoryEnum, $group);
|
||||
}
|
||||
}
|
||||
$this->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,16 +93,28 @@ final class CreativeInventory{
|
||||
* @phpstan-return array<int, Item>
|
||||
*/
|
||||
public function getAll() : array{
|
||||
return Utils::cloneObjectArray($this->creative);
|
||||
return array_map(fn(CreativeInventoryEntry $entry) => $entry->getItem(), $this->creative);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CreativeInventoryEntry[]
|
||||
* @phpstan-return array<int, CreativeInventoryEntry>
|
||||
*/
|
||||
public function getAllEntries() : array{
|
||||
return $this->creative;
|
||||
}
|
||||
|
||||
public function getItem(int $index) : ?Item{
|
||||
return isset($this->creative[$index]) ? clone $this->creative[$index] : null;
|
||||
return $this->getEntry($index)?->getItem();
|
||||
}
|
||||
|
||||
public function getEntry(int $index) : ?CreativeInventoryEntry{
|
||||
return $this->creative[$index] ?? null;
|
||||
}
|
||||
|
||||
public function getItemIndex(Item $item) : int{
|
||||
foreach($this->creative as $i => $d){
|
||||
if($item->equals($d, true, false)){
|
||||
if($d->matchesItem($item)){
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
@ -96,8 +126,8 @@ final class CreativeInventory{
|
||||
* Adds an item to the creative menu.
|
||||
* Note: Players who are already online when this is called will not see this change.
|
||||
*/
|
||||
public function add(Item $item) : void{
|
||||
$this->creative[] = clone $item;
|
||||
public function add(Item $item, CreativeCategory $category = CreativeCategory::ITEMS, ?CreativeGroup $group = null) : void{
|
||||
$this->creative[] = new CreativeInventoryEntry($item, $category, $group);
|
||||
$this->onContentChange();
|
||||
}
|
||||
|
||||
|
48
src/inventory/CreativeInventoryEntry.php
Normal file
48
src/inventory/CreativeInventoryEntry.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\inventory;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
final class CreativeInventoryEntry{
|
||||
private readonly Item $item;
|
||||
|
||||
public function __construct(
|
||||
Item $item,
|
||||
private readonly CreativeCategory $category,
|
||||
private readonly ?CreativeGroup $group = null
|
||||
){
|
||||
$this->item = clone $item;
|
||||
}
|
||||
|
||||
public function getItem() : Item{ return clone $this->item; }
|
||||
|
||||
public function getCategory() : CreativeCategory{ return $this->category; }
|
||||
|
||||
public function getGroup() : ?CreativeGroup{ return $this->group; }
|
||||
|
||||
public function matchesItem(Item $item) : bool{
|
||||
return $item->equals($this->item, checkDamage: true, checkCompound: false);
|
||||
}
|
||||
}
|
38
src/inventory/json/CreativeGroupData.php
Normal file
38
src/inventory/json/CreativeGroupData.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\inventory\json;
|
||||
|
||||
use pocketmine\crafting\json\ItemStackData;
|
||||
|
||||
final class CreativeGroupData{
|
||||
/** @required */
|
||||
public string $group_name;
|
||||
/** @required */
|
||||
public ?ItemStackData $group_icon;
|
||||
/**
|
||||
* @var \pocketmine\crafting\json\ItemStackData[]
|
||||
* @required
|
||||
*/
|
||||
public array $items;
|
||||
}
|
155
src/inventory/transaction/AnvilTransaction.php
Normal file
155
src/inventory/transaction/AnvilTransaction.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?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\inventory\transaction;
|
||||
|
||||
use pocketmine\block\Anvil;
|
||||
use pocketmine\block\inventory\AnvilInventory;
|
||||
use pocketmine\block\utils\AnvilHelper;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\crafting\AnvilCraftResult;
|
||||
use pocketmine\event\player\PlayerUseAnvilEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\world\sound\AnvilBreakSound;
|
||||
use pocketmine\world\sound\AnvilUseSound;
|
||||
use function count;
|
||||
use function mt_rand;
|
||||
|
||||
class AnvilTransaction extends InventoryTransaction{
|
||||
private ?Item $baseItem = null;
|
||||
private ?Item $materialItem = null;
|
||||
|
||||
public function __construct(
|
||||
Player $source,
|
||||
private readonly AnvilCraftResult $expectedResult,
|
||||
private readonly ?string $customName
|
||||
){
|
||||
parent::__construct($source);
|
||||
}
|
||||
|
||||
private function validateFiniteResources(int $xpSpent) : void{
|
||||
$expectedXpCost = $this->expectedResult->getXpCost();
|
||||
if($xpSpent !== $expectedXpCost){
|
||||
throw new TransactionValidationException("Expected the amount of xp spent to be $expectedXpCost, but received $xpSpent");
|
||||
}
|
||||
|
||||
$xpLevel = $this->source->getXpManager()->getXpLevel();
|
||||
if($xpLevel < $expectedXpCost){
|
||||
throw new TransactionValidationException("Player's XP level $xpLevel is less than the required XP level $expectedXpCost");
|
||||
}
|
||||
}
|
||||
|
||||
private function validateInputs(Item $base, Item $material, Item $expectedOutput) : ?int{
|
||||
$calculAttempt = AnvilHelper::calculateResult($base, $material, $this->customName, $this->source->isCreative());
|
||||
if($calculAttempt === null){
|
||||
return null;
|
||||
}
|
||||
$result = $calculAttempt->getOutput();
|
||||
if(!$result->equalsExact($expectedOutput)){
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->baseItem = $base;
|
||||
$this->materialItem = $material;
|
||||
|
||||
return $calculAttempt->getXpCost();
|
||||
}
|
||||
|
||||
public function validate() : void{
|
||||
if(count($this->actions) < 1){
|
||||
throw new TransactionValidationException("Transaction must have at least one action to be executable");
|
||||
}
|
||||
|
||||
/** @var Item[] $inputs */
|
||||
$inputs = [];
|
||||
/** @var Item[] $outputs */
|
||||
$outputs = [];
|
||||
$this->matchItems($outputs, $inputs);
|
||||
|
||||
if(($outputCount = count($outputs)) !== 1){
|
||||
throw new TransactionValidationException("Expected 1 output item, but received $outputCount");
|
||||
}
|
||||
$outputItem = $outputs[0];
|
||||
|
||||
if(($inputCount = count($inputs)) < 1){
|
||||
throw new TransactionValidationException("Expected at least 1 input item, but received $inputCount");
|
||||
}
|
||||
if($inputCount > 2){
|
||||
throw new TransactionValidationException("Expected at most 2 input items, but received $inputCount");
|
||||
}
|
||||
|
||||
if(count($inputs) < 2){
|
||||
$xpCost = $this->validateInputs($inputs[0], VanillaItems::AIR(), $outputItem) ??
|
||||
throw new TransactionValidationException("Inputs do not match expected result");
|
||||
}else{
|
||||
$xpCost = $this->validateInputs($inputs[0], $inputs[1], $outputItem) ??
|
||||
$this->validateInputs($inputs[1], $inputs[0], $outputItem) ??
|
||||
throw new TransactionValidationException("Inputs do not match expected result");
|
||||
}
|
||||
|
||||
if($this->source->hasFiniteResources()){
|
||||
$this->validateFiniteResources($xpCost);
|
||||
}
|
||||
}
|
||||
|
||||
public function execute() : void{
|
||||
parent::execute();
|
||||
|
||||
if($this->source->hasFiniteResources()){
|
||||
$this->source->getXpManager()->subtractXpLevels($this->expectedResult->getXpCost());
|
||||
}
|
||||
|
||||
$inventory = $this->source->getCurrentWindow();
|
||||
if($inventory instanceof AnvilInventory){
|
||||
$world = $inventory->getHolder()->getWorld();
|
||||
if(mt_rand(0, 12) === 0){
|
||||
$anvilBlock = $world->getBlock($inventory->getHolder());
|
||||
if($anvilBlock instanceof Anvil){
|
||||
$newDamage = $anvilBlock->getDamage() + 1;
|
||||
if($newDamage > Anvil::VERY_DAMAGED){
|
||||
$newBlock = VanillaBlocks::AIR();
|
||||
$world->addSound($inventory->getHolder(), new AnvilBreakSound());
|
||||
}else{
|
||||
$newBlock = $anvilBlock->setDamage($newDamage);
|
||||
}
|
||||
$world->setBlock($inventory->getHolder(), $newBlock);
|
||||
}
|
||||
|
||||
}
|
||||
$world->addSound($inventory->getHolder(), new AnvilUseSound());
|
||||
}
|
||||
}
|
||||
|
||||
protected function callExecuteEvent() : bool{
|
||||
if($this->baseItem === null){
|
||||
throw new AssumptionFailedError("Expected that baseItem are not null before executing the event");
|
||||
}
|
||||
|
||||
$ev = new PlayerUseAnvilEvent($this->source, $this->baseItem, $this->materialItem, $this->expectedResult->getOutput(), $this->customName, $this->expectedResult->getXpCost());
|
||||
$ev->call();
|
||||
return !$ev->isCancelled();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ class Item implements \JsonSerializable{
|
||||
|
||||
public const TAG_DISPLAY_NAME = "Name";
|
||||
public const TAG_DISPLAY_LORE = "Lore";
|
||||
public const TAG_REPAIR_COST = "RepairCost";
|
||||
|
||||
public const TAG_KEEP_ON_DEATH = "minecraft:keep_on_death";
|
||||
|
||||
@ -84,6 +85,7 @@ class Item implements \JsonSerializable{
|
||||
protected string $customName = "";
|
||||
/** @var string[] */
|
||||
protected array $lore = [];
|
||||
protected int $anvilRepairCost = 0;
|
||||
/** TODO: this needs to die in a fire */
|
||||
protected ?CompoundTag $blockEntityTag = null;
|
||||
|
||||
@ -282,6 +284,30 @@ class Item implements \JsonSerializable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the anvil repair cost of the item.
|
||||
* This value is used in anvil to determine the XP cost of repairing the item.
|
||||
*
|
||||
* In vanilla, this value is stored in the "RepairCost" tag.
|
||||
*/
|
||||
public function getAnvilRepairCost() : int{
|
||||
return $this->anvilRepairCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the anvil repair cost value of the item.
|
||||
* This value is used in anvil to determine the XP cost of repairing the item.
|
||||
* Higher cost means more XP is required to repair the item.
|
||||
*
|
||||
* In vanilla, this value is stored in the "RepairCost" tag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAnvilRepairCost(int $cost) : self{
|
||||
$this->anvilRepairCost = $cost;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NbtException
|
||||
*/
|
||||
@ -338,6 +364,7 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
|
||||
$this->keepOnDeath = $tag->getByte(self::TAG_KEEP_ON_DEATH, 0) !== 0;
|
||||
$this->anvilRepairCost = $tag->getInt(self::TAG_REPAIR_COST, 0);
|
||||
}
|
||||
|
||||
protected function serializeCompoundTag(CompoundTag $tag) : void{
|
||||
@ -406,6 +433,12 @@ class Item implements \JsonSerializable{
|
||||
}else{
|
||||
$tag->removeTag(self::TAG_KEEP_ON_DEATH);
|
||||
}
|
||||
|
||||
if($this->anvilRepairCost > 0){
|
||||
$tag->setInt(self::TAG_REPAIR_COST, $this->anvilRepairCost);
|
||||
}else{
|
||||
$tag->removeTag(self::TAG_REPAIR_COST);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCount() : int{
|
||||
|
@ -111,7 +111,8 @@ final class LegacyStringToItemParser{
|
||||
*/
|
||||
public function parse(string $input) : Item{
|
||||
$key = $this->reprocess($input);
|
||||
$b = explode(":", $key);
|
||||
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1
|
||||
$b = explode(":", $key, limit: 3);
|
||||
|
||||
if(!isset($b[1])){
|
||||
$meta = 0;
|
||||
|
@ -897,6 +897,18 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_FROSTWALKER, []);
|
||||
}
|
||||
|
||||
public static function enchantment_heavy_weapon_breach() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_BREACH, []);
|
||||
}
|
||||
|
||||
public static function enchantment_heavy_weapon_density() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_DENSITY, []);
|
||||
}
|
||||
|
||||
public static function enchantment_heavy_weapon_windburst() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_HEAVY_WEAPON_WINDBURST, []);
|
||||
}
|
||||
|
||||
public static function enchantment_knockback() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ENCHANTMENT_KNOCKBACK, []);
|
||||
}
|
||||
@ -1108,6 +1120,318 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::ITEM_RECORD_WARD_DESC, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_anvil() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ANVIL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_arrow() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ARROW, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_axe() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_AXE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_banner() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_banner_pattern() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BANNER_PATTERN, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_bed() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BED, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_boat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOAT, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_boots() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BOOTS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_bundles() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUNDLES, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_buttons() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_BUTTONS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_candles() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CANDLES, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_chalkboard() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHALKBOARD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_chest() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHEST, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_chestboat() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTBOAT, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_chestplate() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CHESTPLATE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_compounds() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COMPOUNDS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_concrete() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_concretePowder() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CONCRETEPOWDER, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_cookedFood() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_COOKEDFOOD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_coral() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_coral_decorations() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CORAL_DECORATIONS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_crop() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_CROP, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_door() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DOOR, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_dye() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_DYE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_enchantedBook() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ENCHANTEDBOOK, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_fence() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_fenceGate() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FENCEGATE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_firework() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORK, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_fireworkStars() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FIREWORKSTARS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_flower() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_FLOWER, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_glass() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_glassPane() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLASSPANE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_glazedTerracotta() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GLAZEDTERRACOTTA, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_goatHorn() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GOATHORN, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_grass() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_GRASS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_helmet() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HELMET, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_hoe() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HOE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_horseArmor() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_HORSEARMOR, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_leaves() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEAVES, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_leggings() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LEGGINGS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_lingeringPotion() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LINGERINGPOTION, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_log() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_LOG, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_minecart() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MINECART, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_miscFood() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MISCFOOD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_mobEgg() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MOBEGG, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_monsterStoneEgg() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MONSTERSTONEEGG, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_mushroom() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_MUSHROOM, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_netherWartBlock() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_NETHERWARTBLOCK, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_ominousBottle() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_OMINOUSBOTTLE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_ore() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_ORE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_permission() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PERMISSION, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_pickaxe() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PICKAXE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_planks() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PLANKS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_potion() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_POTION, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_pressurePlate() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRESSUREPLATE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_products() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_PRODUCTS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_rail() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAIL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_rawFood() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RAWFOOD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_record() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_RECORD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_sandstone() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SANDSTONE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_sapling() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SAPLING, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_seed() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SEED, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_shovel() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHOVEL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_shulkerBox() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SHULKERBOX, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_sign() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SIGN, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_skull() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SKULL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_slab() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SLAB, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_splashPotion() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SPLASHPOTION, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_stainedClay() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAINEDCLAY, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_stairs() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STAIRS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_stone() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONE, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_stoneBrick() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_STONEBRICK, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_sword() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_SWORD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_trapdoor() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_TRAPDOOR, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_walls() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WALLS, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_wood() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOD, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_wool() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOL, []);
|
||||
}
|
||||
|
||||
public static function itemGroup_name_woolCarpet() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::ITEMGROUP_NAME_WOOLCARPET, []);
|
||||
}
|
||||
|
||||
public static function kick_admin() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::KICK_ADMIN, []);
|
||||
}
|
||||
|
@ -194,6 +194,9 @@ final class KnownTranslationKeys{
|
||||
public const ENCHANTMENT_FIRE = "enchantment.fire";
|
||||
public const ENCHANTMENT_FISHINGSPEED = "enchantment.fishingSpeed";
|
||||
public const ENCHANTMENT_FROSTWALKER = "enchantment.frostwalker";
|
||||
public const ENCHANTMENT_HEAVY_WEAPON_BREACH = "enchantment.heavy_weapon.breach";
|
||||
public const ENCHANTMENT_HEAVY_WEAPON_DENSITY = "enchantment.heavy_weapon.density";
|
||||
public const ENCHANTMENT_HEAVY_WEAPON_WINDBURST = "enchantment.heavy_weapon.windburst";
|
||||
public const ENCHANTMENT_KNOCKBACK = "enchantment.knockback";
|
||||
public const ENCHANTMENT_LOOTBONUS = "enchantment.lootBonus";
|
||||
public const ENCHANTMENT_LOOTBONUSDIGGER = "enchantment.lootBonusDigger";
|
||||
@ -245,6 +248,84 @@ final class KnownTranslationKeys{
|
||||
public const ITEM_RECORD_STRAD_DESC = "item.record_strad.desc";
|
||||
public const ITEM_RECORD_WAIT_DESC = "item.record_wait.desc";
|
||||
public const ITEM_RECORD_WARD_DESC = "item.record_ward.desc";
|
||||
public const ITEMGROUP_NAME_ANVIL = "itemGroup.name.anvil";
|
||||
public const ITEMGROUP_NAME_ARROW = "itemGroup.name.arrow";
|
||||
public const ITEMGROUP_NAME_AXE = "itemGroup.name.axe";
|
||||
public const ITEMGROUP_NAME_BANNER = "itemGroup.name.banner";
|
||||
public const ITEMGROUP_NAME_BANNER_PATTERN = "itemGroup.name.banner_pattern";
|
||||
public const ITEMGROUP_NAME_BED = "itemGroup.name.bed";
|
||||
public const ITEMGROUP_NAME_BOAT = "itemGroup.name.boat";
|
||||
public const ITEMGROUP_NAME_BOOTS = "itemGroup.name.boots";
|
||||
public const ITEMGROUP_NAME_BUNDLES = "itemGroup.name.bundles";
|
||||
public const ITEMGROUP_NAME_BUTTONS = "itemGroup.name.buttons";
|
||||
public const ITEMGROUP_NAME_CANDLES = "itemGroup.name.candles";
|
||||
public const ITEMGROUP_NAME_CHALKBOARD = "itemGroup.name.chalkboard";
|
||||
public const ITEMGROUP_NAME_CHEST = "itemGroup.name.chest";
|
||||
public const ITEMGROUP_NAME_CHESTBOAT = "itemGroup.name.chestboat";
|
||||
public const ITEMGROUP_NAME_CHESTPLATE = "itemGroup.name.chestplate";
|
||||
public const ITEMGROUP_NAME_COMPOUNDS = "itemGroup.name.compounds";
|
||||
public const ITEMGROUP_NAME_CONCRETE = "itemGroup.name.concrete";
|
||||
public const ITEMGROUP_NAME_CONCRETEPOWDER = "itemGroup.name.concretePowder";
|
||||
public const ITEMGROUP_NAME_COOKEDFOOD = "itemGroup.name.cookedFood";
|
||||
public const ITEMGROUP_NAME_CORAL = "itemGroup.name.coral";
|
||||
public const ITEMGROUP_NAME_CORAL_DECORATIONS = "itemGroup.name.coral_decorations";
|
||||
public const ITEMGROUP_NAME_CROP = "itemGroup.name.crop";
|
||||
public const ITEMGROUP_NAME_DOOR = "itemGroup.name.door";
|
||||
public const ITEMGROUP_NAME_DYE = "itemGroup.name.dye";
|
||||
public const ITEMGROUP_NAME_ENCHANTEDBOOK = "itemGroup.name.enchantedBook";
|
||||
public const ITEMGROUP_NAME_FENCE = "itemGroup.name.fence";
|
||||
public const ITEMGROUP_NAME_FENCEGATE = "itemGroup.name.fenceGate";
|
||||
public const ITEMGROUP_NAME_FIREWORK = "itemGroup.name.firework";
|
||||
public const ITEMGROUP_NAME_FIREWORKSTARS = "itemGroup.name.fireworkStars";
|
||||
public const ITEMGROUP_NAME_FLOWER = "itemGroup.name.flower";
|
||||
public const ITEMGROUP_NAME_GLASS = "itemGroup.name.glass";
|
||||
public const ITEMGROUP_NAME_GLASSPANE = "itemGroup.name.glassPane";
|
||||
public const ITEMGROUP_NAME_GLAZEDTERRACOTTA = "itemGroup.name.glazedTerracotta";
|
||||
public const ITEMGROUP_NAME_GOATHORN = "itemGroup.name.goatHorn";
|
||||
public const ITEMGROUP_NAME_GRASS = "itemGroup.name.grass";
|
||||
public const ITEMGROUP_NAME_HELMET = "itemGroup.name.helmet";
|
||||
public const ITEMGROUP_NAME_HOE = "itemGroup.name.hoe";
|
||||
public const ITEMGROUP_NAME_HORSEARMOR = "itemGroup.name.horseArmor";
|
||||
public const ITEMGROUP_NAME_LEAVES = "itemGroup.name.leaves";
|
||||
public const ITEMGROUP_NAME_LEGGINGS = "itemGroup.name.leggings";
|
||||
public const ITEMGROUP_NAME_LINGERINGPOTION = "itemGroup.name.lingeringPotion";
|
||||
public const ITEMGROUP_NAME_LOG = "itemGroup.name.log";
|
||||
public const ITEMGROUP_NAME_MINECART = "itemGroup.name.minecart";
|
||||
public const ITEMGROUP_NAME_MISCFOOD = "itemGroup.name.miscFood";
|
||||
public const ITEMGROUP_NAME_MOBEGG = "itemGroup.name.mobEgg";
|
||||
public const ITEMGROUP_NAME_MONSTERSTONEEGG = "itemGroup.name.monsterStoneEgg";
|
||||
public const ITEMGROUP_NAME_MUSHROOM = "itemGroup.name.mushroom";
|
||||
public const ITEMGROUP_NAME_NETHERWARTBLOCK = "itemGroup.name.netherWartBlock";
|
||||
public const ITEMGROUP_NAME_OMINOUSBOTTLE = "itemGroup.name.ominousBottle";
|
||||
public const ITEMGROUP_NAME_ORE = "itemGroup.name.ore";
|
||||
public const ITEMGROUP_NAME_PERMISSION = "itemGroup.name.permission";
|
||||
public const ITEMGROUP_NAME_PICKAXE = "itemGroup.name.pickaxe";
|
||||
public const ITEMGROUP_NAME_PLANKS = "itemGroup.name.planks";
|
||||
public const ITEMGROUP_NAME_POTION = "itemGroup.name.potion";
|
||||
public const ITEMGROUP_NAME_PRESSUREPLATE = "itemGroup.name.pressurePlate";
|
||||
public const ITEMGROUP_NAME_PRODUCTS = "itemGroup.name.products";
|
||||
public const ITEMGROUP_NAME_RAIL = "itemGroup.name.rail";
|
||||
public const ITEMGROUP_NAME_RAWFOOD = "itemGroup.name.rawFood";
|
||||
public const ITEMGROUP_NAME_RECORD = "itemGroup.name.record";
|
||||
public const ITEMGROUP_NAME_SANDSTONE = "itemGroup.name.sandstone";
|
||||
public const ITEMGROUP_NAME_SAPLING = "itemGroup.name.sapling";
|
||||
public const ITEMGROUP_NAME_SEED = "itemGroup.name.seed";
|
||||
public const ITEMGROUP_NAME_SHOVEL = "itemGroup.name.shovel";
|
||||
public const ITEMGROUP_NAME_SHULKERBOX = "itemGroup.name.shulkerBox";
|
||||
public const ITEMGROUP_NAME_SIGN = "itemGroup.name.sign";
|
||||
public const ITEMGROUP_NAME_SKULL = "itemGroup.name.skull";
|
||||
public const ITEMGROUP_NAME_SLAB = "itemGroup.name.slab";
|
||||
public const ITEMGROUP_NAME_SPLASHPOTION = "itemGroup.name.splashPotion";
|
||||
public const ITEMGROUP_NAME_STAINEDCLAY = "itemGroup.name.stainedClay";
|
||||
public const ITEMGROUP_NAME_STAIRS = "itemGroup.name.stairs";
|
||||
public const ITEMGROUP_NAME_STONE = "itemGroup.name.stone";
|
||||
public const ITEMGROUP_NAME_STONEBRICK = "itemGroup.name.stoneBrick";
|
||||
public const ITEMGROUP_NAME_SWORD = "itemGroup.name.sword";
|
||||
public const ITEMGROUP_NAME_TRAPDOOR = "itemGroup.name.trapdoor";
|
||||
public const ITEMGROUP_NAME_WALLS = "itemGroup.name.walls";
|
||||
public const ITEMGROUP_NAME_WOOD = "itemGroup.name.wood";
|
||||
public const ITEMGROUP_NAME_WOOL = "itemGroup.name.wool";
|
||||
public const ITEMGROUP_NAME_WOOLCARPET = "itemGroup.name.woolCarpet";
|
||||
public const KICK_ADMIN = "kick.admin";
|
||||
public const KICK_ADMIN_REASON = "kick.admin.reason";
|
||||
public const KICK_REASON_CHEAT = "kick.reason.cheat";
|
||||
|
@ -71,7 +71,7 @@ class Language{
|
||||
|
||||
foreach($files as $file){
|
||||
try{
|
||||
$code = explode(".", $file)[0];
|
||||
$code = explode(".", $file, limit: 2)[0];
|
||||
$strings = self::loadLang($path, $code);
|
||||
if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
|
||||
$result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];
|
||||
|
@ -690,7 +690,7 @@ class InventoryManager{
|
||||
}
|
||||
|
||||
public function syncCreative() : void{
|
||||
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache($this->player->getCreativeInventory()));
|
||||
$this->session->sendDataPacket(CreativeInventoryCache::getInstance()->buildPacket($this->player->getCreativeInventory(), $this->session));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,9 +72,11 @@ final class JwtUtils{
|
||||
* @throws JwtException
|
||||
*/
|
||||
public static function split(string $jwt) : array{
|
||||
$v = explode(".", $jwt);
|
||||
//limit of 4 allows us to detect too many parts without having to split the string up into a potentially large
|
||||
//number of parts
|
||||
$v = explode(".", $jwt, limit: 4);
|
||||
if(count($v) !== 3){
|
||||
throw new JwtException("Expected exactly 3 JWT parts, got " . count($v));
|
||||
throw new JwtException("Expected exactly 3 JWT parts delimited by a period");
|
||||
}
|
||||
return [$v[0], $v[1], $v[2]]; //workaround phpstan bug
|
||||
}
|
||||
|
@ -1058,7 +1058,7 @@ class NetworkSession{
|
||||
];
|
||||
|
||||
$layers = [
|
||||
new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 0.1),
|
||||
new AbilitiesLayer(AbilitiesLayer::LAYER_BASE, $boolAbilities, $for->getFlightSpeedMultiplier(), 1, 0.1),
|
||||
];
|
||||
if(!$for->hasBlockCollision()){
|
||||
//TODO: HACK! In 1.19.80, the client starts falling in our faux spectator mode when it clips into a
|
||||
@ -1068,7 +1068,7 @@ class NetworkSession{
|
||||
|
||||
$layers[] = new AbilitiesLayer(AbilitiesLayer::LAYER_SPECTATOR, [
|
||||
AbilitiesLayer::ABILITY_FLYING => true,
|
||||
], null, null);
|
||||
], null, null, null);
|
||||
}
|
||||
|
||||
$this->sendDataPacket(UpdateAbilitiesPacket::create(new AbilitiesData(
|
||||
|
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
|
||||
|
106
src/network/mcpe/cache/CreativeInventoryCache.php
vendored
106
src/network/mcpe/cache/CreativeInventoryCache.php
vendored
@ -23,23 +23,30 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\cache;
|
||||
|
||||
use pocketmine\inventory\CreativeCategory;
|
||||
use pocketmine\inventory\CreativeInventory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use function is_string;
|
||||
use function spl_object_id;
|
||||
use const PHP_INT_MIN;
|
||||
|
||||
final class CreativeInventoryCache{
|
||||
use SingletonTrait;
|
||||
|
||||
/**
|
||||
* @var CreativeContentPacket[]
|
||||
* @phpstan-var array<int, CreativeContentPacket>
|
||||
* @var CreativeInventoryCacheEntry[]
|
||||
* @phpstan-var array<int, CreativeInventoryCacheEntry>
|
||||
*/
|
||||
private array $caches = [];
|
||||
|
||||
public function getCache(CreativeInventory $inventory) : CreativeContentPacket{
|
||||
private function getCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{
|
||||
$id = spl_object_id($inventory);
|
||||
if(!isset($this->caches[$id])){
|
||||
$inventory->getDestructorCallbacks()->add(function() use ($id) : void{
|
||||
@ -48,7 +55,7 @@ final class CreativeInventoryCache{
|
||||
$inventory->getContentChangedCallbacks()->add(function() use ($id) : void{
|
||||
unset($this->caches[$id]);
|
||||
});
|
||||
$this->caches[$id] = $this->buildCreativeInventoryCache($inventory);
|
||||
$this->caches[$id] = $this->buildCacheEntry($inventory);
|
||||
}
|
||||
return $this->caches[$id];
|
||||
}
|
||||
@ -56,14 +63,91 @@ final class CreativeInventoryCache{
|
||||
/**
|
||||
* Rebuild the cache for the given inventory.
|
||||
*/
|
||||
private function buildCreativeInventoryCache(CreativeInventory $inventory) : CreativeContentPacket{
|
||||
$entries = [];
|
||||
private function buildCacheEntry(CreativeInventory $inventory) : CreativeInventoryCacheEntry{
|
||||
$categories = [];
|
||||
$groups = [];
|
||||
|
||||
$typeConverter = TypeConverter::getInstance();
|
||||
//creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent
|
||||
foreach($inventory->getAll() as $k => $item){
|
||||
$entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item));
|
||||
|
||||
$nextIndex = 0;
|
||||
$groupIndexes = [];
|
||||
$itemGroupIndexes = [];
|
||||
|
||||
foreach($inventory->getAllEntries() as $k => $entry){
|
||||
$group = $entry->getGroup();
|
||||
$category = $entry->getCategory();
|
||||
if($group === null){
|
||||
$groupId = PHP_INT_MIN;
|
||||
}else{
|
||||
$groupId = spl_object_id($group);
|
||||
unset($groupIndexes[$category->name][PHP_INT_MIN]); //start a new anonymous group for this category
|
||||
}
|
||||
|
||||
return CreativeContentPacket::create($entries);
|
||||
//group object may be reused by multiple categories
|
||||
if(!isset($groupIndexes[$category->name][$groupId])){
|
||||
$groupIndexes[$category->name][$groupId] = $nextIndex++;
|
||||
$categories[] = $category;
|
||||
$groups[] = $group;
|
||||
}
|
||||
$itemGroupIndexes[$k] = $groupIndexes[$category->name][$groupId];
|
||||
}
|
||||
|
||||
//creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent
|
||||
$items = [];
|
||||
foreach($inventory->getAllEntries() as $k => $entry){
|
||||
$items[] = new CreativeItemEntry(
|
||||
$k,
|
||||
$typeConverter->coreItemStackToNet($entry->getItem()),
|
||||
$itemGroupIndexes[$k]
|
||||
);
|
||||
}
|
||||
|
||||
return new CreativeInventoryCacheEntry($categories, $groups, $items);
|
||||
}
|
||||
|
||||
public function buildPacket(CreativeInventory $inventory, NetworkSession $session) : CreativeContentPacket{
|
||||
$player = $session->getPlayer() ?? throw new \LogicException("Cannot prepare creative data for a session without a player");
|
||||
$language = $player->getLanguage();
|
||||
$forceLanguage = $player->getServer()->isLanguageForced();
|
||||
$typeConverter = $session->getTypeConverter();
|
||||
$cachedEntry = $this->getCacheEntry($inventory);
|
||||
$translate = function(Translatable|string $translatable) use ($session, $language, $forceLanguage) : string{
|
||||
if(is_string($translatable)){
|
||||
$message = $translatable;
|
||||
}elseif(!$forceLanguage){
|
||||
[$message,] = $session->prepareClientTranslatableMessage($translatable);
|
||||
}else{
|
||||
$message = $language->translate($translatable);
|
||||
}
|
||||
return $message;
|
||||
};
|
||||
|
||||
$groupEntries = [];
|
||||
foreach($cachedEntry->categories as $index => $category){
|
||||
$group = $cachedEntry->groups[$index];
|
||||
$categoryId = match ($category) {
|
||||
CreativeCategory::CONSTRUCTION => CreativeContentPacket::CATEGORY_CONSTRUCTION,
|
||||
CreativeCategory::NATURE => CreativeContentPacket::CATEGORY_NATURE,
|
||||
CreativeCategory::EQUIPMENT => CreativeContentPacket::CATEGORY_EQUIPMENT,
|
||||
CreativeCategory::ITEMS => CreativeContentPacket::CATEGORY_ITEMS
|
||||
};
|
||||
if($group === null){
|
||||
$groupEntries[] = new CreativeGroupEntry($categoryId, "", ItemStack::null());
|
||||
}else{
|
||||
$groupIcon = $group->getIcon();
|
||||
//TODO: HACK! In 1.21.60, Workaround glitchy behaviour when an item is used as an icon for a group it
|
||||
//doesn't belong to. Without this hack, both instances of the item will show a +, but neither of them
|
||||
//will actually expand the group work correctly.
|
||||
$groupIcon->getNamedTag()->setInt("___GroupBugWorkaround___", $index);
|
||||
$groupName = $group->getName();
|
||||
$groupEntries[] = new CreativeGroupEntry(
|
||||
$categoryId,
|
||||
$translate($groupName),
|
||||
$typeConverter->coreItemStackToNet($groupIcon)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return CreativeContentPacket::create($groupEntries, $cachedEntry->items);
|
||||
}
|
||||
}
|
||||
|
48
src/network/mcpe/cache/CreativeInventoryCacheEntry.php
vendored
Normal file
48
src/network/mcpe/cache/CreativeInventoryCacheEntry.php
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\cache;
|
||||
|
||||
use pocketmine\inventory\CreativeCategory;
|
||||
use pocketmine\inventory\CreativeGroup;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeItemEntry;
|
||||
|
||||
final class CreativeInventoryCacheEntry{
|
||||
|
||||
/**
|
||||
* @param CreativeCategory[] $categories
|
||||
* @param CreativeGroup[]|null[] $groups
|
||||
* @param CreativeItemEntry[] $items
|
||||
*
|
||||
* @phpstan-param list<CreativeCategory> $categories
|
||||
* @phpstan-param list<CreativeGroup|null> $groups
|
||||
* @phpstan-param list<CreativeItemEntry> $items
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $categories,
|
||||
public readonly array $groups,
|
||||
public readonly array $items,
|
||||
){
|
||||
//NOOP
|
||||
}
|
||||
}
|
@ -23,10 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function base64_decode;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
@ -41,12 +46,15 @@ final class ItemTypeDictionaryFromDataHelper{
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
}
|
||||
|
||||
$emptyNBT = new CacheableNbt(new CompoundTag());
|
||||
$nbtSerializer = new LittleEndianNbtSerializer();
|
||||
|
||||
$params = [];
|
||||
foreach(Utils::promoteKeys($table) as $name => $entry){
|
||||
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"])){
|
||||
if(!is_array($entry) || !is_string($name) || !isset($entry["component_based"], $entry["runtime_id"], $entry["version"]) || !is_bool($entry["component_based"]) || !is_int($entry["runtime_id"]) || !is_int($entry["version"]) || !(is_string($componentNbt = $entry["component_nbt"] ?? null) || $componentNbt === null)){
|
||||
throw new AssumptionFailedError("Invalid item list format");
|
||||
}
|
||||
$params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"]);
|
||||
$params[] = new ItemTypeEntry($name, $entry["runtime_id"], $entry["component_based"], $entry["version"], $componentNbt === null ? $emptyNBT : new CacheableNbt($nbtSerializer->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($componentNbt, true)))->mustGetCompoundTag()));
|
||||
}
|
||||
return new ItemTypeDictionary($params);
|
||||
}
|
||||
|
@ -23,11 +23,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\handler;
|
||||
|
||||
use pocketmine\block\inventory\AnvilInventory;
|
||||
use pocketmine\block\inventory\EnchantInventory;
|
||||
use pocketmine\block\utils\AnvilHelper;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\inventory\transaction\action\CreateItemAction;
|
||||
use pocketmine\inventory\transaction\action\DestroyItemAction;
|
||||
use pocketmine\inventory\transaction\action\DropItemAction;
|
||||
use pocketmine\inventory\transaction\AnvilTransaction;
|
||||
use pocketmine\inventory\transaction\CraftingTransaction;
|
||||
use pocketmine\inventory\transaction\EnchantingTransaction;
|
||||
use pocketmine\inventory\transaction\InventoryTransaction;
|
||||
@ -39,6 +42,7 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerUIIds;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingConsumeInputStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftingCreateSpecificResultStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeAutoStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeOptionalStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CraftRecipeStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\CreativeCreateStackRequestAction;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\stackrequest\DeprecatedCraftingResultsStackRequestAction;
|
||||
@ -290,7 +294,7 @@ class ItemStackRequestExecutor{
|
||||
* @throws ItemStackRequestProcessException
|
||||
*/
|
||||
private function assertDoingCrafting() : void{
|
||||
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction){
|
||||
if(!$this->specialTransaction instanceof CraftingTransaction && !$this->specialTransaction instanceof EnchantingTransaction && !$this->specialTransaction instanceof AnvilTransaction){
|
||||
if($this->specialTransaction === null){
|
||||
throw new ItemStackRequestProcessException("Expected CraftRecipe or CraftRecipeAuto action to precede this action");
|
||||
}else{
|
||||
@ -348,6 +352,15 @@ class ItemStackRequestExecutor{
|
||||
}
|
||||
}elseif($action instanceof CraftRecipeAutoStackRequestAction){
|
||||
$this->beginCrafting($action->getRecipeId(), $action->getRepetitions());
|
||||
}elseif($action instanceof CraftRecipeOptionalStackRequestAction){
|
||||
$window = $this->player->getCurrentWindow();
|
||||
if($window instanceof AnvilInventory){
|
||||
$result = AnvilHelper::calculateResult($window->getInput(), $window->getMaterial(), $this->request->getFilterStrings()[0] ?? null, $this->player->isCreative());
|
||||
if($result !== null){
|
||||
$this->specialTransaction = new AnvilTransaction($this->player, $result, $this->request->getFilterStrings()[0] ?? null);
|
||||
$this->setNextCreatedItem($result->getOutput());
|
||||
}
|
||||
}
|
||||
}elseif($action instanceof CraftingConsumeInputStackRequestAction){
|
||||
$this->assertDoingCrafting();
|
||||
$this->removeItemFromSlot($action->getSource(), $action->getCount()); //output discarded - we allow CraftingTransaction to verify the balance
|
||||
|
@ -28,6 +28,7 @@ use pocketmine\network\mcpe\cache\CraftingDataCache;
|
||||
use pocketmine\network\mcpe\cache\StaticPacketCache;
|
||||
use pocketmine\network\mcpe\InventoryManager;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\ItemRegistryPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
@ -110,9 +111,11 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
new NetworkPermissions(disableClientSounds: true),
|
||||
[],
|
||||
0,
|
||||
$typeConverter->getItemTypeDictionary()->getEntries(),
|
||||
));
|
||||
|
||||
$this->session->getLogger()->debug("Sending items");
|
||||
$this->session->sendDataPacket(ItemRegistryPacket::create($typeConverter->getItemTypeDictionary()->getEntries()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
|
||||
|
@ -148,7 +148,9 @@ class BanEntry{
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode("|", trim($str));
|
||||
//we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter
|
||||
//we don't want to include unexpected data into the ban reason
|
||||
$parts = explode("|", trim($str), limit: 6);
|
||||
$entry = new BanEntry(trim(array_shift($parts)));
|
||||
if(count($parts) > 0){
|
||||
$entry->setCreated(self::parseDate(array_shift($parts)));
|
||||
|
@ -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;
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\resourcepacks;
|
||||
use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_keys;
|
||||
use function copy;
|
||||
@ -103,9 +104,14 @@ class ResourcePackManager{
|
||||
try{
|
||||
$newPack = $this->loadPackFromPath(Path::join($this->path, $pack));
|
||||
|
||||
$this->resourcePacks[] = $newPack;
|
||||
$index = strtolower($newPack->getPackId());
|
||||
if(!Uuid::isValid($index)){
|
||||
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
|
||||
//for now we need to validate this here to make sure it doesn't cause crashes later on
|
||||
throw new ResourcePackException("Invalid UUID ($index)");
|
||||
}
|
||||
$this->uuidList[$index] = $newPack;
|
||||
$this->resourcePacks[] = $newPack;
|
||||
|
||||
$keyPath = Path::join($this->path, $pack . ".key");
|
||||
if(file_exists($keyPath)){
|
||||
@ -190,6 +196,11 @@ class ResourcePackManager{
|
||||
$resourcePacks = [];
|
||||
foreach($resourceStack as $pack){
|
||||
$uuid = strtolower($pack->getPackId());
|
||||
if(!Uuid::isValid($uuid)){
|
||||
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
|
||||
//for now we need to validate this here to make sure it doesn't cause crashes later on
|
||||
throw new \InvalidArgumentException("Invalid resource pack UUID ($uuid)");
|
||||
}
|
||||
if(isset($uuidList[$uuid])){
|
||||
throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)");
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ use const CASE_LOWER;
|
||||
use const JSON_BIGINT_AS_STRING;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
use const PHP_INT_MAX;
|
||||
use const YAML_UTF8_ENCODING;
|
||||
|
||||
/**
|
||||
@ -339,7 +340,7 @@ class Config{
|
||||
}
|
||||
|
||||
public function setNested(string $key, mixed $value) : void{
|
||||
$vars = explode(".", $key);
|
||||
$vars = explode(".", $key, limit: PHP_INT_MAX);
|
||||
$base = array_shift($vars);
|
||||
|
||||
if(!isset($this->config[$base])){
|
||||
@ -366,7 +367,7 @@ class Config{
|
||||
return $this->nestedCache[$key];
|
||||
}
|
||||
|
||||
$vars = explode(".", $key);
|
||||
$vars = explode(".", $key, limit: PHP_INT_MAX);
|
||||
$base = array_shift($vars);
|
||||
if(isset($this->config[$base])){
|
||||
$base = $this->config[$base];
|
||||
@ -390,7 +391,7 @@ class Config{
|
||||
$this->nestedCache = [];
|
||||
$this->changed = true;
|
||||
|
||||
$vars = explode(".", $key);
|
||||
$vars = explode(".", $key, limit: PHP_INT_MAX);
|
||||
|
||||
$currentNode = &$this->config;
|
||||
while(count($vars) > 0){
|
||||
@ -495,7 +496,7 @@ class Config{
|
||||
*/
|
||||
public static function parseList(string $content) : array{
|
||||
$result = [];
|
||||
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
|
||||
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){
|
||||
$v = trim($v);
|
||||
if($v === ""){
|
||||
continue;
|
||||
|
@ -60,6 +60,7 @@ use const CURLOPT_RETURNTRANSFER;
|
||||
use const CURLOPT_SSL_VERIFYHOST;
|
||||
use const CURLOPT_SSL_VERIFYPEER;
|
||||
use const CURLOPT_TIMEOUT_MS;
|
||||
use const PHP_INT_MAX;
|
||||
use const SOCK_DGRAM;
|
||||
use const SOL_UDP;
|
||||
|
||||
@ -227,9 +228,10 @@ class Internet{
|
||||
$rawHeaders = substr($raw, 0, $headerSize);
|
||||
$body = substr($raw, $headerSize);
|
||||
$headers = [];
|
||||
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
|
||||
//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) as $line){
|
||||
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]);
|
||||
|
@ -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
|
||||
|
@ -264,14 +264,7 @@ final class Utils{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Operating System
|
||||
* Windows => win
|
||||
* MacOS => mac
|
||||
* iOS => ios
|
||||
* Android => android
|
||||
* Linux => Linux
|
||||
* BSD => bsd
|
||||
* Other => other
|
||||
* @return string one of the Utils::OS_* constants
|
||||
*/
|
||||
public static function getOS(bool $recalculate = false) : string{
|
||||
if(self::$os === null || $recalculate){
|
||||
@ -376,7 +369,7 @@ final class Utils{
|
||||
debug_zval_dump($value);
|
||||
$contents = ob_get_contents();
|
||||
if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
|
||||
$ret = explode("\n", $contents);
|
||||
$ret = explode("\n", $contents, limit: 2);
|
||||
ob_end_clean();
|
||||
|
||||
if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
|
||||
@ -594,7 +587,7 @@ final class Utils{
|
||||
* @phpstan-param \Closure(TMemberType) : void $validator
|
||||
*/
|
||||
public static function validateArrayValueType(array $array, \Closure $validator) : void{
|
||||
foreach($array as $k => $v){
|
||||
foreach(Utils::promoteKeys($array) as $k => $v){
|
||||
try{
|
||||
$validator($v);
|
||||
}catch(\TypeError $e){
|
||||
|
@ -375,6 +375,8 @@ class World implements ChunkManager{
|
||||
|
||||
private \Logger $logger;
|
||||
|
||||
private RuntimeBlockStateRegistry $blockStateRegistry;
|
||||
|
||||
/**
|
||||
* @phpstan-return ChunkPosHash
|
||||
*/
|
||||
@ -488,6 +490,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();
|
||||
|
||||
@ -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;
|
||||
@ -1394,7 +1397,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 +1531,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,27 +1582,67 @@ 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);
|
||||
//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]){
|
||||
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $boxes;
|
||||
}
|
||||
@ -1594,13 +1661,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 +1864,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 +1983,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();
|
||||
@ -2573,7 +2642,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
|
||||
|
@ -51,12 +51,12 @@ use function time;
|
||||
class BedrockWorldData extends BaseNbtWorldData{
|
||||
|
||||
public const CURRENT_STORAGE_VERSION = 10;
|
||||
public const CURRENT_STORAGE_NETWORK_VERSION = 748;
|
||||
public const CURRENT_STORAGE_NETWORK_VERSION = 776;
|
||||
public const CURRENT_CLIENT_VERSION_TARGET = [
|
||||
1, //major
|
||||
21, //minor
|
||||
40, //patch
|
||||
1, //revision
|
||||
60, //patch
|
||||
33, //revision
|
||||
0 //is beta
|
||||
];
|
||||
|
||||
|
@ -26,10 +26,12 @@ namespace pocketmine\world\generator;
|
||||
use pocketmine\data\bedrock\BiomeIds;
|
||||
use pocketmine\item\LegacyStringToItemParser;
|
||||
use pocketmine\item\LegacyStringToItemParserException;
|
||||
use pocketmine\world\World;
|
||||
use function array_map;
|
||||
use function explode;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -70,7 +72,7 @@ final class FlatGeneratorOptions{
|
||||
*/
|
||||
public static function parseLayers(string $layers) : array{
|
||||
$result = [];
|
||||
$split = array_map('\trim', explode(',', $layers));
|
||||
$split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN));
|
||||
$y = 0;
|
||||
$itemParser = LegacyStringToItemParser::getInstance();
|
||||
foreach($split as $line){
|
||||
@ -96,7 +98,7 @@ final class FlatGeneratorOptions{
|
||||
* @throws InvalidGeneratorOptionsException
|
||||
*/
|
||||
public static function parsePreset(string $presetString) : self{
|
||||
$preset = explode(";", $presetString);
|
||||
$preset = explode(";", $presetString, limit: 4);
|
||||
$blocks = $preset[1] ?? "";
|
||||
$biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS);
|
||||
$optionsString = $preset[3] ?? "";
|
||||
@ -109,9 +111,10 @@ final class FlatGeneratorOptions{
|
||||
$params = true;
|
||||
if($matches[3][$i] !== ""){
|
||||
$params = [];
|
||||
$p = explode(" ", $matches[3][$i]);
|
||||
$p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX);
|
||||
foreach($p as $k){
|
||||
$k = explode("=", $k);
|
||||
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1
|
||||
$k = explode("=", $k, limit: 3);
|
||||
if(isset($k[1])){
|
||||
$params[$k[0]] = $k[1];
|
||||
}
|
||||
|
@ -18,6 +18,12 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/Server.php
|
||||
|
||||
-
|
||||
message: '#^Dynamic new is not allowed\.$#'
|
||||
identifier: pocketmine.new.dynamic
|
||||
count: 1
|
||||
path: ../../../src/Server.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\<string, list\<string\>\> but returns array\<string, array\<mixed\>\>\.$#'
|
||||
identifier: return.type
|
||||
@ -48,18 +54,18 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/VersionInfo.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\VersionInfo\:\:GIT_HASH\(\) should return string but returns mixed\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: ../../../src/VersionInfo.php
|
||||
|
||||
-
|
||||
message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: ../../../src/VersionInfo.php
|
||||
|
||||
-
|
||||
message: '#^Dynamic new is not allowed\.$#'
|
||||
identifier: pocketmine.new.dynamic
|
||||
count: 1
|
||||
path: ../../../src/block/Block.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
@ -516,6 +522,12 @@ parameters:
|
||||
count: 3
|
||||
path: ../../../src/block/tile/Spawnable.php
|
||||
|
||||
-
|
||||
message: '#^Dynamic new is not allowed\.$#'
|
||||
identifier: pocketmine.new.dynamic
|
||||
count: 1
|
||||
path: ../../../src/block/tile/TileFactory.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#'
|
||||
identifier: argument.type
|
||||
@ -918,24 +930,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$haystack of function stripos expects string, mixed given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$subject of function preg_match expects string, mixed given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, mixed given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginDescription.php
|
||||
|
||||
-
|
||||
message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\<string\>\) does not accept list\<mixed\>\.$#'
|
||||
identifier: assign.propertyType
|
||||
@ -961,13 +955,13 @@ parameters:
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
|
||||
identifier: return.type
|
||||
message: '#^Dynamic new is not allowed\.$#'
|
||||
identifier: pocketmine.new.dynamic
|
||||
count: 1
|
||||
path: ../../../src/resourcepacks/ZippedResourcePack.php
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getSha256\(\) should return string but returns string\|false\.$#'
|
||||
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: ../../../src/resourcepacks/ZippedResourcePack.php
|
||||
@ -1009,7 +1003,7 @@ parameters:
|
||||
path: ../../../src/utils/Config.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<int\|string, mixed\> given\.$#'
|
||||
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<mixed\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: ../../../src/utils/Config.php
|
||||
@ -1278,6 +1272,12 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: '#^Dynamic new is not allowed\.$#'
|
||||
identifier: pocketmine.new.dynamic
|
||||
count: 1
|
||||
path: ../../../src/world/generator/GeneratorRegisterTask.php
|
||||
|
||||
-
|
||||
message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
|
||||
identifier: return.type
|
||||
|
@ -114,12 +114,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/crash/CrashDump.php
|
||||
|
||||
-
|
||||
message: '#^Call to function assert\(\) with false and ''unknown hit type'' will always evaluate to false\.$#'
|
||||
identifier: function.impossibleType
|
||||
count: 1
|
||||
path: ../../../src/entity/projectile/Projectile.php
|
||||
|
||||
-
|
||||
message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\<pocketmine\\item\\WritableBookPage\>\) does not accept non\-empty\-array\<int, pocketmine\\item\\WritableBookPage\>\.$#'
|
||||
identifier: assign.propertyType
|
||||
|
55
tests/phpstan/rules/DisallowDynamicNewRule.php
Normal file
55
tests/phpstan/rules/DisallowDynamicNewRule.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\phpstan\rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
|
||||
/**
|
||||
* @phpstan-implements Rule<New_>
|
||||
*/
|
||||
final class DisallowDynamicNewRule implements Rule{
|
||||
|
||||
public function getNodeType() : string{
|
||||
return New_::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope) : array{
|
||||
/** @var New_ $node */
|
||||
if($node->class instanceof Expr){
|
||||
return [
|
||||
RuleErrorBuilder::message("Dynamic new is not allowed.")
|
||||
->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.")
|
||||
->identifier("pocketmine.new.dynamic")
|
||||
->build()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
92
tests/phpstan/rules/ExplodeLimitRule.php
Normal file
92
tests/phpstan/rules/ExplodeLimitRule.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\phpstan\rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PHPStan\Analyser\ArgumentsNormalizer;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
use PHPStan\Rules\Rule;
|
||||
use PHPStan\Rules\RuleErrorBuilder;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* @phpstan-implements Rule<FuncCall>
|
||||
*/
|
||||
final class ExplodeLimitRule implements Rule{
|
||||
private ReflectionProvider $reflectionProvider;
|
||||
|
||||
public function __construct(
|
||||
ReflectionProvider $reflectionProvider
|
||||
){
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
}
|
||||
|
||||
public function getNodeType() : string{
|
||||
return FuncCall::class;
|
||||
}
|
||||
|
||||
public function processNode(Node $node, Scope $scope) : array{
|
||||
if(!$node->name instanceof Name){
|
||||
return [];
|
||||
}
|
||||
|
||||
if(!$this->reflectionProvider->hasFunction($node->name, $scope)){
|
||||
return [];
|
||||
}
|
||||
|
||||
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
|
||||
|
||||
if($functionReflection->getName() !== 'explode'){
|
||||
return [];
|
||||
}
|
||||
|
||||
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
|
||||
$scope,
|
||||
$node->getArgs(),
|
||||
$functionReflection->getVariants(),
|
||||
$functionReflection->getNamedArgumentsVariants(),
|
||||
);
|
||||
|
||||
$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
|
||||
|
||||
if($normalizedFuncCall === null){
|
||||
return [];
|
||||
}
|
||||
|
||||
$count = count($normalizedFuncCall->getArgs());
|
||||
if($count !== 3){
|
||||
return [
|
||||
RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.')
|
||||
->identifier("pocketmine.explode.limit")
|
||||
->build()
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
194
tests/phpunit/crafting/AnvilCraftTest.php
Normal file
194
tests/phpunit/crafting/AnvilCraftTest.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?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\crafting;
|
||||
|
||||
use Generator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use pocketmine\item\enchantment\EnchantmentInstance;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use function floor;
|
||||
|
||||
class AnvilCraftTest extends TestCase{
|
||||
public static function materialRepairRecipeProvider() : Generator{
|
||||
yield "No repair available" => [
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
VanillaItems::DIAMOND(),
|
||||
null
|
||||
];
|
||||
|
||||
yield "Repair one damage" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage(1),
|
||||
VanillaItems::DIAMOND(),
|
||||
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), null)
|
||||
];
|
||||
|
||||
yield "Repair one damage with more materials than expected" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage(1),
|
||||
VanillaItems::DIAMOND()->setCount(2),
|
||||
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), VanillaItems::DIAMOND())
|
||||
];
|
||||
|
||||
$diamondPickaxeQuarter = (int) floor(VanillaItems::DIAMOND_PICKAXE()->getMaxDurability() / 4);
|
||||
yield "Repair one quarter" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter),
|
||||
VanillaItems::DIAMOND()->setCount(1),
|
||||
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE(), null)
|
||||
];
|
||||
|
||||
yield "Repair one quarter plus 1" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter + 1),
|
||||
VanillaItems::DIAMOND()->setCount(1),
|
||||
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE()->setDamage(1), null)
|
||||
];
|
||||
|
||||
yield "Repair more than one quarter" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter * 2),
|
||||
VanillaItems::DIAMOND()->setCount(2),
|
||||
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE(), null)
|
||||
];
|
||||
|
||||
yield "Repair more than one quarter with more materials than expected" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage($diamondPickaxeQuarter * 2),
|
||||
VanillaItems::DIAMOND()->setCount(3),
|
||||
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE(), VanillaItems::DIAMOND()->setCount(1))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider materialRepairRecipeProvider
|
||||
*/
|
||||
public function testMaterialRepairRecipe(Item $base, Item $material, ?AnvilCraftResult $expected) : void{
|
||||
$recipe = new MaterialRepairRecipe(
|
||||
new WildcardRecipeIngredient(),
|
||||
new WildcardRecipeIngredient()
|
||||
);
|
||||
self::assertAnvilCraftResultEquals($expected, $recipe->getResultFor($base, $material));
|
||||
}
|
||||
|
||||
public static function itemSelfCombineRecipeProvider() : Generator{
|
||||
yield "Combine two identical items without damage and enchants" => [
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
null
|
||||
];
|
||||
|
||||
yield "Enchant on base item and no enchants on material with no damage" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
null
|
||||
];
|
||||
|
||||
yield "No enchant on base item and one enchant on material" => [
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)), null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with damage" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage(10),
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
new AnvilCraftResult(1, VanillaItems::DIAMOND_PICKAXE()->setDamage(0), null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with damage for material" => [
|
||||
VanillaItems::DIAMOND_PICKAXE(),
|
||||
VanillaItems::DIAMOND_PICKAXE()->setDamage(10),
|
||||
null
|
||||
];
|
||||
|
||||
yield "Combine two identical items with different enchantments" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2)),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with different enchantments with damage" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))->setDamage(10),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
new AnvilCraftResult(4, VanillaItems::DIAMOND_PICKAXE()
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with different enchantments with damage for material" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2)),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1))->setDamage(10),
|
||||
new AnvilCraftResult(2, VanillaItems::DIAMOND_PICKAXE()
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::EFFICIENCY(), 2))
|
||||
->addEnchantment(new EnchantmentInstance(VanillaEnchantments::UNBREAKING(), 1)),
|
||||
null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with same enchantment level" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
|
||||
new AnvilCraftResult(8, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2)), null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with same enchantment level and damage" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1))->setDamage(10),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
|
||||
new AnvilCraftResult(10, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2))->setDamage(0), null)
|
||||
];
|
||||
|
||||
yield "Combine two identical items with same enchantment level and damage for material" => [
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1)),
|
||||
VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 1))->setDamage(10),
|
||||
new AnvilCraftResult(8, VanillaItems::DIAMOND_PICKAXE()->addEnchantment(new EnchantmentInstance(VanillaEnchantments::FORTUNE(), 2)), null)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider itemSelfCombineRecipeProvider
|
||||
*/
|
||||
public function testItemSelfCombineRecipe(Item $base, Item $combined, ?AnvilCraftResult $expected) : void{
|
||||
$recipe = new ItemSelfCombineRecipe(new WildcardRecipeIngredient());
|
||||
self::assertAnvilCraftResultEquals($expected, $recipe->getResultFor($base, $combined));
|
||||
}
|
||||
|
||||
private static function assertAnvilCraftResultEquals(?AnvilCraftResult $expected, ?AnvilCraftResult $actual) : void{
|
||||
if($expected === null){
|
||||
self::assertNull($actual, "Recipe did not match expected result");
|
||||
return;
|
||||
}else{
|
||||
self::assertNotNull($actual, "Recipe did not match expected result");
|
||||
}
|
||||
self::assertEquals($expected->getXpCost(), $actual->getXpCost(), "XP cost did not match expected result");
|
||||
self::assertTrue($expected->getOutput()->equalsExact($actual->getOutput()), "Recipe output did not match expected result");
|
||||
$sacrificeResult = $expected->getSacrificeResult();
|
||||
if($sacrificeResult !== null){
|
||||
$resultExpected = $actual->getSacrificeResult();
|
||||
self::assertNotNull($resultExpected, "Recipe sacrifice result did not match expected result");
|
||||
self::assertTrue($sacrificeResult->equalsExact($resultExpected), "Recipe sacrifice result did not match expected result");
|
||||
}else{
|
||||
self::assertNull($actual->getSacrificeResult(), "Recipe sacrifice result did not match expected result");
|
||||
}
|
||||
}
|
||||
}
|
37
tests/phpunit/crafting/WildcardRecipeIngredient.php
Normal file
37
tests/phpunit/crafting/WildcardRecipeIngredient.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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\crafting;
|
||||
|
||||
use pocketmine\item\Item;
|
||||
|
||||
final class WildcardRecipeIngredient implements RecipeIngredient{
|
||||
|
||||
public function __toString() : string{
|
||||
return "WildcardRecipeIngredient()";
|
||||
}
|
||||
|
||||
public function accepts(Item $item) : bool{
|
||||
return true;
|
||||
}
|
||||
}
|
Submodule tests/plugins/DevTools updated: c6dca357c7...a030d39e51
@ -51,8 +51,10 @@ use function array_unique;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function fopen;
|
||||
use function fwrite;
|
||||
use function get_class;
|
||||
use function get_debug_type;
|
||||
@ -885,6 +887,44 @@ function cmdUpdateAll(array $argv) : int{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function cmdDumpTable(array $argv) : int{
|
||||
$tableFile = $argv[2];
|
||||
$outputFile = $argv[3];
|
||||
|
||||
$output = fopen($outputFile, 'wb');
|
||||
if($output === false){
|
||||
fwrite(STDERR, "Failed to open output file: $outputFile\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$table = loadUpgradeTableFromFile($tableFile, reverse: false);
|
||||
ksort($table, SORT_STRING);
|
||||
|
||||
foreach(Utils::stringifyKeys($table) as $oldName => $mappings){
|
||||
fwrite($output, "---------- MAPPING LIST: $oldName ----------\n");
|
||||
foreach($mappings as $mapping){
|
||||
$oldNbt = $mapping->old->toVanillaNbt();
|
||||
$oldNbt->setInt("version", $mapping->new->getVersion());
|
||||
|
||||
//intentionally not reused result of toVanillaNbt otherwise output wouldn't include version
|
||||
fwrite($output, "OLD: " . $mapping->old->toVanillaNbt() . "\n");
|
||||
if(!$oldNbt->equals($mapping->new->toVanillaNbt())){
|
||||
fwrite($output, "NEW: " . $mapping->new->toVanillaNbt() . "\n");
|
||||
}else{
|
||||
fwrite($output, "NEW: version bump only (" . $mapping->new->getVersion() . ")\n");
|
||||
}
|
||||
fwrite($output, "-----\n");
|
||||
}
|
||||
}
|
||||
|
||||
fclose($output);
|
||||
\GlobalLogger::get()->info("Table dump file $outputFile generated successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
@ -893,7 +933,8 @@ function main(array $argv) : int{
|
||||
"generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)],
|
||||
"test" => [["palette upgrade table file", "schema output file"], cmdTest(...)],
|
||||
"update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)],
|
||||
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)]
|
||||
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)],
|
||||
"dump-table" => [["palette upgrade table file", "txt output file"], cmdDumpTable(...)]
|
||||
];
|
||||
|
||||
$selected = $argv[1] ?? null;
|
||||
|
@ -35,6 +35,7 @@ use pocketmine\crafting\json\SmithingTrimRecipeData;
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\data\bedrock\item\BlockItemIdMap;
|
||||
use pocketmine\data\bedrock\item\ItemTypeNames;
|
||||
use pocketmine\inventory\json\CreativeGroupData;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\NBT;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
@ -48,15 +49,17 @@ use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
|
||||
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
|
||||
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
|
||||
use pocketmine\network\mcpe\protocol\CreativeContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemRegistryPacket;
|
||||
use pocketmine\network\mcpe\protocol\PacketPool;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield;
|
||||
use pocketmine\network\mcpe\protocol\types\ItemTypeEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ComplexAliasItemDescriptor;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe;
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
|
||||
@ -134,6 +137,19 @@ class ParserPacketHandler extends PacketHandler{
|
||||
return base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($statePropertiesTag)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ItemStackData[] $items
|
||||
*/
|
||||
private function creativeGroupEntryToJson(CreativeGroupEntry $entry, array $items) : CreativeGroupData{
|
||||
$data = new CreativeGroupData();
|
||||
|
||||
$data->group_name = $entry->getCategoryName();
|
||||
$data->group_icon = $entry->getIcon()->getId() === 0 ? null : $this->itemStackToJson($entry->getIcon());
|
||||
$data->items = $items;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function itemStackToJson(ItemStack $itemStack) : ItemStackData{
|
||||
if($itemStack->getId() === 0){
|
||||
throw new InvalidArgumentException("Cannot serialize a null itemstack");
|
||||
@ -234,31 +250,68 @@ class ParserPacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
public function handleStartGame(StartGamePacket $packet) : bool{
|
||||
$this->itemTypeDictionary = new ItemTypeDictionary($packet->itemTable);
|
||||
|
||||
echo "updating legacy item ID mapping table\n";
|
||||
$table = [];
|
||||
foreach($packet->itemTable as $entry){
|
||||
$table[$entry->getStringId()] = [
|
||||
"runtime_id" => $entry->getNumericId(),
|
||||
"component_based" => $entry->isComponentBased()
|
||||
];
|
||||
}
|
||||
ksort($table, SORT_STRING);
|
||||
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
|
||||
|
||||
foreach(Utils::promoteKeys($packet->levelSettings->experiments->getExperiments()) as $name => $experiment){
|
||||
echo "Experiment \"$name\" is " . ($experiment ? "" : "not ") . "active\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleItemRegistry(ItemRegistryPacket $packet) : bool{
|
||||
$this->itemTypeDictionary = new ItemTypeDictionary($packet->getEntries());
|
||||
|
||||
echo "updating legacy item ID mapping table\n";
|
||||
$emptyNBT = new CompoundTag();
|
||||
$table = [];
|
||||
foreach($packet->getEntries() as $entry){
|
||||
$table[$entry->getStringId()] = [
|
||||
"runtime_id" => $entry->getNumericId(),
|
||||
"component_based" => $entry->isComponentBased(),
|
||||
"version" => $entry->getVersion(),
|
||||
];
|
||||
|
||||
$componentNBT = $entry->getComponentNbt()->getRoot();
|
||||
if(!$componentNBT->equals($emptyNBT)){
|
||||
$table[$entry->getStringId()]["component_nbt"] = base64_encode((new LittleEndianNbtSerializer())->write(new TreeRoot($componentNBT)));
|
||||
}
|
||||
}
|
||||
ksort($table, SORT_STRING);
|
||||
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
|
||||
|
||||
echo "updating item registry\n";
|
||||
$items = array_map(function(ItemTypeEntry $entry) : array{
|
||||
return self::objectToOrderedArray($entry);
|
||||
}, $packet->getEntries());
|
||||
file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleCreativeContent(CreativeContentPacket $packet) : bool{
|
||||
echo "updating creative inventory data\n";
|
||||
$items = array_map(function(CreativeContentEntry $entry) : array{
|
||||
return self::objectToOrderedArray($this->itemStackToJson($entry->getItem()));
|
||||
}, $packet->getEntries());
|
||||
file_put_contents($this->bedrockDataPath . '/creativeitems.json', json_encode($items, JSON_PRETTY_PRINT) . "\n");
|
||||
|
||||
$groupItems = [];
|
||||
foreach($packet->getItems() as $itemEntry){
|
||||
$groupItems[$itemEntry->getGroupId()][] = $this->itemStackToJson($itemEntry->getItem());
|
||||
}
|
||||
|
||||
static $typeMap = [
|
||||
CreativeContentPacket::CATEGORY_CONSTRUCTION => "construction",
|
||||
CreativeContentPacket::CATEGORY_NATURE => "nature",
|
||||
CreativeContentPacket::CATEGORY_EQUIPMENT => "equipment",
|
||||
CreativeContentPacket::CATEGORY_ITEMS => "items",
|
||||
];
|
||||
|
||||
$groupCategories = [];
|
||||
foreach(Utils::promoteKeys($packet->getGroups()) as $groupId => $group){
|
||||
$category = $typeMap[$group->getCategoryId()] ?? throw new PacketHandlingException("Unknown creative category ID " . $group->getCategoryId());
|
||||
//FIXME: objectToOrderedArray might mess with the order of groupItems
|
||||
//this isn't a problem right now because it's a list, but could cause problems in the future
|
||||
$groupCategories[$category][] = self::objectToOrderedArray($this->creativeGroupEntryToJson($group, $groupItems[$groupId]));
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($groupCategories) as $category => $categoryGroups){
|
||||
file_put_contents($this->bedrockDataPath . '/creative/' . $category . '.json', json_encode($categoryGroups, JSON_PRETTY_PRINT) . "\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -571,7 +624,7 @@ function main(array $argv) : int{
|
||||
}
|
||||
|
||||
foreach($packets as $lineNum => $line){
|
||||
$parts = explode(':', $line);
|
||||
$parts = explode(':', $line, limit: 3);
|
||||
if(count($parts) !== 2){
|
||||
fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64');
|
||||
return 1;
|
||||
|
Reference in New Issue
Block a user