Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ae4c84e
First look at waterlogging implementation
ipad54 Oct 13, 2025
e338188
Merge branch 'minor-next' of https://github.com/pmmp/PocketMine-MP in…
ipad54 Oct 13, 2025
defa2ac
fix
ipad54 Oct 13, 2025
5a4edf2
Rename function
ipad54 Oct 13, 2025
b115e28
Bug fixes & review changes
ipad54 Oct 14, 2025
2c5c9b0
Merge branch 'minor-next' of https://github.com/pmmp/PocketMine-MP in…
ipad54 Oct 14, 2025
3e77acb
Move setDisplacedBlock() into an interface
ipad54 Oct 14, 2025
2cc638a
Don't generalise setDisplacedBlock()
ipad54 Oct 15, 2025
b9ba469
Remove accidental space addition
ipad54 Oct 15, 2025
0a21d05
Requested changes
ipad54 Oct 15, 2025
8439acf
Merge branch 'minor-next' into waterlogging
dktapps Oct 17, 2025
8cb5417
Merge branch 'minor-next' of ssh://github.com/pmmp/PocketMine-MP into…
ipad54 Oct 25, 2025
3947f78
Requested changes
ipad54 Oct 25, 2025
f2764af
fix tests
ipad54 Oct 25, 2025
447e9a9
Merge branch 'waterlogging' of https://github.com/ipad54/PocketMine-M…
ipad54 Oct 25, 2025
0b0b65b
Update Stair.php
ipad54 Oct 25, 2025
2ad6b4a
Merge branch 'minor-next' of https://github.com/pmmp/PocketMine-MP in…
ipad54 Oct 31, 2025
40df90f
Rename CoveredByWater to Waterloggable (and its methods), move canBeC…
ipad54 Oct 31, 2025
037b69c
Fixed bad terminology
ipad54 Oct 31, 2025
197e544
Update distance check
ipad54 Oct 31, 2025
89c2622
Fixed colliding behaviour
ipad54 Oct 31, 2025
a8d0f46
update placement logic
ipad54 Oct 31, 2025
fe63ccb
Fix CS
ipad54 Oct 31, 2025
7d434f7
Missed one during rename
ipad54 Oct 31, 2025
798ad0d
Merge branch 'minor-next' of https://github.com/pmmp/PocketMine-MP in…
ipad54 Nov 8, 2025
a5ec228
Apply requested changes
ipad54 Nov 8, 2025
650e40b
Merge branch 'minor-next' into waterlogging
dktapps Dec 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/block/BaseCoral.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralMaterial;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\block\utils\CoveredByWater;
use pocketmine\block\utils\CoveredByWaterTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Item;
use function mt_rand;

abstract class BaseCoral extends Transparent implements CoralMaterial{
abstract class BaseCoral extends CoveredFlowable implements CoralMaterial, CoveredByWater{
use CoralTypeTrait;
use CoveredByWaterTrait{
onNearbyBlockChange as onWaterBlockChange;
}

public function onNearbyBlockChange() : void{
$this->onWaterBlockChange();
if(!$this->dead){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, mt_rand(40, 200));
}
Expand All @@ -56,18 +62,7 @@ public function isAffectedBySilkTouch() : bool{
public function isSolid() : bool{ return false; }

protected function isCoveredWithWater() : bool{
$world = $this->position->getWorld();

$hasWater = false;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
$hasWater = true;
break;
}
}

//TODO: check water inside the block itself (not supported on the API yet)
return $hasWater;
return $this->waterCover !== null;
}

protected function recalculateCollisionBoxes() : array{ return []; }
Expand Down
27 changes: 26 additions & 1 deletion src/block/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ public function writeStateToWorld() : void{
throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
}
$chunk->setBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getStateId());
$chunk->setDisplacedBlockStateId($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getDisplacedBlock()?->getStateId() ?? self::EMPTY_STATE_ID);

$tileType = $this->idInfo->getTileClass();
$oldTile = $world->getTile($this->position);
Expand All @@ -408,6 +409,15 @@ public function writeStateToWorld() : void{
}
}

/**
* @internal
* Returns a block which has been displaced by another block to the second layer.
* Used in waterlogging and snowlogging.
*/
public function getDisplacedBlock() : ?Block{
return null;
}

/**
* Returns whether this block can be placed when obtained as an item.
*/
Expand Down Expand Up @@ -487,7 +497,7 @@ public function onBreak(Item $item, ?Player $player = null, array &$returnedItem
if(($t = $world->getTile($this->position)) !== null){
$t->onBlockDestroyed();
}
$world->setBlock($this->position, VanillaBlocks::AIR());
$world->setBlock($this->position, $this->getDisplacedBlock() ?? VanillaBlocks::AIR());
return true;
}

