diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java index e233c12ac01..061a4989cb1 100644 --- a/src/main/java/ch/njol/skript/registrations/Feature.java +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -18,7 +18,8 @@ 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"), + FOOD_COMPONENTS("food components", LifeCycle.EXPERIMENTAL, "food components"), ; 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..d2be82845be 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/ItemComponentModule.java @@ -8,6 +8,7 @@ import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.bukkit.itemcomponents.equippable.EquippableModule; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodModule; import org.skriptlang.skript.bukkit.itemcomponents.generic.ExprItemCompCopy; public class ItemComponentModule implements AddonModule { @@ -47,7 +48,10 @@ public String toVariableNameString(ComponentWrapper wrapper) { @Override public void load(SkriptAddon addon) { - addon.loadModules(new EquippableModule()); + addon.loadModules( + new EquippableModule(), + new FoodModule() + ); ExprItemCompCopy.register(addon.syntaxRegistry()); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodExperimentalSyntax.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodExperimentalSyntax.java new file mode 100644 index 00000000000..53afaa633b5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodExperimentalSyntax.java @@ -0,0 +1,20 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food; + +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#FOOD_COMPONENTS}. + */ +public interface FoodExperimentalSyntax extends SimpleExperimentalSyntax { + + ExperimentData EXPERIMENT_DATA = ExperimentData.createSingularData(Feature.FOOD_COMPONENTS); + + @Override + default ExperimentData getExperimentData() { + return EXPERIMENT_DATA; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodModule.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodModule.java new file mode 100644 index 00000000000..835ec73b226 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodModule.java @@ -0,0 +1,98 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.ClassInfo; +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.FoodProperties; +import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.CondFoodCompAlwaysEat; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.EffFoodCompAlwaysEat; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.ExprFoodCompNutrition; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.ExprFoodCompSaturation; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.ExprFoodComponent; +import org.skriptlang.skript.bukkit.itemcomponents.food.elements.ExprSecBlankFoodComp; +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 FoodModule implements AddonModule { + + @Override + public boolean canLoad(SkriptAddon addon) { + return Skript.classExists("io.papermc.paper.datacomponent.item.FoodProperties"); + } + + @Override + public void init(SkriptAddon addon) { + Classes.registerClass(new ClassInfo<>(FoodWrapper.class, "foodcomponent") + .user("food ?components?") + .name("Food Component") + .description(""" + Represents a food component used for items. + NOTE: Food 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<>(FoodWrapper.class)) + .parser(new Parser<>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(FoodWrapper wrapper, int flags) { + return "food component"; + } + + @Override + public String toVariableNameString(FoodWrapper wrapper) { + return "food component#" + wrapper.hashCode(); + } + }) + .after("itemstack", "itemtype", "slot") + ); + + Converters.registerConverter(FoodProperties.class, FoodWrapper.class, FoodWrapper::new, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(ItemStack.class, FoodWrapper.class, FoodWrapper::new, Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(ItemType.class, FoodWrapper.class, itemType -> new FoodWrapper(new ItemSource<>(itemType)), Converter.NO_RIGHT_CHAINING); + Converters.registerConverter(Slot.class, FoodWrapper.class, slot -> { + ItemSource itemSource = ItemSource.fromSlot(slot); + if (itemSource == null) + return null; + return new FoodWrapper(itemSource); + }, Converter.NO_RIGHT_CHAINING); + } + + @Override + public void load(SkriptAddon addon) { + register(addon.syntaxRegistry(), + + CondFoodCompAlwaysEat::register, + + EffFoodCompAlwaysEat::register, + + ExprFoodCompNutrition::register, + ExprFoodComponent::register, + ExprFoodCompSaturation::register, + + ExprSecBlankFoodComp::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/food/FoodWrapper.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodWrapper.java new file mode 100644 index 00000000000..87d36619ee2 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/FoodWrapper.java @@ -0,0 +1,95 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food; + +import ch.njol.skript.util.ItemSource; +import io.papermc.paper.datacomponent.DataComponentType.Valued; +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.FoodProperties; +import io.papermc.paper.datacomponent.item.FoodProperties.Builder; +import org.bukkit.inventory.ItemStack; +import org.skriptlang.skript.bukkit.itemcomponents.ComponentWrapper; + +/** + * A {@link ComponentWrapper} for getting and setting data on a {@link FoodProperties} component. + */ +@SuppressWarnings("UnstableApiUsage") +public class FoodWrapper extends ComponentWrapper { + + public FoodWrapper(ItemStack itemStack) { + super(itemStack); + } + + public FoodWrapper(ItemSource itemSource) { + super(itemSource); + } + + public FoodWrapper(FoodProperties component) { + super(component); + } + + public FoodWrapper(Builder builder) { + super(builder); + } + + @Override + public Valued getDataComponentType() { + return DataComponentTypes.FOOD; + } + + @Override + protected FoodProperties getComponent(ItemStack itemStack) { + FoodProperties food = itemStack.getData(DataComponentTypes.FOOD); + if (food != null) + return food; + return FoodProperties.food().build(); + } + + @Override + protected Builder getBuilder(ItemStack itemStack) { + FoodProperties food = itemStack.getData(DataComponentTypes.FOOD); + if (food != null) + return food.toBuilder(); + return FoodProperties.food(); + } + + @Override + protected void setComponent(ItemStack itemStack, FoodProperties component) { + itemStack.setData(DataComponentTypes.FOOD, component); + } + + @Override + protected Builder getBuilder(FoodProperties component) { + return component.toBuilder(); + } + + @Override + public FoodWrapper clone() { + FoodProperties base = getComponent(); + FoodWrapper clone = newWrapper(); + clone.editBuilder(builder -> { + builder.canAlwaysEat(base.canAlwaysEat()); + builder.nutrition(base.nutrition()); + builder.saturation(base.saturation()); + }); + return clone; + } + + @Override + public FoodProperties newComponent() { + return newBuilder().build(); + } + + @Override + public Builder newBuilder() { + return FoodProperties.food(); + } + + @Override + public FoodWrapper newWrapper() { + return newInstance(); + } + + public static FoodWrapper newInstance() { + return new FoodWrapper(FoodProperties.food().build()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/CondFoodCompAlwaysEat.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/CondFoodCompAlwaysEat.java new file mode 100644 index 00000000000..38256a96e14 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/CondFoodCompAlwaysEat.java @@ -0,0 +1,55 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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 org.skriptlang.skript.bukkit.itemcomponents.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Food Component - Can Always Be Eaten") +@Description(""" + Whether an item can be eaten when the player's hunger bar is full. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + if {_item} can not always be eaten: + allow {_item} to always be eaten + """) +@Example(""" + set {_component} to the food component of {_item} + if {_component} can be eaten when full: + prevent {_component} from being eaten when full + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class CondFoodCompAlwaysEat extends PropertyCondition implements FoodExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.CONDITION, + infoBuilder( + CondFoodCompAlwaysEat.class, + PropertyType.CAN, + "(always be eaten|be eaten when full)", + "foodcomponents" + ) + .supplier(CondFoodCompAlwaysEat::new) + .build() + ); + } + + @Override + public boolean check(FoodWrapper wrapper) { + return wrapper.getComponent().canAlwaysEat(); + } + + @Override + protected String getPropertyName() { + return "always be eaten"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/EffFoodCompAlwaysEat.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/EffFoodCompAlwaysEat.java new file mode 100644 index 00000000000..dc6a89ee788 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/EffFoodCompAlwaysEat.java @@ -0,0 +1,80 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Food Component - Always Be Eaten") +@Description(""" + Whether an item should be eaten when the player's hunger bar is full. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + if {_item} can not always be eaten: + allow {_item} to always be eaten + """) +@Example(""" + set {_component} to the food component of {_item} + if {_component} can be eaten when full: + prevent {_component} from being eaten when full + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class EffFoodCompAlwaysEat extends Effect implements FoodExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EFFECT, + SyntaxInfo.builder(EffFoodCompAlwaysEat.class) + .addPatterns( + "(allow|force) %foodcomponents% to (always be eaten|be eaten when full)", + "prevent %foodcomponents% from (always being eaten|being eaten when full)" + ) + .supplier(EffFoodCompAlwaysEat::new) + .build() + ); + } + + private boolean alwaysEat; + private Expression wrappers; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + alwaysEat = matchedPattern == 0; + //noinspection unchecked + wrappers = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + wrappers.stream(event).forEach(wrapper -> wrapper.editBuilder(builder -> { + builder.canAlwaysEat(alwaysEat); + })); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + if (alwaysEat) { + builder.append("allow", wrappers, "to always be eaten"); + } else { + builder.append("prevent", wrappers, "from always being eaten"); + } + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompNutrition.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompNutrition.java new file mode 100644 index 00000000000..a77edf8cd78 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompNutrition.java @@ -0,0 +1,85 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Food Component - Nutritional Value") +@Description(""" + The amount of food points to be restored when the item is eaten. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set the nutritional value of {_item} to 20") +@Example(""" + set {_component} to the food component of {_item} + add 30 to the nutritional value of {_component} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprFoodCompNutrition extends SimplePropertyExpression implements FoodExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprFoodCompNutrition.class, + Integer.class, + "[food] nutritional value", + "foodcomponents", + true + ) + .supplier(ExprFoodCompNutrition::new) + .build() + ); + } + + @Override + public @Nullable Integer convert(FoodWrapper wrapper) { + return wrapper.getComponent().nutrition(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, REMOVE, ADD -> CollectionUtils.array(Number.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + int provided = delta == null ? 0 : ((Number) delta[0]).intValue(); + + getExpr().stream(event).forEach(wrapper -> { + int current = wrapper.getComponent().nutrition(); + int newNutrition = switch (mode) { + case ADD -> Math2.fit(0, current + provided, Integer.MAX_VALUE); + case REMOVE -> Math2.fit(0, current - provided, Integer.MAX_VALUE); + default -> provided; + }; + wrapper.editBuilder(builder -> builder.nutrition(newNutrition)); + }); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return "nutritional value"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompSaturation.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompSaturation.java new file mode 100644 index 00000000000..c062d449898 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodCompSaturation.java @@ -0,0 +1,85 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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.Math2; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Food Component - Saturation Value") +@Description(""" + The amount of saturation to be restored when the item is eaten. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example("set the saturation value of {_item} to 20") +@Example(""" + set {_component} to the food component of {_item} + add 30 to the saturation value of {_component} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprFoodCompSaturation extends SimplePropertyExpression implements FoodExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprFoodCompSaturation.class, + Float.class, + "[food] saturation value", + "foodcomponents", + true + ) + .supplier(ExprFoodCompSaturation::new) + .build() + ); + } + + @Override + public @Nullable Float convert(FoodWrapper wrapper) { + return wrapper.getComponent().saturation(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, REMOVE, ADD -> CollectionUtils.array(Number.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + float provided = delta == null ? 0 : ((Number) delta[0]).floatValue(); + + getExpr().stream(event).forEach(wrapper -> { + float current = wrapper.getComponent().saturation(); + float newSaturation = switch (mode) { + case ADD -> Math2.fit(0, current + provided, Float.MAX_VALUE); + case REMOVE -> Math2.fit(0, current - provided, Float.MAX_VALUE); + default -> provided; + }; + wrapper.editBuilder(builder -> builder.saturation(newSaturation)); + }); + } + + @Override + public Class getReturnType() { + return Float.class; + } + + @Override + protected String getPropertyName() { + return "saturation value"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodComponent.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodComponent.java new file mode 100644 index 00000000000..1a0a4285f49 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprFoodComponent.java @@ -0,0 +1,125 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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.FoodProperties; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Food Component") +@Description(""" + The food component of an item. Any changes made to the food component will be present on the item. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_component} to the food component of {_item} + set the nutritional value of {_component} to 50 + """) +@Example("clear the food component of {_item}") +@Example("reset the food component of {_item}") +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprFoodComponent extends SimplePropertyExpression implements FoodExperimentalSyntax { + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + infoBuilder( + ExprFoodComponent.class, + FoodWrapper.class, + "food component[s]", + "slots/itemtypes", + false + ) + .supplier(ExprFoodComponent::new) + .build() + ); + } + + @Override + public @Nullable FoodWrapper 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 FoodWrapper(itemSource); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, DELETE, RESET -> CollectionUtils.array(FoodWrapper.class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + FoodProperties component = null; + if (delta != null) + component = ((FoodWrapper) 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, FoodProperties component) { + for (ItemData itemData : itemType) { + ItemStack dataStack = itemData.getStack(); + if (dataStack == null) + continue; + changeItemStack(dataStack, mode, component); + } + } + + public void changeSlot(Slot slot, ChangeMode mode, FoodProperties 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, FoodProperties component) { + switch (mode) { + case SET -> itemStack.setData(DataComponentTypes.FOOD, component); + case DELETE -> itemStack.unsetData(DataComponentTypes.FOOD); + case RESET -> itemStack.resetData(DataComponentTypes.FOOD); + } + return itemStack; + } + + @Override + public Class getReturnType() { + return FoodWrapper.class; + } + + @Override + protected String getPropertyName() { + return "food component"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprSecBlankFoodComp.java b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprSecBlankFoodComp.java new file mode 100644 index 00000000000..a3ad5d02762 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/itemcomponents/food/elements/ExprSecBlankFoodComp.java @@ -0,0 +1,112 @@ +package org.skriptlang.skript.bukkit.itemcomponents.food.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.food.FoodExperimentalSyntax; +import org.skriptlang.skript.bukkit.itemcomponents.food.FoodWrapper; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.List; + +@Name("Blank Food Component") +@Description(""" + Gets a blank food component. + NOTE: Food component elements are experimental. Thus, they are subject to change and may not work as intended. + """) +@Example(""" + set {_component} to a blank food component: + set the nutritional value to 30 + set the saturation value to 20 + allow event-food component to always be eaten + set the food component of {_item} to {_component} + """) +@RequiredPlugins("Minecraft 1.21.3+") +@Since("INSERT VERSION") +public class ExprSecBlankFoodComp extends SectionExpression implements FoodExperimentalSyntax { + + private static class BlankFoodSectionEvent extends Event { + + private final FoodWrapper wrapper; + + public BlankFoodSectionEvent(FoodWrapper wrapper) { + this.wrapper = wrapper; + } + + public FoodWrapper getWrapper() { + return wrapper; + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new IllegalStateException(); + } + } + + public static void register(SyntaxRegistry registry) { + registry.register( + SyntaxRegistry.EXPRESSION, + SyntaxInfo.Expression.builder(ExprSecBlankFoodComp.class, FoodWrapper.class) + .addPatterns("a (blank|empty) food component") + .supplier(ExprSecBlankFoodComp::new) + .build() + ); + EventValues.registerEventValue(BlankFoodSectionEvent.class, FoodWrapper.class, BlankFoodSectionEvent::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) { + trigger = SectionUtils.loadLinkedCode("blank food component", (beforeLoading, afterLoading) -> + loadCode(node, "blank food component", beforeLoading, afterLoading, BlankFoodSectionEvent.class) + ); + return trigger != null; + } + return true; + } + + @Override + protected FoodWrapper @Nullable [] get(Event event) { + FoodWrapper wrapper = FoodWrapper.newInstance(); + if (trigger != null) { + BlankFoodSectionEvent sectionEvent = new BlankFoodSectionEvent(wrapper); + Variables.withLocalVariables(event, sectionEvent, () -> TriggerItem.walk(trigger, sectionEvent)); + } + return new FoodWrapper[] {wrapper}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return FoodWrapper.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a blank food component"; + } + +} diff --git a/src/test/skript/tests/general/FoodComponents.sk b/src/test/skript/tests/general/FoodComponents.sk new file mode 100644 index 00000000000..b34624a84f7 --- /dev/null +++ b/src/test/skript/tests/general/FoodComponents.sk @@ -0,0 +1,76 @@ +options: + nutrition: 20 + saturation: 30 + +using food components + +test "food components - new" when running minecraft "1.21.3": + set {_component} to a blank food component + set the nutritional value of {_component} to {@nutrition} + assert the nutritional value of {_component} is {@nutrition} with "Nutritional value of component was not set" + set the saturation value of {_component} to {@saturation} + assert the saturation value of {_component} is {@saturation} with "Saturation value of component was not set" + allow {_component} to always be eaten + assert {_component} can always be eaten with "Component should always be eaten" + prevent {_component} from always being eaten + assert {_component} can not always be eaten with "Component should not always be eaten" + +test "food components - itemtype" when running minecraft "1.21.3": + set {_item} to a leather helmet (item type) + set the nutritional value of {_item} to {@nutrition} + assert the nutritional value of {_item} is {@nutrition} with "Nutritional value of itemtype was not set" + set the saturation value of {_item} to {@saturation} + assert the saturation value of {_item} is {@saturation} with "Saturation value of itemtype was not set" + allow {_item} to always be eaten + assert {_item} can always be eaten with "Itemtype should always be eaten" + prevent {_item} from always being eaten + assert {_item} can not always be eaten with "Itemtype should not always be eaten" + +test "food components - itemstack" when running minecraft "1.21.3": + set {_item} to an iron helmet (item stack) + set the nutritional value of {_item} to {@nutrition} + assert the nutritional value of {_item} is {@nutrition} with "Nutritional value of itemstack was not set" + set the saturation value of {_item} to {@saturation} + assert the saturation value of {_item} is {@saturation} with "Saturation value of itemstack was not set" + allow {_item} to always be eaten + assert {_item} can always be eaten with "Itemstack should always be eaten" + prevent {_item} from always being eaten + assert {_item} can not always be eaten with "Itemstack should not always be eaten" + +test "food 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 nutritional value of (slot 1 of {_gui}) to {@nutrition} + assert the nutritional value of (slot 1 of {_gui}) is {@nutrition} with "Nutritional value of slot was not set" + set the saturation value of (slot 1 of {_gui}) to {@saturation} + assert the saturation value of (slot 1 of {_gui}) is {@saturation} with "Saturation value of slot was not set" + allow (slot 1 of {_gui}) to always be eaten + assert (slot 1 of {_gui}) can always be eaten with "Slot should always be eaten" + prevent (slot 1 of {_gui}) from always being eaten + assert (slot 1 of {_gui}) can not always be eaten with "Slot should not always be eaten" + +test "food components - copy" when running minecraft "1.21.3": + set {_component} to a blank food component + set the nutritional value of {_component} to {@nutrition} + assert the nutritional value of {_component} is {@nutrition} with "Nutritional value of component was not set" + set the saturation value of {_component} to {@saturation} + assert the saturation value of {_component} is {@saturation} with "Saturation value of component was not set" + allow {_component} to always be eaten + assert {_component} can always be eaten with "Component should always be eaten" + prevent {_component} from always being eaten + assert {_component} can not always be eaten with "Component should not always be eaten" + + set {_original} to {_component} + set {_copy} to an item component copy of {_original} + set the nutritional value of {_copy} to 40 + assert the nutritional value of {_copy} is 40 with "Nutritional value of copy was not set" + assert the nutritional value of {_original} is {@nutrition} with "Nutritional value of original should not have changed" + set the saturation value of {_copy} to 50 + assert the saturation value of {_copy} is 50 with "Saturation value of copy was not set" + assert the saturation value of {_original} is {@saturation} with "Saturation value of original should not have changed" + allow {_copy} to always be eaten + assert {_copy} can always be eaten with "Copy should always be eaten" + assert {_original} can not always be eaten with "Original should still not always be eaten" + prevent {_copy} from always being eaten + assert {_copy} can not always be eaten with "Copy should not always be eaten"