Merge branch 'minor-next' into major-next

This commit is contained in:
Dylan K. Taylor 2023-10-24 11:57:30 +01:00
commit d565be93a8
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
55 changed files with 607 additions and 273 deletions

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

@ -18,3 +18,17 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if
## Fixes
- Fixed support conditions for hanging roots, cave vines and dead bushes.
- Fixed connection conditions for fences, glass panes, iron bars, and walls.
# 5.6.1
Released 20th October 2023.
## Performance
- Improved performance of cactus growth by disabling neighbour updates when only the age property was updated. While this isn't a perfect solution, it provides significant performance gains for servers with large cactus farms.
## Fixes
- Fixed `tools/generate-bedrock-data-from-packets.php` incorrectly interpreting network meta as blockstates in some cases (broken crafting recipes).
- Fixed crafting recipes involving beds, skulls and some other items not working correctly (incorrectly interpreted data).
- Fixed crashes when flower pot or cauldron blockentities exist in places where they shouldn't (leftovers from upgraded PM3 worlds).
- Fixed `Entity->broadcastSound()` not firing `WorldSoundEvent` (bypassing internal sound system).
- Fixed wooden signs, buttons and doors not being able to be used as furnace fuel.
- Fixed bone meal and tools only working when used on the top side of dirt and grass. Bone meal now works from any side, and tools work on any side except the bottom.

View File

@ -52,7 +52,7 @@
"symfony/filesystem": "~6.3.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.38",
"phpstan/phpstan": "1.10.39",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "~10.3.0 || ~10.2.0 || ~10.1.0"

14
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": "2282bf7835c1ced757460b083813e092",
"content-hash": "6c48ad06b67c3aa3890f004b197d17bc",
"packages": [
{
"name": "adhocore/json-comment",
@ -1378,16 +1378,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.38",
"version": "1.10.39",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
"reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4",
"reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4",
"shasum": ""
},
"require": {
@ -1436,7 +1436,7 @@
"type": "tidelift"
}
],
"time": "2023-10-06T14:19:14+00:00"
"time": "2023-10-17T15:46:26+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
@ -2964,5 +2964,5 @@
"platform-overrides": {
"php": "8.1.0"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View File

@ -571,6 +571,7 @@ class Server{
$playerPromiseResolver = new PromiseResolver();
$createPlayer = function(Location $location) use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $offlinePlayerData) : void{
/** @see Player::__construct() */
$player = new $class($this, $session, $playerInfo, $authenticated, $location, $offlinePlayerData);
if(!$player->hasPlayedBefore()){
$player->onGround = true; //TODO: this hack is needed for new players in-air ticks - they don't get detected as on-ground until they move

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.6.2";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";

View File

@ -50,7 +50,7 @@ final class AmethystCluster extends Transparent{
private int $stage = self::STAGE_CLUSTER;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, self::STAGE_SMALL_BUD, self::STAGE_CLUSTER, $this->stage);
$w->boundedIntAuto(self::STAGE_SMALL_BUD, self::STAGE_CLUSTER, $this->stage);
}
public function getStage() : int{ return $this->stage; }

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{

View File

@ -58,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);
}

View File

@ -271,4 +271,8 @@ abstract class BaseSign extends Transparent{
public function asItem() : Item{
return ($this->asItemCallback)();
}
public function getFuelTime() : int{
return $this->woodType->isFlammable() ? 200 : 0;
}
}

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
);
}
}

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

@ -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; }

View File

@ -49,7 +49,7 @@ class CaveVines extends Flowable{
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);
}

View File

