diff --git a/src/data/bedrock/EffectIdMap.php b/src/data/bedrock/EffectIdMap.php new file mode 100644 index 000000000..b9ccf2471 --- /dev/null +++ b/src/data/bedrock/EffectIdMap.php @@ -0,0 +1,97 @@ + + */ + private $idToEffect = []; + + /** + * @var int[] + * @phpstan-var array + */ + private $effectToId = []; + + private function __construct(){ + $this->register(EffectIds::SPEED, VanillaEffects::SPEED()); + $this->register(EffectIds::SLOWNESS, VanillaEffects::SLOWNESS()); + $this->register(EffectIds::HASTE, VanillaEffects::HASTE()); + $this->register(EffectIds::MINING_FATIGUE, VanillaEffects::MINING_FATIGUE()); + $this->register(EffectIds::STRENGTH, VanillaEffects::STRENGTH()); + $this->register(EffectIds::INSTANT_HEALTH, VanillaEffects::INSTANT_HEALTH()); + $this->register(EffectIds::INSTANT_DAMAGE, VanillaEffects::INSTANT_DAMAGE()); + $this->register(EffectIds::JUMP_BOOST, VanillaEffects::JUMP_BOOST()); + $this->register(EffectIds::NAUSEA, VanillaEffects::NAUSEA()); + $this->register(EffectIds::REGENERATION, VanillaEffects::REGENERATION()); + $this->register(EffectIds::RESISTANCE, VanillaEffects::RESISTANCE()); + $this->register(EffectIds::FIRE_RESISTANCE, VanillaEffects::FIRE_RESISTANCE()); + $this->register(EffectIds::WATER_BREATHING, VanillaEffects::WATER_BREATHING()); + $this->register(EffectIds::INVISIBILITY, VanillaEffects::INVISIBILITY()); + $this->register(EffectIds::BLINDNESS, VanillaEffects::BLINDNESS()); + $this->register(EffectIds::NIGHT_VISION, VanillaEffects::NIGHT_VISION()); + $this->register(EffectIds::HUNGER, VanillaEffects::HUNGER()); + $this->register(EffectIds::WEAKNESS, VanillaEffects::WEAKNESS()); + $this->register(EffectIds::POISON, VanillaEffects::POISON()); + $this->register(EffectIds::WITHER, VanillaEffects::WITHER()); + $this->register(EffectIds::HEALTH_BOOST, VanillaEffects::HEALTH_BOOST()); + $this->register(EffectIds::ABSORPTION, VanillaEffects::ABSORPTION()); + $this->register(EffectIds::SATURATION, VanillaEffects::SATURATION()); + $this->register(EffectIds::LEVITATION, VanillaEffects::LEVITATION()); + $this->register(EffectIds::FATAL_POISON, VanillaEffects::FATAL_POISON()); + $this->register(EffectIds::CONDUIT_POWER, VanillaEffects::CONDUIT_POWER()); + //TODO: SLOW_FALLING + //TODO: BAD_OMEN + //TODO: VILLAGE_HERO + } + + //TODO: not a big fan of the code duplication here :( + + public function register(int $mcpeId, Effect $effect) : void{ + $this->idToEffect[$mcpeId] = $effect; + $this->effectToId[$effect->getRuntimeId()] = $mcpeId; + } + + public function fromId(int $id) : ?Effect{ + //we might not have all the effect IDs registered + return $this->idToEffect[$id] ?? null; + } + + public function toId(Effect $effect) : int{ + if(!array_key_exists($effect->getRuntimeId(), $this->effectToId)){ + //this should never happen, so we treat it as an exceptional condition + throw new \InvalidArgumentException("Effect does not have a mapped ID"); + } + return $this->effectToId[$effect->getRuntimeId()]; + } +} diff --git a/src/data/bedrock/EffectIds.php b/src/data/bedrock/EffectIds.php new file mode 100644 index 000000000..a54000f7f --- /dev/null +++ b/src/data/bedrock/EffectIds.php @@ -0,0 +1,61 @@ +getListTag("ActiveEffects"); if($activeEffectsTag !== null){ foreach($activeEffectsTag as $e){ - $effect = VanillaEffects::byMcpeId($e->getByte("Id")); + $effect = EffectIdMap::getInstance()->fromId($e->getByte("Id")); if($effect === null){ continue; } @@ -240,7 +241,7 @@ abstract class Living extends Entity{ $effects = []; foreach($this->effectManager->all() as $effect){ $effects[] = CompoundTag::create() - ->setByte("Id", $effect->getId()) + ->setByte("Id", EffectIdMap::getInstance()->toId($effect->getType())) ->setByte("Amplifier", Binary::signByte($effect->getAmplifier())) ->setInt("Duration", $effect->getDuration()) ->setByte("Ambient", $effect->isAmbient() ? 1 : 0) diff --git a/src/entity/effect/Effect.php b/src/entity/effect/Effect.php index a6b85ea06..277b16972 100644 --- a/src/entity/effect/Effect.php +++ b/src/entity/effect/Effect.php @@ -30,7 +30,7 @@ use pocketmine\entity\Living; class Effect{ /** @var int */ - protected $id; + protected $internalRuntimeId; /** @var string */ protected $name; /** @var Color */ @@ -41,14 +41,14 @@ class Effect{ protected $hasBubbles; /** - * @param int $id Effect ID as per Minecraft PE + * @param int $internalRuntimeId Internal runtime ID, unique to this effect type. Used for comparisons. * @param string $name Translation key used for effect name * @param Color $color Color of bubbles given by this effect * @param bool $isBad Whether the effect is harmful * @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles) */ - public function __construct(int $id, string $name, Color $color, bool $isBad = false, bool $hasBubbles = true){ - $this->id = $id; + public function __construct(int $internalRuntimeId, string $name, Color $color, bool $isBad = false, bool $hasBubbles = true){ + $this->internalRuntimeId = $internalRuntimeId; $this->name = $name; $this->color = $color; $this->bad = $isBad; @@ -56,10 +56,11 @@ class Effect{ } /** - * Returns the effect ID as per Minecraft PE + * Returns a unique identifier for this effect type + * WARNING: DO NOT STORE THIS - IT MAY CHANGE BETWEEN RESTARTS */ - public function getId() : int{ - return $this->id; + public function getRuntimeId() : int{ + return $this->internalRuntimeId; } /** diff --git a/src/entity/effect/EffectInstance.php b/src/entity/effect/EffectInstance.php index c006275e7..c908c7ec7 100644 --- a/src/entity/effect/EffectInstance.php +++ b/src/entity/effect/EffectInstance.php @@ -58,10 +58,6 @@ class EffectInstance{ $this->color = $overrideColor ?? $effectType->getColor(); } - public function getId() : int{ - return $this->effectType->getId(); - } - public function getType() : Effect{ return $this->effectType; } diff --git a/src/entity/effect/EffectManager.php b/src/entity/effect/EffectManager.php index d5343b777..93404120a 100644 --- a/src/entity/effect/EffectManager.php +++ b/src/entity/effect/EffectManager.php @@ -83,7 +83,7 @@ class EffectManager{ * Removes the effect with the specified ID from the mob. */ public function remove(Effect $effectType) : void{ - $index = $effectType->getId(); + $index = $effectType->getRuntimeId(); if(isset($this->effects[$index])){ $effect = $this->effects[$index]; $hasExpired = $effect->hasExpired(); @@ -113,14 +113,14 @@ class EffectManager{ * effect. */ public function get(Effect $effect) : ?EffectInstance{ - return $this->effects[$effect->getId()] ?? null; + return $this->effects[$effect->getRuntimeId()] ?? null; } /** * Returns whether the specified effect is active on the mob. */ public function has(Effect $effect) : bool{ - return isset($this->effects[$effect->getId()]); + return isset($this->effects[$effect->getRuntimeId()]); } /** @@ -134,7 +134,7 @@ class EffectManager{ $oldEffect = null; $cancelled = false; - $index = $effect->getType()->getId(); + $index = $effect->getType()->getRuntimeId(); if(isset($this->effects[$index])){ $oldEffect = $this->effects[$index]; if( diff --git a/src/entity/effect/PoisonEffect.php b/src/entity/effect/PoisonEffect.php index 5c977be27..e55da697c 100644 --- a/src/entity/effect/PoisonEffect.php +++ b/src/entity/effect/PoisonEffect.php @@ -33,8 +33,8 @@ class PoisonEffect extends Effect{ /** @var bool */ private $fatal; - public function __construct(int $id, string $name, Color $color, bool $isBad = false, bool $hasBubbles = true, bool $fatal = false){ - parent::__construct($id, $name, $color, $isBad, $hasBubbles); + public function __construct(int $internalRuntimeId, string $name, Color $color, bool $isBad = false, bool $hasBubbles = true, bool $fatal = false){ + parent::__construct($internalRuntimeId, $name, $color, $isBad, $hasBubbles); $this->fatal = $fatal; } diff --git a/src/entity/effect/VanillaEffects.php b/src/entity/effect/VanillaEffects.php index 3458fc587..562682dad 100644 --- a/src/entity/effect/VanillaEffects.php +++ b/src/entity/effect/VanillaEffects.php @@ -62,9 +62,6 @@ use function assert; final class VanillaEffects{ use RegistryTrait; - /** @var Effect[] */ - private static $mcpeIdMap = []; - protected static function setup() : void{ self::register("absorption", new AbsorptionEffect(22, "%potion.absorption", new Color(0x25, 0x52, 0xa5))); //TODO: bad_omen @@ -99,13 +96,6 @@ final class VanillaEffects{ protected static function register(string $name, Effect $member) : void{ self::_registryRegister($name, $member); - assert(!isset(self::$mcpeIdMap[$member->getId()])); - self::$mcpeIdMap[$member->getId()] = $member; - } - - public static function byMcpeId(int $id) : ?Effect{ - self::checkInit(); - return self::$mcpeIdMap[$id] ?? null; } /** diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 214cc7863..51be7de81 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe; use Ds\Set; use Mdanter\Ecc\Crypto\Key\PublicKeyInterface; +use pocketmine\data\bedrock\EffectIdMap; use pocketmine\entity\Attribute; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\Entity; @@ -742,11 +743,12 @@ class NetworkSession{ } public function onEntityEffectAdded(Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{ - $this->sendDataPacket(MobEffectPacket::add($entity->getId(), $replacesOldEffect, $effect->getId(), $effect->getAmplifier(), $effect->isVisible(), $effect->getDuration())); + //TODO: we may need yet another effect <=> ID map in the future depending on protocol changes + $this->sendDataPacket(MobEffectPacket::add($entity->getId(), $replacesOldEffect, EffectIdMap::getInstance()->toId($effect->getType()), $effect->getAmplifier(), $effect->isVisible(), $effect->getDuration())); } public function onEntityEffectRemoved(Living $entity, EffectInstance $effect) : void{ - $this->sendDataPacket(MobEffectPacket::remove($entity->getId(), $effect->getId())); + $this->sendDataPacket(MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType()))); } public function onEntityRemoved(Entity $entity) : void{ diff --git a/tests/phpunit/data/bedrock/EffectIdMapTest.php b/tests/phpunit/data/bedrock/EffectIdMapTest.php new file mode 100644 index 000000000..51b5c4d74 --- /dev/null +++ b/tests/phpunit/data/bedrock/EffectIdMapTest.php @@ -0,0 +1,38 @@ +toId($e); + $e2 = EffectIdMap::getInstance()->fromId($id); + self::assertTrue($e === $e2); + } + } +}