Compare commits

..

98 Commits

Author SHA1 Message Date
ea8f971287 Release 5.23.0 (#6561) 2024-12-05 15:51:13 +00:00
62e1d87f5e Mention internal timings deprecations
plugins shouldn't be using these, but since it's not marked as internal, we can't be sure.
2024-12-05 15:47:34 +00:00
ea068d4907 Update 5.23.md 2024-12-05 15:01:49 +00:00
fa7bc78e7c Prepare 5.23.0 release 2024-12-05 14:29:17 +00:00
0aaf4238a8 more deprecations in line with major-next 2024-12-05 13:02:09 +00:00
35a90d24ec AsyncTask: deprecate progress update related stuff 2024-12-05 12:57:26 +00:00
9a6e258b6c Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-12-05 10:13:06 +00:00
a1448bfb88 5.22.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12160926590
2024-12-04 13:38:41 +00:00
ba6828c6bd Release 5.22.0 (Bedrock 1.21.50 support) (#6559)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-12-04 13:36:52 +00:00
5fc96c393d Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12151373979
2024-12-04 01:39:58 +00:00
2d0321ff02 Switch back to official JsonMapper
the issues that led to the need for a fork have been addressed in the 5.0.0 release.
2024-12-03 15:19:38 +00:00
c56d4d3e3c dependabot: update github actions deps together, monthly 2024-12-03 14:56:22 +00:00
06028aac97 issues: don't recommend forums to get help 2024-12-03 02:07:58 +00:00
950f7ad7a4 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12131296321
2024-12-03 01:39:32 +00:00
49da50659f Bump docker/build-push-action from 6.9.0 to 6.10.0 (#6553) 2024-12-02 16:36:12 +00:00
ae9b4dbb05 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12111121061
2024-12-02 01:41:01 +00:00
fcef015f32 L link 2024-12-02 00:40:55 +00:00
f1a3b42620 Implement frost walker enchantment (#5497)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-12-01 19:46:38 +00:00
f3763ae691 Implement Recovery compass (#5502)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-12-01 18:25:45 +00:00
12214792b3 Allow eating in creative & peaceful
closes #5923
closes #6056
2024-12-01 17:42:26 +00:00
93a9007f3c Added ClosureCommand (#6063)
this is intended to replace PluginCommand and CommandExecutor, both of which are overengineered and unfit for purpose.
Allowing a closure allows much greater flexibility.

We can't use this within the core yet, as plugins will expect PluginBase->getCommand() to return PluginCommand (with its associated setExecutor() and similar APIs).
However, I think this is useful enough to add by itself.
2024-12-01 15:10:07 +00:00
a593180ef9 Deprecate some stuff 2024-12-01 15:01:41 +00:00
61560ec375 Support for collecting timings from threads, and implement async task timings (#6333)
The following callbacks can now be registered in timings, to allow threads to be notified of these events:
- Turning on/off (`TimingsHandler::getToggleCallbacks()->add(...)`)
- Reset (`TimingsHandler::getReloadCallbacks()->add(...)`)
- Collect (`TimingsHandler::getCollectCallbacks()->add(...)`)

Collect callbacks must return `list<Promise>`. The promises must be `resolve()`d with `list<string>` of printed timings records, as returned by `TimingsHandler::printCurrentThreadRecords()`. It's recommended to use 1 promise per thread.

A timings report will be produced once all promises have been resolved.

This system is used internally to collect timings for async tasks (closes #6166).

For timings viewer developers:
Timings format version has been bumped to 3 to accommodate this change. Timings groups should now include a `ThreadId`  at the end of timings group names to ensure that their record IDs are segregated correctly, as they could otherwise conflict between threads. The main thread is not required to specify a thread ID. See pmmp/timings@13cefa6279 for implementation examples.

New PHPStan error is caused by phpstan/phpstan#10924
2024-12-01 14:49:27 +00:00
44771c892d Use PlayerAuthInputPacket::SNEAKING flag to test for sneaking (#6544)
This binds internal sneaking to whether or not the player is currently pressing the shift key, which fixes #5792 and fixes #5903.

However, it does introduce visual issues with sneaking, as explained in #6548. This needs to be worked on separately. For now, it's better we trade 2 functional bugs for 1 visual bug.
2024-12-01 13:14:30 +00:00
89f88a9333 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12092463227
2024-11-30 01:35:06 +00:00
9cab72ed59 5.21.3 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/12090411351
2024-11-29 20:43:17 +00:00
2bd9f4108b 5.21.2 (#6545) 2024-11-29 20:42:14 +00:00
cd2a1fdc1d Bump build/php from 8a396c6 to 5016e0a (#6541) 2024-11-29 11:03:27 +00:00
a396233e57 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12077579940
2024-11-29 01:37:52 +00:00
d666f17ec6 Bump build/php from a51259d to 8a396c6 (#6540) 2024-11-28 15:46:30 +00:00
a84326cb2d Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12060967026
2024-11-28 01:37:49 +00:00
0543bf301e draft-release: updated php_download_url 2024-11-27 19:19:51 +00:00
603ab081bc Merge branch 'stable' into minor-next 2024-11-27 17:56:34 +00:00
daacc8eddb Updated setup-php-action to 3.2.0 2024-11-27 17:56:00 +00:00
269effcecf Introduce Utils::getRandomFloat() (#6532)
Drop-in replacement for lcg_value() for PHP 8.4
2024-11-26 11:33:29 +00:00
ae750b60d0 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12022106146
2024-11-26 01:36:41 +00:00
905a10e980 Deprecate InventoryAction->onAddToTransaction()
this never made any sense
2024-11-25 21:39:35 +00:00
52fe2cb97f PermissionManager: deprecate permission subscription system
this is no longer used by the core, and as far as I can tell no plugin uses it either.
it was used in the past for chat broadcast channels, but not anymore.
2024-11-25 21:29:26 +00:00
8cdc7d7ee1 auto-approve: drop pull_request_review trigger
this doesn't work for PRs from forks, since fork PRs don't have access to repo secrets.

we'll need some more advanced mechanism to avoid redundant reviews, but that's a job for another time.
2024-11-25 20:43:59 +00:00
12ae8dc03b Merge branch 'stable' into minor-next 2024-11-25 14:32:30 +00:00
a9787f0d99 Fix PHPStan error 2024-11-25 14:32:17 +00:00
5325ecee37 Deal with a whole lot of PHPStan suppressed key casting errors
closes #6534
2024-11-25 14:30:58 +00:00
30ee0aa63d Merge branch 'stable' into minor-next 2024-11-24 23:50:56 +00:00
aef4fa7174 Remove unused variable 2024-11-24 23:50:30 +00:00
53aa380ca3 Candle: fix extinguish logic
closes #5983
2024-11-24 23:49:32 +00:00
a5f607138c Implement Ice Bomb (#5452)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2024-11-24 15:01:26 +00:00
8338ebaffd Add generic types for TaskHandler (#6030) 2024-11-24 14:14:34 +00:00
082119cfd2 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990667732
2024-11-23 21:49:01 +00:00
7460e12b6a pr-remove-waiting-label: suppress failure on 404 errors
this usually means the label wasn't on the PR in the first place
2024-11-23 21:48:30 +00:00
a53b0116a0 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990103798
2024-11-23 20:14:23 +00:00
5b72f202bf actions: automatically remove waiting label from PRs on synchronize
there are probably other conditions where we'd want to remove this, but this will do for now.
2024-11-23 20:13:46 +00:00
8b6adf86d6 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11989715327
2024-11-23 19:10:12 +00:00
0070426e97 auto-approve: only re-review if previous review was dismissed
this avoids unnecessary spam when someone clicks "Update branch" on the PR.
2024-11-23 19:07:24 +00:00
a523ed6e40 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11964819241
2024-11-22 01:36:16 +00:00
7c2ed7d884 Fix insta break check (#6528) 2024-11-21 02:22:10 +00:00
1e3a858de6 Add setUp and tearDown for event unit tests 2024-11-20 16:42:50 +00:00
844ba0ff9f Move event test fixtures to a subdirectory 2024-11-20 15:40:34 +00:00
49bdaee930 Move event handler inheritance test to PHPUnit using mock objects
this is dodgy and we shouldn't rely on it long term.

relates to #6524
2024-11-20 15:38:51 +00:00
9195c88670 ConsoleReader: Use proc_open()'s socket support to send commands back to the main server process (#5273)
Support for this was introduced in PHP 8.0, though not mentioned in any changelog: php/php-src#5777

This simplifies the subprocess handling considerably. However, there is a potential for problems if PHP generates any E_* errors, since these get written to STDOUT as well.
To avoid error messages being treated as a command, a hash is attached to each IPC message, seeded with an incrementing counter. This prevents error messages causing command replays or unintended commands.

Unfortunately, PHP doesn't support binding pipes other than stdin/stdout/stderr on Windows for the child process, so we have to use stdout for this. In the future, if it becomes possible, a dedicated pipe for the purpose should be introduced. We'd need something like php://fd/<number> to work on Windows.
2024-11-20 14:56:52 +00:00
ae19d05fd5 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11924942897
2024-11-20 01:27:30 +00:00
bf7a53b00f Update api-change-request.md
[ci skip]
2024-11-19 17:47:46 +00:00
e710b3750f Adjust pretty name of closures on PHP 8.4 (#6351)
related to https://github.com/php/php-src/pull/13550

see analog symfony change: https://github.com/symfony/symfony/pull/54614
2024-11-19 17:05:21 +00:00
8ccd1edb17 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11904511045
2024-11-19 01:36:11 +00:00
faf1e26bac Fix: implicitly nullable parameter declarations deprecated (#6522) 2024-11-18 23:54:22 +00:00
b4259ef988 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11874902000
2024-11-17 01:39:52 +00:00
b2aa6396c3 auto-approve: don't request approvals for draft PRs 2024-11-16 23:15:07 +00:00
1555fa17e7 Added ability to pick end crystal item (#6509) 2024-11-16 19:06:03 +00:00
e77f2c5198 Implemented End Crystal (#4715)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-11-16 17:57:57 +00:00
e8e441f739 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11866065217
2024-11-16 01:37:27 +00:00
48a908ee8c maybe making this specific to gameplay wasn't a good idea 2024-11-16 01:36:51 +00:00
dc87a2b10e Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11865975725
2024-11-16 01:26:31 +00:00
d3add78d3e Add support for basic entity picking via middle-click (#5397)
Support for more advanced stuff like NBT copying wasn't added in this PR, as the NBT used by PM is currently an inconsistent mess and doesn't play nice with vanilla. In the interests of avoiding this mess propagating, it's been left for another time.

Adds PlayerEntityPickEvent a la PlayerBlockPickEvent
and Entity->getPickedItem().
2024-11-15 21:27:10 +00:00
ff695a5f97 PlayerInteractEvent: added APIs to independently control reaction of item and block (#4683)
This allows, for example, banning the usage of spawn eggs, without preventing opening of doors, without the need for item ID whitelists.

It also allows customizing the behaviour of item and block interactions when sneaking - it's now possible to force spawn eggs to work when sneaking, or force containers to open.

Finally, this also allows preventing any interaction at all without preventing block placement (by setting both to false). Since cancelling the event will typically prevent placement too (which might not be desired).

Side note: Blocks are now always synced when right-clicking on a block.
This differs from the previous behaviour, where the blocks were only synced when the action "failed".
However, since this change introduces a situation where the action may succeed but have different results than the client expects, it's best to just always sync blocks in this situation.

Fixes #3267
2024-11-15 21:19:54 +00:00
4b630cb726 start.sh: print warnings on unusual exit codes from the server process (#6497) 2024-11-15 21:14:21 +00:00
4331f69b9c Update team-pr-auto-approve.yml 2024-11-15 20:44:51 +00:00
fb1213e964 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11862895039
2024-11-15 20:21:32 +00:00
8a693f2a4c team-pr-auto-approve: Use RestrictedActions auto approver 2024-11-15 20:08:54 +00:00
8474eaf5f1 Adjust Sugar Cane to break when there is no water (#6486) 2024-11-15 16:27:27 +00:00
a75d4687ce Implemented vanilla /xp command (#6429) 2024-11-15 16:09:55 +00:00
0b0c425805 Extract glow lichen multi face logic into traits (#6396)
This will be useful for future block additions
2024-11-15 15:47:26 +00:00
b5469dede2 Flowable blocks now can't be placed inside liquid (#5392) 2024-11-15 00:10:43 +00:00
e0d270a870 Merge branch 'minor-next' of github.com:pmmp/PocketMine-MP into minor-next 2024-11-14 23:14:33 +00:00
2d9cee3d62 Update Language dependency 2024-11-14 23:14:23 +00:00
ed64231c57 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2024-11-14 23:12:26 +00:00
33a7b46329 Use reflection to locate BlockTypeIds and ItemTypeIds for VanillaBlocks/VanillaItems (#6498)
Use reflection to locate BlockTypeIds and ItemTypeIds for VanillaBlocks/VanillaItems

Since BlockTypeIds and ItemTypeIds are derived from VanillaBlocks and VanillaItems respectively anyway (they only exist to allow identifying blocks/items without having to create instances of them), this hack is probably OK, and reduces the chances of mistakes.
Previously it was explored to have these IDs generated by auto-incrementing in VanillaBlocks/Items and have the constants generated that way, but this proved to be too problematic because of unstable diffs no matter how we chose to sort the elements. See #6313 for previous research on the subject.

This is obviously not a desirable hack to keep long-term. In the future it will probably make sense to redesign VanillaBlocks like so:

enum VanillaBlocks { ... }
VanillaBlocks::STONE (the type ID)
VanillaBlocks::STONE->new() (to create a block)

However, more research is needed on this, as I'd prefer not to make block creation any more verbose.
2024-11-14 17:32:22 +00:00
9b58d35516 Implement Goat horns (#5232)
Co-authored-by: ipad54 <63200545+ipad54@users.noreply.github.com>
Co-authored-by: Dylan T. <dktapps@pmmp.io>
2024-11-14 13:57:07 +00:00
3629ee7e7b Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11824307499
2024-11-13 19:21:16 +00:00
fbeb017670 Promise: allow zero promises
not supporting this has caused problems every time this function has been used in reality so far (#6092 and #6333).
2024-11-13 14:55:14 +00:00
09bf203267 Update RuntimeDataDescriber.php 2024-11-12 22:57:14 +00:00
f3cc4a28e1 Easy wins for PHPStan 2.0 support 2024-11-12 22:12:54 +00:00
fe70150db2 Update composer dependencies 2024-11-12 21:47:52 +00:00
054538e6b7 Update PULL_REQUEST_TEMPLATE.md 2024-11-12 17:54:31 +00:00
3586bc42a9 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11802296296
2024-11-12 17:14:24 +00:00
1f86949836 Create CODEOWNERS 2024-11-12 14:37:02 +00:00
053a71c59d Bump build/php from 084822a to a51259d (#6495) 2024-11-12 14:28:32 +00:00
07d5046b83 5.21.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/11796630402
2024-11-12 11:48:59 +00:00
fa3529966f Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11769186885
2024-11-10 22:56:08 +00:00
05a9e9c76e Implemented sound when drinking a potion (#6444) 2024-11-10 15:07:18 +00:00
191 changed files with 4166 additions and 1924 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @pmmp/server-developers

View File

@ -7,13 +7,13 @@ assignees: ''
---
<!--- tell us what you want -->
## Description
<!--- Describe the problem you want to solve -->
## Problem description
<!--- explain why you want this and why it's a good idea -->
## Justification
<!--- Describe what changes you want to make to solve this problem -->
## Proposed solution
<!--- (optional) describe alternative methods you've explored to achieve your goal -->
## Alternative methods
## Alternative solutions that don't require API changes

View File

@ -3,9 +3,6 @@ contact_links:
- name: Help & support on Discord
url: https://discord.gg/bmSAZBG
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
- name: Help & support on forums
url: https://forums.pmmp.io
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
- name: Documentation
url: https://pmmp.rtfd.io
about: PocketMine-MP documentation

View File

@ -1,43 +1,32 @@
## Introduction
<!-- Summarize your PR here. Keep it short and simple. -->
<!-- Explain existing problems or why this pull request is necessary -->
### Relevant issues
<!-- List relevant issues here -->
### Related issues & PRs
<!--
* Fixes #1
* Fixes #2
* Related to #2
-->
## Changes
### API changes
<!-- Any additions to the API that should be documented in release notes? -->
<!-- If not, you can delete this section -->
### Behavioural changes
<!-- Any change in how the server behaves, or its performance? -->
<!-- If not, you can delete this section -->
## Backwards compatibility
<!-- Any possible backwards incompatible changes? How are they solved, or how can they be solved? -->
<!-- If not, you can delete this section -->
## Follow-up
<!-- Suggest any actions to be done before/after merging this pull request -->
<!--
Requires translations:
| Name | Value in eng.ini |
| :--: | :---: |
| `foo.bar` | `Foo bar` |
-->
<!-- For example, future changes that this PR lays the groundwork for -->
## Tests
<!--
PRs which have not been tested MUST be marked as draft.
If this PR affects gameplay or user experience in some way, it must be manually tested.
Include any screenshots or videos of manual testing here.
Any test plugin code should also be pasted here if it can't be adapted to a PHPUnit test.
-->
I tested this PR by doing the following (tick all that apply):
- [ ] Writing PHPUnit tests (commit these in the `tests/phpunit` folder)
- [ ] Playtesting using a Minecraft client (provide screenshots or a video)
- [ ] Writing a test plugin (provide the code and sample output)
- [ ] Other (provide details)

View File

@ -37,4 +37,7 @@ updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
interval: monthly
groups:
github-actions:
patterns: ["*"]

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v6.10.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.9.0
uses: docker/build-push-action@v6.10.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.9.0
uses: docker/build-push-action@v6.10.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.9.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp

View File

@ -64,6 +64,7 @@ jobs:
id: get-pm-version
run: |
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
@ -72,7 +73,7 @@ jobs:
- name: Generate PHP binary download URL
id: php-binary-url
run: |
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
- name: Generate build info
run: |

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -96,7 +96,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"
@ -128,7 +128,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.1.0
uses: pmmp/setup-php-action@3.2.0
with:
php-version: ${{ inputs.php }}
install-path: "./bin"

View File

@ -0,0 +1,33 @@
name: Remove waiting label from PRs
on:
pull_request_target:
types: synchronize
jobs:
delabel:
name: Remove label
runs-on: ubuntu-latest
steps:
- name: Remove label
uses: actions/github-script@v7
with:
github-token: ${{ github.token }}
script: |
const [owner, repo] = context.payload.repository.full_name.split('/');
try {
await github.rest.issues.removeLabel({
owner: owner,
repo: repo,
issue_number: context.payload.number,
name: "Status: Waiting on Author",
});
} catch (error) {
if (error.status === 404) {
//probably label wasn't set on the issue
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
} else {
throw error;
}
}

View File

@ -9,34 +9,30 @@ on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
permissions:
pull-requests: write
- synchronize
jobs:
approve:
name: Auto approve
dispatch:
name: Request approval
runs-on: ubuntu-latest
if: '! github.event.pull_request.draft'
steps:
- name: Check if PR author has write access
id: check-permission
uses: actions-cool/check-user-permission@v2
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
require: write
username: ${{ github.event.pull_request.user.login }}
#technically this would be fine for dependabot but generally bots don't count as team members
check-bot: true
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
owner: ${{ github.repository_owner }}
repositories: RestrictedActions
#TODO: Some way to avoid unnecessary repeated reviews would be nice here
- name: Approve PR if authorized
if: steps.check-permission.outputs.require-result == 'true' && steps.check-permission.outputs.check-result == 'false'
uses: juliangruber/approve-pull-request-action@v2
- name: Dispatch restricted action
uses: peter-evans/repository-dispatch@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ github.event.pull_request.number }}
token: ${{ steps.generate-token.outputs.token }}
repository: ${{ github.repository_owner }}/RestrictedActions
event-type: auto_approve_collaborator_pr
client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}", "reviewer_id": "0" }'

View File

@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t
- Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones.
- **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes.
- This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers.
- Check out the [issues page]() for something that you could tackle without too much effort.
- Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort.
- **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail.
- **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE.
- **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode.

View File

@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{

View File

@ -122,7 +122,7 @@ if(!is_array($ids)){
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
}
$cleanedIds = [];
foreach($ids as $name => $id){
foreach(Utils::promoteKeys($ids) as $name => $id){
if(!is_string($name) || !is_int($id)){
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
}

View File

@ -42,7 +42,7 @@ $constants = [];
* @phpstan-param-out array<string, string> $constants
*/
function collectProperties(string $prefix, array $properties, array &$constants) : void{
foreach($properties as $propertyName => $property){
foreach(Utils::promoteKeys($properties) as $propertyName => $property){
$fullPropertyName = ($prefix !== "" ? $prefix . "." : "") . $propertyName;
$constName = str_replace([".", "-"], "_", strtoupper($fullPropertyName));

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\update_registry_annotations;
use pocketmine\utils\Utils;
use function basename;
use function class_exists;
use function count;
@ -48,6 +49,7 @@ if(count($argv) !== 2){
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
*/
function generateMethodAnnotations(string $namespaceName, array $members) : string{
$selfName = basename(__FILE__);
@ -60,7 +62,7 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
static $lineTmpl = " * @method static %2\$s %s()";
$memberLines = [];
foreach($members as $name => $member){
foreach(Utils::stringifyKeys($members) as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false && $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();

View File

@ -110,3 +110,19 @@ Released 12th November 2024.
- Fixed garbage collector cycle count increase on player disconnect.
- Fixed weakness effect being applied to all attack types, causing damage splash potions to become weaker.
- Fixed Enchanted Golden Apple regeneration effect amplifier to match vanilla.
# 5.21.2
Released 29th November 2024.
## Fixes
- Fixed blocks destroyable by water being able to be placed inside water.
- Fixed deprecation warnings about nullable typehints on PHP 8.4.
- Fixed `Utils::getNiceClosureName()` not working correctly on PHP 8.4.
- Fixed incorrect break animations when breaking the block behind an instantly-breakable block like a torch.
- Fixed candle extinguish logic.
- Fixed various documentation issues around array key types.
- Introduced a new PHPStan rule along with `Utils::promoteKeys()` to improve PHPStan error reporting around unspecified array key types. Previously, some errors were missed due to PHPStan's BenevolentUnionType.
## DevOps
- `pmmp/server-developers` team is now automatically requested for review on any new PR.
- `Status: Waiting on Author` label is now automatically removed from PRs when they are updated.

16
changelogs/5.22.md Normal file
View File

@ -0,0 +1,16 @@
# 5.22.0
Released 4th December 2024.
**For Minecraft: Bedrock Edition 1.21.50**
This is a support release for Minecraft: Bedrock Edition 1.21.50.
**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.50.
- Removed support for earlier versions.

107
changelogs/5.23.md Normal file
View File

@ -0,0 +1,107 @@
# 5.23.0
Released 5th December 2024.
This is a minor feature release, including new gameplay features, internals improvements, API additions and
deprecations, and improvements to timings.
**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
- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load.
- Added `/xp` command.
- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors.
## Gameplay
- Added the following new items:
- End Crystal
- Goat Horn (all variants)
- Ice Bomb (from Education Edition)
- Recovery Compass
- Added the following enchantments:
- Frost Walker
- Sugarcane now self-destructs when there is no water adjacent to the base block.
- Added basic support for middle-clicking on entities to get their spawn eggs.
- Added sounds when drinking potions.
- Eating food is now allowed in creative mode and in peaceful difficulty.
## API
### `pocketmine\block`
- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks.
- The following API methods have been deprecated:
- `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work
### `pocketmine\command`
- The following classes have been added:
- `ClosureCommand` - allows registering a closure to execute a command
### `pocketmine\event`
- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions.
- This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc.
- If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed).
- The following API methods have been added:
- `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void`
- `public PlayerInteractEvent->setUseItem(bool $useItem) : void`
- `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc).
- `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc).
- The following new classes have been added:
- `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity
### `pocketmine\inventory\transaction`
- The following API methods have been deprecated:
- `InventoryAction->onAddToTransaction()`
### `pocketmine\permission`
- The following API methods have been deprecated:
- `PermissionManager->getPermissionSubscriptions()`
- `PermissionManager->subscribeToPermission()`
- `PermissionManager->unsubscribeFromAllPermissions()`
- `PermissionManager->unsubscribeFromPermission()`
### `pocketmine\plugin`
- The following classes have been deprecated:
- `DiskResourceProvider`
- `ResourceProvider`
### `pocketmine\promise`
- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array.
### `pocketmine\scheduler`
- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`.
- The following API methods have been deprecated
- `AsyncTask->publishProgress()`
- `AsyncTask->onProgressUpdate()`
### `pocketmine\timings`
- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected.
- The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report.
- Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings.
- PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future.
- The following API methods have been added:
- `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list<Promise<list<string>>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`.
- `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset
- `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled
- `public static TimingsHandler::requestPrintTimings() : Promise<list<string>>` - asynchronously collects timing results from all threads and assembles them into a single report
- The following API methods have been deprecated:
- `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead.
- `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed
- The following constants have been deprecated:
- `Timings::GROUP_BREAKDOWN` - no longer used
### `pocketmine\utils`
- The following API methods have been added:
- `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4.
## Internals
- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins.
- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified.
- While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively.
- It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`.
- In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely.
- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions.
- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
- Fixed various deprecation warnings in PHP 8.4.
- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.

View File

@ -32,16 +32,16 @@
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0",
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
"netresearch/jsonmapper": "~v5.0.0",
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40",
"pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40",
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
"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.19.0",
"pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",

317
composer.lock generated
View File

@ -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": "b2fbf6e7a9d650341dc71fa4dd124681",
"content-hash": "732102eca72dc1d29e7b67dfbce07653",
"packages": [
{
"name": "adhocore/json-comment",
@ -125,6 +125,57 @@
],
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "netresearch/jsonmapper",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Map nested JSON structures onto PHP classes",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0"
},
"time": "2024-09-08T10:20:00+00:00"
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.0.0",
@ -153,16 +204,16 @@
},
{
"name": "pocketmine/bedrock-data",
"version": "2.14.0+bedrock-1.21.40",
"version": "2.15.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "606d32ae426164b0615898b95d10e23293bed6ac"
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/606d32ae426164b0615898b95d10e23293bed6ac",
"reference": "606d32ae426164b0615898b95d10e23293bed6ac",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"shasum": ""
},
"type": "library",
@ -173,22 +224,22 @@
"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.40"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50"
},
"time": "2024-10-23T19:19:16+00:00"
"time": "2024-12-04T12:59:12+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.13.0",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "1dee9bbd0aaa65ed108b377b402746defe10b3b0"
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/1dee9bbd0aaa65ed108b377b402746defe10b3b0",
"reference": "1dee9bbd0aaa65ed108b377b402746defe10b3b0",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"shasum": ""
},
"type": "library",
@ -199,22 +250,22 @@
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.13.0"
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0"
},
"time": "2024-10-23T18:38:43+00:00"
"time": "2024-12-04T12:22:49+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "35.0.0+bedrock-1.21.40",
"version": "35.0.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459"
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"shasum": ""
},
"require": {
@ -245,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.0+bedrock-1.21.40"
"source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50"
},
"time": "2024-10-24T15:45:43+00:00"
"time": "2024-12-04T13:02:00+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -420,16 +471,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.19.6",
"version": "2.22.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e"
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
"reference": "93e473e20e7f4515ecf45c5ef0f9155b9247a86e",
"url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"shasum": ""
},
"type": "library",
@ -437,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.19.6"
"source": "https://github.com/pmmp/Language/tree/2.22.0"
},
"time": "2023-08-08T16:53:23+00:00"
"time": "2024-11-16T13:28:01+00:00"
},
{
"name": "pocketmine/log",
@ -565,60 +616,6 @@
},
"time": "2023-07-14T13:01:49+00:00"
},
{
"name": "pocketmine/netresearch-jsonmapper",
"version": "v4.4.999",
"source": {
"type": "git",
"url": "https://github.com/pmmp/netresearch-jsonmapper.git",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"replace": {
"netresearch/jsonmapper": "~4.2.0"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999"
},
"time": "2024-02-23T13:17:01+00:00"
},
{
"name": "pocketmine/raklib",
"version": "1.1.1",
@ -926,16 +923,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.4.9",
"version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "b51ef8059159330b74a4d52f68e671033c0fe463"
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463",
"reference": "b51ef8059159330b74a4d52f68e671033c0fe463",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3",
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3",
"shasum": ""
},
"require": {
@ -972,7 +969,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.9"
"source": "https://github.com/symfony/filesystem/tree/v6.4.13"
},
"funding": [
{
@ -988,24 +985,24 @@
"type": "tidelift"
}
],
"time": "2024-06-28T09:49:33+00:00"
"time": "2024-10-25T15:07:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -1051,7 +1048,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@ -1067,24 +1064,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -1131,7 +1128,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -1147,22 +1144,22 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.12.0",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@ -1201,7 +1198,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@ -1209,20 +1206,20 @@
"type": "tidelift"
}
],
"time": "2024-06-12T14:39:25+00:00"
"time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.0.2",
"version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@ -1233,7 +1230,7 @@
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@ -1265,9 +1262,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
"time": "2024-03-05T20:51:40+00:00"
"time": "2024-10-08T18:51:32+00:00"
},
{
"name": "phar-io/manifest",
@ -1548,32 +1545,32 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.15",
"version": "10.1.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae"
"reference": "7e308268858ed6baedc8704a304727d20bc07c77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
"reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
"reference": "7e308268858ed6baedc8704a304727d20bc07c77",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.18 || ^5.0",
"nikic/php-parser": "^4.19.1 || ^5.1.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
"sebastian/code-unit-reverse-lookup": "^3.0",
"sebastian/complexity": "^3.0",
"sebastian/environment": "^6.0",
"sebastian/lines-of-code": "^2.0",
"sebastian/version": "^4.0",
"theseer/tokenizer": "^1.2.0"
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-text-template": "^3.0.1",
"sebastian/code-unit-reverse-lookup": "^3.0.0",
"sebastian/complexity": "^3.2.0",
"sebastian/environment": "^6.1.0",
"sebastian/lines-of-code": "^2.0.2",
"sebastian/version": "^4.0.1",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^10.1"
@ -1585,7 +1582,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.1-dev"
"dev-main": "10.1.x-dev"
}
},
"autoload": {
@ -1614,7 +1611,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
},
"funding": [
{
@ -1622,7 +1619,7 @@
"type": "github"
}
],
"time": "2024-06-29T08:25:15+00:00"
"time": "2024-08-22T04:31:57+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1869,16 +1866,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.24",
"version": "10.5.38",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015"
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015",
"reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"shasum": ""
},
"require": {
@ -1888,26 +1885,26 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.10.1",
"phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
"myclabs/deep-copy": "^1.12.0",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
"phpunit/php-code-coverage": "^10.1.5",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-invoker": "^4.0",
"phpunit/php-text-template": "^3.0",
"phpunit/php-timer": "^6.0",
"sebastian/cli-parser": "^2.0",
"sebastian/code-unit": "^2.0",
"sebastian/comparator": "^5.0",
"sebastian/diff": "^5.0",
"sebastian/environment": "^6.0",
"sebastian/exporter": "^5.1",
"sebastian/global-state": "^6.0.1",
"sebastian/object-enumerator": "^5.0",
"sebastian/recursion-context": "^5.0",
"sebastian/type": "^4.0",
"sebastian/version": "^4.0"
"phpunit/php-code-coverage": "^10.1.16",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-invoker": "^4.0.0",
"phpunit/php-text-template": "^3.0.1",
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.3",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.2",
"sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.0",
"sebastian/type": "^4.0.0",
"sebastian/version": "^4.0.1"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files"
@ -1950,7 +1947,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.24"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
},
"funding": [
{
@ -1966,7 +1963,7 @@
"type": "tidelift"
}
],
"time": "2024-06-20T13:09:54+00:00"
"time": "2024-10-28T13:06:21+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2138,16 +2135,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.1",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "2db5010a484d53ebf536087a70b4a5423c102372"
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372",
"reference": "2db5010a484d53ebf536087a70b4a5423c102372",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"shasum": ""
},
"require": {
@ -2158,7 +2155,7 @@
"sebastian/exporter": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.3"
"phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@ -2203,7 +2200,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
},
"funding": [
{
@ -2211,7 +2208,7 @@
"type": "github"
}
],
"time": "2023-08-14T13:18:12+00:00"
"time": "2024-10-18T14:56:07+00:00"
},
{
"name": "sebastian/complexity",

View File

@ -48,8 +48,6 @@ parameters:
- tests/phpstan/stubs/leveldb.stub
- tests/phpstan/stubs/pmmpthread.stub
reportUnmatchedIgnoredErrors: false #no other way to silence platform-specific non-warnings
staticReflectionClassNamePatterns:
- "#^COM$#"
typeAliases:
#variadics don't work for this - mixed probably shouldn't work either, but for now it does
#what we actually need is something that accepts an infinite number of parameters, but in the absence of that,

View File

@ -332,7 +332,7 @@ class MemoryManager{
continue;
}
$methodStatics = [];
foreach($method->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($methodStatics) > 0){
@ -360,7 +360,7 @@ class MemoryManager{
'_SESSION' => true
];
foreach($GLOBALS as $varName => $value){
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
if(isset($ignoredGlobals[$varName])){
continue;
}
@ -376,7 +376,7 @@ class MemoryManager{
$reflect = new \ReflectionFunction($function);
$vars = [];
foreach($reflect->getStaticVariables() as $varName => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
if(count($vars) > 0){
@ -416,7 +416,7 @@ class MemoryManager{
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
foreach($reflect->getStaticVariables() as $name => $variable){
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}else{
@ -499,7 +499,7 @@ class MemoryManager{
}
$data = [];
$numeric = 0;
foreach($from as $key => $value){
foreach(Utils::promoteKeys($from) as $key => $value){
$data[$numeric] = [
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),

View File

@ -89,6 +89,8 @@ use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\TimingsCollectionTask;
use pocketmine\scheduler\TimingsControlTask;
use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger;
@ -736,12 +738,15 @@ class Server{
/**
* @return string[][]
* @phpstan-return array<string, list<string>>
*/
public function getCommandAliases() : array{
$section = $this->configGroup->getProperty(Yml::ALIASES);
$result = [];
if(is_array($section)){
foreach($section as $key => $value){
foreach(Utils::promoteKeys($section) as $key => $value){
//TODO: more validation needed here
//key might not be a string, value might not be list<string>
$commands = [];
if(is_array($value)){
$commands = $value;
@ -749,7 +754,7 @@ class Server{
$commands[] = (string) $value;
}
$result[$key] = $commands;
$result[(string) $key] = $commands;
}
}
@ -891,7 +896,36 @@ class Server{
$poolSize = max(1, (int) $poolSize);
}
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
$this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$this->asyncPool->addWorkerStartHook(function(int $i) : void{
if(TimingsHandler::isEnabled()){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i);
}
});
TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
}
});
TimingsHandler::getReloadCallbacks()->add(function() : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
}
});
TimingsHandler::getCollectCallbacks()->add(function() : array{
$promises = [];
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$resolver = new PromiseResolver();
$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);
$promises[] = $resolver->getPromise();
}
return $promises;
});
$netCompressionThreshold = -1;
if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@ -965,9 +999,6 @@ class Server{
)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
DefaultPermissions::registerCorePermissions();
$this->commandMap = new SimpleCommandMap($this);
@ -1094,7 +1125,11 @@ class Server{
$anyWorldFailedToLoad = false;
foreach((array) $this->configGroup->getProperty(Yml::WORLDS, []) as $name => $options){
foreach(Utils::promoteKeys((array) $this->configGroup->getProperty(Yml::WORLDS, [])) as $name => $options){
if(!is_string($name)){
//TODO: this probably should be an error
continue;
}
if($options === null){
$options = [];
}elseif(!is_array($options)){

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.21.1";
public const BASE_VERSION = "5.23.0";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";

View File

@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\AnvilFallSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
use function round;
class Anvil extends Transparent implements Fallable{
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
}
public function onHitGround(FallingBlock $blockEntity) : bool{
if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if($this->damage !== self::VERY_DAMAGED){
$this->damage = $this->damage + 1;
}else{

View File

@ -362,6 +362,8 @@ class Block{
*
* A replacement block may be returned. This is useful if the block type changed due to reading of world data (e.g.
* data from a block entity).
*
* @phpstan-impure
*/
public function readStateFromWorld() : Block{
return $this;

View File

@ -52,6 +52,9 @@ class CakeWithCandle extends BaseCake{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($this->lit && $face !== Facing::UP){
return true;
}
if($this->onInteractCandle($item, $face, $clickVector, $player, $returnedItems)){
return true;
}

View File

@ -69,6 +69,10 @@ class Campfire extends Transparent{
private const UPDATE_INTERVAL_TICKS = 10;
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
protected CampfireInventory $inventory;
/**
@ -129,6 +133,10 @@ class Campfire extends Transparent{
return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
}
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
public function getInventory() : CampfireInventory{
return $this->inventory;
}

View File

@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function intdiv;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
@ -148,7 +148,7 @@ class Farmland extends Transparent{
}
public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){
if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
$ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call();
if(!$ev->isCancelled()){

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\SupportType;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
/**
* "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any
@ -40,6 +41,11 @@ abstract class Flowable extends Transparent{
return false;
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return (!$this->canBeFlowedInto() || !$blockReplace instanceof Liquid) &&
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
/**
* @return AxisAlignedBB[]
*/

View File

@ -24,60 +24,20 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
use function array_key_first;
use function count;
use function shuffle;
class GlowLichen extends Transparent{
/** @var int[] */
protected array $faces = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->facingFlags($this->faces);
}
/** @return int[] */
public function getFaces() : array{ return $this->faces; }
public function hasFace(int $face) : bool{
return isset($this->faces[$face]);
}
/**
* @param int[] $faces
* @return $this
*/
public function setFaces(array $faces) : self{
$uniqueFaces = [];
foreach($faces as $face){
Facing::validate($face);
$uniqueFaces[$face] = $face;
}
$this->faces = $uniqueFaces;
return $this;
}
/** @return $this */
public function setFace(int $face, bool $value) : self{
Facing::validate($face);
if($value){
$this->faces[$face] = $face;
}else{
unset($this->faces[$face]);
}
return $this;
}
use MultiAnySupportTrait;
public function getLightLevel() : int{
return 7;
@ -102,39 +62,11 @@ class GlowLichen extends Transparent{
return true;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
$availableFaces = $this->getAvailableFaces();
if(count($availableFaces) === 0){
return false;
}
$opposite = Facing::opposite($face);
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
$this->faces[$placedFace] = $placedFace;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$changed = false;
foreach($this->faces as $face){
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
unset($this->faces[$face]);
$changed = true;
}
}
if($changed){
$world = $this->position->getWorld();
if(count($this->faces) === 0){
$world->useBreakOn($this->position);
}else{
$world->setBlock($this->position, $this);
}
}
/**
* @return int[]
*/
protected function getInitialPlaceFaces(Block $blockReplace) : array{
return $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
}
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
@ -261,17 +193,4 @@ class GlowLichen extends Transparent{
public function getFlammability() : int{
return 100;
}
/**
* @return array<int, int> $faces
*/
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
$faces[$face] = $face;
}
}
return $faces;
}
}

View File

@ -31,13 +31,13 @@ use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ItemFrameAddItemSound;
use pocketmine\world\sound\ItemFrameRemoveItemSound;
use pocketmine\world\sound\ItemFrameRotateItemSound;
use function is_infinite;
use function is_nan;
use function lcg_value;
class ItemFrame extends Flowable{
use AnyFacingTrait;
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
return false;
}
$world = $this->position->getWorld();
if(lcg_value() <= $this->itemDropChance){
if(Utils::getRandomFloat() <= $this->itemDropChance){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound());
}
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
$drops[] = clone $this->framedItem;
}

View File

@ -33,9 +33,9 @@ use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
use pocketmine\world\sound\FizzSound;
use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{
public const MAX_DECAY = 7;
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
}
return true;
}

View File

@ -39,7 +39,7 @@ class Magma extends Opaque{
}
public function onEntityInside(Entity $entity) : bool{
if($entity instanceof Living && !$entity->isSneaking()){
if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev);
}

View File

@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
/**
* @return Block[]
* @phpstan-return array<int, Block>
*/
public function getAllKnownStates() : array{
return $this->fullList;

View File

@ -36,7 +36,9 @@ use pocketmine\world\Position;
class Sugarcane extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
use StaticSupportTrait {
onNearbyBlockChange as onSupportBlockChange;
}
public const MAX_AGE = 15;
@ -97,7 +99,13 @@ class Sugarcane extends Flowable{
}
public function onRandomTick() : void{
if(!$this->getSide(Facing::DOWN)->hasSameTypeId($this)){
$down = $this->getSide(Facing::DOWN);
if(!$down->hasSameTypeId($this)){
if(!$this->hasNearbyWater($down)){
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
return;
}
if($this->age === self::MAX_AGE){
$this->grow($this->position);
}else{
@ -123,4 +131,23 @@ class Sugarcane extends Flowable{
return false;
}
private function hasNearbyWater(Block $down) : bool{
foreach($down->getHorizontalSides() as $sideBlock){
$blockId = $sideBlock->getTypeId();
if($blockId === BlockTypeIds::WATER || $blockId === BlockTypeIds::FROSTED_ICE){
return true;
}
}
return false;
}
public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN);
if(!$down->hasSameTypeId($this) && !$this->hasNearbyWater($down)){
$this->position->getWorld()->useBreakOn($this->position, createParticles: true);
}else{
$this->onSupportBlockChange();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,263 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\BlockIdentifier as BID;
use pocketmine\block\BlockTypeIds as Ids;
use pocketmine\block\tile\Sign as TileSign;
use pocketmine\block\utils\LeavesType;
use pocketmine\block\utils\SaplingType;
use pocketmine\block\utils\WoodType;
use pocketmine\item\VanillaItems;
/**
* All wood-like blocks have different IDs for different wood types.
*
* We can't make these dynamic, because some types of wood have different type properties (e.g. crimson and warped planks
* are not flammable, but all other planks are).
*
* In the future, it's entirely within the realm of reason that the other types of wood may differ in other ways, such
* as flammability, hardness, required tool tier, etc.
* Therefore, to stay on the safe side of Mojang, wood-like blocks have static types. This does unfortunately generate
* a lot of ugly code.
*
* @internal
*/
final class WoodLikeBlockIdHelper{
public static function getPlanksIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_PLANKS,
WoodType::SPRUCE => Ids::SPRUCE_PLANKS,
WoodType::BIRCH => Ids::BIRCH_PLANKS,
WoodType::JUNGLE => Ids::JUNGLE_PLANKS,
WoodType::ACACIA => Ids::ACACIA_PLANKS,
WoodType::DARK_OAK => Ids::DARK_OAK_PLANKS,
WoodType::MANGROVE => Ids::MANGROVE_PLANKS,
WoodType::CRIMSON => Ids::CRIMSON_PLANKS,
WoodType::WARPED => Ids::WARPED_PLANKS,
WoodType::CHERRY => Ids::CHERRY_PLANKS,
});
}
public static function getFenceIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_FENCE,
WoodType::SPRUCE => Ids::SPRUCE_FENCE,
WoodType::BIRCH => Ids::BIRCH_FENCE,
WoodType::JUNGLE => Ids::JUNGLE_FENCE,
WoodType::ACACIA => Ids::ACACIA_FENCE,
WoodType::DARK_OAK => Ids::DARK_OAK_FENCE,
WoodType::MANGROVE => Ids::MANGROVE_FENCE,
WoodType::CRIMSON => Ids::CRIMSON_FENCE,
WoodType::WARPED => Ids::WARPED_FENCE,
WoodType::CHERRY => Ids::CHERRY_FENCE,
});
}
public static function getSlabIdentifier(WoodType $type) : BID{
return new BID(match($type){
WoodType::OAK => Ids::OAK_SLAB,
WoodType::SPRUCE => Ids::SPRUCE_SLAB,
WoodType::BIRCH => Ids::BIRCH_SLAB,
WoodType::JUNGLE => Ids::JUNGLE_SLAB,
WoodType::ACACIA => Ids::ACACIA_SLAB,
WoodType::DARK_OAK => Ids::DARK_OAK_SLAB,
WoodType::MANGROVE => Ids::MANGROVE_SLAB,
WoodType::CRIMSON => Ids::CRIMSON_SLAB,
WoodType::WARPED => Ids::WARPED_SLAB,
WoodType::CHERRY => Ids::CHERRY_SLAB,
});
}
public static function getLogIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_LOG,
WoodType::SPRUCE => Ids::SPRUCE_LOG,
WoodType::BIRCH => Ids::BIRCH_LOG,
WoodType::JUNGLE => Ids::JUNGLE_LOG,
WoodType::ACACIA => Ids::ACACIA_LOG,
WoodType::DARK_OAK => Ids::DARK_OAK_LOG,
WoodType::MANGROVE => Ids::MANGROVE_LOG,
WoodType::CRIMSON => Ids::CRIMSON_STEM,
WoodType::WARPED => Ids::WARPED_STEM,
WoodType::CHERRY => Ids::CHERRY_LOG,
});
}
public static function getAllSidedLogIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_WOOD,
WoodType::SPRUCE => Ids::SPRUCE_WOOD,
WoodType::BIRCH => Ids::BIRCH_WOOD,
WoodType::JUNGLE => Ids::JUNGLE_WOOD,
WoodType::ACACIA => Ids::ACACIA_WOOD,
WoodType::DARK_OAK => Ids::DARK_OAK_WOOD,
WoodType::MANGROVE => Ids::MANGROVE_WOOD,
WoodType::CRIMSON => Ids::CRIMSON_HYPHAE,
WoodType::WARPED => Ids::WARPED_HYPHAE,
WoodType::CHERRY => Ids::CHERRY_WOOD,
});
}
public static function getLeavesIdentifier(LeavesType $leavesType) : BID{
return new BID(match($leavesType){
LeavesType::OAK => Ids::OAK_LEAVES,
LeavesType::SPRUCE => Ids::SPRUCE_LEAVES,
LeavesType::BIRCH => Ids::BIRCH_LEAVES,
LeavesType::JUNGLE => Ids::JUNGLE_LEAVES,
LeavesType::ACACIA => Ids::ACACIA_LEAVES,
LeavesType::DARK_OAK => Ids::DARK_OAK_LEAVES,
LeavesType::MANGROVE => Ids::MANGROVE_LEAVES,
LeavesType::AZALEA => Ids::AZALEA_LEAVES,
LeavesType::FLOWERING_AZALEA => Ids::FLOWERING_AZALEA_LEAVES,
LeavesType::CHERRY => Ids::CHERRY_LEAVES,
});
}
public static function getSaplingIdentifier(SaplingType $treeType) : BID{
return new BID(match($treeType){
SaplingType::OAK => Ids::OAK_SAPLING,
SaplingType::SPRUCE => Ids::SPRUCE_SAPLING,
SaplingType::BIRCH => Ids::BIRCH_SAPLING,
SaplingType::JUNGLE => Ids::JUNGLE_SAPLING,
SaplingType::ACACIA => Ids::ACACIA_SAPLING,
SaplingType::DARK_OAK => Ids::DARK_OAK_SAPLING,
});
}
/**
* @return BID[]|\Closure[]
* @phpstan-return array{BID, BID, \Closure() : \pocketmine\item\Item}
*/
public static function getSignInfo(WoodType $treeType) : array{
$make = fn(int $floorId, int $wallId, \Closure $getItem) => [
new BID($floorId, TileSign::class),
new BID($wallId, TileSign::class),
$getItem
];
return match($treeType){
WoodType::OAK => $make(Ids::OAK_SIGN, Ids::OAK_WALL_SIGN, fn() => VanillaItems::OAK_SIGN()),
WoodType::SPRUCE => $make(Ids::SPRUCE_SIGN, Ids::SPRUCE_WALL_SIGN, fn() => VanillaItems::SPRUCE_SIGN()),
WoodType::BIRCH => $make(Ids::BIRCH_SIGN, Ids::BIRCH_WALL_SIGN, fn() => VanillaItems::BIRCH_SIGN()),
WoodType::JUNGLE => $make(Ids::JUNGLE_SIGN, Ids::JUNGLE_WALL_SIGN, fn() => VanillaItems::JUNGLE_SIGN()),
WoodType::ACACIA => $make(Ids::ACACIA_SIGN, Ids::ACACIA_WALL_SIGN, fn() => VanillaItems::ACACIA_SIGN()),
WoodType::DARK_OAK => $make(Ids::DARK_OAK_SIGN, Ids::DARK_OAK_WALL_SIGN, fn() => VanillaItems::DARK_OAK_SIGN()),
WoodType::MANGROVE => $make(Ids::MANGROVE_SIGN, Ids::MANGROVE_WALL_SIGN, fn() => VanillaItems::MANGROVE_SIGN()),
WoodType::CRIMSON => $make(Ids::CRIMSON_SIGN, Ids::CRIMSON_WALL_SIGN, fn() => VanillaItems::CRIMSON_SIGN()),
WoodType::WARPED => $make(Ids::WARPED_SIGN, Ids::WARPED_WALL_SIGN, fn() => VanillaItems::WARPED_SIGN()),
WoodType::CHERRY => $make(Ids::CHERRY_SIGN, Ids::CHERRY_WALL_SIGN, fn() => VanillaItems::CHERRY_SIGN()),
};
}
public static function getTrapdoorIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_TRAPDOOR,
WoodType::SPRUCE => Ids::SPRUCE_TRAPDOOR,
WoodType::BIRCH => Ids::BIRCH_TRAPDOOR,
WoodType::JUNGLE => Ids::JUNGLE_TRAPDOOR,
WoodType::ACACIA => Ids::ACACIA_TRAPDOOR,
WoodType::DARK_OAK => Ids::DARK_OAK_TRAPDOOR,
WoodType::MANGROVE => Ids::MANGROVE_TRAPDOOR,
WoodType::CRIMSON => Ids::CRIMSON_TRAPDOOR,
WoodType::WARPED => Ids::WARPED_TRAPDOOR,
WoodType::CHERRY => Ids::CHERRY_TRAPDOOR,
});
}
public static function getButtonIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_BUTTON,
WoodType::SPRUCE => Ids::SPRUCE_BUTTON,
WoodType::BIRCH => Ids::BIRCH_BUTTON,
WoodType::JUNGLE => Ids::JUNGLE_BUTTON,
WoodType::ACACIA => Ids::ACACIA_BUTTON,
WoodType::DARK_OAK => Ids::DARK_OAK_BUTTON,
WoodType::MANGROVE => Ids::MANGROVE_BUTTON,
WoodType::CRIMSON => Ids::CRIMSON_BUTTON,
WoodType::WARPED => Ids::WARPED_BUTTON,
WoodType::CHERRY => Ids::CHERRY_BUTTON,
});
}
public static function getPressurePlateIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_PRESSURE_PLATE,
WoodType::SPRUCE => Ids::SPRUCE_PRESSURE_PLATE,
WoodType::BIRCH => Ids::BIRCH_PRESSURE_PLATE,
WoodType::JUNGLE => Ids::JUNGLE_PRESSURE_PLATE,
WoodType::ACACIA => Ids::ACACIA_PRESSURE_PLATE,
WoodType::DARK_OAK => Ids::DARK_OAK_PRESSURE_PLATE,
WoodType::MANGROVE => Ids::MANGROVE_PRESSURE_PLATE,
WoodType::CRIMSON => Ids::CRIMSON_PRESSURE_PLATE,
WoodType::WARPED => Ids::WARPED_PRESSURE_PLATE,
WoodType::CHERRY => Ids::CHERRY_PRESSURE_PLATE,
});
}
public static function getDoorIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_DOOR,
WoodType::SPRUCE => Ids::SPRUCE_DOOR,
WoodType::BIRCH => Ids::BIRCH_DOOR,
WoodType::JUNGLE => Ids::JUNGLE_DOOR,
WoodType::ACACIA => Ids::ACACIA_DOOR,
WoodType::DARK_OAK => Ids::DARK_OAK_DOOR,
WoodType::MANGROVE => Ids::MANGROVE_DOOR,
WoodType::CRIMSON => Ids::CRIMSON_DOOR,
WoodType::WARPED => Ids::WARPED_DOOR,
WoodType::CHERRY => Ids::CHERRY_DOOR,
});
}
public static function getFenceGateIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_FENCE_GATE,
WoodType::SPRUCE => Ids::SPRUCE_FENCE_GATE,
WoodType::BIRCH => Ids::BIRCH_FENCE_GATE,
WoodType::JUNGLE => Ids::JUNGLE_FENCE_GATE,
WoodType::ACACIA => Ids::ACACIA_FENCE_GATE,
WoodType::DARK_OAK => Ids::DARK_OAK_FENCE_GATE,
WoodType::MANGROVE => Ids::MANGROVE_FENCE_GATE,
WoodType::CRIMSON => Ids::CRIMSON_FENCE_GATE,
WoodType::WARPED => Ids::WARPED_FENCE_GATE,
WoodType::CHERRY => Ids::CHERRY_FENCE_GATE,
});
}
public static function getStairsIdentifier(WoodType $treeType) : BID{
return new BID(match($treeType){
WoodType::OAK => Ids::OAK_STAIRS,
WoodType::SPRUCE => Ids::SPRUCE_STAIRS,
WoodType::BIRCH => Ids::BIRCH_STAIRS,
WoodType::JUNGLE => Ids::JUNGLE_STAIRS,
WoodType::ACACIA => Ids::ACACIA_STAIRS,
WoodType::DARK_OAK => Ids::DARK_OAK_STAIRS,
WoodType::MANGROVE => Ids::MANGROVE_STAIRS,
WoodType::CRIMSON => Ids::CRIMSON_STAIRS,
WoodType::WARPED => Ids::WARPED_STAIRS,
WoodType::CHERRY => Ids::CHERRY_STAIRS,
});
}
}

View File

@ -39,7 +39,10 @@ class EnchantInventory extends SimpleInventory implements BlockInventory, Tempor
public const SLOT_INPUT = 0;
public const SLOT_LAPIS = 1;
/** @var EnchantingOption[] $options */
/**
* @var EnchantingOption[] $options
* @phpstan-var list<EnchantingOption>
*/
private array $options = [];
public function __construct(Position $holder){

View File

@ -0,0 +1,72 @@
<?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\data\runtime\RuntimeDataDescriber;
use pocketmine\math\Facing;
/**
* Used by blocks that can have multiple target faces in the area of one solid block, such as covering three sides of a corner.
*/
trait MultiAnyFacingTrait{
/** @var int[] */
protected array $faces = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->facingFlags($this->faces);
}
/** @return int[] */
public function getFaces() : array{ return $this->faces; }
public function hasFace(int $face) : bool{
return isset($this->faces[$face]);
}
/**
* @param int[] $faces
* @return $this
*/
public function setFaces(array $faces) : self{
$uniqueFaces = [];
foreach($faces as $face){
Facing::validate($face);
$uniqueFaces[$face] = $face;
}
$this->faces = $uniqueFaces;
return $this;
}
/** @return $this */
public function setFace(int $face, bool $value) : self{
Facing::validate($face);
if($value){
$this->faces[$face] = $face;
}else{
unset($this->faces[$face]);
}
return $this;
}
}

View File

@ -0,0 +1,96 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\Block;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function array_key_first;
use function count;
/**
* Used by blocks that have multiple support requirements in the area of one solid block, such as covering three sides of a corner.
* Prevents placement if support isn't available and automatically destroys a block side if it's support is removed.
*/
trait MultiAnySupportTrait{
use MultiAnyFacingTrait;
/**
* Returns a list of faces that block should already have when placed.
*
* @return int[]
*/
abstract protected function getInitialPlaceFaces(Block $blockReplace) : array;
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->faces = $this->getInitialPlaceFaces($blockReplace);
$availableFaces = $this->getAvailableFaces();
if(count($availableFaces) === 0){
return false;
}
$opposite = Facing::opposite($face);
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
$this->faces[$placedFace] = $placedFace;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$changed = false;
foreach($this->faces as $face){
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
unset($this->faces[$face]);
$changed = true;
}
}
if($changed){
$world = $this->position->getWorld();
if(count($this->faces) === 0){
$world->useBreakOn($this->position);
}else{
$world->setBlock($this->position, $this);
}
}
}
/**
* @return array<int, int> $faces
*/
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
$faces[$face] = $face;
}
}
return $faces;
}
}

View File

@ -36,13 +36,17 @@ use function str_contains;
class SignText{
public const LINE_COUNT = 4;
/** @var string[] */
/**
* @var string[]
* @phpstan-var array{0: string, 1: string, 2: string, 3: string}
*/
private array $lines;
private Color $baseColor;
private bool $glowing;
/**
* @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order
* @phpstan-param array{0?: string, 1?: string, 2?: string, 3?: string}|null $lines
*
* @throws \InvalidArgumentException if the array size is greater than 4
* @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array
@ -82,6 +86,7 @@ class SignText{
* Returns an array of lines currently on the sign.
*
* @return string[]
* @phpstan-return array{0: string, 1: string, 2: string, 3: string}
*/
public function getLines() : array{
return $this->lines;

View File

@ -0,0 +1,60 @@
<?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\command;
use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
/**
* @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list<string> $args) : mixed
*/
final class ClosureCommand extends Command{
/** @phpstan-var Execute */
private \Closure $execute;
/**
* @param string[] $permissions
* @phpstan-param Execute $execute
*/
public function __construct(
string $name,
\Closure $execute,
array $permissions,
Translatable|string $description = "",
Translatable|string|null $usageMessage = null,
array $aliases = []
){
Utils::validateCallableSignature(
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
$execute,
);
$this->execute = $execute;
parent::__construct($name, $description, $usageMessage, $aliases);
$this->setPermissions($permissions);
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
return ($this->execute)($sender, $this, $commandLabel, $args);
}
}

View File

@ -44,10 +44,16 @@ abstract class Command{
private string $nextLabel;
private string $label;
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $aliases = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $activeAliases = [];
private ?CommandMap $commandMap = null;
@ -62,6 +68,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
$this->name = $name;
@ -182,6 +189,7 @@ abstract class Command{
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getAliases() : array{
return $this->activeAliases;
@ -201,6 +209,7 @@ abstract class Command{
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function setAliases(array $aliases) : void{
$this->aliases = $aliases;

View File

@ -64,12 +64,14 @@ use pocketmine\command\defaults\TransferServerCommand;
use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\defaults\XpCommand;
use pocketmine\command\utils\CommandStringHelper;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use function array_shift;
use function count;
use function implode;
@ -80,7 +82,10 @@ use function trim;
class SimpleCommandMap implements CommandMap{
/** @var Command[] */
/**
* @var Command[]
* @phpstan-var array<string, Command>
*/
protected array $knownCommands = [];
public function __construct(private Server $server){
@ -128,7 +133,8 @@ class SimpleCommandMap implements CommandMap{
new TitleCommand(),
new TransferServerCommand(),
new VersionCommand(),
new WhitelistCommand()
new WhitelistCommand(),
new XpCommand(),
]);
}
@ -169,7 +175,7 @@ class SimpleCommandMap implements CommandMap{
}
public function unregister(Command $command) : bool{
foreach($this->knownCommands as $lbl => $cmd){
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
if($cmd === $command){
unset($this->knownCommands[$lbl]);
}
@ -237,6 +243,7 @@ class SimpleCommandMap implements CommandMap{
/**
* @return Command[]
* @phpstan-return array<string, Command>
*/
public function getCommands() : array{
return $this->knownCommands;
@ -245,7 +252,7 @@ class SimpleCommandMap implements CommandMap{
public function registerServerAliases() : void{
$values = $this->server->getCommandAliases();
foreach($values as $alias => $commandStrings){
foreach(Utils::stringifyKeys($values) as $alias => $commandStrings){
if(str_contains($alias, ":")){
$this->server->getLogger()->warning($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_alias_illegal($alias)));
continue;

View File

@ -26,28 +26,28 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\scheduler\BulkCurlTaskOperation;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\InternetException;
use pocketmine\utils\InternetRequestResult;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path;
use function count;
use function fclose;
use function file_exists;
use function fopen;
use function fseek;
use function fwrite;
use function http_build_query;
use function implode;
use function is_array;
use function json_decode;
use function mkdir;
use function stream_get_contents;
use function strtolower;
use const CURLOPT_AUTOREFERER;
use const CURLOPT_FOLLOWLOCATION;
@ -101,82 +101,91 @@ class TimingsCommand extends VanillaCommand{
TimingsHandler::reload();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
}elseif($mode === "merged" || $mode === "report" || $paste){
$timings = "";
if($paste){
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail");
}else{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}
$fileTimings = fopen($timings, "a+b");
}
$lines = TimingsHandler::printTimings();
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}
if($paste){
fseek($fileTimings, 0);
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings)
];
fclose($fileTimings);
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}else{
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
$timingsPromise = TimingsHandler::requestPrintTimings();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
$timingsPromise->onCompletion(
fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender),
fn() => throw new AssumptionFailedError("This promise is not expected to be rejected")
);
}else{
throw new InvalidCommandSyntaxException();
}
return true;
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function createReportFile(array $lines, CommandSender $sender) : void{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}
$fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function uploadReport(array $lines, CommandSender $sender) : void{
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => implode("\n", $lines)
];
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\entity\Attribute;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\TextFormat;
use function abs;
use function count;
use function str_ends_with;
use function substr;
class XpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"xp",
KnownTranslationFactory::pocketmine_command_xp_description(),
KnownTranslationFactory::pocketmine_command_xp_usage()
);
$this->setPermissions([
DefaultPermissionNames::COMMAND_XP_SELF,
DefaultPermissionNames::COMMAND_XP_OTHER
]);
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
if(count($args) < 1){
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
if($player === null){
return true;
}
$xpManager = $player->getXpManager();
if(str_ends_with($args[0], "L")){
$xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError();
$maxXpLevel = (int) $xpLevelAttr->getMaxValue();
$currentXpLevel = $xpManager->getXpLevel();
$xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel);
if($xpLevels >= 0){
$xpManager->addXpLevels($xpLevels, false);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName()));
}else{
$xpLevels = abs($xpLevels);
$xpManager->subtractXpLevels($xpLevels);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName()));
}
}else{
$xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX);
if($xp < 0){
$sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED));
}else{
$xpManager->addXp($xp, false);
$sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName()));
}
}
return true;
}
}

View File

@ -29,23 +29,21 @@ use pocketmine\utils\Process;
use function cli_set_process_title;
use function count;
use function dirname;
use function feof;
use function fwrite;
use function stream_socket_client;
use function is_numeric;
use const PHP_EOL;
use const STDOUT;
if(count($argv) !== 2 || !is_numeric($argv[1])){
echo "Usage: " . $argv[0] . " <command token seed>" . PHP_EOL;
exit(1);
}
$commandTokenSeed = (int) $argv[1];
require dirname(__DIR__, 2) . '/vendor/autoload.php';
if(count($argv) !== 2){
die("Please provide a server to connect to");
}
@cli_set_process_title('PocketMine-MP Console Reader');
$errCode = null;
$errMessage = null;
$socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0);
if($socket === false){
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
}
/** @phpstan-var ThreadSafeArray<int, string> $channel */
$channel = new ThreadSafeArray();
@ -75,15 +73,15 @@ $thread = new class($channel) extends NativeThread{
};
$thread->start(NativeThread::INHERIT_NONE);
while(!feof($socket)){
while(true){
$line = $channel->synchronized(function() use ($channel) : ?string{
if(count($channel) === 0){
$channel->wait(1_000_000);
}
$line = $channel->shift();
return $line;
return $channel->shift();
});
if(@fwrite($socket, ($line ?? "") . "\n") === false){
$message = $line !== null ? ConsoleReaderChildProcessUtils::createMessage($line, $commandTokenSeed) : "";
if(@fwrite(STDOUT, $message . "\n") === false){
//Always send even if there's no line, to check if the parent is alive
//If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return
//false even though the connection is actually broken. However, fwrite() will fail.

View File

@ -29,19 +29,16 @@ use Symfony\Component\Filesystem\Path;
use function base64_encode;
use function fgets;
use function fopen;
use function mt_rand;
use function preg_replace;
use function proc_close;
use function proc_open;
use function proc_terminate;
use function rtrim;
use function sprintf;
use function stream_select;
use function stream_socket_accept;
use function stream_socket_get_name;
use function stream_socket_server;
use function stream_socket_shutdown;
use function trim;
use const PHP_BINARY;
use const STREAM_SHUT_RDWR;
/**
* This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes
@ -58,44 +55,44 @@ use const STREAM_SHUT_RDWR;
* communication.
*/
final class ConsoleReaderChildProcessDaemon{
public const TOKEN_DELIMITER = ":";
public const TOKEN_HASH_ALGO = "xxh3";
private \PrefixedLogger $logger;
/** @var resource */
private $subprocess;
/** @var resource */
private $socket;
private int $commandTokenSeed;
public function __construct(
\Logger $logger
){
$this->logger = new \PrefixedLogger($logger, "Console Reader Daemon");
$this->commandTokenSeed = mt_rand();
$this->prepareSubprocess();
}
private function prepareSubprocess() : void{
$server = stream_socket_server("tcp://127.0.0.1:0");
if($server === false){
throw new \RuntimeException("Failed to open console reader socket server");
}
$address = Utils::assumeNotFalse(stream_socket_get_name($server, false), "stream_socket_get_name() shouldn't return false here");
//Windows sucks, and likes to corrupt UTF-8 file paths when they travel to the subprocess, so we base64 encode
//the path to avoid the problem. This is an abysmally shitty hack, but here we are :(
$sub = Utils::assumeNotFalse(proc_open(
[PHP_BINARY, '-dopcache.enable_cli=0', '-r', sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))), $address],
[
PHP_BINARY,
'-dopcache.enable_cli=0',
'-r',
sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))),
(string) $this->commandTokenSeed
],
[
1 => ['socket'],
2 => fopen("php://stderr", "w"),
],
$pipes
), "Something has gone horribly wrong");
$client = stream_socket_accept($server, 15);
if($client === false){
throw new AssumptionFailedError("stream_socket_accept() returned false");
}
stream_socket_shutdown($server, STREAM_SHUT_RDWR);
$this->subprocess = $sub;
$this->socket = $client;
$this->socket = $pipes[1];
}
private function shutdownSubprocess() : void{
@ -104,7 +101,6 @@ final class ConsoleReaderChildProcessDaemon{
//the first place).
proc_terminate($this->subprocess);
proc_close($this->subprocess);
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
}
public function readLine() : ?string{
@ -112,13 +108,27 @@ final class ConsoleReaderChildProcessDaemon{
$w = null;
$e = null;
if(stream_select($r, $w, $e, 0, 0) === 1){
$command = fgets($this->socket);
if($command === false){
$line = fgets($this->socket);
if($line === false){
$this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)");
$this->shutdownSubprocess();
$this->prepareSubprocess();
return null;
}
$line = rtrim($line, "\n");
if($line === ""){
//keepalive
return null;
}
$command = ConsoleReaderChildProcessUtils::parseMessage($line, $this->commandTokenSeed);
if($command === null){
//this is not a command - it may be some kind of error output from the subprocess
//write it directly to the console
$this->logger->warning("Unexpected output from child process: $line");
return null;
}
$command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid");
$command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid");

View File

@ -0,0 +1,71 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\console;
use function hash;
use function strlen;
use function strrpos;
use function substr;
final class ConsoleReaderChildProcessUtils{
public const TOKEN_DELIMITER = ":";
public const TOKEN_HASH_ALGO = "xxh3";
private function __construct(){
}
/**
* Creates an IPC message to transmit a user's input command to the parent process.
*
* Unfortunately we can't currently provide IPC pipes other than stdout/stderr to subprocesses on Windows, so this
* adds a hash of the user input (with a counter as salt) to prevent unintended process output (like error messages)
* from being treated as user input.
*/
public static function createMessage(string $line, int &$counter) : string{
$token = hash(self::TOKEN_HASH_ALGO, $line, options: ['seed' => $counter]);
$counter++;
return $line . self::TOKEN_DELIMITER . $token;
}
/**
* Extracts a command from an IPC message from the console reader subprocess.
* Returns the user's input command, or null if this isn't a user input.
*/
public static function parseMessage(string $message, int &$counter) : ?string{
$delimiterPos = strrpos($message, self::TOKEN_DELIMITER);
if($delimiterPos !== false){
$left = substr($message, 0, $delimiterPos);
$right = substr($message, $delimiterPos + strlen(self::TOKEN_DELIMITER));
$expectedToken = hash(self::TOKEN_HASH_ALGO, $left, options: ['seed' => $counter]);
if($expectedToken === $right){
$counter++;
return $left;
}
}
return null;
}
}

View File

@ -110,14 +110,15 @@ class CraftingManager{
/**
* @param Item[] $items
* @phpstan-param list<Item> $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
/** @var Item[] $result */
$result = [];
foreach($items as $i => $item){
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
@ -134,6 +135,7 @@ class CraftingManager{
/**
* @param Item[] $outputs
* @phpstan-param list<Item> $outputs
*/
private static function hashOutputs(array $outputs) : string{
$outputs = self::pack($outputs);

View File

@ -193,7 +193,7 @@ final class CraftingManagerFromDataHelper{
*/
private static function loadJsonObjectListIntoModel(\JsonMapper $mapper, string $modelClass, array $data) : array{
$result = [];
foreach($data as $i => $item){
foreach(Utils::promoteKeys($data) as $i => $item){
if(!is_object($item)){
throw new SavedDataLoadingException("Invalid entry at index $i: expected object, got " . get_debug_type($item));
}

View File

@ -30,6 +30,7 @@ interface CraftingRecipe{
* Returns a list of items needed to craft this recipe. This MUST NOT include Air items or items with a zero count.
*
* @return RecipeIngredient[]
* @phpstan-return list<RecipeIngredient>
*/
public function getIngredientList() : array;
@ -37,6 +38,7 @@ interface CraftingRecipe{
* Returns a list of results this recipe will produce when the inputs in the given crafting grid are consumed.
*
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array;

View File

@ -32,11 +32,20 @@ use function str_contains;
use function strlen;
class ShapedRecipe implements CraftingRecipe{
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $shape = [];
/** @var RecipeIngredient[] char => RecipeIngredient map */
/**
* @var RecipeIngredient[] char => RecipeIngredient map
* @phpstan-var array<string, RecipeIngredient>
*/
private array $ingredientList = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results = [];
private int $height;
@ -56,6 +65,10 @@ class ShapedRecipe implements CraftingRecipe{
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*
* @phpstan-param list<string> $shape
* @phpstan-param array<string, RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $shape, array $ingredients, array $results){
$this->height = count($shape);
@ -84,7 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
foreach($ingredients as $char => $i){
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 +118,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -112,6 +126,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResultsFor(CraftingGrid $grid) : array{
return $this->getResults();
@ -119,6 +134,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* @return (RecipeIngredient|null)[][]
* @phpstan-return list<list<RecipeIngredient|null>>
*/
public function getIngredientMap() : array{
$ingredients = [];
@ -132,9 +148,6 @@ class ShapedRecipe implements CraftingRecipe{
return $ingredients;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
$ingredients = [];
@ -157,6 +170,7 @@ class ShapedRecipe implements CraftingRecipe{
/**
* Returns an array of strings containing characters representing the recipe's shape.
* @return string[]
* @phpstan-return list<string>
*/
public function getShape() : array{
return $this->shape;

View File

@ -28,15 +28,24 @@ use pocketmine\utils\Utils;
use function count;
class ShapelessRecipe implements CraftingRecipe{
/** @var RecipeIngredient[] */
/**
* @var RecipeIngredient[]
* @phpstan-var list<RecipeIngredient>
*/
private array $ingredients = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
private array $results;
private ShapelessRecipeType $type;
/**
* @param RecipeIngredient[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
* @param Item[] $results List of result items created by this recipe.
*
* @phpstan-param list<RecipeIngredient> $ingredients
* @phpstan-param list<Item> $results
*/
public function __construct(array $ingredients, array $results, ShapelessRecipeType $type){
$this->type = $type;
@ -50,6 +59,7 @@ class ShapelessRecipe implements CraftingRecipe{
/**
* @return Item[]
* @phpstan-return list<Item>
*/
public function getResults() : array{
return Utils::cloneObjectArray($this->results);
@ -63,9 +73,6 @@ class ShapelessRecipe implements CraftingRecipe{
return $this->type;
}
/**
* @return RecipeIngredient[]
*/
public function getIngredientList() : array{
return $this->ingredients;
}

View File

@ -43,7 +43,10 @@ final class CrashDumpData implements \JsonSerializable{
public string $plugin = "";
/** @var string[] */
/**
* @var string[]
* @phpstan-var array<int, string>
*/
public array $code = [];
/** @var string[] */
@ -55,7 +58,10 @@ final class CrashDumpData implements \JsonSerializable{
*/
public array $plugins = [];
/** @var string[] */
/**
* @var string[]
* @phpstan-var list<string>
*/
public array $parameters = [];
public string $serverDotProperties = "";

View File

@ -45,6 +45,7 @@ final class BedrockDataFiles{
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 REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';

View File

@ -122,4 +122,5 @@ final class BiomeIds{
public const DEEP_DARK = 190;
public const MANGROVE_SWAMP = 191;
public const CHERRY_GROVE = 192;
public const PALE_GARDEN = 193;
}

View File

@ -66,5 +66,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data\bedrock;
use pocketmine\item\GoatHornType;
use pocketmine\utils\SingletonTrait;
final class GoatHornTypeIdMap{
use SingletonTrait;
/** @phpstan-use IntSaveIdMapTrait<GoatHornType> */
use IntSaveIdMapTrait;
private function __construct(){
foreach(GoatHornType::cases() as $case){
$this->register(match($case){
GoatHornType::PONDER => GoatHornTypeIds::PONDER,
GoatHornType::SING => GoatHornTypeIds::SING,
GoatHornType::SEEK => GoatHornTypeIds::SEEK,
GoatHornType::FEEL => GoatHornTypeIds::FEEL,
GoatHornType::ADMIRE => GoatHornTypeIds::ADMIRE,
GoatHornType::CALL => GoatHornTypeIds::CALL,
GoatHornType::YEARN => GoatHornTypeIds::YEARN,
GoatHornType::DREAM => GoatHornTypeIds::DREAM
}, $case);
}
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data\bedrock;
final class GoatHornTypeIds{
public const PONDER = 0;
public const SING = 1;
public const SEEK = 2;
public const FEEL = 3;
public const ADMIRE = 4;
public const CALL = 5;
public const YEARN = 6;
public const DREAM = 7;
}

View File

@ -48,7 +48,7 @@ final class ItemTagToIdMap{
throw new AssumptionFailedError("Invalid item tag map, expected array");
}
$cleanMap = [];
foreach($map as $tagName => $ids){
foreach(Utils::promoteKeys($map) as $tagName => $ids){
if(!is_string($tagName)){
throw new AssumptionFailedError("Invalid item tag name $tagName, expected string as key");
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use function is_array;
use function is_int;
use function is_string;
@ -43,7 +44,7 @@ abstract class LegacyToStringIdMap{
if(!is_array($stringToLegacyId)){
throw new AssumptionFailedError("Invalid format of ID map");
}
foreach($stringToLegacyId as $stringId => $legacyId){
foreach(Utils::promoteKeys($stringToLegacyId) as $stringId => $legacyId){
if(!is_string($stringId) || !is_int($legacyId)){
throw new AssumptionFailedError("ID map should have string keys and int values");
}

View File

@ -93,12 +93,17 @@ final class BlockStateNames{
public const MC_VERTICAL_HALF = "minecraft:vertical_half";
public const MOISTURIZED_AMOUNT = "moisturized_amount";
public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits";
public const NATURAL = "natural";
public const OCCUPIED_BIT = "occupied_bit";
public const OMINOUS = "ominous";
public const OPEN_BIT = "open_bit";
public const ORIENTATION = "orientation";
public const OUTPUT_LIT_BIT = "output_lit_bit";
public const OUTPUT_SUBTRACT_BIT = "output_subtract_bit";
public const PALE_MOSS_CARPET_SIDE_EAST = "pale_moss_carpet_side_east";
public const PALE_MOSS_CARPET_SIDE_NORTH = "pale_moss_carpet_side_north";
public const PALE_MOSS_CARPET_SIDE_SOUTH = "pale_moss_carpet_side_south";
public const PALE_MOSS_CARPET_SIDE_WEST = "pale_moss_carpet_side_west";
public const PERSISTENT_BIT = "persistent_bit";
public const PILLAR_AXIS = "pillar_axis";
public const PORTAL_AXIS = "portal_axis";
@ -116,6 +121,7 @@ final class BlockStateNames{
public const STABILITY_CHECK = "stability_check";
public const STRUCTURE_BLOCK_TYPE = "structure_block_type";
public const SUSPENDED_BIT = "suspended_bit";
public const TIP = "tip";
public const TOGGLE_BIT = "toggle_bit";
public const TORCH_FACING_DIRECTION = "torch_facing_direction";
public const TRIAL_SPAWNER_STATE = "trial_spawner_state";

View File

@ -106,6 +106,22 @@ final class BlockStateStringValues{
public const ORIENTATION_UP_WEST = "up_west";
public const ORIENTATION_WEST_UP = "west_up";
public const PALE_MOSS_CARPET_SIDE_EAST_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_EAST_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_EAST_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_NORTH_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_NORTH_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_NORTH_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_SOUTH_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_SOUTH_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_SOUTH_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_WEST_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_WEST_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_WEST_TALL = "tall";
public const PILLAR_AXIS_X = "x";
public const PILLAR_AXIS_Y = "y";
public const PILLAR_AXIS_Z = "z";

View File

@ -192,6 +192,7 @@ final class BlockTypeNames{
public const CAVE_VINES_HEAD_WITH_BERRIES = "minecraft:cave_vines_head_with_berries";
public const CHAIN = "minecraft:chain";
public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block";
public const CHALKBOARD = "minecraft:chalkboard";
public const CHEMICAL_HEAT = "minecraft:chemical_heat";
public const CHERRY_BUTTON = "minecraft:cherry_button";
public const CHERRY_DOOR = "minecraft:cherry_door";
@ -219,6 +220,7 @@ final class BlockTypeNames{
public const CHISELED_POLISHED_BLACKSTONE = "minecraft:chiseled_polished_blackstone";
public const CHISELED_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block";
public const CHISELED_RED_SANDSTONE = "minecraft:chiseled_red_sandstone";
public const CHISELED_RESIN_BRICKS = "minecraft:chiseled_resin_bricks";
public const CHISELED_SANDSTONE = "minecraft:chiseled_sandstone";
public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks";
public const CHISELED_TUFF = "minecraft:chiseled_tuff";
@ -227,6 +229,7 @@ final class BlockTypeNames{
public const CHORUS_PLANT = "minecraft:chorus_plant";
public const CLAY = "minecraft:clay";
public const CLIENT_REQUEST_PLACEHOLDER_BLOCK = "minecraft:client_request_placeholder_block";
public const CLOSED_EYEBLOSSOM = "minecraft:closed_eyeblossom";
public const COAL_BLOCK = "minecraft:coal_block";
public const COAL_ORE = "minecraft:coal_ore";
public const COARSE_DIRT = "minecraft:coarse_dirt";
@ -262,6 +265,7 @@ final class BlockTypeNames{
public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks";
public const CRAFTER = "minecraft:crafter";
public const CRAFTING_TABLE = "minecraft:crafting_table";
public const CREAKING_HEART = "minecraft:creaking_heart";
public const CREEPER_HEAD = "minecraft:creeper_head";
public const CRIMSON_BUTTON = "minecraft:crimson_button";
public const CRIMSON_DOOR = "minecraft:crimson_door";
@ -831,6 +835,7 @@ final class BlockTypeNames{
public const OBSERVER = "minecraft:observer";
public const OBSIDIAN = "minecraft:obsidian";
public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight";
public const OPEN_EYEBLOSSOM = "minecraft:open_eyeblossom";
public const ORANGE_CANDLE = "minecraft:orange_candle";
public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake";
public const ORANGE_CARPET = "minecraft:orange_carpet";
@ -856,6 +861,26 @@ final class BlockTypeNames{
public const OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:oxidized_double_cut_copper_slab";
public const PACKED_ICE = "minecraft:packed_ice";
public const PACKED_MUD = "minecraft:packed_mud";
public const PALE_HANGING_MOSS = "minecraft:pale_hanging_moss";
public const PALE_MOSS_BLOCK = "minecraft:pale_moss_block";
public const PALE_MOSS_CARPET = "minecraft:pale_moss_carpet";
public const PALE_OAK_BUTTON = "minecraft:pale_oak_button";
public const PALE_OAK_DOOR = "minecraft:pale_oak_door";
public const PALE_OAK_DOUBLE_SLAB = "minecraft:pale_oak_double_slab";
public const PALE_OAK_FENCE = "minecraft:pale_oak_fence";
public const PALE_OAK_FENCE_GATE = "minecraft:pale_oak_fence_gate";
public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign";
public const PALE_OAK_LEAVES = "minecraft:pale_oak_leaves";
public const PALE_OAK_LOG = "minecraft:pale_oak_log";
public const PALE_OAK_PLANKS = "minecraft:pale_oak_planks";
public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate";
public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling";
public const PALE_OAK_SLAB = "minecraft:pale_oak_slab";
public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs";
public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign";
public const PALE_OAK_TRAPDOOR = "minecraft:pale_oak_trapdoor";
public const PALE_OAK_WALL_SIGN = "minecraft:pale_oak_wall_sign";
public const PALE_OAK_WOOD = "minecraft:pale_oak_wood";
public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight";
public const PEONY = "minecraft:peony";
public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab";
@ -994,6 +1019,13 @@ final class BlockTypeNames{
public const REINFORCED_DEEPSLATE = "minecraft:reinforced_deepslate";
public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block";
public const RESERVED6 = "minecraft:reserved6";
public const RESIN_BLOCK = "minecraft:resin_block";
public const RESIN_BRICK_DOUBLE_SLAB = "minecraft:resin_brick_double_slab";
public const RESIN_BRICK_SLAB = "minecraft:resin_brick_slab";
public const RESIN_BRICK_STAIRS = "minecraft:resin_brick_stairs";
public const RESIN_BRICK_WALL = "minecraft:resin_brick_wall";
public const RESIN_BRICKS = "minecraft:resin_bricks";
public const RESIN_CLUMP = "minecraft:resin_clump";
public const RESPAWN_ANCHOR = "minecraft:respawn_anchor";
public const ROSE_BUSH = "minecraft:rose_bush";
public const SAND = "minecraft:sand";
@ -1096,6 +1128,8 @@ final class BlockTypeNames{
public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood";
public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log";
public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood";
public const STRIPPED_PALE_OAK_LOG = "minecraft:stripped_pale_oak_log";
public const STRIPPED_PALE_OAK_WOOD = "minecraft:stripped_pale_oak_wood";
public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log";
public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood";
public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae";

View File

@ -31,6 +31,7 @@ use pocketmine\block\utils\DyeColor;
use pocketmine\block\VanillaBlocks as Blocks;
use pocketmine\data\bedrock\CompoundTypeIds;
use pocketmine\data\bedrock\DyeColorIdMap;
use pocketmine\data\bedrock\GoatHornTypeIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames as Ids;
use pocketmine\data\bedrock\item\SavedItemData as Data;
use pocketmine\data\bedrock\MedicineTypeIdMap;
@ -38,6 +39,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\SuspiciousStewTypeIdMap;
use pocketmine\item\Banner;
use pocketmine\item\Dye;
use pocketmine\item\GoatHorn;
use pocketmine\item\Item;
use pocketmine\item\Medicine;
use pocketmine\item\Potion;
@ -230,6 +232,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::EMERALD, Items::EMERALD());
$this->map1to1Item(Ids::ENCHANTED_BOOK, Items::ENCHANTED_BOOK());
$this->map1to1Item(Ids::ENCHANTED_GOLDEN_APPLE, Items::ENCHANTED_GOLDEN_APPLE());
$this->map1to1Item(Ids::END_CRYSTAL, Items::END_CRYSTAL());
$this->map1to1Item(Ids::ENDER_PEARL, Items::ENDER_PEARL());
$this->map1to1Item(Ids::EXPERIENCE_BOTTLE, Items::EXPERIENCE_BOTTLE());
$this->map1to1Item(Ids::EYE_ARMOR_TRIM_SMITHING_TEMPLATE, Items::EYE_ARMOR_TRIM_SMITHING_TEMPLATE());
@ -263,6 +266,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::HONEY_BOTTLE, Items::HONEY_BOTTLE());
$this->map1to1Item(Ids::HONEYCOMB, Items::HONEYCOMB());
$this->map1to1Item(Ids::HOST_ARMOR_TRIM_SMITHING_TEMPLATE, Items::HOST_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ICE_BOMB, Items::ICE_BOMB());
$this->map1to1Item(Ids::INK_SAC, Items::INK_SAC());
$this->map1to1Item(Ids::IRON_AXE, Items::IRON_AXE());
$this->map1to1Item(Ids::IRON_BOOTS, Items::IRON_BOOTS());
@ -348,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON());
$this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS());
$this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST());
$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
@ -483,6 +488,14 @@ final class ItemSerializerDeserializerRegistrar{
},
fn(Banner $item) => DyeColorIdMap::getInstance()->toInvertedId($item->getColor())
);
$this->map1to1ItemWithMeta(
Ids::GOAT_HORN,
Items::GOAT_HORN(),
function(GoatHorn $item, int $meta) : void{
$item->setHornType(GoatHornTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown goat horn type ID $meta"));
},
fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType())
);
$this->map1to1ItemWithMeta(
Ids::MEDICINE,
Items::MEDICINE(),

View File

@ -75,6 +75,7 @@ final class ItemTypeNames{
public const BLEACH = "minecraft:bleach";
public const BLUE_BUNDLE = "minecraft:blue_bundle";
public const BLUE_DYE = "minecraft:blue_dye";
public const BOARD = "minecraft:board";
public const BOAT = "minecraft:boat";
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template";
@ -154,6 +155,7 @@ final class ItemTypeNames{
public const CORAL_FAN = "minecraft:coral_fan";
public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg";
public const CREAKING_SPAWN_EGG = "minecraft:creaking_spawn_egg";
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
public const CRIMSON_DOOR = "minecraft:crimson_door";
@ -398,6 +400,11 @@ final class ItemTypeNames{
public const ORANGE_DYE = "minecraft:orange_dye";
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
public const PAINTING = "minecraft:painting";
public const PALE_OAK_BOAT = "minecraft:pale_oak_boat";
public const PALE_OAK_CHEST_BOAT = "minecraft:pale_oak_chest_boat";
public const PALE_OAK_DOOR = "minecraft:pale_oak_door";
public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign";
public const PALE_OAK_SIGN = "minecraft:pale_oak_sign";
public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg";
public const PAPER = "minecraft:paper";
public const PARROT_SPAWN_EGG = "minecraft:parrot_spawn_egg";
@ -448,6 +455,7 @@ final class ItemTypeNames{
public const RED_FLOWER = "minecraft:red_flower";
public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater";
public const RESIN_BRICK = "minecraft:resin_brick";
public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template";
public const ROTTEN_FLESH = "minecraft:rotten_flesh";
public const SADDLE = "minecraft:saddle";

View File

@ -56,7 +56,7 @@ final class R12ItemIdToBlockIdMap{
}
$builtMap = [];
foreach($map as $itemId => $blockId){
foreach(Utils::promoteKeys($map) as $itemId => $blockId){
if(!is_string($itemId)){
throw new AssumptionFailedError("Invalid blockitem ID mapping table, expected string as key");
}

View File

@ -89,6 +89,11 @@ interface RuntimeDataDescriber extends RuntimeEnumDescriber{
public function straightOnlyRailShape(int &$railShape) : void;
/**
* @phpstan-template T of \UnitEnum
* @phpstan-param T &$case
* @phpstan-param-out T $case
*/
public function enum(\UnitEnum &$case) : void;
/**

View File

@ -35,6 +35,7 @@ use pocketmine\event\entity\EntityMotionEvent;
use pocketmine\event\entity\EntityRegainHealthEvent;
use pocketmine\event\entity\EntitySpawnEvent;
use pocketmine\event\entity\EntityTeleportEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector2;
@ -74,7 +75,6 @@ use function deg2rad;
use function floor;
use function fmod;
use function get_class;
use function lcg_value;
use function sin;
use function spl_object_id;
use const M_PI_2;
@ -102,7 +102,10 @@ abstract class Entity{
return self::$entityCount++;
}
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $hasSpawned = [];
protected int $id;
@ -906,7 +909,7 @@ abstract class Entity{
return false;
}
$force = lcg_value() * 0.2 + 0.1;
$force = Utils::getRandomFloat() * 0.2 + 0.1;
$this->motion = match($direction){
Facing::WEST => $this->motion->withComponents(-$force, null, null),
@ -1560,6 +1563,13 @@ abstract class Entity{
$this->hasSpawned = [];
}
/**
* Returns the item that players will equip when middle-clicking on this entity.
*/
public function getPickedItem() : ?Item{
return null;
}
/**
* Flags the entity to be removed from the world on the next tick.
*/

View File

@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\EntityDataHelper as Helper;
use pocketmine\entity\object\EndCrystal;
use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity;
@ -42,6 +43,7 @@ use pocketmine\entity\projectile\Arrow;
use pocketmine\entity\projectile\Egg;
use pocketmine\entity\projectile\EnderPearl;
use pocketmine\entity\projectile\ExperienceBottle;
use pocketmine\entity\projectile\IceBomb;
use pocketmine\entity\projectile\Snowball;
use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\Item;
@ -92,6 +94,10 @@ final class EntityFactory{
return new Egg(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['Egg', 'minecraft:egg']);
$this->register(EndCrystal::class, function(World $world, CompoundTag $nbt) : EndCrystal{
return new EndCrystal(Helper::parseLocation($nbt, $world), $nbt);
}, ['EnderCrystal', 'minecraft:ender_crystal']);
$this->register(EnderPearl::class, function(World $world, CompoundTag $nbt) : EnderPearl{
return new EnderPearl(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['ThrownEnderpearl', 'minecraft:ender_pearl']);
@ -115,6 +121,10 @@ final class EntityFactory{
return new FallingBlock(Helper::parseLocation($nbt, $world), FallingBlock::parseBlockNBT(RuntimeBlockStateRegistry::getInstance(), $nbt), $nbt);
}, ['FallingSand', 'minecraft:falling_block']);
$this->register(IceBomb::class, function(World $world, CompoundTag $nbt) : IceBomb{
return new IceBomb(Helper::parseLocation($nbt, $world), null, $nbt);
}, ['minecraft:ice_bomb']);
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
$itemTag = $nbt->getCompoundTag(ItemEntity::TAG_ITEM);
if($itemTag === null){

View File

@ -34,6 +34,7 @@ interface FoodSource extends Consumable{
/**
* Returns whether a Human eating this FoodSource must have a non-full hunger bar.
* This is ignored in creative mode and in peaceful difficulty.
*/
public function requiresHunger() : bool;
}

View File

@ -68,6 +68,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player;
use pocketmine\world\sound\TotemUseSound;
use pocketmine\world\World;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use function array_fill;
@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->hungerManager;
}
/**
* Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()},
* as HungerManager only handles the hunger bar.
*/
public function canEat() : bool{
return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL;
}
public function consumeObject(Consumable $consumable) : bool{
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){
return false;
}

View File

@ -88,7 +88,8 @@ class HungerManager{
}
/**
* Returns whether this Human may consume objects requiring hunger.
* Returns whether the food level is below the maximum.
* This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that.
*/
public function isHungry() : bool{
return $this->getFood() < $this->getMaxFood();

View File

@ -25,6 +25,8 @@ namespace pocketmine\entity;
use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\VanillaBlocks;
use pocketmine\block\Water;
use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\animation\DeathAnimation;
use pocketmine\entity\animation\HurtAnimation;
@ -44,6 +46,7 @@ use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\math\VoxelRayTrace;
use pocketmine\nbt\tag\CompoundTag;
@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player;
use pocketmine\timings\Timings;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use pocketmine\world\sound\BurpSound;
use pocketmine\world\sound\EntityLandSound;
use pocketmine\world\sound\EntityLongFallSound;
use pocketmine\world\sound\EntityShortFallSound;
use pocketmine\world\sound\ItemBreakSound;
use function abs;
use function array_shift;
use function atan2;
use function ceil;
use function count;
use function floor;
use function ksort;
use function lcg_value;
use function max;
use function min;
use function mt_getrandmax;
@ -128,6 +132,8 @@ abstract class Living extends Entity{
protected bool $gliding = false;
protected bool $swimming = false;
private ?int $frostWalkerLevel = null;
protected function getInitialDragMultiplier() : float{ return 0.02; }
protected function getInitialGravity() : float{ return 0.08; }
@ -151,6 +157,14 @@ abstract class Living extends Entity{
$this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
)));
$this->armorInventory->getListeners()->add(new CallbackInventoryListener(
onSlotChange: function(Inventory $inventory, int $slot) : void{
if($slot === ArmorInventory::SLOT_FEET){
$this->frostWalkerLevel = null;
}
},
onContentChange: function() : void{ $this->frostWalkerLevel = null; }
));
$health = $this->getMaxHealth();
@ -490,7 +504,7 @@ abstract class Living extends Entity{
$helmet = $this->armorInventory->getHelmet();
if($helmet instanceof Armor){
$finalDamage = $source->getFinalDamage();
$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2));
$this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
$this->armorInventory->setHelmet($helmet);
}
}
@ -687,6 +701,47 @@ abstract class Living extends Entity{
return $hasUpdate;
}
protected function move(float $dx, float $dy, float $dz) : void{
$oldX = $this->location->x;
$oldZ = $this->location->z;
parent::move($dx, $dy, $dz);
$frostWalkerLevel = $this->getFrostWalkerLevel();
if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){
$this->applyFrostWalker($frostWalkerLevel);
}
}
protected function applyFrostWalker(int $level) : void{
$radius = $level + 2;
$world = $this->getWorld();
$baseX = $this->location->getFloorX();
$y = $this->location->getFloorY() - 1;
$baseZ = $this->location->getFloorZ();
$frostedIce = VanillaBlocks::FROSTED_ICE();
for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
$block = $world->getBlockAt($x, $y, $z);
if(
!$block instanceof Water ||
!$block->isSource() ||
$world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
){
continue;
}
$world->setBlockAt($x, $y, $z, $frostedIce);
}
}
}
public function getFrostWalkerLevel() : int{
return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER());
}
/**
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
*/
@ -697,7 +752,7 @@ abstract class Living extends Entity{
$this->setBreathing(false);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
lcg_value() <= (1 / ($respirationLevel + 1))
Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
){
$ticks -= $tickDiff;
if($ticks <= -20){

View File

@ -26,6 +26,7 @@ namespace pocketmine\entity;
use pocketmine\entity\animation\SquidInkCloudAnimation;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
@ -124,4 +125,8 @@ class Squid extends WaterAnimal{
VanillaItems::INK_SAC()->setCount(mt_rand(1, 3))
];
}
public function getPickedItem() : ?Item{
return VanillaItems::SQUID_SPAWN_EGG();
}
}

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
@ -87,6 +89,10 @@ class Villager extends Living implements Ageable{
return $this->baby;
}
public function getPickedItem() : ?Item{
return VanillaItems::VILLAGER_SPAWN_EGG();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::BABY, $this->baby);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use function mt_rand;
@ -65,4 +66,8 @@ class Zombie extends Living{
//TODO: check for equipment and whether it's a baby
return 5;
}
public function getPickedItem() : ?Item{
return VanillaItems::ZOMBIE_SPAWN_EGG();
}
}

View File

@ -0,0 +1,146 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\entity\object;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Explosive;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityPreExplodeEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\world\Explosion;
class EndCrystal extends Entity implements Explosive{
private const TAG_SHOWBASE = "ShowBottom"; //TAG_Byte
private const TAG_BLOCKTARGET_X = "BlockTargetX"; //TAG_Int
private const TAG_BLOCKTARGET_Y = "BlockTargetY"; //TAG_Int
private const TAG_BLOCKTARGET_Z = "BlockTargetZ"; //TAG_Int
public static function getNetworkTypeId() : string{ return EntityIds::ENDER_CRYSTAL; }
protected bool $showBase = false;
protected ?Vector3 $beamTarget = null;
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); }
protected function getInitialDragMultiplier() : float{ return 1.0; }
protected function getInitialGravity() : float{ return 0.0; }
public function isFireProof() : bool{
return true;
}
public function getPickedItem() : ?Item{
return VanillaItems::END_CRYSTAL();
}
public function showBase() : bool{
return $this->showBase;
}
public function setShowBase(bool $showBase) : void{
$this->showBase = $showBase;
$this->networkPropertiesDirty = true;
}
public function getBeamTarget() : ?Vector3{
return $this->beamTarget;
}
public function setBeamTarget(?Vector3 $beamTarget) : void{
$this->beamTarget = $beamTarget;
$this->networkPropertiesDirty = true;
}
public function attack(EntityDamageEvent $source) : void{
parent::attack($source);
if(
$source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
!$this->isFlaggedForDespawn() &&
!$source->isCancelled()
){
$this->flagForDespawn();
$this->explode();
}
}
protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);
$this->setMaxHealth(1);
$this->setHealth(1);
$this->setShowBase($nbt->getByte(self::TAG_SHOWBASE, 0) === 1);
if(
($beamXTag = $nbt->getTag(self::TAG_BLOCKTARGET_X)) instanceof IntTag &&
($beamYTag = $nbt->getTag(self::TAG_BLOCKTARGET_Y)) instanceof IntTag &&
($beamZTag = $nbt->getTag(self::TAG_BLOCKTARGET_Z)) instanceof IntTag
){
$this->setBeamTarget(new Vector3($beamXTag->getValue(), $beamYTag->getValue(), $beamZTag->getValue()));
}
}
public function saveNBT() : CompoundTag{
$nbt = parent::saveNBT();
$nbt->setByte(self::TAG_SHOWBASE, $this->showBase ? 1 : 0);
if($this->beamTarget !== null){
$nbt->setInt(self::TAG_BLOCKTARGET_X, $this->beamTarget->getFloorX());
$nbt->setInt(self::TAG_BLOCKTARGET_Y, $this->beamTarget->getFloorY());
$nbt->setInt(self::TAG_BLOCKTARGET_Z, $this->beamTarget->getFloorZ());
}
return $nbt;
}
public function explode() : void{
$ev = new EntityPreExplodeEvent($this, 6);
$ev->call();
if(!$ev->isCancelled()){
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
if($ev->isBlockBreaking()){
$explosion->explodeA();
}
$explosion->explodeB();
}
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);
$properties->setGenericFlag(EntityMetadataFlags::SHOWBASE, $this->showBase);
$properties->setBlockPos(EntityMetadataProperties::BLOCK_TARGET, BlockPosition::fromVector3($this->beamTarget ?? Vector3::zero()));
}
}

View File

@ -35,6 +35,7 @@ use pocketmine\entity\Location;
use pocketmine\event\entity\EntityBlockChangeEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag;
@ -194,6 +195,10 @@ class FallingBlock extends Entity{
return $nbt;
}
public function getPickedItem() : ?Item{
return $this->block->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);

View File

@ -28,6 +28,7 @@ use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Location;
use pocketmine\event\entity\EntityDamageByEntityEvent;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
@ -165,6 +166,10 @@ class Painting extends Entity{
));
}
public function getPickedItem() : ?Item{
return VanillaItems::PAINTING();
}
/**
* Returns the painting motive (which image is displayed on the painting)
*/

View File

@ -23,11 +23,13 @@ declare(strict_types=1);
namespace pocketmine\entity\object;
use pocketmine\block\VanillaBlocks;
use pocketmine\entity\Entity;
use pocketmine\entity\EntitySizeInfo;
use pocketmine\entity\Explosive;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\event\entity\EntityPreExplodeEvent;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@ -127,6 +129,10 @@ class PrimedTNT extends Entity implements Explosive{
}
}
public function getPickedItem() : ?Item{
return VanillaBlocks::TNT()->setWorksUnderwater($this->worksUnderwater)->asItem();
}
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
parent::syncNetworkData($properties);

View 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\entity\projectile;
use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\VanillaBlocks;
use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
use pocketmine\world\particle\ItemBreakParticle;
use pocketmine\world\sound\IceBombHitSound;
class IceBomb extends Throwable{
public static function getNetworkTypeId() : string{ return EntityIds::ICE_BOMB; }
public function getResultDamage() : int{
return -1;
}
protected function calculateInterceptWithBlock(Block $block, Vector3 $start, Vector3 $end) : ?RayTraceResult{
if($block->getTypeId() === BlockTypeIds::WATER){
$pos = $block->getPosition();
return AxisAlignedBB::one()->offset($pos->x, $pos->y, $pos->z)->calculateIntercept($start, $end);
}
return parent::calculateInterceptWithBlock($block, $start, $end);
}
protected function onHit(ProjectileHitEvent $event) : void{
$world = $this->getWorld();
$pos = $this->location;
$world->addSound($pos, new IceBombHitSound());
$itemBreakParticle = new ItemBreakParticle(VanillaItems::ICE_BOMB());
for($i = 0; $i < 6; ++$i){
$world->addParticle($pos, $itemBreakParticle);
}
}
protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{
parent::onHitBlock($blockHit, $hitResult);
$pos = $blockHit->getPosition();
$world = $pos->getWorld();
$posX = $pos->getFloorX();
$posY = $pos->getFloorY();
$posZ = $pos->getFloorZ();
$ice = VanillaBlocks::ICE();
for($x = $posX - 1; $x <= $posX + 1; $x++){
for($y = $posY - 1; $y <= $posY + 1; $y++){
for($z = $posZ - 1; $z <= $posZ + 1; $z++){
if($world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::WATER){
$world->setBlockAt($x, $y, $z, $ice);
}
}
}
}
}
}

View File

@ -28,6 +28,7 @@ use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity;
use pocketmine\entity\Living;
use pocketmine\entity\Location;
use pocketmine\entity\object\EndCrystal;
use pocketmine\event\entity\EntityCombustByEntityEvent;
use pocketmine\event\entity\EntityDamageByChildEntityEvent;
use pocketmine\event\entity\EntityDamageByEntityEvent;
@ -96,7 +97,7 @@ abstract class Projectile extends Entity{
}
public function canCollideWith(Entity $entity) : bool{
return $entity instanceof Living && !$this->onGround;
return ($entity instanceof Living || $entity instanceof EndCrystal) && !$this->onGround;
}
public function canBeCollidedWith() : bool{

View File

@ -30,7 +30,10 @@ use function spl_object_id;
use const SORT_NUMERIC;
class HandlerList{
/** @var RegisteredListener[][] */
/**
* @var RegisteredListener[][]
* @phpstan-var array<int, array<int, RegisteredListener>>
*/
private array $handlerSlots = [];
/** @var RegisteredListenerCache[] */

View File

@ -0,0 +1,53 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\event\player;
use pocketmine\entity\Entity;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\item\Item;
use pocketmine\player\Player;
/**
* Called when a player middle-clicks on an entity to get an item in creative mode.
*/
class PlayerEntityPickEvent extends PlayerEvent implements Cancellable{
use CancellableTrait;
public function __construct(
Player $player,
private Entity $entityClicked,
private Item $resultItem
){
$this->player = $player;
}
public function getEntity() : Entity{
return $this->entityClicked;
}
public function getResultItem() : Item{
return $this->resultItem;
}
}

View File

@ -42,6 +42,9 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
protected Vector3 $touchVector;
protected bool $useItem = true;
protected bool $useBlock = true;
public function __construct(
Player $player,
protected Item $item,
@ -73,4 +76,28 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{
public function getFace() : int{
return $this->blockFace;
}
/**
* Returns whether the item may react to the interaction. If disabled, items such as spawn eggs will not activate.
* This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
*/
public function useItem() : bool{ return $this->useItem; }
/**
* Sets whether the used item may react to the interaction. If false, items such as spawn eggs will not activate.
* This does NOT prevent blocks from being placed - it makes the item behave as if the player is sneaking.
*/
public function setUseItem(bool $useItem) : void{ $this->useItem = $useItem; }
/**
* Returns whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
* respond, containers will not open, etc.
*/
public function useBlock() : bool{ return $this->useBlock; }
/**
* Sets whether the block may react to the interaction. If false, doors, fence gates and trapdoors will not
* respond, containers will not open, etc.
*/
public function setUseBlock(bool $useBlock) : void{ $this->useBlock = $useBlock; }
}

View File

@ -41,7 +41,10 @@ use function spl_object_id;
*/
abstract class BaseInventory implements Inventory, SlotValidatedInventory{
protected int $maxStackSize = Inventory::MAX_STACK;
/** @var Player[] */
/**
* @var Player[]
* @phpstan-var array<int, Player>
*/
protected array $viewers = [];
/**
* @var InventoryListener[]|ObjectSet
@ -286,8 +289,6 @@ abstract class BaseInventory implements Inventory, SlotValidatedInventory{
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $searchItems */
/** @var Item[] $slots */
$searchItems = [];
foreach($slots as $slot){
if(!$slot->isNull()){

View File

@ -36,7 +36,10 @@ final class CreativeInventory{
use SingletonTrait;
use DestructorCallbackTrait;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var array<int, Item>
*/
private array $creative = [];
/** @phpstan-var ObjectSet<\Closure() : void> */
@ -69,6 +72,7 @@ final class CreativeInventory{
/**
* @return Item[]
* @phpstan-return array<int, Item>
*/
public function getAll() : array{
return Utils::cloneObjectArray($this->creative);

View File

@ -57,9 +57,15 @@ class CraftingTransaction extends InventoryTransaction{
protected ?CraftingRecipe $recipe = null;
protected ?int $repetitions = null;
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $inputs = [];
/** @var Item[] */
/**
* @var Item[]
* @phpstan-var list<Item>
*/
protected array $outputs = [];
private CraftingManager $craftingManager;
@ -74,6 +80,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @return Item[]
*
* @phpstan-param list<Item> $providedItems
* @phpstan-return list<Item>
*/
private static function packItems(array $providedItems) : array{
$packedProvidedItems = [];
@ -94,6 +103,9 @@ class CraftingTransaction extends InventoryTransaction{
/**
* @param Item[] $providedItems
* @param RecipeIngredient[] $recipeIngredients
*
* @phpstan-param list<Item> $providedItems
* @phpstan-param list<RecipeIngredient> $recipeIngredients
*/
public static function matchIngredients(array $providedItems, array $recipeIngredients, int $expectedIterations) : void{
if(count($recipeIngredients) === 0){
@ -172,6 +184,9 @@ class CraftingTransaction extends InventoryTransaction{
* @param Item[] $txItems
* @param Item[] $recipeItems
*
* @phpstan-param list<Item> $txItems
* @phpstan-param list<Item> $recipeItems
*
* @throws TransactionValidationException
*/
protected function matchOutputs(array $txItems, array $recipeItems) : int{

View File

@ -29,6 +29,7 @@ use pocketmine\inventory\transaction\action\InventoryAction;
use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function array_keys;
use function array_values;
use function assert;
@ -57,10 +58,16 @@ use function spl_object_id;
class InventoryTransaction{
protected bool $hasExecuted = false;
/** @var Inventory[] */
/**
* @var Inventory[]
* @phpstan-var array<int, Inventory>
*/
protected array $inventories = [];
/** @var InventoryAction[] */
/**
* @var InventoryAction[]
* @phpstan-var array<int, InventoryAction>
*/
protected array $actions = [];
/**
@ -81,6 +88,7 @@ class InventoryTransaction{
/**
* @return Inventory[]
* @phpstan-return array<int, Inventory>
*/
public function getInventories() : array{
return $this->inventories;
@ -93,6 +101,7 @@ class InventoryTransaction{
* significance and should not be relied on.
*
* @return InventoryAction[]
* @phpstan-return array<int, InventoryAction>
*/
public function getActions() : array{
return $this->actions;
@ -102,6 +111,9 @@ class InventoryTransaction{
if(!isset($this->actions[$hash = spl_object_id($action)])){
$this->actions[$hash] = $action;
$action->onAddToTransaction($this);
if($action instanceof SlotChangeAction && !isset($this->inventories[$inventoryId = spl_object_id($action->getInventory())])){
$this->inventories[$inventoryId] = $action->getInventory();
}
}else{
throw new \InvalidArgumentException("Tried to add the same action to a transaction twice");
}
@ -120,21 +132,11 @@ class InventoryTransaction{
$this->actions = $actions;
}
/**
* @internal This method should not be used by plugins, it's used to add tracked inventories for InventoryActions
* involving inventories.
*/
public function addInventory(Inventory $inventory) : void{
if(!isset($this->inventories[$hash = spl_object_id($inventory)])){
$this->inventories[$hash] = $inventory;
}
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
* @phpstan-param-out Item[] $needItems
* @phpstan-param-out Item[] $haveItems
* @phpstan-param-out list<Item> $needItems
* @phpstan-param-out list<Item> $haveItems
*
* @throws TransactionValidationException
*/
@ -188,11 +190,8 @@ class InventoryTransaction{
* wrong order), so this method also tries to chain them into order.
*/
protected function squashDuplicateSlotChanges() : void{
/** @var SlotChangeAction[][] $slotChanges */
$slotChanges = [];
/** @var Inventory[] $inventories */
$inventories = [];
/** @var int[] $slots */
$slots = [];
foreach($this->actions as $key => $action){
@ -203,7 +202,7 @@ class InventoryTransaction{
}
}
foreach($slotChanges as $hash => $list){
foreach(Utils::stringifyKeys($slotChanges) as $hash => $list){
if(count($list) === 1){ //No need to compact slot changes if there is only one on this slot
continue;
}
@ -233,6 +232,7 @@ class InventoryTransaction{
/**
* @param SlotChangeAction[] $possibleActions
* @phpstan-param list<SlotChangeAction> $possibleActions
*/
protected function findResultItem(Item $needOrigin, array $possibleActions) : ?Item{
assert(count($possibleActions) > 0);

View File

@ -60,6 +60,7 @@ abstract class InventoryAction{
/**
* Called when the action is added to the specified InventoryTransaction.
* @deprecated
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{

View File

@ -25,7 +25,6 @@ namespace pocketmine\inventory\transaction\action;
use pocketmine\inventory\Inventory;
use pocketmine\inventory\SlotValidatedInventory;
use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\Item;
use pocketmine\player\Player;
@ -85,13 +84,6 @@ class SlotChangeAction extends InventoryAction{
}
}
/**
* Adds this action's target inventory to the transaction's inventory list.
*/
public function onAddToTransaction(InventoryTransaction $transaction) : void{
$transaction->addInventory($this->inventory);
}
/**
* Sets the item into the target inventory.
*/

View File

@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\player\Player;
use pocketmine\utils\Binary;
use function lcg_value;
use pocketmine\utils\Utils;
use function mt_rand;
class Armor extends Durable{
@ -129,7 +129,7 @@ class Armor extends Durable{
$chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){
if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best
if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best
$negated++;
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\nbt\tag\CompoundTag;
use function lcg_value;
use pocketmine\utils\Utils;
use function min;
abstract class Durable extends Item{
@ -87,7 +87,7 @@ abstract class Durable extends Item{
$chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){
if(lcg_value() > $chance){
if(Utils::getRandomFloat() > $chance){
$negated++;
}
}

59
src/item/EndCrystal.php Normal file
View File

@ -0,0 +1,59 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds;
use pocketmine\entity\Location;
use pocketmine\entity\object\EndCrystal as EntityEndCrystal;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function count;
class EndCrystal extends Item{
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
if($blockClicked->getTypeId() === BlockTypeIds::OBSIDIAN || $blockClicked->getTypeId() === BlockTypeIds::BEDROCK){
$pos = $blockClicked->getPosition();
$world = $pos->getWorld();
$bb = AxisAlignedBB::one()
->offset($pos->getX(), $pos->getY(), $pos->getZ())
->extend(Facing::UP, 1);
if(
count($world->getNearbyEntities($bb)) === 0 &&
$blockClicked->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR &&
$blockClicked->getSide(Facing::UP, 2)->getTypeId() === BlockTypeIds::AIR
){
$crystal = new EntityEndCrystal(Location::fromObject($pos->add(0.5, 1, 0.5), $world));
$crystal->spawnToAll();
$this->pop();
return ItemUseResult::SUCCESS;
}
}
return ItemUseResult::NONE;
}
}

View File

@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{
}
public function canStartUsingItem(Player $player) : bool{
return !$this->requiresHunger() || $player->getHungerManager()->isHungry();
return !$this->requiresHunger() || $player->canEat();
}
}

71
src/item/GoatHorn.php Normal file
View File

@ -0,0 +1,71 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\sound\GoatHornSound;
class GoatHorn extends Item implements Releasable{
private GoatHornType $goatHornType = GoatHornType::PONDER;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->enum($this->goatHornType);
}
public function getHornType() : GoatHornType{ return $this->goatHornType; }
/**
* @return $this
*/
public function setHornType(GoatHornType $type) : self{
$this->goatHornType = $type;
return $this;
}
public function getMaxStackSize() : int{
return 1;
}
public function getCooldownTicks() : int{
return 140;
}
public function getCooldownTag() : ?string{
return ItemCooldownTags::GOAT_HORN;
}
public function canStartUsingItem(Player $player) : bool{
return true;
}
public function onClickAir(Player $player, Vector3 $directionVector, array &$returnedItems) : ItemUseResult{
$position = $player->getPosition();
$position->getWorld()->addSound($position, new GoatHornSound($this->goatHornType));
return ItemUseResult::SUCCESS;
}
}

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