mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-15 19:46:20 +00:00
Implement Lingering potion & Area Effect Cloud (#5276)
Co-authored-by: Dylan T. <dktapps@pmmp.io>
This commit is contained in:
@@ -82,7 +82,7 @@ final class Cauldron extends Transparent{
|
||||
$this->fill(FillableCauldron::MAX_FILL_LEVEL, VanillaBlocks::LAVA_CAULDRON(), $item, VanillaItems::BUCKET(), $returnedItems);
|
||||
}elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){
|
||||
//TODO: powder snow cauldron
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){
|
||||
if($item->getType() === PotionType::WATER){
|
||||
$this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
|
||||
}else{
|
||||
|
@@ -123,7 +123,7 @@ final class WaterCauldron extends FillableCauldron{
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new CauldronAddDyeSound());
|
||||
|
||||
$item->pop();
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){
|
||||
if($item->getType() === PotionType::WATER){
|
||||
$this->setCustomWaterColor(null)->addFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
|
||||
}else{
|
||||
|
@@ -76,7 +76,6 @@ final class Cauldron extends Spawnable{
|
||||
default => throw new AssumptionFailedError("Unexpected potion item type")
|
||||
});
|
||||
|
||||
//TODO: lingering potion
|
||||
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null;
|
||||
$nbt->setShort(self::TAG_POTION_ID, $type === null ? self::POTION_ID_NONE : PotionTypeIdMap::getInstance()->toId($type));
|
||||
|
||||
@@ -96,7 +95,7 @@ final class Cauldron extends Spawnable{
|
||||
$this->potionItem = match($containerType){
|
||||
self::POTION_CONTAINER_TYPE_NORMAL => VanillaItems::POTION()->setType($potionType),
|
||||
self::POTION_CONTAINER_TYPE_SPLASH => VanillaItems::SPLASH_POTION()->setType($potionType),
|
||||
self::POTION_CONTAINER_TYPE_LINGERING => throw new SavedDataLoadingException("Not implemented"),
|
||||
self::POTION_CONTAINER_TYPE_LINGERING => VanillaItems::LINGERING_POTION()->setType($potionType),
|
||||
default => throw new SavedDataLoadingException("Invalid potion container type ID $containerType")
|
||||
};
|
||||
}else{
|
||||
@@ -115,7 +114,6 @@ final class Cauldron extends Spawnable{
|
||||
default => throw new AssumptionFailedError("Unexpected potion item type")
|
||||
});
|
||||
|
||||
//TODO: lingering potion
|
||||
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null;
|
||||
$nbt->setShort(self::TAG_POTION_ID, $type === null ? self::POTION_ID_NONE : PotionTypeIdMap::getInstance()->toId($type));
|
||||
|
||||
|
@@ -519,6 +519,14 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
},
|
||||
fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType())
|
||||
);
|
||||
$this->map1to1ItemWithMeta(
|
||||
Ids::LINGERING_POTION,
|
||||
Items::LINGERING_POTION(),
|
||||
function(SplashPotion $item, int $meta) : void{
|
||||
$item->setType(PotionTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown potion type ID $meta"));
|
||||
},
|
||||
fn(SplashPotion $item) => PotionTypeIdMap::getInstance()->toId($item->getType())
|
||||
);
|
||||
$this->map1to1ItemWithMeta(
|
||||
Ids::MEDICINE,
|
||||
Items::MEDICINE(),
|
||||
|
@@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
|
||||
use pocketmine\data\bedrock\PotionTypeIds;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\entity\EntityDataHelper as Helper;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
use pocketmine\entity\object\EndCrystal;
|
||||
use pocketmine\entity\object\ExperienceOrb;
|
||||
use pocketmine\entity\object\FallingBlock;
|
||||
@@ -87,6 +88,10 @@ final class EntityFactory{
|
||||
//define legacy save IDs first - use them for saving for maximum compatibility with Minecraft PC
|
||||
//TODO: index them by version to allow proper multi-save compatibility
|
||||
|
||||
$this->register(AreaEffectCloud::class, function(World $world, CompoundTag $nbt) : AreaEffectCloud{
|
||||
return new AreaEffectCloud(Helper::parseLocation($nbt, $world), $nbt);
|
||||
}, ['AreaEffectCloud', 'minecraft:area_effect_cloud']);
|
||||
|
||||
$this->register(Arrow::class, function(World $world, CompoundTag $nbt) : Arrow{
|
||||
return new Arrow(Helper::parseLocation($nbt, $world), null, $nbt->getByte(Arrow::TAG_CRIT, 0) === 1, $nbt);
|
||||
}, ['Arrow', 'minecraft:arrow']);
|
||||
|
223
src/entity/effect/EffectCollection.php
Normal file
223
src/entity/effect/EffectCollection.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?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\entity\effect;
|
||||
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\Utils;
|
||||
use function abs;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
class EffectCollection{
|
||||
|
||||
/** @var EffectInstance[] */
|
||||
protected array $effects = [];
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
protected ObjectSet $effectAddHooks;
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
protected ObjectSet $effectRemoveHooks;
|
||||
|
||||
protected Color $bubbleColor;
|
||||
|
||||
protected bool $onlyAmbientEffects = false;
|
||||
|
||||
/**
|
||||
* Validates whether an effect will be used for bubbles color calculation.
|
||||
*
|
||||
* @phpstan-var \Closure(EffectInstance) : bool
|
||||
*/
|
||||
protected \Closure $effectFilterForBubbles;
|
||||
|
||||
public function __construct(){
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->effectAddHooks = new ObjectSet();
|
||||
$this->effectRemoveHooks = new ObjectSet();
|
||||
|
||||
$this->setEffectFilterForBubbles(static fn(EffectInstance $e) : bool => $e->isVisible() && $e->getType()->hasBubbles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the effects in the collection, indexed by spl_object_id of the effect type.
|
||||
* @return EffectInstance[]
|
||||
*/
|
||||
public function all() : array{
|
||||
return $this->effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all effects.
|
||||
*/
|
||||
public function clear() : void{
|
||||
foreach($this->effects as $effect){
|
||||
$this->remove($effect->getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the effect with the specified ID.
|
||||
*/
|
||||
public function remove(Effect $effectType) : void{
|
||||
$index = spl_object_id($effectType);
|
||||
if(isset($this->effects[$index])){
|
||||
$effect = $this->effects[$index];
|
||||
|
||||
unset($this->effects[$index]);
|
||||
foreach($this->effectRemoveHooks as $hook){
|
||||
$hook($effect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effect instance active with the specified ID, or null if does not have the
|
||||
* effect.
|
||||
*/
|
||||
public function get(Effect $effect) : ?EffectInstance{
|
||||
return $this->effects[spl_object_id($effect)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified effect is active.
|
||||
*/
|
||||
public function has(Effect $effect) : bool{
|
||||
return isset($this->effects[spl_object_id($effect)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* In the following cases it will return true:
|
||||
* - if the effect type is not already applied
|
||||
* - if an existing effect of the same type can be replaced (due to shorter duration or lower level)
|
||||
*/
|
||||
public function canAdd(EffectInstance $effect) : bool{
|
||||
$index = spl_object_id($effect->getType());
|
||||
if(isset($this->effects[$index])){
|
||||
$oldEffect = $this->effects[$index];
|
||||
if(
|
||||
abs($effect->getAmplifier()) < $oldEffect->getAmplifier()
|
||||
|| (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) && $effect->getDuration() < $oldEffect->getDuration())
|
||||
){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an effect to the collection.
|
||||
* Existing effects of the same type will be replaced if {@see self::canAdd()} returns true.
|
||||
*
|
||||
* @return bool whether the effect has been successfully applied.
|
||||
*/
|
||||
public function add(EffectInstance $effect) : bool{
|
||||
if($this->canAdd($effect)){
|
||||
$index = spl_object_id($effect->getType());
|
||||
$replacesOldEffect = isset($this->effects[$index]);
|
||||
|
||||
$this->effects[$index] = $effect;
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($effect, $replacesOldEffect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter that determines which effects will be displayed in the bubbles.
|
||||
*
|
||||
* @phpstan-param \Closure(EffectInstance) : bool $filter
|
||||
*/
|
||||
public function setEffectFilterForBubbles(\Closure $filter) : void{
|
||||
Utils::validateCallableSignature(fn(EffectInstance $e) : bool => false, $filter);
|
||||
$this->effectFilterForBubbles = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the potion bubbles colour based on the active effects.
|
||||
*/
|
||||
protected function recalculateEffectColor() : void{
|
||||
/** @var Color[] $colors */
|
||||
$colors = [];
|
||||
$ambient = true;
|
||||
foreach($this->effects as $effect){
|
||||
if(($this->effectFilterForBubbles)($effect)){
|
||||
$level = $effect->getEffectLevel();
|
||||
$color = $effect->getColor();
|
||||
for($i = 0; $i < $level; ++$i){
|
||||
$colors[] = $color;
|
||||
}
|
||||
|
||||
if(!$effect->isAmbient()){
|
||||
$ambient = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($colors) > 0){
|
||||
$this->bubbleColor = Color::mix(...$colors);
|
||||
$this->onlyAmbientEffects = $ambient;
|
||||
}else{
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->onlyAmbientEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBubbleColor() : Color{
|
||||
return $this->bubbleColor;
|
||||
}
|
||||
|
||||
public function hasOnlyAmbientEffects() : bool{
|
||||
return $this->onlyAmbientEffects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
public function getEffectAddHooks() : ObjectSet{
|
||||
return $this->effectAddHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
public function getEffectRemoveHooks() : ObjectSet{
|
||||
return $this->effectRemoveHooks;
|
||||
}
|
||||
}
|
@@ -23,56 +23,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\entity\effect;
|
||||
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\event\entity\EntityEffectAddEvent;
|
||||
use pocketmine\event\entity\EntityEffectRemoveEvent;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function abs;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
class EffectManager{
|
||||
/** @var EffectInstance[] */
|
||||
protected array $effects = [];
|
||||
|
||||
protected Color $bubbleColor;
|
||||
protected bool $onlyAmbientEffects = false;
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
protected ObjectSet $effectAddHooks;
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
protected ObjectSet $effectRemoveHooks;
|
||||
class EffectManager extends EffectCollection{
|
||||
|
||||
public function __construct(
|
||||
private Living $entity
|
||||
){
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->effectAddHooks = new ObjectSet();
|
||||
$this->effectRemoveHooks = new ObjectSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Effects currently active on the mob.
|
||||
* @return EffectInstance[]
|
||||
*/
|
||||
public function all() : array{
|
||||
return $this->effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all effects from the mob.
|
||||
*/
|
||||
public function clear() : void{
|
||||
foreach($this->effects as $effect){
|
||||
$this->remove($effect->getType());
|
||||
}
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,55 +53,17 @@ class EffectManager{
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->effects[$index]);
|
||||
$effect->getType()->remove($this->entity, $effect);
|
||||
foreach($this->effectRemoveHooks as $hook){
|
||||
$hook($effect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
parent::remove($effectType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effect instance active on this entity with the specified ID, or null if the mob does not have the
|
||||
* effect.
|
||||
*/
|
||||
public function get(Effect $effect) : ?EffectInstance{
|
||||
return $this->effects[spl_object_id($effect)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified effect is active on the mob.
|
||||
*/
|
||||
public function has(Effect $effect) : bool{
|
||||
return isset($this->effects[spl_object_id($effect)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an effect to the mob.
|
||||
* If a weaker effect of the same type is already applied, it will be replaced.
|
||||
* If a weaker or equal-strength effect is already applied but has a shorter duration, it will be replaced.
|
||||
*
|
||||
* @return bool whether the effect has been successfully applied.
|
||||
*/
|
||||
public function add(EffectInstance $effect) : bool{
|
||||
$oldEffect = null;
|
||||
$cancelled = false;
|
||||
|
||||
$index = spl_object_id($effect->getType());
|
||||
if(isset($this->effects[$index])){
|
||||
$oldEffect = $this->effects[$index];
|
||||
if(
|
||||
abs($effect->getAmplifier()) < $oldEffect->getAmplifier()
|
||||
|| (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) && $effect->getDuration() < $oldEffect->getDuration())
|
||||
){
|
||||
$cancelled = true;
|
||||
}
|
||||
}
|
||||
$oldEffect = $this->effects[$index] ?? null;
|
||||
|
||||
$ev = new EntityEffectAddEvent($this->entity, $effect, $oldEffect);
|
||||
if($cancelled){
|
||||
if(!$this->canAdd($effect)){
|
||||
$ev->cancel();
|
||||
}
|
||||
|
||||
@@ -153,53 +77,8 @@ class EffectManager{
|
||||
}
|
||||
|
||||
$effect->getType()->add($this->entity, $effect);
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($effect, $oldEffect !== null);
|
||||
}
|
||||
|
||||
$this->effects[$index] = $effect;
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the mob's potion bubbles colour based on the active effects.
|
||||
*/
|
||||
protected function recalculateEffectColor() : void{
|
||||
/** @var Color[] $colors */
|
||||
$colors = [];
|
||||
$ambient = true;
|
||||
foreach($this->effects as $effect){
|
||||
if($effect->isVisible() && $effect->getType()->hasBubbles()){
|
||||
$level = $effect->getEffectLevel();
|
||||
$color = $effect->getColor();
|
||||
for($i = 0; $i < $level; ++$i){
|
||||
$colors[] = $color;
|
||||
}
|
||||
|
||||
if(!$effect->isAmbient()){
|
||||
$ambient = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($colors) > 0){
|
||||
$this->bubbleColor = Color::mix(...$colors);
|
||||
$this->onlyAmbientEffects = $ambient;
|
||||
}else{
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->onlyAmbientEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBubbleColor() : Color{
|
||||
return $this->bubbleColor;
|
||||
}
|
||||
|
||||
public function hasOnlyAmbientEffects() : bool{
|
||||
return $this->onlyAmbientEffects;
|
||||
return parent::add($effect);
|
||||
}
|
||||
|
||||
public function tick(int $tickDiff = 1) : bool{
|
||||
@@ -216,20 +95,4 @@ class EffectManager{
|
||||
|
||||
return count($this->effects) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
public function getEffectAddHooks() : ObjectSet{
|
||||
return $this->effectAddHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
public function getEffectRemoveHooks() : ObjectSet{
|
||||
return $this->effectRemoveHooks;
|
||||
}
|
||||
}
|
||||
|
427
src/entity/object/AreaEffectCloud.php
Normal file
427
src/entity/object/AreaEffectCloud.php
Normal file
@@ -0,0 +1,427 @@
|
||||
<?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\entity\object;
|
||||
|
||||
use pocketmine\data\bedrock\EffectIdMap;
|
||||
use pocketmine\data\bedrock\PotionTypeIds;
|
||||
use pocketmine\entity\effect\EffectCollection;
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\entity\effect\InstantEffect;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\EntitySizeInfo;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\event\entity\AreaEffectCloudApplyEvent;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\particle\PotionSplashParticle;
|
||||
use function count;
|
||||
use function max;
|
||||
use function round;
|
||||
|
||||
class AreaEffectCloud extends Entity{
|
||||
|
||||
public const DEFAULT_DURATION = 600; // in ticks
|
||||
public const DEFAULT_DURATION_CHANGE_ON_USE = 0; // in ticks
|
||||
|
||||
public const UPDATE_DELAY = 10; // in ticks
|
||||
public const REAPPLICATION_DELAY = 40; // in ticks
|
||||
|
||||
public const DEFAULT_RADIUS = 3.0; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_ON_PICKUP = -0.5; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_ON_USE = -0.5; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_PER_TICK = -(self::DEFAULT_RADIUS / self::DEFAULT_DURATION); // in blocks
|
||||
|
||||
protected const TAG_POTION_ID = "PotionId"; //TAG_Short
|
||||
protected const TAG_SPAWN_TICK = "SpawnTick"; //TAG_Long
|
||||
protected const TAG_DURATION = "Duration"; //TAG_Int
|
||||
protected const TAG_PICKUP_COUNT = "PickupCount"; //TAG_Int
|
||||
protected const TAG_DURATION_ON_USE = "DurationOnUse"; //TAG_Int
|
||||
protected const TAG_REAPPLICATION_DELAY = "ReapplicationDelay"; //TAG_Int
|
||||
protected const TAG_INITIAL_RADIUS = "InitialRadius"; //TAG_Float
|
||||
protected const TAG_RADIUS = "Radius"; //TAG_Float
|
||||
protected const TAG_RADIUS_CHANGE_ON_PICKUP = "RadiusChangeOnPickup"; //TAG_Float
|
||||
protected const TAG_RADIUS_ON_USE = "RadiusOnUse"; //TAG_Float
|
||||
protected const TAG_RADIUS_PER_TICK = "RadiusPerTick"; //TAG_Float
|
||||
protected const TAG_EFFECTS = "mobEffects"; //TAG_List
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::AREA_EFFECT_CLOUD; }
|
||||
|
||||
protected int $age = 0;
|
||||
|
||||
protected EffectCollection $effectCollection;
|
||||
|
||||
/** @var array<int, int> entity ID => expiration */
|
||||
protected array $victims = [];
|
||||
|
||||
protected int $maxAge = self::DEFAULT_DURATION;
|
||||
protected int $maxAgeChangeOnUse = self::DEFAULT_DURATION_CHANGE_ON_USE;
|
||||
|
||||
protected int $reapplicationDelay = self::REAPPLICATION_DELAY;
|
||||
|
||||
protected int $pickupCount = 0;
|
||||
protected float $radiusChangeOnPickup = self::DEFAULT_RADIUS_CHANGE_ON_PICKUP;
|
||||
|
||||
protected float $initialRadius = self::DEFAULT_RADIUS;
|
||||
protected float $radius = self::DEFAULT_RADIUS;
|
||||
protected float $radiusChangeOnUse = self::DEFAULT_RADIUS_CHANGE_ON_USE;
|
||||
protected float $radiusChangePerTick = self::DEFAULT_RADIUS_CHANGE_PER_TICK;
|
||||
|
||||
public function __construct(
|
||||
Location $location,
|
||||
?CompoundTag $nbt = null
|
||||
){
|
||||
parent::__construct($location, $nbt);
|
||||
}
|
||||
|
||||
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.5, $this->radius * 2); }
|
||||
|
||||
protected function getInitialDragMultiplier() : float{ return 0.0; }
|
||||
|
||||
protected function getInitialGravity() : float{ return 0.0; }
|
||||
|
||||
protected function initEntity(CompoundTag $nbt) : void{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->effectCollection = new EffectCollection();
|
||||
$this->effectCollection->getEffectAddHooks()->add(function() : void{ $this->networkPropertiesDirty = true; });
|
||||
$this->effectCollection->getEffectRemoveHooks()->add(function() : void{ $this->networkPropertiesDirty = true; });
|
||||
$this->effectCollection->setEffectFilterForBubbles(static fn(EffectInstance $e) : bool => $e->isVisible());
|
||||
|
||||
$worldTime = $this->getWorld()->getTime();
|
||||
$this->age = max($worldTime - $nbt->getLong(self::TAG_SPAWN_TICK, $worldTime), 0);
|
||||
$this->maxAge = $nbt->getInt(self::TAG_DURATION, self::DEFAULT_DURATION);
|
||||
$this->maxAgeChangeOnUse = $nbt->getInt(self::TAG_DURATION_ON_USE, self::DEFAULT_DURATION_CHANGE_ON_USE);
|
||||
$this->pickupCount = $nbt->getInt(self::TAG_PICKUP_COUNT, 0);
|
||||
$this->reapplicationDelay = $nbt->getInt(self::TAG_REAPPLICATION_DELAY, self::REAPPLICATION_DELAY);
|
||||
|
||||
$this->initialRadius = $nbt->getFloat(self::TAG_INITIAL_RADIUS, self::DEFAULT_RADIUS);
|
||||
$this->setRadius($nbt->getFloat(self::TAG_RADIUS, $this->initialRadius));
|
||||
$this->radiusChangeOnPickup = $nbt->getFloat(self::TAG_RADIUS_CHANGE_ON_PICKUP, self::DEFAULT_RADIUS_CHANGE_ON_PICKUP);
|
||||
$this->radiusChangeOnUse = $nbt->getFloat(self::TAG_RADIUS_ON_USE, self::DEFAULT_RADIUS_CHANGE_ON_USE);
|
||||
$this->radiusChangePerTick = $nbt->getFloat(self::TAG_RADIUS_PER_TICK, self::DEFAULT_RADIUS_CHANGE_PER_TICK);
|
||||
|
||||
$effectsTag = $nbt->getListTag(self::TAG_EFFECTS, CompoundTag::class);
|
||||
if($effectsTag !== null){
|
||||
foreach($effectsTag as $e){
|
||||
$effect = EffectIdMap::getInstance()->fromId($e->getByte("Id"));
|
||||
if($effect === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->effectCollection->add(new EffectInstance(
|
||||
$effect,
|
||||
$e->getInt("Duration"),
|
||||
Binary::unsignByte($e->getByte("Amplifier")),
|
||||
$e->getByte("ShowParticles", 1) !== 0,
|
||||
$e->getByte("Ambient", 0) !== 0
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setLong(self::TAG_SPAWN_TICK, $this->getWorld()->getTime() - $this->age);
|
||||
$nbt->setShort(self::TAG_POTION_ID, PotionTypeIds::WATER); //not used, mobEffects is used exclusively in Bedrock
|
||||
$nbt->setInt(self::TAG_DURATION, $this->maxAge);
|
||||
$nbt->setInt(self::TAG_DURATION_ON_USE, $this->maxAgeChangeOnUse);
|
||||
$nbt->setInt(self::TAG_PICKUP_COUNT, $this->pickupCount);
|
||||
$nbt->setInt(self::TAG_REAPPLICATION_DELAY, $this->reapplicationDelay);
|
||||
$nbt->setFloat(self::TAG_INITIAL_RADIUS, $this->initialRadius);
|
||||
$nbt->setFloat(self::TAG_RADIUS, $this->radius);
|
||||
$nbt->setFloat(self::TAG_RADIUS_CHANGE_ON_PICKUP, $this->radiusChangeOnPickup);
|
||||
$nbt->setFloat(self::TAG_RADIUS_ON_USE, $this->radiusChangeOnUse);
|
||||
$nbt->setFloat(self::TAG_RADIUS_PER_TICK, $this->radiusChangePerTick);
|
||||
|
||||
if(count($this->effectCollection->all()) > 0){
|
||||
$effects = [];
|
||||
foreach($this->effectCollection->all() as $effect){
|
||||
$effects[] = CompoundTag::create()
|
||||
->setByte("Id", EffectIdMap::getInstance()->toId($effect->getType()))
|
||||
->setByte("Amplifier", Binary::signByte($effect->getAmplifier()))
|
||||
->setInt("Duration", $effect->getDuration())
|
||||
->setByte("Ambient", $effect->isAmbient() ? 1 : 0)
|
||||
->setByte("ShowParticles", $effect->isVisible() ? 1 : 0);
|
||||
}
|
||||
$nbt->setTag(self::TAG_EFFECTS, new ListTag($effects));
|
||||
}
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
public function isFireProof() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canBeCollidedWith() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current age of the cloud (in ticks).
|
||||
*/
|
||||
public function getAge() : int{
|
||||
return $this->age;
|
||||
}
|
||||
|
||||
public function getEffects() : EffectCollection{
|
||||
return $this->effectCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial radius (in blocks).
|
||||
*/
|
||||
public function getInitialRadius() : float{
|
||||
return $this->initialRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current radius (in blocks).
|
||||
*/
|
||||
public function getRadius() : float{
|
||||
return $this->radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current radius (in blocks).
|
||||
*/
|
||||
protected function setRadius(float $radius) : void{
|
||||
$this->radius = $radius;
|
||||
$this->setSize($this->getInitialSizeInfo());
|
||||
$this->networkPropertiesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when it is
|
||||
* picked up (in blocks). Usually negative resulting in a radius reduction.
|
||||
*
|
||||
* Applied when getting dragon breath bottle.
|
||||
*/
|
||||
public function getRadiusChangeOnPickup() : float{
|
||||
return $this->radiusChangeOnPickup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when it is
|
||||
* picked up (in blocks). Usually negative resulting in a radius reduction.
|
||||
*
|
||||
* Applied when getting dragon breath bottle.
|
||||
*/
|
||||
public function setRadiusChangeOnPickup(float $radiusChangeOnPickup) : void{
|
||||
$this->radiusChangeOnPickup = $radiusChangeOnPickup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when it
|
||||
* applies an effect to an entity (in blocks). Usually negative resulting in a radius reduction.
|
||||
*/
|
||||
public function getRadiusChangeOnUse() : float{
|
||||
return $this->radiusChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when it
|
||||
* applies an effect to an entity (in blocks).
|
||||
*/
|
||||
public function setRadiusChangeOnUse(float $radiusChangeOnUse) : void{
|
||||
$this->radiusChangeOnUse = $radiusChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when an update
|
||||
* is performed (in blocks). Usually negative resulting in a radius reduction.
|
||||
*/
|
||||
public function getRadiusChangePerTick() : float{
|
||||
return $this->radiusChangePerTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when an update is performed (in blocks).
|
||||
*/
|
||||
public function setRadiusChangePerTick(float $radiusChangePerTick) : void{
|
||||
$this->radiusChangePerTick = $radiusChangePerTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the age at which the cloud will despawn.
|
||||
*/
|
||||
public function getMaxAge() : int{
|
||||
return $this->maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the age at which the cloud will despawn.
|
||||
*/
|
||||
public function setMaxAge(int $maxAge) : void{
|
||||
$this->maxAge = $maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the max age of this cloud will change by when it
|
||||
* applies an effect to an entity (in ticks).
|
||||
*/
|
||||
public function getMaxAgeChangeOnUse() : int{
|
||||
return $this->maxAgeChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the max age of this cloud will change by when it
|
||||
* applies an effect to an entity (in ticks).
|
||||
*/
|
||||
public function setMaxAgeChangeOnUse(int $maxAgeChangeOnUse) : void{
|
||||
$this->maxAgeChangeOnUse = $maxAgeChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that an entity will be immune from subsequent exposure (in ticks).
|
||||
*/
|
||||
public function getReapplicationDelay() : int{
|
||||
return $this->reapplicationDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time that an entity will be immune from subsequent exposure (in ticks).
|
||||
*/
|
||||
public function setReapplicationDelay(int $delay) : void{
|
||||
$this->reapplicationDelay = $delay;
|
||||
}
|
||||
|
||||
protected function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->age += $tickDiff;
|
||||
$radius = $this->radius + ($this->radiusChangePerTick * $tickDiff);
|
||||
if($radius < 0.5){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
$this->setRadius($radius);
|
||||
if($this->age >= self::UPDATE_DELAY && ($this->age % self::UPDATE_DELAY) === 0){
|
||||
if($this->age > $this->maxAge){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach($this->victims as $entityId => $expiration){
|
||||
if($this->age >= $expiration){
|
||||
unset($this->victims[$entityId]);
|
||||
}
|
||||
}
|
||||
|
||||
$entities = [];
|
||||
$radiusChange = 0.0;
|
||||
$maxAgeChange = 0;
|
||||
foreach($this->getWorld()->getCollidingEntities($this->getBoundingBox(), $this) as $entity){
|
||||
if(!$entity instanceof Living || isset($this->victims[$entity->getId()])){
|
||||
continue;
|
||||
}
|
||||
$entityPosition = $entity->getPosition();
|
||||
$xDiff = $entityPosition->getX() - $this->location->getX();
|
||||
$zDiff = $entityPosition->getZ() - $this->location->getZ();
|
||||
if(($xDiff ** 2 + $zDiff ** 2) > $this->radius ** 2){
|
||||
continue;
|
||||
}
|
||||
$entities[] = $entity;
|
||||
if($this->radiusChangeOnUse !== 0.0){
|
||||
$radiusChange += $this->radiusChangeOnUse;
|
||||
if($this->radius + $radiusChange <= 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($this->maxAgeChangeOnUse !== 0){
|
||||
$maxAgeChange += $this->maxAgeChangeOnUse;
|
||||
if($this->maxAge + $maxAgeChange <= 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(count($entities) === 0){
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
$ev = new AreaEffectCloudApplyEvent($this, $entities);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
foreach($ev->getAffectedEntities() as $entity){
|
||||
foreach($this->effectCollection->all() as $effect){
|
||||
$effect = clone $effect; //avoid accidental modification
|
||||
if($effect->getType() instanceof InstantEffect){
|
||||
$effect->getType()->applyEffect($entity, $effect, 0.5, $this);
|
||||
}else{
|
||||
$entity->getEffects()->add($effect->setDuration((int) round($effect->getDuration() / 4)));
|
||||
}
|
||||
}
|
||||
if($this->reapplicationDelay !== 0){
|
||||
$this->victims[$entity->getId()] = $this->age + $this->reapplicationDelay;
|
||||
}
|
||||
}
|
||||
|
||||
$radius = $this->radius + $radiusChange;
|
||||
$maxAge = $this->maxAge + $maxAgeChange;
|
||||
if($radius <= 0 || $maxAge <= 0){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
$this->setRadius($radius);
|
||||
$this->setMaxAge($maxAge);
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
|
||||
parent::syncNetworkData($properties);
|
||||
|
||||
//visual properties
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS, $this->radius);
|
||||
$properties->setInt(EntityMetadataProperties::POTION_COLOR, Binary::signInt((
|
||||
count($this->effectCollection->all()) === 0 ? PotionSplashParticle::DEFAULT_COLOR() : $this->effectCollection->getBubbleColor()
|
||||
)->toARGB()));
|
||||
|
||||
//these are properties the client expects, and are used for client-sided logic, which we don't want
|
||||
$properties->setByte(EntityMetadataProperties::POTION_AMBIENT, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_DURATION, -1);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP, 0);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS_PER_TICK, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_SPAWN_TIME, 0);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_PICKUP_COUNT, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_WAITING, 0);
|
||||
}
|
||||
|
||||
protected function destroyCycles() : void{
|
||||
//wipe out callback refs
|
||||
$this->effectCollection = new EffectCollection();
|
||||
parent::destroyCycles();
|
||||
}
|
||||
}
|
@@ -32,10 +32,10 @@ use pocketmine\entity\effect\InstantEffect;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
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;
|
||||
@@ -96,8 +96,8 @@ class SplashPotion extends Throwable{
|
||||
$this->getWorld()->addParticle($this->location, $particle);
|
||||
$this->broadcastSound(new PotionSplashSound());
|
||||
|
||||
if($hasEffects){
|
||||
if(!$this->willLinger()){
|
||||
if(!$this->willLinger()){
|
||||
if($hasEffects){
|
||||
foreach($this->getWorld()->getCollidingEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living){
|
||||
$distanceSquared = $entity->getEyePos()->distanceSquared($this->location);
|
||||
@@ -126,10 +126,18 @@ class SplashPotion extends Throwable{
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//TODO: lingering potions
|
||||
}
|
||||
}elseif($event instanceof ProjectileHitBlockEvent && $this->getPotionType() === PotionType::WATER){
|
||||
}else{
|
||||
$entity = new AreaEffectCloud(Location::fromObject($this->location->floor()->add(0.5, 0.5, 0.5), $this->getWorld()));
|
||||
foreach($this->potionType->getEffects() as $effect){
|
||||
$entity->getEffects()->add($effect);
|
||||
}
|
||||
if(($owner = $this->getOwningEntity()) !== null && !$owner->isClosed()){
|
||||
$entity->setOwningEntity($owner);
|
||||
}
|
||||
$entity->spawnToAll();
|
||||
}
|
||||
if(!$hasEffects && $event instanceof ProjectileHitBlockEvent && $this->getPotionType() === PotionType::WATER){
|
||||
$blockIn = $event->getBlockHit()->getSide($event->getRayTraceResult()->getHitFace());
|
||||
|
||||
if($blockIn->hasTypeTag(BlockTypeTags::FIRE)){
|
||||
|
64
src/event/entity/AreaEffectCloudApplyEvent.php
Normal file
64
src/event/entity/AreaEffectCloudApplyEvent.php
Normal 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\entity;
|
||||
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when an area effect cloud applies effects to entities.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<AreaEffectCloud>
|
||||
*/
|
||||
class AreaEffectCloudApplyEvent extends EntityEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Living[] $affectedEntities
|
||||
*/
|
||||
public function __construct(
|
||||
AreaEffectCloud $entity,
|
||||
protected array $affectedEntities
|
||||
){
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AreaEffectCloud
|
||||
*/
|
||||
public function getEntity(){
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the affected entities.
|
||||
*
|
||||
* @return Living[]
|
||||
*/
|
||||
public function getAffectedEntities() : array{
|
||||
return $this->affectedEntities;
|
||||
}
|
||||
}
|
@@ -33,6 +33,16 @@ class SplashPotion extends ProjectileItem{
|
||||
|
||||
private PotionType $potionType = PotionType::WATER;
|
||||
|
||||
public function __construct(
|
||||
ItemIdentifier $identifier,
|
||||
string $name = "Splash Potion",
|
||||
array $enchantmentTags = [],
|
||||
private bool $linger = false
|
||||
){
|
||||
//TODO: remove unnecessary default parameters in PM6, they remain because backward compatibility
|
||||
parent::__construct($identifier, $name, $enchantmentTags);
|
||||
}
|
||||
|
||||
protected function describeState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->potionType);
|
||||
}
|
||||
@@ -52,10 +62,19 @@ class SplashPotion extends ProjectileItem{
|
||||
}
|
||||
|
||||
protected function createEntity(Location $location, Player $thrower) : Throwable{
|
||||
return new SplashPotionEntity($location, $thrower, $this->potionType);
|
||||
$projectile = new SplashPotionEntity($location, $thrower, $this->potionType);
|
||||
$projectile->setLinger($this->linger);
|
||||
return $projectile;
|
||||
}
|
||||
|
||||
public function getThrowForce() : float{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this splash potion will create an area-effect cloud on impact.
|
||||
*/
|
||||
public function willLinger() : bool{
|
||||
return $this->linger;
|
||||
}
|
||||
}
|
||||
|
@@ -1226,6 +1226,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
|
||||
$result->register($prefix("potion"), fn() => Items::POTION()->setType($potionType));
|
||||
$result->register($prefix("splash_potion"), fn() => Items::SPLASH_POTION()->setType($potionType));
|
||||
$result->register($prefix("lingering_potion"), fn() => Items::LINGERING_POTION()->setType($potionType));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -222,6 +222,7 @@ use function strtolower;
|
||||
* @method static Armor LEATHER_CAP()
|
||||
* @method static Armor LEATHER_PANTS()
|
||||
* @method static Armor LEATHER_TUNIC()
|
||||
* @method static SplashPotion LINGERING_POTION()
|
||||
* @method static Item MAGMA_CREAM()
|
||||
* @method static Boat MANGROVE_BOAT()
|
||||
* @method static HangingSign MANGROVE_HANGING_SIGN()
|
||||
@@ -543,6 +544,7 @@ final class VanillaItems{
|
||||
self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
|
||||
self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
|
||||
self::register("leather", fn(IID $id) => new Item($id, "Leather"));
|
||||
self::register("lingering_potion", fn(IID $id) => new SplashPotion($id, "Lingering Potion", linger: true));
|
||||
self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
|
||||
self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
|
||||
self::register("mangrove_hanging_sign", fn(IID $id) => new HangingSign($id, "Mangrove Hanging Sign", Blocks::MANGROVE_CEILING_CENTER_HANGING_SIGN(), Blocks::MANGROVE_CEILING_EDGES_HANGING_SIGN(), Blocks::MANGROVE_WALL_HANGING_SIGN()));
|
||||
|
Reference in New Issue
Block a user