feat: Implement Brewing (#4413)

The following API constants have been added:
- tile\BrewingStand::BREW_TIME_TICKS
The following public API methods have been added:
- utils\BrewingStandSlot->getSlotNumber() : int
- CraftingManager->getPotionTypeRecipes() : array<string, array<string, PotionTypeRecipe>>
- CraftingManager->getPotionContainerChangeRecipes() : array<int, array<string, PotionContainerChangeRecipe>>
- CraftingManager->registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void
- CraftingManager->registerPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void
The following classes have been added:
- BrewingRecipe
- PotionTypeRecipe
- PotionContainerChangeRecipe
- BrewItemEvent
- BrewingFuelUseEvent
- PotionFinishBrewingSound
This commit is contained in:
VixikHD 2022-01-22 17:54:58 +01:00 committed by GitHub
parent b2630a0920
commit e0da99a973
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 606 additions and 16 deletions

View File

@ -125,6 +125,24 @@ class BrewingStand extends Transparent{
}
public function onScheduledUpdate() : void{
//TODO
$brewing = $this->position->getWorld()->getTile($this->position);
if($brewing instanceof TileBrewingStand){
if($brewing->onUpdate()){
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, 1);
}
$changed = false;
foreach(BrewingStandSlot::getAll() as $slot){
$occupied = !$brewing->getInventory()->isSlotEmpty($slot->getSlotNumber());
if($occupied !== $this->hasSlot($slot)){
$this->setSlot($slot, $occupied);
$changed = true;
}
}
if($changed){
$this->position->getWorld()->setBlock($this->position, $this);
}
}
}
}

View File

