diff --git a/README.md b/README.md index 9918215..93a0e3c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ Adds fireworks to your PocketMine server
Download the compiled `.phar` format of Fireworks from the [Poggit CI](https://poggit.pmmp.io/ci/BlockHorizons/Fireworks). ## API ### Adding firework items to a player's inventory -Giving players fireworks is easy as pie. Here are some examples (where `$player` is a `\pocketmine\Player` object): +Giving players fireworks is easy as pie. Here are some examples (where `$player` is a `\pocketmine\player\Player` +object): - **Base firework** ```php /** @var Fireworks $fw */ diff --git a/plugin.yml b/plugin.yml index f3a7014..748fb57 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,4 +1,4 @@ name: Fireworks main: BlockHorizons\Fireworks\Loader -api: ["3.9.0", "3.17.1"] -version: 0.0.4 +api: 5.0.0 +version: 0.0.4.1 diff --git a/src/BlockHorizons/Fireworks/Loader.php b/src/BlockHorizons/Fireworks/Loader.php index 3705fcb..3c139d3 100644 --- a/src/BlockHorizons/Fireworks/Loader.php +++ b/src/BlockHorizons/Fireworks/Loader.php @@ -4,21 +4,21 @@ namespace BlockHorizons\Fireworks; -use BlockHorizons\Fireworks\entity\FireworksRocket; use BlockHorizons\Fireworks\item\Fireworks; -use pocketmine\entity\Entity; -use pocketmine\item\Item; -use pocketmine\item\ItemFactory; +use pocketmine\data\bedrock\item\ItemTypeNames; +use pocketmine\data\bedrock\item\SavedItemData; +use pocketmine\item\ItemIdentifier; +use pocketmine\item\ItemTypeIds; use pocketmine\plugin\PluginBase; +use pocketmine\world\format\io\GlobalItemDataHandlers; -class Loader extends PluginBase { +class Loader extends PluginBase +{ public function onEnable(): void { - ItemFactory::registerItem(new Fireworks(), true); - Item::initCreativeItems(); //will load firework rockets from pocketmine's resources folder - if (!Entity::registerEntity(FireworksRocket::class, false, ["FireworksRocket", "minecraft:fireworks_rocket"])) { - $this->getLogger()->error("Failed to register FireworksRocket entity with savename 'FireworksRocket'"); - } + $fireworks = new Fireworks(new ItemIdentifier(ItemTypeIds::newId()), "Fireworks"); + GlobalItemDataHandlers::getSerializer()->map($fireworks, static fn() => new SavedItemData(ItemTypeNames::FIREWORK_ROCKET)); + GlobalItemDataHandlers::getDeserializer()->map(ItemTypeNames::FIREWORK_ROCKET, static fn() => clone $fireworks); } } \ No newline at end of file diff --git a/src/BlockHorizons/Fireworks/entity/FireworksRocket.php b/src/BlockHorizons/Fireworks/entity/FireworksRocket.php index 8946209..4e546e6 100644 --- a/src/BlockHorizons/Fireworks/entity/FireworksRocket.php +++ b/src/BlockHorizons/Fireworks/entity/FireworksRocket.php @@ -1,65 +1,97 @@ fireworks = $fireworks; + parent::__construct($location, $fireworks->getNamedTag()); + $this->setMotion(new Vector3(0.001, 0.05, 0.001)); - if($fireworks !== null && $fireworks->getNamedTagEntry("Fireworks") instanceof CompoundTag) { - $this->propertyManager->setCompoundTag(self::DATA_FIREWORK_ITEM, $fireworks->getNamedTag()); - $this->setLifeTime($fireworks->getRandomizedFlightDuration()); + if ($fireworks->getNamedTag()->getCompoundTag("Fireworks") !== null) { + $this->setLifeTime($lifeTime ?? $fireworks->getRandomizedFlightDuration()); } - $level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_LAUNCH); + $location->getWorld()->broadcastPacketToViewers($this->location, LevelSoundEventPacket::nonActorSound(LevelSoundEvent::LAUNCH, $this->location->asVector3(), false)); + } + + protected function getInitialDragMultiplier(): float{ + return 0.99; + } + + protected function getInitialGravity(): float{ + return 0.05; } - protected function tryChangeMovement(): void { + /** + * TODO: The entity should be saved and loaded, but this is not possible. + * @see https://bugs.mojang.com/browse/MCPE-165230 + */ + public function canSaveWithChunk(): bool{ + return false; + } + + protected function tryChangeMovement(): void + { $this->motion->x *= 1.15; $this->motion->y += 0.04; $this->motion->z *= 1.15; } - public function entityBaseTick(int $tickDiff = 1): bool { - if($this->closed) { + public function entityBaseTick(int $tickDiff = 1): bool + { + if ($this->closed) { return false; } $hasUpdate = parent::entityBaseTick($tickDiff); - if($this->doLifeTimeTick()) { + if ($this->doLifeTimeTick()) { $hasUpdate = true; } return $hasUpdate; } - public function setLifeTime(int $life): void { + public function setLifeTime(int $life): void + { $this->lifeTime = $life; } - protected function doLifeTimeTick(): bool { - if(!$this->isFlaggedForDespawn() and --$this->lifeTime < 0) { + protected function doLifeTimeTick(): bool + { + if (--$this->lifeTime < 0 && !$this->isFlaggedForDespawn()) { $this->doExplosionAnimation(); + $this->playSounds(); $this->flagForDespawn(); return true; } @@ -67,7 +99,52 @@ protected function doLifeTimeTick(): bool { return false; } - protected function doExplosionAnimation(): void { - $this->broadcastEntityEvent(ActorEventPacket::FIREWORK_PARTICLES); + protected function doExplosionAnimation(): void + { + $this->broadcastAnimation(new FireworkParticleAnimation($this), $this->getViewers()); } + + public function playSounds(): void + { + // This late in, there's 0 chance fireworks tag is null + $fireworksTag = $this->fireworks->getNamedTag()->getCompoundTag("Fireworks"); + $explosionsTag = $fireworksTag->getListTag("Explosions"); + if ($explosionsTag === null) { + // We don't throw an error here since there are fireworks that can die without noise or particles, + // which means they are lacking an explosion tag. + return; + } + + foreach ($explosionsTag->getValue() as $info) { + if ($info instanceof CompoundTag) { + if ($info->getByte("FireworkType", 0) === Fireworks::TYPE_HUGE_SPHERE) { + $this->getWorld()->broadcastPacketToViewers($this->location, LevelSoundEventPacket::nonActorSound(LevelSoundEvent::LARGE_BLAST, $this->location->asVector3(), false)); + } else { + $this->getWorld()->broadcastPacketToViewers($this->location, LevelSoundEventPacket::nonActorSound(LevelSoundEvent::BLAST, $this->location->asVector3(), false)); + } + + if ($info->getByte("FireworkFlicker", 0) === 1) { + $this->getWorld()->broadcastPacketToViewers($this->location, LevelSoundEventPacket::nonActorSound(LevelSoundEvent::TWINKLE, $this->location->asVector3(), false)); + } + } + } + } + + public function syncNetworkData(EntityMetadataCollection $properties): void + { + parent::syncNetworkData($properties); + $properties->setCompoundTag(self::DATA_FIREWORK_ITEM, new CacheableNbt($this->fireworks->getNamedTag())); + } + + protected function getInitialSizeInfo(): EntitySizeInfo + { + return new EntitySizeInfo(0.25, 0.25); + } + + public function saveNBT(): CompoundTag + { + $nbt = parent::saveNBT(); + $nbt->setTag("Item", $this->fireworks->nbtSerialize()); + return $nbt; + } } \ No newline at end of file diff --git a/src/BlockHorizons/Fireworks/entity/animation/FireworkParticleAnimation.php b/src/BlockHorizons/Fireworks/entity/animation/FireworkParticleAnimation.php new file mode 100644 index 0000000..9f5f6b2 --- /dev/null +++ b/src/BlockHorizons/Fireworks/entity/animation/FireworkParticleAnimation.php @@ -0,0 +1,28 @@ +firework = $firework; + } + + public function encode(): array + { + return [ + ActorEventPacket::create($this->firework->getId(), ActorEvent::FIREWORK_PARTICLES, 0) + ]; + } +} \ No newline at end of file diff --git a/src/BlockHorizons/Fireworks/item/Fireworks.php b/src/BlockHorizons/Fireworks/item/Fireworks.php index 4047348..daf5d7a 100644 --- a/src/BlockHorizons/Fireworks/item/Fireworks.php +++ b/src/BlockHorizons/Fireworks/item/Fireworks.php @@ -4,13 +4,15 @@ namespace BlockHorizons\Fireworks\item; +use BlockHorizons\Fireworks\entity\FireworksRocket; use pocketmine\block\Block; -use pocketmine\entity\Entity; +use pocketmine\entity\Location; use pocketmine\item\Item; +use pocketmine\item\ItemUseResult; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\Player; +use pocketmine\player\Player; class Fireworks extends Item { @@ -38,10 +40,6 @@ class Fireworks extends Item { public const COLOR_GOLD = "\x0e"; public const COLOR_WHITE = "\x0f"; - public function __construct(int $meta = 0) { - parent::__construct(self::FIREWORKS, $meta, "Fireworks"); - } - public function getFlightDuration(): int { return $this->getExplosionsTag()->getByte("Flight", 1); } @@ -51,41 +49,42 @@ public function getRandomizedFlightDuration(): int { } public function setFlightDuration(int $duration): void { - $tag = $this->getExplosionsTag(); - $tag->setByte("Flight", $duration); - $this->setNamedTagEntry($tag); + $this->getExplosionsTag()->setByte("Flight", $duration); } public function addExplosion(int $type, string $color, string $fade = "", bool $flicker = false, bool $trail = false): void { - $explosion = new CompoundTag(); - $explosion->setByte("FireworkType", $type); - $explosion->setByteArray("FireworkColor", $color); - $explosion->setByteArray("FireworkFade", $fade); - $explosion->setByte("FireworkFlicker", $flicker ? 1 : 0); - $explosion->setByte("FireworkTrail", $trail ? 1 : 0); - $tag = $this->getExplosionsTag(); - $explosions = $tag->getListTag("Explosions") ?? new ListTag("Explosions"); - $explosions->push($explosion); - $tag->setTag($explosions); - $this->setNamedTagEntry($tag); - } + $explosions = $tag->getListTag("Explosions"); + if($explosions === null){ + $tag->setTag("Explosions", $explosions = new ListTag()); + } - protected function getExplosionsTag(): CompoundTag { - return $this->getNamedTag()->getCompoundTag("Fireworks") ?? new CompoundTag("Fireworks"); + $explosions->push(CompoundTag::create() + ->setByte("FireworkType", $type) + ->setByteArray("FireworkColor", $color) + ->setByteArray("FireworkFade", $fade) + ->setByte("FireworkFlicker", $flicker ? 1 : 0) + ->setByte("FireworkTrail", $trail ? 1 : 0) + ); } - public function onActivate(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector): bool { - $nbt = Entity::createBaseNBT($blockReplace->add(0.5, 0, 0.5), new Vector3(0.001, 0.05, 0.001), lcg_value() * 360, 90); + protected function getExplosionsTag(): CompoundTag + { + $tag = $this->getNamedTag()->getCompoundTag("Fireworks"); + if ($tag === null) { + $this->getNamedTag()->setTag("Fireworks", $tag = CompoundTag::create()); + } + return $tag; + } - $entity = Entity::createEntity("FireworksRocket", $player->getLevel(), $nbt, $this); + public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems): ItemUseResult + { + $entity = new FireworksRocket(Location::fromObject($blockReplace->getPosition()->add(0.5, 0, 0.5), $player->getWorld(), lcg_value() * 360, 90), $this); - if($entity instanceof Entity) { - --$this->count; - $entity->spawnToAll(); - return true; - } - return false; + $this->pop(); + $entity->spawnToAll(); + //TODO: what if the entity was marked for deletion? + return ItemUseResult::SUCCESS(); } -} \ No newline at end of file +}