diff --git a/src/block/PressurePlate.php b/src/block/PressurePlate.php index 4df0bf927..d67433a75 100644 --- a/src/block/PressurePlate.php +++ b/src/block/PressurePlate.php @@ -24,14 +24,33 @@ declare(strict_types=1); namespace pocketmine\block; 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{ + private readonly int $deactivationDelayTicks; + + public function __construct( + BlockIdentifier $idInfo, + string $name, + BlockTypeInfo $typeInfo, + int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6 + ){ + parent::__construct($idInfo, $name, $typeInfo); + $this->deactivationDelayTicks = $deactivationDelayTicks; + } + public function isSolid() : bool{ return false; } @@ -61,5 +80,89 @@ abstract class PressurePlate extends Transparent{ } } - //TODO + public function hasEntityCollision() : bool{ + return true; + } + + public function onEntityInside(Entity $entity) : bool{ + if(!$this->hasOutputSignal()){ + $this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 0); + } + return true; + } + + /** + * Returns the AABB that entities must intersect to activate the pressure plate. + * Note that this is not the same as the collision box (pressure plate doesn't have one), nor the visual bounding + * box. The activation area has a height of 0.25 blocks. + */ + protected function getActivationBox() : AxisAlignedBB{ + return AxisAlignedBB::one() + ->squash(Axis::X, 1 / 8) + ->squash(Axis::Z, 1 / 8) + ->trim(Facing::UP, 3 / 4) + ->offset($this->position->x, $this->position->y, $this->position->z); + } + + /** + * TODO: make this abstract in PM6 + */ + protected function hasOutputSignal() : bool{ + return false; + } + + /** + * TODO: make this abstract in PM6 + * + * @param Entity[] $entities + * + * @return mixed[] + * @phpstan-return array{Block, ?bool} + */ + protected function calculatePlateState(array $entities) : array{ + return [$this, null]; + } + + /** + * Filters entities which don't affect the pressure plate state from the given list. + * + * @param Entity[] $entities + * @return Entity[] + */ + protected function filterIrrelevantEntities(array $entities) : array{ + return $entities; + } + + public function onScheduledUpdate() : void{ + $world = $this->position->getWorld(); + + $intersectionAABB = $this->getActivationBox(); + $activatingEntities = $this->filterIrrelevantEntities($world->getNearbyEntities($intersectionAABB)); + + //if an irrelevant entity is inside the full cube space of the pressure plate but not activating the plate, + //it will cause scheduled updates on the plate every tick. We don't want to fire events in this case if the + //plate is already deactivated. + if(count($activatingEntities) > 0 || $this->hasOutputSignal()){ + [$newState, $pressedChange] = $this->calculatePlateState($activatingEntities); + + //always call this, in case there are new entities on the plate + if(PressurePlateUpdateEvent::hasHandlers()){ + $ev = new PressurePlateUpdateEvent($this, $newState, $activatingEntities); + $ev->call(); + $newState = $ev->isCancelled() ? null : $ev->getNewState(); + } + if($newState !== null){ + $world->setBlock($this->position, $newState); + if($pressedChange !== null){ + $world->addSound($this->position, $pressedChange ? + new PressurePlateActivateSound($this) : + new PressurePlateDeactivateSound($this) + ); + } + } + if($pressedChange ?? $this->hasOutputSignal()){ + $world->scheduleDelayedBlockUpdate($this->position, $this->deactivationDelayTicks); + } + } + } } diff --git a/src/block/SimplePressurePlate.php b/src/block/SimplePressurePlate.php index e4278410d..3429b9b5d 100644 --- a/src/block/SimplePressurePlate.php +++ b/src/block/SimplePressurePlate.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\data\runtime\RuntimeDataDescriber; +use function count; abstract class SimplePressurePlate extends PressurePlate{ protected bool $pressed = false; @@ -39,4 +40,19 @@ abstract class SimplePressurePlate extends PressurePlate{ $this->pressed = $pressed; return $this; } + + protected function hasOutputSignal() : bool{ + return $this->pressed; + } + + protected function calculatePlateState(array $entities) : array{ + $newPressed = count($entities) > 0; + if($newPressed === $this->pressed){ + return [$this, null]; + } + return [ + (clone $this)->setPressed($newPressed), + $newPressed + ]; + } } diff --git a/src/block/StonePressurePlate.php b/src/block/StonePressurePlate.php index 626e6d885..5ddc5a599 100644 --- a/src/block/StonePressurePlate.php +++ b/src/block/StonePressurePlate.php @@ -23,6 +23,13 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\entity\Entity; +use pocketmine\entity\Living; +use function array_filter; + class StonePressurePlate extends SimplePressurePlate{ + protected function filterIrrelevantEntities(array $entities) : array{ + return array_filter($entities, fn(Entity $e) => $e instanceof Living); //TODO: armor stands should activate stone plates too + } } diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index cb612031f..a41c3985b 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -1114,8 +1114,20 @@ final class VanillaBlocks{ self::register("lily_pad", new WaterLily(new BID(Ids::LILY_PAD), "Lily Pad", new Info(BreakInfo::instant()))); $weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD())); - self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy(new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY), "Weighted Pressure Plate Heavy", $weightedPressurePlateBreakInfo)); - self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight(new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT), "Weighted Pressure Plate Light", $weightedPressurePlateBreakInfo)); + self::register("weighted_pressure_plate_heavy", new WeightedPressurePlateHeavy( + new BID(Ids::WEIGHTED_PRESSURE_PLATE_HEAVY), + "Weighted Pressure Plate Heavy", + $weightedPressurePlateBreakInfo, + deactivationDelayTicks: 10, + signalStrengthFactor: 0.1 + )); + self::register("weighted_pressure_plate_light", new WeightedPressurePlateLight( + new BID(Ids::WEIGHTED_PRESSURE_PLATE_LIGHT), + "Weighted Pressure Plate Light", + $weightedPressurePlateBreakInfo, + deactivationDelayTicks: 10, + signalStrengthFactor: 1.0 + )); self::register("wheat", new Wheat(new BID(Ids::WHEAT), "Wheat Block", new Info(BreakInfo::instant()))); $leavesBreakInfo = new Info(new class(0.2, ToolType::HOE) extends BreakInfo{ @@ -1266,7 +1278,7 @@ final class VanillaBlocks{ self::register($idName("door"), new WoodenDoor(WoodLikeBlockIdHelper::getDoorIdentifier($woodType), $name . " Door", $woodenDoorBreakInfo, $woodType)); self::register($idName("button"), new WoodenButton(WoodLikeBlockIdHelper::getButtonIdentifier($woodType), $name . " Button", $woodenButtonBreakInfo, $woodType)); - self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType)); + self::register($idName("pressure_plate"), new WoodenPressurePlate(WoodLikeBlockIdHelper::getPressurePlateIdentifier($woodType), $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20)); self::register($idName("trapdoor"), new WoodenTrapdoor(WoodLikeBlockIdHelper::getTrapdoorIdentifier($woodType), $name . " Trapdoor", $woodenDoorBreakInfo, $woodType)); [$floorSignId, $wallSignId, $signAsItem] = WoodLikeBlockIdHelper::getSignInfo($woodType); @@ -1491,7 +1503,7 @@ final class VanillaBlocks{ $prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : ""); self::register("polished_blackstone", new Opaque(new BID(Ids::POLISHED_BLACKSTONE), $prefix(""), $blackstoneBreakInfo)); self::register("polished_blackstone_button", new StoneButton(new BID(Ids::POLISHED_BLACKSTONE_BUTTON), $prefix("Button"), new Info(BreakInfo::pickaxe(0.5)))); - self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD())))); + self::register("polished_blackstone_pressure_plate", new StonePressurePlate(new BID(Ids::POLISHED_BLACKSTONE_PRESSURE_PLATE), $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD())), 20)); self::register("polished_blackstone_slab", new Slab(new BID(Ids::POLISHED_BLACKSTONE_SLAB), $prefix(""), $slabBreakInfo)); self::register("polished_blackstone_stairs", new Stair(new BID(Ids::POLISHED_BLACKSTONE_STAIRS), $prefix("Stairs"), $blackstoneBreakInfo)); self::register("polished_blackstone_wall", new Wall(new BID(Ids::POLISHED_BLACKSTONE_WALL), $prefix("Wall"), $blackstoneBreakInfo)); diff --git a/src/block/WeightedPressurePlate.php b/src/block/WeightedPressurePlate.php index bdfae5082..726b31f6b 100644 --- a/src/block/WeightedPressurePlate.php +++ b/src/block/WeightedPressurePlate.php @@ -24,7 +24,40 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\AnalogRedstoneSignalEmitterTrait; +use function ceil; +use function count; +use function max; +use function min; -abstract class WeightedPressurePlate extends PressurePlate{ +class WeightedPressurePlate extends PressurePlate{ use AnalogRedstoneSignalEmitterTrait; + + private readonly float $signalStrengthFactor; + + /** + * @param float $signalStrengthFactor Number of entities on the plate is divided by this value to get signal strength + */ + public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, int $deactivationDelayTicks, float $signalStrengthFactor = 1.0){ + parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks); + $this->signalStrengthFactor = $signalStrengthFactor; + } + + protected function hasOutputSignal() : bool{ + return $this->signalStrength > 0; + } + + protected function calculatePlateState(array $entities) : array{ + $newSignalStrength = min(15, max(0, + (int) ceil(count($entities) * $this->signalStrengthFactor) + )); + if($newSignalStrength === $this->signalStrength){ + return [$this, null]; + } + $wasActive = $this->signalStrength !== 0; + $isActive = $newSignalStrength !== 0; + return [ + (clone $this)->setOutputSignalStrength($newSignalStrength), + $wasActive !== $isActive ? $isActive : null + ]; + } } diff --git a/src/block/WeightedPressurePlateHeavy.php b/src/block/WeightedPressurePlateHeavy.php index 390297436..9a8d1c31b 100644 --- a/src/block/WeightedPressurePlateHeavy.php +++ b/src/block/WeightedPressurePlateHeavy.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +/** + * @deprecated + */ class WeightedPressurePlateHeavy extends WeightedPressurePlate{ } diff --git a/src/block/WeightedPressurePlateLight.php b/src/block/WeightedPressurePlateLight.php index 458c07e1a..85c13d438 100644 --- a/src/block/WeightedPressurePlateLight.php +++ b/src/block/WeightedPressurePlateLight.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\block; +/** + * @deprecated + */ class WeightedPressurePlateLight extends WeightedPressurePlate{ } diff --git a/src/block/WoodenPressurePlate.php b/src/block/WoodenPressurePlate.php index baaf44c2c..a629c2f1c 100644 --- a/src/block/WoodenPressurePlate.php +++ b/src/block/WoodenPressurePlate.php @@ -23,11 +23,23 @@ declare(strict_types=1); namespace pocketmine\block; +use pocketmine\block\utils\WoodType; use pocketmine\block\utils\WoodTypeTrait; class WoodenPressurePlate extends SimplePressurePlate{ use WoodTypeTrait; + public function __construct( + BlockIdentifier $idInfo, + string $name, + BlockTypeInfo $typeInfo, + WoodType $woodType, + int $deactivationDelayTicks = 20 //TODO: make this mandatory in PM6 + ){ + $this->woodType = $woodType; + parent::__construct($idInfo, $name, $typeInfo, $deactivationDelayTicks); + } + public function getFuelTime() : int{ return 300; } diff --git a/src/event/block/PressurePlateUpdateEvent.php b/src/event/block/PressurePlateUpdateEvent.php new file mode 100644 index 000000000..485a3a6be --- /dev/null +++ b/src/event/block/PressurePlateUpdateEvent.php @@ -0,0 +1,53 @@ +activatingEntities; } +} diff --git a/src/world/sound/PressurePlateActivateSound.php b/src/world/sound/PressurePlateActivateSound.php new file mode 100644 index 000000000..fac24e285 --- /dev/null +++ b/src/world/sound/PressurePlateActivateSound.php @@ -0,0 +1,46 @@ +getBlockTranslator()->internalIdToNetworkId($this->block->getStateId()) + )]; + } +} diff --git a/src/world/sound/PressurePlateDeactivateSound.php b/src/world/sound/PressurePlateDeactivateSound.php new file mode 100644 index 000000000..895bb8b8a --- /dev/null +++ b/src/world/sound/PressurePlateDeactivateSound.php @@ -0,0 +1,46 @@ +getBlockTranslator()->internalIdToNetworkId($this->block->getStateId()) + )]; + } +}