@ -24,16 +24,29 @@ declare(strict_types=1);
namespace pocketmine\block\tile;
use pocketmine\block\inventory\BrewingStandInventory;
use pocketmine\crafting\BrewingRecipe;
use pocketmine\event\block\BrewingFuelUseEvent;
use pocketmine\event\block\BrewItemEvent;
use pocketmine\inventory\CallbackInventoryListener;
use pocketmine\inventory\Inventory;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\ContainerSetDataPacket;
use pocketmine\player\Player;
use pocketmine\world\sound\PotionFinishBrewingSound;
use pocketmine\world\World;
use function array_map;
use function count;
class BrewingStand extends Spawnable implements Container, Nameable{
use NameableTrait {
addAdditionalSpawnData as addNameSpawnData;
}
use ContainerTrait;
use NameableTrait;
public const BREW_TIME_TICKS = 400; // Brew time in ticks
private const TAG_BREW_TIME = "BrewTime"; //TAG_Short
private const TAG_BREW_TIME_PE = "CookTime"; //TAG_Short
@ -41,15 +54,11 @@ class BrewingStand extends Spawnable implements Container, Nameable{
private const TAG_REMAINING_FUEL_TIME = "Fuel"; //TAG_Byte
private const TAG_REMAINING_FUEL_TIME_PE = "FuelAmount"; //TAG_Short
/** @var BrewingStandInventory */
private $inventory;
private BrewingStandInventory $inventory;
/** @var int */
private $brewTime = 0;
/** @var int */
private $maxFuelTime = 0;
/** @var int */
private $remainingFuelTime = 0;
private int $brewTime = 0;
private int $maxFuelTime = 0;
private int $remainingFuelTime = 0;
public function __construct(World $world, Vector3 $pos){
parent::__construct($world, $pos);
@ -83,6 +92,14 @@ class BrewingStand extends Spawnable implements Container, Nameable{
$nbt->setShort(self::TAG_REMAINING_FUEL_TIME_PE, $this->remainingFuelTime);
}
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$this->addNameSpawnData($nbt);
$nbt->setShort(self::TAG_BREW_TIME_PE, $this->brewTime);
$nbt->setShort(self::TAG_MAX_FUEL_TIME, $this->maxFuelTime);
$nbt->setShort(self::TAG_REMAINING_FUEL_TIME_PE, $this->remainingFuelTime);
}
public function getDefaultName() : string{
return "Brewing Stand";
}
@ -108,4 +125,135 @@ class BrewingStand extends Spawnable implements Container, Nameable{
public function getRealInventory(){
return $this->inventory;
}
private function checkFuel(Item $item) : void{
$ev = new BrewingFuelUseEvent($this);
if(!$item->equals(VanillaItems::BLAZE_POWDER(), true, false)){
$ev->cancel();
}
$ev->call();
if($ev->isCancelled()){
return;
}
$item->pop();
$this->inventory->setItem(BrewingStandInventory::SLOT_FUEL, $item);
$this->maxFuelTime = $this->remainingFuelTime = $ev->getFuelTime();
}
/**
* @return BrewingRecipe[]
* @phpstan-return array<int, BrewingRecipe>
*/
private function getBrewableRecipes() : array{
if($this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT)->isNull()){
return [];
}
$recipes = [];
foreach([BrewingStandInventory::SLOT_BOTTLE_LEFT, BrewingStandInventory::SLOT_BOTTLE_MIDDLE, BrewingStandInventory::SLOT_BOTTLE_RIGHT] as $slot){
$input = $this->inventory->getItem($slot);
if($input->isNull()){
continue;
}
if(($recipe = $this->position->getWorld()->getServer()->getCraftingManager()->matchBrewingRecipe($input, $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT))) !== null){
$recipes[$slot] = $recipe;
}
}
return $recipes;
}
public function onUpdate() : bool{
if($this->closed){
return false;
}
$this->timings->startTiming();
$prevBrewTime = $this->brewTime;
$prevRemainingFuelTime = $this->remainingFuelTime;
$prevMaxFuelTime = $this->maxFuelTime;
$ret = false;
$fuel = $this->inventory->getItem(BrewingStandInventory::SLOT_FUEL);
$ingredient = $this->inventory->getItem(BrewingStandInventory::SLOT_INGREDIENT);
$recipes = $this->getBrewableRecipes();
$canBrew = count($recipes) !== 0;
if($this->remainingFuelTime <= 0 && $canBrew){
$this->checkFuel($fuel);
}
if($this->remainingFuelTime > 0){
if($canBrew){
if($this->brewTime === 0){
$this->brewTime = self::BREW_TIME_TICKS;
--$this->remainingFuelTime;
}
--$this->brewTime;
if($this->brewTime <= 0){
$anythingBrewed = false;
foreach($recipes as $slot => $recipe){
$input = $this->inventory->getItem($slot);
$output = $recipe->getResultFor($input);
if($output === null){
continue;
}
$ev = new BrewItemEvent($this, $slot, $input, $output, $recipe);
$ev->call();
if($ev->isCancelled()){
continue;
}
$this->inventory->setItem($slot, $ev->getResult());
$anythingBrewed = true;
}
if($anythingBrewed){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new PotionFinishBrewingSound());
}
$ingredient->pop();
$this->inventory->setItem(BrewingStandInventory::SLOT_INGREDIENT, $ingredient);
$this->brewTime = 0;
}else{
$ret = true;
}
}else{
$this->brewTime = 0;
}
}else{
$this->brewTime = $this->remainingFuelTime = $this->maxFuelTime = 0;
}
$viewers = array_map(fn(Player $p) => $p->getNetworkSession()->getInvManager(), $this->inventory->getViewers());
foreach($viewers as $v){
if($v === null){
continue;
}
if($prevBrewTime !== $this->brewTime){
$v->syncData($this->inventory, ContainerSetDataPacket::PROPERTY_BREWING_STAND_BREW_TIME, $this->brewTime);
}
if($prevRemainingFuelTime !== $this->remainingFuelTime){
$v->syncData($this->inventory, ContainerSetDataPacket::PROPERTY_BREWING_STAND_FUEL_AMOUNT, $this->remainingFuelTime);
}
if($prevMaxFuelTime !== $this->maxFuelTime){
$v->syncData($this->inventory, ContainerSetDataPacket::PROPERTY_BREWING_STAND_FUEL_TOTAL, $this->maxFuelTime);
}
}
$this->timings->stopTiming();
return $ret;
}
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\inventory\BrewingStandInventory;
use pocketmine\utils\EnumTrait;
/**
@ -36,13 +37,24 @@ use pocketmine\utils\EnumTrait;
* @method static BrewingStandSlot SOUTHWEST()
*/
final class BrewingStandSlot{
use EnumTrait;
use EnumTrait {
__construct as Enum___construct;
}
protected static function setup() : void{
self::registerAll(
new self("east"),
new self("northwest"),
new self("southwest")
new self("east", BrewingStandInventory::SLOT_BOTTLE_LEFT),
new self("northwest", BrewingStandInventory::SLOT_BOTTLE_MIDDLE),
new self("southwest", BrewingStandInventory::SLOT_BOTTLE_RIGHT)
);
}
private function __construct(string $enumName, private int $slotNumber){
$this->Enum___construct($enumName);
}
/**
* Returns the brewing stand inventory slot number associated with this visual slot.
*/
public function getSlotNumber() : int{ return $this->slotNumber; }
}

View File