Expand Down Expand Up @@ -520,6 +530,17 @@ public function onScheduledUpdate() : void{

}

/**
* @internal
* Similar to onScheduledUpdate(), but called for "displaced" blocks (e.g. water),
* placed at the same position with their owning blocks.
*
* This is internal and used only in things such as waterlogging, plugins should NOT use this.
*/
public function onDisplacedScheduledUpdate() : void{
//NOOP
}

/**
* Do actions when interacted by Item. Returns if it has done anything
*
Expand Down Expand Up @@ -623,6 +644,10 @@ final public function getPosition() : Position{
final public function position(World $world, int $x, int $y, int $z) : void{
$this->position = new Position($x, $y, $z, $world);
$this->collisionBoxes = null;

if(($block = $this->getDisplacedBlock()) !== null){
$block->position($world, $x, $y, $z);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit sketch. In the default case, this will allocate a new air block, position it, and then immediately throw it away. That doesn't seem good for performance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be fine now, as getDisplacedBlock() now returns null in most cases.

}
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/block/BrewingStand.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

use pocketmine\block\tile\BrewingStand as TileBrewingStand;
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\block\utils\CoveredByWater;
use pocketmine\block\utils\CoveredByWaterTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
Expand All @@ -36,7 +38,8 @@
use function array_key_exists;
use function spl_object_id;

class BrewingStand extends Transparent{
class BrewingStand extends Transparent implements CoveredByWater{
use CoveredByWaterTrait;

/**
* @var BrewingStandSlot[]
Expand Down
21 changes: 19 additions & 2 deletions src/block/Button.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

use pocketmine\block\utils\AnyFacing;
use pocketmine\block\utils\AnyFacingTrait;
use pocketmine\block\utils\CoveredByWater;
use pocketmine\block\utils\CoveredByWaterTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
use pocketmine\math\Facing;
Expand All @@ -34,8 +37,12 @@
use pocketmine\world\sound\RedstonePowerOffSound;
use pocketmine\world\sound\RedstonePowerOnSound;

abstract class Button extends Flowable implements AnyFacing{
abstract class Button extends Transparent implements AnyFacing, CoveredByWater{
use AnyFacingTrait;
use CoveredByWaterTrait{
place as waterPlace;
onNearbyBlockChange as onWaterBlockChange;
}

protected bool $pressed = false;

Expand All @@ -55,7 +62,7 @@ public function setPressed(bool $pressed) : self{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedAt($blockReplace, $face)){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
return $this->waterPlace($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
}
Expand Down Expand Up @@ -84,11 +91,21 @@ public function onScheduledUpdate() : void{
}

public function onNearbyBlockChange() : void{
$this->onWaterBlockChange();

if(!$this->canBeSupportedAt($this, $this->facing)){
$this->position->getWorld()->useBreakOn($this->position);
}
}

public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}

protected function recalculateCollisionBoxes() : array{
return [];
}

private function canBeSupportedAt(Block $block, int $face) : bool{
return $block->getAdjacentSupportType(Facing::opposite($face))->hasCenterSupport();
}
Expand Down
43 changes: 29 additions & 14 deletions src/block/Campfire.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

use pocketmine\block\inventory\CampfireInventory;
use pocketmine\block\tile\Campfire as TileCampfire;
use pocketmine\block\utils\CoveredByWater;
use pocketmine\block\utils\CoveredByWaterTrait;
use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\Lightable;
Expand Down Expand Up @@ -61,13 +63,19 @@
use function min;
use function mt_rand;

class Campfire extends Transparent implements Lightable, HorizontalFacing{
class Campfire extends Transparent implements Lightable, HorizontalFacing, CoveredByWater{
use HorizontalFacingTrait{
HorizontalFacingTrait::describeBlockOnlyState as encodeFacingState;
}
use LightableTrait{
LightableTrait::describeBlockOnlyState as encodeLitState;
}
use CoveredByWaterTrait{
place as waterPlace;
readStateFromWorld as readWaterStateFromWorld;
onNearbyBlockChange as onWaterBlockChange;
onEntityInside as onWaterEntityInside;
}

private const UPDATE_INTERVAL_TICKS = 10;

Expand All @@ -90,6 +98,9 @@ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{

public function readStateFromWorld() : Block{
parent::readStateFromWorld();

$this->readWaterStateFromWorld();

$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileCampfire){
$this->inventory = $tile->getInventory();
Expand Down Expand Up @@ -179,22 +190,24 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
$this->facing = $player->getHorizontalFacing();
}
$this->lit = true;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
return $this->waterPlace($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}

public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if(!$this->lit){
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
$item->pop();
$this->ignite();
$this->position->getWorld()->addSound($this->position, new BlazeShootSound());
return true;
}elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
if($item instanceof Durable){
$item->applyDamage(1);
if($this->waterCover === null){
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
$item->pop();
$this->ignite();
$this->position->getWorld()->addSound($this->position, new BlazeShootSound());
return true;
}elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
if($item instanceof Durable){
$item->applyDamage(1);
}
$this->ignite();
return true;
}
$this->ignite();
return true;
}
}elseif($item instanceof Shovel){
$item->applyDamage(1);
Expand All @@ -215,13 +228,15 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
}

public function onNearbyBlockChange() : void{
if($this->lit && $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){
$this->onWaterBlockChange();
if($this->lit && ($this->waterCover !== null || $this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER)){
$this->extinguish();
//TODO: Waterlogging
}
}

public function onEntityInside(Entity $entity) : bool{
$this->onWaterEntityInside($entity);

if(!$this->lit){
if($entity->isOnFire()){
$this->ignite();
Expand Down
4 changes: 3 additions & 1 deletion src/block/Cauldron.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use pocketmine\block\tile\Cauldron as TileCauldron;
use pocketmine\block\utils\SupportType;
use pocketmine\block\utils\WaterCoverHelper;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\Potion;
Expand Down Expand Up @@ -95,7 +96,8 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player

public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if($world->getBlock($this->position->up())->getTypeId() === BlockTypeIds::WATER){
$block = $world->getBlock($this->position->up());
if(WaterCoverHelper::isWater($block)){
$cauldron = VanillaBlocks::WATER_CAULDRON()->setFillLevel(FillableCauldron::MAX_FILL_LEVEL);
$world->setBlock($this->position, $cauldron);
$world->addSound($this->position->add(0.5, 0.5, 0.5), $cauldron->getFillSound());
Expand Down
39 changes: 39 additions & 0 deletions src/block/CoveredFlowable.php
Original file line number Diff line number Diff line change
@@ -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\CoveredByWater;
use pocketmine\math\Vector3;

/**
* Flowable blocks that can be waterlogged.
*/
abstract class CoveredFlowable extends Flowable implements CoveredByWater{

public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return
($this->canBeCovered() && $blockReplace instanceof Water && ($blockReplace->isSource() || $this->canBeCoveredByFlowing())) ||
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
}
19 changes: 18 additions & 1 deletion src/block/Door.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

