mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 19:24:12 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
ea8f971287 | |||
62e1d87f5e | |||
ea068d4907 | |||
fa7bc78e7c | |||
0aaf4238a8 | |||
35a90d24ec | |||
9a6e258b6c | |||
a1448bfb88 | |||
ba6828c6bd | |||
5fc96c393d | |||
2d0321ff02 | |||
c56d4d3e3c | |||
06028aac97 | |||
950f7ad7a4 | |||
49da50659f | |||
ae9b4dbb05 | |||
fcef015f32 | |||
f1a3b42620 | |||
f3763ae691 | |||
12214792b3 | |||
93a9007f3c | |||
a593180ef9 | |||
61560ec375 | |||
44771c892d | |||
89f88a9333 | |||
9cab72ed59 | |||
2bd9f4108b | |||
cd2a1fdc1d | |||
a396233e57 | |||
d666f17ec6 | |||
a84326cb2d | |||
0543bf301e | |||
603ab081bc | |||
daacc8eddb | |||
269effcecf | |||
ae750b60d0 | |||
905a10e980 | |||
52fe2cb97f | |||
8cdc7d7ee1 | |||
12ae8dc03b | |||
a9787f0d99 | |||
5325ecee37 | |||
30ee0aa63d | |||
aef4fa7174 | |||
53aa380ca3 | |||
a5f607138c | |||
8338ebaffd | |||
082119cfd2 | |||
7460e12b6a | |||
a53b0116a0 | |||
5b72f202bf | |||
8b6adf86d6 | |||
0070426e97 | |||
a523ed6e40 | |||
7c2ed7d884 | |||
1e3a858de6 | |||
844ba0ff9f | |||
49bdaee930 | |||
9195c88670 | |||
ae19d05fd5 | |||
bf7a53b00f | |||
e710b3750f | |||
8ccd1edb17 | |||
faf1e26bac | |||
b4259ef988 | |||
b2aa6396c3 | |||
1555fa17e7 | |||
e77f2c5198 | |||
e8e441f739 | |||
48a908ee8c | |||
dc87a2b10e | |||
d3add78d3e | |||
ff695a5f97 | |||
4b630cb726 | |||
4331f69b9c | |||
fb1213e964 | |||
8a693f2a4c | |||
8474eaf5f1 | |||
a75d4687ce | |||
0b0c425805 | |||
b5469dede2 | |||
e0d270a870 | |||
2d9cee3d62 | |||
ed64231c57 | |||
33a7b46329 | |||
9b58d35516 | |||
3629ee7e7b | |||
fbeb017670 | |||
09bf203267 | |||
f3cc4a28e1 | |||
fe70150db2 | |||
054538e6b7 | |||
3586bc42a9 | |||
1f86949836 | |||
053a71c59d | |||
07d5046b83 | |||
fa3529966f | |||
05a9e9c76e |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @pmmp/server-developers
|
10
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
10
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
@ -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
|
||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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
|
||||
|
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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)
|
||||
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@ -37,4 +37,7 @@ updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: monthly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns: ["*"]
|
||||
|
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.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
|
||||
|
3
.github/workflows/draft-release.yml
vendored
3
.github/workflows/draft-release.yml
vendored
@ -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: |
|
||||
|
8
.github/workflows/main-php-matrix.yml
vendored
8
.github/workflows/main-php-matrix.yml
vendored
@ -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"
|
||||
|
33
.github/workflows/pr-remove-waiting-label.yml
vendored
Normal file
33
.github/workflows/pr-remove-waiting-label.yml
vendored
Normal 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;
|
||||
}
|
||||
}
|
38
.github/workflows/team-pr-auto-approve.yml
vendored
38
.github/workflows/team-pr-auto-approve.yml
vendored
@ -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" }'
|
||||
|
@ -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.
|
||||
|
@ -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{
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
|
Submodule build/php updated: 084822aa9e...5016e0a3d5
@ -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
16
changelogs/5.22.md
Normal 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
107
changelogs/5.23.md
Normal 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.
|
@ -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
317
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "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",
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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)){
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()){
|
||||
|
@ -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[]
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ class RuntimeBlockStateRegistry{
|
||||
|
||||
/**
|
||||
* @return Block[]
|
||||
* @phpstan-return array<int, Block>
|
||||
*/
|
||||
public function getAllKnownStates() : array{
|
||||
return $this->fullList;
|
||||
|
@ -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
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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){
|
||||
|
72
src/block/utils/MultiAnyFacingTrait.php
Normal file
72
src/block/utils/MultiAnyFacingTrait.php
Normal 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;
|
||||
}
|
||||
}
|
96
src/block/utils/MultiAnySupportTrait.php
Normal file
96
src/block/utils/MultiAnySupportTrait.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
60
src/command/ClosureCommand.php
Normal file
60
src/command/ClosureCommand.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
89
src/command/defaults/XpCommand.php
Normal file
89
src/command/defaults/XpCommand.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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");
|
||||
|
71
src/console/ConsoleReaderChildProcessUtils.php
Normal file
71
src/console/ConsoleReaderChildProcessUtils.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = "";
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
48
src/data/bedrock/GoatHornTypeIdMap.php
Normal file
48
src/data/bedrock/GoatHornTypeIdMap.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\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);
|
||||
}
|
||||
}
|
||||
}
|
35
src/data/bedrock/GoatHornTypeIds.php
Normal file
35
src/data/bedrock/GoatHornTypeIds.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\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;
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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(),
|
||||
|
@ -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";
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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){
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
146
src/entity/object/EndCrystal.php
Normal file
146
src/entity/object/EndCrystal.php
Normal 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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
*/
|
||||
|
@ -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);
|
||||
|
||||
|
86
src/entity/projectile/IceBomb.php
Normal file
86
src/entity/projectile/IceBomb.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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[] */
|
||||
|
53
src/event/player/PlayerEntityPickEvent.php
Normal file
53
src/event/player/PlayerEntityPickEvent.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
|
@ -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()){
|
||||
|
@ -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);
|
||||
|
@ -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{
|
||||
|
@ -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);
|
||||
|
@ -60,6 +60,7 @@ abstract class InventoryAction{
|
||||
|
||||
/**
|
||||
* Called when the action is added to the specified InventoryTransaction.
|
||||
* @deprecated
|
||||
*/
|
||||
public function onAddToTransaction(InventoryTransaction $transaction) : void{
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
@ -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
59
src/item/EndCrystal.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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
71
src/item/GoatHorn.php
Normal 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
Reference in New Issue
Block a user