Compare commits

...

114 Commits
5.6.1 ... 5.8.2

Author SHA1 Message Date
c3c81b09e8 Release 5.8.2 2023-11-09 18:51:17 +00:00
08f9873c32 Update build/php submodule to pmmp/PHP-Binaries@3c3c483baa 2023-11-09 18:30:03 +00:00
e3700cab50 ZlibCompressor: use libdeflate for level 0 compression
this is supported since libdeflate 1.15 and ext-libdeflate 0.2.0.
Everyone should be using these versions by now anyway, and if they aren't, they should update.

libdeflate's level 0 compression is over 20 times faster than zlib, so this is a nice performance improvement.
2023-11-09 18:05:07 +00:00
ace
c1ed182112 Fix loading of sign text from vanilla world (#6122) 2023-11-09 14:22:37 +00:00
5f3a2a5096 BlockStateUpgrader: fixed undefined array key when flattening a block whose new ID is the same as the old
this came up during 1.20.50 testing, where minecraft:stone[stone_type=stone] continues to have an ID minecraft:stone without the stone_type property.
This could have appeared in other ways, such as with an invalid legacy blockstate.
2023-11-08 18:52:22 +00:00
8ccaf907d1 tools/generate-blockstate-upgrade-schema: do not optimize state remaps with unchanged IDs
processRemappedStates() needs to know about the full set of states to generate reliable mappings.
Without it, it may generate flattening rules or state matching criteria that unintentionally match states that it's not aware of.
Sadly, this does make some schemas bigger, but it's for the best.
2023-11-08 15:19:02 +00:00
6b5c405939 Bump shivammathur/setup-php from 2.26.0 to 2.27.1 (#6142)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.26.0 to 2.27.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.26.0...2.27.1)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 12:06:43 +00:00
9fc9609694 Fix ancient debris not being fireproof (#6138) 2023-11-06 15:01:43 +00:00
1055b7580a Bump phpstan/phpstan from 1.10.40 to 1.10.41 (#6139)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.40 to 1.10.41.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.40...1.10.41)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 15:00:06 +00:00
3385087c56 Fix Iron & Gold ores not affected by fortune (#6134) 2023-11-04 10:57:53 +00:00
1e4a1565bb Stem: fixed not attaching to grown pumpkin/melon
no idea how this got missed ...
2023-11-02 14:48:45 +00:00
8aaa6dd176 Stem: do not disable ticking when the stem is not attached to a pumpkin
fixes #6131
2023-11-02 14:47:55 +00:00
07dff9c9e8 5.8.2 is next 2023-11-01 17:55:23 +00:00
75a39491be Release 5.8.1 2023-11-01 17:55:22 +00:00
a10e4b6481 Merge branch 'minor-next' into stable 2023-11-01 17:51:52 +00:00
68c6b87678 5.8.1 is next 2023-11-01 17:49:56 +00:00
e20c031aa1 Release 5.8.0 2023-11-01 17:49:53 +00:00
9832fe899f Merge branch 'stable' into minor-next 2023-11-01 16:39:44 +00:00
55f3477ed9 5.7.2 is next 2023-11-01 16:37:46 +00:00
2c17f82eb8 Release 5.7.1 2023-11-01 16:37:46 +00:00
e6e2c54ec9 Fixed various reentrant-unsafe 2D array element unsets (similar to previous commit)
this pattern was used in various places
2023-11-01 16:28:59 +00:00
abce512860 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-11-01 16:13:44 +00:00
0093732d49 PermissionManager: fixed non-reentrant-safe permission unsubscribing
during unset(), the destructors for other objects with cyclic references can get triggered, resulting in the functions being reentered before the count() call. This leads to a crash because the offset no longer exists.
Instead, we check if only the given PermissibleInternal is present, and clean everything up with a single unset instead of two.
This could also have been solved by adding extra isset() checks before checking the counts, but this way seemed more elegant.

This is similar to an issue with AsyncTask thread-local storage a few months ago, which was also caused by GC reentrancy.

closes #6119
2023-11-01 16:13:28 +00:00
9eb2a46942 World: remove useless isChunkLoaded checks
getChunkEntities() will return an empty array if the chunk isn't loaded anyway, so this is just wasting CPU cycles.
2023-11-01 15:53:37 +00:00
1402571055 Bump phpstan/phpstan from 1.10.39 to 1.10.40 (#6126)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.39 to 1.10.40.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.39...1.10.40)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 15:26:46 +00:00
34bb86d2bf Bump phpstan/phpstan-strict-rules from 1.5.1 to 1.5.2 (#6125)
Bumps [phpstan/phpstan-strict-rules](https://github.com/phpstan/phpstan-strict-rules) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/phpstan/phpstan-strict-rules/releases)
- [Commits](https://github.com/phpstan/phpstan-strict-rules/compare/1.5.1...1.5.2)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-strict-rules
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 15:26:30 +00:00
b41960dfec Merge branch 'stable' into minor-next 2023-10-26 12:55:47 +01:00
0b2fc84827 5.7.1 is next 2023-10-26 12:51:45 +01:00
22b9e70372 Release 5.7.0 2023-10-26 12:51:45 +01:00
a222636476 Merge branch 'legacy/pm4' into stable 2023-10-26 12:47:19 +01:00
fb586cc562 4.25.1 is next 2023-10-26 12:43:16 +01:00
f3f22ba48b Release 4.25.0 2023-10-26 12:43:16 +01:00
a2e6e2e5b9 Update PHPStan 2023-10-26 12:39:45 +01:00
1aaaadb909 1.20.40 changes (PM4) 2023-10-26 12:36:12 +01:00
53a740433f Changes for 1.20.40 2023-10-26 12:32:59 +01:00
8491d3c6c0 Merge branch 'stable' into minor-next 2023-10-24 11:56:51 +01:00
d637370b83 Wipe internal block AABB cache only for specific blocks that need it
the vast majority of blocks don't need this cache erasing, so it's costing performance for no good reason.
2023-10-23 17:38:04 +01:00
f655eda3b3 Player: bypass slow function call
for some reason the isSpectator() call here can take upwards of 2 microseconds, for no obvious reason. Subsequent calls are much faster, so I think there's some weird cache thing going on here.
2023-10-23 16:05:13 +01:00
af432c1a7f Give neighbour block updates its own timer
this way we aren't conflating them with scheduled updates, which are usually caused by e.g. water
2023-10-23 12:33:36 +01:00
9fcc9f4338 StringToItemParser: added missing blocks
closes #6108
2023-10-23 12:24:02 +01:00
41c5f63565 World: remove dead code 2023-10-23 12:17:41 +01:00
73b1fba53c Fixed Promise<null> calling rejection handler given after being successfully resolved
closes #6110

this is a weird use case, but it should work nonetheless.
2023-10-23 11:46:08 +01:00
8e17aed4f4 Fix build 2023-10-20 17:43:04 +01:00
1f461977d4 Block: Avoid useless Vector3 allocations in getHorizontalSides and getAllSides 2023-10-20 17:28:19 +01:00
e4888d7102 ÂCONTRIBUTING.md: restructure, reword and reorganize
[ci skip]
2023-10-20 12:12:14 +01:00
450ad42202 Added some new recommendations to CONTRIBUTING.md 2023-10-20 11:37:58 +01:00
eb935ca80f ÂLeaves: use a constant for max distance from wood 2023-10-20 11:20:30 +01:00
1c5d3b43be 5.6.2 is next 2023-10-20 10:30:50 +01:00
dbb5a32a96 Liquid: eliminate unnecessary Position allocations and getSide() calls 2023-10-19 17:09:13 +01:00
9474324f75 Liquid: simplify code 2023-10-19 17:02:40 +01:00
ada37899aa Liquid: improve code legibility and fix a bunch of PHPStan errors 2023-10-19 17:02:22 +01:00
f1440324a7 Update PHPStan baselines 2023-10-19 16:56:15 +01:00
73659318f6 World: Avoid unnecessary Vector3 usages in neighbour block update scheduling
The old code was allocating 6 Vector3s which were all immediately discarded. In addition, we didn't need to take the performance hit of reading Vector3 properties when we could have just passed integers directly.
The real performance difference of this is likely to be close to zero, but it's still worth doing.
2023-10-19 16:46:52 +01:00
f868c1d8c6 Liquid: Update legacy code with Facing::OFFSET 2023-10-19 16:38:34 +01:00
114f444ec3 Update PHPStan baseline 2023-10-19 13:28:40 +01:00
19a1792184 Fixed behavioural change on invalid Facing introduced by previous commit
while this shouldn't be given invalid facings anyway, Vector3::getSide() accepts them and just returns itself.
2023-10-19 13:27:44 +01:00
6a3ec70c72 ÂBlock: use Facing::OFFSET in getSide()
instead of the comically inefficient getBlock() + throwaway Position->getSide()
This improved the function's performance by 2.3x.
2023-10-19 13:25:32 +01:00
ccd2cdd324 World: improve performance of calculating non-cached AABBs for a cell
Avoiding getSide() improved the performance by 2x ...
2023-10-19 13:13:46 +01:00
c7a358a56f World: extract getBlockCollisionBoxes() from getCollisionBoxes()
closes #6104

This function has been a footgun for anyone using it, since it also returns entity AABBs by default.
In all core use cases, this functionality was disabled, and we were paying a needless (admittedly micro) performance penalty for passing the extra useless argument and useless condition check.
2023-10-19 12:52:24 +01:00
48dcf0e32c Farmland: Remember relative location of nearby water in blockstate data (#6090)
Farmland can end up scanning up to 162 blocks looking for water in the worst case. This is obviously not great for huge farms where there are thousands of blocks of the stuff.

In most farms, the water won't be moved, and nor will the farmland. This means that we can avoid this costly search on random updates.

This PR implements a cache using blockstate data (only possible in PM5) which stores an index mapping to a coordinate offset where water was previously found by this farmland block. This allows the farmland to avoid water searching entirely in most cases.

This is a colossal improvement as compared to scanning the whole 9x2x9 area every time, which, on average, scans about 40 blocks to find water if the water is at the same Y coordinate. In real terms this translates into about a 8x performance improvement for farmland (see timings below).
2023-10-17 16:25:13 +01:00
7f3de835e4 Block: private internal function 2023-10-17 15:23:39 +01:00
63fcf9879a Block: Use xxhash'd XOR mask to improve state data lower bits distribution
xxhash is generally well known for its hash key properties, so this is a suitable use case.
We XOR the state data with a partial hash of xxh3(typeID), which provides sufficient hash distribution regardless of the size of state data.
The previous method started to break down as the number of bits exceeded the number of significant bits of type ID (about 10 currently).

As well as being better for hash distribution regardless of state data size, this also reduces the load factor of RuntimeBlockRegistry to 1.08 (previously around 1.24), which is a nice bonus.
2023-10-17 15:21:22 +01:00
d0d16cdeb7 RuntimeDataDescriber: Introduce boundedIntAuto, replacing boundedInt
closes #6096
boundedIntAuto automatically calculates the correct number of bits to use based on the given bounds. The bounds must be constant, of course.
2023-10-17 12:03:43 +01:00
18b711aca8 Apparently this function is no longer needed 2023-10-17 11:34:02 +01:00
b0936a50c1 Block: split generateStatePermutations into type and state parts
this makes it marginally faster, since we can skip all permutations containing invalid type data.
I measured a performance improvement of about 20% across all blocks.
In addition, this makes it easier to locate where a problem is coming from if invalid inputs are accepted.
2023-10-17 11:28:58 +01:00
ace
82d6fc3890 Fix loading of Chiseled Bookshelf tile from vanilla world (#6084)
Co-authored-by: Dylan T <dktapps@pmmp.io>
2023-10-17 11:10:36 +01:00
3c614b505d Merge branch 'stable' into minor-next 2023-10-16 21:28:59 +01:00
538b698a00 Revert "World: specialize nearby entity updating for block updates"
This reverts commit 128eb500eb.

This breaks when entities in neighbouring chunks overlap into the
current chunk without actually being tracked by it.
Perhaps it might be worth having entities tracked by all chunks their
AABB touches in the future, so that we don't have to check padding
chunks and waste CPU time.
2023-10-14 19:43:46 +01:00
128eb500eb World: specialize nearby entity updating for block updates
this slashes the cost of checking this with no entities by 50%, which should be the common case for farms and such.
once factoring in other things, this translates into a real-world performance gain of about 15% for block updates.
2023-10-13 17:35:47 +01:00
ace
8b52a5cd9e Fix book placed in wrong slot in Chiseled Bookshelf (#6085) 2023-10-10 12:35:50 +01:00
ace
c9163a1505 ChiseledBookshelf: Fix duplication bug (#6086)
Also fixes being able to put more than 1 book in the slot
2023-10-10 10:59:39 +01:00
ee26d6d570 LightUpdate: avoid trying to propagate light into nodes with higher light levels
Track which direction the current node's light came from, and don't
check it again when we check the current node's adjacent blocks.

e.g. if this node was the eastern neighbour of a light source, we don't
need to check this node's western neighbour, as we already know it has
a higher light level than our own.

This improves performance of basic light spread in a void by about 6%,
which isn't a huge amount, but it's something.

I've yet to explore whether light removal could also benefit from this
change.
2023-10-09 17:06:02 +01:00
006f78c0a7 Updated block consistency check 2023-09-29 12:57:39 +01:00
55cc5a6651 NetherVines: optimize condition order 2023-09-29 12:45:55 +01:00
390cc3060a Disable random ticking for some blocks depending on their states
we don't need to waste CPU time attempting to tick fully grown crops.
2023-09-29 12:45:37 +01:00
ca69f08da0 ChorusFlower: move ticksRandomly() to a more appropriate place 2023-09-29 12:44:27 +01:00
eac0564792 Fix CS 2023-09-29 12:31:11 +01:00
628d77f8d7 Implemented pitcher plants, crops and pods 2023-09-29 12:30:52 +01:00
fe543a4789 AgeableTrait: fixed incorrect number of bits
the original method breaks if MAX_AGE is 0 or any power of 2.
2023-09-29 12:05:39 +01:00
31cd096b4b Implement torchflower, its seeds and its crop 2023-09-28 17:13:33 +01:00
78cc5ba635 CS again 2023-09-28 16:06:38 +01:00
4b9d170954 RuntimeDataDescriber: added dynamic method for reading and writing enum sets
this was previously only needed for brewing stands, but it's now become needed for chiselled bookshelves too.
2023-09-28 16:06:17 +01:00
d94391af57 Implement Chiseled Bookshelf (#5827) 2023-09-28 15:56:46 +01:00
a6b030f2b3 Fix CSÂ 2023-09-27 17:07:02 +01:00
56d7039086 Implemented budding amethyst and amethyst clusters 2023-09-27 17:02:37 +01:00
4f13e446a1 StringToItemParser: clean up hardcoded potion aliases
these were needed in PM4, but the type is dynamic in PM5.
2023-09-27 13:18:02 +01:00
6ec340359b Implemented crimson and warped roots 2023-09-27 13:07:46 +01:00
ee6d551729 Include TitleID in PlayerInfo extraData (#6054) 2023-09-21 14:48:50 +01:00
04b815a87a Merge branch 'stable' into minor-next 2023-09-21 13:27:11 +01:00
3906600d44 Fix CS 2023-09-20 19:44:53 +01:00
3f7abf29a8 Added PHPStan rule to flag usages of legacy enum accessors provided by LegacyEnumShimTrait
closes #6061
2023-09-20 19:42:21 +01:00
fe3e2cc90a Merge branch 'stable' into minor-next 2023-09-20 19:14:34 +01:00
e9169cfa67 ChorusPlant: change stupid code 2023-09-08 17:34:47 +01:00
537e194161 ChorusPlant: make use of StaticSupportTrait 2023-09-08 17:33:32 +01:00
f7f5af607c Merge branch 'stable' into minor-next 2023-09-08 17:20:45 +01:00
b293d7bf1f Static support trait (#6044)
Added StaticSupportTrait for blocks which require unconditional support

dynamic support requirements, such as those presented by item frames and torches, are not included.

in addition, double blocks, such as tallgrass, small dripleaf and doors, do not cooperate well with this, so they are also not included.

some blocks which could be migrated (such as chorus plant) were skipped due to unresolved problems.
2023-09-08 17:19:06 +01:00
2a528b4afb SetupWizard: stop depending on Java gamemode IDs for option selection 2023-09-08 16:15:07 +01:00
999eab0c84 SetupWizard: use Server constants directly 2023-09-08 15:43:36 +01:00
33a0fb9061 SetupWizard: clean up crusty code that loads server.properties 3 times 2023-09-08 15:14:51 +01:00
904b0acfff SetupWizard: replace hardcoded config keys 2023-09-08 15:11:59 +01:00
093b1e1b18 Merge branch 'stable' into minor-next 2023-09-08 12:48:40 +01:00
d60fca0a1c Age blocks logic moved into dedicated trait (#5962) 2023-09-08 12:25:26 +01:00
d3ab516ba4 CS 2023-09-08 11:26:58 +01:00
aa916b2c49 WoodLikeBlockIdHelper: reduce obnoxious code 2023-09-08 11:17:47 +01:00
7ce33d9375 Migrate final remaining EnumTrait users to native enums 2023-09-08 10:34:12 +01:00
14f2368454 ToolTier: remove legacy EnumTrait annotations and doc comment info 2023-09-08 10:02:50 +01:00
07194e3884 CS again 2023-09-07 20:33:30 +01:00
58278f22f3 Remove deprecated API usages from tests 2023-09-07 20:30:45 +01:00
7dcd2592d4 RuntimeDataDescriber: Support dynamically describing arbitrary enums (#6039)
Previously, we were using codegen to support describing a fixed set of enums.

Instead, we implement an enum() function, allowing any native PHP enum to be described.
All enums used in runtime data have been migrated to native PHP 8.1 enums in minor-next to facilitate this.

This implementation:

- is faster (in extreme cases by 40x, such as with PotionType)
- requires way less code
- does not require a build step
- is way more flexible

This fixes #5877, increasing the range of stuff that plugins are now able to do.

EnumTrait enums are not supported, as it's easier and cleaner to just support native enums. Most core EnumTrait enums have been migrated to native enums by now to facilitate this.
2023-09-07 20:07:14 +01:00
6887fcd590 RuntimeEnum(De)SerializerTrait no longer uses legacy accessors for enum members 2023-09-07 19:40:14 +01:00
c168818311 missed one 2023-09-07 19:39:13 +01:00
b50efbc15a Fix CS 2023-09-07 19:38:29 +01:00
94d98fb5c4 Migrate all but two remaining legacy enums to native PHP 8.1 enums 2023-09-07 19:32:45 +01:00
ae564e445d Start migrating EnumTrait enums to PHP 8.1 native enums 2023-09-07 17:20:52 +01:00
277 changed files with 4950 additions and 4689 deletions

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.26.0
uses: shivammathur/setup-php@2.27.1
with:
php-version: 8.1

View File

@ -20,7 +20,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.26.0
uses: shivammathur/setup-php@2.27.1
with:
php-version: ${{ matrix.php-version }}

View File

@ -152,9 +152,6 @@ jobs:
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Regenerate RuntimeEnum(De)serializer
run: php build/generate-runtime-enum-serializers.php
- name: Regenerate BedrockData available files constants
run: php build/generate-bedrockdata-path-consts.php
@ -176,7 +173,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.26.0
uses: shivammathur/setup-php@2.27.1
with:
php-version: 8.1
tools: php-cs-fixer:3.17

View File

@ -88,45 +88,58 @@ Depending on the changes, maintainers might ask you to make changes to the PR to
### Requirements
The following are required as a minimum for pull requests. PRs that don't meet these requirements will be declined unless updated to meet them.
#### Licensing
PocketMine-MP is licensed under [LGPLv3 license](LICENSE).
By proposing a pull request, you agree to your code being distributed within PocketMine-MP under the same license.
If you take code from other projects, that code MUST be licensed under an LGPL-compatible license.
#### PRs should be about exactly ONE thing
If you want to make multiple changes, those changes should each be contributed as separate pull requests. **DO NOT** mix unrelated changes.
#### PRs must not include unnecessary/unrelated changes
Do not include changes which aren't strictly necessary. This makes it harder to review a PR, because the code diff becomes larger and harder to review.
This means:
- don't reformat or rearrange existing code
- don't change things that aren't related to the PR's objective
- don't rewrite existing code just to make it "look nicer"
- don't change PhpDocs to native types in code you didn't write
#### Tests must be provided
Where possible, PHPUnit tests should be written for new or changed code.
If that's not possible (e.g. for in-game functionality), the code must be tested manually and details of the tests done must be provided.
**Simply saying "Tested" is not acceptable** and will lead to your PR being declined.
#### Comments and documentation must be written in American English
English is the shared languages of all current maintainers.
#### Code must be in the PocketMine-MP style
It's your responsibility to ensure your code matches the formatting and styling of the rest of the code.
If you use PhpStorm, a `Project` code style is provided, which you can use to automatically format new code.
You can also use [`php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to format your code.
- **All code must be licensed under the [LGPLv3 license](LICENSE)** as per PocketMine-MP's own license, or a compatible license.
- By proposing a pull request, you agree to your code being distributed within PocketMine-MP under the same license.
- If you take code from other projects, that code MUST be licensed under an LGPL-compatible license.
- **PRs should be about ONE thing**
- If you want to make multiple changes, those changes should each be contributed as separate pull requests. **DO NOT** mix unrelated changes.
- **Do not include unnecessary changes.** This makes the code diff larger and more noisy, making it harder to review.
- Don't change things that aren't related to the PR's objective
- Don't reformat or rearrange existing code without a good reason related to the PR's objective
- Don't rewrite existing code just to make it "look nicer"
- Don't change PhpDocs to native types in code you didn't write, unless that's the objective of the PR
- **Test code changes, and tell us what tests have been done.**
- Where possible, PHPUnit tests should be written for new or changed code. If that's not possible (e.g. for in-game functionality), the code must be tested manually and details of the tests done must be provided.
- **Simply saying "Tested" is not acceptable** and could lead to your PR being declined.
- **Code, comments and documentation must be written in American English.** English is the shared languages of all current maintainers.
- **Code must be in the PocketMine-MP style.**
- It's your responsibility to ensure your code matches the formatting and styling of the rest of the code.
- If you use PhpStorm, a `Project` code style is provided, which you can use to automatically format new code.
- You can also use [`php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) to format your code.
- **Use `final` and `private` wherever possible**.
- Changing from `private` to `protected` or `final` to non-`final` doesn't break backwards compatibility, but the opposite does.
- `private` and `final` also enable certain performance optimizations which are otherwise not possible.
- `private` members can be freely changed, added and removed in the future, so it's ideal for internal functions. Abusing `protected` makes internal improvements inconvenient.
- "Let's leave it protected/public in case someone needs it for ... idk what" is **not a valid reason to expose things**. If there isn't a clear reason for something to be accessible from the outside, don't expose it.
- **This is a lesson learned through years of experience.** You may not like it, but it's for the best.
- **Immutable things are almost always preferred.**
- Do not add unnecessary setters or public writable properties to classes. As above, "Let's leave it in case someone needs it" is **not a valid reason to expose things**.
- Mutable classes and properties are unpredictable, since code has no way to know if the object it's working with might be randomly modified by another part of the code. This makes it harder to maintain code and debug issues.
- Most classes exist only to hold some data. These are called "data transfer objects" (DTOs). These types of classes should pretty much always be immutable.
- Make use of `final`, `private` and `readonly` modifiers.
### Recommendations
- **Be patient.** Reviewing pull requests takes a lot of time and energy, and maintainers are often unavailable or busy. Your PR might not receive attention for a while.
- 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.
- **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.
- **Do not make large pull requests without an RFC.**
- Large changes should be discussed beforehand using the [RFC / Change Proposal](#rfcs--change-proposals) process.
- Large changes are much harder to review, and are more likely to be declined if maintainers don't have a good idea what you're trying to do in advance.
- **Create a new branch on your fork for each pull request.** This allows you to use the same fork to make multiple pull requests at the same time.
- **Make your PR diff as small as possible.** Smaller PRs are **much more likely** to be accepted, as they are easier to review.
- Avoid moving code around in files if possible.
- Don't make random CS changes. This makes the diff noisier and harder to review.
- **Use descriptive commit titles.** You can see an example [here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- **Do not include multiple unrelated changes in one commit.** An atomic style for commits is preferred - this means that changes included in a commit should be part of a single distinct change set. See [this link](https://www.freshconsulting.com/atomic-commits/) for more information on atomic commits. See the [documentation on `git add`](https://git-scm.com/docs/git-add) for information on how to isolate local changes for committing.
- **Your pull request will be checked and discussed in due time.** Since the team is scattered all around the world, your PR may not receive any attention for some time.
- **Do not make large pull requests without an RFC.** Large changes should be discussed beforehand using the [RFC / Change Proposal](#rfcs--change-proposals) process. Large changes are much harder to review and are more likely to be declined if maintainers don't have a good idea what you're trying to do in advance.
- **Do not copy-paste code**. There are potential license issues implicit with copy-pasting, and copy-paste usually indicates a lack of understanding of the actual code. Copy-pasted code is obvious a mile off and **any PR like this is likely to be closed**. If you want to use somebody else's code from a Git repository, **use [GIT's cherry-pick feature](https://git-scm.com/docs/git-cherry-pick)** to cherry-pick the commit.
- **Split unrelated changes into multiple commits.**
- An atomic style for commits is preferred - this means that changes included in a commit should be part of a single distinct change set.
- If you need to use "and" or "multiple changes" in your commit message, the commit probably needs to be split up. There are exceptions, but this is a good rule of thumb.
- See [this link](https://www.freshconsulting.com/atomic-commits/) for more information on atomic commits.
- See the [documentation on `git add -i` or `git add -p`](https://git-scm.com/docs/git-add) for information on how to split up local changes for committing.
**Thanks for contributing to PocketMine-MP!**

View File

@ -1,265 +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\build\generate_runtime_enum_serializers;
use pocketmine\block\utils\BellAttachmentType;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DirtType;
use pocketmine\block\utils\DripleafState;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FroglightType;
use pocketmine\block\utils\LeverFacing;
use pocketmine\block\utils\MobHeadType;
use pocketmine\block\utils\MushroomBlockType;
use pocketmine\block\utils\SlabType;
use pocketmine\item\MedicineType;
use pocketmine\item\PotionType;
use pocketmine\item\SuspiciousStewType;
use function array_key_first;
use function array_keys;
use function array_map;
use function ceil;
use function count;
use function dirname;
use function file_put_contents;
use function implode;
use function ksort;
use function lcfirst;
use function log;
use function ob_get_clean;
use function ob_start;
use const SORT_STRING;
require dirname(__DIR__) . '/vendor/autoload.php';
/**
* @param string[] $memberNames
* @phpstan-param list<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildWriterFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
$bits = getBitsRequired($memberNames);
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$this->writeInt($bits, match(\$value){";
foreach($memberNames as $key => $memberName){
$lines[] = "\t\t$memberName => $key,";
}
$lines[] = "\t\tdefault => throw new \pocketmine\utils\AssumptionFailedError(\"All $virtualTypeName cases should be covered\")";
$lines[] = "\t});";
$lines[] = "}";
return $lines;
}
/**
* @param string[] $memberNames
* @phpstan-param list<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildReaderFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
$bits = getBitsRequired($memberNames);
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$value = match(\$this->readInt($bits)){";
foreach($memberNames as $key => $memberName){
$lines[] = "\t\t$key => $memberName,";
}
$lines[] = "\t\tdefault => throw new InvalidSerializedRuntimeDataException(\"Invalid serialized value for $virtualTypeName\")";
$lines[] = "\t};";
$lines[] = "}";
return $lines;
}
function buildInterfaceFunc(string $nativeTypeName, string $functionName) : string{
return "public function $functionName(\\$nativeTypeName &\$value) : void;";
}
/**
* @param string[] $memberNames
* @phpstan-param list<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildSizeCalculationFunc(string $nativeTypeName, string $functionName, array $memberNames) : array{
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$this->addBits(" . getBitsRequired($memberNames) . ");";
$lines[] = "}";
return $lines;
}
/**
* @param mixed[] $members
*/
function getBitsRequired(array $members) : int{
return (int) ceil(log(count($members), 2));
}
/**
* @param object[] $members
* @phpstan-param array<string, object> $members
*
* @return string[]
* @phpstan-return list<string>
*/
function stringifyEnumMembers(array $members, string $enumClass) : array{
ksort($members, SORT_STRING);
return array_map(fn(string $enumCaseName) => "\\$enumClass::$enumCaseName()", array_keys($members));
}
$enumsUsed = [
BellAttachmentType::getAll(),
CopperOxidation::getAll(),
CoralType::getAll(),
DirtType::getAll(),
DripleafState::getAll(),
DyeColor::getAll(),
FroglightType::getAll(),
LeverFacing::getAll(),
MedicineType::getAll(),
MushroomBlockType::getAll(),
MobHeadType::getAll(),
SlabType::getAll(),
SuspiciousStewType::getAll(),
PotionType::getAll()
];
$readerFuncs = [
"" => [
"abstract protected function readInt(int \$bits) : int;"
]
];
$writerFuncs = [
"" => [
"abstract protected function writeInt(int \$bits, int \$value) : void;"
]
];
$interfaceFuncs = [];
$sizeCalculationFuncs = [
"" => [
"abstract protected function addBits(int \$bits) : void;"
]
];
foreach($enumsUsed as $enumMembers){
if(count($enumMembers) === 0){
throw new \InvalidArgumentException("Enum members cannot be empty");
}
$reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]);
$virtualTypeName = $reflect->getShortName();
$nativeTypeName = $reflect->getName();
$functionName = lcfirst($virtualTypeName);
$stringifiedMembers = stringifyEnumMembers($enumMembers, $nativeTypeName);
$writerFuncs[$functionName] = buildWriterFunc(
$virtualTypeName,
$nativeTypeName,
$stringifiedMembers,
$functionName
);
$readerFuncs[$functionName] = buildReaderFunc(
$virtualTypeName,
$nativeTypeName,
$stringifiedMembers,
$functionName
);
$interfaceFuncs[$functionName] = [buildInterfaceFunc(
$nativeTypeName,
$functionName
)];
$sizeCalculationFuncs[$functionName] = buildSizeCalculationFunc(
$nativeTypeName,
$functionName,
$stringifiedMembers
);
}
/**
* @param string[][] $functions
* @phpstan-param array<string, list<string>> $functions
*/
function printFunctions(array $functions, string $className, string $classType) : void{
ksort($functions, SORT_STRING);
ob_start();
echo <<<'HEADER'
<?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\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
*/
HEADER;
echo "$classType $className{\n\n";
echo implode("\n\n", array_map(fn(array $functionLines) => "\t" . implode("\n\t", $functionLines), $functions));
echo "\n\n}\n";
file_put_contents(dirname(__DIR__) . '/src/data/runtime/' . $className . '.php', ob_get_clean());
}
printFunctions($writerFuncs, "RuntimeEnumSerializerTrait", "trait");
printFunctions($readerFuncs, "RuntimeEnumDeserializerTrait", "trait");
printFunctions($interfaceFuncs, "RuntimeEnumDescriber", "interface");
printFunctions($sizeCalculationFuncs, "RuntimeEnumSizeCalculatorTrait", "trait");
echo "Done. Don't forget to run CS fixup after generating code.\n";

16
changelogs/4.25.md Normal file
View File

@ -0,0 +1,16 @@
# 4.25.0
Released 26th October 2023.
**For Minecraft: Bedrock Edition 1.20.40**
This is a support release for Minecraft: Bedrock Edition 1.20.40.
**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.20.40.
- Removed support for older versions.

27
changelogs/5.7.md Normal file
View File

@ -0,0 +1,27 @@
# 5.7.0
Released 26th October 2023.
**For Minecraft: Bedrock Edition 1.20.40**
This is a support release for Minecraft: Bedrock Edition 1.20.40.
**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.20.40.
- Removed support for older versions.
## Fixes
- Fixed `cartography_table`, `smithing_table`, `stripped_cherry_log` and `stripped_cherry_wood` not working in `StringToItemParser`.
- Fixed `Promise<null>::onCompletion()` always calling the reject handler if the promise was already completed.
# 5.7.1
Released 1st November 2023.
## Fixes
- Fixed non-reentrant-safe code in `PermissionManager` and various other subscriber subsystems.
- These issues caused server crashes when deleting a subscriber indirectly triggered the deletion of other subscribers (e.g. due to the GC activating in `unset()`).

138
changelogs/5.8.md Normal file
View File

@ -0,0 +1,138 @@
# 5.8.0
Released 1st November 2023.
**Borked release, forgot to merge branches.**
# 5.8.1
Released 1st November 2023.
**For Minecraft: Bedrock Edition 1.20.40**
This is a minor feature release, including new gameplay features, various performance improvements to internal `World` and `Block` systems, and changes to the API.
**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
- Neighbour block updates now have a separate timer for timings. Previously, these were counted under `Scheduled Block Updates`, which was misleading.
## Performance
- `LightUpdate` now avoids attempting to propagate back in the same direction the light came from. This produces a small performance improvement of around 6% in light propagation.
- Improved worst-case (non-cached) performance of `World::getCollisionBlocks()` (and its successor `World::getBlockCollisionBlocks()`).
- While 5.5.0 introduced caching at the `World` level for AABBs, the cache was rarely useful due to entity and player movement being too unpredictable. This meant that most users saw a performance degradation with lots of moving entities, except in specific situations.
- Performance for fetching non-cached AABBs for a cell is now improved by 2x. Overall performance benefit to a server depends on the number of entities and players.
- Added cache for hydrated farmland blocks to remember the last known location of nearby water.
- If nearby water sources are not changed, this cache allows hydrated farmland to completely avoid checking up to 161 nearby blocks for water after the first check.
- Tests with large wheat farms showed a 25% performance improvement in overall server performance compared to previous 5.x versions.
- Migrated various internal enums to native PHP 8.1 enums. Bypassing magic `__callStatic()` accessors improved performance in many areas, although it's hard to quantify the exact benefit.
- Made use of `Facing::OFFSET` constant in various places to avoid unnecessary `Vector3` and `Position` object allocations. Many pathways benefit from this, including neighbour block updates (due to faster `Block::getSide()` and less useless objects).
- Avoided clearing block AABB caches except when strictly necessary. Previously, the cache was wiped every time blocks were read from the world, making them mostly useless.
- Avoided random updates on blocks which have reached their final state, such as fully-grown crops. This produces a minimal performance improvement.
- Removed useless checks in some `World` hot paths.
## API
### General
- All enums have been migrated to native PHP 8.1 enums.
- For now, the old APIs and accessors are still usable (via `LegacyEnumShimTrait`), but these will be removed in the next major release.
- `EnumTrait` has been deprecated, and will be removed in the next major release.
- Migration for most plugin developers will simply involve deleting `()` from the end of enum case usages, which is a trivial change and also improves performance.
- Plugin usages of `EnumTrait` are encouraged to move to native enums, optionally using `LegacyEnumShimTrait` to provide backwards compatibility.
- See [this code](https://github.com/pmmp/PocketMine-MP/blob/9832fe899f13a8ea47cc9d73de7088f7775a12f5/src/block/utils/DyeColor.php#L85-L107) for an example of how to associate properties with enum cases (since native enums don't support this directly).
- Thanks to improvements in `RuntimeDataDescriber`, any native enum can now be used as a custom block's state property.
### `pocketmine\block`
- The following classes have been added:
- `utils\AgeableTrait` - used by blocks which have an age property, such as crops
- `utils\StaticSupportTrait` - used by blocks which have the same support requirements regardless of their state, such as crops
### `pocketmine\data\runtime`
- The following API methods have been added:
- `public RuntimeDataDescriber->boundedIntAuto(int $min, int $max, int &$value) : void` - similar to `boundedInt()`, but automatically calculates the needed number of bits based on the given min/max
- `public RuntimeDataDescriber->enum(T extends \UnitEnum &$case) : void` - describes any native PHP 8.1 enum case
- `public RuntimeDataDescriber->enumSet(array<int, T extends \UnitEnum> &$set, array<int, T extends \UnitEnum> $allCases) : void` - describes a set of enum cases (similar to bitflags)
- The following API methods have been deprecated:
- `RuntimeDataDescriber->bellAttachmentType()` - use `enum()` instead
- `RuntimeDataDescriber->boundedInt()` - use `boundedIntAuto()` instead
- `RuntimeDataDescriber->brewingStandSlots()` - use `enumSet()` instead
- `RuntimeDataDescriber->copperOxidation()` - use `enum()` instead
- `RuntimeDataDescriber->coralType()` - use `enum()` instead
- `RuntimeDataDescriber->dirtType()` - use `enum()` instead
- `RuntimeDataDescriber->dripleafState()` - use `enum()` instead
- `RuntimeDataDescriber->dyeColor()` - use `enum()` instead
- `RuntimeDataDescriber->froglightType()` - use `enum()` instead
- `RuntimeDataDescriber->leverFacing()` - use `enum()` instead
- `RuntimeDataDescriber->medicineType()` - use `enum()` instead
- `RuntimeDataDescriber->mobHeadType()` - use `enum()` instead
- `RuntimeDataDescriber->mushroomBlockType()` - use `enum()` instead
- `RuntimeDataDescriber->potionType()` - use `enum()` instead
- `RuntimeDataDescriber->slabType()` - use `enum()` instead
- `RuntimeDataDescriber->suspiciousStewType()` - use `enum()` instead
### `pocketmine\player`
- `TitleID` is now included in `PlayerInfo` metadata for plugin consumption.
### `pocketmine\world`
- The following API methods have been added:
- `public World->getBlockCollisionBoxes(AxisAlignedBB $bb) : list<AxisAlignedBB>` - similar to `getCollisionBoxes` but exclusively for blocks, avoiding the need for conditionally useless parameters
- The following API methods have been deprecated:
- `World->getCollisionBoxes()` - use `getBlockCollisionBoxes()` instead (alongside `getCollidingEntities()` if entity collision boxes are also required)
### `pocketmine\utils`
- The following classes have been deprecated:
- `EnumTrait` - use native PHP 8.1 enums instead
- The following classes have been added:
- `LegacyEnumShimTrait` - can be `use`d by native PHP 8.1 enums to provide the same API as `EnumTrait`
## Gameplay
### Blocks
- Implemented the following blocks:
- Amethyst
- Amethyst Cluster
- Chiseled Bookshelf
- Crimson Roots
- Pitcher Crop
- Pitcher Plant
- Torchflower
- Torchflower Crop
- Warped Roots
### Items
- Implemented the following items:
- Pitcher Pod
- Torchflower Seeds
## Internals
- `Farmland` block now has an extra property (`waterPositionIndex`) stored in its blockstate ID to track the position of nearby water. This property is not saved to disk, and is only used for caching.
- The format of internal blockstate ID has been updated.
- The lower `11` bits are now reserved for state data (previously `8` bits). This increase facilitates the new cache for `Farmland` blocks.
- The state data bits are now XOR'd with the `xxh3` of the block's type ID, instead of being directly XOR'd with the type ID.
- This XOR improves the distribution of the lower bits of the blockstate ID, which is important for hashtable indexing to minimize collisions.
- Previously, the lower bits were XOR'd with the type ID directly, but the effectiveness of this reduced as more state data bits were added.
- `xxh3` produces consistently good results for this purpose regardless of the number of state data bits allocated.
- Hash collisions with blockstate IDs are reduced by 50% with this change, which is a happy side effect.
- This is backwards-incompatible, so anyone saving internal blockstate IDs on disk or in a DB will be burned by this change (though they shouldn't have been doing that anyway).
- Removed code generation step for `RuntimeDataDescriber` enum serialization. All described enums now use PHP 8.1 native enums, which can be described without codegen using `RuntimeDataDescriber->enum()`.
- Added `DeprecatedLegacyEnumAccessRule` custom PHPStan rule to flag legacy `EnumTrait` case accessors.
- Cleaned up remaining hardcoded `Config` keys in `SetupWizard`. These usages now use auto-generated constants like the rest of the codebase.
# 5.8.2
Released 9th November 2023.
## Performance
- Improved performance of small packet zero-compression (unintended use of slow zlib compressor instead of fast libdeflate one).
- This affected the majority of outbound packets, as most packets are below the 256-byte threshold for compression.
- This faster method is over 20x faster than the old method, producing noticeable performance gains for large servers.
## Fixes
- Fixed melons and pumpkins not growing.
- Fixed melon and pumpkin stems not attaching to the grown melon/pumpkin.
- Fixed iron and gold ores not being affected by the Fortune enchantment.
- Fixed ancient debris burning in lava.
- Fixed sign (front) text loading from vanilla world saves (back text is not yet supported).
## Internals
- Removed bogus optimization from `tools/generate-blockstate-upgrade-schema.php` that could cause incorrect `remappedStates` generation when some of the states stayed under the old ID.
- Fixed possible crash in `BlockStateUpgrader` name flattening rule handling with invalid blockstate NBT data.

View File

@ -33,10 +33,10 @@
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0",
"pocketmine/netresearch-jsonmapper": "~v4.2.1000",
"pocketmine/bedrock-block-upgrade-schema": "~3.2.0+bedrock-1.20.30",
"pocketmine/bedrock-data": "~2.5.0+bedrock-1.20.30",
"pocketmine/bedrock-block-upgrade-schema": "~3.3.0+bedrock-1.20.40",
"pocketmine/bedrock-data": "~2.6.0+bedrock-1.20.40",
"pocketmine/bedrock-item-upgrade-schema": "~1.5.0+bedrock-1.20.30",
"pocketmine/bedrock-protocol": "~24.0.0+bedrock-1.20.30",
"pocketmine/bedrock-protocol": "~25.0.0+bedrock-1.20.40",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
@ -52,7 +52,7 @@
"symfony/filesystem": "~6.3.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.39",
"phpstan/phpstan": "1.10.41",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "~10.3.0 || ~10.2.0 || ~10.1.0"

64
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6c48ad06b67c3aa3890f004b197d17bc",
"content-hash": "5c19f4766fd04be0cbd38d9f4681864e",
"packages": [
{
"name": "adhocore/json-comment",
@ -122,16 +122,16 @@
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "3.2.0",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "874e1c0c9b7b620744d08b59c78354fe9f028dfa"
"reference": "ee46b9367af262bbddd9f122d4d5b5b495b892e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/874e1c0c9b7b620744d08b59c78354fe9f028dfa",
"reference": "874e1c0c9b7b620744d08b59c78354fe9f028dfa",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/ee46b9367af262bbddd9f122d4d5b5b495b892e7",
"reference": "ee46b9367af262bbddd9f122d4d5b5b495b892e7",
"shasum": ""
},
"type": "library",
@ -142,22 +142,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/3.2.0"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/3.3.0"
},
"time": "2023-09-20T17:03:43+00:00"
"time": "2023-10-16T16:11:02+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "2.5.1+bedrock-1.20.30",
"version": "2.6.0+bedrock-1.20.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "8f9d96047a731c37b18b28c2bfcdfa2705bb303f"
"reference": "37e780d28b470230bda3579b04cb50d406e3fbe6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/8f9d96047a731c37b18b28c2bfcdfa2705bb303f",
"reference": "8f9d96047a731c37b18b28c2bfcdfa2705bb303f",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/37e780d28b470230bda3579b04cb50d406e3fbe6",
"reference": "37e780d28b470230bda3579b04cb50d406e3fbe6",
"shasum": ""
},
"type": "library",
@ -168,9 +168,9 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/2.5.1+bedrock-1.20.30"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.20.40"
},
"time": "2023-09-27T11:40:15+00:00"
"time": "2023-10-26T10:39:13+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
@ -200,16 +200,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "24.0.0+bedrock-1.20.30",
"version": "25.0.0+bedrock-1.20.40",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "38a516274aa6641b0da38011af35a5587fc87895"
"reference": "69c36c96f6835e93fc278071aa2bb9829abe5cf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/38a516274aa6641b0da38011af35a5587fc87895",
"reference": "38a516274aa6641b0da38011af35a5587fc87895",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/69c36c96f6835e93fc278071aa2bb9829abe5cf8",
"reference": "69c36c96f6835e93fc278071aa2bb9829abe5cf8",
"shasum": ""
},
"require": {
@ -241,9 +241,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/24.0.0+bedrock-1.20.30"
"source": "https://github.com/pmmp/BedrockProtocol/tree/25.0.0+bedrock-1.20.40"
},
"time": "2023-09-20T16:57:53+00:00"
"time": "2023-10-26T11:03:10+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -1378,16 +1378,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.39",
"version": "1.10.41",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4"
"reference": "c6174523c2a69231df55bdc65b61655e72876d76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4",
"reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76",
"reference": "c6174523c2a69231df55bdc65b61655e72876d76",
"shasum": ""
},
"require": {
@ -1436,7 +1436,7 @@
"type": "tidelift"
}
],
"time": "2023-10-17T15:46:26+00:00"
"time": "2023-11-05T12:57:57+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -1492,21 +1492,21 @@
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.5.1",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6"
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6",
"reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542",
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10"
"phpstan/phpstan": "^1.10.34"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@ -1535,9 +1535,9 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2"
},
"time": "2023-03-29T14:47:40+00:00"
"time": "2023-10-30T14:35:06+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -2964,5 +2964,5 @@
"platform-overrides": {
"php": "8.1.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View File

@ -10,6 +10,7 @@ includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
rules:
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule

View File

@ -144,6 +144,13 @@ namespace pocketmine {
$messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version.";
}
if(($libdeflate_version = phpversion("libdeflate")) !== false){
//make sure level 0 compression is available
if(version_compare($libdeflate_version, "0.2.0") < 0 || version_compare($libdeflate_version, "0.3.0") >= 0){
$messages[] = "php-libdeflate ^0.2.0 is required, while you have $libdeflate_version.";
}
}
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}

View File

@ -390,7 +390,7 @@ class Server{
}
public function getGamemode() : GameMode{
return GameMode::fromString($this->configGroup->getConfigString(ServerProperties::GAME_MODE, GameMode::SURVIVAL()->name())) ?? GameMode::SURVIVAL();
return GameMode::fromString($this->configGroup->getConfigString(ServerProperties::GAME_MODE)) ?? GameMode::SURVIVAL;
}
public function getForceGamemode() : bool{
@ -818,7 +818,7 @@ class Server{
ServerProperties::ENABLE_IPV6 => true,
ServerProperties::WHITELIST => false,
ServerProperties::MAX_PLAYERS => self::DEFAULT_MAX_PLAYERS,
ServerProperties::GAME_MODE => GameMode::SURVIVAL()->name(),
ServerProperties::GAME_MODE => GameMode::SURVIVAL->name, //TODO: this probably shouldn't use the enum name directly
ServerProperties::FORCE_GAME_MODE => false,
ServerProperties::HARDCORE => false,
ServerProperties::PVP => true,
@ -1020,7 +1020,7 @@ class Server{
$this->forceShutdownExit();
return;
}
if(!$this->enablePlugins(PluginEnableOrder::STARTUP())){
if(!$this->enablePlugins(PluginEnableOrder::STARTUP)){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
$this->forceShutdownExit();
return;
@ -1031,7 +1031,7 @@ class Server{
return;
}
if(!$this->enablePlugins(PluginEnableOrder::POSTWORLD())){
if(!$this->enablePlugins(PluginEnableOrder::POSTWORLD)){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_plugin_someEnableErrors()));
$this->forceShutdownExit();
return;
@ -1259,9 +1259,10 @@ class Server{
*/
public function unsubscribeFromBroadcastChannel(string $channelId, CommandSender $subscriber) : void{
if(isset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)])){
unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]);
if(count($this->broadcastSubscribers[$channelId]) === 0){
if(count($this->broadcastSubscribers[$channelId]) === 1){
unset($this->broadcastSubscribers[$channelId]);
}else{
unset($this->broadcastSubscribers[$channelId][spl_object_id($subscriber)]);
}
}
}
@ -1386,14 +1387,14 @@ class Server{
public function enablePlugins(PluginEnableOrder $type) : bool{
$allSuccess = true;
foreach($this->pluginManager->getPlugins() as $plugin){
if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder()->equals($type)){
if(!$plugin->isEnabled() && $plugin->getDescription()->getOrder() === $type){
if(!$this->pluginManager->enablePlugin($plugin)){
$allSuccess = false;
}
}
}
if($type->equals(PluginEnableOrder::POSTWORLD())){
if($type === PluginEnableOrder::POSTWORLD){
$this->commandMap->registerServerAliases();
}

View File

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

View File

@ -0,0 +1,133 @@
<?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\utils\AmethystTrait;
use pocketmine\block\utils\AnyFacingTrait;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
final class AmethystCluster extends Transparent{
use AmethystTrait;
use AnyFacingTrait;
public const STAGE_SMALL_BUD = 0;
public const STAGE_MEDIUM_BUD = 1;
public const STAGE_LARGE_BUD = 2;
public const STAGE_CLUSTER = 3;
private int $stage = self::STAGE_CLUSTER;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->boundedIntAuto(self::STAGE_SMALL_BUD, self::STAGE_CLUSTER, $this->stage);
}
public function getStage() : int{ return $this->stage; }
public function setStage(int $stage) : self{
if($stage < self::STAGE_SMALL_BUD || $stage > self::STAGE_CLUSTER){
throw new \InvalidArgumentException("Size must be in range " . self::STAGE_SMALL_BUD . " ... " . self::STAGE_CLUSTER);
}
$this->stage = $stage;
return $this;
}
public function getLightLevel() : int{
return match($this->stage){
self::STAGE_SMALL_BUD => 1,
self::STAGE_MEDIUM_BUD => 2,
self::STAGE_LARGE_BUD => 4,
self::STAGE_CLUSTER => 5,
default => throw new AssumptionFailedError("Invalid stage $this->stage"),
};
}
protected function recalculateCollisionBoxes() : array{
$myAxis = Facing::axis($this->facing);
$box = AxisAlignedBB::one();
foreach([Axis::Y, Axis::Z, Axis::X] as $axis){
if($axis === $myAxis){
continue;
}
$box->squash($axis, $this->stage === self::STAGE_SMALL_BUD ? 4 / 16 : 3 / 16);
}
$box->trim($this->facing, 1 - ($this->stage === self::STAGE_CLUSTER ? 7 / 16 : ($this->stage + 3) / 16));
return [$box];
}
private function canBeSupportedAt(Block $block, int $facing) : bool{
return $block->getAdjacentSupportType($facing) === SupportType::FULL;
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
return false;
}
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this, Facing::opposite($this->facing))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
if($this->stage === self::STAGE_CLUSTER){
return [VanillaItems::AMETHYST_SHARD()->setCount(FortuneDropHelper::weighted($item, min: 4, maxBase: 4))];
}
return [];
}
public function getDropsForIncompatibleTool(Item $item) : array{
if($this->stage === self::STAGE_CLUSTER){
return [VanillaItems::AMETHYST_SHARD()->setCount(FortuneDropHelper::weighted($item, min: 2, maxBase: 2))];
}
return [];
}
}

View File

@ -52,7 +52,7 @@ class Anvil extends Transparent implements Fallable{
private int $damage = self::UNDAMAGED;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, self::UNDAMAGED, self::VERY_DAMAGED, $this->damage);
$w->boundedIntAuto(self::UNDAMAGED, self::VERY_DAMAGED, $this->damage);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@ -78,7 +78,7 @@ class Anvil extends Transparent implements Fallable{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
@ -46,6 +47,7 @@ use function mt_rand;
use const PHP_INT_MAX;
class Bamboo extends Transparent{
use StaticSupportTrait;
public const NO_LEAVES = 0;
public const SMALL_LEAVES = 1;
@ -56,7 +58,7 @@ class Bamboo extends Transparent{
protected int $leafSize = self::NO_LEAVES;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, self::NO_LEAVES, self::LARGE_LEAVES, $this->leafSize);
$w->boundedIntAuto(self::NO_LEAVES, self::LARGE_LEAVES, $this->leafSize);
$w->bool($this->thick);
$w->bool($this->ready);
}
@ -95,7 +97,7 @@ class Bamboo extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
private static function getOffsetSeed(int $x, int $y, int $z) : int{
@ -120,12 +122,14 @@ class Bamboo extends Transparent{
return new Vector3($retX, 0, $retZ);
}
private function canBeSupportedBy(Block $block) : bool{
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
return
$block->getTypeId() === BlockTypeIds::GRAVEL ||
$block->hasTypeTag(BlockTypeTags::DIRT) ||
$block->hasTypeTag(BlockTypeTags::MUD) ||
$block->hasTypeTag(BlockTypeTags::SAND);
$supportBlock->hasSameTypeId($this) ||
$supportBlock->getTypeId() === BlockTypeIds::GRAVEL ||
$supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
$supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
$supportBlock->hasTypeTag(BlockTypeTags::SAND);
}
private function seekToTop() : Bamboo{
@ -153,14 +157,6 @@ class Bamboo extends Transparent{
return false;
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
$below = $world->getBlock($this->position->down());
if(!$this->canBeSupportedBy($below) && !$below->hasSameTypeId($this)){
$world->useBreakOn($this->position);
}
}
private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -23,17 +23,21 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Bamboo as ItemBamboo;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class BambooSapling extends Flowable{
use StaticSupportTrait;
private bool $ready = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@ -48,19 +52,13 @@ final class BambooSapling extends Flowable{
return $this;
}
private function canBeSupportedBy(Block $block) : bool{
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
return
$block->getTypeId() === BlockTypeIds::GRAVEL ||
$block->hasTypeTag(BlockTypeTags::DIRT) ||
$block->hasTypeTag(BlockTypeTags::MUD) ||
$block->hasTypeTag(BlockTypeTags::SAND);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedBy($blockReplace->position->getWorld()->getBlock($blockReplace->position->down()))){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
$supportBlock->getTypeId() === BlockTypeIds::GRAVEL ||
$supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
$supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
$supportBlock->hasTypeTag(BlockTypeTags::SAND);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@ -73,13 +71,6 @@ final class BambooSapling extends Flowable{
return false;
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if(!$this->canBeSupportedBy($world->getBlock($this->position->down()))){
$world->useBreakOn($this->position);
}
}
private function grow(?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){

View File

@ -26,7 +26,6 @@ namespace pocketmine\block;
use pocketmine\block\tile\Banner as TileBanner;
use pocketmine\block\utils\BannerPatternLayer;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Banner as ItemBanner;
use pocketmine\item\Item;
@ -48,11 +47,6 @@ abstract class BaseBanner extends Transparent{
*/
protected array $patterns = [];
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::BLACK();
parent::__construct($idInfo, $name, $typeInfo);
}
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$tile = $this->position->getWorld()->getTile($this->position);
@ -111,7 +105,7 @@ abstract class BaseBanner extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
private function canBeSupportedBy(Block $block) : bool{

View File

@ -131,6 +131,6 @@ abstract class BaseBigDripleaf extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\FoodSource;
@ -31,27 +32,16 @@ use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
abstract class BaseCake extends Transparent implements FoodSource{
use StaticSupportTrait;
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getTypeId() !== BlockTypeIds::AIR){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::AIR){ //Replace with common break method
$this->position->getWorld()->useBreakOn($this->position);
}
private function canBeSupportedAt(Block $block) : bool{
return $block->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::AIR;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
@ -33,11 +32,6 @@ use function mt_rand;
abstract class BaseCoral extends Transparent{
use CoralTypeTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->coralType = CoralType::TUBE();
parent::__construct($idInfo, $name, $typeInfo);
}
public function onNearbyBlockChange() : void{
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));
@ -78,6 +72,6 @@ abstract class BaseCoral extends Transparent{
protected function recalculateCollisionBoxes() : array{ return []; }
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -103,7 +103,7 @@ abstract class BaseSign extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
abstract protected function getSupportingFace() : int;
@ -172,13 +172,13 @@ abstract class BaseSign extends Transparent{
}
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
ItemTypeIds::BONE_MEAL => DyeColor::WHITE(),
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE(),
ItemTypeIds::COCOA_BEANS => DyeColor::BROWN(),
ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
ItemTypeIds::COCOA_BEANS => DyeColor::BROWN,
default => null
};
if($dyeColor !== null){
$color = $dyeColor->equals(DyeColor::BLACK()) ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
$color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
if(
$color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
$this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)

View File

@ -48,11 +48,6 @@ class Bed extends Transparent{
protected bool $occupied = false;
protected bool $head = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::RED();
parent::__construct($idInfo, $name, $typeInfo);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->bool($this->occupied);
@ -65,6 +60,8 @@ class Bed extends Transparent{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileBed){
$this->color = $tile->getColor();
}else{
$this->color = DyeColor::RED; //legacy pre-1.1 beds don't have tiles
}
return $this;
@ -87,7 +84,7 @@ class Bed extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function isHeadPart() : bool{
@ -209,7 +206,7 @@ class Bed extends Transparent{
}
private function canBeSupportedAt(Block $block) : bool{
return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE());
return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
public function getMaxStackSize() : int{ return 1; }

View File

@ -35,32 +35,26 @@ use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\BellRingSound;
final class Bell extends Transparent{
use HorizontalFacingTrait;
private BellAttachmentType $attachmentType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->attachmentType = BellAttachmentType::FLOOR();
parent::__construct($idInfo, $name, $typeInfo);
}
private BellAttachmentType $attachmentType = BellAttachmentType::FLOOR;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->bellAttachmentType($this->attachmentType);
$w->enum($this->attachmentType);
$w->horizontalFacing($this->facing);
}
protected function recalculateCollisionBoxes() : array{
if($this->attachmentType->equals(BellAttachmentType::FLOOR())){
if($this->attachmentType === BellAttachmentType::FLOOR){
return [
AxisAlignedBB::one()->squash(Facing::axis($this->facing), 1 / 4)->trim(Facing::UP, 3 / 16)
];
}
if($this->attachmentType->equals(BellAttachmentType::CEILING())){
if($this->attachmentType === BellAttachmentType::CEILING){
return [
AxisAlignedBB::one()->contract(1 / 4, 0, 1 / 4)->trim(Facing::DOWN, 1 / 4)
];
@ -72,12 +66,12 @@ final class Bell extends Transparent{
->trim(Facing::DOWN, 1 / 4);
return [
$this->attachmentType->equals(BellAttachmentType::ONE_WALL()) ? $box->trim($this->facing, 3 / 16) : $box
$this->attachmentType === BellAttachmentType::ONE_WALL ? $box->trim($this->facing, 3 / 16) : $box
];
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function getAttachmentType() : BellAttachmentType{ return $this->attachmentType; }
@ -89,7 +83,7 @@ final class Bell extends Transparent{
}
private function canBeSupportedAt(Block $block, int $face) : bool{
return !$block->getAdjacentSupportType($face)->equals(SupportType::NONE());
return $block->getAdjacentSupportType($face) !== SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@ -100,15 +94,15 @@ final class Bell extends Transparent{
if($player !== null){
$this->setFacing(Facing::opposite($player->getHorizontalFacing()));
}
$this->setAttachmentType(BellAttachmentType::FLOOR());
$this->setAttachmentType(BellAttachmentType::FLOOR);
}elseif($face === Facing::DOWN){
$this->setAttachmentType(BellAttachmentType::CEILING());
$this->setAttachmentType(BellAttachmentType::CEILING);
}else{
$this->setFacing($face);
$this->setAttachmentType(
$this->canBeSupportedAt($blockReplace, $face) ?
BellAttachmentType::TWO_WALLS() :
BellAttachmentType::ONE_WALL()
BellAttachmentType::TWO_WALLS :
BellAttachmentType::ONE_WALL
);
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
@ -116,11 +110,10 @@ final class Bell extends Transparent{
public function onNearbyBlockChange() : void{
foreach(match($this->attachmentType){
BellAttachmentType::CEILING() => [Facing::UP],
BellAttachmentType::FLOOR() => [Facing::DOWN],
BellAttachmentType::ONE_WALL() => [Facing::opposite($this->facing)],
BellAttachmentType::TWO_WALLS() => [$this->facing, Facing::opposite($this->facing)],
default => throw new AssumptionFailedError("All cases of BellAttachmentType must be handled")
BellAttachmentType::CEILING => [Facing::UP],
BellAttachmentType::FLOOR => [Facing::DOWN],
BellAttachmentType::ONE_WALL => [Facing::opposite($this->facing)],
BellAttachmentType::TWO_WALLS => [$this->facing, Facing::opposite($this->facing)]
} as $supportBlockDirection){
if(!$this->canBeSupportedAt($this, $supportBlockDirection)){
$this->position->getWorld()->useBreakOn($this->position);
@ -159,10 +152,9 @@ final class Bell extends Transparent{
private function isValidFaceToRing(int $faceHit) : bool{
return match($this->attachmentType){
BellAttachmentType::CEILING() => true,
BellAttachmentType::FLOOR() => Facing::axis($faceHit) === Facing::axis($this->facing),
BellAttachmentType::ONE_WALL(), BellAttachmentType::TWO_WALLS() => $faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true),
default => throw new AssumptionFailedError("All cases of BellAttachmentType must be handled")
BellAttachmentType::CEILING => true,
BellAttachmentType::FLOOR => Facing::axis($faceHit) === Facing::axis($this->facing),
BellAttachmentType::ONE_WALL, BellAttachmentType::TWO_WALLS => $faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true),
};
}
}

View File

@ -30,22 +30,16 @@ use pocketmine\entity\projectile\Projectile;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\sound\DripleafTiltDownSound;
use pocketmine\world\sound\DripleafTiltUpSound;
class BigDripleafHead extends BaseBigDripleaf{
protected DripleafState $leafState;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->leafState = DripleafState::STABLE();
parent::__construct($idInfo, $name, $typeInfo);
}
protected DripleafState $leafState = DripleafState::STABLE;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
parent::describeBlockOnlyState($w);
$w->dripleafState($this->leafState);
$w->enum($this->leafState);
}
protected function isHead() : bool{
@ -76,20 +70,20 @@ class BigDripleafHead extends BaseBigDripleaf{
private function getLeafTopOffset() : float{
return match($this->leafState){
DripleafState::STABLE(), DripleafState::UNSTABLE() => 1 / 16,
DripleafState::PARTIAL_TILT() => 3 / 16,
DripleafState::STABLE, DripleafState::UNSTABLE => 1 / 16,
DripleafState::PARTIAL_TILT => 3 / 16,
default => 0
};
}
public function onEntityInside(Entity $entity) : bool{
if(!$entity instanceof Projectile && $this->leafState->equals(DripleafState::STABLE())){
if(!$entity instanceof Projectile && $this->leafState === DripleafState::STABLE){
//the entity must be standing on top of the leaf - do not collapse if the entity is standing underneath
$intersection = AxisAlignedBB::one()
->offset($this->position->x, $this->position->y, $this->position->z)
->trim(Facing::DOWN, 1 - $this->getLeafTopOffset());
if($entity->getBoundingBox()->intersectsWith($intersection)){
$this->setTiltAndScheduleTick(DripleafState::UNSTABLE());
$this->setTiltAndScheduleTick(DripleafState::UNSTABLE);
return false;
}
}
@ -97,22 +91,21 @@ class BigDripleafHead extends BaseBigDripleaf{
}
public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
if(!$this->leafState->equals(DripleafState::FULL_TILT())){
$this->setTiltAndScheduleTick(DripleafState::FULL_TILT());
if($this->leafState !== DripleafState::FULL_TILT){
$this->setTiltAndScheduleTick(DripleafState::FULL_TILT);
$this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
}
}
public function onScheduledUpdate() : void{
if(!$this->leafState->equals(DripleafState::STABLE())){
if($this->leafState->equals(DripleafState::FULL_TILT())){
$this->position->getWorld()->setBlock($this->position, $this->setLeafState(DripleafState::STABLE()));
if($this->leafState !== DripleafState::STABLE){
if($this->leafState === DripleafState::FULL_TILT){
$this->position->getWorld()->setBlock($this->position, $this->setLeafState(DripleafState::STABLE));
$this->position->getWorld()->addSound($this->position, new DripleafTiltUpSound());
}else{
$this->setTiltAndScheduleTick(match($this->leafState->id()){
DripleafState::UNSTABLE()->id() => DripleafState::PARTIAL_TILT(),
DripleafState::PARTIAL_TILT()->id() => DripleafState::FULL_TILT(),
default => throw new AssumptionFailedError("All types should be covered")
$this->setTiltAndScheduleTick(match($this->leafState){
DripleafState::UNSTABLE => DripleafState::PARTIAL_TILT,
DripleafState::PARTIAL_TILT => DripleafState::FULL_TILT,
});
$this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
}
@ -120,7 +113,7 @@ class BigDripleafHead extends BaseBigDripleaf{
}
protected function recalculateCollisionBoxes() : array{
if(!$this->leafState->equals(DripleafState::FULL_TILT())){
if($this->leafState !== DripleafState::FULL_TILT){
return [
AxisAlignedBB::one()
->trim(Facing::DOWN, 11 / 16)

View File

@ -42,7 +42,6 @@ use pocketmine\item\enchantment\ItemEnchantmentTags;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\item\ItemBlock;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
@ -50,22 +49,26 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\Chunk;
use pocketmine\world\Position;
use pocketmine\world\World;
use function count;
use function get_class;
use function hash;
use const PHP_INT_MAX;
class Block{
public const INTERNAL_STATE_DATA_BITS = 8;
public const INTERNAL_STATE_DATA_BITS = 11;
public const INTERNAL_STATE_DATA_MASK = ~(~0 << self::INTERNAL_STATE_DATA_BITS);
/**
* @internal
* Hardcoded int is `Binary::readLong(hash('xxh3', Binary::writeLLong(BlockTypeIds::AIR), binary: true))`
* TODO: it would be much easier if we could just make this 0 or some other easy value
*/
public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (BlockTypeIds::AIR & self::INTERNAL_STATE_DATA_MASK);
public const EMPTY_STATE_ID = (BlockTypeIds::AIR << self::INTERNAL_STATE_DATA_BITS) | (-7482769108513497636 & self::INTERNAL_STATE_DATA_MASK);
protected BlockIdentifier $idInfo;
protected string $fallbackName;
@ -80,6 +83,23 @@ class Block{
private Block $defaultState;
private int $stateIdXorMask;
/**
* Computes the mask to be XOR'd with the state data.
* This is to improve distribution of the state data bits, which occupy the least significant bits of the state ID.
* Improved distribution improves PHP array performance when using the state ID as a key, as PHP arrays use some of
* the lower bits of integer keys directly without hashing.
*
* The type ID is included in the XOR mask. This is not necessary to improve distribution, but it reduces the number
* of operations required to compute the state ID (micro optimization).
*/
private static function computeStateIdXorMask(int $typeId) : int{
return
$typeId << self::INTERNAL_STATE_DATA_BITS |
(Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
}
/**
* @param string $name English name of the block type (TODO: implement translations)
*/
@ -97,6 +117,9 @@ class Block{
$this->describeBlockOnlyState($calculator);
$this->requiredBlockOnlyStateDataBits = $calculator->getBitsUsed();
$this->stateIdXorMask = self::computeStateIdXorMask($idInfo->getBlockTypeId());
//this must be done last, otherwise the defaultState could have uninitialized fields
$defaultState = clone $this;
$this->defaultState = $defaultState;
$defaultState->defaultState = $defaultState;
@ -152,13 +175,7 @@ class Block{
* {@link RuntimeBlockStateRegistry::fromStateId()}.
*/
public function getStateId() : int{
$typeId = $this->getTypeId();
//TODO: this XOR mask improves hashtable distribution, but it's only effective if the number of unique block
//type IDs is larger than the number of available state data bits. We should probably hash (e.g. using xxhash)
//the type ID to create a better mask.
//Alternatively, we could hash the whole state ID, but this is currently problematic, since we currently need
//to be able to recover the state data from the state ID because of UnknownBlock.
return ($typeId << self::INTERNAL_STATE_DATA_BITS) | ($this->encodeFullState() ^ ($typeId & self::INTERNAL_STATE_DATA_MASK));
return $this->encodeFullState() ^ $this->stateIdXorMask;
}
/**
@ -228,12 +245,6 @@ class Block{
}
}
private function decodeFullState(int $data) : void{
$reader = new RuntimeDataReader($this->requiredBlockItemStateDataBits + $this->requiredBlockOnlyStateDataBits, $data);
$this->decodeBlockItemState($reader->readInt($this->requiredBlockItemStateDataBits));
$this->decodeBlockOnlyState($reader->readInt($this->requiredBlockOnlyStateDataBits));
}
private function encodeBlockItemState() : int{
$writer = new RuntimeDataWriter($this->requiredBlockItemStateDataBits);
@ -304,34 +315,44 @@ class Block{
if($bits > Block::INTERNAL_STATE_DATA_BITS){
throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
}
for($stateData = 0; $stateData < (1 << $bits); ++$stateData){
$v = clone $this;
for($blockItemStateData = 0; $blockItemStateData < (1 << $this->requiredBlockItemStateDataBits); ++$blockItemStateData){
$withType = clone $this;
try{
$v->decodeFullState($stateData);
if($v->encodeFullState() !== $stateData){
throw new \LogicException(static::class . "::decodeStateData() accepts invalid state data (returned " . $v->encodeFullState() . " for input $stateData)");
$withType->decodeBlockItemState($blockItemStateData);
$encoded = $withType->encodeBlockItemState();
if($encoded !== $blockItemStateData){
throw new \LogicException(static::class . "::decodeBlockItemState() accepts invalid inputs (returned $encoded for input $blockItemStateData)");
}
}catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
continue;
}
yield $v;
for($blockOnlyStateData = 0; $blockOnlyStateData < (1 << $this->requiredBlockOnlyStateDataBits); ++$blockOnlyStateData){
$withState = clone $withType;
try{
$withState->decodeBlockOnlyState($blockOnlyStateData);
$encoded = $withState->encodeBlockOnlyState();
if($encoded !== $blockOnlyStateData){
throw new \LogicException(static::class . "::decodeBlockOnlyState() accepts invalid inputs (returned $encoded for input $blockOnlyStateData)");
}
}catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
continue;
}
yield $withState;
}
}
}
/**
* Called when this block is created, set, or has a neighbouring block update, to re-detect dynamic properties which
* are not saved on the world.
*
* Clears any cached precomputed objects, such as bounding boxes. Remove any outdated precomputed things such as
* AABBs and force recalculation.
* are not saved in the blockstate ID.
* If any such properties are updated, don't forget to clear things like AABB caches if necessary.
*
* 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).
*/
public function readStateFromWorld() : Block{
$this->collisionBoxes = null;
return $this;
}
@ -571,6 +592,7 @@ class Block{
*/
final public function position(World $world, int $x, int $y, int $z) : void{
$this->position = new Position($x, $y, $z, $world);
$this->collisionBoxes = null;
}
/**
@ -721,8 +743,14 @@ class Block{
* @return Block
*/
public function getSide(int $side, int $step = 1){
if($this->position->isValid()){
return $this->position->getWorld()->getBlock($this->position->getSide($side, $step));
$position = $this->position;
if($position->isValid()){
[$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
return $position->getWorld()->getBlockAt(
$position->x + ($dx * $step),
$position->y + ($dy * $step),
$position->z + ($dz * $step)
);
}
throw new \LogicException("Block does not have a valid world");
@ -736,8 +764,14 @@ class Block{
*/
public function getHorizontalSides() : \Generator{
$world = $this->position->getWorld();
foreach($this->position->sidesAroundAxis(Axis::Y) as $vector3){
yield $world->getBlock($vector3);
foreach(Facing::HORIZONTAL as $facing){
[$dx, $dy, $dz] = Facing::OFFSET[$facing];
//TODO: yield Facing as the key?
yield $world->getBlockAt(
$this->position->x + $dx,
$this->position->y + $dy,
$this->position->z + $dz
);
}
}
@ -749,8 +783,13 @@ class Block{
*/
public function getAllSides() : \Generator{
$world = $this->position->getWorld();
foreach($this->position->sides() as $vector3){
yield $world->getBlock($vector3);
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
//TODO: yield Facing as the key?
yield $world->getBlockAt(
$this->position->x + $dx,
$this->position->y + $dy,
$this->position->z + $dz
);
}
}
@ -877,7 +916,7 @@ class Block{
* blocks placed on the given face can be supported by this block.
*/
public function getSupportType(int $facing) : SupportType{
return SupportType::FULL();
return SupportType::FULL;
}
protected function getAdjacentSupportType(int $facing) : SupportType{

View File

@ -562,10 +562,10 @@ final class BlockTypeIds{
public const WEIGHTED_PRESSURE_PLATE_HEAVY = 10532;
public const WEIGHTED_PRESSURE_PLATE_LIGHT = 10533;
public const WHEAT = 10534;
public const BUDDING_AMETHYST = 10535;
public const WHITE_TULIP = 10536;
public const WOOL = 10537;
public const AMETHYST_CLUSTER = 10538;
public const GLAZED_TERRACOTTA = 10539;
public const AMETHYST = 10540;
public const ANCIENT_DEBRIS = 10541;
@ -737,8 +737,16 @@ final class BlockTypeIds{
public const BIG_DRIPLEAF_HEAD = 10707;
public const BIG_DRIPLEAF_STEM = 10708;
public const PINK_PETALS = 10709;
public const CRIMSON_ROOTS = 10710;
public const WARPED_ROOTS = 10711;
public const CHISELED_BOOKSHELF = 10712;
public const TORCHFLOWER = 10713;
public const TORCHFLOWER_CROP = 10714;
public const PITCHER_PLANT = 10715;
public const PITCHER_CROP = 10716;
public const DOUBLE_PITCHER_CROP = 10717;
public const FIRST_UNUSED_BLOCK_ID = 10710;
public const FIRST_UNUSED_BLOCK_ID = 10718;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;

View File

@ -34,6 +34,7 @@ use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function array_key_exists;
use function spl_object_id;
class BrewingStand extends Transparent{
@ -44,7 +45,7 @@ class BrewingStand extends Transparent{
protected array $slots = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->brewingStandSlots($this->slots);
$w->enumSet($this->slots, BrewingStandSlot::cases());
}
protected function recalculateCollisionBoxes() : array{
@ -61,18 +62,18 @@ class BrewingStand extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function hasSlot(BrewingStandSlot $slot) : bool{
return array_key_exists($slot->id(), $this->slots);
return array_key_exists(spl_object_id($slot), $this->slots);
}
public function setSlot(BrewingStandSlot $slot, bool $occupied) : self{
if($occupied){
$this->slots[$slot->id()] = $slot;
$this->slots[spl_object_id($slot)] = $slot;
}else{
unset($this->slots[$slot->id()]);
unset($this->slots[spl_object_id($slot)]);
}
return $this;
}
@ -89,7 +90,7 @@ class BrewingStand extends Transparent{
public function setSlots(array $slots) : self{
$this->slots = [];
foreach($slots as $slot){
$this->slots[$slot->id()] = $slot;
$this->slots[spl_object_id($slot)] = $slot;
}
return $this;
}
@ -114,7 +115,7 @@ class BrewingStand extends Transparent{
}
$changed = false;
foreach(BrewingStandSlot::getAll() as $slot){
foreach(BrewingStandSlot::cases() as $slot){
$occupied = !$brewing->getInventory()->isSlotEmpty($slot->getSlotNumber());
if($occupied !== $this->hasSlot($slot)){
$this->setSlot($slot, $occupied);

View File

@ -0,0 +1,68 @@
<?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\utils\AmethystTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function array_rand;
use function mt_rand;
final class BuddingAmethyst extends Opaque{
use AmethystTrait;
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
if(mt_rand(1, 5) === 1){
$face = Facing::ALL[array_rand(Facing::ALL)];
$adjacent = $this->getSide($face);
//TODO: amethyst buds can spawn in water - we need waterlogging support for this
$newStage = null;
if($adjacent->getTypeId() === BlockTypeIds::AIR){
$newStage = AmethystCluster::STAGE_SMALL_BUD;
}elseif(
$adjacent->getTypeId() === BlockTypeIds::AMETHYST_CLUSTER &&
$adjacent instanceof AmethystCluster &&
$adjacent->getStage() < AmethystCluster::STAGE_CLUSTER &&
$adjacent->getFacing() === $face
){
$newStage = $adjacent->getStage() + 1;
}
if($newStage !== null){
BlockEventHelper::grow($adjacent, VanillaBlocks::AMETHYST_CLUSTER()->setStage($newStage)->setFacing($face), null);
}
}
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
}

View File

@ -23,39 +23,22 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\entity\EntityDamageByBlockEvent;
use pocketmine\event\entity\EntityDamageEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Cactus extends Transparent{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 15;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function hasEntityCollision() : bool{
return true;
}
@ -69,7 +52,7 @@ class Cactus extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onEntityInside(Entity $entity) : bool{
@ -78,23 +61,18 @@ class Cactus extends Transparent{
return true;
}
private function canBeSupportedBy(Block $block) : bool{
return $block->hasSameTypeId($this) || $block->hasTypeTag(BlockTypeTags::SAND);
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
$world->useBreakOn($this->position);
}else{
foreach(Facing::HORIZONTAL as $side){
$b = $this->getSide($side);
if($b->isSolid()){
$world->useBreakOn($this->position);
break;
}
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
if(!$supportBlock->hasSameTypeId($this) && !$supportBlock->hasTypeTag(BlockTypeTags::SAND)){
return false;
}
foreach(Facing::HORIZONTAL as $side){
if($block->getSide($side)->isSolid()){
return false;
}
}
return true;
}
public function ticksRandomly() : bool{
@ -124,18 +102,4 @@ class Cactus extends Transparent{
}
}
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
foreach(Facing::HORIZONTAL as $side){
if($this->getSide($side)->isSolid()){
return false;
}
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
}

View File

@ -37,7 +37,7 @@ class Cake extends BaseCake{
protected int $bites = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, 0, self::MAX_BITES, $this->bites);
$w->boundedIntAuto(0, self::MAX_BITES, $this->bites);
}
/**

View File

@ -30,7 +30,7 @@ class CakeWithDyedCandle extends CakeWithCandle{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
$this->color = DyeColor::WHITE;
parent::__construct($idInfo, $name, $typeInfo);
}

View File

@ -48,7 +48,7 @@ class Candle extends Transparent{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$this->encodeLitState($w);
$w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
$w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
}
public function getCount() : int{ return $this->count; }
@ -91,7 +91,7 @@ class Candle extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
protected function getCandleIfCompatibleType(Block $block) : ?Candle{

View File

@ -24,21 +24,13 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\item\Item;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Carpet extends Flowable{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $typeInfo);
}
use StaticSupportTrait;
public function isSolid() : bool{
return true;
@ -51,19 +43,8 @@ class Carpet extends Flowable{
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getTypeId() !== BlockTypeIds::AIR){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::AIR){
$this->position->getWorld()->useBreakOn($this->position);
}
private function canBeSupportedAt(Block $block) : bool{
return $block->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::AIR;
}
public function getFlameEncouragement() : int{

View File

@ -61,7 +61,7 @@ final class Cauldron extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return $facing === Facing::UP ? SupportType::EDGE() : SupportType::NONE();
return $facing === Facing::UP ? SupportType::EDGE : SupportType::NONE;
}
/**
@ -83,7 +83,7 @@ final class Cauldron extends Transparent{
}elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){
//TODO: powder snow cauldron
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
if($item->getType()->equals(PotionType::WATER())){
if($item->getType() === PotionType::WATER){
$this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
}else{
$this->fill(PotionCauldron::POTION_FILL_AMOUNT, VanillaBlocks::POTION_CAULDRON()->setPotionItem($item), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
@ -38,14 +40,16 @@ use pocketmine\world\sound\GlowBerriesPickSound;
use function mt_rand;
class CaveVines extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 25;
protected int $age = 0;
protected bool $berries = false;
protected bool $head = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(5, 0, self::MAX_AGE, $this->age);
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
$w->bool($this->berries);
$w->bool($this->head);
}
@ -66,19 +70,6 @@ class CaveVines extends Flowable{
return $this;
}
public function getAge() : int{
return $this->age;
}
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0-" . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function canClimb() : bool{
return true;
}
@ -89,19 +80,10 @@ class CaveVines extends Flowable{
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::UP);
return $supportBlock->getSupportType(Facing::DOWN)->equals(SupportType::FULL()) || $supportBlock->hasSameTypeId($this);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
return $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || $supportBlock->hasSameTypeId($this);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
$this->age = mt_rand(0, self::MAX_AGE);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@ -177,6 +159,6 @@ class CaveVines extends Flowable{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -33,7 +33,7 @@ final class Chain extends Transparent{
use PillarRotationTrait;
public function getSupportType(int $facing) : SupportType{
return $this->axis === Axis::Y && Facing::axis($facing) === Axis::Y ? SupportType::CENTER() : SupportType::NONE();
return $this->axis === Axis::Y && Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
}
protected function recalculateCollisionBoxes() : array{

View File

@ -45,7 +45,7 @@ class Chest extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onPostPlace() : void{

View File

@ -0,0 +1,134 @@
<?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\tile\ChiseledBookshelf as TileChiseledBookshelf;
use pocketmine\block\utils\ChiseledBookshelfSlot;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Book;
use pocketmine\item\EnchantedBook;
use pocketmine\item\Item;
use pocketmine\item\WritableBookBase;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use function spl_object_id;
class ChiseledBookshelf extends Opaque{
use HorizontalFacingTrait;
use FacesOppositePlacingPlayerTrait;
/**
* @var ChiseledBookshelfSlot[]
* @phpstan-var array<int, ChiseledBookshelfSlot>
*/
private array $slots = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->enumSet($this->slots, ChiseledBookshelfSlot::cases());
}
/**
* Returns whether the given slot is displayed as occupied.
* This doesn't guarantee that there is or isn't a book in the bookshelf's inventory.
*/
public function hasSlot(ChiseledBookshelfSlot $slot) : bool{
return isset($this->slots[spl_object_id($slot)]);
}
/**
* Sets whether the given slot is displayed as occupied.
*
* This doesn't modify the bookshelf's inventory, so you can use this to make invisible
* books or display books that aren't actually in the bookshelf.
*
* To modify the contents of the bookshelf inventory, access the tile inventory.
*
* @return $this
*/
public function setSlot(ChiseledBookshelfSlot $slot, bool $occupied) : self{
if($occupied){
$this->slots[spl_object_id($slot)] = $slot;
}else{
unset($this->slots[spl_object_id($slot)]);
}
return $this;
}
/**
* Returns which slots of the bookshelf are displayed as occupied.
* As above, these values do not necessarily reflect the contents of the bookshelf inventory,
* although they usually will unless modified by plugins.
*
* @return ChiseledBookshelfSlot[]
* @phpstan-return array<int, ChiseledBookshelfSlot>
*/
public function getSlots() : array{
return $this->slots;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($face !== $this->facing){
return false;
}
$x = Facing::axis($face) === Axis::X ? $clickVector->getZ() : $clickVector->getX();
$slot = ChiseledBookshelfSlot::fromBlockFaceCoordinates(
Facing::isPositive(Facing::rotateY($face, true)) ? 1 - $x : $x,
$clickVector->y
);
$tile = $this->position->getWorld()->getTile($this->position);
if(!$tile instanceof TileChiseledBookshelf){
return false;
}
$inventory = $tile->getInventory();
if(!$inventory->isSlotEmpty($slot->value)){
$returnedItems[] = $inventory->getItem($slot->value);
$inventory->clear($slot->value);
$this->setSlot($slot, false);
}elseif($item instanceof WritableBookBase || $item instanceof Book || $item instanceof EnchantedBook){
//TODO: type tags like blocks would be better for this
$inventory->setItem($slot->value, $item->pop());
$this->setSlot($slot, true);
}else{
return true;
}
$this->position->getWorld()->setBlock($this->position, $this);
return true;
}
public function getDropsForCompatibleTool(Item $item) : array{
return [];
}
public function isAffectedBySilkTouch() : bool{
return true;
}
}

View File

@ -23,52 +23,38 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\Position;
use pocketmine\world\sound\ChorusFlowerDieSound;
use pocketmine\world\sound\ChorusFlowerGrowSound;
use pocketmine\world\World;
use function array_rand;
use function min;
use function mt_rand;
final class ChorusFlower extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MIN_AGE = 0;
public const MAX_AGE = 5;
private const MAX_STEM_HEIGHT = 5;
private int $age = self::MIN_AGE;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, self::MIN_AGE, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < self::MIN_AGE || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in the range " . self::MIN_AGE . " ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()];
}
private function canBeSupportedAt(Position $position) : bool{
private function canBeSupportedAt(Block $block) : bool{
$position = $block->getPosition();
$world = $position->getWorld();
$down = $world->getBlock($position->down());
@ -93,25 +79,10 @@ final class ChorusFlower extends Flowable{
return $plantAdjacent;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace->getPosition())){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this->position)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
$this->position->getWorld()->useBreakOn($this->position);
}
public function ticksRandomly() : bool{ return $this->age < self::MAX_AGE; }
/**
* @phpstan-return array{int, bool}
*/
@ -181,11 +152,13 @@ final class ChorusFlower extends Flowable{
if($tx === null){
$tx = new BlockTransaction($this->position->getWorld());
}
$tx->addBlock($this->position->getSide($facing), (clone $this)->setAge($this->getAge() + $ageChange));
$tx->addBlock($this->position->getSide($facing), (clone $this)->setAge(min(self::MAX_AGE, $this->getAge() + $ageChange)));
return $tx;
}
public function ticksRandomly() : bool{ return $this->age < self::MAX_AGE; }
public function onRandomTick() : void{
$world = $this->position->getWorld();

View File

@ -23,18 +23,16 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\Position;
use function mt_rand;
final class ChorusPlant extends Flowable{
use StaticSupportTrait;
protected function recalculateCollisionBoxes() : array{
$bb = AxisAlignedBB::one();
@ -52,7 +50,8 @@ final class ChorusPlant extends Flowable{
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
}
private function canStay(Position $position) : bool{
private function canBeSupportedAt(Block $block) : bool{
$position = $block->getPosition();
$world = $position->getWorld();
$down = $world->getBlock($position->down());
@ -72,24 +71,7 @@ final class ChorusPlant extends Flowable{
}
}
if($this->canBeSupportedBy($down)){
return true;
}
return false;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canStay($blockReplace->getPosition())){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canStay($this->position)){
$this->position->getWorld()->useBreakOn($this->position);
}
return $this->canBeSupportedBy($down);
}
public function getDropsForCompatibleTool(Item $item) : array{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
@ -41,25 +42,13 @@ use function mt_rand;
class CocoaBlock extends Transparent{
use HorizontalFacingTrait;
use AgeableTrait;
public const MAX_AGE = 2;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->boundedInt(2, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
}
/**
@ -77,11 +66,11 @@ class CocoaBlock extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
private function canAttachTo(Block $block) : bool{
return $block instanceof Wood && $block->getWoodType()->equals(WoodType::JUNGLE());
return $block instanceof Wood && $block->getWoodType() === WoodType::JUNGLE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@ -110,7 +99,7 @@ class CocoaBlock extends Transparent{
}
public function ticksRandomly() : bool{
return true;
return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{

View File

@ -24,13 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
class Concrete extends Opaque{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $typeInfo);
}
}

View File

@ -25,7 +25,6 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\Fallable;
use pocketmine\block\utils\FallableTrait;
use pocketmine\math\Facing;
@ -36,11 +35,6 @@ class ConcretePowder extends Opaque implements Fallable{
onNearbyBlockChange as protected startFalling;
}
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $typeInfo);
}
public function onNearbyBlockChange() : void{
if(($water = $this->getAdjacentWater()) !== null){
BlockEventHelper::form($this, VanillaBlocks::CONCRETE()->setColor($this->color), $water);

View File

@ -23,14 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CopperTrait;
class CopperStairs extends Stair{
use CopperTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->oxidation = CopperOxidation::NONE();
parent::__construct($idInfo, $name, $typeInfo);
}
}

View File

@ -23,29 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class Coral extends BaseCoral{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if(!$this->canBeSupportedAt($this)){
$world->useBreakOn($this->position);
}else{
parent::onNearbyBlockChange();
}
}
use StaticSupportTrait;
private function canBeSupportedAt(Block $block) : bool{
return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\item\Item;
use function mt_rand;
@ -32,11 +31,6 @@ use function mt_rand;
final class CoralBlock extends Opaque{
use CoralTypeTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->coralType = CoralType::TUBE();
parent::__construct($idInfo, $name, $typeInfo);
}
public function onNearbyBlockChange() : void{
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));

View File

@ -23,51 +23,34 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
abstract class Crops extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 7;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockReplace->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
private function canBeSupportedAt(Block $block) : bool{
return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($this->age < self::MAX_AGE && $item instanceof Fertilizer){
$block = clone $this;
$block->age += mt_rand(2, 5);
if($block->age > self::MAX_AGE){
$block->age = self::MAX_AGE;
$tempAge = $block->age + mt_rand(2, 5);
if($tempAge > self::MAX_AGE){
$tempAge = self::MAX_AGE;
}
$block->age = $tempAge;
if(BlockEventHelper::grow($this, $block, $player)){
$item->pop();
}
@ -78,14 +61,8 @@ abstract class Crops extends Flowable{
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::FARMLAND){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function ticksRandomly() : bool{
return true;
return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{

View File

@ -42,7 +42,7 @@ class DaylightSensor extends Transparent{
protected bool $inverted = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, 0, 15, $this->signalStrength);
$w->boundedIntAuto(0, 15, $this->signalStrength);
$w->bool($this->inverted);
}
@ -70,7 +70,7 @@ class DaylightSensor extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -23,29 +23,14 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class DeadBush extends Flowable{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
use StaticSupportTrait;
public function getDropsForIncompatibleTool(Item $item) : array{
return [
@ -65,11 +50,12 @@ class DeadBush extends Flowable{
return 100;
}
private function canBeSupportedBy(Block $block) : bool{
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
return
$block->hasTypeTag(BlockTypeTags::SAND) ||
$block->hasTypeTag(BlockTypeTags::MUD) ||
match($block->getTypeId()){
$supportBlock->hasTypeTag(BlockTypeTags::SAND) ||
$supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
match($supportBlock->getTypeId()){
//can't use DIRT tag here because it includes farmland
BlockTypeIds::PODZOL,
BlockTypeIds::MYCELIUM,

View File

@ -38,15 +38,10 @@ use pocketmine\world\sound\ItemUseOnBlockSound;
use pocketmine\world\sound\WaterSplashSound;
class Dirt extends Opaque{
protected DirtType $dirtType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->dirtType = DirtType::NORMAL();
parent::__construct($idInfo, $name, $typeInfo);
}
protected DirtType $dirtType = DirtType::NORMAL;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->dirtType($this->dirtType);
$w->enum($this->dirtType);
}
public function getDirtType() : DirtType{ return $this->dirtType; }
@ -67,16 +62,16 @@ class Dirt extends Opaque{
$item->applyDamage(1);
$newBlock = $this->dirtType->equals(DirtType::NORMAL()) ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();
$newBlock = $this->dirtType === DirtType::NORMAL ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();
$center = $this->position->add(0.5, 0.5, 0.5);
$world->addSound($center, new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
if($this->dirtType->equals(DirtType::ROOTED())){
if($this->dirtType === DirtType::ROOTED){
$world->dropItem($center, VanillaBlocks::HANGING_ROOTS()->asItem());
}
return true;
}elseif($this->dirtType->equals(DirtType::ROOTED()) && $item instanceof Fertilizer){
}elseif($this->dirtType === DirtType::ROOTED && $item instanceof Fertilizer){
$down = $this->getSide(Facing::DOWN);
if($down->getTypeId() !== BlockTypeIds::AIR){
return true;
@ -85,7 +80,7 @@ class Dirt extends Opaque{
$item->pop();
$world->setBlock($down->position, VanillaBlocks::HANGING_ROOTS());
//TODO: bonemeal particles, growth sounds
}elseif(($item instanceof Potion || $item instanceof SplashPotion) && $item->getType()->equals(PotionType::WATER())){
}elseif(($item instanceof Potion || $item instanceof SplashPotion) && $item->getType() === PotionType::WATER){
$item->pop();
$world->setBlock($this->position, VanillaBlocks::MUD());
$world->addSound($this->position, new WaterSplashSound(0.5));

View File

@ -51,6 +51,8 @@ class Door extends Transparent{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$this->collisionBoxes = null;
//copy door properties from other half
$other = $this->getSide($this->top ? Facing::DOWN : Facing::UP);
if($other instanceof Door && $other->hasSameTypeId($this)){
@ -102,7 +104,7 @@ class Door extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onNearbyBlockChange() : void{

View File

@ -0,0 +1,110 @@
<?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\utils\AgeableTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
final class DoublePitcherCrop extends DoublePlant{
use AgeableTrait {
describeBlockOnlyState as describeAge;
}
public const MAX_AGE = 1;
public function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
parent::describeBlockOnlyState($w);
$this->describeAge($w);
}
protected function recalculateCollisionBoxes() : array{
if($this->top){
return [];
}
//the pod exists only in the bottom half of the plant
return [
AxisAlignedBB::one()
->trim(Facing::UP, 11 / 16)
->squash(Axis::X, 3 / 16)
->squash(Axis::Z, 3 / 16)
->extend(Facing::DOWN, 1 / 16) //presumably this is to correct for farmland being 15/16 of a block tall
];
}
private function grow(?Player $player) : bool{
if($this->age >= self::MAX_AGE){
return false;
}
$bottom = $this->top ? $this->getSide(Facing::DOWN) : $this;
$top = $this->top ? $this : $this->getSide(Facing::UP);
if($top->getTypeId() !== BlockTypeIds::AIR && !$top->hasSameTypeId($this)){
return false;
}
$newState = (clone $this)->setAge($this->age + 1);
$tx = new BlockTransaction($this->position->getWorld());
$tx->addBlock($bottom->position, (clone $newState)->setTop(false));
$tx->addBlock($top->position, (clone $newState)->setTop(true));
$ev = new StructureGrowEvent($bottom, $tx, $player);
$ev->call();
return !$ev->isCancelled() && $tx->apply();
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item instanceof Fertilizer && $this->grow($player)){
$item->pop();
return true;
}
return false;
}
public function ticksRandomly() : bool{
return $this->age < self::MAX_AGE && !$this->top;
}
public function onRandomTick() : void{
//TODO: the growth speed is influenced by farmland and nearby crops
//only the bottom half of the plant can grow randomly
if(mt_rand(0, 2) === 0 && !$this->top){
$this->grow(null);
}
}
}

View File

@ -50,7 +50,7 @@ class DragonEgg extends Transparent implements Fallable{
}
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
if($player !== null && !$player->getGamemode()->equals(GameMode::CREATIVE())){
if($player !== null && $player->getGamemode() !== GameMode::CREATIVE){
$this->teleport();
return true;
}
@ -82,6 +82,6 @@ class DragonEgg extends Transparent implements Fallable{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -24,19 +24,13 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
class DyedCandle extends Candle{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $typeInfo);
}
protected function getCandleIfCompatibleType(Block $block) : ?Candle{
$result = parent::getCandleIfCompatibleType($block);
//different coloured candles can't be combined in the same block
return $result instanceof DyedCandle && $result->color->equals($this->color) ? $result : null;
return $result instanceof DyedCandle && $result->color === $this->color ? $result : null;
}
}

View File

@ -24,13 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
final class DyedShulkerBox extends ShulkerBox{
use ColoredTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::WHITE();
parent::__construct($idInfo, $name, $typeInfo);
}
}

View File

@ -41,7 +41,7 @@ class EnchantingTable extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -49,7 +49,7 @@ class EnderChest extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -31,15 +31,40 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use function intdiv;
use function lcg_value;
class Farmland extends Transparent{
public const MAX_WETNESS = 7;
private const WATER_SEARCH_HORIZONTAL_LENGTH = 9;
private const WATER_SEARCH_VERTICAL_LENGTH = 2;
private const WATER_POSITION_INDEX_UNKNOWN = -1;
/** Total possible options for water X/Z indexes */
private const WATER_POSITION_INDICES_TOTAL = (self::WATER_SEARCH_HORIZONTAL_LENGTH ** 2) * 2;
protected int $wetness = 0; //"moisture" blockstate property in PC
/**
* Cached value indicating the relative coordinates of the most recently found water block.
*
* If this is set to a non-unknown value, the farmland block will check the relative coordinates indicated by
* this value for water, before searching the entire 9x2x9 grid around the farmland. This significantly benefits
* hydrating or fully hydrated farmland, avoiding the need for costly searches on every random tick.
*
* If the coordinates indicated don't contain water, the full 9x2x9 volume will be searched as before. A new index
* will be recorded if water is found, otherwise it will be set to unknown and future searches will search the full
* 9x2x9 volume again.
*
* This property is not exposed to the API or saved on disk. It is only used by PocketMine-MP at runtime as a cache.
*/
private int $waterPositionIndex = self::WATER_POSITION_INDEX_UNKNOWN;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, 0, self::MAX_WETNESS, $this->wetness);
$w->boundedIntAuto(0, self::MAX_WETNESS, $this->wetness);
$w->boundedIntAuto(-1, self::WATER_POSITION_INDICES_TOTAL - 1, $this->waterPositionIndex);
}
public function getWetness() : int{ return $this->wetness; }
@ -53,6 +78,22 @@ class Farmland extends Transparent{
return $this;
}
/**
* @internal
*/
public function getWaterPositionIndex() : int{ return $this->waterPositionIndex; }
/**
* @internal
*/
public function setWaterPositionIndex(int $waterPositionIndex) : self{
if($waterPositionIndex < -1 || $waterPositionIndex >= self::WATER_POSITION_INDICES_TOTAL){
throw new \InvalidArgumentException("Water XZ index must be in range -1 ... " . (self::WATER_POSITION_INDICES_TOTAL - 1));
}
$this->waterPositionIndex = $waterPositionIndex;
return $this;
}
/**
* @return AxisAlignedBB[]
*/
@ -72,6 +113,11 @@ class Farmland extends Transparent{
public function onRandomTick() : void{
$world = $this->position->getWorld();
//this property may be updated by canHydrate() - track this so we know if we need to set the block again
$oldWaterPositionIndex = $this->waterPositionIndex;
$changed = false;
if(!$this->canHydrate()){
if($this->wetness > 0){
$event = new FarmlandHydrationChangeEvent($this, $this->wetness, $this->wetness - 1);
@ -79,9 +125,11 @@ class Farmland extends Transparent{
if(!$event->isCancelled()){
$this->wetness = $event->getNewHydration();
$world->setBlock($this->position, $this, false);
$changed = true;
}
}else{
$world->setBlock($this->position, VanillaBlocks::DIRT());
$changed = true;
}
}elseif($this->wetness < self::MAX_WETNESS){
$event = new FarmlandHydrationChangeEvent($this, $this->wetness, self::MAX_WETNESS);
@ -89,8 +137,14 @@ class Farmland extends Transparent{
if(!$event->isCancelled()){
$this->wetness = $event->getNewHydration();
$world->setBlock($this->position, $this, false);
$changed = true;
}
}
if(!$changed && $oldWaterPositionIndex !== $this->waterPositionIndex){
//ensure the water square index is saved regardless of whether anything else happened
$world->setBlock($this->position, $this, false);
}
}
public function onEntityLand(Entity $entity) : ?float{
@ -105,19 +159,39 @@ class Farmland extends Transparent{
}
protected function canHydrate() : bool{
//TODO: check rain
$start = $this->position->add(-4, 0, -4);
$end = $this->position->add(4, 1, 4);
for($y = $start->y; $y <= $end->y; ++$y){
for($z = $start->z; $z <= $end->z; ++$z){
for($x = $start->x; $x <= $end->x; ++$x){
if($this->position->getWorld()->getBlockAt($x, $y, $z) instanceof Water){
$world = $this->position->getWorld();
$startX = $this->position->getFloorX() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
$startY = $this->position->getFloorY();
$startZ = $this->position->getFloorZ() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
if($this->waterPositionIndex !== self::WATER_POSITION_INDEX_UNKNOWN){
$raw = $this->waterPositionIndex;
$x = $raw % self::WATER_SEARCH_HORIZONTAL_LENGTH;
$raw = intdiv($raw, self::WATER_SEARCH_HORIZONTAL_LENGTH);
$z = $raw % self::WATER_SEARCH_HORIZONTAL_LENGTH;
$raw = intdiv($raw, self::WATER_SEARCH_HORIZONTAL_LENGTH);
$y = $raw % self::WATER_SEARCH_VERTICAL_LENGTH;
if($world->getBlockAt($startX + $x, $startY + $y, $startZ + $z) instanceof Water){
return true;
}
}
//no water found at cached position - search the whole area
//y will increment after x/z have been exhausted, as usually water will be at the same Y as the farmland
for($y = 0; $y < self::WATER_SEARCH_VERTICAL_LENGTH; $y++){
for($x = 0; $x < self::WATER_SEARCH_HORIZONTAL_LENGTH; $x++){
for($z = 0; $z < self::WATER_SEARCH_HORIZONTAL_LENGTH; $z++){
if($world->getBlockAt($startX + $x, $startY + $y, $startZ + $z) instanceof Water){
$this->waterPositionIndex = $x + ($z * self::WATER_SEARCH_HORIZONTAL_LENGTH) + ($y * self::WATER_SEARCH_HORIZONTAL_LENGTH ** 2);
return true;
}
}
}
}
$this->waterPositionIndex = self::WATER_POSITION_INDEX_UNKNOWN;
return false;
}

View File

@ -40,9 +40,11 @@ class Fence extends Transparent{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$this->collisionBoxes = null;
foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing);
if($block instanceof static || $block instanceof FenceGate || $block->getSupportType(Facing::opposite($facing))->equals(SupportType::FULL())){
if($block instanceof static || $block instanceof FenceGate || $block->getSupportType(Facing::opposite($facing)) === SupportType::FULL){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);
@ -98,6 +100,6 @@ class Fence extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return Facing::axis($facing) === Axis::Y ? SupportType::CENTER() : SupportType::NONE();
return Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
}
}

View File

@ -72,7 +72,7 @@ class FenceGate extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
private function checkInWall() : bool{

View File

@ -38,7 +38,7 @@ abstract class FillableCauldron extends Transparent{
private int $fillLevel = self::MIN_FILL_LEVEL;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, self::MIN_FILL_LEVEL, self::MAX_FILL_LEVEL, $this->fillLevel);
$w->boundedIntAuto(self::MIN_FILL_LEVEL, self::MAX_FILL_LEVEL, $this->fillLevel);
}
public function getFillLevel() : int{ return $this->fillLevel; }
@ -64,7 +64,7 @@ abstract class FillableCauldron extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return $facing === Facing::UP ? SupportType::EDGE() : SupportType::NONE();
return $facing === Facing::UP ? SupportType::EDGE : SupportType::NONE;
}
protected function withFillLevel(int $fillLevel) : Block{

View File

@ -23,9 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockBurnEvent;
use pocketmine\math\Facing;
use pocketmine\world\format\Chunk;
@ -36,31 +36,16 @@ use function min;
use function mt_rand;
class Fire extends BaseFire{
use AgeableTrait;
public const MAX_AGE = 15;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
protected function getFireDamage() : int{
return 1;
}
private function canBeSupportedBy(Block $block) : bool{
return $block->getSupportType(Facing::UP)->equals(SupportType::FULL());
return $block->getSupportType(Facing::UP) === SupportType::FULL;
}
public function onNearbyBlockChange() : void{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
@ -35,6 +36,8 @@ use function atan2;
use function rad2deg;
final class FloorCoralFan extends BaseCoral{
use StaticSupportTrait;
private int $axis = Axis::X;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
@ -53,9 +56,6 @@ final class FloorCoralFan extends BaseCoral{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
if($player !== null){
$playerBlockPos = $player->getPosition()->floor();
$directionVector = $blockReplace->getPosition()->subtractVector($playerBlockPos)->normalize();
@ -73,15 +73,6 @@ final class FloorCoralFan extends BaseCoral{
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if(!$this->canBeSupportedAt($this)){
$world->useBreakOn($this->position);
}else{
parent::onNearbyBlockChange();
}
}
private function canBeSupportedAt(Block $block) : bool{
return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}

View File

@ -48,6 +48,6 @@ abstract class Flowable extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -23,28 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\item\Item;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
class Flower extends Flowable{
use StaticSupportTrait;
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->hasTypeTag(BlockTypeTags::DIRT) || $down->hasTypeTag(BlockTypeTags::MUD)){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
$down = $this->getSide(Facing::DOWN);
if(!$down->hasTypeTag(BlockTypeTags::DIRT) && !$down->hasTypeTag(BlockTypeTags::MUD)){
$this->position->getWorld()->useBreakOn($this->position);
}
private function canBeSupportedAt(Block $block) : bool{
$supportBlock = $block->getSide(Facing::DOWN);
return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
public function getFlameEncouragement() : int{

View File

@ -24,15 +24,16 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\tile\FlowerPot as TileFlowerPot;
use pocketmine\block\utils\StaticSupportTrait;
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 function assert;
class FlowerPot extends Flowable{
use StaticSupportTrait;
protected ?Block $plant = null;
@ -89,20 +90,6 @@ class FlowerPot extends Flowable{
return [AxisAlignedBB::one()->contract(3 / 16, 0, 3 / 16)->trim(Facing::UP, 5 / 8)];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function canBeSupportedAt(Block $block) : bool{
return $block->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport();
}

View File

@ -28,15 +28,10 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
final class Froglight extends SimplePillar{
private FroglightType $froglightType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->froglightType = FroglightType::OCHRE();
parent::__construct($idInfo, $name, $typeInfo);
}
private FroglightType $froglightType = FroglightType::OCHRE;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->froglightType($this->froglightType);
$w->enum($this->froglightType);
}
public function getFroglightType() : FroglightType{ return $this->froglightType; }

View File

@ -23,30 +23,15 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use function mt_rand;
class FrostedIce extends Ice{
use AgeableTrait;
public const MAX_AGE = 3;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ... " . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function onNearbyBlockChange() : void{
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(20, 40));
}

View File

@ -24,15 +24,9 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\ColoredTrait;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
class GlazedTerracotta extends Opaque{
use ColoredTrait;
use FacesOppositePlacingPlayerTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->color = DyeColor::BLACK();
parent::__construct($idInfo, $name, $typeInfo);
}
}

View File

@ -95,7 +95,7 @@ class GlowLichen extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function canBeReplaced() : bool{
@ -121,7 +121,7 @@ class GlowLichen extends Transparent{
$changed = false;
foreach($this->faces as $face){
if(!$this->getAdjacentSupportType($face)->equals(SupportType::FULL())){
if($this->getAdjacentSupportType($face) !== SupportType::FULL){
unset($this->faces[$face]);
$changed = true;
}
@ -156,7 +156,7 @@ class GlowLichen extends Transparent{
$supportBlock = $world->getBlock($replacePos->getSide($spreadFace));
$supportFace = Facing::opposite($spreadFace);
if(!$supportBlock->getSupportType($supportFace)->equals(SupportType::FULL())){
if($supportBlock->getSupportType($supportFace) !== SupportType::FULL){
return false;
}
@ -268,7 +268,7 @@ class GlowLichen extends Transparent{
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face)->equals(SupportType::FULL())){
if(!$this->hasFace($face) && $this->getAdjacentSupportType($face) === SupportType::FULL){
$faces[$face] = $face;
}
}

View File

@ -23,13 +23,14 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
final class GoldOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaItems::RAW_GOLD()];
return [VanillaItems::RAW_GOLD()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))];
}
public function isAffectedBySilkTouch() : bool{ return true; }

View File

@ -69,7 +69,7 @@ class Grass extends Opaque{
$b = $world->getBlockAt($x, $y, $z);
if(
!($b instanceof Dirt) ||
!$b->getDirtType()->equals(DirtType::NORMAL()) ||
$b->getDirtType() !== DirtType::NORMAL ||
$world->getFullLightAt($x, $y + 1, $z) < 4 ||
$world->getBlockAt($x, $y + 1, $z)->getLightFilter() >= 2
){

View File

@ -23,32 +23,18 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
final class HangingRoots extends Flowable{
use StaticSupportTrait;
private function canBeSupportedAt(Block $block) : bool{
return $block->getAdjacentSupportType(Facing::UP)->hasCenterSupport(); //weird I know, but they can be placed on the bottom of fences
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function getDropsForIncompatibleTool(Item $item) : array{
if($item->hasEnchantment(VanillaEnchantments::SILK_TOUCH())){
return $this->getDropsForCompatibleTool($item);

View File

@ -68,9 +68,9 @@ class Hopper extends Transparent{
public function getSupportType(int $facing) : SupportType{
return match($facing){
Facing::UP => SupportType::FULL(),
Facing::DOWN => $this->facing === Facing::DOWN ? SupportType::CENTER() : SupportType::NONE(),
default => SupportType::NONE()
Facing::UP => SupportType::FULL,
Facing::DOWN => $this->facing === Facing::DOWN ? SupportType::CENTER : SupportType::NONE,
default => SupportType::NONE
};
}

View File

@ -23,13 +23,14 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
final class IronOre extends Opaque{
public function getDropsForCompatibleTool(Item $item) : array{
return [VanillaItems::RAW_IRON()];
return [VanillaItems::RAW_IRON()->setCount(FortuneDropHelper::weighted($item, min: 1, maxBase: 1))];
}
public function isAffectedBySilkTouch() : bool{ return true; }

View File

@ -164,7 +164,7 @@ class ItemFrame extends Flowable{
}
private function canBeSupportedAt(Block $block, int $face) : bool{
return !$block->getAdjacentSupportType($face)->equals(SupportType::NONE());
return $block->getAdjacentSupportType($face) !== SupportType::NONE;
}
public function onNearbyBlockChange() : void{

View File

@ -66,7 +66,7 @@ class Ladder extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
@ -85,6 +85,6 @@ class Ladder extends Transparent{
}
private function canBeSupportedAt(Block $block, int $face) : bool{
return $block->getAdjacentSupportType($face)->equals(SupportType::FULL());
return $block->getAdjacentSupportType($face) === SupportType::FULL;
}
}

View File

@ -73,7 +73,7 @@ class Lantern extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{

View File

@ -33,12 +33,13 @@ use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
use function mt_rand;
class Leaves extends Transparent{
private const MAX_LOG_DISTANCE = 4;
protected LeavesType $leavesType; //immutable for now
protected bool $noDecay = false;
protected bool $checkDecay = false;
@ -92,7 +93,7 @@ class Leaves extends Transparent{
return true;
}
if($block instanceof Leaves && $distance <= 4){
if($block instanceof Leaves && $distance <= self::MAX_LOG_DISTANCE){
foreach(Facing::ALL as $side){
if($this->findLog($pos->getSide($side), $visited, $distance + 1)){
return true;
@ -111,7 +112,7 @@ class Leaves extends Transparent{
}
public function ticksRandomly() : bool{
return true;
return !$this->noDecay && $this->checkDecay;
}
public function onRandomTick() : void{
@ -147,23 +148,22 @@ class Leaves extends Transparent{
if(FortuneDropHelper::bonusChanceDivisor($item, 20, 4)){ //Saplings
// TODO: according to the wiki, the jungle saplings have a different drop rate
$sapling = (match($this->leavesType){
LeavesType::ACACIA() => VanillaBlocks::ACACIA_SAPLING(),
LeavesType::BIRCH() => VanillaBlocks::BIRCH_SAPLING(),
LeavesType::DARK_OAK() => VanillaBlocks::DARK_OAK_SAPLING(),
LeavesType::JUNGLE() => VanillaBlocks::JUNGLE_SAPLING(),
LeavesType::OAK() => VanillaBlocks::OAK_SAPLING(),
LeavesType::SPRUCE() => VanillaBlocks::SPRUCE_SAPLING(),
LeavesType::MANGROVE(), //TODO: mangrove propagule
LeavesType::AZALEA(), LeavesType::FLOWERING_AZALEA() => null, //TODO: azalea
LeavesType::CHERRY() => null, //TODO: cherry
default => throw new AssumptionFailedError("Unreachable")
LeavesType::ACACIA => VanillaBlocks::ACACIA_SAPLING(),
LeavesType::BIRCH => VanillaBlocks::BIRCH_SAPLING(),
LeavesType::DARK_OAK => VanillaBlocks::DARK_OAK_SAPLING(),
LeavesType::JUNGLE => VanillaBlocks::JUNGLE_SAPLING(),
LeavesType::OAK => VanillaBlocks::OAK_SAPLING(),
LeavesType::SPRUCE => VanillaBlocks::SPRUCE_SAPLING(),
LeavesType::MANGROVE, //TODO: mangrove propagule
LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea
LeavesType::CHERRY => null, //TODO: cherry
})?->asItem();
if($sapling !== null){
$drops[] = $sapling;
}
}
if(
($this->leavesType->equals(LeavesType::OAK()) || $this->leavesType->equals(LeavesType::DARK_OAK())) &&
($this->leavesType === LeavesType::OAK || $this->leavesType === LeavesType::DARK_OAK) &&
FortuneDropHelper::bonusChanceDivisor($item, 200, 20)
){ //Apples
$drops[] = VanillaItems::APPLE();
@ -188,6 +188,6 @@ class Leaves extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -87,7 +87,7 @@ class Lectern extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function isProducingSignal() : bool{ return $this->producingSignal; }

View File

@ -36,16 +36,11 @@ use pocketmine\world\sound\RedstonePowerOffSound;
use pocketmine\world\sound\RedstonePowerOnSound;
class Lever extends Flowable{
protected LeverFacing $facing;
protected LeverFacing $facing = LeverFacing::UP_AXIS_X;
protected bool $activated = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->facing = LeverFacing::UP_AXIS_X();
parent::__construct($idInfo, $name, $typeInfo);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->leverFacing($this->facing);
$w->enum($this->facing);
$w->bool($this->activated);
}
@ -77,12 +72,12 @@ class Lever extends Flowable{
return $x;
};
$this->facing = match($face){
Facing::DOWN => $selectUpDownPos(LeverFacing::DOWN_AXIS_X(), LeverFacing::DOWN_AXIS_Z()),
Facing::UP => $selectUpDownPos(LeverFacing::UP_AXIS_X(), LeverFacing::UP_AXIS_Z()),
Facing::NORTH => LeverFacing::NORTH(),
Facing::SOUTH => LeverFacing::SOUTH(),
Facing::WEST => LeverFacing::WEST(),
Facing::EAST => LeverFacing::EAST(),
Facing::DOWN => $selectUpDownPos(LeverFacing::DOWN_AXIS_X, LeverFacing::DOWN_AXIS_Z),
Facing::UP => $selectUpDownPos(LeverFacing::UP_AXIS_X, LeverFacing::UP_AXIS_Z),
Facing::NORTH => LeverFacing::NORTH,
Facing::SOUTH => LeverFacing::SOUTH,
Facing::WEST => LeverFacing::WEST,
Facing::EAST => LeverFacing::EAST,
default => throw new AssumptionFailedError("Bad facing value"),
};

View File

@ -35,7 +35,7 @@ final class Light extends Flowable{
private int $level = self::MAX_LIGHT_LEVEL;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level);
$w->boundedIntAuto(self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level);
}
public function getLightLevel() : int{ return $this->level; }

View File

@ -49,7 +49,7 @@ abstract class Liquid extends Transparent{
protected bool $still = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, 0, self::MAX_DECAY, $this->decay);
$w->boundedIntAuto(0, self::MAX_DECAY, $this->decay);
$w->bool($this->falling);
$w->bool($this->still);
}
@ -97,7 +97,7 @@ abstract class Liquid extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function getDropsForCompatibleTool(Item $item) : array{
@ -165,23 +165,22 @@ abstract class Liquid extends Transparent{
$vX = $vY = $vZ = 0;
$x = $this->position->getFloorX();
$y = $this->position->getFloorY();
$z = $this->position->getFloorZ();
$decay = $this->getEffectiveFlowDecay($this);
$world = $this->position->getWorld();
foreach(Facing::HORIZONTAL as $j){
$x = $this->position->x;
$y = $this->position->y;
$z = $this->position->z;
[$dx, $dy, $dz] = Facing::OFFSET[$j];
match($j){
Facing::WEST => --$x,
Facing::EAST => ++$x,
Facing::NORTH => --$z,
Facing::SOUTH => ++$z
};
$sideX = $x + $dx;
$sideY = $y + $dy;
$sideZ = $z + $dz;
$sideBlock = $world->getBlockAt($x, $y, $z);
$sideBlock = $world->getBlockAt($sideX, $sideY, $sideZ);
$blockDecay = $this->getEffectiveFlowDecay($sideBlock);
if($blockDecay < 0){
@ -189,21 +188,21 @@ abstract class Liquid extends Transparent{
continue;
}
$blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($x, $y - 1, $z));
$blockDecay = $this->getEffectiveFlowDecay($world->getBlockAt($sideX, $sideY - 1, $sideZ));
if($blockDecay >= 0){
$realDecay = $blockDecay - ($decay - 8);
$vX += ($x - $this->position->x) * $realDecay;
$vY += ($y - $this->position->y) * $realDecay;
$vZ += ($z - $this->position->z) * $realDecay;
$vX += $dx * $realDecay;
$vY += $dy * $realDecay;
$vZ += $dz * $realDecay;
}
continue;
}else{
$realDecay = $blockDecay - $decay;
$vX += ($x - $this->position->x) * $realDecay;
$vY += ($y - $this->position->y) * $realDecay;
$vZ += ($z - $this->position->z) * $realDecay;
$vX += $dx * $realDecay;
$vY += $dy * $realDecay;
$vZ += $dz * $realDecay;
}
}
@ -211,10 +210,10 @@ abstract class Liquid extends Transparent{
if($this->falling){
foreach(Facing::HORIZONTAL as $facing){
$pos = $this->position->getSide($facing);
[$dx, $dy, $dz] = Facing::OFFSET[$facing];
if(
!$this->canFlowInto($world->getBlockAt($pos->x, $pos->y, $pos->z)) ||
!$this->canFlowInto($world->getBlockAt($pos->x, $pos->y + 1, $pos->z))
!$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy, $z + $dz)) ||
!$this->canFlowInto($world->getBlockAt($x + $dx, $y + $dy + 1, $z + $dz))
){
$vector = $vector->normalize()->add(0, -6, 0);
break;
@ -260,13 +259,17 @@ abstract class Liquid extends Transparent{
$world = $this->position->getWorld();
$x = $this->position->getFloorX();
$y = $this->position->getFloorY();
$z = $this->position->getFloorZ();
if(!$this->isSource()){
$smallestFlowDecay = -100;
$this->adjacentSources = 0;
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x, $this->position->y, $this->position->z - 1), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x, $this->position->y, $this->position->z + 1), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x - 1, $this->position->y, $this->position->z), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($this->position->x + 1, $this->position->y, $this->position->z), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z - 1), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x, $y, $z + 1), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x - 1, $y, $z), $smallestFlowDecay);
$smallestFlowDecay = $this->getSmallestFlowDecay($world->getBlockAt($x + 1, $y, $z), $smallestFlowDecay);
$newDecay = $smallestFlowDecay + $multiplier;
$falling = false;
@ -275,13 +278,13 @@ abstract class Liquid extends Transparent{
$newDecay = -1;
}
if($this->getEffectiveFlowDecay($world->getBlockAt($this->position->x, $this->position->y + 1, $this->position->z)) >= 0){
if($this->getEffectiveFlowDecay($world->getBlockAt($x, $y + 1, $z)) >= 0){
$falling = true;
}
$minAdjacentSources = $this->getMinAdjacentSourcesToFormSource();
if($minAdjacentSources !== null && $this->adjacentSources >= $minAdjacentSources){
$bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z);
$bottomBlock = $world->getBlockAt($x, $y - 1, $z);
if($bottomBlock->isSolid() || ($bottomBlock instanceof Liquid && $bottomBlock->hasSameTypeId($this) && $bottomBlock->isSource())){
$newDecay = 0;
$falling = false;
@ -290,17 +293,17 @@ abstract class Liquid extends Transparent{
if($falling !== $this->falling || (!$falling && $newDecay !== $this->decay)){
if(!$falling && $newDecay < 0){
$world->setBlock($this->position, VanillaBlocks::AIR());
$world->setBlockAt($x, $y, $z, VanillaBlocks::AIR());
return;
}
$this->falling = $falling;
$this->decay = $falling ? 0 : $newDecay;
$world->setBlock($this->position, $this); //local block update will cause an update to be scheduled
$world->setBlockAt($x, $y, $z, $this); //local block update will cause an update to be scheduled
}
}
$bottomBlock = $world->getBlockAt($this->position->x, $this->position->y - 1, $this->position->z);
$bottomBlock = $world->getBlockAt($x, $y - 1, $z);
$this->flowIntoBlock($bottomBlock, 0, true);
@ -313,8 +316,9 @@ abstract class Liquid extends Transparent{
if($adjacentDecay <= self::MAX_DECAY){
$calculator = new MinimumCostFlowCalculator($world, $this->getFlowDecayPerBlock(), $this->canFlowInto(...));
foreach($calculator->getOptimalFlowDirections($this->position->getFloorX(), $this->position->getFloorY(), $this->position->getFloorZ()) as $facing){
$this->flowIntoBlock($world->getBlock($this->position->getSide($facing)), $adjacentDecay, false);
foreach($calculator->getOptimalFlowDirections($x, $y, $z) as $facing){
[$dx, $dy, $dz] = Facing::OFFSET[$facing];
$this->flowIntoBlock($world->getBlockAt($x + $dx, $y + $dy, $z + $dz), $adjacentDecay, false);
}
}
}

View File

@ -39,18 +39,13 @@ class MobHead extends Flowable{
public const MIN_ROTATION = 0;
public const MAX_ROTATION = 15;
protected MobHeadType $mobHeadType;
protected MobHeadType $mobHeadType = MobHeadType::SKELETON;
protected int $facing = Facing::NORTH;
protected int $rotation = self::MIN_ROTATION; //TODO: split this into floor skull and wall skull handling
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->mobHeadType = MobHeadType::SKELETON(); //TODO: this should be a parameter
parent::__construct($idInfo, $name, $typeInfo);
}
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->mobHeadType($this->mobHeadType);
$w->enum($this->mobHeadType);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{

View File

@ -42,6 +42,6 @@ class MonsterSpawner extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -52,7 +52,7 @@ class Mycelium extends Opaque{
$z = mt_rand($this->position->z - 1, $this->position->z + 1);
$world = $this->position->getWorld();
$block = $world->getBlockAt($x, $y, $z);
if($block instanceof Dirt && $block->getDirtType()->equals(DirtType::NORMAL())){
if($block instanceof Dirt && $block->getDirtType() === DirtType::NORMAL){
if($block->getSide(Facing::UP) instanceof Transparent){
BlockEventHelper::spread($block, VanillaBlocks::MYCELIUM(), $this);
}

View File

@ -70,7 +70,7 @@ class NetherPortal extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
public function getDrops(Item $item) : array{

39
src/block/NetherRoots.php Normal file
View File

@ -0,0 +1,39 @@
<?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\utils\StaticSupportTrait;
use pocketmine\math\Facing;
final class NetherRoots extends Flowable{
use StaticSupportTrait;
private function canBeSupportedAt(Block $block) : bool{
//TODO: nylium, moss
$supportBlock = $block->getSide(Facing::DOWN);
return
$supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
$supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
}

View File

@ -23,9 +23,10 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
@ -41,44 +42,23 @@ use function mt_rand;
* This class is used for Weeping & Twisting vines, because they have same behaviour
*/
class NetherVines extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 25;
/** Direction the vine grows towards. */
private int $growthFace;
protected int $age = 0;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, int $growthFace){
$this->growthFace = $growthFace;
parent::__construct($idInfo, $name, $typeInfo);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(5, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{
return $this->age;
}
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0-" . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function isAffectedBySilkTouch() : bool{
return true;
}
public function ticksRandomly() : bool{
return true;
}
public function canClimb() : bool{
return true;
}
@ -88,12 +68,6 @@ class NetherVines extends Flowable{
return $supportBlock->getSupportType($this->growthFace)->hasCenterSupport() || $supportBlock->hasSameTypeId($this);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
/**
* Returns the block at the end of the vine structure furthest from the supporting block.
*/
@ -106,9 +80,6 @@ class NetherVines extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($blockReplace)){
return false;
}
$this->age = mt_rand(0, self::MAX_AGE - 1);
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
@ -123,8 +94,12 @@ class NetherVines extends Flowable{
return false;
}
public function ticksRandomly() : bool{
return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{
if(mt_rand(1, 10) === 1 && $this->age < self::MAX_AGE){
if($this->age < self::MAX_AGE && mt_rand(1, 10) === 1){
if($this->getSide($this->growthFace)->canBeReplaced()){
$this->grow(null);
}
@ -184,6 +159,6 @@ class NetherVines extends Flowable{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
return SupportType::NONE;
}
}

View File

@ -23,53 +23,26 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class NetherWartPlant extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 3;
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, 0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }
/** @return $this */
public function setAge(int $age) : self{
if($age < 0 || $age > self::MAX_AGE){
throw new \InvalidArgumentException("Age must be in range 0 ..." . self::MAX_AGE);
}
$this->age = $age;
return $this;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$down = $this->getSide(Facing::DOWN);
if($down->getTypeId() === BlockTypeIds::SOUL_SAND){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
public function onNearbyBlockChange() : void{
if($this->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::SOUL_SAND){
$this->position->getWorld()->useBreakOn($this->position);
}
private function canBeSupportedAt(Block $block) : bool{
return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::SOUL_SAND;
}
public function ticksRandomly() : bool{
return true;
return $this->age < self::MAX_AGE;
}
public function onRandomTick() : void{

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
@ -35,6 +36,9 @@ use pocketmine\world\BlockTransaction;
class PinkPetals extends Flowable{
use HorizontalFacingTrait;
use StaticSupportTrait {
canBePlacedAt as supportedWhenPlacedAt;
}
public const MIN_COUNT = 1;
public const MAX_COUNT = 4;
@ -43,7 +47,7 @@ class PinkPetals extends Flowable{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
$w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
}
public function getCount() : int{
@ -65,20 +69,11 @@ class PinkPetals extends Flowable{
return $supportBlock->hasTypeTag(BlockTypeTags::DIRT) || $supportBlock->hasTypeTag(BlockTypeTags::MUD);
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return ($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT) || parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
return ($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT) || $this->supportedWhenPlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedAt($this)){
return false;
}
if($blockReplace instanceof PinkPetals && $blockReplace->getCount() < self::MAX_COUNT){
$this->count = $blockReplace->getCount() + 1;
$this->facing = $blockReplace->getFacing();

105
src/block/PitcherCrop.php Normal file
View File

@ -0,0 +1,105 @@
<?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\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
final class PitcherCrop extends Flowable{
use AgeableTrait;
use StaticSupportTrait;
public const MAX_AGE = 2;
private function canBeSupportedAt(Block $block) : bool{
return $block->getSide(Facing::DOWN)->getTypeId() === BlockTypeIds::FARMLAND;
}
protected function recalculateCollisionBoxes() : array{
$widthTrim = $this->age === 0 ? 5 : 3;
$heightTrim = $this->age === 0 ? 13 : 11;
return [
AxisAlignedBB::one()
->trim(Facing::UP, $heightTrim / 16)
->squash(Axis::X, $widthTrim / 16)
->squash(Axis::Z, $widthTrim / 16)
->extend(Facing::DOWN, 1 / 16) //presumably this is to correct for farmland being 15/16 of a block tall
];
}
private function grow(?Player $player) : bool{
if($this->age > self::MAX_AGE){
return false;
}
if($this->age === self::MAX_AGE){
$up = $this->getSide(Facing::UP);
if($up->getTypeId() !== BlockTypeIds::AIR){
return false;
}
$tx = new BlockTransaction($this->position->getWorld());
$tx->addBlock($this->position, VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(false));
$tx->addBlock($this->position->up(), VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(true));
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
return !$ev->isCancelled() && $tx->apply();
}
return BlockEventHelper::grow($this, (clone $this)->setAge($this->age + 1), $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item instanceof Fertilizer && $this->grow($player)){
$item->pop();
return true;
}
return false;
}
public function ticksRandomly() : bool{
return true;
}
public function onRandomTick() : void{
//TODO: the growth speed is influenced by farmland and nearby crops
if(mt_rand(0, 2) === 0){
$this->grow(null);
}
}
}

View File

@ -23,21 +23,19 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\entity\Entity;
use pocketmine\event\block\PressurePlateUpdateEvent;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\PressurePlateActivateSound;
use pocketmine\world\sound\PressurePlateDeactivateSound;
use function count;
abstract class PressurePlate extends Transparent{
use StaticSupportTrait;
private readonly int $deactivationDelayTicks;
@ -60,24 +58,11 @@ abstract class PressurePlate extends Transparent{
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedAt($blockReplace)){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
return SupportType::NONE;
}
private function canBeSupportedAt(Block $block) : bool{
return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE());
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
public function hasEntityCollision() : bool{

View File

@ -29,17 +29,12 @@ use pocketmine\item\Item;
use function mt_rand;
class RedMushroomBlock extends Opaque{
protected MushroomBlockType $mushroomBlockType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->mushroomBlockType = MushroomBlockType::ALL_CAP();
parent::__construct($idInfo, $name, $typeInfo);
}
protected MushroomBlockType $mushroomBlockType = MushroomBlockType::ALL_CAP;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
//these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative),
//so this information needs to be kept in the type info
$w->mushroomBlockType($this->mushroomBlockType);
$w->enum($this->mushroomBlockType);
}
public function getMushroomBlockType() : MushroomBlockType{ return $this->mushroomBlockType; }
@ -61,10 +56,10 @@ class RedMushroomBlock extends Opaque{
}
public function getSilkTouchDrops(Item $item) : array{
return [(clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP())->asItem()];
return [(clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP)->asItem()];
}
public function getPickedItem(bool $addUserData = false) : Item{
return (clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP())->asItem();
return (clone $this)->setMushroomBlockType(MushroomBlockType::ALL_CAP)->asItem();
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\block\tile\Comparator;
use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@ -41,6 +42,7 @@ class RedstoneComparator extends Flowable{
use HorizontalFacingTrait;
use AnalogRedstoneSignalEmitterTrait;
use PoweredByRedstoneTrait;
use StaticSupportTrait;
protected bool $isSubtractMode = false;
@ -85,14 +87,10 @@ class RedstoneComparator extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedAt($blockReplace)){
if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
return false;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@ -101,14 +99,8 @@ class RedstoneComparator extends Flowable{
return true;
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function canBeSupportedAt(Block $block) : bool{
return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE());
return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
//TODO: redstone functionality

View File

@ -70,7 +70,7 @@ class RedstoneOre extends Opaque{
}
public function ticksRandomly() : bool{
return true;
return $this->lit;
}
public function onRandomTick() : void{

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\PoweredByRedstoneTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@ -37,6 +38,7 @@ use pocketmine\world\BlockTransaction;
class RedstoneRepeater extends Flowable{
use HorizontalFacingTrait;
use PoweredByRedstoneTrait;
use StaticSupportTrait;
public const MIN_DELAY = 1;
public const MAX_DELAY = 4;
@ -45,7 +47,7 @@ class RedstoneRepeater extends Flowable{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->boundedInt(2, self::MIN_DELAY, self::MAX_DELAY, $this->delay);
$w->boundedIntAuto(self::MIN_DELAY, self::MAX_DELAY, $this->delay);
$w->bool($this->powered);
}
@ -68,15 +70,11 @@ class RedstoneRepeater extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedAt($blockReplace)){
if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
if($player !== null){
$this->facing = Facing::opposite($player->getHorizontalFacing());
}
return false;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
@ -87,14 +85,8 @@ class RedstoneRepeater extends Flowable{
return true;
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedAt($this)){
$this->position->getWorld()->useBreakOn($this->position);
}
}
private function canBeSupportedAt(Block $block) : bool{
return !$block->getAdjacentSupportType(Facing::DOWN)->equals(SupportType::NONE());
return $block->getAdjacentSupportType(Facing::DOWN) !== SupportType::NONE;
}
//TODO: redstone functionality

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