namespace pocketmine\block;

use pocketmine\block\utils\CoveredByWater;
use pocketmine\block\utils\CoveredByWaterTrait;
use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
Expand All @@ -35,8 +37,12 @@
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\DoorSound;

class Door extends Transparent implements HorizontalFacing{
class Door extends Transparent implements HorizontalFacing, CoveredByWater{
use HorizontalFacingTrait;
use CoveredByWaterTrait{
readStateFromWorld as readWaterStateFromWorld;
onNearbyBlockChange as onWaterBlockChange;
}

protected bool $top = false;
protected bool $hingeRight = false;
Expand All @@ -52,6 +58,8 @@ protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
public function readStateFromWorld() : Block{
parent::readStateFromWorld();

$this->readWaterStateFromWorld();

$this->collisionBoxes = null;

//copy door properties from other half
Expand Down Expand Up @@ -106,6 +114,8 @@ public function getSupportType(int $facing) : SupportType{
}

public function onNearbyBlockChange() : void{
$this->onWaterBlockChange();

if(!$this->canBeSupportedAt($this) && !$this->getSide(Facing::DOWN) instanceof Door){ //Replace with common break method
$this->position->getWorld()->useBreakOn($this->position); //this will delete both halves if they exist
}
Expand All @@ -132,6 +142,13 @@ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Blo
$topHalf = clone $this;
$topHalf->top = true;

if($blockReplace instanceof Water && $blockReplace->isSource()){
$this->waterCover = clone $blockReplace;
}
if($blockUp instanceof Water && $blockUp->isSource()){
$topHalf->waterCover = clone $blockUp;
}

$tx->addBlock($blockReplace->position, $this)->addBlock($blockUp->position, $topHalf);
return true;
}
Expand Down
Loading