mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-07 12:18:46 +00:00
Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/15288247521
This commit is contained in:
commit
d5a1007c80
@ -786,8 +786,9 @@ final class BlockTypeIds{
|
|||||||
public const RESIN_BRICKS = 10756;
|
public const RESIN_BRICKS = 10756;
|
||||||
public const RESIN_CLUMP = 10757;
|
public const RESIN_CLUMP = 10757;
|
||||||
public const CHISELED_RESIN_BRICKS = 10758;
|
public const CHISELED_RESIN_BRICKS = 10758;
|
||||||
|
public const RESPAWN_ANCHOR = 10759;
|
||||||
|
|
||||||
public const FIRST_UNUSED_BLOCK_ID = 10759;
|
public const FIRST_UNUSED_BLOCK_ID = 10760;
|
||||||
|
|
||||||
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
||||||
|
|
||||||
|
123
src/block/RespawnAnchor.php
Normal file
123
src/block/RespawnAnchor.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?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\data\runtime\RuntimeDataDescriber;
|
||||||
|
use pocketmine\event\block\BlockPreExplodeEvent;
|
||||||
|
use pocketmine\event\player\PlayerRespawnAnchorUseEvent;
|
||||||
|
use pocketmine\item\Item;
|
||||||
|
use pocketmine\item\ItemTypeIds;
|
||||||
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
|
use pocketmine\math\Vector3;
|
||||||
|
use pocketmine\player\Player;
|
||||||
|
use pocketmine\utils\TextFormat;
|
||||||
|
use pocketmine\world\Explosion;
|
||||||
|
use pocketmine\world\Position;
|
||||||
|
use pocketmine\world\sound\RespawnAnchorChargeSound;
|
||||||
|
use pocketmine\world\sound\RespawnAnchorSetSpawnSound;
|
||||||
|
|
||||||
|
final class RespawnAnchor extends Opaque{
|
||||||
|
private const MIN_CHARGES = 0;
|
||||||
|
private const MAX_CHARGES = 4;
|
||||||
|
|
||||||
|
private int $charges = self::MIN_CHARGES;
|
||||||
|
|
||||||
|
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||||
|
$w->boundedIntAuto(self::MIN_CHARGES, self::MAX_CHARGES, $this->charges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCharges() : int{
|
||||||
|
return $this->charges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return $this */
|
||||||
|
public function setCharges(int $charges) : self{
|
||||||
|
if($charges < self::MIN_CHARGES || $charges > self::MAX_CHARGES){
|
||||||
|
throw new \InvalidArgumentException("Charges must be between " . self::MIN_CHARGES . " and " . self::MAX_CHARGES . ", given: $charges");
|
||||||
|
}
|
||||||
|
$this->charges = $charges;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLightLevel() : int{
|
||||||
|
return $this->charges > 0 ? ($this->charges * 4) - 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||||
|
if($item->getTypeId() === ItemTypeIds::fromBlockTypeId(BlockTypeIds::GLOWSTONE) && $this->charges < self::MAX_CHARGES){
|
||||||
|
$this->position->getWorld()->setBlock($this->position, $this->setCharges($this->charges + 1));
|
||||||
|
$this->position->getWorld()->addSound($this->position, new RespawnAnchorChargeSound());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->charges > self::MIN_CHARGES){
|
||||||
|
if($player === null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ev = new PlayerRespawnAnchorUseEvent($player, $this, PlayerRespawnAnchorUseEvent::ACTION_EXPLODE);
|
||||||
|
$ev->call();
|
||||||
|
if($ev->isCancelled()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch($ev->getAction()){
|
||||||
|
case PlayerRespawnAnchorUseEvent::ACTION_EXPLODE:
|
||||||
|
$this->explode($player);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case PlayerRespawnAnchorUseEvent::ACTION_SET_SPAWN:
|
||||||
|
if($player->getSpawn() !== null && $player->getSpawn()->equals($this->position)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$player->setSpawn($this->position);
|
||||||
|
$this->position->getWorld()->addSound($this->position, new RespawnAnchorSetSpawnSound());
|
||||||
|
$player->sendMessage(KnownTranslationFactory::tile_respawn_anchor_respawnSet()->prefix(TextFormat::GRAY));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function explode(?Player $player) : void{
|
||||||
|
$ev = new BlockPreExplodeEvent($this, 5, $player);
|
||||||
|
$ev->setIncendiary(true);
|
||||||
|
|
||||||
|
$ev->call();
|
||||||
|
if($ev->isCancelled()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->position->getWorld()->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
|
|
||||||
|
$explosion = new Explosion(Position::fromObject($this->position->add(0.5, 0.5, 0.5), $this->position->getWorld()), $ev->getRadius(), $this);
|
||||||
|
$explosion->setFireChance($ev->getFireChance());
|
||||||
|
|
||||||
|
if($ev->isBlockBreaking()){
|
||||||
|
$explosion->explodeA();
|
||||||
|
}
|
||||||
|
$explosion->explodeB();
|
||||||
|
}
|
||||||
|
}
|
@ -694,6 +694,7 @@ use function strtolower;
|
|||||||
* @method static Stair RESIN_BRICK_STAIRS()
|
* @method static Stair RESIN_BRICK_STAIRS()
|
||||||
* @method static Wall RESIN_BRICK_WALL()
|
* @method static Wall RESIN_BRICK_WALL()
|
||||||
* @method static ResinClump RESIN_CLUMP()
|
* @method static ResinClump RESIN_CLUMP()
|
||||||
|
* @method static RespawnAnchor RESPAWN_ANCHOR()
|
||||||
* @method static DoublePlant ROSE_BUSH()
|
* @method static DoublePlant ROSE_BUSH()
|
||||||
* @method static Sand SAND()
|
* @method static Sand SAND()
|
||||||
* @method static Opaque SANDSTONE()
|
* @method static Opaque SANDSTONE()
|
||||||
@ -1647,6 +1648,8 @@ final class VanillaBlocks{
|
|||||||
self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo));
|
self::register("warped_roots", fn(BID $id) => new NetherRoots($id, "Warped Roots", $netherRootsInfo));
|
||||||
|
|
||||||
self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
|
self::register("chain", fn(BID $id) => new Chain($id, "Chain", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 30.0))));
|
||||||
|
|
||||||
|
self::register("respawn_anchor", fn(BID $id) => new RespawnAnchor($id, "Respawn Anchor", new Info(BreakInfo::pickaxe(50.0, ToolTier::DIAMOND, 6000.0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function registerBlocksR17() : void{
|
private static function registerBlocksR17() : void{
|
||||||
|
@ -122,6 +122,7 @@ use pocketmine\block\RedstoneRepeater;
|
|||||||
use pocketmine\block\RedstoneTorch;
|
use pocketmine\block\RedstoneTorch;
|
||||||
use pocketmine\block\RedstoneWire;
|
use pocketmine\block\RedstoneWire;
|
||||||
use pocketmine\block\ResinClump;
|
use pocketmine\block\ResinClump;
|
||||||
|
use pocketmine\block\RespawnAnchor;
|
||||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||||
use pocketmine\block\Sapling;
|
use pocketmine\block\Sapling;
|
||||||
use pocketmine\block\SeaPickle;
|
use pocketmine\block\SeaPickle;
|
||||||
@ -1753,6 +1754,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
|||||||
return Writer::create(Ids::RESIN_CLUMP)
|
return Writer::create(Ids::RESIN_CLUMP)
|
||||||
->writeFacingFlags($block->getFaces());
|
->writeFacingFlags($block->getFaces());
|
||||||
});
|
});
|
||||||
|
$this->map(Blocks::RESPAWN_ANCHOR(), function(RespawnAnchor $block) : Writer{
|
||||||
|
return Writer::create(Ids::RESPAWN_ANCHOR)
|
||||||
|
->writeInt(StateNames::RESPAWN_ANCHOR_CHARGE, $block->getCharges());
|
||||||
|
});
|
||||||
$this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH)));
|
$this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH)));
|
||||||
$this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
|
$this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
|
||||||
$this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS);
|
$this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS);
|
||||||
|
@ -1717,6 +1717,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
|||||||
$this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS());
|
$this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS());
|
||||||
$this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in));
|
$this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in));
|
||||||
$this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags()));
|
$this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags()));
|
||||||
|
$this->map(Ids::RESPAWN_ANCHOR, function(Reader $in) : Block{
|
||||||
|
return Blocks::RESPAWN_ANCHOR()
|
||||||
|
->setCharges($in->readBoundedInt(StateNames::RESPAWN_ANCHOR_CHARGE, 0, 4));
|
||||||
|
});
|
||||||
$this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB());
|
$this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB());
|
||||||
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
|
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
|
||||||
$this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in));
|
$this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in));
|
||||||
|
@ -129,7 +129,7 @@ class EndCrystal extends Entity implements Explosive{
|
|||||||
$ev = new EntityPreExplodeEvent($this, 6);
|
$ev = new EntityPreExplodeEvent($this, 6);
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
|
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this, $ev->getFireChance());
|
||||||
if($ev->isBlockBreaking()){
|
if($ev->isBlockBreaking()){
|
||||||
$explosion->explodeA();
|
$explosion->explodeA();
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ class PrimedTNT extends Entity implements Explosive{
|
|||||||
$ev->call();
|
$ev->call();
|
||||||
if(!$ev->isCancelled()){
|
if(!$ev->isCancelled()){
|
||||||
//TODO: deal with underwater TNT (underwater TNT treats water as if it has a blast resistance of 0)
|
//TODO: deal with underwater TNT (underwater TNT treats water as if it has a blast resistance of 0)
|
||||||
$explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this);
|
$explosion = new Explosion(Position::fromObject($this->location->add(0, $this->size->getHeight() / 2, 0), $this->getWorld()), $ev->getRadius(), $this, $ev->getFireChance());
|
||||||
if($ev->isBlockBreaking()){
|
if($ev->isBlockBreaking()){
|
||||||
$explosion->explodeA();
|
$explosion->explodeA();
|
||||||
}
|
}
|
||||||
|
122
src/event/block/BlockExplodeEvent.php
Normal file
122
src/event/block/BlockExplodeEvent.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\event\block;
|
||||||
|
|
||||||
|
use pocketmine\block\Block;
|
||||||
|
use pocketmine\event\Cancellable;
|
||||||
|
use pocketmine\event\CancellableTrait;
|
||||||
|
use pocketmine\utils\Utils;
|
||||||
|
use pocketmine\world\Position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a block explodes, after explosion impact has been calculated.
|
||||||
|
*
|
||||||
|
* @see BlockPreExplodeEvent
|
||||||
|
*/
|
||||||
|
class BlockExplodeEvent extends BlockEvent implements Cancellable{
|
||||||
|
use CancellableTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Block[] $blocks
|
||||||
|
* @param Block[] $ignitions
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
Block $block,
|
||||||
|
private Position $position,
|
||||||
|
private array $blocks,
|
||||||
|
private float $yield,
|
||||||
|
private array $ignitions
|
||||||
|
){
|
||||||
|
parent::__construct($block);
|
||||||
|
|
||||||
|
Utils::checkFloatNotInfOrNaN("yield", $yield);
|
||||||
|
if($yield < 0.0 || $yield > 100.0){
|
||||||
|
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPosition() : Position{
|
||||||
|
return $this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the percentage chance of drops from each block destroyed by the explosion.
|
||||||
|
*
|
||||||
|
* @return float 0-100
|
||||||
|
*/
|
||||||
|
public function getYield() : float{
|
||||||
|
return $this->yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the percentage chance of drops from each block destroyed by the explosion.
|
||||||
|
*
|
||||||
|
* @param float $yield 0-100
|
||||||
|
*/
|
||||||
|
public function setYield(float $yield) : void{
|
||||||
|
Utils::checkFloatNotInfOrNaN("yield", $yield);
|
||||||
|
if($yield < 0.0 || $yield > 100.0){
|
||||||
|
throw new \InvalidArgumentException("Yield must be in range 0.0 - 100.0");
|
||||||
|
}
|
||||||
|
$this->yield = $yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of blocks destroyed by the explosion.
|
||||||
|
*
|
||||||
|
* @return Block[]
|
||||||
|
*/
|
||||||
|
public function getAffectedBlocks() : array{
|
||||||
|
return $this->blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the blocks destroyed by the explosion.
|
||||||
|
*
|
||||||
|
* @param Block[] $blocks
|
||||||
|
*/
|
||||||
|
public function setAffectedBlocks(array $blocks) : void{
|
||||||
|
Utils::validateArrayValueType($blocks, fn(Block $block) => null);
|
||||||
|
$this->blocks = $blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of affected blocks that will be replaced by fire.
|
||||||
|
*
|
||||||
|
* @return Block[]
|
||||||
|
*/
|
||||||
|
public function getIgnitions() : array{
|
||||||
|
return $this->ignitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of blocks that will be replaced by fire.
|
||||||
|
*
|
||||||
|
* @param Block[] $ignitions
|
||||||
|
*/
|
||||||
|
public function setIgnitions(array $ignitions) : void{
|
||||||
|
Utils::validateArrayValueType($ignitions, fn(Block $block) => null);
|
||||||
|
$this->ignitions = $ignitions;
|
||||||
|
}
|
||||||
|
}
|
129
src/event/block/BlockPreExplodeEvent.php
Normal file
129
src/event/block/BlockPreExplodeEvent.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\event\block;
|
||||||
|
|
||||||
|
use pocketmine\block\Block;
|
||||||
|
use pocketmine\event\Cancellable;
|
||||||
|
use pocketmine\event\CancellableTrait;
|
||||||
|
use pocketmine\player\Player;
|
||||||
|
use pocketmine\utils\Utils;
|
||||||
|
use pocketmine\world\Explosion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a block wants to explode, before the explosion impact is calculated.
|
||||||
|
* This allows changing the explosion force, fire chance and whether it will destroy blocks.
|
||||||
|
*
|
||||||
|
* @see BlockExplodeEvent
|
||||||
|
*/
|
||||||
|
class BlockPreExplodeEvent extends BlockEvent implements Cancellable{
|
||||||
|
use CancellableTrait;
|
||||||
|
|
||||||
|
private bool $blockBreaking = true;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Block $block,
|
||||||
|
private float $radius,
|
||||||
|
private readonly ?Player $player = null,
|
||||||
|
private float $fireChance = 0.0
|
||||||
|
){
|
||||||
|
Utils::checkFloatNotInfOrNaN("radius", $radius);
|
||||||
|
if($radius <= 0){
|
||||||
|
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||||
|
}
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||||
|
}
|
||||||
|
parent::__construct($block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRadius() : float{
|
||||||
|
return $this->radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRadius(float $radius) : void{
|
||||||
|
Utils::checkFloatNotInfOrNaN("radius", $radius);
|
||||||
|
if($radius <= 0){
|
||||||
|
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||||
|
}
|
||||||
|
$this->radius = $radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBlockBreaking() : bool{
|
||||||
|
return $this->blockBreaking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBlockBreaking(bool $affectsBlocks) : void{
|
||||||
|
$this->blockBreaking = $affectsBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the explosion will create a fire.
|
||||||
|
*/
|
||||||
|
public function isIncendiary() : bool{
|
||||||
|
return $this->fireChance > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the explosion will create a fire by filling fireChance with default values.
|
||||||
|
*
|
||||||
|
* If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0).
|
||||||
|
*/
|
||||||
|
public function setIncendiary(bool $incendiary) : void{
|
||||||
|
if(!$incendiary){
|
||||||
|
$this->fireChance = 0;
|
||||||
|
}elseif($this->fireChance <= 0){
|
||||||
|
$this->fireChance = Explosion::DEFAULT_FIRE_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a chance between 0 and 1 of creating a fire.
|
||||||
|
*/
|
||||||
|
public function getFireChance() : float{
|
||||||
|
return $this->fireChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a chance between 0 and 1 of creating a fire.
|
||||||
|
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||||
|
*
|
||||||
|
* @param float $fireChance 0 ... 1
|
||||||
|
*/
|
||||||
|
public function setFireChance(float $fireChance) : void{
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||||
|
}
|
||||||
|
$this->fireChance = $fireChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the player who triggered the block explosion.
|
||||||
|
* Returns null if the block was exploded by other means.
|
||||||
|
*/
|
||||||
|
public function getPlayer() : ?Player{
|
||||||
|
return $this->player;
|
||||||
|
}
|
||||||
|
}
|
@ -43,13 +43,15 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Block[] $blocks
|
* @param Block[] $blocks
|
||||||
* @param float $yield 0-100
|
* @param float $yield 0-100
|
||||||
|
* @param Block[] $ignitions
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Entity $entity,
|
Entity $entity,
|
||||||
protected Position $position,
|
protected Position $position,
|
||||||
protected array $blocks,
|
protected array $blocks,
|
||||||
protected float $yield
|
protected float $yield,
|
||||||
|
private array $ignitions
|
||||||
){
|
){
|
||||||
$this->entity = $entity;
|
$this->entity = $entity;
|
||||||
if($yield < 0.0 || $yield > 100.0){
|
if($yield < 0.0 || $yield > 100.0){
|
||||||
@ -98,4 +100,23 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
|||||||
}
|
}
|
||||||
$this->yield = $yield;
|
$this->yield = $yield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of blocks that will be replaced by fire.
|
||||||
|
*
|
||||||
|
* @param Block[] $ignitions
|
||||||
|
*/
|
||||||
|
public function setIgnitions(array $ignitions) : void{
|
||||||
|
Utils::validateArrayValueType($ignitions, fn(Block $block) => null);
|
||||||
|
$this->ignitions = $ignitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of affected blocks that will be replaced by fire.
|
||||||
|
*
|
||||||
|
* @return Block[]
|
||||||
|
*/
|
||||||
|
public function getIgnitions() : array{
|
||||||
|
return $this->ignitions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ namespace pocketmine\event\entity;
|
|||||||
use pocketmine\entity\Entity;
|
use pocketmine\entity\Entity;
|
||||||
use pocketmine\event\Cancellable;
|
use pocketmine\event\Cancellable;
|
||||||
use pocketmine\event\CancellableTrait;
|
use pocketmine\event\CancellableTrait;
|
||||||
|
use pocketmine\utils\Utils;
|
||||||
|
use pocketmine\world\Explosion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an entity decides to explode, before the explosion's impact is calculated.
|
* Called when an entity decides to explode, before the explosion's impact is calculated.
|
||||||
@ -42,11 +44,16 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Entity $entity,
|
Entity $entity,
|
||||||
protected float $radius
|
protected float $radius,
|
||||||
|
private float $fireChance = 0.0,
|
||||||
){
|
){
|
||||||
if($radius <= 0){
|
if($radius <= 0){
|
||||||
throw new \InvalidArgumentException("Explosion radius must be positive");
|
throw new \InvalidArgumentException("Explosion radius must be positive");
|
||||||
}
|
}
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be between 0 and 1.");
|
||||||
|
}
|
||||||
$this->entity = $entity;
|
$this->entity = $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +68,47 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
|||||||
$this->radius = $radius;
|
$this->radius = $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the explosion will create a fire.
|
||||||
|
*/
|
||||||
|
public function isIncendiary() : bool{
|
||||||
|
return $this->fireChance > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the explosion will create a fire by filling fireChance with default values.
|
||||||
|
*
|
||||||
|
* If $incendiary is true, the fire chance will be filled only if explosion isn't currently creating a fire (if fire chance is 0).
|
||||||
|
*/
|
||||||
|
public function setIncendiary(bool $incendiary) : void{
|
||||||
|
if(!$incendiary){
|
||||||
|
$this->fireChance = 0;
|
||||||
|
}elseif($this->fireChance <= 0){
|
||||||
|
$this->fireChance = Explosion::DEFAULT_FIRE_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a chance between 0 and 1 of creating a fire.
|
||||||
|
*/
|
||||||
|
public function getFireChance() : float{
|
||||||
|
return $this->fireChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a chance between 0 and 1 of creating a fire.
|
||||||
|
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||||
|
*
|
||||||
|
* @param float $fireChance 0 ... 1
|
||||||
|
*/
|
||||||
|
public function setFireChance(float $fireChance) : void{
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be between 0 and 1.");
|
||||||
|
}
|
||||||
|
$this->fireChance = $fireChance;
|
||||||
|
}
|
||||||
|
|
||||||
public function isBlockBreaking() : bool{
|
public function isBlockBreaking() : bool{
|
||||||
return $this->blockBreaking;
|
return $this->blockBreaking;
|
||||||
}
|
}
|
||||||
|
56
src/event/player/PlayerRespawnAnchorUseEvent.php
Normal file
56
src/event/player/PlayerRespawnAnchorUseEvent.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\event\player;
|
||||||
|
|
||||||
|
use pocketmine\block\Block;
|
||||||
|
use pocketmine\event\Cancellable;
|
||||||
|
use pocketmine\event\CancellableTrait;
|
||||||
|
use pocketmine\player\Player;
|
||||||
|
|
||||||
|
class PlayerRespawnAnchorUseEvent extends PlayerEvent implements Cancellable{
|
||||||
|
use CancellableTrait;
|
||||||
|
|
||||||
|
public const ACTION_EXPLODE = 0;
|
||||||
|
public const ACTION_SET_SPAWN = 1;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Player $player,
|
||||||
|
protected Block $block,
|
||||||
|
private int $action = self::ACTION_EXPLODE
|
||||||
|
){
|
||||||
|
$this->player = $player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlock() : Block{
|
||||||
|
return $this->block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAction() : int{
|
||||||
|
return $this->action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAction(int $action) : void{
|
||||||
|
$this->action = $action;
|
||||||
|
}
|
||||||
|
}
|
@ -993,6 +993,7 @@ final class StringToItemParser extends StringToTParser{
|
|||||||
$result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL());
|
$result->registerBlock("resin_brick_wall", fn() => Blocks::RESIN_BRICK_WALL());
|
||||||
$result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS());
|
$result->registerBlock("resin_bricks", fn() => Blocks::RESIN_BRICKS());
|
||||||
$result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP());
|
$result->registerBlock("resin_clump", fn() => Blocks::RESIN_CLUMP());
|
||||||
|
$result->registerBlock("respawn_anchor", fn() => Blocks::RESPAWN_ANCHOR());
|
||||||
$result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
|
$result->registerBlock("rooted_dirt", fn() => Blocks::DIRT()->setDirtType(DirtType::ROOTED));
|
||||||
$result->registerBlock("rose", fn() => Blocks::POPPY());
|
$result->registerBlock("rose", fn() => Blocks::POPPY());
|
||||||
$result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH());
|
$result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH());
|
||||||
|
@ -27,6 +27,7 @@ use DateTimeImmutable;
|
|||||||
use pocketmine\block\BaseSign;
|
use pocketmine\block\BaseSign;
|
||||||
use pocketmine\block\Bed;
|
use pocketmine\block\Bed;
|
||||||
use pocketmine\block\BlockTypeTags;
|
use pocketmine\block\BlockTypeTags;
|
||||||
|
use pocketmine\block\RespawnAnchor;
|
||||||
use pocketmine\block\UnknownBlock;
|
use pocketmine\block\UnknownBlock;
|
||||||
use pocketmine\block\VanillaBlocks;
|
use pocketmine\block\VanillaBlocks;
|
||||||
use pocketmine\command\CommandSender;
|
use pocketmine\command\CommandSender;
|
||||||
@ -137,6 +138,7 @@ use pocketmine\world\sound\EntityAttackNoDamageSound;
|
|||||||
use pocketmine\world\sound\EntityAttackSound;
|
use pocketmine\world\sound\EntityAttackSound;
|
||||||
use pocketmine\world\sound\FireExtinguishSound;
|
use pocketmine\world\sound\FireExtinguishSound;
|
||||||
use pocketmine\world\sound\ItemBreakSound;
|
use pocketmine\world\sound\ItemBreakSound;
|
||||||
|
use pocketmine\world\sound\RespawnAnchorDepleteSound;
|
||||||
use pocketmine\world\sound\Sound;
|
use pocketmine\world\sound\Sound;
|
||||||
use pocketmine\world\World;
|
use pocketmine\world\World;
|
||||||
use pocketmine\YmlServerProperties;
|
use pocketmine\YmlServerProperties;
|
||||||
@ -2543,6 +2545,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
}
|
}
|
||||||
$this->logger->debug("Respawn position located, completing respawn");
|
$this->logger->debug("Respawn position located, completing respawn");
|
||||||
$ev = new PlayerRespawnEvent($this, $safeSpawn);
|
$ev = new PlayerRespawnEvent($this, $safeSpawn);
|
||||||
|
$spawnPosition = $ev->getRespawnPosition();
|
||||||
|
$spawnBlock = $spawnPosition->getWorld()->getBlock($spawnPosition);
|
||||||
|
if($spawnBlock instanceof RespawnAnchor){
|
||||||
|
if($spawnBlock->getCharges() > 0){
|
||||||
|
$spawnPosition->getWorld()->setBlock($spawnPosition, $spawnBlock->setCharges($spawnBlock->getCharges() - 1));
|
||||||
|
$spawnPosition->getWorld()->addSound($spawnPosition, new RespawnAnchorDepleteSound());
|
||||||
|
}else{
|
||||||
|
$defaultSpawn = $this->server->getWorldManager()->getDefaultWorld()?->getSpawnLocation();
|
||||||
|
if($defaultSpawn !== null){
|
||||||
|
$this->setSpawn($defaultSpawn);
|
||||||
|
$ev->setRespawnPosition($defaultSpawn);
|
||||||
|
$this->sendMessage(KnownTranslationFactory::tile_respawn_anchor_notValid()->prefix(TextFormat::GRAY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$ev->call();
|
$ev->call();
|
||||||
|
|
||||||
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
|
$realSpawn = Position::fromObject($ev->getRespawnPosition()->add(0.5, 0, 0.5), $ev->getRespawnPosition()->getWorld());
|
||||||
|
@ -26,16 +26,20 @@ namespace pocketmine\world;
|
|||||||
use pocketmine\block\Block;
|
use pocketmine\block\Block;
|
||||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||||
use pocketmine\block\TNT;
|
use pocketmine\block\TNT;
|
||||||
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\block\VanillaBlocks;
|
use pocketmine\block\VanillaBlocks;
|
||||||
use pocketmine\entity\Entity;
|
use pocketmine\entity\Entity;
|
||||||
|
use pocketmine\event\block\BlockExplodeEvent;
|
||||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||||
use pocketmine\event\entity\EntityDamageEvent;
|
use pocketmine\event\entity\EntityDamageEvent;
|
||||||
use pocketmine\event\entity\EntityExplodeEvent;
|
use pocketmine\event\entity\EntityExplodeEvent;
|
||||||
use pocketmine\item\VanillaItems;
|
use pocketmine\item\VanillaItems;
|
||||||
use pocketmine\math\AxisAlignedBB;
|
use pocketmine\math\AxisAlignedBB;
|
||||||
|
use pocketmine\math\Facing;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
|
use pocketmine\utils\Utils;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
use pocketmine\world\particle\HugeExplodeSeedParticle;
|
use pocketmine\world\particle\HugeExplodeSeedParticle;
|
||||||
use pocketmine\world\sound\ExplodeSound;
|
use pocketmine\world\sound\ExplodeSound;
|
||||||
@ -48,25 +52,36 @@ use function mt_rand;
|
|||||||
use function sqrt;
|
use function sqrt;
|
||||||
|
|
||||||
class Explosion{
|
class Explosion{
|
||||||
|
public const DEFAULT_FIRE_CHANCE = 1.0 / 3.0;
|
||||||
|
|
||||||
private int $rays = 16;
|
private int $rays = 16;
|
||||||
public World $world;
|
public World $world;
|
||||||
|
|
||||||
/** @var Block[] */
|
/**
|
||||||
|
* @var Block[]
|
||||||
|
* @phpstan-var array<int, Block>
|
||||||
|
*/
|
||||||
public array $affectedBlocks = [];
|
public array $affectedBlocks = [];
|
||||||
public float $stepLen = 0.3;
|
public float $stepLen = 0.3;
|
||||||
|
/** @var Block[] */
|
||||||
|
private array $fireIgnitions = [];
|
||||||
|
|
||||||
private SubChunkExplorer $subChunkExplorer;
|
private SubChunkExplorer $subChunkExplorer;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public Position $source,
|
public Position $source,
|
||||||
public float $radius,
|
public float $radius,
|
||||||
private Entity|Block|null $what = null
|
private Entity|Block|null $what = null,
|
||||||
|
private float $fireChance = 0.0
|
||||||
){
|
){
|
||||||
if(!$this->source->isValid()){
|
if(!$this->source->isValid()){
|
||||||
throw new \InvalidArgumentException("Position does not have a valid world");
|
throw new \InvalidArgumentException("Position does not have a valid world");
|
||||||
}
|
}
|
||||||
$this->world = $this->source->getWorld();
|
$this->world = $this->source->getWorld();
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||||
|
}
|
||||||
if($radius <= 0){
|
if($radius <= 0){
|
||||||
throw new \InvalidArgumentException("Explosion radius must be greater than 0, got $radius");
|
throw new \InvalidArgumentException("Explosion radius must be greater than 0, got $radius");
|
||||||
}
|
}
|
||||||
@ -85,6 +100,7 @@ class Explosion{
|
|||||||
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
||||||
|
|
||||||
$mRays = $this->rays - 1;
|
$mRays = $this->rays - 1;
|
||||||
|
$incendiary = $this->fireChance > 0;
|
||||||
for($i = 0; $i < $this->rays; ++$i){
|
for($i = 0; $i < $this->rays; ++$i){
|
||||||
for($j = 0; $j < $this->rays; ++$j){
|
for($j = 0; $j < $this->rays; ++$j){
|
||||||
for($k = 0; $k < $this->rays; ++$k){
|
for($k = 0; $k < $this->rays; ++$k){
|
||||||
@ -127,7 +143,12 @@ class Explosion{
|
|||||||
$_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false);
|
$_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false);
|
||||||
foreach($_block->getAffectedBlocks() as $_affectedBlock){
|
foreach($_block->getAffectedBlocks() as $_affectedBlock){
|
||||||
$_affectedBlockPos = $_affectedBlock->getPosition();
|
$_affectedBlockPos = $_affectedBlock->getPosition();
|
||||||
$this->affectedBlocks[World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z)] = $_affectedBlock;
|
$posHash = World::blockHash($_affectedBlockPos->x, $_affectedBlockPos->y, $_affectedBlockPos->z);
|
||||||
|
$this->affectedBlocks[$posHash] = $_affectedBlock;
|
||||||
|
|
||||||
|
if($incendiary && Utils::getRandomFloat() <= $this->fireChance){
|
||||||
|
$this->fireIgnitions[$posHash] = $_affectedBlock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,13 +171,32 @@ class Explosion{
|
|||||||
$yield = min(100, (1 / $this->radius) * 100);
|
$yield = min(100, (1 / $this->radius) * 100);
|
||||||
|
|
||||||
if($this->what instanceof Entity){
|
if($this->what instanceof Entity){
|
||||||
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield);
|
$ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield, $this->fireIgnitions);
|
||||||
|
|
||||||
|
$ev->call();
|
||||||
|
if($ev->isCancelled()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$yield = $ev->getYield();
|
||||||
|
$this->affectedBlocks = $ev->getBlockList();
|
||||||
|
$this->fireIgnitions = $ev->getIgnitions();
|
||||||
|
}elseif($this->what instanceof Block){
|
||||||
|
$ev = new BlockExplodeEvent(
|
||||||
|
$this->what,
|
||||||
|
$this->source,
|
||||||
|
$this->affectedBlocks,
|
||||||
|
$yield,
|
||||||
|
$this->fireIgnitions,
|
||||||
|
);
|
||||||
|
|
||||||
$ev->call();
|
$ev->call();
|
||||||
if($ev->isCancelled()){
|
if($ev->isCancelled()){
|
||||||
return false;
|
return false;
|
||||||
}else{
|
}else{
|
||||||
$yield = $ev->getYield();
|
$yield = $ev->getYield();
|
||||||
$this->affectedBlocks = $ev->getBlockList();
|
$this->affectedBlocks = $ev->getAffectedBlocks();
|
||||||
|
$this->fireIgnitions = $ev->getIgnitions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +238,9 @@ class Explosion{
|
|||||||
|
|
||||||
$air = VanillaItems::AIR();
|
$air = VanillaItems::AIR();
|
||||||
$airBlock = VanillaBlocks::AIR();
|
$airBlock = VanillaBlocks::AIR();
|
||||||
|
$fireBlock = VanillaBlocks::FIRE();
|
||||||
|
|
||||||
foreach($this->affectedBlocks as $block){
|
foreach($this->affectedBlocks as $hash => $block){
|
||||||
$pos = $block->getPosition();
|
$pos = $block->getPosition();
|
||||||
if($block instanceof TNT){
|
if($block instanceof TNT){
|
||||||
$block->ignite(mt_rand(10, 30));
|
$block->ignite(mt_rand(10, 30));
|
||||||
@ -212,7 +253,13 @@ class Explosion{
|
|||||||
if(($t = $this->world->getTileAt($pos->x, $pos->y, $pos->z)) !== null){
|
if(($t = $this->world->getTileAt($pos->x, $pos->y, $pos->z)) !== null){
|
||||||
$t->onBlockDestroyed(); //needed to create drops for inventories
|
$t->onBlockDestroyed(); //needed to create drops for inventories
|
||||||
}
|
}
|
||||||
$this->world->setBlockAt($pos->x, $pos->y, $pos->z, $airBlock);
|
$targetBlock =
|
||||||
|
isset($this->fireIgnitions[$hash]) &&
|
||||||
|
$block->getSide(Facing::DOWN)->getSupportType(Facing::UP) === SupportType::FULL ?
|
||||||
|
$fireBlock :
|
||||||
|
$airBlock;
|
||||||
|
|
||||||
|
$this->world->setBlockAt($pos->x, $pos->y, $pos->z, $targetBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,4 +268,18 @@ class Explosion{
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a chance between 0 and 1 of creating a fire.
|
||||||
|
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||||
|
*
|
||||||
|
* @param float $fireChance 0 ... 1
|
||||||
|
*/
|
||||||
|
public function setFireChance(float $fireChance) : void{
|
||||||
|
Utils::checkFloatNotInfOrNaN("fireChance", $fireChance);
|
||||||
|
if($fireChance < 0.0 || $fireChance > 1.0){
|
||||||
|
throw new \InvalidArgumentException("Fire chance must be a number between 0 and 1.");
|
||||||
|
}
|
||||||
|
$this->fireChance = $fireChance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,9 +93,11 @@ use pocketmine\world\format\io\GlobalBlockStateHandlers;
|
|||||||
use pocketmine\world\format\io\WritableWorldProvider;
|
use pocketmine\world\format\io\WritableWorldProvider;
|
||||||
use pocketmine\world\format\LightArray;
|
use pocketmine\world\format\LightArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
|
use pocketmine\world\generator\executor\AsyncGeneratorExecutor;
|
||||||
|
use pocketmine\world\generator\executor\GeneratorExecutor;
|
||||||
|
use pocketmine\world\generator\executor\GeneratorExecutorSetupParameters;
|
||||||
|
use pocketmine\world\generator\executor\SyncGeneratorExecutor;
|
||||||
use pocketmine\world\generator\GeneratorManager;
|
use pocketmine\world\generator\GeneratorManager;
|
||||||
use pocketmine\world\generator\GeneratorRegisterTask;
|
|
||||||
use pocketmine\world\generator\GeneratorUnregisterTask;
|
|
||||||
use pocketmine\world\generator\PopulationTask;
|
use pocketmine\world\generator\PopulationTask;
|
||||||
use pocketmine\world\light\BlockLightUpdate;
|
use pocketmine\world\light\BlockLightUpdate;
|
||||||
use pocketmine\world\light\LightPopulationTask;
|
use pocketmine\world\light\LightPopulationTask;
|
||||||
@ -336,11 +338,7 @@ class World implements ChunkManager{
|
|||||||
*/
|
*/
|
||||||
private array $chunkPopulationRequestQueueIndex = [];
|
private array $chunkPopulationRequestQueueIndex = [];
|
||||||
|
|
||||||
/**
|
private readonly GeneratorExecutor $generatorExecutor;
|
||||||
* @var true[]
|
|
||||||
* @phpstan-var array<int, true>
|
|
||||||
*/
|
|
||||||
private array $generatorRegisteredWorkers = [];
|
|
||||||
|
|
||||||
private bool $autoSave = true;
|
private bool $autoSave = true;
|
||||||
|
|
||||||
@ -360,9 +358,6 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
private bool $doingTick = false;
|
private bool $doingTick = false;
|
||||||
|
|
||||||
/** @phpstan-var class-string<generator\Generator> */
|
|
||||||
private string $generator;
|
|
||||||
|
|
||||||
private bool $unloaded = false;
|
private bool $unloaded = false;
|
||||||
/**
|
/**
|
||||||
* @var \Closure[]
|
* @var \Closure[]
|
||||||
@ -498,7 +493,23 @@ class World implements ChunkManager{
|
|||||||
$generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ??
|
$generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator()) ??
|
||||||
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
|
throw new AssumptionFailedError("WorldManager should already have checked that the generator exists");
|
||||||
$generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions());
|
$generator->validateGeneratorOptions($this->provider->getWorldData()->getGeneratorOptions());
|
||||||
$this->generator = $generator->getGeneratorClass();
|
|
||||||
|
$executorSetupParameters = new GeneratorExecutorSetupParameters(
|
||||||
|
worldMinY: $this->minY,
|
||||||
|
worldMaxY: $this->maxY,
|
||||||
|
generatorSeed: $this->getSeed(),
|
||||||
|
generatorClass: $generator->getGeneratorClass(),
|
||||||
|
generatorSettings: $this->provider->getWorldData()->getGeneratorOptions()
|
||||||
|
);
|
||||||
|
$this->generatorExecutor = $generator->isFast() ?
|
||||||
|
new SyncGeneratorExecutor($executorSetupParameters) :
|
||||||
|
new AsyncGeneratorExecutor(
|
||||||
|
$this->logger,
|
||||||
|
$this->workerPool,
|
||||||
|
$executorSetupParameters,
|
||||||
|
$this->worldId
|
||||||
|
);
|
||||||
|
|
||||||
$this->chunkPopulationRequestQueue = new \SplQueue();
|
$this->chunkPopulationRequestQueue = new \SplQueue();
|
||||||
$this->addOnUnloadCallback(function() : void{
|
$this->addOnUnloadCallback(function() : void{
|
||||||
$this->logger->debug("Cancelling unfulfilled generation requests");
|
$this->logger->debug("Cancelling unfulfilled generation requests");
|
||||||
@ -534,17 +545,6 @@ class World implements ChunkManager{
|
|||||||
$this->initRandomTickBlocksFromConfig($cfg);
|
$this->initRandomTickBlocksFromConfig($cfg);
|
||||||
|
|
||||||
$this->timings = new WorldTimings($this);
|
$this->timings = new WorldTimings($this);
|
||||||
|
|
||||||
$this->workerPool->addWorkerStartHook($workerStartHook = function(int $workerId) : void{
|
|
||||||
if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){
|
|
||||||
$this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered");
|
|
||||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$workerPool = $this->workerPool;
|
|
||||||
$this->addOnUnloadCallback(static function() use ($workerPool, $workerStartHook) : void{
|
|
||||||
$workerPool->removeWorkerStartHook($workerStartHook);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{
|
private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{
|
||||||
@ -585,21 +585,6 @@ class World implements ChunkManager{
|
|||||||
return $this->tickRateTime;
|
return $this->tickRateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerGeneratorToWorker(int $worker) : void{
|
|
||||||
$this->logger->debug("Registering generator on worker $worker");
|
|
||||||
$this->workerPool->submitTaskToWorker(new GeneratorRegisterTask($this, $this->generator, $this->provider->getWorldData()->getGeneratorOptions()), $worker);
|
|
||||||
$this->generatorRegisteredWorkers[$worker] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function unregisterGenerator() : void{
|
|
||||||
foreach($this->workerPool->getRunningWorkers() as $i){
|
|
||||||
if(isset($this->generatorRegisteredWorkers[$i])){
|
|
||||||
$this->workerPool->submitTaskToWorker(new GeneratorUnregisterTask($this), $i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->generatorRegisteredWorkers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getServer() : Server{
|
public function getServer() : Server{
|
||||||
return $this->server;
|
return $this->server;
|
||||||
}
|
}
|
||||||
@ -657,7 +642,7 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
$this->unregisterGenerator();
|
$this->generatorExecutor->shutdown();
|
||||||
|
|
||||||
$this->provider->close();
|
$this->provider->close();
|
||||||
$this->blockCache = [];
|
$this->blockCache = [];
|
||||||
@ -3467,8 +3452,8 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
|
$centerChunk = $this->loadChunk($chunkX, $chunkZ);
|
||||||
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
|
$adjacentChunks = $this->getAdjacentChunks($chunkX, $chunkZ);
|
||||||
$task = new PopulationTask(
|
|
||||||
$this->worldId,
|
$this->generatorExecutor->populate(
|
||||||
$chunkX,
|
$chunkX,
|
||||||
$chunkZ,
|
$chunkZ,
|
||||||
$centerChunk,
|
$centerChunk,
|
||||||
@ -3481,15 +3466,6 @@ class World implements ChunkManager{
|
|||||||
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
|
$this->generateChunkCallback($chunkPopulationLockId, $chunkX, $chunkZ, $centerChunk, $adjacentChunks, $temporaryChunkLoader);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$workerId = $this->workerPool->selectWorker();
|
|
||||||
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
|
|
||||||
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
|
|
||||||
unset($this->generatorRegisteredWorkers[$workerId]);
|
|
||||||
}
|
|
||||||
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
|
||||||
$this->registerGeneratorToWorker($workerId);
|
|
||||||
}
|
|
||||||
$this->workerPool->submitTaskToWorker($task, $workerId);
|
|
||||||
|
|
||||||
return $resolver->getPromise();
|
return $resolver->getPromise();
|
||||||
}finally{
|
}finally{
|
||||||
|
@ -50,7 +50,7 @@ final class GeneratorManager{
|
|||||||
}catch(InvalidGeneratorOptionsException $e){
|
}catch(InvalidGeneratorOptionsException $e){
|
||||||
return $e;
|
return $e;
|
||||||
}
|
}
|
||||||
});
|
}, fast: true);
|
||||||
$this->addGenerator(Normal::class, "normal", fn() => null);
|
$this->addGenerator(Normal::class, "normal", fn() => null);
|
||||||
$this->addAlias("normal", "default");
|
$this->addAlias("normal", "default");
|
||||||
$this->addGenerator(Nether::class, "nether", fn() => null);
|
$this->addGenerator(Nether::class, "nether", fn() => null);
|
||||||
@ -62,6 +62,7 @@ final class GeneratorManager{
|
|||||||
* @param string $name Alias for this generator type that can be written in configs
|
* @param string $name Alias for this generator type that can be written in configs
|
||||||
* @param \Closure $presetValidator Callback to validate generator options for new worlds
|
* @param \Closure $presetValidator Callback to validate generator options for new worlds
|
||||||
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
|
||||||
|
* @param bool $fast Whether this generator is fast enough to run without async tasks
|
||||||
*
|
*
|
||||||
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
|
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
|
||||||
*
|
*
|
||||||
@ -69,7 +70,7 @@ final class GeneratorManager{
|
|||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false) : void{
|
public function addGenerator(string $class, string $name, \Closure $presetValidator, bool $overwrite = false, bool $fast = false) : void{
|
||||||
Utils::testValidInstance($class, Generator::class);
|
Utils::testValidInstance($class, Generator::class);
|
||||||
|
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
@ -77,7 +78,7 @@ final class GeneratorManager{
|
|||||||
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
|
throw new \InvalidArgumentException("Alias \"$name\" is already assigned");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator);
|
$this->list[$name] = new GeneratorManagerEntry($class, $presetValidator, $fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,12 +31,15 @@ final class GeneratorManagerEntry{
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $generatorClass,
|
private string $generatorClass,
|
||||||
private \Closure $presetValidator
|
private \Closure $presetValidator,
|
||||||
|
private readonly bool $fast
|
||||||
){}
|
){}
|
||||||
|
|
||||||
/** @phpstan-return class-string<Generator> */
|
/** @phpstan-return class-string<Generator> */
|
||||||
public function getGeneratorClass() : string{ return $this->generatorClass; }
|
public function getGeneratorClass() : string{ return $this->generatorClass; }
|
||||||
|
|
||||||
|
public function isFast() : bool{ return $this->fast; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InvalidGeneratorOptionsException
|
* @throws InvalidGeneratorOptionsException
|
||||||
*/
|
*/
|
||||||
|
@ -27,11 +27,18 @@ use pocketmine\scheduler\AsyncTask;
|
|||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\FastChunkSerializer;
|
use pocketmine\world\format\io\FastChunkSerializer;
|
||||||
|
use pocketmine\world\generator\executor\ThreadLocalGeneratorContext;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function igbinary_serialize;
|
use function igbinary_serialize;
|
||||||
use function igbinary_unserialize;
|
use function igbinary_unserialize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* TODO: this should be moved to the executor namespace, but plugins have unfortunately used it directly due to the
|
||||||
|
* difficulty of regenerating chunks. This should be addressed in the future.
|
||||||
|
* For the remainder of PM5, we can't relocate this class.
|
||||||
|
*
|
||||||
* @phpstan-type OnCompletion \Closure(Chunk $centerChunk, array<int, Chunk> $adjacentChunks) : void
|
* @phpstan-type OnCompletion \Closure(Chunk $centerChunk, array<int, Chunk> $adjacentChunks) : void
|
||||||
*/
|
*/
|
||||||
class PopulationTask extends AsyncTask{
|
class PopulationTask extends AsyncTask{
|
||||||
|
106
src/world/generator/executor/AsyncGeneratorExecutor.php
Normal file
106
src/world/generator/executor/AsyncGeneratorExecutor.php
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?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\world\generator\executor;
|
||||||
|
|
||||||
|
use pocketmine\scheduler\AsyncPool;
|
||||||
|
use pocketmine\world\format\Chunk;
|
||||||
|
use pocketmine\world\generator\PopulationTask;
|
||||||
|
use function array_key_exists;
|
||||||
|
|
||||||
|
final class AsyncGeneratorExecutor implements GeneratorExecutor{
|
||||||
|
private static int $nextAsyncContextId = 1;
|
||||||
|
|
||||||
|
private readonly \Logger $logger;
|
||||||
|
|
||||||
|
/** @phpstan-var \Closure(int) : void */
|
||||||
|
private readonly \Closure $workerStartHook;
|
||||||
|
|
||||||
|
private readonly int $asyncContextId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var true[]
|
||||||
|
* @phpstan-var array<int, true>
|
||||||
|
*/
|
||||||
|
private array $generatorRegisteredWorkers = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\Logger $logger,
|
||||||
|
private readonly AsyncPool $workerPool,
|
||||||
|
private readonly GeneratorExecutorSetupParameters $setupParameters,
|
||||||
|
int $asyncContextId = null
|
||||||
|
){
|
||||||
|
$this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor");
|
||||||
|
|
||||||
|
//TODO: we only allow setting this for PM5 because of PopulationTask uses in plugins
|
||||||
|
$this->asyncContextId = $asyncContextId ?? self::$nextAsyncContextId++;
|
||||||
|
|
||||||
|
$this->workerStartHook = function(int $workerId) : void{
|
||||||
|
if(array_key_exists($workerId, $this->generatorRegisteredWorkers)){
|
||||||
|
$this->logger->debug("Worker $workerId with previously registered generator restarted, flagging as unregistered");
|
||||||
|
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$this->workerPool->addWorkerStartHook($this->workerStartHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registerGeneratorToWorker(int $worker) : void{
|
||||||
|
$this->logger->debug("Registering generator on worker $worker");
|
||||||
|
$this->workerPool->submitTaskToWorker(new AsyncGeneratorRegisterTask($this->setupParameters, $this->asyncContextId), $worker);
|
||||||
|
$this->generatorRegisteredWorkers[$worker] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function unregisterGenerator() : void{
|
||||||
|
foreach($this->workerPool->getRunningWorkers() as $i){
|
||||||
|
if(isset($this->generatorRegisteredWorkers[$i])){
|
||||||
|
$this->workerPool->submitTaskToWorker(new AsyncGeneratorUnregisterTask($this->asyncContextId), $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->generatorRegisteredWorkers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{
|
||||||
|
$task = new PopulationTask(
|
||||||
|
$this->asyncContextId,
|
||||||
|
$chunkX,
|
||||||
|
$chunkZ,
|
||||||
|
$centerChunk,
|
||||||
|
$adjacentChunks,
|
||||||
|
$onCompletion
|
||||||
|
);
|
||||||
|
$workerId = $this->workerPool->selectWorker();
|
||||||
|
if(!isset($this->workerPool->getRunningWorkers()[$workerId]) && isset($this->generatorRegisteredWorkers[$workerId])){
|
||||||
|
$this->logger->debug("Selected worker $workerId previously had generator registered, but is now offline");
|
||||||
|
unset($this->generatorRegisteredWorkers[$workerId]);
|
||||||
|
}
|
||||||
|
if(!isset($this->generatorRegisteredWorkers[$workerId])){
|
||||||
|
$this->registerGeneratorToWorker($workerId);
|
||||||
|
}
|
||||||
|
$this->workerPool->submitTaskToWorker($task, $workerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shutdown() : void{
|
||||||
|
$this->unregisterGenerator();
|
||||||
|
$this->workerPool->removeWorkerStartHook($this->workerStartHook);
|
||||||
|
}
|
||||||
|
}
|
@ -21,37 +21,20 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace pocketmine\world\generator;
|
namespace pocketmine\world\generator\executor;
|
||||||
|
|
||||||
use pocketmine\scheduler\AsyncTask;
|
use pocketmine\scheduler\AsyncTask;
|
||||||
use pocketmine\world\World;
|
|
||||||
|
|
||||||
class GeneratorRegisterTask extends AsyncTask{
|
class AsyncGeneratorRegisterTask extends AsyncTask{
|
||||||
public int $seed;
|
|
||||||
public int $worldId;
|
|
||||||
public int $worldMinY;
|
|
||||||
public int $worldMaxY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @phpstan-param class-string<Generator> $generatorClass
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
World $world,
|
private readonly GeneratorExecutorSetupParameters $setupParameters,
|
||||||
public string $generatorClass,
|
private readonly int $contextId
|
||||||
public string $generatorSettings
|
){}
|
||||||
){
|
|
||||||
$this->seed = $world->getSeed();
|
|
||||||
$this->worldId = $world->getId();
|
|
||||||
$this->worldMinY = $world->getMinY();
|
|
||||||
$this->worldMaxY = $world->getMaxY();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onRun() : void{
|
public function onRun() : void{
|
||||||
/**
|
$setupParameters = $this->setupParameters;
|
||||||
* @var Generator $generator
|
$generator = $setupParameters->createGenerator();
|
||||||
* @see Generator::__construct()
|
ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $setupParameters->worldMinY, $setupParameters->worldMaxY), $this->contextId);
|
||||||
*/
|
|
||||||
$generator = new $this->generatorClass($this->seed, $this->generatorSettings);
|
|
||||||
ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,19 +21,16 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace pocketmine\world\generator;
|
namespace pocketmine\world\generator\executor;
|
||||||
|
|
||||||
use pocketmine\scheduler\AsyncTask;
|
use pocketmine\scheduler\AsyncTask;
|
||||||
use pocketmine\world\World;
|
|
||||||
|
|
||||||
class GeneratorUnregisterTask extends AsyncTask{
|
class AsyncGeneratorUnregisterTask extends AsyncTask{
|
||||||
public int $worldId;
|
public function __construct(
|
||||||
|
private readonly int $contextId
|
||||||
public function __construct(World $world){
|
){}
|
||||||
$this->worldId = $world->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onRun() : void{
|
public function onRun() : void{
|
||||||
ThreadLocalGeneratorContext::unregister($this->worldId);
|
ThreadLocalGeneratorContext::unregister($this->contextId);
|
||||||
}
|
}
|
||||||
}
|
}
|
38
src/world/generator/executor/GeneratorExecutor.php
Normal file
38
src/world/generator/executor/GeneratorExecutor.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?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\world\generator\executor;
|
||||||
|
|
||||||
|
use pocketmine\world\format\Chunk;
|
||||||
|
|
||||||
|
interface GeneratorExecutor{
|
||||||
|
/**
|
||||||
|
* @param Chunk[]|null[] $adjacentChunks
|
||||||
|
* @phpstan-param array<int, Chunk|null> $adjacentChunks
|
||||||
|
* @phpstan-param \Closure(Chunk $centerChunk, array<int, Chunk> $adjacentChunks) : void $onCompletion
|
||||||
|
*/
|
||||||
|
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void;
|
||||||
|
|
||||||
|
public function shutdown() : void;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
<?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\world\generator\executor;
|
||||||
|
|
||||||
|
use pmmp\thread\ThreadSafe;
|
||||||
|
use pocketmine\world\generator\Generator;
|
||||||
|
|
||||||
|
final class GeneratorExecutorSetupParameters extends ThreadSafe{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-param class-string<covariant \pocketmine\world\generator\Generator> $generatorClass
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly int $worldMinY,
|
||||||
|
public readonly int $worldMaxY,
|
||||||
|
public readonly int $generatorSeed,
|
||||||
|
public readonly string $generatorClass,
|
||||||
|
public readonly string $generatorSettings,
|
||||||
|
){}
|
||||||
|
|
||||||
|
public function createGenerator() : Generator{
|
||||||
|
/**
|
||||||
|
* @var Generator $generator
|
||||||
|
* @see Generator::__construct()
|
||||||
|
*/
|
||||||
|
$generator = new $this->generatorClass($this->generatorSeed, $this->generatorSettings);
|
||||||
|
return $generator;
|
||||||
|
}
|
||||||
|
}
|
61
src/world/generator/executor/SyncGeneratorExecutor.php
Normal file
61
src/world/generator/executor/SyncGeneratorExecutor.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?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\world\generator\executor;
|
||||||
|
|
||||||
|
use pocketmine\world\format\Chunk;
|
||||||
|
use pocketmine\world\generator\Generator;
|
||||||
|
use pocketmine\world\generator\PopulationUtils;
|
||||||
|
|
||||||
|
final class SyncGeneratorExecutor implements GeneratorExecutor{
|
||||||
|
|
||||||
|
private readonly Generator $generator;
|
||||||
|
private readonly int $worldMinY;
|
||||||
|
private readonly int $worldMaxY;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
GeneratorExecutorSetupParameters $setupParameters
|
||||||
|
){
|
||||||
|
$this->generator = $setupParameters->createGenerator();
|
||||||
|
$this->worldMinY = $setupParameters->worldMinY;
|
||||||
|
$this->worldMaxY = $setupParameters->worldMaxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function populate(int $chunkX, int $chunkZ, ?Chunk $centerChunk, array $adjacentChunks, \Closure $onCompletion) : void{
|
||||||
|
[$centerChunk, $adjacentChunks] = PopulationUtils::populateChunkWithAdjacents(
|
||||||
|
$this->worldMinY,
|
||||||
|
$this->worldMaxY,
|
||||||
|
$this->generator,
|
||||||
|
$chunkX,
|
||||||
|
$chunkZ,
|
||||||
|
$centerChunk,
|
||||||
|
$adjacentChunks
|
||||||
|
);
|
||||||
|
|
||||||
|
$onCompletion($centerChunk, $adjacentChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shutdown() : void{
|
||||||
|
//NOOP
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,9 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace pocketmine\world\generator;
|
namespace pocketmine\world\generator\executor;
|
||||||
|
|
||||||
|
use pocketmine\world\generator\Generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages thread-local caches for generators and the things needed to support them
|
* Manages thread-local caches for generators and the things needed to support them
|
35
src/world/sound/RespawnAnchorChargeSound.php
Normal file
35
src/world/sound/RespawnAnchorChargeSound.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\world\sound;
|
||||||
|
|
||||||
|
use pocketmine\math\Vector3;
|
||||||
|
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||||
|
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||||
|
|
||||||
|
class RespawnAnchorChargeSound implements Sound{
|
||||||
|
|
||||||
|
public function encode(Vector3 $pos) : array{
|
||||||
|
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::RESPAWN_ANCHOR_CHARGE, $pos, false)];
|
||||||
|
}
|
||||||
|
}
|
35
src/world/sound/RespawnAnchorDepleteSound.php
Normal file
35
src/world/sound/RespawnAnchorDepleteSound.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\world\sound;
|
||||||
|
|
||||||
|
use pocketmine\math\Vector3;
|
||||||
|
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||||
|
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||||
|
|
||||||
|
class RespawnAnchorDepleteSound implements Sound{
|
||||||
|
|
||||||
|
public function encode(Vector3 $pos) : array{
|
||||||
|
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::RESPAWN_ANCHOR_DEPLETE, $pos, false)];
|
||||||
|
}
|
||||||
|
}
|
35
src/world/sound/RespawnAnchorSetSpawnSound.php
Normal file
35
src/world/sound/RespawnAnchorSetSpawnSound.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\world\sound;
|
||||||
|
|
||||||
|
use pocketmine\math\Vector3;
|
||||||
|
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
|
||||||
|
use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
|
||||||
|
|
||||||
|
class RespawnAnchorSetSpawnSound implements Sound{
|
||||||
|
|
||||||
|
public function encode(Vector3 $pos) : array{
|
||||||
|
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::RESPAWN_ANCHOR_SET_SPAWN, $pos, false)];
|
||||||
|
}
|
||||||
|
}
|
@ -1272,18 +1272,18 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||||
|
|
||||||
-
|
|
||||||
message: '#^Dynamic new is not allowed\.$#'
|
|
||||||
identifier: pocketmine.new.dynamic
|
|
||||||
count: 1
|
|
||||||
path: ../../../src/world/generator/GeneratorRegisterTask.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
|
message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
|
||||||
identifier: return.type
|
identifier: return.type
|
||||||
count: 1
|
count: 1
|
||||||
path: ../../../src/world/generator/biome/BiomeSelector.php
|
path: ../../../src/world/generator/biome/BiomeSelector.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Dynamic new is not allowed\.$#'
|
||||||
|
identifier: pocketmine.new.dynamic
|
||||||
|
count: 1
|
||||||
|
path: ../../../src/world/generator/executor/GeneratorExecutorSetupParameters.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#'
|
message: '#^Cannot call method getBiomeId\(\) on pocketmine\\world\\format\\Chunk\|null\.$#'
|
||||||
identifier: method.nonObject
|
identifier: method.nonObject
|
||||||
|
@ -615,6 +615,7 @@
|
|||||||
"RESIN_BRICK_STAIRS": 8,
|
"RESIN_BRICK_STAIRS": 8,
|
||||||
"RESIN_BRICK_WALL": 162,
|
"RESIN_BRICK_WALL": 162,
|
||||||
"RESIN_CLUMP": 64,
|
"RESIN_CLUMP": 64,
|
||||||
|
"RESPAWN_ANCHOR": 5,
|
||||||
"ROSE_BUSH": 2,
|
"ROSE_BUSH": 2,
|
||||||
"SAND": 1,
|
"SAND": 1,
|
||||||
"SANDSTONE": 1,
|
"SANDSTONE": 1,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user