@ -48,7 +48,7 @@ class CocoaBlock extends Transparent{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->horizontalFacing($this->facing);
$w->boundedInt(2, 0, self::MAX_AGE, $this->age);
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
}
/**

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);
}

View File

@ -54,7 +54,12 @@ class Dirt extends Opaque{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
$world = $this->position->getWorld();
if($face === Facing::UP && $item instanceof Hoe){
if($face !== Facing::DOWN && $item instanceof Hoe){
$up = $this->getSide(Facing::UP);
if($up->getTypeId() !== BlockTypeIds::AIR){
return true;
}
$item->applyDamage(1);
$newBlock = $this->dirtType === DirtType::NORMAL ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();

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)){

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,6 +40,8 @@ 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)) === SupportType::FULL){

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; }

View File

@ -82,7 +82,7 @@ class Grass extends Opaque{
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($face !== Facing::UP){
if($this->getSide(Facing::UP)->getTypeId() !== BlockTypeIds::AIR){
return false;
}
$world = $this->position->getWorld();
@ -91,20 +91,23 @@ class Grass extends Opaque{
TallGrassObject::growGrass($world, $this->position, new Random(mt_rand()), 8, 2);
return true;
}elseif($item instanceof Hoe){
$item->applyDamage(1);
$newBlock = VanillaBlocks::FARMLAND();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
}
if($face !== Facing::DOWN){
if($item instanceof Hoe){
$item->applyDamage(1);
$newBlock = VanillaBlocks::FARMLAND();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
return true;
}elseif($item instanceof Shovel && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR){
$item->applyDamage(1);
$newBlock = VanillaBlocks::GRASS_PATH();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
return true;
}elseif($item instanceof Shovel){
$item->applyDamage(1);
$newBlock = VanillaBlocks::GRASS_PATH();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
return true;
return true;
}
}
return false;

View File

@ -38,6 +38,8 @@ 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;
@ -91,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;

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);
}
@ -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

@ -47,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{

View File

@ -47,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);
}

View File

@ -39,7 +39,7 @@ class SeaPickle extends Transparent{
protected bool $underwater = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, self::MIN_COUNT, self::MAX_COUNT, $this->count);
$w->boundedIntAuto(self::MIN_COUNT, self::MAX_COUNT, $this->count);
$w->bool($this->underwater);
}

View File

@ -47,7 +47,7 @@ class SnowLayer extends Flowable implements Fallable{
protected int $layers = self::MIN_LAYERS;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(3, self::MIN_LAYERS, self::MAX_LAYERS, $this->layers);
$w->boundedIntAuto(self::MIN_LAYERS, self::MAX_LAYERS, $this->layers);
}
public function getLayers() : int{ return $this->layers; }

View File

@ -49,6 +49,8 @@ class Stair extends Transparent{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$this->collisionBoxes = null;
$clockwise = Facing::rotateY($this->facing, true);
if(($backFacing = $this->getPossibleCornerFacing(false)) !== null){
$this->shape = $backFacing === $clockwise ? StairShape::OUTER_RIGHT : StairShape::OUTER_LEFT;

View File

@ -39,6 +39,8 @@ class Thin extends Transparent{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$this->collisionBoxes = null;
foreach(Facing::HORIZONTAL as $facing){
$side = $this->getSide($facing);
if($side instanceof Thin || $side instanceof Wall || $side->getSupportType(Facing::opposite($facing)) === SupportType::FULL){

View File

@ -35,4 +35,8 @@ class WoodenButton extends Button{
public function hasEntityCollision() : bool{
return false; //TODO: arrows activate wooden buttons
}
public function getFuelTime() : int{
return $this->woodType->isFlammable() ? 100 : 0;
}
}

View File

@ -27,4 +27,8 @@ use pocketmine\block\utils\WoodTypeTrait;
class WoodenDoor extends Door{
use WoodTypeTrait;
public function getFuelTime() : int{
return $this->woodType->isFlammable() ? 200 : 0;
}
}

View File

@ -24,9 +24,16 @@ declare(strict_types=1);
namespace pocketmine\block\tile;
use pocketmine\block\utils\ChiseledBookshelfSlot;
use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\data\bedrock\item\SavedItemStackData;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item;
use pocketmine\math\Vector3;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\world\World;
use function count;
@ -55,4 +62,56 @@ class ChiseledBookshelf extends Tile implements Container{
public function writeSaveData(CompoundTag $nbt) : void{
$this->saveItems($nbt);
}
protected function loadItems(CompoundTag $tag) : void{
if(($inventoryTag = $tag->getTag(Container::TAG_ITEMS)) instanceof ListTag && $inventoryTag->getTagType() === NBT::TAG_Compound){
$inventory = $this->getRealInventory();
$listeners = $inventory->getListeners()->toArray();
$inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
$newContents = [];
/** @var CompoundTag $itemNBT */
foreach($inventoryTag as $slot => $itemNBT){
try{
$count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT);
if($count === 0){
continue;
}
$newContents[$slot] = Item::nbtDeserialize($itemNBT);
}catch(SavedDataLoadingException $e){
//TODO: not the best solution
\GlobalLogger::get()->logException($e);
continue;
}
}
$inventory->setContents($newContents);
$inventory->getListeners()->add(...$listeners);
}
if(($lockTag = $tag->getTag(Container::TAG_LOCK)) instanceof StringTag){
$this->lock = $lockTag->getValue();
}
}
protected function saveItems(CompoundTag $tag) : void{
$items = [];
foreach($this->getRealInventory()->getContents(true) as $slot => $item){
if($item->isNull()){
$items[$slot] = CompoundTag::create()
->setByte(SavedItemStackData::TAG_COUNT, 0)
->setShort(SavedItemData::TAG_DAMAGE, 0)
->setString(SavedItemData::TAG_NAME, "")
->setByte(SavedItemStackData::TAG_WAS_PICKED_UP, 0);
}else{
$items[$slot] = $item->nbtSerialize();
}
}
$tag->setTag(Container::TAG_ITEMS, new ListTag($items, NBT::TAG_Compound));
if($this->lock !== null){
$tag->setString(Container::TAG_LOCK, $this->lock);
}
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\data\runtime\RuntimeDataDescriber;
use function log;
/**
* This trait is used for blocks that have an age property.
@ -34,7 +33,7 @@ trait AgeableTrait{
protected int $age = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(((int) log(self::MAX_AGE, 2)) + 1, 0, self::MAX_AGE, $this->age);
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
}
public function getAge() : int{ return $this->age; }

View File

@ -29,7 +29,7 @@ trait AnalogRedstoneSignalEmitterTrait{
protected int $signalStrength = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, 0, 15, $this->signalStrength);
$w->boundedIntAuto(0, 15, $this->signalStrength);
}
public function getOutputSignalStrength() : int{ return $this->signalStrength; }

View File

@ -58,17 +58,10 @@ final class MinimumCostFlowCalculator{
if($j === $originOpposite || $j === $lastOpposite){
continue;
}
$x = $blockX;
$y = $blockY;
$z = $blockZ;
match($j){
Facing::WEST => --$x,
Facing::EAST => ++$x,
Facing::NORTH => --$z,
Facing::SOUTH => ++$z
};
[$dx, $dy, $dz] = Facing::OFFSET[$j];
$x = $blockX + $dx;
$y = $blockY + $dy;
$z = $blockZ + $dz;
if(!isset($this->flowCostVisited[$hash = World::blockHash($x, $y, $z)])){
if(!$this->world->isInWorld($x, $y, $z) || !$this->canFlowInto($this->world->getBlockAt($x, $y, $z))){
@ -109,16 +102,10 @@ final class MinimumCostFlowCalculator{
$flowCost = array_fill_keys(Facing::HORIZONTAL, 1000);
$maxCost = intdiv(4, $this->flowDecayPerBlock);
foreach(Facing::HORIZONTAL as $j){
$x = $originX;
$y = $originY;
$z = $originZ;
match($j){
Facing::WEST => --$x,
Facing::EAST => ++$x,
Facing::NORTH => --$z,
Facing::SOUTH => ++$z
};
[$dx, $dy, $dz] = Facing::OFFSET[$j];
$x = $originX + $dx;
$y = $originY + $dy;
$z = $originZ + $dz;
if(!$this->world->isInWorld($x, $y, $z) || !$this->canFlowInto($this->world->getBlockAt($x, $y, $z))){
$this->flowCostVisited[World::blockHash($x, $y, $z)] = self::BLOCKED;

View File

@ -31,7 +31,7 @@ trait SignLikeRotationTrait{
private $rotation = 0;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, 0, 15, $this->rotation);
$w->boundedIntAuto(0, 15, $this->rotation);
}
public function getRotation() : int{ return $this->rotation; }

View File

@ -38,8 +38,17 @@ use pocketmine\math\Facing;
interface RuntimeDataDescriber{
public function int(int $bits, int &$value) : void;
/**
* @deprecated Use {@link RuntimeDataDescriber::boundedIntAuto()} instead.
*/
public function boundedInt(int $bits, int $min, int $max, int &$value) : void;
/**
* Same as boundedInt() but automatically calculates the required number of bits from the range.
* The range bounds must be constant.
*/
public function boundedIntAuto(int $min, int $max, int &$value) : void;
public function bool(bool &$value) : void;
public function horizontalFacing(int &$facing) : void;

View File

@ -31,6 +31,7 @@ use pocketmine\math\Facing;
use pocketmine\utils\AssumptionFailedError;
use function get_class;
use function intdiv;
use function log;
use function spl_object_id;
final class RuntimeDataReader implements RuntimeDataDescriber{
@ -56,7 +57,20 @@ final class RuntimeDataReader implements RuntimeDataDescriber{
$value = $this->readInt($bits);
}
protected function readBoundedInt(int $bits, int $min, int $max) : int{
/**
* @deprecated Use {@link self::boundedIntAuto()} instead.
*/
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
$offset = $this->offset;
$this->boundedIntAuto($min, $max, $value);
$actualBits = $this->offset - $offset;
if($this->offset !== $offset + $bits){
throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
}
}
private function readBoundedIntAuto(int $min, int $max) : int{
$bits = ((int) log($max - $min, 2)) + 1;
$result = $this->readInt($bits) + $min;
if($result < $min || $result > $max){
throw new InvalidSerializedRuntimeDataException("Value is outside the range $min - $max");
@ -64,8 +78,8 @@ final class RuntimeDataReader implements RuntimeDataDescriber{
return $result;
}
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
$value = $this->readBoundedInt($bits, $min, $max);
public function boundedIntAuto(int $min, int $max, int &$value) : void{
$value = $this->readBoundedIntAuto($min, $max);
}
protected function readBool() : bool{
@ -160,7 +174,7 @@ final class RuntimeDataReader implements RuntimeDataDescriber{
public function wallConnections(array &$connections) : void{
$result = [];
$offset = 0;
$packed = $this->readBoundedInt(7, 0, (3 ** 4) - 1);
$packed = $this->readBoundedIntAuto(0, (3 ** 4) - 1);
foreach(Facing::HORIZONTAL as $facing){
$type = intdiv($packed, (3 ** $offset)) % 3;
if($type !== 0){

View File

@ -26,6 +26,7 @@ namespace pocketmine\data\runtime;
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\math\Facing;
use function count;
use function log;
final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
private int $bits = 0;
@ -42,8 +43,20 @@ final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
$this->addBits($bits);
}
/**
* @deprecated Use {@link self::boundedIntAuto()} instead.
*/
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
$this->addBits($bits);
$currentBits = $this->bits;
$this->boundedIntAuto($min, $max, $value);
$actualBits = $this->bits - $currentBits;
if($actualBits !== $bits){
throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
}
}
public function boundedIntAuto(int $min, int $max, int &$value) : void{
$this->addBits(((int) log($max - $min, 2)) + 1);
}
public function bool(bool &$value) : void{

View File

@ -28,6 +28,7 @@ use pocketmine\block\utils\WallConnectionType;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
use function array_flip;
use function log;
use function spl_object_id;
final class RuntimeDataWriter implements RuntimeDataDescriber{
@ -54,15 +55,28 @@ final class RuntimeDataWriter implements RuntimeDataDescriber{
$this->writeInt($bits, $value);
}
protected function writeBoundedInt(int $bits, int $min, int $max, int $value) : void{
/**
* @deprecated Use {@link self::boundedIntAuto()} instead.
*/
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
$offset = $this->offset;
$this->writeBoundedIntAuto($min, $max, $value);
$actualBits = $this->offset - $offset;
if($actualBits !== $bits){
throw new \InvalidArgumentException("Bits should be $actualBits for the given bounds, but received $bits. Use boundedIntAuto() for automatic bits calculation.");
}
}
private function writeBoundedIntAuto(int $min, int $max, int $value) : void{
if($value < $min || $value > $max){
throw new \InvalidArgumentException("Value $value is outside the range $min - $max");
}
$bits = ((int) log($max - $min, 2)) + 1;
$this->writeInt($bits, $value - $min);
}
public function boundedInt(int $bits, int $min, int $max, int &$value) : void{
$this->writeBoundedInt($bits, $min, $max, $value);
public function boundedIntAuto(int $min, int $max, int &$value) : void{
$this->writeBoundedIntAuto($min, $max, $value);
}
protected function writeBool(bool $value) : void{
@ -151,7 +165,7 @@ final class RuntimeDataWriter implements RuntimeDataDescriber{
} * (3 ** $offset);
$offset++;
}
$this->writeBoundedInt(7, 0, (3 ** 4) - 1, $packed);
$this->writeBoundedIntAuto(0, (3 ** 4) - 1, $packed);
}
/**

View File

@ -842,7 +842,7 @@ abstract class Entity{
protected function checkObstruction(float $x, float $y, float $z) : bool{
$world = $this->getWorld();
if(count($world->getCollisionBoxes($this, $this->getBoundingBox(), false)) === 0){
if(count($world->getBlockCollisionBoxes($this->boundingBox)) === 0){
return false;
}
@ -1144,7 +1144,7 @@ abstract class Entity{
assert(abs($dx) <= 20 && abs($dy) <= 20 && abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz");
$list = $this->getWorld()->getCollisionBoxes($this, $moveBB->addCoord($dx, $dy, $dz), false);
$list = $this->getWorld()->getBlockCollisionBoxes($moveBB->addCoord($dx, $dy, $dz));
foreach($list as $bb){
$dy = $bb->calculateYOffset($moveBB, $dy);
@ -1176,7 +1176,7 @@ abstract class Entity{
$stepBB = clone $this->boundingBox;
$list = $this->getWorld()->getCollisionBoxes($this, $stepBB->addCoord($dx, $dy, $dz), false);
$list = $this->getWorld()->getBlockCollisionBoxes($stepBB->addCoord($dx, $dy, $dz));
foreach($list as $bb){
$dy = $bb->calculateYOffset($stepBB, $dy);
}

View File

@ -209,6 +209,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("carpet", fn() => Blocks::CARPET());
$result->registerBlock("carrot_block", fn() => Blocks::CARROTS());
$result->registerBlock("carrots", fn() => Blocks::CARROTS());
$result->registerBlock("cartography_table", fn() => Blocks::CARTOGRAPHY_TABLE());
$result->registerBlock("carved_pumpkin", fn() => Blocks::CARVED_PUMPKIN());
$result->registerBlock("cauldron", fn() => Blocks::CAULDRON());
$result->registerBlock("cave_vines", fn() => Blocks::CAVE_VINES());
@ -984,6 +985,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("slime_block", fn() => Blocks::SLIME());
$result->registerBlock("small_amethyst_bud", fn() => Blocks::AMETHYST_CLUSTER()->setStage(AmethystCluster::STAGE_SMALL_BUD));
$result->registerBlock("small_dripleaf", fn() => Blocks::SMALL_DRIPLEAF());
$result->registerBlock("smithing_table", fn() => Blocks::SMITHING_TABLE());
$result->registerBlock("smoker", fn() => Blocks::SMOKER());
$result->registerBlock("smooth_basalt", fn() => Blocks::SMOOTH_BASALT());
$result->registerBlock("smooth_quartz", fn() => Blocks::SMOOTH_QUARTZ());
@ -1056,6 +1058,8 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("stripped_acacia_wood", fn() => Blocks::ACACIA_WOOD()->setStripped(true));
$result->registerBlock("stripped_birch_log", fn() => Blocks::BIRCH_LOG()->setStripped(true));
$result->registerBlock("stripped_birch_wood", fn() => Blocks::BIRCH_WOOD()->setStripped(true));
$result->registerBlock("stripped_cherry_log", fn() => Blocks::CHERRY_LOG()->setStripped(true));
$result->registerBlock("stripped_cherry_wood", fn() => Blocks::CHERRY_WOOD()->setStripped(true));
$result->registerBlock("stripped_crimson_hyphae", fn() => Blocks::CRIMSON_HYPHAE()->setStripped(true));
$result->registerBlock("stripped_crimson_stem", fn() => Blocks::CRIMSON_STEM()->setStripped(true));
$result->registerBlock("stripped_dark_oak_log", fn() => Blocks::DARK_OAK_LOG()->setStripped(true));

View File

@ -1224,7 +1224,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
protected function checkGroundState(float $wantedX, float $wantedY, float $wantedZ, float $dx, float $dy, float $dz) : void{
if($this->isSpectator()){
if($this->gamemode === GameMode::SPECTATOR){
$this->onGround = false;
}else{
$bb = clone $this->boundingBox;

View File

@ -41,8 +41,11 @@ final class Promise{
* @phpstan-param \Closure() : void $onFailure
*/
public function onCompletion(\Closure $onSuccess, \Closure $onFailure) : void{
if($this->shared->resolved){
$this->shared->result === null ? $onFailure() : $onSuccess($this->shared->result);
$state = $this->shared->state;
if($state === true){
$onSuccess($this->shared->result);
}elseif($state === false){
$onFailure();
}else{
$this->shared->onSuccess[spl_object_id($onSuccess)] = $onSuccess;
$this->shared->onFailure[spl_object_id($onFailure)] = $onFailure;
@ -50,6 +53,8 @@ final class Promise{
}
public function isResolved() : bool{
return $this->shared->resolved;
//TODO: perhaps this should return true when rejected? currently there's no way to tell if a promise was
//rejected or just hasn't been resolved yet
return $this->shared->state === true;
}
}

View File

@ -41,10 +41,10 @@ final class PromiseResolver{
* @phpstan-param TValue $value
*/
public function resolve(mixed $value) : void{
if($this->shared->resolved){
if($this->shared->state !== null){
throw new \LogicException("Promise has already been resolved/rejected");
}
$this->shared->resolved = true;
$this->shared->state = true;
$this->shared->result = $value;
foreach($this->shared->onSuccess as $c){
$c($value);
@ -54,10 +54,10 @@ final class PromiseResolver{
}
public function reject() : void{
if($this->shared->resolved){
if($this->shared->state !== null){
throw new \LogicException("Promise has already been resolved/rejected");
}
$this->shared->resolved = true;
$this->shared->state = false;
foreach($this->shared->onFailure as $c){
$c();
}

View File

@ -41,8 +41,8 @@ final class PromiseSharedData{
*/
public array $onFailure = [];
public bool $resolved = false;
public ?bool $state = null;
/** @phpstan-var TValue|null */
public mixed $result = null;
/** @phpstan-var TValue */
public mixed $result;
}

View File

@ -948,9 +948,7 @@ class World implements ChunkManager{
$this->providerGarbageCollectionTicker = 0;
}
//Do block updates
$this->timings->scheduledBlockUpdates->startTiming();
//Delayed updates
while($this->scheduledBlockUpdateQueue->count() > 0 && $this->scheduledBlockUpdateQueue->current()["priority"] <= $currentTick){
/** @var Vector3 $vec */
@ -962,7 +960,9 @@ class World implements ChunkManager{
$block = $this->getBlock($vec);
$block->onScheduledUpdate();
}
$this->timings->scheduledBlockUpdates->stopTiming();
$this->timings->neighbourBlockUpdates->startTiming();
//Normal updates
while($this->neighbourBlockUpdateQueue->count() > 0){
$index = $this->neighbourBlockUpdateQueue->dequeue();
@ -973,11 +973,6 @@ class World implements ChunkManager{
}
$block = $this->getBlockAt($x, $y, $z);
$replacement = $block->readStateFromWorld(); //for blocks like fences, force recalculation of connected AABBs
if($replacement !== $block){
$replacement->position($this, $x, $y, $z);
$block = $replacement;
}
if(BlockUpdateEvent::hasHandlers()){
$ev = new BlockUpdateEvent($block);
@ -992,7 +987,7 @@ class World implements ChunkManager{
$block->onNearbyBlockChange();
}
$this->timings->scheduledBlockUpdates->stopTiming();
$this->timings->neighbourBlockUpdates->stopTiming();
$this->timings->entityTick->startTiming();
//Update entities that need update
@ -1102,19 +1097,22 @@ class World implements ChunkManager{
$blockPosition = BlockPosition::fromVector3($b);
$tile = $this->getTileAt($b->x, $b->y, $b->z);
if($tile instanceof Spawnable && count($fakeStateProperties = $tile->getRenderUpdateBugWorkaroundStateProperties($fullBlock)) > 0){
$originalStateData = $blockTranslator->internalIdToNetworkStateData($fullBlock->getStateId());
$fakeStateData = new BlockStateData(
$originalStateData->getName(),
array_merge($originalStateData->getStates(), $fakeStateProperties),
$originalStateData->getVersion()
);
$packets[] = UpdateBlockPacket::create(
$blockPosition,
$blockTranslator->getBlockStateDictionary()->lookupStateIdFromData($fakeStateData) ?? throw new AssumptionFailedError("Unmapped fake blockstate data: " . $fakeStateData->toNbt()),
UpdateBlockPacket::FLAG_NETWORK,
UpdateBlockPacket::DATA_LAYER_NORMAL
);
if($tile instanceof Spawnable){
$expectedClass = $fullBlock->getIdInfo()->getTileClass();
if($expectedClass !== null && $tile instanceof $expectedClass && count($fakeStateProperties = $tile->getRenderUpdateBugWorkaroundStateProperties($fullBlock)) > 0){
$originalStateData = $blockTranslator->internalIdToNetworkStateData($fullBlock->getStateId());
$fakeStateData = new BlockStateData(
$originalStateData->getName(),
array_merge($originalStateData->getStates(), $fakeStateProperties),
$originalStateData->getVersion()
);
$packets[] = UpdateBlockPacket::create(
$blockPosition,
$blockTranslator->getBlockStateDictionary()->lookupStateIdFromData($fakeStateData) ?? throw new AssumptionFailedError("Unmapped fake blockstate data: " . $fakeStateData->toNbt()),
UpdateBlockPacket::FLAG_NETWORK,
UpdateBlockPacket::DATA_LAYER_NORMAL
);
}
}
$packets[] = UpdateBlockPacket::create(
$blockPosition,
@ -1455,9 +1453,9 @@ class World implements ChunkManager{
$this->scheduledBlockUpdateQueue->insert(new Vector3((int) $pos->x, (int) $pos->y, (int) $pos->z), $delay + $this->server->getTick());
}
private function tryAddToNeighbourUpdateQueue(Vector3 $pos) : void{
if($this->isInWorld($pos->x, $pos->y, $pos->z)){
$hash = World::blockHash($pos->x, $pos->y, $pos->z);
private function tryAddToNeighbourUpdateQueue(int $x, int $y, int $z) : void{
if($this->isInWorld($x, $y, $z)){
$hash = World::blockHash($x, $y, $z);
if(!isset($this->neighbourBlockUpdateQueueIndex[$hash])){
$this->neighbourBlockUpdateQueue->enqueue($hash);
$this->neighbourBlockUpdateQueueIndex[$hash] = true;
@ -1465,17 +1463,28 @@ class World implements ChunkManager{
}
}
/**
* Identical to {@link World::notifyNeighbourBlockUpdate()}, but without the Vector3 requirement. We don't want or
* need Vector3 in the places where this is called.
*
* TODO: make this the primary method in PM6
*/
private function internalNotifyNeighbourBlockUpdate(int $x, int $y, int $z) : void{
$this->tryAddToNeighbourUpdateQueue($x, $y, $z);
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$this->tryAddToNeighbourUpdateQueue($x + $dx, $y + $dy, $z + $dz);
}
}
/**
* Notify the blocks at and around the position that the block at the position may have changed.
* This will cause onNearbyBlockChange() to be called for these blocks.
* TODO: Accept plain integers in PM6 - the Vector3 requirement is an unnecessary inconvenience
*
* @see Block::onNearbyBlockChange()
*/
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
$this->tryAddToNeighbourUpdateQueue($pos);
foreach($pos->sides() as $side){
$this->tryAddToNeighbourUpdateQueue($side);
}
$this->internalNotifyNeighbourBlockUpdate($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ());
}
/**
@ -1526,13 +1535,13 @@ class World implements ChunkManager{
*
* @return AxisAlignedBB[]
*/
private function getCollisionBoxesForCell(int $x, int $y, int $z) : array{
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{
$block = $this->getBlockAt($x, $y, $z);
$boxes = $block->getCollisionBoxes();
$cellBB = AxisAlignedBB::one()->offset($x, $y, $z);
foreach(Facing::ALL as $facing){
$extraBoxes = $block->getSide($facing)->getCollisionBoxes();
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
@ -1547,7 +1556,7 @@ class World implements ChunkManager{
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{
public function getBlockCollisionBoxes(AxisAlignedBB $bb) : array{
$minX = (int) floor($bb->minX);
$minY = (int) floor($bb->minY);
$minZ = (int) floor($bb->minZ);
@ -1563,7 +1572,7 @@ class World implements ChunkManager{
for($y = $minY; $y <= $maxY; ++$y){
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getCollisionBoxesForCell($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z);
foreach($boxes as $blockBB){
if($blockBB->intersectsWith($bb)){
@ -1574,6 +1583,19 @@ class World implements ChunkManager{
}
}
return $collides;
}
/**
* @deprecated Use {@link World::getBlockCollisionBoxes()} instead (alongside {@link World::getCollidingEntities()}
* if entity collision boxes are also required).
*
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{
$collides = $this->getBlockCollisionBoxes($bb);
if($entities){
foreach($this->getCollidingEntities($bb->expandedCopy(0.25, 0.25, 0.25), $entity) as $ent){
$collides[] = clone $ent->boundingBox;
@ -1930,7 +1952,7 @@ class World implements ChunkManager{
if($update){
$this->updateAllLight($x, $y, $z);
$this->notifyNeighbourBlockUpdate($pos);
$this->internalNotifyNeighbourBlockUpdate($x, $y, $z);
}
$this->timings->setBlock->stopTiming();
@ -2873,15 +2895,7 @@ class World implements ChunkManager{
}elseif($this->getTile($tilePosition) !== null){
$logger->error("Cannot add tile at x=$tilePosition->x,y=$tilePosition->y,z=$tilePosition->z: Another tile is already at that position");
}else{
$block = $this->getBlockAt($tilePosition->getFloorX(), $tilePosition->getFloorY(), $tilePosition->getFloorZ());
$expectedClass = $block->getIdInfo()->getTileClass();
if($expectedClass === null){
$logger->error("Cannot add tile at x=$tilePosition->x,y=$tilePosition->y,z=$tilePosition->z: Block at that position (" . $block->getName() . ") does not expect a tile");
}elseif(!($tile instanceof $expectedClass)){
$logger->error("Cannot add tile at x=$tilePosition->x,y=$tilePosition->y,z=$tilePosition->z: Tile is of wrong type (expected $expectedClass but have " . get_class($tile) . ")");
}else{
$this->addTile($tile);
}
$this->addTile($tile);
}
}

View File

@ -34,6 +34,7 @@ class WorldTimings{
public TimingsHandler $doChunkUnload;
public TimingsHandler $scheduledBlockUpdates;
public TimingsHandler $neighbourBlockUpdates;
public TimingsHandler $randomChunkUpdates;
public TimingsHandler $randomChunkUpdatesChunkSelection;
public TimingsHandler $doChunkGC;
@ -77,6 +78,7 @@ class WorldTimings{
$this->doChunkUnload = self::newTimer($name, "Unload Chunks");
$this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates");
$this->neighbourBlockUpdates = self::newTimer($name, "Neighbour Block Updates");
$this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates");
$this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection");
$this->doChunkGC = self::newTimer($name, "Garbage Collection");

View File

@ -45,11 +45,26 @@ parameters:
count: 1
path: ../../../src/VersionInfo.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 3
path: ../../../src/block/Block.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 3
path: ../../../src/block/Block.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\format\\\\Chunk\\:\\:setBlockStateId\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Block.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 3
path: ../../../src/block/Block.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@ -140,21 +155,6 @@ parameters:
count: 1
path: ../../../src/block/DragonEgg.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Farmland.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Farmland.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Farmland.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 1
@ -285,31 +285,16 @@ parameters:
count: 1
path: ../../../src/block/Leaves.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 11
path: ../../../src/block/Liquid.php
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Liquid.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 11
path: ../../../src/block/Liquid.php
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
path: ../../../src/block/Liquid.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#"
count: 11
path: ../../../src/block/Liquid.php
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 1
@ -937,12 +922,12 @@ parameters:
-
message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#1 \\$x of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-
@ -967,12 +952,12 @@ parameters:
-
message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#2 \\$y of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-
@ -1002,12 +987,12 @@ parameters:
-
message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:isInWorld\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-
message: "#^Parameter \\#3 \\$z of static method pocketmine\\\\world\\\\World\\:\\:blockHash\\(\\) expects int, float\\|int given\\.$#"
count: 3
count: 2
path: ../../../src/world/World.php
-

View File

@ -28,6 +28,7 @@ use function asort;
use function file_get_contents;
use function is_array;
use function json_decode;
use function log;
use function print_r;
use const SORT_STRING;
@ -125,6 +126,14 @@ class BlockTest extends TestCase{
self::assertInstanceOf(Air::class, $block);
}
public function testStateDataSizeNotTooLarge() : void{
$typeIdBitsMin = ((int) log(BlockTypeIds::FIRST_UNUSED_BLOCK_ID, 2)) + 1;
$typeIdBitsMin++; //for custom blocks
self::assertLessThanOrEqual(32, Block::INTERNAL_STATE_DATA_BITS + $typeIdBitsMin, "State data size cannot be larger than " . (32 - $typeIdBitsMin) . " bits (need at least $typeIdBitsMin bits for block type ID)");
}
public function testAsItemFromItem() : void{
$block = VanillaBlocks::FLOWER_POT();
$item = $block->asItem();

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@ use pocketmine\block\BaseBanner;
use pocketmine\block\Bed;
use pocketmine\block\BlockTypeIds;
use pocketmine\block\CaveVines;
use pocketmine\block\Farmland;
use pocketmine\block\MobHead;
use pocketmine\block\RuntimeBlockStateRegistry;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
@ -76,6 +77,8 @@ final class BlockSerializerDeserializerTest extends TestCase{
$newBlock->setMobHeadType($block->getMobHeadType());
}elseif($block instanceof CaveVines && $newBlock instanceof CaveVines && !$block->hasBerries()){
$newBlock->setHead($block->isHead());
}elseif($block instanceof Farmland && $newBlock instanceof Farmland){
$block->setWaterPositionIndex($newBlock->getWaterPositionIndex());
}
self::assertSame($block->getStateId(), $newBlock->getStateId(), "Mismatch of blockstate for " . $block->getName() . ", " . print_r($block, true) . " vs " . print_r($newBlock, true));

View File

@ -0,0 +1,42 @@
<?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\promise;
use PHPUnit\Framework\TestCase;
final class PromiseTest extends TestCase{
public function testPromiseNull() : void{
$resolver = new PromiseResolver();
$resolver->resolve(null);
$resolver->getPromise()->onCompletion(
function(mixed $value) : void{
self::assertNull($value);
},
function() : void{
self::fail("Promise should not be rejected");
}
);
}
}