diff --git a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/PotionContents.java b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/PotionContents.java
index 1583d40833..984f793a42 100644
--- a/paper-api/src/main/java/io/papermc/paper/datacomponent/item/PotionContents.java
+++ b/paper-api/src/main/java/io/papermc/paper/datacomponent/item/PotionContents.java
@@ -60,6 +60,27 @@ public interface PotionContents {
@Contract(pure = true)
@Nullable String customName();
+ /**
+ * All effects that this component applies.
+ *
+ * This is a combination of the base potion type and any custom effects.
+ *
+ * @return an unmodifiable list of all effects.
+ */
+ @Contract(pure = true)
+ @Unmodifiable List allEffects();
+
+ /**
+ * Computes the effective colour of this potion contents component.
+ *
+ * This blends all custom effects, or uses a default fallback colour.
+ * It may or may not have an alpha channel, used for tipped arrows.
+ *
+ * @return the effective colour this component would display with.
+ */
+ @Contract(pure = true)
+ Color computeEffectiveColor();
+
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Builder extends DataComponentBuilder {
diff --git a/paper-api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java b/paper-api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java
index 02b0a3878b..3364254ada 100644
--- a/paper-api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java
+++ b/paper-api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java
@@ -8,6 +8,7 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Unmodifiable;
/**
* Represents a potion or item that can have custom effects.
@@ -74,6 +75,16 @@ public interface PotionMeta extends ItemMeta {
@NotNull
List getCustomEffects();
+ /**
+ * All effects that this potion meta holds.
+ *
+ * This is a combination of the base potion type and any custom effects.
+ *
+ * @return an unmodifiable list of all effects.
+ */
+ @NotNull
+ @Unmodifiable List getAllEffects();
+
/**
* Adds a custom potion effect to this potion.
*
@@ -146,6 +157,16 @@ public interface PotionMeta extends ItemMeta {
*/
void setColor(@Nullable Color color);
+ /**
+ * Computes the effective colour of this potion meta.
+ *
+ * This blends all custom effects, or uses a default fallback color.
+ *
+ * @return the effective potion color
+ */
+ @NotNull
+ Color computeEffectiveColor();
+
/**
* Checks for existence of a custom potion name translation suffix.
*
diff --git a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperPotionContents.java b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperPotionContents.java
index 4712f8bbaa..d1ddcc17db 100644
--- a/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperPotionContents.java
+++ b/paper-server/src/main/java/io/papermc/paper/datacomponent/item/PaperPotionContents.java
@@ -5,6 +5,8 @@ import io.papermc.paper.util.MCUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import net.minecraft.world.effect.MobEffectInstance;
import org.bukkit.Color;
import org.bukkit.craftbukkit.potion.CraftPotionType;
@@ -48,6 +50,19 @@ public record PaperPotionContents(
return this.impl.customName().orElse(null);
}
+ @Override
+ public @Unmodifiable List allEffects() {
+ //noinspection SimplifyStreamApiCallChains - explicity want it unmodifiable, as toList() api doesnt guarantee this.
+ return StreamSupport.stream(this.impl.getAllEffects().spliterator(), false)
+ .map(CraftPotionUtil::toBukkit)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ @Override
+ public Color computeEffectiveColor() {
+ return Color.fromARGB(this.impl.getColor());
+ }
+
static final class BuilderImpl implements PotionContents.Builder {
private final List customEffects = new ObjectArrayList<>();
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
index a3d3ea247a..05db0af608 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.inventory;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap.Builder;
import java.util.ArrayList;
@@ -25,6 +26,7 @@ import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
+import org.jetbrains.annotations.NotNull;
@DelegateDeserialization(SerializableMeta.class)
class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
@@ -202,6 +204,19 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
return ImmutableList.of();
}
+ @Override
+ @NotNull
+ public List getAllEffects() {
+ final ImmutableList.Builder builder = ImmutableList.builder();
+ if (this.hasBasePotionType()) {
+ builder.addAll(this.getBasePotionType().getPotionEffects());
+ }
+ if (this.hasCustomEffects()) {
+ builder.addAll(this.customEffects);
+ }
+ return builder.build();
+ }
+
@Override
public boolean addCustomEffect(PotionEffect effect, boolean overwrite) {
Preconditions.checkArgument(effect != null, "Potion effect cannot be null");
@@ -305,6 +320,17 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
this.color = color == null ? null : color.asRGB();
}
+ @Override
+ @NotNull
+ public Color computeEffectiveColor() {
+ if (hasColor()) return getColor();
+
+ return Color.fromRGB(
+ PotionContents.getColorOptional(Collections2.transform(getAllEffects(), CraftPotionUtil::fromBukkit))
+ .orElse(PotionContents.BASE_POTION_COLOR) & 0xFFFFFF
+ );
+ }
+
@Override
public boolean hasCustomPotionName() {
return this.customName != null;