diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java index e233c12ac01..e8abed02f54 100644 --- a/src/main/java/ch/njol/skript/registrations/Feature.java +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -18,7 +18,9 @@ public enum Feature implements Experiment { CATCH_ERRORS("catch runtime errors", LifeCycle.EXPERIMENTAL, "error catching [section]"), TYPE_HINTS("type hints", LifeCycle.EXPERIMENTAL, "[local variable] type hints"), DAMAGE_SOURCE("damage source", LifeCycle.EXPERIMENTAL, "damage source[s]"), - EQUIPPABLE_COMPONENTS("equippable components", LifeCycle.EXPERIMENTAL, "equippable components") + EQUIPPABLE_COMPONENTS("equippable components", LifeCycle.EXPERIMENTAL, "equippable components"), + CONSUMABLE_COMPONENTS("consumable components", LifeCycle.EXPERIMENTAL, "consumable components"), + CONSUME_EFFECTS("consume effects", LifeCycle.EXPERIMENTAL, "consume effects"), ; private final String codeName; diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java index 943e6a3d0bf..b257a3daa89 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java @@ -7,6 +7,7 @@ import ch.njol.skript.registrations.Classes; import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableModule; import org.skriptlang.skript.bukkit.itemcomponents.equippable.EquippableModule; import org.skriptlang.skript.bukkit.itemcomponents.generic.ExprItemCompCopy; @@ -47,7 +48,10 @@ public String toVariableNameString(ComponentWrapper wrapper) { @Override public void load(SkriptAddon addon) { - addon.loadModules(new EquippableModule()); + addon.loadModules( + new EquippableModule(), + new ConsumableModule() + ); ExprItemCompCopy.register(addon.syntaxRegistry()); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableExperimentSyntax.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableExperimentSyntax.java new file mode 100644 index 00000000000..5239ff03922 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableExperimentSyntax.java @@ -0,0 +1,20 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable; + +import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.registrations.Feature; +import org.skriptlang.skript.lang.experiment.ExperimentData; +import org.skriptlang.skript.lang.experiment.SimpleExperimentalSyntax; + +/** + * Typed {@link SimpleExperimentalSyntax} for {@link SyntaxElement}s that require {@link Feature#CONSUMABLE_COMPONENTS}. + */ +public interface ConsumableExperimentSyntax extends SimpleExperimentalSyntax { + + ExperimentData EXPERIMENT_DATA = ExperimentData.createSingularData(Feature.CONSUMABLE_COMPONENTS); + + @Override + default ExperimentData getExperimentData() { + return EXPERIMENT_DATA; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableModule.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableModule.java new file mode 100644 index 00000000000..7547a0b7c28 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableModule.java @@ -0,0 +1,137 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.ItemSource; +import ch.njol.skript.util.slot.Slot; +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.elements.*; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.Arrays; +import java.util.function.Consumer; + +public class ConsumableModule implements AddonModule { + + @Override + public boolean canLoad(SkriptAddon addon) { + return Skript.classExists("io.papermc.paper.datacomponent.item.Consumable"); + } + + @Override + public void init(SkriptAddon addon) { + Classes.registerClass(new ClassInfo<>(ConsumableWrapper.class, "consumablecomponent") + .user("consumable ?components?") + .name("Consumable Component") + .description(""" + Represents a consumable component used for items. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) + .requiredPlugins("Minecraft 1.21.3+") + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(ConsumableWrapper.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(ConsumableWrapper wrapper, int flags) { + return "consumable component"; + } + + @Override + public String toVariableNameString(ConsumableWrapper wrapper) { + return "consumable component#" + wrapper.hashCode(); + } + }) + .after("itemstack", "itemtype", "slot") + ); + + Classes.registerClass(new ClassInfo<>(ConsumeEffect.class, "consumeeffect") + .user("consume ?effects?") + .name("Consume Effect") + .description("An effect applied to an item. The effect activates when the item is consumed.") + .requiredPlugins("Minecraft 1.21.3+") + .since("INSERT VERSION") + ); + + Classes.registerClass(new EnumClassInfo<>(ConsumeEffectType.class, "consumeeffecttype", "consume effect types") + .user("consume ?effect ?types?") + .name("Consume Effect Type") + .description(""" + Represents a consume effect type. + NOTE: A type is not the same as a consume effect and cannot be used to apply to a consumable component. + """) + .requiredPlugins("Minecraft 1.21.3+") + .since("INSERT VERSION") + ); + + Classes.registerClass(new EnumClassInfo<>(ItemUseAnimation.class, "itemuseanimation", "item use animations") + .user("item ?us(e|age) ?animations?") + .name("Item Use Animation") + .description("An animation for when an item is used.") + .requiredPlugins("Minecraft 1.21.3+") + .since("INSERT VERSION") + ); + + Converters.registerConverter(Consumable.class, ConsumableWrapper.class, ConsumableWrapper::new, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(ItemStack.class, ConsumableWrapper.class, ConsumableWrapper::new, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(ItemType.class, ConsumableWrapper.class, itemType -> new ConsumableWrapper(new ItemSource<>(itemType)), Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Slot.class, ConsumableWrapper.class, slot -> { + ItemSource itemSource = ItemSource.fromSlot(slot); + if (itemSource == null) + return null; + return new ConsumableWrapper(itemSource); + }, Converter.NO_RIGHT_CHAINING); + + Comparators.registerComparator(ConsumeEffect.class, ConsumeEffectType.class, (effect, type) -> + Relation.get(type.getEffectClass().isInstance(effect)) + ); + } + + @Override + public void load(SkriptAddon addon) { + register(addon.syntaxRegistry(), + + CondConsCompParticles::register, + + EffConsCompParticles::register, + + ExprConsCompAnimation::register, + ExprConsCompEffects::register, + ExprConsCompSound::register, + ExprConsCompTime::register, + ExprConsumableComponent::register, + ExprConsumeEffectApply::register, + ExprConsumeEffectRemove::register, + ExprConsumeEffectSound::register, + ExprConsumeEffectTeleport::register, + + ExprSecBlankConsComp::register, + + LitConsumeEffectClear::register + ); + } + + private void register(SyntaxRegistry registry, Consumer... consumers) { + Arrays.stream(consumers).forEach(consumer -> consumer.accept(registry)); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableWrapper.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableWrapper.java new file mode 100644 index 00000000000..c57eadd4888 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumableWrapper.java @@ -0,0 +1,168 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable; + +import ch.njol.skript.util.ItemSource; +import io.papermc.paper.datacomponent.DataComponentBuilder; +import io.papermc.paper.datacomponent.DataComponentType.Valued; +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.bukkit.itemcomponents.ComponentWrapper; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper.ConsumableBuilder; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("UnstableApiUsage") +public class ConsumableWrapper extends ComponentWrapper { + + public ConsumableWrapper(ItemStack itemStack) { + super(itemStack); + } + + public ConsumableWrapper(ItemSource itemSource) { + super(itemSource); + } + + public ConsumableWrapper(Consumable component) { + super(component); + } + + public ConsumableWrapper(ConsumableBuilder builder) { + super(builder); + } + + @Override + public Valued getDataComponentType() { + return DataComponentTypes.CONSUMABLE; + } + + @Override + protected Consumable getComponent(ItemStack itemStack) { + Consumable consumable = itemStack.getData(DataComponentTypes.CONSUMABLE); + if (consumable != null) + return consumable; + return Consumable.consumable().build(); + } + + @Override + protected ConsumableBuilder getBuilder(ItemStack itemStack) { + Consumable consumable = itemStack.getData(DataComponentTypes.CONSUMABLE); + if (consumable != null) + return new ConsumableBuilder(consumable); + return new ConsumableBuilder(); + } + + @Override + protected void setComponent(ItemStack itemStack, Consumable component) { + itemStack.setData(DataComponentTypes.CONSUMABLE, component); + } + + @Override + protected ConsumableBuilder getBuilder(Consumable component) { + return new ConsumableBuilder(component); + } + + @Override + public ConsumableWrapper clone() { + ConsumableWrapper clone = newWrapper(); + Consumable base = getComponent(); + return clone; + } + + @Override + public Consumable newComponent() { + return newBuilder().build(); + } + + @Override + public ConsumableBuilder newBuilder() { + return new ConsumableBuilder(); + } + + @Override + public ConsumableWrapper newWrapper() { + return newInstance(); + } + + public static ConsumableWrapper newInstance() { + return new ConsumableWrapper(Consumable.consumable().build()); + } + + /** + * Custom builder class for {@link Consumable} that mimics {@link Consumable.Builder} allowing methods not + * shared across all versions to be used. + */ + @SuppressWarnings("NonExtendableApiUsage") + public static class ConsumableBuilder implements DataComponentBuilder { + + private ItemUseAnimation animation = ItemUseAnimation.EAT; + private List consumeEffects = new ArrayList<>(); + private boolean consumeParticles = true; + private float consumeSeconds = 5; + private Key sound = Registry.SOUNDS.getKey(Sound.ENTITY_GENERIC_EAT); + + public ConsumableBuilder() {} + + public ConsumableBuilder(Consumable consumable) { + this.animation = consumable.animation(); + this.consumeEffects.addAll(consumable.consumeEffects()); + this.consumeParticles = consumable.hasConsumeParticles(); + this.consumeSeconds = consumable.consumeSeconds(); + this.sound = consumable.sound(); + } + + public ConsumableBuilder animation(ItemUseAnimation animation) { + this.animation = animation; + return this; + } + + public ConsumableBuilder addEffect(ConsumeEffect effect) { + consumeEffects.add(effect); + return this; + } + + public ConsumableBuilder addEffects(List effects) { + this.consumeEffects.addAll(effects); + return this; + } + + public ConsumableBuilder effects(List effects) { + this.consumeEffects.clear(); + this.consumeEffects.addAll(effects); + return this; + } + + public ConsumableBuilder hasConsumeParticles(boolean consumeParticles) { + this.consumeParticles = consumeParticles; + return this; + } + + public ConsumableBuilder consumeSeconds(float seconds) { + this.consumeSeconds = seconds; + return this; + } + + public ConsumableBuilder sound(Key sound) { + this.sound = sound; + return this; + } + + @Override + public Consumable build() { + return Consumable.consumable() + .animation(animation) + .addEffects(consumeEffects) + .hasConsumeParticles(consumeParticles) + .consumeSeconds(consumeSeconds) + .sound(sound) + .build(); + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectExperimentalSyntax.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectExperimentalSyntax.java new file mode 100644 index 00000000000..a5d5a00ffc1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectExperimentalSyntax.java @@ -0,0 +1,20 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable; + +import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.registrations.Feature; +import org.skriptlang.skript.lang.experiment.ExperimentData; +import org.skriptlang.skript.lang.experiment.SimpleExperimentalSyntax; + +/** + * Typed {@link SimpleExperimentalSyntax} for {@link SyntaxElement}s that require {@link Feature#CONSUME_EFFECTS}. + */ +public interface ConsumeEffectExperimentalSyntax extends SimpleExperimentalSyntax { + + ExperimentData EXPERIMENT_DATA = ExperimentData.createSingularData(Feature.CONSUME_EFFECTS); + + @Override + default ExperimentData getExperimentData() { + return EXPERIMENT_DATA; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectType.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectType.java new file mode 100644 index 00000000000..0c2973a4e1e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/ConsumeEffectType.java @@ -0,0 +1,35 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable; + +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect.ApplyStatusEffects; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect.ClearAllStatusEffects; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect.PlaySound; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect.RemoveStatusEffects; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect.TeleportRandomly; + +/** + * Types of {@link ConsumeEffect}s. + */ +@SuppressWarnings("UnstableApiUsage") +public enum ConsumeEffectType { + + APPLY_STATUS_EFFECTS(ApplyStatusEffects.class), + CLEAR_ALL_STATUS_EFFECTS(ClearAllStatusEffects.class), + PLAY_SOUND(PlaySound.class), + REMOVE_STATUS_EFFECTS(RemoveStatusEffects.class), + TELEPORT_RANDOMLY(TeleportRandomly.class); + + private final Class effectClass; + + ConsumeEffectType(Class effectClass) { + this.effectClass = effectClass; + } + + /** + * @return The {@link ConsumeEffect} class for this {@link ConsumeEffectType}. + */ + public Class getEffectClass() { + return effectClass; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/CondConsCompParticles.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/CondConsCompParticles.java new file mode 100644 index 00000000000..687372676e7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/CondConsCompParticles.java @@ -0,0 +1,58 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consumable Component - Has Particles") +@Description(""" + Whether an item has particles enabled when being consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + if {_item} has consumption particles enabled: + disable the consume particles of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class CondConsCompParticles extends PropertyCondition implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.CONDITION, + infoBuilder(CondConsCompParticles.class, PropertyType.HAVE, "consum(e|ption) particles [enabled|:disabled]", "consumablecomponents") + .supplier(CondConsCompParticles::new) + .build() + ); + } + + private boolean checkEnabled; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + checkEnabled = !parseResult.hasTag("disabled"); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public boolean check(ConsumableWrapper wrapper) { + return wrapper.getComponent().hasConsumeParticles() == checkEnabled; + } + + @Override + protected String getPropertyName() { + if (checkEnabled) + return "consume particles enabled"; + return "consume particles disabled"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/EffConsCompParticles.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/EffConsCompParticles.java new file mode 100644 index 00000000000..7846add9cd9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/EffConsCompParticles.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consumable Component - Particles") +@Description(""" + Whether an item should have particles enabled when being consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + if {_item} does not have consumption particles enabled: + enable the consumption particles of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class EffConsCompParticles extends Effect implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EFFECT, + SyntaxInfo.builder(EffConsCompParticles.class) + .addPatterns("(enable|:disable) [the] consum(e|ption) particle[s] [effect[s]] (of|for) %consumablecomponents%") + .supplier(EffConsCompParticles::new) + .build() + ); + } + + private Expression wrappers; + private boolean enable; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + wrappers = (Expression) exprs[0]; + enable = !parseResult.hasTag("disable"); + return true; + } + + @Override + protected void execute(Event event) { + wrappers.stream(event).forEach(wrapper -> + wrapper.editBuilder(builder -> builder.hasConsumeParticles(enable))); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + if (enable) { + builder.append("enable"); + } else { + builder.append("disable"); + } + builder.append("the consume particle effects of", wrappers); + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompAnimation.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompAnimation.java new file mode 100644 index 00000000000..a030b9ce5bb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompAnimation.java @@ -0,0 +1,70 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consumable Component - Animation") +@Description(""" + The animation that plays when the item is being consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set {_anim} to the consumption animation of {_item}") +@Example("set the consumption animation of {_item} to drink animation") +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsCompAnimation extends SimplePropertyExpression implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprConsCompAnimation.class, ItemUseAnimation.class, "consum(e|ption) animation", "consumablecomponents", true) + .supplier(ExprConsCompAnimation::new) + .build() + ); + } + + @Override + public @Nullable ItemUseAnimation convert(ConsumableWrapper wrapper) { + return wrapper.getComponent().animation(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) + return CollectionUtils.array(ItemUseAnimation.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + ItemUseAnimation animation = (ItemUseAnimation) delta[0]; + + getExpr().stream(event).forEach(wrapper -> + wrapper.editBuilder(builder -> builder.animation(animation))); + } + + @Override + public Class getReturnType() { + return ItemUseAnimation.class; + } + + @Override + protected String getPropertyName() { + return "consume animation"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompEffects.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompEffects.java new file mode 100644 index 00000000000..a9515928f62 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompEffects.java @@ -0,0 +1,112 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Consumable Component - Consume Effects") +@Description(""" + The consume effects that should activate when the item is consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set {_effects::*} to the consumption effects of {_item}") +@Example(""" + set {_effect} to a consume effect to clear all potion effects + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsCompEffects extends PropertyExpression implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprConsCompEffects.class, ConsumeEffect.class, "consum(e|ption) effects", "consumablecomponents", true) + .supplier(ExprConsCompEffects::new) + .build() + ); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + setExpr((Expression) exprs[0]); + return true; + } + + @Override + protected ConsumeEffect[] get(Event event, ConsumableWrapper[] source) { + List effects = new ArrayList<>(); + for (ConsumableWrapper wrapper : source) { + effects.addAll(wrapper.getComponent().consumeEffects()); + } + return effects.toArray(ConsumeEffect[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, REMOVE, ADD -> CollectionUtils.array(ConsumeEffect[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + List provided = new ArrayList<>(); + if (delta != null) { + for (Object object : delta) { + if (object instanceof ConsumeEffect effect) + provided.add(effect); + } + } + + getExpr().stream(event).forEach(wrapper -> { + List current = new ArrayList<>(wrapper.getComponent().consumeEffects()); + switch (mode) { + case SET -> { + current.clear(); + current.addAll(provided); + } + case ADD -> current.addAll(provided); + case REMOVE -> current.removeAll(provided); + case DELETE -> current.clear(); + } + wrapper.editBuilder(builder -> builder.effects(current)); + }); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return ConsumeEffect.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the consume effects of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompSound.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompSound.java new file mode 100644 index 00000000000..736ee969ef9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompSound.java @@ -0,0 +1,86 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.bukkitutil.SoundUtils; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consumable Component - Consume Sound") +@Description(""" + The sound to be played when the item is being consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set {_sound} to the consumption sound of {_item}") +@Example("set the consumption sound of {_item} to \"minecraft:entity.sheep.ambient\"") +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprConsCompSound extends SimplePropertyExpression implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprConsCompSound.class, String.class, "consum(e|ption) sound", "consumablecomponents", true) + .supplier(ExprConsCompSound::new) + .build() + ); + } + + @Override + public @Nullable String convert(ConsumableWrapper wrapper) { + return wrapper.getComponent().sound().toString(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) + return CollectionUtils.array(String.class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Sound enumSound = null; + if (delta != null) { + String string = (String) delta[0]; + enumSound = SoundUtils.getSound(string); + if (enumSound == null) { + error("Could not find a sound with the id '" + string + "'."); + return; + } + } + + Key key; + if (enumSound != null) { + key = Registry.SOUNDS.getKey(enumSound); + } else { + key = null; + } + + getExpr().stream(event).forEach(wrapper -> + wrapper.editBuilder(builder -> builder.sound(key))); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + protected String getPropertyName() { + return "consume sound"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompTime.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompTime.java new file mode 100644 index 00000000000..540f8047b95 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsCompTime.java @@ -0,0 +1,81 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.time.temporal.ChronoUnit; + +@Name("Consumable Component - Consume Time") +@Description(""" + The time it takes for an item to be consumed. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set the consumption time of {_item} to 5 seconds") +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprConsCompTime extends SimplePropertyExpression implements ConsumableExperimentSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprConsCompTime.class, Timespan.class, "consum(e|ption) time", "consumablecomponents", true) + .supplier(ExprConsCompTime::new) + .build() + ); + } + + @Override + public @Nullable Timespan convert(ConsumableWrapper wrapper) { + float seconds = wrapper.getComponent().consumeSeconds(); + return new Timespan(TimePeriod.SECOND, (long) seconds); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, REMOVE, ADD -> CollectionUtils.array(Timespan.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + float seconds = delta == null ? 0f : ((Timespan) delta[0]).get(ChronoUnit.SECONDS); + float finalSeconds = Math2.fit(0, seconds, Float.MAX_VALUE); + getExpr().stream(event).forEach(wrapper -> { + float current = wrapper.getComponent().consumeSeconds(); + switch (mode) { + case SET, DELETE -> current = finalSeconds; + case ADD -> current = Math2.fit(0, current + finalSeconds, Float.MAX_VALUE); + case REMOVE -> current = Math2.fit(0, current - finalSeconds, Float.MAX_VALUE); + } + float finalCurrent = current; + wrapper.editBuilder(builder -> builder.consumeSeconds(finalCurrent)); + }); + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "consume time"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumableComponent.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumableComponent.java new file mode 100644 index 00000000000..c1613c00c37 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumableComponent.java @@ -0,0 +1,120 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.aliases.ItemData; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.ItemSource; +import ch.njol.skript.util.slot.Slot; +import ch.njol.util.coll.CollectionUtils; +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.Consumable; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consumable Component") +@Description(""" + The consumable component of an item. Any changes made to the consumable component will be present on the item. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("clear the consumable component of {_item}") +@Example("reset the consumable component of {_item}") +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsumableComponent extends SimplePropertyExpression implements ConsumableExperimentSyntax { + + static { + register(ExprConsumableComponent.class, ConsumableWrapper.class, "consumable component[s]", "slots/itemtypes"); + } + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder(ExprConsumableComponent.class, ConsumableWrapper.class, "consumable component[s]", "slots/itemtypes", false) + .supplier(ExprConsumableComponent::new) + .build() + ); + } + + @Override + public @Nullable ConsumableWrapper convert(Object object) { + ItemSource itemSource = null; + if (object instanceof ItemType itemType) { + itemSource = new ItemSource<>(itemType); + } else if (object instanceof Slot slot) { + itemSource = ItemSource.fromSlot(slot); + } + return itemSource == null ? null : new ConsumableWrapper(itemSource); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(ConsumableWrapper.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Consumable component = null; + if (delta != null) + component = ((ConsumableWrapper) delta[0]).getComponent(); + + for (Object object : getExpr().getArray(event)) { + if (object instanceof ItemType itemType) { + changeItemType(itemType, mode, component); + } else if (object instanceof Slot slot) { + changeSlot(slot, mode, component); + } + } + } + + public void changeItemType(ItemType itemType, ChangeMode mode, Consumable component) { + for (ItemData itemData : itemType) { + ItemStack dataStack = itemData.getStack(); + if (dataStack == null) + continue; + changeItemStack(dataStack, mode, component); + } + } + + public void changeSlot(Slot slot, ChangeMode mode, Consumable component) { + ItemStack itemStack = slot.getItem(); + if (itemStack == null) + return; + itemStack = changeItemStack(itemStack, mode, component); + slot.setItem(itemStack); + } + + @SuppressWarnings("UnstableApiUsage") + public ItemStack changeItemStack(ItemStack itemStack, ChangeMode mode, Consumable component) { + switch (mode) { + case SET -> itemStack.setData(DataComponentTypes.CONSUMABLE, component); + case DELETE -> itemStack.unsetData(DataComponentTypes.CONSUMABLE); + case RESET -> itemStack.resetData(DataComponentTypes.CONSUMABLE); + } + return itemStack; + } + + @Override + public Class getReturnType() { + return ConsumableWrapper.class; + } + + @Override + protected String getPropertyName() { + return "consumable component"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectApply.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectApply.java new file mode 100644 index 00000000000..e47eda3256e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectApply.java @@ -0,0 +1,105 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.Math2; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import org.bukkit.event.Event; +import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumeEffectExperimentalSyntax; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Consume Effect - Apply Effects") +@Description(""" + A consume effect that applies the provided potions when the item has been consumed. + The probability is the chance the potions get applied when the item has been consumed. + Consume effects have to be added to the consumable component of an item. + NOTE: Consume Effect elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_effect} to a consume effect to apply (a new potion effect of strength of tier 3 for 1 hour) with a probability of 100 + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsumeEffectApply extends SimpleExpression implements ConsumeEffectExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprConsumeEffectApply.class, ConsumeEffect.class) + .addPatterns("[a] consume effect to apply %potioneffects% with [a] probability of %number%") + .supplier(ExprConsumeEffectApply::new) + .build() + ); + } + + private Expression effects; + private Expression probability; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + effects = (Expression) exprs[0]; + //noinspection unchecked + probability = (Expression) exprs[1]; + return true; + } + + @Override + protected ConsumeEffect @Nullable [] get(Event event) { + List potions = new ArrayList<>(effects.stream(event).toList()); + if (potions.isEmpty()) + return null; + + Number number = this.probability.getSingle(event); + if (number == null) + return null; + float probability = Math2.fit(0, number.floatValue(), 100) / 100; + + ConsumeEffect effect = ConsumeEffect.applyStatusEffects(potions, probability); + return new ConsumeEffect[] {effect}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ConsumeEffect.class; + } + + @Override + public Expression simplify() { + if (effects instanceof Literal && probability instanceof Literal) + return SimplifiedLiteral.fromExpression(this); + return this; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("a consume effect to apply", effects) + .append("with a probability of", probability) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectRemove.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectRemove.java new file mode 100644 index 00000000000..ffb96a80fec --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectRemove.java @@ -0,0 +1,98 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.set.RegistryKeySet; +import org.bukkit.event.Event; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.ComponentUtils; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumeEffectExperimentalSyntax; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.List; + +@Name("Consume Effect - Remove Effects") +@Description(""" + A consume effect that removes the provided potion types when the item has been consumed. + Consume effects have to be added to the consumable component of an item. + NOTE: Consume Effect elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_effect} to a consume effect to remove blindness, bad luck and slowness + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsumeEffectRemove extends SimpleExpression implements ConsumeEffectExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprConsumeEffectRemove.class, ConsumeEffect.class) + .addPatterns("[a] consume effect to remove %potioneffecttypes%") + .supplier(ExprConsumeEffectRemove::new) + .build() + ); + } + + private Expression effectTypes; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + effectTypes = (Expression) exprs[0]; + return true; + } + + @Override + protected ConsumeEffect @Nullable [] get(Event event) { + List types = new ArrayList<>(effectTypes.stream(event).toList()); + if (types.isEmpty()) + return null; + + RegistryKeySet keys = ComponentUtils.collectionToRegistryKeySet(types, RegistryKey.MOB_EFFECT); + ConsumeEffect effect = ConsumeEffect.removeEffects(keys); + return new ConsumeEffect[] {effect}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ConsumeEffect.class; + } + + @Override + public Expression simplify() { + if (effectTypes instanceof Literal) + return SimplifiedLiteral.fromExpression(this); + return this; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("a consume effect to remove", effectTypes) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectSound.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectSound.java new file mode 100644 index 00000000000..ad239e422db --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectSound.java @@ -0,0 +1,101 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.bukkitutil.SoundUtils; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumeEffectExperimentalSyntax; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consume Effect - Play Sound") +@Description(""" + A consume effect that plays a sound when the item has been consumed. + Consume effects have to be added to the consumable component of an item. + NOTE: Consume Effect elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_effect} to a consume effect to play the sound "ui.toast.challenge.complete" + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsumeEffectSound extends SimpleExpression implements ConsumeEffectExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprConsumeEffectSound.class, ConsumeEffect.class) + .addPatterns("[a] consume effect to play [[the] sound] %string%") + .supplier(ExprConsumeEffectSound::new) + .build() + ); + } + + private Expression string; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + string = (Expression) exprs[0]; + return true; + } + + @Override + protected ConsumeEffect @Nullable [] get(Event event) { + String string = this.string.getSingle(event); + if (string == null) + return null; + + Sound sound = SoundUtils.getSound(string); + if (sound == null) { + error("Could not find a sound with the id '" + string + "'."); + return null; + } + Key key = Registry.SOUNDS.getKey(sound); + ConsumeEffect effect = ConsumeEffect.playSoundConsumeEffect(key); + + return new ConsumeEffect[] {effect}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ConsumeEffect.class; + } + + @Override + public Expression simplify() { + if (string instanceof Literal) + return SimplifiedLiteral.fromExpression(this); + return this; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("a consume effect to play the sound", string) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectTeleport.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectTeleport.java new file mode 100644 index 00000000000..9656443e83b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprConsumeEffectTeleport.java @@ -0,0 +1,103 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumeEffectExperimentalSyntax; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consume Effect - Teleport Randomly") +@Description(""" + A consume effect that teleports randomly in the provided radius or diameter when the item has been consumed. + Consume effects have to be added to the consumable component of an item. + NOTE: Consume Effect elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_effect} to a consume effect to teleport randomly in a radius of 5 + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class ExprConsumeEffectTeleport extends SimpleExpression implements ConsumeEffectExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprConsumeEffectTeleport.class, ConsumeEffect.class) + .addPatterns("[a] consume effect to teleport randomly in [a] (radius|:diameter) of %number%") + .supplier(ExprConsumeEffectTeleport::new) + .build() + ); + } + + private Expression number; + private boolean isDiameter; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + number = (Expression) exprs[0]; + isDiameter = parseResult.hasTag("diameter"); + return true; + } + + @Override + protected ConsumeEffect @Nullable [] get(Event event) { + Number number = this.number.getSingle(event); + if (number == null) + return null; + + float diameter = Math.abs(number.floatValue()); + if (!isDiameter) + diameter *= 2; + + ConsumeEffect effect = ConsumeEffect.teleportRandomlyEffect(diameter); + return new ConsumeEffect[] {effect}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ConsumeEffect.class; + } + + @Override + public Expression simplify() { + if (number instanceof Literal) + return SimplifiedLiteral.fromExpression(this); + return this; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("a consume effect to teleport randomly in a"); + if (isDiameter) { + builder.append("diameter"); + } else { + builder.append("radius"); + } + builder.append("of", number); + return builder.toString(); + } + + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprSecBlankConsComp.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprSecBlankConsComp.java new file mode 100644 index 00000000000..56bcf10cd8b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/ExprSecBlankConsComp.java @@ -0,0 +1,116 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.util.SectionUtils; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableExperimentSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumableWrapper; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Name("Blank Consumable Component") +@Description(""" + Gets a blank consumable component. + NOTE: Consumable component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_component} to a blank consumable component: + set the consumption animation to drink animation + add (a consume effect to clear all potion effects) to the consume effects + set the consumption time to 5 seconds + set the consumption sound to "ui.toast.out" + enable the consumption particles for event-consumable component + set the consumable component of {_item} to {_component} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprSecBlankConsComp extends SectionExpression implements ConsumableExperimentSyntax { + + private static class BlankConsumableSectionEvent extends Event { + + private final ConsumableWrapper wrapper; + + public BlankConsumableSectionEvent(ConsumableWrapper wrapper) { + this.wrapper = wrapper; + } + + public ConsumableWrapper getWrapper() { + return wrapper; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new IllegalStateException(); + } + } + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprSecBlankConsComp.class, ConsumableWrapper.class) + .addPatterns("a (blank|empty) consumable component") + .supplier(ExprSecBlankConsComp::new) + .build() + ); + EventValues.registerEventValue(BlankConsumableSectionEvent.class, ConsumableWrapper.class, BlankConsumableSectionEvent::getWrapper); + } + + private Trigger trigger; + + @Override + public boolean init(Expression[] exprs, int pattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { + if (node != null) { + AtomicBoolean isDelayed = new AtomicBoolean(false); + trigger = SectionUtils.loadLinkedCode("blank consumable component", (beforeLoading, afterLoading) -> + loadCode(node, "blank consumable component", beforeLoading, afterLoading, BlankConsumableSectionEvent.class) + ); + return trigger != null; + } + return true; + } + + @Override + protected ConsumableWrapper @Nullable [] get(Event event) { + ConsumableWrapper wrapper = ConsumableWrapper.newInstance(); + if (trigger != null) { + BlankConsumableSectionEvent sectionEvent = new BlankConsumableSectionEvent(wrapper); + Variables.withLocalVariables(event, sectionEvent, () -> TriggerItem.walk(trigger, sectionEvent)); + } + return new ConsumableWrapper[] {wrapper}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return ConsumableWrapper.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a blank consumable component"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/LitConsumeEffectClear.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/LitConsumeEffectClear.java new file mode 100644 index 00000000000..04d0f337774 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/consumable/elements/LitConsumeEffectClear.java @@ -0,0 +1,58 @@ +package org.skriptlang.skript.bukkit.itemcomponents.consumable.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.util.Kleenean; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.consumable.ConsumeEffectExperimentalSyntax; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Consume Effect - Clear Effects") +@Description(""" + A consume effect that clears all potions when the item has been consumed. + Consume effects have to be added to the consumable component of an item. + NOTE: Consume Effect elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_effect} to a consume effect to clear all potion effects + add {_effect} to the consume effects of {_item} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +@SuppressWarnings("UnstableApiUsage") +public class LitConsumeEffectClear extends SimpleLiteral implements ConsumeEffectExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(LitConsumeEffectClear.class, ConsumeEffect.class) + .addPatterns("[a] consume effect to clear all (potion|status) effects") + .supplier(LitConsumeEffectClear::new) + .build() + ); + } + + public LitConsumeEffectClear() { + super(ConsumeEffect.clearAllStatusEffects(), false); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a consume effect to clear all potion effects"; + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index f131ca399b2..a0a4d7e9ea6 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -3002,6 +3002,28 @@ damage types: wither: wither wither_skull: wither skull +# Consume Effect Types +consume effect types: + apply_status_effects: status apply + clear_all_status_effects: status clear + play_sound: play sound, sound + remove_status_effects: status remove + teleport_randomly: teleport randomly, teleport + +# Item Use Animations +item use animations: + block: block animation + bow: bow animation + brush: brush animation + bundle: bundle animation + crossbow: crossbow animation + drink: drink animation + eat: eat animation + none: none, no animation + spear: spear animation + spyglass: spyglass animation + toot_horn: toot horn animation + # -- Boolean -- boolean: true: @@ -3122,6 +3144,10 @@ types: frogvariant: frog variant¦s @a itemcomponent: item component¦s @an equippablecomponent: equippable component¦s @an + consumablecomponent: consumable component¦s @a + consumeeffect: consume effect¦s @a + consumeeffecttype: consume effect type¦s @a + itemuseanimation: item use animation¦s @an # Skript weathertype: weather type¦s @a diff --git a/src/test/skript/tests/general/ConsumableComponents.sk b/src/test/skript/tests/general/ConsumableComponents.sk new file mode 100644 index 00000000000..27b897d2126 --- /dev/null +++ b/src/test/skript/tests/general/ConsumableComponents.sk @@ -0,0 +1,129 @@ +options: + animation: drink animation + time: 1 hour + sound: "minecraft:ui.toast.challenge_complete" + effect: a consume effect to clear all potion effects + +using consumable components +using consume effects + +test "consumable components - new" when running minecraft "1.21.3": + set {_component} to a blank consumable component + set the consumption animation of {_component} to {@animation} + assert the consumption animation of {_component} is {@animation} with "Consume animation of component was not set" + set the consumption time of {_component} to {@time} + assert the consumption time of {_component} is {@time} with "Consume time of component was not set" + set the consumption sound of {_component} to {@sound} + assert the consumption sound of {_component} is {@sound} with "Consume sound of component was not set" + + add {@effect} to the consume effects of {_component} + assert the consume effects of {_component} contains {@effect} with "Consume effect was not added to component" + clear the consume effects of {_component} + assert the consume effects of {_component} is not set with "Consume effects of component was not cleared" + + enable the consumption particles of {_component} + assert {_component} has consumption particles enabled with "Consume particles of component were not enabled" + disable the consumption particles of {_component} + assert {_component} does not have consumption particles enabled with "Consume particles of component were not disabled" + +test "consumable components - itemtype" when running minecraft "1.21.3": + set {_item} to a leather helmet (item type) + set the consumption animation of {_item} to {@animation} + assert the consumption animation of {_item} is {@animation} with "Consume animation of itemtype was not set" + set the consumption time of {_item} to {@time} + assert the consumption time of {_item} is {@time} with "Consume time of itemtype was not set" + set the consumption sound of {_item} to {@sound} + assert the consumption sound of {_item} is {@sound} with "Consume sound of itemtype was not set" + + add {@effect} to the consume effects of {_item} + assert the consume effects of {_item} contains {@effect} with "Consume effect was not added to itemtype" + clear the consume effects of {_item} + assert the consume effects of {_item} is not set with "Consume effects of itemtype was not cleared" + + enable the consumption particles of {_item} + assert {_item} has consumption particles enabled with "Consume particles of itemtype were not enabled" + disable the consumption particles of {_item} + assert {_item} does not have consumption particles enabled with "Consume particles of itemtype were not disabled" + +test "consumable components - itemstack" when running minecraft "1.21.3": + set {_item} to an iron helmet (item stack) + set the consumption animation of {_item} to {@animation} + assert the consumption animation of {_item} is {@animation} with "Consume animation of itemstack was not set" + set the consumption time of {_item} to {@time} + assert the consumption time of {_item} is {@time} with "Consume time of itemstack was not set" + set the consumption sound of {_item} to {@sound} + assert the consumption sound of {_item} is {@sound} with "Consume sound of itemstack was not set" + + add {@effect} to the consume effects of {_item} + assert the consume effects of {_item} contains {@effect} with "Consume effect was not added to itemstack" + clear the consume effects of {_item} + assert the consume effects of {_item} is not set with "Consume effects of itemstack was not cleared" + + enable the consumption particles of {_item} + assert {_item} has consumption particles enabled with "Consume particles of itemstack were not enabled" + disable the consumption particles of {_item} + assert {_item} does not have consumption particles enabled with "Consume particles of itemstack were not disabled" + +test "consumable components - copy" when running minecraft "1.21.3": + set {_component} to a blank consumable component + set the consumption animation of {_component} to {@animation} + assert the consumption animation of {_component} is {@animation} with "Consume animation of component was not set" + set the consumption time of {_component} to {@time} + assert the consumption time of {_component} is {@time} with "Consume time of component was not set" + set the consumption sound of {_component} to {@sound} + assert the consumption sound of {_component} is {@sound} with "Consume sound of component was not set" + + add {@effect} to the consume effects of {_component} + assert the consume effects of {_component} contains {@effect} with "Consume effect was not added to component" + clear the consume effects of {_component} + assert the consume effects of {_component} is not set with "Consume effects of component was not cleared" + + enable the consumption particles of {_component} + assert {_component} has consumption particles enabled with "Consume particles of component were not enabled" + disable the consumption particles of {_component} + assert {_component} does not have consumption particles enabled with "Consume particles of component were not disabled" + + set {_original} to {_component} + set {_copy} to a item component copy of {_original} + set the consumption animation of {_copy} to eat animation + assert the consumption animation of {_copy} is eat animation with "Consume animation of copy was not set" + assert the consumption animation of {_original} is {@animation} with "Consume animation of original should not have changed" + set the consumption time of {_copy} to 30 minutes + assert the consumption time of {_copy} is 30 minutes with "Consume time of copy was not set" + assert the consumption time of {_original} is {@time} with "Consume time of original should not have changed" + set the consumption sound of {_copy} to "minecraft:ui.toast.in" + assert the consumption sound of {_copy} is "minecraft:ui.toast.in" with "Consume sound of copy was not set" + assert the consumption sound of {_original} is {@sound} with "Consume sound of original should not have changed" + + add {@effect} to the consume effects of {_copy} + assert the consume effects of {_copy} contains {@effect} with "Consume effect was not added to copy" + assert the consume effects of {_original} is not set with "Consume effects of original should not have changed" + clear the consume effects of {_copy} + assert the consume effects of {_copy} is not set with "Consume effects of copy was not cleared" + + enable the consumption particles of {_copy} + assert {_copy} has consumption particles enabled with "Consume particles of copy were not enabled" + assert {_original} does not have consumption particles enabled with "Consume particles of original should not have changed" + disable the consumption particles of {_copy} + assert {_copy} does not have consumption particles enabled with "Consume particles of copy were not disabled" + +test "consumable components - slot" when running minecraft "1.21.3": + set {_gui} to a chest inventory with 1 row + set slot 1 of {_gui} to a diamond helmet + + set the consumption animation of (slot 1 of {_gui}) to {@animation} + assert the consumption animation of (slot 1 of {_gui}) is {@animation} with "Consume animation of slot was not set" + set the consumption time of (slot 1 of {_gui}) to {@time} + assert the consumption time of (slot 1 of {_gui}) is {@time} with "Consume time of slot was not set" + set the consumption sound of (slot 1 of {_gui}) to {@sound} + assert the consumption sound of (slot 1 of {_gui}) is {@sound} with "Consume sound of slot was not set" + + add {@effect} to the consume effects of (slot 1 of {_gui}) + assert the consume effects of (slot 1 of {_gui}) contains {@effect} with "Consume effect was not added to slot" + clear the consume effects of (slot 1 of {_gui}) + assert the consume effects of (slot 1 of {_gui}) is not set with "Consume effects of slot was not cleared" + + enable the consumption particles of (slot 1 of {_gui}) + assert (slot 1 of {_gui}) has consumption particles enabled with "Consume particles of slot were not enabled" + disable the consumption particles of (slot 1 of {_gui}) + assert (slot 1 of {_gui}) does not have consumption particles enabled with "Consume particles of slot were not disabled" diff --git a/src/test/skript/tests/general/ConsumeEffects.sk b/src/test/skript/tests/general/ConsumeEffects.sk new file mode 100644 index 00000000000..75f335ac540 --- /dev/null +++ b/src/test/skript/tests/general/ConsumeEffects.sk @@ -0,0 +1,18 @@ + +using consume effects + +test "consume effect type comparison" when running minecraft "1.21.3": + set {_apply} to a consume effect to apply (a new potion effect of strength of tier 3 for 1 hour) with a probability of 100 + assert {_apply} is (status apply) with "Consume effect apply did not match" + + set {_remove} to a consume effect to remove blindness, bad luck and slowness + assert {_remove} is (status remove) with "Consume effect remove did not match" + + set {_clear} to a consume effect to clear all potion effects + assert {_clear} is (status clear) with "Consume effect clear did not match" + + set {_sound} to a consume effect to play the sound "minecraft:ui.toast.challenge_complete" + assert {_sound} is (sound) with "Consume effect sound did not match" + + set {_teleport} to a consume effect to teleport randomly in a radius of 5 + assert (a consume effect to teleport randomly in a radius of 5) is (teleport (consume effect type)) with "Consume effect teleport did not match"