@ -0,0 +1,30 @@
<?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\crafting;
use pocketmine\item\Item;
interface BrewingRecipe{
public function getResultFor(Item $input) : ?Item;
}

View File

@ -45,6 +45,18 @@ class CraftingManager{
*/
protected $furnaceRecipeManagers;
/**
* @var PotionTypeRecipe[][]
* @phpstan-var array<string, array<string, PotionTypeRecipe>>
*/
protected $potionTypeRecipes = [];
/**
* @var PotionContainerChangeRecipe[][]
* @phpstan-var array<int, array<string, PotionContainerChangeRecipe>>
*/
protected $potionContainerChangeRecipes = [];
/**
* @var ObjectSet
* @phpstan-var ObjectSet<\Closure() : void>
@ -140,6 +152,22 @@ class CraftingManager{
return $this->furnaceRecipeManagers[$furnaceType->id()];
}
/**
* @return PotionTypeRecipe[][]
* @phpstan-return array<string, array<string, PotionTypeRecipe>>
*/
public function getPotionTypeRecipes() : array{
return $this->potionTypeRecipes;
}
/**
* @return PotionContainerChangeRecipe[][]
* @phpstan-return array<int, array<string, PotionContainerChangeRecipe>>
*/
public function getPotionContainerChangeRecipes() : array{
return $this->potionContainerChangeRecipes;
}
public function registerShapedRecipe(ShapedRecipe $recipe) : void{
$this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe;
@ -156,6 +184,25 @@ class CraftingManager{
}
}
public function registerPotionTypeRecipe(PotionTypeRecipe $recipe) : void{
$input = $recipe->getInput();
$ingredient = $recipe->getIngredient();
$this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":" . ($ingredient->hasAnyDamageValue() ? "?" : $ingredient->getMeta())] = $recipe;
foreach($this->recipeRegisteredCallbacks as $callback){
$callback();
}
}
public function registerPotionContainerChangeRecipe(PotionContainerChangeRecipe $recipe) : void{
$ingredient = $recipe->getIngredient();
$this->potionContainerChangeRecipes[$recipe->getInputItemId()][$ingredient->getId() . ":" . ($ingredient->hasAnyDamageValue() ? "?" : $ingredient->getMeta())] = $recipe;
foreach($this->recipeRegisteredCallbacks as $callback){
$callback();
}
}
/**
* @param Item[] $outputs
*/
@ -206,4 +253,11 @@ class CraftingManager{
}
}
}
public function matchBrewingRecipe(Item $input, Item $ingredient) : ?BrewingRecipe{
return $this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":" . $ingredient->getMeta()] ??
$this->potionTypeRecipes[$input->getId() . ":" . $input->getMeta()][$ingredient->getId() . ":?"] ??
$this->potionContainerChangeRecipes[$input->getId()][$ingredient->getId() . ":" . $ingredient->getMeta()] ??
$this->potionContainerChangeRecipes[$input->getId()][$ingredient->getId() . ":?"] ?? null;
}
}

View File

@ -77,6 +77,20 @@ final class CraftingManagerFromDataHelper{
Item::jsonDeserialize($recipe["input"]))
);
}
foreach($recipes["potion_type"] as $recipe){
$result->registerPotionTypeRecipe(new PotionTypeRecipe(
Item::jsonDeserialize($recipe["input"]),
Item::jsonDeserialize($recipe["ingredient"]),
Item::jsonDeserialize($recipe["output"])
));
}
foreach($recipes["potion_container_change"] as $recipe){
$result->registerPotionContainerChangeRecipe(new PotionContainerChangeRecipe(
$recipe["input_item_id"],
Item::jsonDeserialize($recipe["ingredient"]),
$recipe["output_item_id"]
));
}
return $result;
}

View File

@ -0,0 +1,54 @@
<?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\crafting;
use pocketmine\item\Item;
use pocketmine\item\ItemFactory;
class PotionContainerChangeRecipe implements BrewingRecipe{
public function __construct(
private int $inputItemId,
private Item $ingredient,
private int $outputItemId
){
$this->ingredient = clone $ingredient;
}
public function getInputItemId() : int{
return $this->inputItemId;
}
public function getIngredient() : Item{
return clone $this->ingredient;
}
public function getOutputItemId() : int{
return $this->outputItemId;
}
public function getResultFor(Item $input) : ?Item{
return $input->getId() === $this->getInputItemId() ? ItemFactory::getInstance()->get($this->getOutputItemId(), $input->getMeta()) : null;
}
}

View File

