diff --git a/src/pocketmine/entity/Effect.php b/src/pocketmine/entity/Effect.php index bb8ee9913..8d8bb992e 100644 --- a/src/pocketmine/entity/Effect.php +++ b/src/pocketmine/entity/Effect.php @@ -179,6 +179,14 @@ class Effect{ return $this->bad; } + /** + * Returns whether the effect is by default an instant effect. + * @return bool + */ + public function isInstantEffect() : bool{ + return $this->defaultDuration <= 1; + } + /** * Returns the default duration this effect will apply for if a duration is not specified. * @return int @@ -236,8 +244,9 @@ class Effect{ * * @param Living $entity * @param EffectInstance $instance + * @param float $potency */ - public function applyEffect(Living $entity, EffectInstance $instance) : void{ + public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0) : void{ switch($this->id){ /** @noinspection PhpMissingBreakStatementInspection */ case Effect::POISON: @@ -269,12 +278,12 @@ class Effect{ case Effect::INSTANT_HEALTH: //TODO: add particles (witch spell) if($entity->getHealth() < $entity->getMaxHealth()){ - $entity->heal(new EntityRegainHealthEvent($entity, 4 << $instance->getAmplifier(), EntityRegainHealthEvent::CAUSE_MAGIC)); + $entity->heal(new EntityRegainHealthEvent($entity, (4 << $instance->getAmplifier()) * $potency, EntityRegainHealthEvent::CAUSE_MAGIC)); } break; case Effect::INSTANT_DAMAGE: //TODO: add particles (witch spell) - $entity->attack(new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 4 << $instance->getAmplifier())); + $entity->attack(new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, (4 << $instance->getAmplifier()) * $potency)); break; case Effect::SATURATION: if($entity instanceof Human){ diff --git a/src/pocketmine/entity/Entity.php b/src/pocketmine/entity/Entity.php index aeb2c76e5..3e5dba9a7 100644 --- a/src/pocketmine/entity/Entity.php +++ b/src/pocketmine/entity/Entity.php @@ -35,6 +35,7 @@ use pocketmine\entity\object\PaintingMotive; use pocketmine\entity\projectile\Arrow; use pocketmine\entity\projectile\Egg; use pocketmine\entity\projectile\Snowball; +use pocketmine\entity\projectile\SplashPotion; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\event\entity\EntityDespawnEvent; use pocketmine\event\entity\EntityLevelChangeEvent; @@ -236,6 +237,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{ Entity::registerEntity(Painting::class, false, ['Painting', 'minecraft:painting']); Entity::registerEntity(PrimedTNT::class, false, ['PrimedTnt', 'PrimedTNT', 'minecraft:tnt']); Entity::registerEntity(Snowball::class, false, ['Snowball', 'minecraft:snowball']); + Entity::registerEntity(SplashPotion::class, false, ['ThrownPotion', 'minecraft:potion', 'thrownpotion']); Entity::registerEntity(Squid::class, false, ['Squid', 'minecraft:squid']); Entity::registerEntity(Villager::class, false, ['Villager', 'minecraft:villager']); Entity::registerEntity(Zombie::class, false, ['Zombie', 'minecraft:zombie']); diff --git a/src/pocketmine/entity/projectile/SplashPotion.php b/src/pocketmine/entity/projectile/SplashPotion.php new file mode 100644 index 000000000..cfccd78f8 --- /dev/null +++ b/src/pocketmine/entity/projectile/SplashPotion.php @@ -0,0 +1,154 @@ +setPotionId($this->namedtag->getShort("PotionId", 0)); + } + + public function saveNBT(){ + parent::saveNBT(); + $this->namedtag->setShort("PotionId", $this->getPotionId()); + } + + public function getResultDamage() : int{ + return -1; //no damage + } + + protected function onHit(ProjectileHitEvent $event) : void{ + $effects = $this->getPotionEffects(); + $hasEffects = true; + + if(empty($effects)){ + $colors = [ + new Color(0x38, 0x5d, 0xc6) //Default colour for splash water bottle and similar with no effects. + ]; + $hasEffects = false; + }else{ + $colors = []; + foreach($effects as $effect){ + $level = $effect->getEffectLevel(); + for($j = 0; $j < $level; ++$j){ + $colors[] = $effect->getColor(); + } + } + } + + $this->level->broadcastLevelEvent($this, LevelEventPacket::EVENT_PARTICLE_SPLASH, Color::mix(...$colors)->toARGB()); + $this->level->broadcastLevelSoundEvent($this, LevelSoundEventPacket::SOUND_GLASS); + + if($hasEffects){ + if(!$this->willLinger()){ + foreach($this->level->getNearbyEntities($this->boundingBox->grow(4.125, 2.125, 4.125), $this) as $entity){ + if($entity instanceof Living){ + $distanceSquared = $entity->distanceSquared($this); + if($distanceSquared > 16){ //4 blocks + continue; + } + + $distanceMultiplier = 1 - (sqrt($distanceSquared) / 4); + if($event instanceof ProjectileHitEntityEvent and $entity === $event->getEntityHit()){ + $distanceMultiplier = 1.0; + } + + foreach($this->getPotionEffects() as $effect){ + //getPotionEffects() is used to get COPIES to avoid accidentally modifying the same effect instance already applied to another entity + + if(!$effect->getType()->isInstantEffect()){ + $newDuration = (int) round($effect->getDuration() * 0.75 * $distanceMultiplier); + if($newDuration < 20){ + continue; + } + $effect->setDuration($newDuration); + $entity->addEffect($effect); + }else{ + $effect->getType()->applyEffect($entity, $effect, $distanceMultiplier); + } + } + } + } + }else{ + //TODO: lingering potions + } + } + + $this->flagForDespawn(); + } + + /** + * 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. + * @return int + */ + public function getPotionId() : int{ + return $this->propertyManager->getShort(self::DATA_POTION_AUX_VALUE) ?? 0; + } + + /** + * @param int $id + */ + public function setPotionId(int $id) : void{ + $this->propertyManager->setShort(self::DATA_POTION_AUX_VALUE, $id); + } + + /** + * Returns whether this splash potion will create an area-effect cloud when it lands. + * @return bool + */ + public function willLinger() : bool{ + return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_LINGER); + } + + /** + * Sets whether this splash potion will create an area-effect-cloud when it lands. + * @param bool $value + */ + public function setLinger(bool $value = true) : void{ + $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_LINGER, $value); + } + + /** + * @return EffectInstance[] + */ + public function getPotionEffects() : array{ + return Potion::getPotionEffectsById($this->getPotionId()); + } +} diff --git a/src/pocketmine/item/ItemFactory.php b/src/pocketmine/item/ItemFactory.php index 45eabf946..1771bd20d 100644 --- a/src/pocketmine/item/ItemFactory.php +++ b/src/pocketmine/item/ItemFactory.php @@ -219,7 +219,7 @@ class ItemFactory{ self::registerItem(new Item(Item::CHORUS_FRUIT_POPPED, 0, "Popped Chorus Fruit")); //TODO: DRAGON_BREATH - //TODO: SPLASH_POTION + self::registerItem(new SplashPotion()); //TODO: LINGERING_POTION diff --git a/src/pocketmine/item/ProjectileItem.php b/src/pocketmine/item/ProjectileItem.php index 0b6a83ea5..894523cc9 100644 --- a/src/pocketmine/item/ProjectileItem.php +++ b/src/pocketmine/item/ProjectileItem.php @@ -28,6 +28,7 @@ use pocketmine\entity\projectile\Projectile; use pocketmine\event\entity\ProjectileLaunchEvent; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\LevelSoundEventPacket; +use pocketmine\nbt\tag\CompoundTag; use pocketmine\Player; abstract class ProjectileItem extends Item{ @@ -36,8 +37,18 @@ abstract class ProjectileItem extends Item{ abstract public function getThrowForce() : float; + /** + * Helper function to apply extra NBT tags to pass to the created projectile. + * + * @param CompoundTag $tag + */ + protected function addExtraTags(CompoundTag $tag) : void{ + + } + public function onClickAir(Player $player, Vector3 $directionVector) : bool{ $nbt = Entity::createBaseNBT($player->add(0, $player->getEyeHeight(), 0), $directionVector, $player->yaw, $player->pitch); + $this->addExtraTags($nbt); $projectile = Entity::createEntity($this->getProjectileEntityType(), $player->getLevel(), $nbt, $player); if($projectile !== null){ diff --git a/src/pocketmine/item/SplashPotion.php b/src/pocketmine/item/SplashPotion.php new file mode 100644 index 000000000..b765f5d3a --- /dev/null +++ b/src/pocketmine/item/SplashPotion.php @@ -0,0 +1,45 @@ +setShort("PotionId", $this->meta); + } +}