getId()] = $effect; } public static function getEffect(int $id) : ?Effect{ return self::$effects[$id] ?? null; } public static function getEffectByName(string $name) : ?Effect{ $const = self::class . "::" . strtoupper($name); if(defined($const)){ return self::getEffect(constant($const)); } return null; } /** @var int */ protected $id; /** @var string */ protected $name; /** @var Color */ protected $color; /** @var bool */ protected $bad; /** @var int */ protected $defaultDuration; /** @var bool */ protected $hasBubbles; /** * @param int $id Effect ID as per Minecraft PE * @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 int $defaultDuration Duration in ticks the effect will last for by default if applied without a duration. * @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, int $defaultDuration = 300 * 20, bool $hasBubbles = true){ $this->id = $id; $this->name = $name; $this->color = $color; $this->bad = $isBad; $this->defaultDuration = $defaultDuration; $this->hasBubbles = $hasBubbles; } /** * Returns the effect ID as per Minecraft PE */ public function getId() : int{ return $this->id; } /** * Returns the translation key used to translate this effect's name. */ public function getName() : string{ return $this->name; } /** * Returns a Color object representing this effect's particle colour. */ public function getColor() : Color{ return clone $this->color; } /** * Returns whether this effect is harmful. * TODO: implement inverse effect results for undead mobs */ public function isBad() : bool{ return $this->bad; } /** * Returns whether the effect is by default an instant effect. */ public function isInstantEffect() : bool{ return $this->defaultDuration <= 1; } /** * Returns the default duration (in ticks) this effect will apply for if a duration is not specified. */ public function getDefaultDuration() : int{ return $this->defaultDuration; } /** * Returns whether this effect will give the subject potion bubbles. */ public function hasBubbles() : bool{ return $this->hasBubbles; } /** * Returns whether the effect will do something on the current tick. */ public function canTick(EffectInstance $instance) : bool{ switch($this->id){ case Effect::POISON: case Effect::FATAL_POISON: if(($interval = (25 >> $instance->getAmplifier())) > 0){ return ($instance->getDuration() % $interval) === 0; } return true; case Effect::WITHER: if(($interval = (50 >> $instance->getAmplifier())) > 0){ return ($instance->getDuration() % $interval) === 0; } return true; case Effect::REGENERATION: if(($interval = (40 >> $instance->getAmplifier())) > 0){ return ($instance->getDuration() % $interval) === 0; } return true; case Effect::HUNGER: return true; case Effect::INSTANT_DAMAGE: case Effect::INSTANT_HEALTH: case Effect::SATURATION: //If forced to last longer than 1 tick, these apply every tick. return true; } return false; } /** * Applies effect results to an entity. This will not be called unless canTick() returns true. */ public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null, ?Entity $sourceOwner = null) : void{ switch($this->id){ /** @noinspection PhpMissingBreakStatementInspection */ case Effect::POISON: if($entity->getHealth() <= 1){ break; } case Effect::FATAL_POISON: $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 1); $entity->attack($ev); break; case Effect::WITHER: $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 1); $entity->attack($ev); break; case Effect::REGENERATION: if($entity->getHealth() < $entity->getMaxHealth()){ $ev = new EntityRegainHealthEvent($entity, 1, EntityRegainHealthEvent::CAUSE_MAGIC); $entity->heal($ev); } break; case Effect::HUNGER: if($entity instanceof Human){ $entity->exhaust(0.025 * $instance->getEffectLevel(), PlayerExhaustEvent::CAUSE_POTION); } break; case Effect::INSTANT_HEALTH: //TODO: add particles (witch spell) if($entity->getHealth() < $entity->getMaxHealth()){ $entity->heal(new EntityRegainHealthEvent($entity, (4 << $instance->getAmplifier()) * $potency, EntityRegainHealthEvent::CAUSE_MAGIC)); } break; case Effect::INSTANT_DAMAGE: //TODO: add particles (witch spell) $damage = (4 << $instance->getAmplifier()) * $potency; if($source !== null and $sourceOwner !== null){ $ev = new EntityDamageByChildEntityEvent($sourceOwner, $source, $entity, EntityDamageEvent::CAUSE_MAGIC, $damage); }elseif($source !== null){ $ev = new EntityDamageByEntityEvent($source, $entity, EntityDamageEvent::CAUSE_MAGIC, $damage); }else{ $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, $damage); } $entity->attack($ev); break; case Effect::SATURATION: if($entity instanceof Human){ $entity->addFood($instance->getEffectLevel()); $entity->addSaturation($instance->getEffectLevel() * 2); } break; } } /** * Applies effects to the entity when the effect is first added. */ public function add(Living $entity, EffectInstance $instance) : void{ switch($this->id){ case Effect::INVISIBILITY: $entity->setInvisible(); $entity->setNameTagVisible(false); break; case Effect::SPEED: $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() * (1 + 0.2 * $instance->getEffectLevel())); break; case Effect::SLOWNESS: $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() * (1 - 0.15 * $instance->getEffectLevel()), true); break; case Effect::HEALTH_BOOST: $entity->setMaxHealth($entity->getMaxHealth() + 4 * $instance->getEffectLevel()); break; case Effect::ABSORPTION: $new = (4 * $instance->getEffectLevel()); if($new > $entity->getAbsorption()){ $entity->setAbsorption($new); } break; } } /** * Removes the effect from the entity, resetting any changed values back to their original defaults. */ public function remove(Living $entity, EffectInstance $instance) : void{ switch($this->id){ case Effect::INVISIBILITY: $entity->setInvisible(false); $entity->setNameTagVisible(true); break; case Effect::SPEED: $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() / (1 + 0.2 * $instance->getEffectLevel())); break; case Effect::SLOWNESS: $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() / (1 - 0.15 * $instance->getEffectLevel())); break; case Effect::HEALTH_BOOST: $entity->setMaxHealth($entity->getMaxHealth() - 4 * $instance->getEffectLevel()); break; case Effect::ABSORPTION: $entity->setAbsorption(0); break; } } public function __clone(){ $this->color = clone $this->color; } }