diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 89c320bc0db..81ee8bbb37d 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -77,6 +77,7 @@ import org.skriptlang.skript.bukkit.loottables.LootTableModule; import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys; import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; +import org.skriptlang.skript.bukkit.recipes.RecipeModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; @@ -555,6 +556,7 @@ public void onEnable() { TagModule.load(); FurnaceModule.load(); LootTableModule.load(); + RecipeModule.load(); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/CreateRecipeEvent.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/CreateRecipeEvent.java new file mode 100644 index 00000000000..e4383bd3270 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/CreateRecipeEvent.java @@ -0,0 +1,172 @@ +package org.skriptlang.skript.bukkit.recipes; + +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.*; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableBlastingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableCampfireRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableFurnaceRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableSmokingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe.MutableShapedRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe.MutableShapelessRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTransformRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTrimRecipe; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; + +/** + * Event class used with ExprSecCreateRecipe to allow the creation of MutableRecipe + * Allows elements used to determine if the recipe being created is of the required type + */ +public class CreateRecipeEvent extends Event { + private boolean errorInSection = false; + private final RecipeType recipeType; + private final MutableRecipe mutableRecipe; + + public CreateRecipeEvent(NamespacedKey key, RecipeType recipeType) { + this.recipeType = recipeType; + this.mutableRecipe = recipeType.createMutableRecipe(key); + } + + public void setErrorInSection() { + this.errorInSection = true; + } + + public boolean getErrorInSection() { + return errorInSection; + } + + public RecipeType getRecipeType() { + return recipeType; + } + + public MutableRecipe getMutableRecipe() { + return mutableRecipe; + } + + /** + * Event correlating to creating a {@link MutableCraftingRecipe} + */ + public static class CraftingRecipeEvent extends CreateRecipeEvent { + + public CraftingRecipeEvent(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + }; + + /** + * Event correlating to creating a {@link MutableShapedRecipe} + */ + public static class ShapedRecipeEvent extends CraftingRecipeEvent { + public ShapedRecipeEvent(NamespacedKey key) { + super(key, RecipeType.SHAPED); + }; + } + + /** + * Event correlating to creating a {@link MutableShapelessRecipe} + */ + public static class ShapelessRecipeEvent extends CraftingRecipeEvent { + public ShapelessRecipeEvent(NamespacedKey key) { + super(key, RecipeType.SHAPELESS); + }; + } + } + + /** + * Event correlating to creating a {@link MutableCookingRecipe} + */ + public static class CookingRecipeEvent extends CreateRecipeEvent { + + public CookingRecipeEvent(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + }; + + /** + * Event correlating to creating a {@link MutableBlastingRecipe} + */ + public static class BlastingRecipeEvent extends CookingRecipeEvent { + public BlastingRecipeEvent(NamespacedKey key) { + super(key, RecipeType.BLASTING); + } + } + + /** + * Event correlating to creating a {@link MutableCampfireRecipe} + */ + public static class CampfireRecipeEvent extends CookingRecipeEvent { + public CampfireRecipeEvent(NamespacedKey key) { + super(key, RecipeType.CAMPFIRE); + } + } + + /** + * Event correlating to creating a {@link MutableFurnaceRecipe} + */ + public static class FurnaceRecipeEvent extends CookingRecipeEvent { + public FurnaceRecipeEvent(NamespacedKey key) { + super(key, RecipeType.FURNACE); + } + } + + /** + * Event correlating to creating a {@link MutableSmokingRecipe} + */ + public static class SmokingRecipeEvent extends CookingRecipeEvent { + public SmokingRecipeEvent(NamespacedKey key) { + super(key, RecipeType.SMOKING); + } + } + } + + /** + * Event correlating to creating a {@link MutableSmithingRecipe} + */ + public static class SmithingRecipeEvent extends CreateRecipeEvent { + public SmithingRecipeEvent(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + } + + /** + * Event correlating to creating a {@link MutableSmithingTransformRecipe} + */ + public static class SmithingTransformRecipeEvent extends SmithingRecipeEvent { + public SmithingTransformRecipeEvent(NamespacedKey key) { + super(key, RecipeType.SMITHING_TRANSFORM); + } + } + + /** + * Event correlating to creating a {@link MutableSmithingTrimRecipe} + */ + public static class SmithingTrimRecipeEvent extends SmithingRecipeEvent { + public SmithingTrimRecipeEvent(NamespacedKey key) { + super(key, RecipeType.SMITHING_TRIM); + } + } + } + + /** + * Event correlating to creating a {@link MutableStonecuttingRecipe} + */ + public static class StonecuttingRecipeEvent extends CreateRecipeEvent { + public StonecuttingRecipeEvent(NamespacedKey key) { + super(key, RecipeType.STONECUTTING); + } + } + + /** + * Event correlating to creating a {@link MutableTransmuteRecipe} + */ + public static class TransmuteRecipeEvent extends CreateRecipeEvent { + public TransmuteRecipeEvent(NamespacedKey key) { + super(key, RecipeType.TRANSMUTE); + } + } + + @Override + public @NotNull HandlerList getHandlers() { + throw new IllegalStateException(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/MutableRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/MutableRecipe.java new file mode 100644 index 00000000000..1ce451a6353 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/MutableRecipe.java @@ -0,0 +1,496 @@ +package org.skriptlang.skript.bukkit.recipes; + +import ch.njol.skript.Skript; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.*; +import org.bukkit.inventory.recipe.CookingBookCategory; +import org.bukkit.inventory.recipe.CraftingBookCategory; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mutable Recipe class allowing data to be set before creation of a recipe. + */ +public abstract class MutableRecipe implements Recipe { + + private final RecipeType recipeType; + private final NamespacedKey key; + private ItemStack result = new ItemStack(Material.AIR); + private final List errors = new ArrayList<>(); + + public MutableRecipe(NamespacedKey key, RecipeType recipeType) { + this.key = key; + this.recipeType = recipeType; + } + + public void setResult(ItemStack result) { + this.result = result; + } + + @Override + public @NotNull ItemStack getResult() { + return result; + } + + public NamespacedKey getKey() { + return this.key; + } + + public RecipeType getRecipeType() { + return recipeType; + } + + public void addError(String error) { + errors.add(error); + } + + public List getErrors() { + return errors; + } + + public abstract Recipe create(); + + public abstract static class MutableCraftingRecipe extends MutableRecipe implements MutableGroupRecipe { + private final RecipeChoice[] ingredients = new RecipeChoice[9]; + private String group; + private CraftingBookCategory category; + + public MutableCraftingRecipe(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + } + + @Override + public abstract Recipe create(); + + public void setIngredients(int placement, RecipeChoice item) { + ingredients[placement] = item; + } + + public void setGroup(String group) { + this.group = group; + } + + public void setCategory(CraftingBookCategory category) { + this.category = category; + } + + public RecipeChoice[] getIngredients() { + return ingredients; + } + + public String getGroup() { + return this.group; + } + + public CraftingBookCategory getCategory() { + return this.category; + } + + public static class MutableShapedRecipe extends MutableCraftingRecipe { + + public MutableShapedRecipe(NamespacedKey key) { + super(key, RecipeType.SHAPED); + } + + private static final char[] characters = new char[]{'a','b','c','d','e','f','g','h','i'}; + + @Override + public ShapedRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + RecipeChoice[] ingredients = getIngredients(); + if (ingredients.length == 0) { + addError("You must have at least 1 ingredient when creating a '" + getRecipeType() + "' recipe."); + return null; + } + ShapedRecipe shapedRecipe = new ShapedRecipe(getKey(), getResult()); + shapedRecipe.shape("abc","def","ghi"); + for (int i = 0; i < ingredients.length; i++) { + RecipeChoice thisChoice = ingredients[i]; + if (thisChoice != null) + shapedRecipe.setIngredient(characters[i], thisChoice); + } + if (getGroup() != null) + shapedRecipe.setGroup(getGroup()); + if (getCategory() != null) + shapedRecipe.setCategory(getCategory()); + return shapedRecipe; + } + + } + + public static class MutableShapelessRecipe extends MutableCraftingRecipe { + + public MutableShapelessRecipe(NamespacedKey key) { + super(key, RecipeType.SHAPELESS); + } + + @Override + public ShapelessRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + RecipeChoice[] ingredients = getIngredients(); + if (ingredients.length == 0) { + addError("You must have at least 1 ingredient when creating a '" + getRecipeType() + "' recipe."); + return null; + } + ShapelessRecipe shapelessRecipe = new ShapelessRecipe(getKey(), getResult()); + for (RecipeChoice thisChoice : ingredients) { + if (thisChoice != null) + shapelessRecipe.addIngredient(thisChoice); + } + if (getGroup() != null) + shapelessRecipe.setGroup(getGroup()); + if (getCategory() != null) + shapelessRecipe.setCategory(getCategory()); + return shapelessRecipe; + } + } + } + + public static class MutableCookingRecipe extends MutableRecipe implements MutableGroupRecipe { + private RecipeChoice input; + private String group; + private CookingBookCategory category; + private int cookingTime = 10; + private float experience = 0; + + public MutableCookingRecipe(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + } + + public void setInput(RecipeChoice input) { + this.input = input; + } + + public void setGroup(String group) { + this.group = group; + } + + public void setCategory(CookingBookCategory category) { + this.category = category; + } + + public void setCookingTime(int cookingTime) { + this.cookingTime = cookingTime; + } + + public void setExperience(float experience) { + this.experience = experience; + } + + public RecipeChoice getInput() { + return input; + } + + public String getGroup() { + return group; + } + + public CookingBookCategory getCategory() { + return category; + } + + public int getCookingTime() { + return cookingTime; + } + + public float getExperience() { + return experience; + } + + @Override + public CookingRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + if (getInput() == null) { + addError("You must provide an input item when creating a " + getRecipeType() + " recipe."); + return null; + } + var recipe = switch (getRecipeType()) { + case BLASTING -> new BlastingRecipe(getKey(), getResult(), getInput(), getExperience(), getCookingTime()); + case FURNACE -> new FurnaceRecipe(getKey(), getResult(), getInput(), getExperience(), getCookingTime()); + case SMOKING -> new SmokingRecipe(getKey(), getResult(), getInput(), getExperience(), getCookingTime()); + case CAMPFIRE -> new CampfireRecipe(getKey(), getResult(), getInput(), getExperience(), getCookingTime()); + default -> throw new IllegalStateException("Unexpected value: " + getRecipeType()); + }; + if (getGroup() != null) + recipe.setGroup(getGroup()); + if (getCategory() != null) + recipe.setCategory(getCategory()); + return recipe; + } + + public static class MutableBlastingRecipe extends MutableCookingRecipe { + public MutableBlastingRecipe(NamespacedKey key) { + super(key, RecipeType.BLASTING); + } + + @Override + public BlastingRecipe create() { + return (BlastingRecipe) super.create(); + } + } + + public static class MutableFurnaceRecipe extends MutableCookingRecipe { + public MutableFurnaceRecipe(NamespacedKey key) { + super(key, RecipeType.FURNACE); + } + + @Override + public FurnaceRecipe create() { + return (FurnaceRecipe) super.create(); + } + } + + public static class MutableSmokingRecipe extends MutableCookingRecipe { + public MutableSmokingRecipe(NamespacedKey key) { + super(key, RecipeType.SMOKING); + } + + @Override + public SmokingRecipe create() { + return (SmokingRecipe) super.create(); + } + } + + public static class MutableCampfireRecipe extends MutableCookingRecipe { + public MutableCampfireRecipe(NamespacedKey key) { + super(key, RecipeType.CAMPFIRE); + } + + @Override + public CampfireRecipe create() { + return (CampfireRecipe) super.create(); + } + } + + } + + public static class MutableSmithingRecipe extends MutableRecipe { + + private static final boolean SUPPORTS_COPY_NBT = Skript.methodExists(SmithingRecipe.class, "willCopyNbt"); + private static final boolean SUPPORTS_COPY_DATA = Skript.methodExists(SmithingRecipe.class, "willCopyDataComponents"); + private static final boolean RUNNING_1_20_0 = Skript.isRunningMinecraft(1, 20, 0); + + private RecipeChoice base; + private RecipeChoice template; + private RecipeChoice addition; + private Boolean copyData = null; + + public MutableSmithingRecipe(NamespacedKey key, RecipeType recipeType) { + super(key, recipeType); + } + + public void setBase(RecipeChoice base) { + this.base = base; + } + + public void setTemplate(RecipeChoice template) { + this.template = template; + } + + public void setAddition(RecipeChoice addition) { + this.addition = addition; + } + + public void setCopyData(Boolean copyData) { + this.copyData = copyData; + } + + public RecipeChoice getBase() { + return base; + } + + public RecipeChoice getTemplate() { + return template; + } + + public RecipeChoice getAddition() { + return addition; + } + + + public Boolean willCopyData() { + return copyData; + } + + @Override + public SmithingRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + RecipeChoice base = getBase(), addition = getAddition(); + if (base == null) { + addError("You must provide a base item when creating a smithing recipe."); + return null; + } + if (addition == null) { + addError("You must provide an additional item when creating a smithing recipe."); + return null; + } + if ((SUPPORTS_COPY_NBT || SUPPORTS_COPY_DATA) && copyData != null) + return new SmithingRecipe(getKey(), getResult(), getBase(), getAddition(), copyData); + return new SmithingRecipe(getKey(), getResult(), getBase(), getAddition()); + } + + public static class MutableSmithingTransformRecipe extends MutableSmithingRecipe { + public MutableSmithingTransformRecipe(NamespacedKey key) { + super(key, RecipeType.SMITHING_TRANSFORM); + } + + @Override + public SmithingTransformRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + RecipeChoice base = getBase(), template = getTemplate(), addition = getAddition(); + if (base == null) { + addError("You must provide a base item when creating a smithing recipe."); + return null; + } + if (addition == null) { + addError("You must provide an additional item when creating a smithing recipe."); + return null; + } + if (template == null) { + addError("You must provide a template item when creating a smithing recipe."); + return null; + } + if (RUNNING_1_20_0 && (SUPPORTS_COPY_NBT || SUPPORTS_COPY_DATA) && willCopyData() != null) + return new SmithingTransformRecipe(getKey(), getResult(), getTemplate(), getBase(), getAddition(), willCopyData()); + return new SmithingTransformRecipe(getKey(), getResult(), getTemplate(), getBase(), getAddition()); + } + } + + public static class MutableSmithingTrimRecipe extends MutableSmithingRecipe { + public MutableSmithingTrimRecipe(NamespacedKey key) { + super(key, RecipeType.SMITHING_TRIM); + } + + @Override + public SmithingTrimRecipe create() { + RecipeChoice base = getBase(), template = getTemplate(), addition = getAddition(); + if (base == null) { + addError("You must provide a base item when creating a smithing recipe."); + return null; + } + if (addition == null) { + addError("You must provide an additional item when creating a smithing recipe."); + return null; + } + if (template == null) { + addError("You must provide a template item when creating a smithing recipe."); + return null; + } + if (RUNNING_1_20_0 && (SUPPORTS_COPY_NBT || SUPPORTS_COPY_DATA) && willCopyData() != null) + return new SmithingTrimRecipe(getKey(), getTemplate(), getBase(), getAddition(), willCopyData()); + return new SmithingTrimRecipe(getKey(), getTemplate(), getBase(), getAddition()); + } + } + } + + public static class MutableStonecuttingRecipe extends MutableRecipe implements MutableGroupRecipe { + private RecipeChoice input; + private String group; + + public MutableStonecuttingRecipe(NamespacedKey key) { + super(key, RecipeType.STONECUTTING); + } + + public void setInput(RecipeChoice input) { + this.input = input; + } + + public RecipeChoice getInput() { + return input; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getGroup() { + return group; + } + + @Override + public StonecuttingRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + if (getInput() == null) { + addError("You must provide an input item when creating a stonecutting recipe."); + return null; + } + StonecuttingRecipe recipe = new StonecuttingRecipe(getKey(), getResult(), input); + if (group != null && !group.isEmpty()) + recipe.setGroup(group); + return recipe; + } + } + + public static class MutableTransmuteRecipe extends MutableRecipe { + + private RecipeChoice input, material; + + public MutableTransmuteRecipe(NamespacedKey key) { + super(key, RecipeType.TRANSMUTE); + } + + public void setInput(RecipeChoice input) { + this.input = input; + } + + public void setMaterial(RecipeChoice material) { + this.material = material; + } + + public RecipeChoice getInput() { + return input; + } + + public RecipeChoice getMaterial() { + return material; + } + + @Override + public TransmuteRecipe create() { + if (getResult().getType() == Material.AIR) { + addError("You must provide a result item when creating a recipe"); + return null; + } + if (getInput() == null) { + addError("You must provide an input item when creating a transmute recipe."); + return null; + } + if (getMaterial() == null) { + addError("You must provide a transmute item when creating a transmute recipe."); + return null; + } + return new TransmuteRecipe(getKey(), getResult().getType(), input, material); + } + } + + /** + * Interface used to have mutable recipes, that are able to have groups, to be combined + */ + public interface MutableGroupRecipe { + String getGroup(); + void setGroup(String object); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeCategory.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeCategory.java new file mode 100644 index 00000000000..139655c3e07 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeCategory.java @@ -0,0 +1,38 @@ +package org.skriptlang.skript.bukkit.recipes; + +import org.bukkit.inventory.recipe.CookingBookCategory; +import org.bukkit.inventory.recipe.CraftingBookCategory; + +/** + * Enum for converting Bukkit recipe categories into one usable by Skript. + */ +public enum RecipeCategory { + + CRAFTING_BUILDING(CraftingBookCategory.BUILDING), + CRAFTING_EQUIPMENT(CraftingBookCategory.EQUIPMENT), + CRAFTING_MISC(CraftingBookCategory.MISC), + CRAFTING_REDSTONE(CraftingBookCategory.REDSTONE), + + COOKING_BLOCKS(CookingBookCategory.BLOCKS), + COOKING_FOOD(CookingBookCategory.FOOD), + COOKING_MISC(CookingBookCategory.MISC); + + private final Enum category; + + RecipeCategory(Enum category) { + this.category = category; + } + + public Enum getCategory() { + return category; + } + + public static RecipeCategory convertBukkitToSkript(Enum category) { + for (RecipeCategory recipeCategory : RecipeCategory.values()) { + if (recipeCategory.category.equals(category)) + return recipeCategory; + } + return null; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeModule.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeModule.java new file mode 100644 index 00000000000..652dec41d1b --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeModule.java @@ -0,0 +1,72 @@ +package org.skriptlang.skript.bukkit.recipes; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.EventValues; +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.player.PlayerRecipeDiscoverEvent; +import org.bukkit.inventory.Recipe; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; +import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; + +import java.io.IOException; + +public class RecipeModule { + + public static void load() throws IOException { + Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.recipes", "elements"); + + // --- CLASSES --- // + + Classes.registerClass(new ClassInfo<>(Recipe.class, "recipe") + .user("recipes?") + .name("Recipe") + .description("Represents a recipe.") + .usage("recipes") + .since("INSERT VERSION") + .defaultExpression(new EventValueExpression<>(Recipe.class))); + + Classes.registerClass(new EnumClassInfo<>(RecipeCategory.class, "recipecategory", "recipe categories") + .user("recipe ?categor(y|ies)") + .name("Recipe Category") + .description("Represents the different categories of recipes.") + .since("INSERT VERSION") + ); + + Classes.registerClass(new EnumClassInfo<>(RecipeType.class, "recipetype", "recipe types", false) + .user("recipe ?types?") + .name("Recipe Type") + .description("Represents the type of a recipe.") + .since("INSERT VERSION") + ); + + // --- COMPARATORS --- // + + Comparators.registerComparator(RecipeType.class, RecipeType.class, (type1, type2) -> { + if (type1.getRecipeClass() != null && type2.getRecipeClass() != null) + return Relation.get(type2.getRecipeClass().isAssignableFrom(type1.getRecipeClass())); + return Relation.NOT_EQUAL; + }); + + // --- EVENT VALUES --- // + + //PrepareItemCraftEvent + EventValues.registerEventValue(PrepareItemCraftEvent.class, Recipe.class, PrepareItemCraftEvent::getRecipe); + + //CraftItemEvent + EventValues.registerEventValue(CraftItemEvent.class, Recipe.class, CraftItemEvent::getRecipe); + + // PlayerRecipeDiscoverEvent + EventValues.registerEventValue(PlayerRecipeDiscoverEvent.class, Recipe.class, + event -> Bukkit.getRecipe(event.getRecipe()) + ); + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeUtils.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeUtils.java new file mode 100644 index 00000000000..29157a917db --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/RecipeUtils.java @@ -0,0 +1,305 @@ +package org.skriptlang.skript.bukkit.recipes; + +import ch.njol.skript.Skript; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.*; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CookingRecipeEvent.BlastingRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CookingRecipeEvent.CampfireRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CookingRecipeEvent.FurnaceRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CookingRecipeEvent.SmokingRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CraftingRecipeEvent.ShapedRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CraftingRecipeEvent.ShapelessRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTransformRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTrimRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableBlastingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableCampfireRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableFurnaceRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe.MutableSmokingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe.MutableShapedRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe.MutableShapelessRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTransformRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTrimRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableStonecuttingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableTransmuteRecipe; +import org.skriptlang.skript.bukkit.recipes.elements.ExprSecCreateRecipe; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utils used for getting data from {@link RecipeType} + */ +public class RecipeUtils { + + /** + * Enum for storing all types of Recipes (until Bukkit makes an enum or registry) + */ + public enum RecipeType { + SHAPED(ShapedRecipe.class, ShapedRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new ShapedRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableShapedRecipe(key); + } + + }, + SHAPELESS(ShapelessRecipe.class, ShapelessRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new ShapelessRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableShapelessRecipe(key); + } + + }, + // TODO: Remove method and apply class directly when MC version is raised to 1.21.2+ + TRANSMUTE(getTransmuteRecipeClass(), TransmuteRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new TransmuteRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableTransmuteRecipe(key); + } + + }, + // TODO: Remove method and apply class directly when MC version is raised to 1.20.1+ + // Having 'CRAFTING' under the subclasses allows for proper ExprRecipeType + CRAFTING(getCraftingRecipeClass(), CraftingRecipeEvent.class) { + // A 'CraftingRecipeEvent' should never be created + @Override + public @Nullable CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return null; + } + // A 'MutableCraftingRecipe' should never be created + @Override + public @Nullable MutableRecipe createMutableRecipe(NamespacedKey key) { + return null; + } + + }, + BLASTING(BlastingRecipe.class, BlastingRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new BlastingRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableBlastingRecipe(key); + } + + }, + FURNACE(FurnaceRecipe.class, FurnaceRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new FurnaceRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableFurnaceRecipe(key); + } + + }, + CAMPFIRE(CampfireRecipe.class, CampfireRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new CampfireRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableCampfireRecipe(key); + } + + }, + SMOKING(SmokingRecipe.class, SmokingRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new SmokingRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableSmokingRecipe(key); + } + + }, + // Having 'COOKING' under the subclasses allows for proper ExprRecipeType + COOKING(CookingRecipe.class, CookingRecipeEvent.class) { + // A 'CookingRecipeEvent' should never be created + @Override + public @Nullable CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return null; + } + // A 'MutableCookingRecipe' should never be created + @Override + public @Nullable MutableRecipe createMutableRecipe(NamespacedKey key) { + return null; + } + + }, + SMITHING_TRANSFORM(SmithingTransformRecipe.class, SmithingTransformRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new SmithingTransformRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableSmithingTransformRecipe(key); + } + + }, + SMITHING_TRIM(SmithingTrimRecipe.class, SmithingTrimRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new SmithingTrimRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableSmithingTrimRecipe(key); + } + + }, + // Having 'SMITHING' under the subclasses allows for proper ExprRecipeType + SMITHING(SmithingRecipe.class, SmithingRecipeEvent.class) { + // TODO: return null after minimum support version is raised to 1.20+ + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new SmithingRecipeEvent(key, this); + } + // TODO: return null after minimum support version is raised to 1.20+ + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableSmithingRecipe(key, this); + } + + }, + STONECUTTING(StonecuttingRecipe.class, StonecuttingRecipeEvent.class) { + @Override + public CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return new StonecuttingRecipeEvent(key); + } + + @Override + public MutableRecipe createMutableRecipe(NamespacedKey key) { + return new MutableStonecuttingRecipe(key); + } + }, + COMPLEX(ComplexRecipe.class, null) { + // A 'CompleRecipeEvent' does not exist + @Override + public @Nullable CreateRecipeEvent createRecipeEvent(NamespacedKey key) { + return null; + } + // A 'MutableComplexRecipe' does not exist + @Override + public @Nullable MutableRecipe createMutableRecipe(NamespacedKey key) { + return null; + } + }; + + /** + * Create a {@link CreateRecipeEvent} used for {@link ExprSecCreateRecipe} + * @param key The key to create a recipe + * @return + */ + public abstract @Nullable CreateRecipeEvent createRecipeEvent(NamespacedKey key); + + /** + * Create a {@link MutableRecipe} designed to set data before final creation. + * Used for {@link ExprSecCreateRecipe}. + * @param key + * @return + */ + public abstract @Nullable MutableRecipe createMutableRecipe(NamespacedKey key); + + private final @Nullable Class recipeClass; + private final @Nullable Class eventClass; + + RecipeType(@Nullable Class recipeClass, @Nullable Class eventClass) { + this.recipeClass = recipeClass; + this.eventClass = eventClass; + } + + /** + * Gets the Bukkit recipe type class. + * @return Bukkit recipe class + */ + public @Nullable Class getRecipeClass() { + return recipeClass; + } + + /** + * Gets the custom event used when creating a new recipe. + * @return Custom event class + */ + public @Nullable Class getEventClass() { + return eventClass; + } + + // Due to 1.19 not having 'CraftingRecipe.class' + private static @Nullable Class getCraftingRecipeClass() { + if (Skript.classExists("org.bukkit.inventory.CraftingRecipe")) + return CraftingRecipe.class; + return null; + } + + private static @Nullable Class getTransmuteRecipeClass() { + if (Skript.classExists("org.bukkit.inventory.TransmuteRecipe")) + return TransmuteRecipe.class; + return null; + } + + } + + private static final Map, RecipeType> recipeClassConverter = new HashMap<>(); + + static { + for (RecipeType recipeType : RecipeType.values()) { + if (recipeType.recipeClass != null) + recipeClassConverter.put(recipeType.recipeClass, recipeType); + } + } + + /** + * Gets {@link RecipeType} from provided recipe class. + * + * Note: MC versions below 1.20 converts Smithing Transform and Smithing Trim Recipes to a Smithing Recipe, causing the returned RecipeType to be Smithing. + * @param providedClass Bukkit recipe class + * @return Recipe Type + */ + public static @Nullable RecipeType getRecipeType(@NotNull Class providedClass) { + RecipeType recipeType = recipeClassConverter.get(providedClass); + if (recipeType == null) + recipeType = recipeClassConverter.get(providedClass.getSuperclass()); + return recipeType; + } + + /** + * Gets {@link RecipeType} from provided recipe. + * @param providedRecipe Recipe + * @return Recipe Type + */ + public static @Nullable RecipeType getRecipeType(@NotNull Recipe providedRecipe) { + return getRecipeType(providedRecipe.getClass()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondDiscoveredRecipes.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondDiscoveredRecipes.java new file mode 100644 index 00000000000..28e6360ff6f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondDiscoveredRecipes.java @@ -0,0 +1,67 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.Keyed; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; + +@Name("Has Discovered Recipe") +@Description("Checks whether the players have discovered a recipe.") +@Examples({ + "if player has discovered recipe \"custom_recipe\":", + "\tgive player 1 diamond", + "", + "if all players have not discovered recipe \"custom_recipe\":", + "\tkill all players", +}) +@Since("INSERT VERSION") +public class CondDiscoveredRecipes extends Condition { + + static { + Skript.registerCondition(CondDiscoveredRecipes.class, + "%players% (has|have) (discovered|unlocked) [the] [recipe[s]] %recipes%", + "%players% (hasn't|has not|haven't|have not) (discovered|unlocked) [the] [recipe[s]] %recipes%"); + } + + private Expression exprPlayer; + private Expression exprRecipe; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + exprPlayer = (Expression) exprs[0]; + //noinspection unchecked + exprRecipe = (Expression) exprs[1]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + Recipe[] recipes = exprRecipe.getArray(event); + return exprPlayer.check(event, player -> + SimpleExpression.check(recipes, recipe -> { + assert recipe instanceof Keyed; + Keyed recipeKey = (Keyed) recipe; + return player.hasDiscoveredRecipe(recipeKey.getKey()); + }, isNegated(), exprRecipe.getAnd()) + ); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return exprPlayer.toString(event, debug) + (isNegated() ? " have not" : " have") + " found recipes " + exprRecipe.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondSmithingCopy.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondSmithingCopy.java new file mode 100644 index 00000000000..3369cbe36c1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/CondSmithingCopy.java @@ -0,0 +1,89 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.SmithingRecipe; +import org.bukkit.inventory.SmithingTransformRecipe; +import org.bukkit.inventory.SmithingTrimRecipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTransformRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTrimRecipe; + +@Name("Recipe Allows Data Copy") +@Description({ + "Checks whether the recipe allows copying data from the base item to the result item of the recipe.", + "", + "This condition can only be used with smithing recipes on PaperMC version 1.19", + "This condition can be used with smithing transform and smithing trim recipes on PaperMC version 1.20.0+", +}) +@Examples({ + "loop the server's recipes:", + "\tif all:", + "\t\tthe recipe type of loop-value is a smithing recipe", + "\t\tthe loop-value allows data copying", + "\tthen:", + "\t\tbroadcast loop-value" +}) +@RequiredPlugins("Paper") +@Since("INSERT VERSION") +public class CondSmithingCopy extends Condition { + + private static final boolean SUPPORTS_COPY_NBT = Skript.methodExists(SmithingRecipe.class, "willCopyNbt"); + private static final boolean SUPPORTS_COPY_DATA = Skript.methodExists(SmithingRecipe.class, "willCopyDataComponents"); + private static final boolean RUNNING_1_20_0 = Skript.isRunningMinecraft(1, 20, 0); + + static { + if (SUPPORTS_COPY_NBT || SUPPORTS_COPY_DATA) { + Skript.registerCondition(CondSmithingCopy.class, ConditionType.PROPERTY, + "[the] %recipes% allow[s] [item] data (copying|transferring)", + "[the] %recipes% allow[s] [the] [item] data to be (copied|transferred)", + "[the] %recipes% (don't|do not|doesn't|does not) allow[s] [item] data (copying|transferring)", + "[the] %recipes% (don't|do not|doesn't|does not) allow[s] [the] [item] data to be (copied|transferred)"); + } + } + + private Expression exprRecipe; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + exprRecipe = (Expression) exprs[0]; + setNegated(matchedPattern >= 2); + return true; + } + + @Override + public boolean check(Event event) { + return exprRecipe.check(event, recipe -> { + if (recipe instanceof MutableSmithingRecipe mutableRecipe) { + if (!RUNNING_1_20_0 && (mutableRecipe instanceof MutableSmithingTransformRecipe || mutableRecipe instanceof MutableSmithingTrimRecipe)) { + return isNegated(); + } + return mutableRecipe.willCopyData(); + } else if (recipe instanceof SmithingRecipe smithingRecipe) { + if (!RUNNING_1_20_0 && (smithingRecipe instanceof SmithingTransformRecipe || smithingRecipe instanceof SmithingTrimRecipe)) { + return isNegated(); + } + if (SUPPORTS_COPY_DATA) { + return smithingRecipe.willCopyDataComponents(); + } else if (SUPPORTS_COPY_NBT) { + return smithingRecipe.willCopyNbt(); + } + } + return isNegated(); + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return exprRecipe.toString(event, debug) + (isNegated() ? "does not" : "") + " allow data copying"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffDiscoverRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffDiscoverRecipe.java new file mode 100644 index 00000000000..bc901c8e5b0 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffDiscoverRecipe.java @@ -0,0 +1,83 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +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.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; + +@Name("Discover Recipe") +@Description("Makes the specified players discover or forget the recipes.") +@Examples({ + "make player discover recipe \"my_recipe\"", + "make player undiscover recipe \"my_recipe\"", + "unlock recipe \"my_recipe\" for all players", + "lock recipe \"my_recipe\" for all players" +}) +@Since("INSERT VERSION") +public class EffDiscoverRecipe extends Effect { + + static { + Skript.registerEffect(EffDiscoverRecipe.class, + "make %players% (discover|unlock) [the] [recipe[s]] %recipes%", + "make %players% forget [the] [recipe[s]] %recipes%", + "(discover|unlock) [the] [recipe[s]] %recipes% for %players%", + "(undiscover|lock) [the] [recipe[s]] %recipes% for %players%"); + } + + private Expression players; + private Expression recipes; + private boolean isDiscover = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isDiscover = matchedPattern == 0 || matchedPattern == 2; + //noinspection unchecked + players = (Expression) (matchedPattern <= 1 ? exprs[0] : exprs[1]); + //noinspection unchecked + recipes = (Expression) (matchedPattern <= 1 ? exprs[1] : exprs[0]); + return true; + } + + @Override + protected void execute(Event event) { + Player[] playerArray = players.getArray(event); + for (Object object : recipes.getArray(event)) { + if (object instanceof Recipe actualRecipe && actualRecipe instanceof Keyed recipeKey) { + NamespacedKey key = recipeKey.getKey(); + for (Player player : playerArray) { + if (isDiscover) { + player.discoverRecipe(key); + } else { + player.undiscoverRecipe(key); + } + } + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("make", players); + if (isDiscover) { + builder.append("discover"); + } else { + builder.append("undiscover"); + } + builder.append("recipes", recipes); + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffRemoveRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffRemoveRecipe.java new file mode 100644 index 00000000000..1c5b8a207e5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffRemoveRecipe.java @@ -0,0 +1,59 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +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.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; + +@Name("Remove Recipe") +@Description({ + "Remove the specified recipes from the server.", + "This will cause all players who have discovered the recipe to forget it.", + "Removing a minecraft recipe is not persistent across server restart." +}) +@Examples("remove the recipe \"my_recipe\" from the server") +@Since("INSERT VERSION") +public class EffRemoveRecipe extends Effect { + + static { + Skript.registerEffect(EffRemoveRecipe.class, + "(remove|delete) [the] [recipe[s]] %recipes% [from [the] server]"); + } + + private Expression recipes; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + recipes = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Recipe recipe : recipes.getArray(event)) { + if (recipe instanceof Keyed recipeKey) { + NamespacedKey key = recipeKey.getKey(); + if (Bukkit.getRecipe(key) != null) + Bukkit.removeRecipe(key); + } + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "remove recipes " + recipes.toString(event, debug) + " from the server"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffSmithingCopy.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffSmithingCopy.java new file mode 100644 index 00000000000..0601efd0882 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EffSmithingCopy.java @@ -0,0 +1,86 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.SmithingRecipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTransformRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTrimRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe; + +@Name("Allow Data Copy") +@Description({ + "Allow the recipe to copy data from the base item to the result item.", + "", + "This condition can only be used with smithing recipes on PaperMC version 1.19", + "This condition can be used with smithing transform and smithing trim recipes on PaperMC version 1.20.0+", +}) +@Examples({ + "set {_recipe} to a new smithing transform recipe:", + "\tset the recipe base item to a raw iron named \"Impure Iron\"", + "\tset the recipe additional item to an iron nugget named \"Pure Iron\"", + "\tset the recipe result item to an iron ingot named \"Heavenly Iron\"", + "\tallow the item data to be copied from the base item to the result item" +}) +@RequiredPlugins("Paper") +@Since("INSERT VERSION") +public class EffSmithingCopy extends Effect { + + private static final boolean SUPPORTS_COPY_NBT = Skript.methodExists(SmithingRecipe.class, "willCopyNbt"); + private static final boolean SUPPORTS_COPY_DATA = Skript.methodExists(SmithingRecipe.class, "willCopyDataComponents"); + private static final boolean RUNNING_1_20_0 = Skript.isRunningMinecraft(1, 20, 0); + + static { + if (SUPPORTS_COPY_NBT || SUPPORTS_COPY_DATA) { + Skript.registerEffect(EffSmithingCopy.class, + "allow [the] [item] data to be (copied|transferred) from [the] base item to [the] result item", + "allow (copying|transferring) [the] [item] data from [the] base item to [the] result item", + "disallow [the] [item] data to be (copied|transferred) from [the] base item to [the] result item", + "disallow (copying|transferring) [the] [item] data from [the] base item to [the] result item"); + } + } + + private boolean copyData; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(CreateRecipeEvent.class)) { + Skript.error("This effect can only be used when creating a new recipe."); + return false; + } else if (!getParser().isCurrentEvent(SmithingRecipeEvent.class)) { + if (RUNNING_1_20_0) { + Skript.error("This effect can only be used when creating a new Smithing, Smithing Transform or Smithing Trim Recipe."); + } else { + Skript.error("This effect can only be used when creating a new Smithing Recipe."); + } + return false; + } else if (!RUNNING_1_20_0 && (getParser().isCurrentEvent(SmithingTransformRecipeEvent.class, SmithingTrimRecipeEvent.class))) { + Skript.error("Your Paper MC version only allows this effect for creating Smithing Recipes and not Smithing Transform or Smithing Trim Recipes."); + return false; + } + copyData = matchedPattern <= 1; + return true; + } + + @Override + protected void execute(Event event) { + if (!(event instanceof SmithingRecipeEvent smithingEvent)) + return; + + MutableSmithingRecipe mutableRecipe = (MutableSmithingRecipe) smithingEvent.getMutableRecipe(); + mutableRecipe.setCopyData(copyData); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return (copyData ? "allow" : "disallow") + " copying the data from the base item to the result item"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EvtDiscoverRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EvtDiscoverRecipe.java new file mode 100644 index 00000000000..167740a2301 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/EvtDiscoverRecipe.java @@ -0,0 +1,64 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerRecipeDiscoverEvent; +import org.jetbrains.annotations.Nullable; + +@Name("Player Discover Recipe") +@Description("Called when a player discovers a new recipe.") +@Examples({ + "on player discovered recipe:", + "\tbroadcast event-recipe", + "", + "on discovered recipe of \"my_recipe\":", + "\tbroadcast event-recipe" +}) +@Since("INSERT VERSION") +public class EvtDiscoverRecipe extends SkriptEvent { + + static { + Skript.registerEvent("Player Discover Recipe", EvtDiscoverRecipe.class, PlayerRecipeDiscoverEvent.class, + "[player] discover[ed|ing] recipe[s] [of %-strings%]", + "[player] recipe discover[ed|ing] [of %-strings%]" + ); + } + + private @Nullable Expression recipes; + + @Override + public boolean init(Literal[] exprs, int matchedPattern, ParseResult parseResult) { + //noinspection unchecked + recipes = (Expression) exprs[0]; + return true; + } + + @Override + public boolean check(Event event) { + if (!(event instanceof PlayerRecipeDiscoverEvent discoverEvent)) + return false; + if (recipes == null) + return true; + NamespacedKey eventRecipeKey = discoverEvent.getRecipe(); + for (String string : recipes.getArray(event)) { + if (eventRecipeKey.equals(NamespacedKey.fromString(string, Skript.getInstance()))) + return true; + } + return false; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "player discovered recipes" + (recipes == null ? "" : " of " + recipes.toString(event, debug)); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprAllRecipes.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprAllRecipes.java new file mode 100644 index 00000000000..01fe5fa3e34 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprAllRecipes.java @@ -0,0 +1,212 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import ch.njol.util.coll.iterator.CheckedIterator; +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@Name("All Recipes") +@Description({ + "Retrieve all recipes registered in the server. Includes the options to:", + "
    ", + "
  • Get only recipes provided by Minecraft or custom recipes
  • ", + "
  • Specific recipe types
  • ", + "
  • For specific items
  • ", + "
", + "", + "When resetting the server recipes, all custom recipes from any plugin will be removed, regardless of specifying additional data. " + + "Only vanilla recipes will remain.", + "When deleting the server recipes, you are allowed to delete recipes using the options listed above.", +}) +@Examples({ + "set {_list::*} to all of the server's recipe of type shaped recipe for netherite ingot", + "set {_list::*} to the server's mc recipes of type cooking recipe for raw beef", + "set {_list::*} to server's custom recipes of type blasting for raw iron named \"Impure Iron\"", + "", + "reset all of the server's recipes", + "", + "delete all the server's recipes for netherite ingot", + "clear all of the server minecraft recipes", + "delete all of the server's custom shaped recipes" +}) +@Since("INSERT VERSION") +public class ExprAllRecipes extends SimpleExpression { + + static { + Skript.registerExpression(ExprAllRecipes.class, Recipe.class, ExpressionType.SIMPLE, + "[all [[of] the]|the] server['s] recipes [for %-itemstacks/itemtypes%]", + "[all [[of] the]|the] server['s] (mc|minecraft|vanilla) recipes [for %-itemstacks/itemtypes%]", + "[all [[of] the]|the] server['s] custom recipes [for %-itemstacks/itemtypes%]", + "[all [[of] the]|the] server['s] %recipetype% [for %-itemstacks/itemtypes%]", + "[all [[of] the]|the] server['s] (mc|minecraft|vanilla) %recipetype% [for %-itemstacks/itemtypes%]", + "[all [[of] the]|the] server['s] custom %recipetype% [for %-itemstacks/itemtypes%]"); + } + + private Expression recipeTypeExpr; + private Expression itemExpr; + private boolean getMinecraft, getCustom; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (matchedPattern <= 2 && exprs[0] != null) { + itemExpr = exprs[0]; + } else if (matchedPattern >= 3) { + //noinspection unchecked + recipeTypeExpr = (Expression) exprs[0]; + itemExpr = exprs[1]; + } + getMinecraft = matchedPattern == 1 || matchedPattern == 4; + getCustom = matchedPattern == 2 || matchedPattern == 5; + return true; + } + + @Override + protected Recipe @Nullable [] get(Event event) { + List recipes = new ArrayList<>(); + CheckedIterator iterator = getSelectedRecipes(event); + if (iterator == null) + return null; + iterator.forEachRemaining(recipes::add); + return recipes.toArray(new Recipe[0]); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.RESET || mode == ChangeMode.DELETE) + return CollectionUtils.array(Recipe.class); + else if (mode == ChangeMode.ADD) + return CollectionUtils.array(Recipe[].class); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + switch (mode) { + case RESET -> { + Bukkit.resetRecipes(); + } + case DELETE -> { + CheckedIterator iterator = getSelectedRecipes(event); + if (iterator == null) + return; + iterator.forEachRemaining(recipe -> { + if (recipe instanceof Keyed key) + Bukkit.removeRecipe(key.getKey()); + }); + } + case ADD -> { + Recipe[] recipes = delta != null ? (Recipe[]) delta : null; + if (recipes == null || recipes.length == 0) + return; + for (Recipe recipe : recipes) { + if (!(recipe instanceof Keyed keyed)) + continue; + NamespacedKey key = keyed.getKey(); + if (Bukkit.getRecipe(key) != null) + Bukkit.removeRecipe(key); + Bukkit.addRecipe(recipe); + } + + } + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return Recipe.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("all of the"); + if (getMinecraft) { + builder.append("minecraft"); + } else if (getCustom) { + builder.append("custom"); + } + if (recipeTypeExpr != null) + builder.append(recipeTypeExpr); + builder.append("recipes"); + if (itemExpr != null) + builder.append("for", itemExpr); + return builder.toString(); + } + + // Gets the recipes based on the provided input from the user + private CheckedIterator getSelectedRecipes(Event event) { + Iterator iterator = null; + // User is looking for a certain type of item(s) + if (itemExpr != null) { + List itemRecipes = new ArrayList<>(); + for (Object object : itemExpr.getArray(event)) { + ItemStack stack = null; + if (object instanceof ItemStack itemStack) { + stack = itemStack; + } else if (object instanceof ItemType itemType) { + stack = itemType.getRandom(); + } + if (stack != null) + itemRecipes.addAll(Bukkit.getRecipesFor(stack)); + } + // If no recipes contain the items the user is looking for, return null + if (itemRecipes.isEmpty()) + return null; + iterator = itemRecipes.iterator(); + } else { + // Items not specified, getting all recipes + iterator = Bukkit.recipeIterator(); + } + + RecipeType recipeType = recipeTypeExpr != null ? recipeTypeExpr.getSingle(event) : null; + + return new CheckedIterator(iterator, recipe -> { + if (recipe instanceof Keyed keyed) { + NamespacedKey key = keyed.getKey(); + // If the user wants only "minecraft:" recipes + if (getMinecraft && !key.getNamespace().equalsIgnoreCase("minecraft")) + return false; + // If the user wants only custom recipes + else if (getCustom && key.getNamespace().equalsIgnoreCase("minecraft")) + return false; + + // If the user wants a specific recipe type + if (recipeType != null) { + if (!(recipe.getClass().equals(recipeType.getRecipeClass()) || recipeType.getRecipeClass().isInstance(recipe))) + return false; + } + return true; + } + return false; + }); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprGetRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprGetRecipe.java new file mode 100644 index 00000000000..be8407a0740 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprGetRecipe.java @@ -0,0 +1,74 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +@Name("Recipe") +@Description("Returns the recipe registered with the provided id.") +@Examples({ + "set {_recipe} to recipe with the key \"my_recipe\"", + "set {_recipes::*} to recipes from the ids \"my_recipe\" and \"custom_recipe\"" +}) +@Since("INSERT VERSION") +public class ExprGetRecipe extends SimpleExpression { + + static { + Skript.registerExpression(ExprGetRecipe.class, Recipe.class, ExpressionType.SIMPLE, + "[the] recipe[s] (with|from) [the] (key|id)[s] %strings%"); + } + + private Expression recipeNames; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + recipeNames = (Expression) exprs[0]; + return true; + } + + @Override + protected Recipe @Nullable [] get(Event event) { + List recipeList = new ArrayList<>(); + for (String name : recipeNames.getArray(event)) { + NamespacedKey key = NamespacedKey.fromString(name, Skript.getInstance()); + if (key == null) + continue; + Recipe check = Bukkit.getRecipe(key); + if (check != null) + recipeList.add(check); + } + return recipeList.toArray(new Recipe[0]); + } + + @Override + public boolean isSingle() { + return recipeNames.isSingle(); + } + + @Override + public Class getReturnType() { + return Recipe.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "recipes with the keys " + recipeNames.toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCategory.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCategory.java new file mode 100644 index 00000000000..efd872e0542 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCategory.java @@ -0,0 +1,121 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +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 org.bukkit.event.Event; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.inventory.recipe.CookingBookCategory; +import org.bukkit.inventory.recipe.CraftingBookCategory; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe; +import org.skriptlang.skript.bukkit.recipes.RecipeCategory; + +@Name("Recipe Category") +@Description("The recipe category of a shaped, shapeless, blasting, furnace, campfire or smoking recipe.") +@Examples({ + "set {_recipe} to a new shaped recipe with the key \"my_recipe\":", + "\tset the recipe ingredients to diamond, air, diamond, air, emerald, air, diamond, air and diamond", + "\tset the recipe category to misc crafting category", + "\tset the recipe result item to nether star", + "", + "set {_recipe} to a new blasting recipe with the id \"my_recipe\":", + "\tset the recipe input item to coal", + "\tset the recipe category to misc cooking category", + "\tset the recipe result item to gunpowder", + "", + "loop the server's recipes:", + "\tbroadcast recipe category of loop-recipe" +}) +@Since("INSERT VERSION") +public class ExprRecipeCategory extends SimplePropertyExpression { + + static { + registerDefault(ExprRecipeCategory.class, RecipeCategory.class, "recipe category", "recipes"); + } + + private boolean isEvent = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (exprs[0].isDefault() && getParser().isCurrentEvent(CreateRecipeEvent.class)) + isEvent = true; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable RecipeCategory convert(Recipe recipe) { + Enum category = null; + if (recipe instanceof MutableRecipe mutableRecipe) { + if (mutableRecipe instanceof MutableCraftingRecipe mutableCraftingRecipe) { + category = mutableCraftingRecipe.getCategory(); + } else if (mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe) { + category = mutableCookingRecipe.getCategory(); + } + } else { + // TODO: Combine ShapedRecipe and ShapelessRecipe into CraftingRecipe when minimum version is raised to 1.20.1 or higher. + if (recipe instanceof ShapedRecipe shapedRecipe) { + category = shapedRecipe.getCategory(); + } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { + category = shapelessRecipe.getCategory(); + } else if (recipe instanceof CookingRecipe cookingRecipe) { + category = cookingRecipe.getCategory(); + } + } + if (category != null) + return RecipeCategory.convertBukkitToSkript(category); + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + Skript.error("You can not set the recipe category of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(RecipeCategory.class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof CreateRecipeEvent recipeEvent)) + return; + RecipeCategory recipeCategory = (RecipeCategory) (delta != null ? delta[0] : null); + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + if (mutableRecipe instanceof MutableCraftingRecipe mutableCraftingRecipe) { + if (!(recipeCategory.getCategory() instanceof CraftingBookCategory category)) + return; + mutableCraftingRecipe.setCategory(category); + } else if (mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe) { + if (!(recipeCategory.getCategory() instanceof CookingBookCategory category)) + return; + mutableCookingRecipe.setCategory(category); + } + } + + @Override + public Class getReturnType() { + return RecipeCategory.class; + } + + @Override + protected String getPropertyName() { + return "recipe category"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCookingTime.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCookingTime.java new file mode 100644 index 00000000000..d2e11b88693 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeCookingTime.java @@ -0,0 +1,90 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe; + +@Name("Recipe Cooking Time") +@Description("The cooking time of a blasting, furnace, campfire or smoking recipe.") +@Examples({ + "set {_recipe} to a new blasting recipe with the key \"my_recipe\":", + "\tset the recipe input item to raw gold named \"Impure Gold\"", + "\tset the recipe cooking time to 10 seconds", + "\tset the recipe result to gold ingot named \"Pure Gold\"" +}) +@Since("INSERT VERSION") +public class ExprRecipeCookingTime extends SimplePropertyExpression { + + static { + registerDefault(ExprRecipeCookingTime.class, Timespan.class, "recipe cook[ing] time", "recipes"); + } + + private boolean isEvent = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (exprs[0].isDefault() && getParser().isCurrentEvent(CreateRecipeEvent.class)) + isEvent = true; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable Timespan convert(Recipe recipe) { + if (recipe instanceof MutableCookingRecipe mutableCookingRecipe) { + return new Timespan(TimePeriod.TICK, mutableCookingRecipe.getCookingTime()); + } else if (recipe instanceof CookingRecipe cookingRecipe) { + return new Timespan(TimePeriod.TICK, cookingRecipe.getCookingTime()); + } + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + Skript.error("You can not set the recipe cooking time of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(Timespan.class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof CreateRecipeEvent recipeEvent)) + return; + + Timespan timespan = (Timespan) delta[0]; + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + if (!(mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe)) + return; + mutableCookingRecipe.setCookingTime((int) timespan.getAs(Timespan.TimePeriod.TICK)); + } + + @Override + public Class getReturnType() { + return Timespan.class; + } + + @Override + protected String getPropertyName() { + return "recipe cooking time"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeExperience.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeExperience.java new file mode 100644 index 00000000000..3e24249d1c7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeExperience.java @@ -0,0 +1,89 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +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 org.bukkit.event.Event; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCookingRecipe; + +@Name("Recipe Experience") +@Description("The experience of a blasting, furnace, campfire, or smoking recipe.") +@Examples({ + "set {_recipe} to a new blasting recipe with the key \"my_recipe\":", + "\tset the recipe input item to a raw copper named \"Impure Copper\"", + "\tset the recipe experience to 20", + "\tset the recipe result to copper ingot named \"Pure Copper\"" +}) +@Since("INSERT VERSION") +public class ExprRecipeExperience extends SimplePropertyExpression { + + static { + registerDefault(ExprRecipeExperience.class, Float.class, "recipe [e]xp[erience]", "recipes"); + } + + private boolean isEvent = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (exprs[0].isDefault() && getParser().isCurrentEvent(CreateRecipeEvent.class)) + isEvent = true; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable Float convert(Recipe recipe) { + if (recipe instanceof MutableCookingRecipe mutableCookingRecipe) { + return mutableCookingRecipe.getExperience(); + } else if (recipe instanceof CookingRecipe cookingRecipe) { + return cookingRecipe.getExperience(); + } + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + Skript.error("You can not set the recipe experience of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(Float.class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof CreateRecipeEvent recipeEvent)) + return; + + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + if (!(mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe)) + return; + + float experience = (float) delta[0]; + mutableCookingRecipe.setExperience(experience); + } + + @Override + public Class getReturnType() { + return Float.class; + } + + @Override + protected String getPropertyName() { + return "recipe experience"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeGroup.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeGroup.java new file mode 100644 index 00000000000..d66688f58fb --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeGroup.java @@ -0,0 +1,101 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +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 org.bukkit.event.Event; +import org.bukkit.inventory.*; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableGroupRecipe; + +@Name("Recipe Group") +@Description({ + "The recipe group of a shaped, shapeless, blasting, furnace, campfire, smoking, or stonecutting recipe.", + "Groups recipes together under the provided string." +}) +@Examples({ + "set {_recipe} to a new shapeless recipe with the key \"my_recipe\":", + "\tset the recipe ingredients to 3 diamonds, 3 emeralds and 3 netherite ingots", + "\tset the recipe group to \"my group\"", + "\tset the recipe result to nether star" +}) +@Since("INSERT VERSION") +public class ExprRecipeGroup extends SimplePropertyExpression { + + static { + registerDefault(ExprRecipeGroup.class, String.class, "recipe group", "recipes"); + } + + private boolean isEvent = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (exprs[0].isDefault() && getParser().isCurrentEvent(CreateRecipeEvent.class)) + isEvent = true; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable String convert(Recipe recipe) { + if (recipe instanceof MutableGroupRecipe mutableGroupRecipe) { + return mutableGroupRecipe.getGroup(); + } else { + // TODO: Combine ShapedRecipe and ShapelessRecipe into CraftingRecipe when minimum version is raised to 1.20.1 or higher. + if (recipe instanceof ShapedRecipe shapedRecipe) { + return shapedRecipe.getGroup(); + } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { + return shapelessRecipe.getGroup(); + } else if (recipe instanceof CookingRecipe cookingRecipe) { + return cookingRecipe.getGroup(); + } else if (recipe instanceof StonecuttingRecipe stonecuttingRecipe) { + return stonecuttingRecipe.getGroup(); + } + } + return null; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + Skript.error("You can not set the recipe group of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(String.class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof CreateRecipeEvent recipeEvent)) + return; + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + + String group = (String) delta[0]; + if (group.isEmpty()) + return; + + if (mutableRecipe instanceof MutableGroupRecipe mutableGroupRecipe) + mutableGroupRecipe.setGroup(group); + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + protected String getPropertyName() { + return "recipe group"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeIngredients.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeIngredients.java new file mode 100644 index 00000000000..e3d070190ef --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeIngredients.java @@ -0,0 +1,476 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.config.Node; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Material; +import org.bukkit.event.Event; +import org.bukkit.inventory.*; +import org.bukkit.inventory.RecipeChoice.ExactChoice; +import org.bukkit.inventory.RecipeChoice.MaterialChoice; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.*; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableCraftingRecipe.MutableShapedRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTransformRecipe; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe.MutableSmithingRecipe.MutableSmithingTrimRecipe; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.*; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.CraftingRecipeEvent.ShapedRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTransformRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent.SmithingRecipeEvent.SmithingTrimRecipeEvent; +import org.skriptlang.skript.log.runtime.SyntaxRuntimeErrorProducer; + +import java.util.*; + +@Name("Recipe Ingredients") +@Description({ + "The ingredients of a shaped or shapeless recipe.", + "The ingredients of a row for a shaped recipe.", + "The input item of a blasting, furnace, campfire, smoking, stonecutting and transmute recipe.", + "The base, template, addition items of a smithing transform and smithing trim recipe.", + "the transmute items of a transmute recipe." +}) +@Examples({ + "set {_recipe} to a new shaped recipe with the key \"my_recipe\":", + "\tset the recipe ingredients to diamond, air, diamond, air, emerald, air, diamond, air and diamond", + "\t#OR", + "\tset the recipe ingredients of the 1st row to diamond, air and diamond", + "\tset the recipe ingredients of second row to air, emerald and air", + "\tset the recipe ingredients of the third row to diamond, air and diamond", + "\tset the recipe result to beacon", + "", + "set {_recipe} to a shapeless recipe with id \"my_recipe\":", + "\tset recipe ingredients to iron ingot, gold ingot, iron ingot, nether star, 5 obsidian, nether star, iron ingot, gold ingot and iron ingot", + "\tset recipe result item to beacon named \"OP Beacon\"", + "", + "set {_recipe} to new blasting recipe with the id \"my_recipe\":", + "\tset the recipe input item to netherite ingot named \"Impure Netherite\"", + "\tset the recipe result item to netherite ingot named \"Pure Netherite\"", + "", + "set {_recipe} to a new smithing transform recipe with key \"my_recipe\":", + "\tset the recipe base item to diamond helmet", + "\tset the recipe template item to paper named \"Blueprint\"", + "\tset the recipe addition item to netherite ingot named \"Pure Netherite\"", + "\tset the recipe result to netherite helmet named \"Pure Helmet\"", + "", + "set {_recipe} to a new transmute recipe with key \"my_recipe\":", + "\tset the recipe input item to leather helmet", + "\tset the recipe transmute item to nether star named \"Free Upgrade\"", + "\tset the recipe result to netherite helmet" +}) +public class ExprRecipeIngredients extends PropertyExpression implements SyntaxRuntimeErrorProducer { + + enum RecipePattern { + INGREDIENTS("[recipe] ingredients", "recipe ingredients", CraftingRecipeEvent.class, + "This can only be used when creating a Shaped or Shapeless Recipe."), + FIRSTROW("[recipe] ingredients of [the] (1st|first) row", "recipe ingredients of the first row", ShapedRecipeEvent.class, + "This can only be used when creating a Shaped Recipe."), + SECONDROW("[recipe] ingredients of [the] (2nd|second) row", "recipe ingredients of the first row", ShapedRecipeEvent.class, + "This can only be used when creating a Shaped Recipe."), + THIRDROW("[recipe] ingredients of [the] (3rd|third) row", "recipe ingredients of the first row", ShapedRecipeEvent.class, + "This can only be used when creating a Shaped Recipe."), + INPUT("recipe (input|source) [item]", "recipe input item", new Class[]{CookingRecipeEvent.class, StonecuttingRecipeEvent.class, TransmuteRecipeEvent.class}, + "This can only be used when creating a Cooking, Blasting, Furnace, Campfire, Smoking, Stonecutting, or Transmute Recipe."), + BASE("[recipe] base item[s]", "recipe base items", SmithingRecipeEvent.class, + "This can only be used when creating a Smithing, Smithing Transform, or Smithing Trim Recipe."), + TEMPLATE("[recipe] template item[s]", "recipe template items", new Class[]{SmithingTransformRecipeEvent.class, SmithingTrimRecipeEvent.class}, + "This can only be used when creating a Smithing Transform or Smithing Trim Recipe."), + ADDITION("[recipe] addition[al] item[s]", "recipe additional items", SmithingRecipeEvent.class, + "This can only be used when creating a Smithing, Smithing Transform or Smithing Trim Recipe."), + TRANSMUTE("[recipe] transmute item[s]", "recipe transmute items", TransmuteRecipeEvent.class, + "This can only be used creating a Transmute Recipe."); + + + private String pattern, pattern2, toString, error; + private Class[] eventClasses; + + RecipePattern(String pattern, String toString, Class eventClass, String error) { + //noinspection unchecked + this(pattern, toString, new Class[]{eventClass}, error); + } + + RecipePattern(String pattern, String toString, Class[] eventClasses, String error) { + this.pattern = "[the] " + pattern + " [of %recipes%]"; + this.pattern2 = "[the] %recipes%'[s] " + pattern; + this.toString = toString; + this.eventClasses = eventClasses; + this.error = error; + } + + } + + private static final RecipePattern[] recipePatterns = RecipePattern.values(); + + static { + String[] patterns = new String[recipePatterns.length * 2]; + for (RecipePattern pattern : recipePatterns) { + patterns[(2 * pattern.ordinal())] = pattern.pattern; + patterns[(2 * pattern.ordinal()) + 1] = pattern.pattern2; + } + Skript.registerExpression(ExprRecipeIngredients.class, ItemStack.class, ExpressionType.PROPERTY, patterns); + } + + private boolean isEvent = false; + private RecipePattern selectedPattern; + private Node node; + private String rawExpr; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + selectedPattern = recipePatterns[matchedPattern / 2]; + if (exprs[0].isDefault()) { + if (getParser().isCurrentEvent(CreateRecipeEvent.class)) { + if (!getParser().isCurrentEvent(selectedPattern.eventClasses)) { + Skript.error(selectedPattern.error); + return false; + } + isEvent = true; + } + } + //noinspection unchecked + setExpr((Expression) exprs[0]); + node = getParser().getNode(); + rawExpr = parseResult.expr; + return true; + } + + @Override + protected ItemStack @Nullable [] get(Event event, Recipe[] source) { + List ingredients = new ArrayList<>(); + for (Recipe recipe : source) { + switch (selectedPattern) { + case INGREDIENTS -> { + ingredients.addAll(getterIngredients(recipe)); + } + case FIRSTROW, SECONDROW, THIRDROW -> { + ingredients.addAll(getterRows(recipe)); + } + case BASE, TEMPLATE, ADDITION -> { + ingredients.addAll(getterSmithing(recipe)); + } + case INPUT -> { + ingredients.addAll(getterInput(recipe)); + } + case TRANSMUTE -> { + ingredients.addAll(getterTransmute(recipe)); + } + } + } + return ingredients.toArray(ItemStack[]::new); + } + + private List getterIngredients(Recipe recipe) { + List ingredients = new ArrayList<>(); + if (recipe instanceof MutableCraftingRecipe mutableCraftingRecipe) { + Arrays.stream(mutableCraftingRecipe.getIngredients()).forEach(recipeChoice -> { + if (recipeChoice instanceof ExactChoice exactChoice) { + ingredients.addAll(exactChoice.getChoices()); + } else if (recipeChoice instanceof MaterialChoice materialChoice) { + materialChoice.getChoices().stream().forEach(material -> { + ingredients.add(new ItemStack(material)); + }); + } + }); + } else if (recipe instanceof ShapedRecipe shapedRecipe) { + Map ingredientMap = shapedRecipe.getIngredientMap(); + ingredients.addAll(ingredientMap.values().stream() + .map(itemStack -> { + if (itemStack == null) return new ItemStack(Material.AIR); + return itemStack; + }).toList()); + } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { + ingredients.addAll(shapelessRecipe.getIngredientList()); + } else { + error("You can only get the ingredients of a Shaped or Shapeless Recipe."); + } + return ingredients; + } + + private List getterRows(Recipe recipe) { + List ingredients = new ArrayList<>(); + if (recipe instanceof MutableShapedRecipe mutableShapedRecipe) { + RecipeChoice[] choices = mutableShapedRecipe.getIngredients(); + int row = selectedPattern.ordinal() - 1; + for (int i = 0; i < 3; i++) { + ExactChoice exactChoice = (ExactChoice) choices[i * row + i]; + ingredients.addAll(exactChoice.getChoices()); + } + } else if (recipe instanceof ShapedRecipe shapedRecipe) { + String[] shape = shapedRecipe.getShape(); + Map ingredientMap = shapedRecipe.getIngredientMap(); + String row = shape[selectedPattern.ordinal() - 1]; + for (Character character : row.toCharArray()) { + ItemStack stack = ingredientMap.get(character); + if (stack == null) stack = new ItemStack(Material.AIR); + ingredients.add(stack); + } + } else { + error("You can only get the ingredients of a row for a Shaped Recipe."); + } + return ingredients; + } + + private List getterSmithing(Recipe recipe) { + List ingredients = new ArrayList<>(); + if (recipe instanceof MutableSmithingRecipe mutableSmithingRecipe) { + ExactChoice exactChoice = (ExactChoice) switch (selectedPattern) { + case BASE -> mutableSmithingRecipe.getBase(); + case ADDITION -> mutableSmithingRecipe.getAddition(); + case TEMPLATE -> { + if (recipe instanceof MutableSmithingTransformRecipe || recipe instanceof MutableSmithingTrimRecipe) { + yield mutableSmithingRecipe.getTemplate(); + } + yield null; + } + default -> null; + }; + if (exactChoice != null) + ingredients.addAll(exactChoice.getChoices()); + } else if (recipe instanceof SmithingRecipe smithingRecipe) { + RecipeChoice choice = switch (selectedPattern) { + case BASE -> smithingRecipe.getBase(); + case TEMPLATE -> { + if (recipe instanceof SmithingTransformRecipe transformRecipe) + yield transformRecipe.getTemplate(); + else if (recipe instanceof SmithingTrimRecipe trimRecipe) + yield trimRecipe.getTemplate(); + yield null; + } + case ADDITION -> smithingRecipe.getAddition(); + default -> null; + }; + if (choice instanceof ExactChoice exactChoice) { + ingredients.addAll(exactChoice.getChoices()); + } else if (choice instanceof MaterialChoice materialChoice) { + ingredients.addAll( + materialChoice.getChoices().stream().map(ItemStack::new).toList() + ); + } + } else { + error("You can only get the base items of a Smithing, Smithing Transform and Smithing Trim Recipe."); + } + return ingredients; + } + + private List getterInput(Recipe recipe) { + List ingredients = new ArrayList<>(); + if (recipe instanceof MutableRecipe mutableRecipe) { + if (mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe) { + ingredients.addAll(((ExactChoice) mutableCookingRecipe.getInput()).getChoices()); + } else if (mutableRecipe instanceof MutableStonecuttingRecipe mutableStonecuttingRecipe) { + ingredients.addAll(((ExactChoice) mutableStonecuttingRecipe.getInput()).getChoices()); + } else if (mutableRecipe instanceof MutableTransmuteRecipe mutableTransmuteRecipe) { + ingredients.addAll(((ExactChoice) mutableTransmuteRecipe.getInput()).getChoices()); + } + } else { + RecipeChoice choice = null; + if (recipe instanceof CookingRecipe cookingRecipe) { + choice = cookingRecipe.getInputChoice(); + } else if (recipe instanceof StonecuttingRecipe stonecuttingRecipe) { + choice = stonecuttingRecipe.getInputChoice(); + } else if (recipe instanceof TransmuteRecipe transmuteRecipe) { + choice = transmuteRecipe.getInput(); + } else { + error("You can only get the input item of a Cooking, Blasting, Furnace, Campfire, Smoking and Stonecutting Recipe."); + } + if (choice instanceof ExactChoice exactChoice) { + ingredients.addAll(exactChoice.getChoices()); + } else if (choice instanceof MaterialChoice materialChoice) { + ingredients.addAll(materialChoice.getChoices().stream().map(ItemStack::new).toList()); + } + } + return ingredients; + } + + private List getterTransmute(Recipe recipe) { + List ingredients = new ArrayList<>(); + if (recipe instanceof MutableTransmuteRecipe mutableTransmuteRecipe) { + ingredients.addAll(((ExactChoice) mutableTransmuteRecipe.getInput()).getChoices()); + } else if (recipe instanceof TransmuteRecipe transmuteRecipe) { + RecipeChoice choice = transmuteRecipe.getMaterial(); + if (choice instanceof ExactChoice exactChoice) { + ingredients.addAll(exactChoice.getChoices()); + } else if (choice instanceof MaterialChoice materialChoice) { + ingredients.addAll(materialChoice.getChoices().stream().map(ItemStack::new).toList()); + } + } + return ingredients; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + error("You can not set the " + selectedPattern.toString + " of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(ItemType[].class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof CreateRecipeEvent recipeEvent)) + return; + + Map items = new HashMap<>(); + for (int i = 0; i < delta.length; i++) { + Object object = delta[i]; + if (object instanceof ItemType itemType) { + List thisItems = new ArrayList<>(); + itemType.getAll().forEach(thisItems::add); + items.put(i, thisItems.toArray(ItemStack[]::new)); + } + } + + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + + switch (selectedPattern) { + case INGREDIENTS -> { + changerIngredients(items, mutableRecipe, recipeEvent); + } + case FIRSTROW, SECONDROW, THIRDROW -> { + changerRows(items, mutableRecipe, recipeEvent); + } + case BASE, TEMPLATE, ADDITION -> { + changerSmithing(items, mutableRecipe, recipeEvent); + } + case INPUT -> { + changerInput(items, mutableRecipe, recipeEvent); + } + case TRANSMUTE -> { + changerTransmute(items, mutableRecipe, recipeEvent); + } + } + } + + private void changerIngredients(Map items, MutableRecipe mutableRecipe, CreateRecipeEvent recipeEvent) { + if (!(mutableRecipe instanceof MutableCraftingRecipe mutableCraftingRecipe)) + return; + + if (items.size() > 9) { + error("You can only provide up to 9 items when setting the ingredients for a '" + recipeEvent.getRecipeType() + "' recipe."); + recipeEvent.setErrorInSection(); + return; + } + for (Map.Entry entry : items.entrySet()) { + ItemStack[] ingredients = entry.getValue(); + if (Arrays.stream(ingredients).anyMatch(itemStack -> itemStack.getType().isAir())) { + if (ingredients.length > 1) { + error("You can not provide air with a list of other items."); + recipeEvent.setErrorInSection(); + return; + } else { + continue; + } + } + RecipeChoice choice = new ExactChoice(ingredients); + mutableCraftingRecipe.setIngredients(entry.getKey(), choice); + } + } + + private void changerRows(Map items, MutableRecipe mutableRecipe, CreateRecipeEvent recipeEvent) { + if (!(mutableRecipe instanceof MutableShapedRecipe mutableShapedRecipe)) + return; + if (items.size() > 3) { + error("You can only provide up to 3 items when setting the ingredients of a row for a '" + recipeEvent.getRecipeType() + "' recipe."); + recipeEvent.setErrorInSection(); + return; + } + for (Map.Entry entry : items.entrySet()) { + ItemStack[] ingredients = entry.getValue(); + if (Arrays.stream(ingredients).anyMatch(itemStack -> itemStack.getType().isAir())) { + if (ingredients.length > 1) { + error("You can not provide 'air' with a list of other items."); + recipeEvent.setErrorInSection(); + return; + } else { + continue; + } + } + RecipeChoice choice = new ExactChoice(ingredients); + mutableShapedRecipe.setIngredients(((3 * (selectedPattern.ordinal() - 1)) + entry.getKey()), choice); + } + } + + private void changerSmithing(Map items, MutableRecipe mutableRecipe, CreateRecipeEvent recipeEvent) { + if (!(mutableRecipe instanceof MutableSmithingRecipe mutableSmithingRecipe)) + return; + List stackList = new ArrayList<>(); + items.entrySet().stream().forEach(entry -> stackList.addAll(Arrays.asList(entry.getValue()))); + if (stackList.stream().anyMatch(itemStack -> itemStack.getType().isAir())) { + error("You can not provide 'air' with this expression."); + recipeEvent.setErrorInSection(); + return; + } + RecipeChoice choice = new ExactChoice(stackList); + switch (selectedPattern) { + case BASE -> mutableSmithingRecipe.setBase(choice); + case TEMPLATE -> mutableSmithingRecipe.setTemplate(choice); + case ADDITION -> mutableSmithingRecipe.setAddition(choice); + } + } + + private void changerInput(Map items, MutableRecipe mutableRecipe, CreateRecipeEvent recipeEvent) { + List stackList = new ArrayList<>(); + items.entrySet().stream().forEach(entry -> stackList.addAll(Arrays.asList(entry.getValue()))); + if (stackList.stream().anyMatch(itemStack -> itemStack.getType().isAir())) { + error("You can not provide 'air' with this expression."); + recipeEvent.setErrorInSection(); + return; + } + RecipeChoice choice = new ExactChoice(stackList); + if (mutableRecipe instanceof MutableCookingRecipe mutableCookingRecipe) { + mutableCookingRecipe.setInput(choice); + } else if (mutableRecipe instanceof MutableStonecuttingRecipe mutableStonecuttingRecipe) { + mutableStonecuttingRecipe.setInput(choice); + } else if (mutableRecipe instanceof MutableTransmuteRecipe mutableTransmuteRecipe) { + mutableTransmuteRecipe.setInput(choice); + } + } + + private void changerTransmute(Map items, MutableRecipe mutableRecipe, CreateRecipeEvent recipeEvent) { + List stackList = new ArrayList<>(); + items.entrySet().stream().forEach(entry -> stackList.addAll(Arrays.asList(entry.getValue()))); + if (stackList.stream().anyMatch(itemStack -> itemStack.getType().isAir())) { + error("You can not provide 'air' with this expression."); + recipeEvent.setErrorInSection(); + return; + } + RecipeChoice choice = new ExactChoice(stackList); + if (mutableRecipe instanceof MutableTransmuteRecipe mutableTransmuteRecipe) { + mutableTransmuteRecipe.setMaterial(choice); + } + } + + @Override + public Class getReturnType() { + return ItemStack.class; + } + + @Override + public Node getNode() { + return node; + } + + @Override + public @Nullable String toHighlight() { + return rawExpr; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the " + selectedPattern.toString + " of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeKey.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeKey.java new file mode 100644 index 00000000000..f453b07c739 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeKey.java @@ -0,0 +1,47 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.Keyed; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; + +@Name("Recipe Name") +@Description("Get the namespaced key of a recipe.") +@Examples({ + "loop all of the server's recipes:", + "\tbroadcast the recipe key of loop-recipe", + "\tadd loop-recipe's id to {_list::*}" +}) +@Since("INSERT VERSION") +public class ExprRecipeKey extends SimplePropertyExpression { + + static { + register(ExprRecipeKey.class, String.class, "recipe (key|id)[s]", "recipes"); + } + + @Override + public @Nullable String convert(Recipe recipe) { + if (recipe instanceof MutableRecipe recipeWrapper) { + return recipeWrapper.getKey().toString(); + } else if (recipe instanceof Keyed key) { + return key.getKey().toString(); + } + return null; + } + + @Override + protected String getPropertyName() { + return "recipe key"; + } + + @Override + public Class getReturnType() { + return String.class; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeResult.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeResult.java new file mode 100644 index 00000000000..1fb74732711 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeResult.java @@ -0,0 +1,80 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +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 org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; + +@Name("Recipe Result") +@Description("The result item for a recipe.") +@Examples({ + "set {_recipe} to a new shaped recipe with the key \"my_recipe\":", + "\tset the recipe ingredients of 1st row to diamond, air and diamond", + "\tset the recipe result to diamond sword named \"Chosen One\"" +}) +@Since("INSERT VERSION") +public class ExprRecipeResult extends SimplePropertyExpression { + + static { + registerDefault(ExprRecipeResult.class, ItemStack.class, "recipe result [item]", "recipes"); + } + + private boolean isEvent = false; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (getParser().isCurrentEvent(CreateRecipeEvent.class)) + isEvent = true; + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public @Nullable ItemStack convert(Recipe recipe) { + return recipe.getResult(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (!isEvent) { + Skript.error("You can not set the recipe result of existing recipes."); + } else if (mode == ChangeMode.SET) { + return CollectionUtils.array(ItemStack.class); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (delta == null || !(event instanceof CreateRecipeEvent recipeEvent)) + return; + + MutableRecipe mutableRecipe = recipeEvent.getMutableRecipe(); + + ItemStack result = (ItemStack) delta[0]; + mutableRecipe.setResult(result); + } + + @Override + public Class getReturnType() { + return ItemStack.class; + } + + @Override + protected String getPropertyName() { + return "recipe result item"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeType.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeType.java new file mode 100644 index 00000000000..95f3dbd30dd --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprRecipeType.java @@ -0,0 +1,44 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; + +@Name("Recipe Type") +@Description("Get the recipe type of a recipe.") +@Examples({ + "loop all of the server's recipes:", + "\tbroadcast the recipe type of loop-recipe" +}) +@Since("INSERT VERSION") +public class ExprRecipeType extends SimplePropertyExpression { + + static { + register(ExprRecipeType.class, RecipeType.class, "recipe type", "recipes"); + } + + @Override + public @Nullable RecipeType convert(Recipe recipe) { + if (recipe instanceof MutableRecipe mutableRecipe) + return mutableRecipe.getRecipeType(); + return RecipeUtils.getRecipeType(recipe); + } + + @Override + protected String getPropertyName() { + return "recipe type"; + } + + @Override + public Class getReturnType() { + return RecipeType.class; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprSecCreateRecipe.java b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprSecCreateRecipe.java new file mode 100644 index 00000000000..f42b9fdf505 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/recipes/elements/ExprSecCreateRecipe.java @@ -0,0 +1,189 @@ +package org.skriptlang.skript.bukkit.recipes.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.*; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.variables.Variables; +import ch.njol.util.Kleenean; +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.recipes.CreateRecipeEvent; +import org.skriptlang.skript.bukkit.recipes.MutableRecipe; +import org.skriptlang.skript.bukkit.recipes.RecipeUtils.RecipeType; +import org.skriptlang.skript.log.runtime.SyntaxRuntimeErrorProducer; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Name("New Recipe") +@Description({ + "Create a custom recipe for any of the following types:", + "shaped, shapeless, blasting, furnace, campfire, smoking, smithing transform, smithing trim, stonecutting or transmute.", + "", + "All recipes except smithing trim require a 'result item'.", + "Blasting, furnace, campfire and smoking all fall under cooking recipe type.", + "Groups only apply to shaped, shapeless, cooking, and stonecutting recipes.", + "Category only applies to shaped, shapeless and cooking recipes.", + "You can not create a cooking, crafting and complex recipe type.", + "Custom recipes are not persistent across server restart.", + "MC versions below 1.20 convert smithing transform and smithing trim recipes to a smithing recipe when added to the server." +}) +@Examples({ + "set {_recipe} to a new shaped recipe with the key \"my_recipe\":", + "\tset the recipe ingredients to diamond, air, diamond, air, emerald, air, diamond and diamond #OR", + "\tset recipe ingredients of 1st row to diamond, air and diamond", + "\tset recipe ingredients of second row to air, emerald and air", + "\tset recipe ingredients of 3rd row to diamond, air and diamond", + "\tset recipe group to \"custom group\"", + "\tset recipe category to misc crafting category", + "\tset recipe result to diamond sword named \"Heavenly Sword\"", + "", + "set {_recipe} to a new shapeless recipe with id \"my_recipe\":", + "\tset recipe ingredients to 3 diamonds, 3 emeralds and 3 iron ingots", + "\tset the recipe group to \"custom group\"", + "\tset the recipe category to misc crafting category", + "\tset the recipe result item to diamond helmet named \"Heavenly Helm\"", + "", + "#Furnace, Campfire and Smoking follow same format as Blasting example", + "set {_recipe} to a new blasting recipe with the id \"my_recipe\":", + "\tset the recipe experience to 5", + "\tset the recipe cooking time to 10 seconds", + "\tset the recipe group to \"custom group\"", + "\tset the recipe category to misc cooking category", + "\tset the recipe input item to coal named \"Ash\"", + "\tset the recipe result item to gunpowder named \"Dust\"", + "", + "#Smithing Trim follows the same format, except for 'result item'", + "set {_recipe} to a new smithing transform recipe with key \"my_recipe\":", + "\tset the recipe base item to diamond helmet", + "\tset the recipe template item to paper named \"Blueprint\"", + "\tset the recipe addition item to netherite ingot named \"Pure Netherite\"", + "\tset the recipe result to netherite helmet named \"Pure Helmet\"", + "", + "set {_recipe} to a new stonecutting recipe with id \"my_recipe\":", + "\tset the recipe source item to cobblestone named \"Cracked Stone\"", + "\tset the recipe group to \"custom group\"", + "\tset the recipe result to stone named \"Refurnished Stone\"", + "", + "set {_recipe} to a new transmute recipe with key \"my_recipe\":", + "\tset the recipe input item to leather helmet", + "\tset the recipe transmute item to nether star named \"Free Upgrade\"", + "\tset the recipe result to netherite helmet" +}) +@Since("INSERT VERSION") +public class ExprSecCreateRecipe extends SectionExpression implements SyntaxRuntimeErrorProducer { + + private static final boolean SUPPORT_SMITHING = !Skript.isRunningMinecraft(1, 20, 0); + + static { + Skript.registerExpression(ExprSecCreateRecipe.class, Recipe.class, ExpressionType.SIMPLE, + "a new %*recipetype% with [the] (key|id) %string%"); + EventValues.registerEventValue(CreateRecipeEvent.class, Recipe.class, CreateRecipeEvent::getMutableRecipe); + } + + private RecipeType providedType; + private Expression providedName; + private Trigger trigger; + private Node node; + private String rawExpr; + + @Override + public boolean init(Expression[] exprs, int pattern, Kleenean isDelayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { + if (node == null) { + Skript.error("Creating a new recipe requires a section."); + return false; + } + //noinspection unchecked + providedType = ((Literal) exprs[0]).getSingle(); + if (providedType == RecipeType.COOKING || providedType == RecipeType.CRAFTING || providedType == RecipeType.COMPLEX) { + Skript.error("You can not create a new '" + providedType + "' recipe type."); + return false; + } else if (providedType == RecipeType.SMITHING && !SUPPORT_SMITHING) { + Skript.error("You can not create a new 'smithing' recipe type on MC version 1.20+."); + return false; + } else if (providedType.getRecipeClass() == null) { + Skript.error("You can not create a new '" + providedType + "' recipe type on this MC version."); + return false; + } + //noinspection unchecked + providedName = (Expression) exprs[1]; + AtomicBoolean delayed = new AtomicBoolean(false); + Runnable afterLoading = () -> delayed.set(!getParser().getHasDelayBefore().isFalse()); + //noinspection unchecked + trigger = loadCode(node, "create recipe", afterLoading, providedType.getEventClass()); + if (delayed.get()) { + Skript.error("Delays cannot be used within a 'create recipe' section."); + return false; + } + this.node = getParser().getNode(); + rawExpr = result.expr; + return true; + } + + @Override + protected Recipe @Nullable [] get(Event event) { + String name = providedName.getSingle(event); + if (name == null || name.isEmpty()) { + error("The id for a recipe must not be null nor empty."); + return null; + } + NamespacedKey key = NamespacedKey.fromString(name, Skript.getInstance()); + if (key == null) { + error("The provided id is invalid."); + return null; + } + CreateRecipeEvent recipeEvent = providedType.createRecipeEvent(key); + if (recipeEvent == null) + throw new IllegalStateException("Unexpected value: " + providedType); + + Variables.withLocalVariables(event, recipeEvent, () -> TriggerItem.walk(trigger, recipeEvent)); + // If any of the used expressions or effects produce an error, fail creation + if (recipeEvent.getErrorInSection()) + return null; + + MutableRecipe recipeWrapper = recipeEvent.getMutableRecipe(); + Recipe recipe = recipeWrapper.create(); + // If the recipe failed to build + if (recipe == null) { + error(recipeWrapper.getErrors().toString()); + return null; + } + return new Recipe[]{recipe}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Recipe.class; + } + + @Override + public Node getNode() { + return node; + } + + @Override + public @Nullable String toHighlight() { + return rawExpr; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "a new " + providedType + " recipe with the key " + providedName.toString(event, debug); + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 999c0f394bd..bf23af59409 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2555,6 +2555,33 @@ input keys: sneak: sneak key, sneaking key sprint: sprint key, sprinting key +# -- Recipe Categories -- +recipe categories: + crafting_building: building crafting category + crafting_equipment: equipment crafting category + crafting_misc: misc crafting category, miscellaneous crafting category + crafting_redstone: redstone crafting category + cooking_blocks: blocks cooking category + cooking_food: food cooking category + cooking_misc: misc cooking category, miscellaneous cooking category + +# -- Recipe Types -- +recipe types: + crafting: crafting recipe, crafting recipes + shaped: shaped recipe, shaped crafting recipe, shaped recipes, shaped crafting recipes + shapeless: shapeless recipe, shapeless crafting recipe, shapeless recipes, shapeless crafting recipes + cooking: cooking recipe, cooking recipes + blasting: blasting recipe, blasting recipes + furnace: furnace recipe, furnace recipes + campfire: campfire recipe, campfire recipes + smoking: smoking recipe, smoking recipes + smithing: smithing recipe, smithing recipes + smithing_transform: smithing transform recipe, smithing transform recipes + smithing_trim: smithing trim recipe, smithing trim recipes + stonecutting: stonecutting recipe, stonecutting recipes + complex: complex recipe, complex recipes + transmute: transmute recipe, transmute recipes + # -- Boolean -- boolean: true: @@ -2653,6 +2680,7 @@ types: lootcontext: loot context¦s @a bannerpatterntype: banner pattern type¦s @a bannerpattern: banner pattern¦s @a + recipe: recipe¦s @a # Skript weathertype: weather type¦s @a @@ -2681,6 +2709,8 @@ types: named: named thing¦s @a numbered: numbered thing¦s @a containing: container¦s @a + recipetype: recipe type¦s @a + recipecategory: recipe categor¦y¦ies @a # Hooks money: money diff --git a/src/test/skript/tests/syntaxes/effects/EffRemoveRecipe.sk b/src/test/skript/tests/syntaxes/effects/EffRemoveRecipe.sk new file mode 100644 index 00000000000..0b5c8d6150d --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffRemoveRecipe.sk @@ -0,0 +1,10 @@ +test "remove recipe": + set {_recipe} to a new blasting recipe with the key "test_remove_recipe": + set the recipe input item to diamond named "test_remove_recipe" + set the recipe result item to nether star named "test_remove_recipe" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_remove_recipe" + assert {_get} is set with "Blasting recipe was not added to the server" + remove recipe with the key "test_remove_recipe" from the server + set {_get} to recipe with the key "test_remove_recipe" + assert {_get} is not set with "Recipe was not removed from the server" diff --git a/src/test/skript/tests/syntaxes/effects/EffSmithingCopy.sk b/src/test/skript/tests/syntaxes/effects/EffSmithingCopy.sk new file mode 100644 index 00000000000..96ba3dcb748 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffSmithingCopy.sk @@ -0,0 +1,19 @@ +test "smithing recipe data copy" when running minecraft "1.20.0": + ### + Combines: + EffSmithingData + CondSmithingData + ### + set {_base} to diamond named "test_smithing_recipe_data_component_copy" + set {_template} to netherite ingot named "test_smithing_recipe_data_component_copy" + set {_addition} to emerald named "test_smithing_recipe_data_component_copy" + set {_recipe} to a new smithing transform recipe with the key "test_smithing_recipe_data_component_copy": + set the recipe base item to {_base} + set the recipe template item to {_template} + set the recipe addition item to {_addition} + set the recipe result to netherite sword named "test_smithing_recipe_data_component_copy" + disallow the item data to be copied from the base item to the result item + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_smithing_recipe_data_component_copy" + assert {_recipe} does not allow data copying with "Created recipe should not allow data component copying" + assert {_get} does not allow data copying with "Retrieved recipe should not allow data component copying" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprAllRecipes.sk b/src/test/skript/tests/syntaxes/expressions/ExprAllRecipes.sk new file mode 100644 index 00000000000..415de88f728 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprAllRecipes.sk @@ -0,0 +1,17 @@ +test "all recipes": + set {_all} to size of all of the server's recipes + assert {_all} > 0 with "All recipes did not return sufficient data" + set {_all_shaped} to size of all of the server's shaped recipes + assert {_all_shaped} > 0 with "All shaped recipes did not return sufficient data" + set {_custom} to size of all of the server's custom recipes + set {_custom_shaped} to size of all of the server's custom shaped recipes + + set {_recipe} to a new shaped recipe with the key "test_all_recipes": + set the recipe ingredients to raw iron, raw gold and raw copper + set the recipe result to lightning rod + add {_recipe} to the server's recipes + + assert (size of all of the server's recipes) = ({_all} + 1) with "Mismatching size of all comparison" + assert (size of all of the server's shaped recipes) = ({_all_shaped} + 1) with "Mismatching size of all shaped comparison" + assert (size of all of the server's custom recipes) = ({_custom} + 1) with "Mismatching size of all custom comparison" + assert (size of all of the server's custom shaped recipes) = ({_custom_shaped} + 1) with "Mismacthing size of all custom shaped comparison" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRecipeIngredients.sk b/src/test/skript/tests/syntaxes/expressions/ExprRecipeIngredients.sk new file mode 100644 index 00000000000..405c5dba1f7 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRecipeIngredients.sk @@ -0,0 +1,79 @@ +test "recipe ingredients": + set {_ingredients::*} to raw iron, raw gold, raw copper, iron ingot, gold ingot, copper ingot, diamond, emerald and netherite ingot + set {_recipe} to a new shaped recipe with the key "test_recipe_ingredients": + set the recipe ingredients to {_ingredients::*} + set the recipe result to nether star named "test_recipe_ingredients" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_ingredients" + assert the recipe ingredients of {_recipe} contains {_ingredients::*} with "Mismatch of recipe ingredients from created recipe" + assert the recipe ingredients of {_get} contains {_ingredients::*} with "Mismatch of recipe ingredients from retrieved recipe" + +test "recipe ingredients of rows": + set {_first::*} to diamond, emerald and netherite ingot + set {_second::*} to copper ingot, gold ingot and iron ingot + set {_third::*} to raw copper, raw gold and raw iron + set {_recipe} to a new shaped recipe with the key "test_recipe_ingredients_rows": + set the recipe ingredients of first row to {_first::*} + set the recipe ingredients of second row to {_second::*} + set the recipe ingredients of third row to {_third::*} + set the recipe result to nether star named "test_recipe_ingredients_rows" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_ingredients_rows" + assert the recipe ingredients of first row of {_recipe} contains {_first::*} with "Mismatch of recipe ingredients first row from created recipe" + assert the recipe ingredients of second row of {_recipe} contains {_second::*} with "Mismatch of recipe ingredients second row from created recipe" + assert the recipe ingredients of third row of {_recipe} contains {_third::*} with "Mismatch of recipe ingredients third row from created recipe" + assert the recipe ingredients of first row of {_get} contains {_first::*} with "Mismatch of recipe ingredients first row from retrieved recipe" + assert the recipe ingredients of second row of {_get} contains {_second::*} with "Mismatch of recipe ingredients second row from retrieved recipe" + assert the recipe ingredients of third row of {_get} contains {_third::*} with "Mismatch of recipe ingredients third row from retrieved recipe" + +test "recipe cooking input": + set {_input} to grass block named "test_recipe_cooking_input" + set {_recipe} to a new blasting recipe with the key "test_recipe_cooking_input": + set the recipe input item to {_input} + set the recipe result to nether star named "test_recipe_cooking_input" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_cooking_input" + assert the recipe input item of {_recipe} = {_input} with "Mismatch of recipe input item from created recipe" + assert the recipe input item of {_get} = {_input} with "Mismatch of recipe input item from retrieved recipe" + +test "recipe stonecutting input": + set {_input} to stone named "test_recipe_stonecutting_input" + set {_recipe} to a new stonecutting recipe with the key "test_recipe_stonecutting_input": + set the recipe input item to {_input} + set the recipe result to smooth stone named "test_recipe_stonecutting_input" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_stonecutting_input" + assert the recipe input item of {_recipe} = {_input} with "Mismatch of recipe input item from created recipe" + assert the recipe input item of {_get} = {_input} with "Mismatch of recipe input item from retrieved recipe" + +test "recipe base, template and addition item" when running minecraft "1.20": + set {_base} to diamond named "test_recipe_base_template_addition" + set {_template} to netherite ingot named "test_recipe_base_template_addition" + set {_addition} to emerald named "test_recipe_base_template_addition" + set {_recipe} to a new smithing transform recipe with the key "test_recipe_base_template_addition": + set the recipe base item to {_base} + set the recipe template item to {_template} + set the recipe addition item to {_addition} + set the recipe result to netherite sword named "test_recipe_base_template_addition" + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_base_template_addition" + assert the recipe base item of {_recipe} = {_base} with "Mismatch of recipe base item from created recipe" + assert the recipe template item of {_recipe} = {_template} with "Mismatch of recipe template item from created recipe" + assert the recipe addition item of {_recipe} = {_addition} with "Mismatch of recipe addition item from created recipe" + assert the recipe base item of {_get} = {_base} with "Mismatch of recipe base item from retrieved recipe" + assert the recipe template item of {_get} = {_template} with "Mismatch of recipe template item from retrieved recipe" + assert the recipe addition item of {_get} = {_addition} with "Mismatch of recipe addition item from retrieved recipe" + +test "recipe transmute item" when running minecraft "1.21.2": + set {_recipe} to a new transmute recipe with the key "test_recipe_transmute": + set the recipe input item to a leather helmet + set the recipe transmute item to a nether star + set the recipe result item to a netherite helmet + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_transmute" + assert the recipe input item of {_recipe} is a leather helmet with "Mismatch of recipe input item from created recipe" + assert the recipe transmute item of {_recipe} is a nether star with "Mismatch of recipe transmute item from created recipe" + assert the recipe result item of {_recipe} is a netherite helmet with "Mismatch of recipe result item from created recipe" + assert the recipe input item of {_get} is a leather helmet with "Mismatch of recipe input item from retrieved recipe" + assert the recipe transmute item of {_get} is a nether star with "Mismatch of recipe transmute item from retrieved recipe" + assert the recipe result item of {_get} is a netherite helmet with "Mismatch of recipe result item from retrieved recipe" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRecipeResult.sk b/src/test/skript/tests/syntaxes/expressions/ExprRecipeResult.sk new file mode 100644 index 00000000000..80793b994af --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRecipeResult.sk @@ -0,0 +1,9 @@ +test "recipe result": + set {_result} to copper ingot named "test_recipe_result" + set {_recipe} to a new blasting recipe with key "test_recipe_result": + set the recipe input item to raw copper named "test_recipe_result" + set the recipe result to {_result} + add {_recipe} to the server's recipes + set {_get} to recipe with the key "test_recipe_result" + assert the recipe result of {_recipe} = {_result} with "Result mismatch of created recipe" + assert the recipe result of {_get} = {_result} with "Result mismatch of retrieved recipe" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprRecipeValues.sk b/src/test/skript/tests/syntaxes/expressions/ExprRecipeValues.sk new file mode 100644 index 00000000000..3fcb91cd4ac --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprRecipeValues.sk @@ -0,0 +1,59 @@ +test "cooking recipe values": + ### + Combines: + ExprRecipeCookingTime + ExprRecipeCategory + ExprRecipeExperience + ExprRecipeGroup + ExprGetRecipe + ExprRecipeKey + ### + + set {_recipe} to a new blasting recipe with the key "test_cooking_recipe_values": + set the recipe input item to raw copper named "test_cooking_recipe_values" + set the recipe cooking time to 20 seconds + set the recipe category to misc cooking category + set the recipe experience to 100 + set the recipe group to "test_cooking_recipe_values" + set the recipe result to copper ingot named "test_cooking_recipe_values" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_cooking_recipe_values" + assert {_get} is set with "Blasting recipe was not added to the server" + + assert the recipe cooking time of {_recipe} is 20 seconds with "Cooking time mismatch of created recipe" + assert the recipe category of {_recipe} is misc cooking category with "Cooking category mismatch of created recipe" + assert the recipe experience of {_recipe} is 100 with "Experience mismatch of created recipe" + assert the recipe group of {_recipe} is "test_cooking_recipe_values" with "Group mismatch of created recipe" + assert the recipe key of {_recipe} is "skript:test_cooking_recipe_values" with "Recipe key mismatch of created recipe" + + assert the recipe cooking time of {_get} is 20 seconds with "Cooking time mismatch of retrieved recipe" + assert the recipe category of {_get} is misc cooking category with "Cooking category mismatch of retrieved recipe" + assert the recipe experience of {_get} is 100 with "Experience mismatch of retrieved recipe" + assert the recipe group of {_get} is "test_cooking_recipe_values" with "Group mismatch of retrieved recipe" + assert the recipe key of {_get} is "skript:test_cooking_recipe_values" with "Recipe key mismatch of retrieved recipe" + +test "crafting recipe values": + ### + Combines: + ExprRecipeCategory + ExprRecipeGroup + ExprGetRecipe + ExprRecipeKey + ### + + set {_recipe} to a new shaped recipe with the key "test_crafting_recipe_values": + set the recipe ingredients to oak log, oak planks and oak wood + set the recipe category to misc crafting category + set the recipe group to "test_crafting_recipe_values" + set the recipe result to oak sapling + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_crafting_recipe_values" + assert {_get} is set with "Shaped recipe was not added to the server" + + assert the recipe category of {_recipe} is misc crafting category with "Crafting category mismatch of created recipe" + assert the recipe group of {_recipe} is "test_crafting_recipe_values" with "Group mismatch of created recipe" + assert the recipe key of {_recipe} is "skript:test_crafting_recipe_values" with "Recipe key mismatch of created recipe" + + assert the recipe category of {_get} is misc crafting category with "Crafting category mismatch of retrieved recipe" + assert the recipe group of {_get} is "test_crafting_recipe_values" with "Group mismatch of retrieved recipe" + assert the recipe key of {_get} is "skript:test_crafting_recipe_values" with "Recipe key mismatch of retrieved recipe" diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecCreateRecipe.sk b/src/test/skript/tests/syntaxes/sections/ExprSecCreateRecipe.sk new file mode 100644 index 00000000000..d96194e9e48 --- /dev/null +++ b/src/test/skript/tests/syntaxes/sections/ExprSecCreateRecipe.sk @@ -0,0 +1,147 @@ +### + Combines: + ExprSecCreateRecipe + ExprRecipeType +### + +test "register shaped recipe": + set {_recipe} to a new shaped recipe with the key "test_register_shaped_recipe": + set the recipe ingredients to diamond, air, diamond, air, emerald, air, diamond, air and diamond + set the recipe result to netherite sword named "test_register_shaped_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_shaped_recipe" + assert {_get} is set with "Shaped recipe was not added to the server" + + assert the recipe type of {_recipe} is shaped recipe with "Created recipe is not a shaped recipe" + assert the recipe type of {_get} is shaped recipe with "Retrieved recipe is not a shaped recipe" + +test "register shapeless recipe": + set {_recipe} to a new shapeless recipe with the key "test_register_shapeless_recipe": + set the recipe ingredients to diamond, iron ingot and netherite ingot + set the recipe result to netherite sword named "test_register_shapeless_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_shapeless_recipe" + assert {_get} is set with "Shapeless recipe was not added to the server" + + assert the recipe type of {_recipe} is shapeless recipe with "Created recipe is not a shapeless recipe" + assert the recipe type of {_get} is shapeless recipe with "Retrieved recipe is not a shapeless recipe" + +test "register shapeless recipe fail": + parse: + set {_recipe} to a new shapeless recipe with the key "test_register_shapeless_recipe_fail": + set the recipe ingredients of the 1st row to iron ingot, diamond and gold ingot #Fail + set the recipe result to netherite sword named "test_register_shapeless_recipe_fail" + assert last parse logs contains "This can only be used when creating a Shaped Recipe." with "Ingredients of a row did not fail for Shapeless recipe" + +test "register blasting recipe": + set {_recipe} to a new blasting recipe with the key "test_register_blasting_recipe": + set the recipe input item to raw iron named "test_register_blasting_recipe" + set the recipe result to iron ingot named "test_register_blasting_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_blasting_recipe" + assert {_get} is set with "Blasting recipe was not added to the server" + + assert the recipe type of {_recipe} is blasting recipe with "Created recipe is not a blasting recipe" + assert the recipe type of {_get} is blasting recipe with "Retrieved recipe is not a blasting recipe" + +test "register furnace recipe": + set {_recipe} to a new furnace recipe with the key "test_register_furnace_recipe": + set the recipe input item to raw gold named "test_register_furnace_recipe" + set the recipe result to gold ingot named "test_register_furnace_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_furnace_recipe" + assert {_get} is set with "Furnace recipe was not added to the server" + + assert the recipe type of {_recipe} is furnace recipe with "Created recipe is not a furnace recipe" + assert the recipe type of {_get} is furnace recipe with "Retrieved recipe is not a furnace recipe" + +test "register campfire recipe": + set {_recipe} to a new campfire recipe with the key "test_register_campfire_recipe": + set the recipe input item to raw copper named "test_register_campfire_recipe" + set the recipe result to copper ingot named "test_register_campfire_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_campfire_recipe" + assert {_get} is set with "Campfire recipe was not added to the server" + + assert the recipe type of {_recipe} is campfire recipe with "Created recipe is not a campfire recipe" + assert the recipe type of {_get} is campfire recipe with "Retrieved recipe is not a campfire recipe" + +test "register smoking recipe": + set {_recipe} to a new smoking recipe with the key "test_register_smoking_recipe": + set the recipe input item to raw iron named "test_register_smoking_recipe" + set the recipe result to iron ingot named "test_register_smoking_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_smoking_recipe" + assert {_get} is set with "Smoking recipe was not added to the server" + + assert the recipe type of {_recipe} is smoking recipe with "Created recipe is not a smoking recipe" + assert the recipe type of {_get} is smoking recipe with "Retrieved recipe is not a smoking recipe" + +test "register smithing recipe" when running below minecraft "1.20": + set {_recipe} to a new smithing recipe with the key "test_register_smithing_recipe": + set the recipe base item to iron ingot named "test_register_smithing_recipe" + set the recipe additional item to gold ingot named "test_register_smithing_recipe" + set the recipe result to netherite ingot named "test_register_smithing_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_smithing_recipe" + assert {_get} is set with "Smithing recipe was not added to the server" + + assert the recipe type of {_recipe} is smithing recipe with "Created recipe is not a smithing recipe" + assert the recipe type of {_get} is smithing recipe with "Retrieved recipe is not a smithing recipe" + +test "register smithing transform recipe": + set {_recipe} to a new smithing transform recipe with the key "test_register_smithing_transform_recipe": + set the recipe base item to iron ingot named "test_register_smithing_transform_recipe" + set the recipe additional item to gold ingot named "test_register_smithing_transform_recipe" + set the recipe template item to diamond named "test_register_smithing_transform_recipe" + set the recipe result to netherite ingot named "test_register_smithing_transform_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_smithing_transform_recipe" + assert {_get} is set with "Smithing Transform recipe was not added to the server" + + if running below minecraft "1.20": + assert the recipe type of {_recipe} is smithing recipe with "Created recipe is not a smithing recipe" + assert the recipe type of {_get} is smithing recipe with "Retrieved recipe is not a smithing recipe" + else: + assert the recipe type of {_recipe} is smithing transform recipe with "Created recipe is not a smithing transform recipe" + assert the recipe type of {_get} is smithing transform recipe with "Retrieved recipe is not a smithing transform recipe" + +test "register smithing trim recipe": + set {_recipe} to a new smithing trim recipe with the key "test_register_smithing_trim_recipe": + set the recipe base item to iron ingot named "test_register_smithing_trim_recipe" + set the recipe additional item to gold ingot named "test_register_smithing_trim_recipe" + set the recipe template item to diamond named "test_register_smithing_trim_recipe" + set the recipe result to netherite ingot named "test_register_smithing_trim_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_smithing_trim_recipe" + assert {_get} is set with "Smithing Trim recipe was not added to the server" + + if running below minecraft "1.20": + assert the recipe type of {_recipe} is smithing recipe with "Created recipe is not a smithing recipe" + assert the recipe type of {_get} is smithing recipe with "Retrieved recipe is not a smithing recipe" + else: + assert the recipe type of {_recipe} is smithing trim recipe with "Created recipe is not a smithing trim recipe" + assert the recipe type of {_get} is smithing trim recipe with "Retrieved recipe is not a smithing trim recipe" + +test "register stonecutting recipe": + set {_recipe} to a new stonecutting recipe with the key "test_register_stonecutting_recipe": + set the recipe input item to stone named "test_register_stonecutting_recipe" + set the recipe result item to smooth stone named "test_register_stonecutting_recipe" + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_stonecutting_recipe" + assert {_get} is set with "Stonecutting recipe was not added to the server" + + assert the recipe type of {_recipe} is stonecutting recipe with "Created recipe is not a stonecutting recipe" + assert the recipe type of {_get} is stonecutting recipe with "Retrieved recipe is not a stonecutting recipe" + +test "register transmute recipe" when running minecraft "1.21.2": + set {_recipe} to a new transmute recipe with the key "test_register_transmute_recipe": + set the recipe input item to leather helmet + set the recipe transmute item to nether star + set the recipe result item to netherite helmet + add {_recipe} to the server's recipes + set {_get} to the recipe with the key "test_register_transmute_recipe" + assert {_get} is set with "Transmute recipe was not added to the server" + + assert the recipe type of {_recipe} is transmute recipe with "Created recipe is not a transmute recipe" + assert the recipe type of {_recipe} is transmute recipe with "Retrieved recipe is not a transmute recipe"