Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Lingering potion & Area Effect Cloud #5276

Open
wants to merge 57 commits into
base: minor-next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
f4a9c03
Implement lingering potion
IvanCraft623 Sep 2, 2022
c8e3037
Add LingeringPotion doc
IvanCraft623 Sep 2, 2022
e66d376
Implement an EffectContainer
IvanCraft623 Sep 2, 2022
08de81c
Cauldron integration
IvanCraft623 Sep 2, 2022
5ff5256
Constants usage! :D
IvanCraft623 Sep 3, 2022
5250078
Vanilla parity
IvanCraft623 Sep 3, 2022
ffe7158
More vanilla parity!
IvanCraft623 Sep 3, 2022
deeeeb6
Match vanilla properties
IvanCraft623 Sep 6, 2022
cb6cf6a
Use RADIUS_PER_TICK const
IvanCraft623 Sep 6, 2022
2ce169d
A lot of changes
IvanCraft623 Sep 20, 2022
f479548
shut
IvanCraft623 Sep 20, 2022
c030d77
Shut!
IvanCraft623 Sep 20, 2022
d8638e2
Merge branch 'next-major' into lingering_potion
IvanCraft623 Oct 7, 2022
3bbc9c2
Move initial radius to constructor
IvanCraft623 Oct 8, 2022
ae28310
shut
IvanCraft623 Oct 8, 2022
80c20c0
Fix radius
IvanCraft623 Oct 8, 2022
6c9f96a
Improve documentation
IvanCraft623 Oct 8, 2022
f1ff59a
Merge branch 'next-major' into lingering_potion
IvanCraft623 Oct 14, 2022
3f64d4d
Rearrange and rename some properties
dktapps Oct 14, 2022
42f93d5
Mark as protected NBT tags
IvanCraft623 Oct 15, 2022
0c5946d
Document units
IvanCraft623 Oct 15, 2022
8a910e0
Avoid age underflow
IvanCraft623 Oct 15, 2022
df50e71
Implement AreaEffectCloudApplyEvent
IvanCraft623 Oct 16, 2022
6f968ad
shut PHPStan!
IvanCraft623 Oct 16, 2022
8922880
Apply suggestions from dktapps
IvanCraft623 Oct 16, 2022
823cea3
Ensure that all entities returned by `AreaEffectCloudApplyEvent::getA…
IvanCraft623 Oct 16, 2022
880f4ee
Merge branch 'next-major' into lingering_potion
IvanCraft623 Dec 22, 2022
5915074
Hack to disable client side radius calculadtion
IvanCraft623 Dec 22, 2022
0a197d7
Merge branch 'next-major' into lingering_potion
IvanCraft623 Dec 24, 2022
f158118
Merge branch 'minor-next' into lingering_potion
IvanCraft623 Jun 4, 2023
414ae0e
Merge remote-tracking branch 'upstream/minor-next' into lingering_potion
IvanCraft623 Jan 3, 2024
2c79230
Merge branch 'minor-next' into lingering_potion
IvanCraft623 Feb 19, 2024
e5287f5
Use of native enum case accessor
IvanCraft623 Feb 19, 2024
595a41e
Merge branch 'minor-next' into lingering_potion
IvanCraft623 Dec 2, 2024
20488a3
Make `SplashPotion::setLinger()` not fluent :(
IvanCraft623 Dec 2, 2024
4e97049
Remove `EffectManager::add()` `$force` param
IvanCraft623 Dec 2, 2024
cef5f21
Ugh remove $force param from EffectContainer too
IvanCraft623 Dec 2, 2024
09d1b36
...
IvanCraft623 Dec 2, 2024
28aa818
Modernize code on item\LingeringPotion
IvanCraft623 Dec 3, 2024
52943e2
StringToItemParser: SPLASH_POTION => LINGERING_POTION
IvanCraft623 Dec 3, 2024
910b73b
Merge branch 'lingering_potion' of https://github.com/IvanCraft623/Po…
IvanCraft623 Dec 3, 2024
dc7d6c5
Updade EffectContainer::canAdd() documentation
IvanCraft623 Dec 7, 2024
d78bf09
Remove @return
IvanCraft623 Dec 7, 2024
3e04f3b
Rename EffectContainer::setEffectValidatorForBubbles() method
IvanCraft623 Dec 7, 2024
0128947
Introduce $linger param on SplashPotion
IvanCraft623 Dec 7, 2024
dd1c9f5
Update SplashPotion::willLinger() doc
IvanCraft623 Dec 12, 2024
4ab9bc7
Skip $enchantmentTags param on SplashPotion construction :P
IvanCraft623 Dec 12, 2024
6d854d3
Get rid of EffectContainer::defaultBubbleColor
IvanCraft623 Dec 12, 2024
a413c3b
Add missing filter -> validator terminology changes
IvanCraft623 Dec 12, 2024
265816b
Merge remote-tracking branch 'origin/minor-next' into lingering_potion
dktapps Dec 19, 2024
9f3f486
Cleanup, terminology improvements, make AreaEffectCloud constructor b…
dktapps Dec 19, 2024
33a9138
Remove crap that has no business being sent over the network
dktapps Dec 19, 2024
0be33c6
we're not syncing this over the network anymore
dktapps Dec 19, 2024
3e17f50
Remove useless property
dktapps Dec 19, 2024
2e26c08
oops
dktapps Dec 19, 2024
6a59e98
Remove internal function
dktapps Dec 19, 2024
d064709
Fucking CS
dktapps Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/block/Cauldron.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
$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{
Expand Down
2 changes: 1 addition & 1 deletion src/block/WaterCauldron.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player
$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{
Expand Down
4 changes: 1 addition & 3 deletions src/block/tile/Cauldron.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
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));

Expand All @@ -96,7 +95,7 @@ public function readSaveData(CompoundTag $nbt) : void{
$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{
Expand All @@ -115,7 +114,6 @@ protected function writeSaveData(CompoundTag $nbt) : void{
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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@ function(GoatHorn $item, int $meta) : void{
},
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(),
Expand Down
13 changes: 13 additions & 0 deletions src/entity/EntityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -86,6 +87,18 @@ public function __construct(){
//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{
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort(AreaEffectCloud::TAG_POTION_ID, PotionTypeIds::WATER));
if($potionType === null){
throw new SavedDataLoadingException("No such potion type");
}
return new AreaEffectCloud(
Helper::parseLocation($nbt, $world),
$potionType,
$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']);
Expand Down
229 changes: 229 additions & 0 deletions src/entity/effect/EffectContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?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 DaveRandom\CallbackValidator\BuiltInTypes;
use DaveRandom\CallbackValidator\CallbackType;
use DaveRandom\CallbackValidator\ParameterType;
use DaveRandom\CallbackValidator\ReturnType;
use pocketmine\color\Color;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils;
use function abs;
use function count;
use function spl_object_id;

class EffectContainer{

/** @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(function(EffectInstance $effect) : bool{
return $effect->isVisible() && $effect->getType()->hasBubbles();
});
}

/**
* Returns an array of Effects currently active.
* @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.
* If {@link EffectContainer::canAdd()} conditions are met.
*
* @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 $effectValidator
*/
public function setEffectFilterForBubbles(\Closure $effectValidator) : void{
Utils::validateCallableSignature(new CallbackType(new ReturnType(BuiltInTypes::BOOL), new ParameterType("effect", EffectInstance::class)), $effectValidator);
$this->effectFilterForBubbles = $effectValidator;
}

/**
* 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;
}
}
Loading
Loading