diff --git a/src/data/bedrock/PotionTypeIdMap.php b/src/data/bedrock/PotionTypeIdMap.php new file mode 100644 index 000000000..d665b0222 --- /dev/null +++ b/src/data/bedrock/PotionTypeIdMap.php @@ -0,0 +1,99 @@ + + */ + private array $idToEnum; + + /** + * @var int[] + * @phpstan-var array + */ + private array $enumToId; + + private function __construct(){ + $this->register(PotionTypeIds::WATER, PotionType::WATER()); + $this->register(PotionTypeIds::MUNDANE, PotionType::MUNDANE()); + $this->register(PotionTypeIds::LONG_MUNDANE, PotionType::LONG_MUNDANE()); + $this->register(PotionTypeIds::THICK, PotionType::THICK()); + $this->register(PotionTypeIds::AWKWARD, PotionType::AWKWARD()); + $this->register(PotionTypeIds::NIGHT_VISION, PotionType::NIGHT_VISION()); + $this->register(PotionTypeIds::LONG_NIGHT_VISION, PotionType::LONG_NIGHT_VISION()); + $this->register(PotionTypeIds::INVISIBILITY, PotionType::INVISIBILITY()); + $this->register(PotionTypeIds::LONG_INVISIBILITY, PotionType::LONG_INVISIBILITY()); + $this->register(PotionTypeIds::LEAPING, PotionType::LEAPING()); + $this->register(PotionTypeIds::LONG_LEAPING, PotionType::LONG_LEAPING()); + $this->register(PotionTypeIds::STRONG_LEAPING, PotionType::STRONG_LEAPING()); + $this->register(PotionTypeIds::FIRE_RESISTANCE, PotionType::FIRE_RESISTANCE()); + $this->register(PotionTypeIds::LONG_FIRE_RESISTANCE, PotionType::LONG_FIRE_RESISTANCE()); + $this->register(PotionTypeIds::SWIFTNESS, PotionType::SWIFTNESS()); + $this->register(PotionTypeIds::LONG_SWIFTNESS, PotionType::LONG_SWIFTNESS()); + $this->register(PotionTypeIds::STRONG_SWIFTNESS, PotionType::STRONG_SWIFTNESS()); + $this->register(PotionTypeIds::SLOWNESS, PotionType::SLOWNESS()); + $this->register(PotionTypeIds::LONG_SLOWNESS, PotionType::LONG_SLOWNESS()); + $this->register(PotionTypeIds::WATER_BREATHING, PotionType::WATER_BREATHING()); + $this->register(PotionTypeIds::LONG_WATER_BREATHING, PotionType::LONG_WATER_BREATHING()); + $this->register(PotionTypeIds::HEALING, PotionType::HEALING()); + $this->register(PotionTypeIds::STRONG_HEALING, PotionType::STRONG_HEALING()); + $this->register(PotionTypeIds::HARMING, PotionType::HARMING()); + $this->register(PotionTypeIds::STRONG_HARMING, PotionType::STRONG_HARMING()); + $this->register(PotionTypeIds::POISON, PotionType::POISON()); + $this->register(PotionTypeIds::LONG_POISON, PotionType::LONG_POISON()); + $this->register(PotionTypeIds::STRONG_POISON, PotionType::STRONG_POISON()); + $this->register(PotionTypeIds::REGENERATION, PotionType::REGENERATION()); + $this->register(PotionTypeIds::LONG_REGENERATION, PotionType::LONG_REGENERATION()); + $this->register(PotionTypeIds::STRONG_REGENERATION, PotionType::STRONG_REGENERATION()); + $this->register(PotionTypeIds::STRENGTH, PotionType::STRENGTH()); + $this->register(PotionTypeIds::LONG_STRENGTH, PotionType::LONG_STRENGTH()); + $this->register(PotionTypeIds::STRONG_STRENGTH, PotionType::STRONG_STRENGTH()); + $this->register(PotionTypeIds::WEAKNESS, PotionType::WEAKNESS()); + $this->register(PotionTypeIds::LONG_WEAKNESS, PotionType::LONG_WEAKNESS()); + $this->register(PotionTypeIds::WITHER, PotionType::WITHER()); + } + + private function register(int $id, PotionType $type) : void{ + $this->idToEnum[$id] = $type; + $this->enumToId[$type->id()] = $id; + } + + public function fromId(int $id) : ?PotionType{ + return $this->idToEnum[$id] ?? null; + } + + public function toId(PotionType $type) : int{ + if(!isset($this->enumToId[$type->id()])){ + throw new \InvalidArgumentException("Type does not have a mapped ID"); + } + return $this->enumToId[$type->id()]; + } +} diff --git a/src/data/bedrock/PotionTypeIds.php b/src/data/bedrock/PotionTypeIds.php new file mode 100644 index 000000000..41167c2d6 --- /dev/null +++ b/src/data/bedrock/PotionTypeIds.php @@ -0,0 +1,64 @@ +register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{ - $potionType = $nbt->getShort("PotionId", Potion::WATER); + $potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER)); + if($potionType === null){ + $potionType = PotionType::WATER(); //TODO: this should be an error, but we haven't registered all the types yet + } return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt); }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION); diff --git a/src/entity/projectile/SplashPotion.php b/src/entity/projectile/SplashPotion.php index 8cde81282..49bb264c0 100644 --- a/src/entity/projectile/SplashPotion.php +++ b/src/entity/projectile/SplashPotion.php @@ -26,6 +26,7 @@ namespace pocketmine\entity\projectile; use pocketmine\block\BlockLegacyIds; use pocketmine\block\VanillaBlocks; use pocketmine\color\Color; +use pocketmine\data\bedrock\PotionTypeIdMap; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\InstantEffect; use pocketmine\entity\Entity; @@ -35,6 +36,7 @@ use pocketmine\event\entity\ProjectileHitBlockEvent; use pocketmine\event\entity\ProjectileHitEntityEvent; use pocketmine\event\entity\ProjectileHitEvent; use pocketmine\item\Potion; +use pocketmine\item\PotionType; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; @@ -55,17 +57,16 @@ class SplashPotion extends Throwable{ /** @var bool */ protected $linger = false; - /** @var int */ - protected $potionId; + protected PotionType $potionType; - public function __construct(Location $location, ?Entity $shootingEntity, int $potionId, ?CompoundTag $nbt = null){ - $this->potionId = $potionId; + public function __construct(Location $location, ?Entity $shootingEntity, PotionType $potionType, ?CompoundTag $nbt = null){ + $this->potionType = $potionType; parent::__construct($location, $shootingEntity, $nbt); } public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); - $nbt->setShort("PotionId", $this->getPotionId()); + $nbt->setShort("PotionId", PotionTypeIdMap::getInstance()->toId($this->getPotionType())); return $nbt; } @@ -128,7 +129,7 @@ class SplashPotion extends Throwable{ }else{ //TODO: lingering potions } - }elseif($event instanceof ProjectileHitBlockEvent and $this->getPotionId() === Potion::WATER){ + }elseif($event instanceof ProjectileHitBlockEvent and $this->getPotionType()->equals(PotionType::WATER())){ $blockIn = $event->getBlockHit()->getSide($event->getRayTraceResult()->getHitFace()); if($blockIn->getId() === BlockLegacyIds::FIRE){ @@ -145,12 +146,12 @@ class SplashPotion extends Throwable{ /** * Returns the meta value of the potion item that this splash potion corresponds to. This decides what effects will be applied to the entity when it collides with its target. */ - public function getPotionId() : int{ - return $this->potionId; + public function getPotionType() : PotionType{ + return $this->potionType; } - public function setPotionId(int $id) : void{ - $this->potionId = $id; //TODO: validation + public function setPotionType(PotionType $type) : void{ + $this->potionType = $type; $this->networkPropertiesDirty = true; } @@ -173,13 +174,13 @@ class SplashPotion extends Throwable{ * @return EffectInstance[] */ public function getPotionEffects() : array{ - return Potion::getPotionEffectsById($this->getPotionId()); + return $this->potionType->getEffects(); } protected function syncNetworkData(EntityMetadataCollection $properties) : void{ parent::syncNetworkData($properties); - $properties->setShort(EntityMetadataProperties::POTION_AUX_VALUE, $this->potionId); + $properties->setShort(EntityMetadataProperties::POTION_AUX_VALUE, PotionTypeIdMap::getInstance()->toId($this->potionType)); $properties->setGenericFlag(EntityMetadataFlags::LINGER, $this->linger); } } diff --git a/src/item/ItemFactory.php b/src/item/ItemFactory.php index 8cfaec10f..64707f13e 100644 --- a/src/item/ItemFactory.php +++ b/src/item/ItemFactory.php @@ -33,6 +33,7 @@ use pocketmine\block\utils\TreeType; use pocketmine\block\VanillaBlocks; use pocketmine\data\bedrock\DyeColorIdMap; use pocketmine\data\bedrock\EntityLegacyIds; +use pocketmine\data\bedrock\PotionTypeIdMap; use pocketmine\entity\Entity; use pocketmine\entity\Location; use pocketmine\entity\Squid; @@ -278,9 +279,10 @@ class ItemFactory{ ))->setColor($color)); } - foreach(Potion::ALL as $type){ - $this->register(new Potion(new ItemIdentifier(ItemIds::POTION, $type), "Potion", $type)); - $this->register(new SplashPotion(new ItemIdentifier(ItemIds::SPLASH_POTION, $type), "Splash Potion", $type)); + foreach(PotionType::getAll() as $type){ + $typeId = PotionTypeIdMap::getInstance()->toId($type); + $this->register(new Potion(new ItemIdentifier(ItemIds::POTION, $typeId), "Potion", $type)); + $this->register(new SplashPotion(new ItemIdentifier(ItemIds::SPLASH_POTION, $typeId), "Splash Potion", $type)); } foreach(TreeType::getAll() as $type){ diff --git a/src/item/Potion.php b/src/item/Potion.php index 4d4dd1b34..13fc8640a 100644 --- a/src/item/Potion.php +++ b/src/item/Potion.php @@ -23,242 +23,15 @@ declare(strict_types=1); namespace pocketmine\item; -use pocketmine\entity\effect\EffectInstance; -use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\Living; class Potion extends Item implements ConsumableItem{ - public const WATER = 0; - public const MUNDANE = 1; - public const LONG_MUNDANE = 2; - public const THICK = 3; - public const AWKWARD = 4; - public const NIGHT_VISION = 5; - public const LONG_NIGHT_VISION = 6; - public const INVISIBILITY = 7; - public const LONG_INVISIBILITY = 8; - public const LEAPING = 9; - public const LONG_LEAPING = 10; - public const STRONG_LEAPING = 11; - public const FIRE_RESISTANCE = 12; - public const LONG_FIRE_RESISTANCE = 13; - public const SWIFTNESS = 14; - public const LONG_SWIFTNESS = 15; - public const STRONG_SWIFTNESS = 16; - public const SLOWNESS = 17; - public const LONG_SLOWNESS = 18; - public const WATER_BREATHING = 19; - public const LONG_WATER_BREATHING = 20; - public const HEALING = 21; - public const STRONG_HEALING = 22; - public const HARMING = 23; - public const STRONG_HARMING = 24; - public const POISON = 25; - public const LONG_POISON = 26; - public const STRONG_POISON = 27; - public const REGENERATION = 28; - public const LONG_REGENERATION = 29; - public const STRONG_REGENERATION = 30; - public const STRENGTH = 31; - public const LONG_STRENGTH = 32; - public const STRONG_STRENGTH = 33; - public const WEAKNESS = 34; - public const LONG_WEAKNESS = 35; - public const WITHER = 36; + private PotionType $potionType; - public const ALL = [ - self::WATER, - self::MUNDANE, - self::LONG_MUNDANE, - self::THICK, - self::AWKWARD, - self::NIGHT_VISION, - self::LONG_NIGHT_VISION, - self::INVISIBILITY, - self::LONG_INVISIBILITY, - self::LEAPING, - self::LONG_LEAPING, - self::STRONG_LEAPING, - self::FIRE_RESISTANCE, - self::LONG_FIRE_RESISTANCE, - self::SWIFTNESS, - self::LONG_SWIFTNESS, - self::STRONG_SWIFTNESS, - self::SLOWNESS, - self::LONG_SLOWNESS, - self::WATER_BREATHING, - self::LONG_WATER_BREATHING, - self::HEALING, - self::STRONG_HEALING, - self::HARMING, - self::STRONG_HARMING, - self::POISON, - self::LONG_POISON, - self::STRONG_POISON, - self::REGENERATION, - self::LONG_REGENERATION, - self::STRONG_REGENERATION, - self::STRENGTH, - self::LONG_STRENGTH, - self::STRONG_STRENGTH, - self::WEAKNESS, - self::LONG_WEAKNESS, - self::WITHER - ]; - - /** - * Returns a list of effects applied by potions with the specified ID. - * - * @return EffectInstance[] - */ - public static function getPotionEffectsById(int $id) : array{ - switch($id){ - case self::WATER: - case self::MUNDANE: - case self::LONG_MUNDANE: - case self::THICK: - case self::AWKWARD: - return []; - case self::NIGHT_VISION: - return [ - new EffectInstance(VanillaEffects::NIGHT_VISION(), 3600) - ]; - case self::LONG_NIGHT_VISION: - return [ - new EffectInstance(VanillaEffects::NIGHT_VISION(), 9600) - ]; - case self::INVISIBILITY: - return [ - new EffectInstance(VanillaEffects::INVISIBILITY(), 3600) - ]; - case self::LONG_INVISIBILITY: - return [ - new EffectInstance(VanillaEffects::INVISIBILITY(), 9600) - ]; - case self::LEAPING: - return [ - new EffectInstance(VanillaEffects::JUMP_BOOST(), 3600) - ]; - case self::LONG_LEAPING: - return [ - new EffectInstance(VanillaEffects::JUMP_BOOST(), 9600) - ]; - case self::STRONG_LEAPING: - return [ - new EffectInstance(VanillaEffects::JUMP_BOOST(), 1800, 1) - ]; - case self::FIRE_RESISTANCE: - return [ - new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 3600) - ]; - case self::LONG_FIRE_RESISTANCE: - return [ - new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 9600) - ]; - case self::SWIFTNESS: - return [ - new EffectInstance(VanillaEffects::SPEED(), 3600) - ]; - case self::LONG_SWIFTNESS: - return [ - new EffectInstance(VanillaEffects::SPEED(), 9600) - ]; - case self::STRONG_SWIFTNESS: - return [ - new EffectInstance(VanillaEffects::SPEED(), 1800, 1) - ]; - case self::SLOWNESS: - return [ - new EffectInstance(VanillaEffects::SLOWNESS(), 1800) - ]; - case self::LONG_SLOWNESS: - return [ - new EffectInstance(VanillaEffects::SLOWNESS(), 4800) - ]; - case self::WATER_BREATHING: - return [ - new EffectInstance(VanillaEffects::WATER_BREATHING(), 3600) - ]; - case self::LONG_WATER_BREATHING: - return [ - new EffectInstance(VanillaEffects::WATER_BREATHING(), 9600) - ]; - case self::HEALING: - return [ - new EffectInstance(VanillaEffects::INSTANT_HEALTH()) - ]; - case self::STRONG_HEALING: - return [ - new EffectInstance(VanillaEffects::INSTANT_HEALTH(), null, 1) - ]; - case self::HARMING: - return [ - new EffectInstance(VanillaEffects::INSTANT_DAMAGE()) - ]; - case self::STRONG_HARMING: - return [ - new EffectInstance(VanillaEffects::INSTANT_DAMAGE(), null, 1) - ]; - case self::POISON: - return [ - new EffectInstance(VanillaEffects::POISON(), 900) - ]; - case self::LONG_POISON: - return [ - new EffectInstance(VanillaEffects::POISON(), 2400) - ]; - case self::STRONG_POISON: - return [ - new EffectInstance(VanillaEffects::POISON(), 440, 1) - ]; - case self::REGENERATION: - return [ - new EffectInstance(VanillaEffects::REGENERATION(), 900) - ]; - case self::LONG_REGENERATION: - return [ - new EffectInstance(VanillaEffects::REGENERATION(), 2400) - ]; - case self::STRONG_REGENERATION: - return [ - new EffectInstance(VanillaEffects::REGENERATION(), 440, 1) - ]; - case self::STRENGTH: - return [ - new EffectInstance(VanillaEffects::STRENGTH(), 3600) - ]; - case self::LONG_STRENGTH: - return [ - new EffectInstance(VanillaEffects::STRENGTH(), 9600) - ]; - case self::STRONG_STRENGTH: - return [ - new EffectInstance(VanillaEffects::STRENGTH(), 1800, 1) - ]; - case self::WEAKNESS: - return [ - new EffectInstance(VanillaEffects::WEAKNESS(), 1800) - ]; - case self::LONG_WEAKNESS: - return [ - new EffectInstance(VanillaEffects::WEAKNESS(), 4800) - ]; - case self::WITHER: - return [ - new EffectInstance(VanillaEffects::WITHER(), 800, 1) - ]; - } - - return []; - } - - /** @var int */ - private $potionId; - - public function __construct(ItemIdentifier $identifier, string $name, int $potionId){ + public function __construct(ItemIdentifier $identifier, string $name, PotionType $potionType){ parent::__construct($identifier, $name); - $this->potionId = $potionId; + $this->potionType = $potionType; } public function getMaxStackSize() : int{ @@ -271,7 +44,7 @@ class Potion extends Item implements ConsumableItem{ public function getAdditionalEffects() : array{ //TODO: check CustomPotionEffects NBT - return self::getPotionEffectsById($this->potionId); + return $this->potionType->getEffects(); } public function getResidue() : Item{ diff --git a/src/item/PotionType.php b/src/item/PotionType.php new file mode 100644 index 000000000..533d29724 --- /dev/null +++ b/src/item/PotionType.php @@ -0,0 +1,202 @@ + []), + new self("mundane", fn() => []), + new self("long_mundane", fn() => []), + new self("thick", fn() => []), + new self("awkward", fn() => []), + new self("night_vision", fn() => [ + new EffectInstance(VanillaEffects::NIGHT_VISION(), 3600) + ]), + new self("long_night_vision", fn() => [ + new EffectInstance(VanillaEffects::NIGHT_VISION(), 9600) + ]), + new self("invisibility", fn() => [ + new EffectInstance(VanillaEffects::INVISIBILITY(), 3600) + ]), + new self("long_invisibility", fn() => [ + new EffectInstance(VanillaEffects::INVISIBILITY(), 9600) + ]), + new self("leaping", fn() => [ + new EffectInstance(VanillaEffects::JUMP_BOOST(), 3600) + ]), + new self("long_leaping", fn() => [ + new EffectInstance(VanillaEffects::JUMP_BOOST(), 9600) + ]), + new self("strong_leaping", fn() => [ + new EffectInstance(VanillaEffects::JUMP_BOOST(), 1800, 1) + ]), + new self("fire_resistance", fn() => [ + new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 3600) + ]), + new self("long_fire_resistance", fn() => [ + new EffectInstance(VanillaEffects::FIRE_RESISTANCE(), 9600) + ]), + new self("swiftness", fn() => [ + new EffectInstance(VanillaEffects::SPEED(), 3600) + ]), + new self("long_swiftness", fn() => [ + new EffectInstance(VanillaEffects::SPEED(), 9600) + ]), + new self("strong_swiftness", fn() => [ + new EffectInstance(VanillaEffects::SPEED(), 1800, 1) + ]), + new self("slowness", fn() => [ + new EffectInstance(VanillaEffects::SLOWNESS(), 1800) + ]), + new self("long_slowness", fn() => [ + new EffectInstance(VanillaEffects::SLOWNESS(), 4800) + ]), + new self("water_breathing", fn() => [ + new EffectInstance(VanillaEffects::WATER_BREATHING(), 3600) + ]), + new self("long_water_breathing", fn() => [ + new EffectInstance(VanillaEffects::WATER_BREATHING(), 9600) + ]), + new self("healing", fn() => [ + new EffectInstance(VanillaEffects::INSTANT_HEALTH()) + ]), + new self("strong_healing", fn() => [ + new EffectInstance(VanillaEffects::INSTANT_HEALTH(), null, 1) + ]), + new self("harming", fn() => [ + new EffectInstance(VanillaEffects::INSTANT_DAMAGE()) + ]), + new self("strong_harming", fn() => [ + new EffectInstance(VanillaEffects::INSTANT_DAMAGE(), null, 1) + ]), + new self("poison", fn() => [ + new EffectInstance(VanillaEffects::POISON(), 900) + ]), + new self("long_poison", fn() => [ + new EffectInstance(VanillaEffects::POISON(), 2400) + ]), + new self("strong_poison", fn() => [ + new EffectInstance(VanillaEffects::POISON(), 440, 1) + ]), + new self("regeneration", fn() => [ + new EffectInstance(VanillaEffects::REGENERATION(), 900) + ]), + new self("long_regeneration", fn() => [ + new EffectInstance(VanillaEffects::REGENERATION(), 2400) + ]), + new self("strong_regeneration", fn() => [ + new EffectInstance(VanillaEffects::REGENERATION(), 440, 1) + ]), + new self("strength", fn() => [ + new EffectInstance(VanillaEffects::STRENGTH(), 3600) + ]), + new self("long_strength", fn() => [ + new EffectInstance(VanillaEffects::STRENGTH(), 9600) + ]), + new self("strong_strength", fn() => [ + new EffectInstance(VanillaEffects::STRENGTH(), 1800, 1) + ]), + new self("weakness", fn() => [ + new EffectInstance(VanillaEffects::WEAKNESS(), 1800) + ]), + new self("long_weakness", fn() => [ + new EffectInstance(VanillaEffects::WEAKNESS(), 4800) + ]), + new self("wither", fn() => [ + new EffectInstance(VanillaEffects::WITHER(), 800, 1) + ]) + ); + } + + /** @phpstan-var \Closure() : list */ + private \Closure $effectsGetter; + + /** + * @phpstan-param \Closure() : list $effectsGetter + */ + private function __construct(string $enumName, \Closure $effectsGetter){ + $this->Enum___construct($enumName); + $this->effectsGetter = $effectsGetter; + } + + /** + * @return EffectInstance[] + * @phpstan-return list + */ + public function getEffects() : array{ + return ($this->effectsGetter)(); + } +} diff --git a/src/item/SplashPotion.php b/src/item/SplashPotion.php index b1330875a..1b338a96b 100644 --- a/src/item/SplashPotion.php +++ b/src/item/SplashPotion.php @@ -30,12 +30,11 @@ use pocketmine\player\Player; class SplashPotion extends ProjectileItem{ - /** @var int */ - private $potionId; + private PotionType $potionType; - public function __construct(ItemIdentifier $identifier, string $name, int $potionId){ + public function __construct(ItemIdentifier $identifier, string $name, PotionType $potionType){ parent::__construct($identifier, $name); - $this->potionId = $potionId; + $this->potionType = $potionType; } public function getMaxStackSize() : int{ @@ -43,7 +42,7 @@ class SplashPotion extends ProjectileItem{ } protected function createEntity(Location $location, Player $thrower) : Throwable{ - return new SplashPotionEntity($location, $thrower, $this->potionId); + return new SplashPotionEntity($location, $thrower, $this->potionType); } public function getThrowForce() : float{