mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-20 18:34:07 +00:00
Implemented Respawn Anchor (#6646)
PlayerRespawnAnchorUseEvent is also added with options SET_SPAWN and EXPLODE, which allows plugins to customise the outcome of using the anchor in PM, which currently doesn't support dimensions. The event is also cancellable.
This commit is contained in:
parent
059f4ee7bf
commit
bf33a625c9
@ -786,8 +786,9 @@ final class BlockTypeIds{
|
||||
public const RESIN_BRICKS = 10756;
|
||||
public const RESIN_CLUMP = 10757;
|
||||
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;
|
||||
|
||||
|
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 Wall RESIN_BRICK_WALL()
|
||||
* @method static ResinClump RESIN_CLUMP()
|
||||
* @method static RespawnAnchor RESPAWN_ANCHOR()
|
||||
* @method static DoublePlant ROSE_BUSH()
|
||||
* @method static Sand SAND()
|
||||
* @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("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{
|
||||
|
@ -122,6 +122,7 @@ use pocketmine\block\RedstoneRepeater;
|
||||
use pocketmine\block\RedstoneTorch;
|
||||
use pocketmine\block\RedstoneWire;
|
||||
use pocketmine\block\ResinClump;
|
||||
use pocketmine\block\RespawnAnchor;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\Sapling;
|
||||
use pocketmine\block\SeaPickle;
|
||||
@ -1754,6 +1755,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
return Writer::create(Ids::RESIN_CLUMP)
|
||||
->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->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
|
||||
$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->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::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->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
|
||||
$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->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this);
|
||||
$explosion = new Explosion($this->getPosition(), $ev->getRadius(), $this, $ev->getFireChance());
|
||||
if($ev->isBlockBreaking()){
|
||||
$explosion->explodeA();
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ class PrimedTNT extends Entity implements Explosive{
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
//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()){
|
||||
$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 float $yield 0-100
|
||||
* @param float $yield 0-100
|
||||
* @param Block[] $ignitions
|
||||
*/
|
||||
public function __construct(
|
||||
Entity $entity,
|
||||
protected Position $position,
|
||||
protected array $blocks,
|
||||
protected float $yield
|
||||
protected float $yield,
|
||||
private array $ignitions
|
||||
){
|
||||
$this->entity = $entity;
|
||||
if($yield < 0.0 || $yield > 100.0){
|
||||
@ -98,4 +100,23 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
|
||||
}
|
||||
$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\event\Cancellable;
|
||||
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.
|
||||
@ -42,11 +44,16 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
||||
|
||||
public function __construct(
|
||||
Entity $entity,
|
||||
protected float $radius
|
||||
protected float $radius,
|
||||
private float $fireChance = 0.0,
|
||||
){
|
||||
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 between 0 and 1.");
|
||||
}
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
@ -61,6 +68,47 @@ class EntityPreExplodeEvent extends EntityEvent implements Cancellable{
|
||||
$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{
|
||||
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_bricks", fn() => Blocks::RESIN_BRICKS());
|
||||
$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("rose", fn() => Blocks::POPPY());
|
||||
$result->registerBlock("rose_bush", fn() => Blocks::ROSE_BUSH());
|
||||
|
@ -26,6 +26,7 @@ namespace pocketmine\player;
|
||||
use pocketmine\block\BaseSign;
|
||||
use pocketmine\block\Bed;
|
||||
use pocketmine\block\BlockTypeTags;
|
||||
use pocketmine\block\RespawnAnchor;
|
||||
use pocketmine\block\UnknownBlock;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\command\CommandSender;
|
||||
@ -136,6 +137,7 @@ use pocketmine\world\sound\EntityAttackNoDamageSound;
|
||||
use pocketmine\world\sound\EntityAttackSound;
|
||||
use pocketmine\world\sound\FireExtinguishSound;
|
||||
use pocketmine\world\sound\ItemBreakSound;
|
||||
use pocketmine\world\sound\RespawnAnchorDepleteSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\YmlServerProperties;
|
||||
@ -2538,6 +2540,21 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
$this->logger->debug("Respawn position located, completing respawn");
|
||||
$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();
|
||||
|
||||
$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\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\TNT;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\block\BlockExplodeEvent;
|
||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageByEntityEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\event\entity\EntityExplodeEvent;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
use pocketmine\world\particle\HugeExplodeSeedParticle;
|
||||
use pocketmine\world\sound\ExplodeSound;
|
||||
@ -48,25 +52,36 @@ use function mt_rand;
|
||||
use function sqrt;
|
||||
|
||||
class Explosion{
|
||||
public const DEFAULT_FIRE_CHANCE = 1.0 / 3.0;
|
||||
|
||||
private int $rays = 16;
|
||||
public World $world;
|
||||
|
||||
/** @var Block[] */
|
||||
/**
|
||||
* @var Block[]
|
||||
* @phpstan-var array<int, Block>
|
||||
*/
|
||||
public array $affectedBlocks = [];
|
||||
public float $stepLen = 0.3;
|
||||
/** @var Block[] */
|
||||
private array $fireIgnitions = [];
|
||||
|
||||
private SubChunkExplorer $subChunkExplorer;
|
||||
|
||||
public function __construct(
|
||||
public Position $source,
|
||||
public float $radius,
|
||||
private Entity|Block|null $what = null
|
||||
private Entity|Block|null $what = null,
|
||||
private float $fireChance = 0.0
|
||||
){
|
||||
if(!$this->source->isValid()){
|
||||
throw new \InvalidArgumentException("Position does not have a valid world");
|
||||
}
|
||||
$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){
|
||||
throw new \InvalidArgumentException("Explosion radius must be greater than 0, got $radius");
|
||||
}
|
||||
@ -85,6 +100,7 @@ class Explosion{
|
||||
$blockFactory = RuntimeBlockStateRegistry::getInstance();
|
||||
|
||||
$mRays = $this->rays - 1;
|
||||
$incendiary = $this->fireChance > 0;
|
||||
for($i = 0; $i < $this->rays; ++$i){
|
||||
for($j = 0; $j < $this->rays; ++$j){
|
||||
for($k = 0; $k < $this->rays; ++$k){
|
||||
@ -127,7 +143,12 @@ class Explosion{
|
||||
$_block = $this->world->getBlockAt($vBlockX, $vBlockY, $vBlockZ, true, false);
|
||||
foreach($_block->getAffectedBlocks() as $_affectedBlock){
|
||||
$_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);
|
||||
|
||||
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();
|
||||
if($ev->isCancelled()){
|
||||
return false;
|
||||
}else{
|
||||
$yield = $ev->getYield();
|
||||
$this->affectedBlocks = $ev->getBlockList();
|
||||
$this->affectedBlocks = $ev->getAffectedBlocks();
|
||||
$this->fireIgnitions = $ev->getIgnitions();
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,8 +238,9 @@ class Explosion{
|
||||
|
||||
$air = VanillaItems::AIR();
|
||||
$airBlock = VanillaBlocks::AIR();
|
||||
$fireBlock = VanillaBlocks::FIRE();
|
||||
|
||||
foreach($this->affectedBlocks as $block){
|
||||
foreach($this->affectedBlocks as $hash => $block){
|
||||
$pos = $block->getPosition();
|
||||
if($block instanceof TNT){
|
||||
$block->ignite(mt_rand(10, 30));
|
||||
@ -212,7 +253,13 @@ class Explosion{
|
||||
if(($t = $this->world->getTileAt($pos->x, $pos->y, $pos->z)) !== null){
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
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)];
|
||||
}
|
||||
}
|
@ -615,6 +615,7 @@
|
||||
"RESIN_BRICK_STAIRS": 8,
|
||||
"RESIN_BRICK_WALL": 162,
|
||||
"RESIN_CLUMP": 64,
|
||||
"RESPAWN_ANCHOR": 5,
|
||||
"ROSE_BUSH": 2,
|
||||
"SAND": 1,
|
||||
"SANDSTONE": 1,
|
||||
|
Loading…
x
Reference in New Issue
Block a user