@ -0,0 +1,55 @@
<?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\crafting;
use pocketmine\item\Item;
class PotionTypeRecipe implements BrewingRecipe{
public function __construct(
private Item $input,
private Item $ingredient,
private Item $output
){
$this->input = clone $input;
$this->ingredient = clone $ingredient;
$this->output = clone $output;
}
public function getInput() : Item{
return clone $this->input;
}
public function getIngredient() : Item{
return clone $this->ingredient;
}
public function getOutput() : Item{
return clone $this->output;
}
public function getResultFor(Item $input) : ?Item{
return $input->equals($this->input, true, false) ? $this->getOutput() : null;
}
}

View File

@ -0,0 +1,71 @@
<?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\tile\BrewingStand;
use pocketmine\crafting\BrewingRecipe;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\item\Item;
class BrewItemEvent extends BlockEvent implements Cancellable{
use CancellableTrait;
public function __construct(
private BrewingStand $brewingStand,
private int $slot,
private Item $input,
private Item $result,
private BrewingRecipe $recipe
){
parent::__construct($brewingStand->getBlock());
}
public function getBrewingStand() : BrewingStand{
return $this->brewingStand;
}
/**
* Returns which slot of the brewing stand's inventory the potion is in.
*/
public function getSlot() : int{
return $this->slot;
}
public function getInput() : Item{
return clone $this->input;
}
public function getResult() : Item{
return clone $this->result;
}
public function setResult(Item $result) : void{
$this->result = clone $result;
}
public function getRecipe() : BrewingRecipe{
return $this->recipe;
}
}

View File

@ -0,0 +1,64 @@
<?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\tile\BrewingStand;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
/**
* Called when a brewing stand consumes a new fuel item.
*/
class BrewingFuelUseEvent extends BlockEvent implements Cancellable{
use CancellableTrait;
private int $fuelTime = 20;
public function __construct(
private BrewingStand $brewingStand
){
parent::__construct($brewingStand->getBlock());
}
public function getBrewingStand() : BrewingStand{
return $this->brewingStand;
}
/**
* Returns how many times the fuel can be used for potion brewing before it runs out.
*/
public function getFuelTime() : int{
return $this->fuelTime;
}
/**
* Sets how many times the fuel can be used for potion brewing before it runs out.
*/
public function setFuelTime(int $fuelTime) : void{
if($fuelTime <= 0){
throw new \InvalidArgumentException("Fuel time must be positive");
}
$this->fuelTime = $fuelTime;
}
}

View File

@ -26,12 +26,15 @@ namespace pocketmine\network\mcpe\cache;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\FurnaceType;
use pocketmine\item\Item;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\recipe\CraftingRecipeBlockName;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipeBlockName;
use pocketmine\network\mcpe\protocol\types\recipe\PotionContainerChangeRecipe as ProtocolPotionContainerChangeRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\PotionTypeRecipe as ProtocolPotionTypeRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient;
use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe;
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
@ -137,7 +140,39 @@ final class CraftingDataCache{
}
}
$potionTypeRecipes = [];
foreach($manager->getPotionTypeRecipes() as $recipes){
foreach($recipes as $recipe){
$input = $converter->coreItemStackToNet($recipe->getInput());
$ingredient = $converter->coreItemStackToNet($recipe->getIngredient());
$output = $converter->coreItemStackToNet($recipe->getOutput());
$potionTypeRecipes[] = new ProtocolPotionTypeRecipe(
$input->getId(),
$input->getMeta(),
$ingredient->getId(),
$ingredient->getMeta(),
$output->getId(),
$output->getMeta()
);
}
}
$potionContainerChangeRecipes = [];
$itemTranslator = ItemTranslator::getInstance();
foreach($manager->getPotionContainerChangeRecipes() as $recipes){
foreach($recipes as $recipe){
$input = $itemTranslator->toNetworkId($recipe->getInputItemId(), 0);
$ingredient = $itemTranslator->toNetworkId($recipe->getIngredient()->getId(), 0);
$output = $itemTranslator->toNetworkId($recipe->getOutputItemId(), 0);
$potionContainerChangeRecipes[] = new ProtocolPotionContainerChangeRecipe(
$input[0],
$ingredient[0],
$output[0]
);
}
}
Timings::$craftingDataCacheRebuild->stopTiming();
return CraftingDataPacket::create($recipesWithTypeIds, [], [], [], true);
return CraftingDataPacket::create($recipesWithTypeIds, $potionTypeRecipes, $potionContainerChangeRecipes, [], true);
}
}

View 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;
final class PotionFinishBrewingSound implements Sound{
public function encode(Vector3 $pos) : array{
return [LevelSoundEventPacket::nonActorSound(LevelSoundEvent::POTION_BREWED, $pos, false)];
}
}