Crafting stat has doubled its power:

+ Edit any number of recipes for the item (not just one) from the GUI
+ Specify the output amount of each recipe
+ Choose what ingredients turn into per-recipe (like milk buckets turning empty when crafting cakes)
+ Much better API
~ Option to hide the recipe from the recipe book, and to get a book that unlocks it, but recipes unlocked this way are lost when relogging...

**Requires latest MythicLib**
This commit is contained in:
Gunging 2021-06-26 12:50:40 -04:00
parent 33a191ffaf
commit 646b440281
55 changed files with 5141 additions and 683 deletions

View File

@ -215,7 +215,7 @@ public class ItemStats {
// Crafting Stats // Crafting Stats
CRAFTING = new Crafting(), CRAFTING = new Crafting(),
CRAFT_PERMISSION = new StringStat("CRAFT_PERMISSION", VersionMaterial.OAK_SIGN.toMaterial(), "Crafting Recipe Permission", new String[]{"The permission needed to craft this item.", "Changing this value requires &o/mi reload recipes&7."}, new String[]{"all"}), CRAFT_PERMISSION = new StringStat("CRAFT_PERMISSION", VersionMaterial.OAK_SIGN.toMaterial(), "Crafting Recipe Permission", new String[]{"The permission needed to craft this item.", "Changing this value requires &o/mi reload recipes&7."}, new String[]{"all"}),
CRAFT_AMOUNT = new DoubleStat("CRAFTED_AMOUNT", Material.WOODEN_AXE, "Crafted Amount", new String[]{"The stack count for", "this item when crafted."}, new String[]{"all"}), //CRAFT_AMOUNT = new DoubleStat("CRAFTED_AMOUNT", Material.WOODEN_AXE, "Crafted Amount", new String[]{"The stack count for", "this item when crafted."}, new String[]{"all"}),
// Unique Stats // Unique Stats
AUTOSMELT = new BooleanStat("AUTOSMELT", Material.COAL, "Autosmelt", new String[]{"If set to true, your tool will", "automaticaly smelt mined ores."}, new String[]{"tool"}), AUTOSMELT = new BooleanStat("AUTOSMELT", Material.COAL, "Autosmelt", new String[]{"If set to true, your tool will", "automaticaly smelt mined ores."}, new String[]{"tool"}),

View File

@ -38,6 +38,7 @@ import net.Indyuce.mmoitems.comp.rpg.DefaultHook;
import net.Indyuce.mmoitems.comp.rpg.McMMOHook; import net.Indyuce.mmoitems.comp.rpg.McMMOHook;
import net.Indyuce.mmoitems.comp.rpg.RPGHandler; import net.Indyuce.mmoitems.comp.rpg.RPGHandler;
import net.Indyuce.mmoitems.gui.PluginInventory; import net.Indyuce.mmoitems.gui.PluginInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeBrowserGUI;
import net.Indyuce.mmoitems.gui.listener.GuiListener; import net.Indyuce.mmoitems.gui.listener.GuiListener;
import net.Indyuce.mmoitems.listener.*; import net.Indyuce.mmoitems.listener.*;
import net.Indyuce.mmoitems.manager.*; import net.Indyuce.mmoitems.manager.*;
@ -140,6 +141,7 @@ public class MMOItems extends LuminePlugin {
new SpigotPlugin(39267, this).checkForUpdate(); new SpigotPlugin(39267, this).checkForUpdate();
new MMOItemsMetrics(); new MMOItemsMetrics();
RecipeBrowserGUI.registerNativeRecipes();
abilityManager.initialize(); abilityManager.initialize();
configManager = new ConfigManager(); configManager = new ConfigManager();

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api; package net.Indyuce.mmoitems.api;
import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.MythicLib;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.droptable.DropTable; import net.Indyuce.mmoitems.api.droptable.DropTable;
import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.NumericStatFormula; import net.Indyuce.mmoitems.api.util.NumericStatFormula;
@ -8,142 +9,162 @@ import net.Indyuce.mmoitems.comp.itemglow.TierColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
public class ItemTier { public class ItemTier {
private final String name, id; @NotNull private final String name, id;
// unidentification // Unidentification
private final UnidentificationInfo unidentificationInfo; @NotNull private final UnidentificationInfo unidentificationInfo;
// deconstruction // deconstruction
private final DropTable deconstruct; @Nullable private final DropTable deconstruct;
// item glow options // item glow options
private final TierColor color; @Nullable private TierColor color = null;
private final boolean hint; private boolean hint = false;
// item generation // item generation
private final double chance; private final double chance;
private final NumericStatFormula capacity; @Nullable private final NumericStatFormula capacity;
private static final Random RANDOM = new Random(); @NotNull private static final Random RANDOM = new Random();
private static final boolean GLOW = Bukkit.getPluginManager().getPlugin("GlowAPI") != null; private static final boolean GLOW = Bukkit.getPluginManager().getPlugin("GlowAPI") != null;
public ItemTier(ConfigurationSection config) { /**
* Load an ItemTier from the YML Configuration Itself
*
* @param config Configuration section to get all values from
*/
public ItemTier(@NotNull ConfigurationSection config) {
// The name and ID, crucial parts.
id = config.getName().toUpperCase().replace("-", "_"); id = config.getName().toUpperCase().replace("-", "_");
name = MythicLib.plugin.parseColors(config.getString("name")); name = MythicLib.plugin.parseColors(config.getString("name"));
deconstruct = config.contains("deconstruct-item") ? new DropTable(config.getConfigurationSection("deconstruct-item")) : null;
unidentificationInfo = new UnidentificationInfo(config.getConfigurationSection("unidentification"));
// Deconstruct and Unidentification
deconstruct = config.contains("deconstruct-item") ? new DropTable(config.getConfigurationSection("deconstruct-item")) : null;
ConfigurationSection unidentificationSection = config.getConfigurationSection("unidentification");
if (unidentificationSection == null) { unidentificationInfo = getDefaultUnident(); }
else { unidentificationInfo = new UnidentificationInfo(unidentificationSection); }
//noinspection ErrorNotRethrown
try { try {
hint = config.contains("item-glow") && config.getBoolean("item-glow.hint");
color = config.contains("item-glow") ? new TierColor(config.getString("item-glow.color"), GLOW) : null; // Is it defined?
ConfigurationSection glowSection = config.getConfigurationSection("item-glow");
// Alr then lets read it
if (glowSection != null) {
// Does it hint?
hint = glowSection.getBoolean("hint");
// Does it color?
color = new TierColor(config.getString("color", "WHITE"), GLOW);
}
} catch (NoClassDefFoundError | IllegalAccessException | NoSuchFieldException | SecurityException exception) { } catch (NoClassDefFoundError | IllegalAccessException | NoSuchFieldException | SecurityException exception) {
throw new IllegalArgumentException("Could not load tier color: " + exception.getMessage());
// No hints
hint = false;
color = null;
// Grrr but GlowAPI crashing shall not crash MMOItems tiers wtf
MMOItems.print(null, "Could not load glow color for tier $r{0}$b;$f {1}", "Tier Hints", id, exception.getMessage());
} }
// What are the chances?
chance = config.getDouble("generation.chance"); chance = config.getDouble("generation.chance");
capacity = config.contains("generation.capacity") ? new NumericStatFormula(config.getConfigurationSection("generation.capacity")) : null; capacity = config.contains("generation.capacity") ? new NumericStatFormula(config.getConfigurationSection("generation.capacity")) : null;
} }
public String getId() { @NotNull public String getId() { return id; }
return id;
}
public String getName() { @NotNull public String getName() { return name; }
return name;
}
public boolean hasDropTable() { public boolean hasDropTable() { return deconstruct != null; }
return deconstruct != null;
}
public DropTable getDropTable() { @Nullable public DropTable getDropTable() { return deconstruct; }
return deconstruct;
}
public boolean hasColor() { public boolean hasColor() { return color != null; }
return color != null;
}
public TierColor getColor() { @Nullable public TierColor getColor() { return color; }
return color;
}
public boolean isHintEnabled() { public boolean isHintEnabled() { return hint; }
return hint;
}
/** /**
* @return The chance of the tier being chosen when generating a random item * @return The chance of the tier being chosen when generating a random item
*/ */
public double getGenerationChance() { public double getGenerationChance() { return chance; }
return chance;
}
/** /**
* @return If the item tier has a modifier capacity ie if this tier let * @return If the item tier has a modifier capacity ie if this tier let
* generated items have modifiers * generated items have modifiers
*/ */
public boolean hasCapacity() { public boolean hasCapacity() { return capacity != null; }
return capacity != null;
}
/** /**
* @return The formula for modifier capacity which can be then rolled to * @return The formula for modifier capacity which can be then rolled to
* generate a random amount of modifier capacity when generating a * generate a random amount of modifier capacity when generating a
* random item * random item
*/ */
public NumericStatFormula getModifierCapacity() { @Nullable public NumericStatFormula getModifierCapacity() { return capacity; }
return capacity;
}
public UnidentificationInfo getUnidentificationInfo() { @NotNull public UnidentificationInfo getUnidentificationInfo() { return unidentificationInfo; }
return unidentificationInfo;
}
/** /**
* @return Reads the deconstruction drop table. This may return a list * @return Reads the deconstruction drop table. This may return a list
* containing multiple items and they should all be added to the * containing multiple items and they should all be added to the
* player's inventory * player's inventory
*/ */
public List<ItemStack> getDeconstructedLoot(PlayerData player) { public List<ItemStack> getDeconstructedLoot(@NotNull PlayerData player) {
//noinspection ConstantConditions
return hasDropTable() ? deconstruct.read(player, false) : new ArrayList<>(); return hasDropTable() ? deconstruct.read(player, false) : new ArrayList<>();
} }
/**
* @return Default unidentification info, if it is missing in the config.
*/
@NotNull private UnidentificationInfo getDefaultUnident() { return new UnidentificationInfo(UnidentificationInfo.UNIDENT_NAME, UnidentificationInfo.UNIDENT_PREFIX, 0); }
public class UnidentificationInfo { public class UnidentificationInfo {
private final String name, prefix; @NotNull private final String unidentificationName, prefix;
private final int range; private final int range;
public UnidentificationInfo(ConfigurationSection config) { public static final String UNIDENT_NAME = "Unidentified Item";
this(color(config.getString("name")), color(config.getString("prefix")), config.getInt("range")); public static final String UNIDENT_PREFIX = "Unknown";
public UnidentificationInfo(@NotNull ConfigurationSection config) {
this(color(config.getString("name", UNIDENT_NAME)), color(config.getString("prefix", UNIDENT_PREFIX)), config.getInt("range"));
} }
public UnidentificationInfo(String name, String prefix, int range) { public UnidentificationInfo(@NotNull String name, @NotNull String prefix, int range) {
this.name = name; unidentificationName = name;
this.prefix = prefix; this.prefix = prefix;
this.range = range; this.range = range;
} }
public String getPrefix() { @NotNull public String getPrefix() { return prefix; }
return prefix;
}
public String getDisplayName() { @NotNull public String getDisplayName() {
return name; return unidentificationName;
} }
public int[] calculateRange(int level) { public int[] calculateRange(int level) {
int min = (int) Math.max(1, (level - (double) range * RANDOM.nextDouble())); int min = (int) Math.max(1, (level - (double) range * RANDOM.nextDouble()));
return new int[] { min, min + range }; return new int[] { min, min + range };
} }
} }
private String color(String str) { private String color(@Nullable String str) {
return MythicLib.plugin.parseColors(str); return MythicLib.plugin.parseColors(str);
} }
} }

View File

@ -6,6 +6,8 @@ import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class ReforgeOptions { public class ReforgeOptions {
public static boolean dropRestoredGems; public static boolean dropRestoredGems;
@ -28,6 +30,42 @@ public class ReforgeOptions {
public void setKeepCase(@NotNull String kc) { keepCase = kc; } public void setKeepCase(@NotNull String kc) { keepCase = kc; }
@NotNull public String getKeepCase() { return keepCase; } @NotNull public String getKeepCase() { return keepCase; }
@NotNull ArrayList<String> blacklistedItems = new ArrayList<>();
/**
* Apparently, people like to use MMOItems for quests. This
* will make items NEVER update with RevID
*
* @param mmoitemID Item ID. Listen, including MMOItem Type as
* well is unnecessary hassle, complicates the
* implementation.
*
* People who name all their items "1", "2", ...
* can learn to not use magic numbers ffs.
*
* @return If this item should not update with RevID (when passing
* these options, of course).
*/
public boolean isBlacklisted(@NotNull String mmoitemID) { return blacklistedItems.contains(mmoitemID); }
/**
* Apparently, people like to use MMOItems for quests. This
* will make items NEVER update with RevID
*
* @param mmoitemID Item ID. Listen, including MMOItem Type as
* well is unnecessary hassle, complicates the
* implementation.
*
* People who name all their items "1", "2", ...
* can learn to not use magic numbers ffs.
*/
public void addToBlacklist(@NotNull String mmoitemID) { blacklistedItems.add(mmoitemID); }
/**
* No MMOItem-ID restrictions on RevID.
*/
public void clearBlacklist() { blacklistedItems.clear(); }
public ReforgeOptions(ConfigurationSection config) { public ReforgeOptions(ConfigurationSection config) {
this.keepName = config.getBoolean("display-name"); this.keepName = config.getBoolean("display-name");
this.keepLore = config.getBoolean("lore"); this.keepLore = config.getBoolean("lore");

View File

@ -1,25 +1,27 @@
package net.Indyuce.mmoitems.api.crafting.recipe; package net.Indyuce.mmoitems.api.crafting.recipe;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicBlueprintInventory; import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicRecipeInventory; import io.lumine.mythic.lib.api.crafting.ingredients.*;
import io.lumine.mythic.lib.api.crafting.outputs.MRORecipe; import io.lumine.mythic.lib.api.crafting.outputs.MRORecipe;
import io.lumine.mythic.lib.api.crafting.outputs.MythicRecipeOutput; import io.lumine.mythic.lib.api.crafting.outputs.MythicRecipeOutput;
import io.lumine.mythic.lib.api.crafting.recipes.MythicCachedResult; import io.lumine.mythic.lib.api.crafting.recipes.MythicCachedResult;
import io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipe;
import io.lumine.mythic.lib.api.crafting.recipes.vmp.VanillaInventoryMapping; import io.lumine.mythic.lib.api.crafting.recipes.vmp.VanillaInventoryMapping;
import io.lumine.mythic.lib.api.item.NBTItem; import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.api.util.Ref; import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers; import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.interaction.GemStone; import net.Indyuce.mmoitems.api.interaction.GemStone;
import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate; import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.stat.Enchants; import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.stat.data.EnchantListData; import net.Indyuce.mmoitems.stat.data.EnchantListData;
import net.Indyuce.mmoitems.stat.data.GemSocketsData; import net.Indyuce.mmoitems.stat.data.GemSocketsData;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryAction;
@ -47,12 +49,165 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
* @param enchantmentTreatment Should enchantments be destroyed? * @param enchantmentTreatment Should enchantments be destroyed?
* @param upgradeTreatment How will upgrades combine? * @param upgradeTreatment How will upgrades combine?
*/ */
public CustomSmithingRecipe(@NotNull MMOItemTemplate outputItem, boolean dropGemstones, @NotNull SmithingCombinationType enchantmentTreatment, @NotNull SmithingCombinationType upgradeTreatment) { public CustomSmithingRecipe(@NotNull MMOItemTemplate outputItem, boolean dropGemstones, @NotNull SmithingCombinationType enchantmentTreatment, @NotNull SmithingCombinationType upgradeTreatment, int outputAmount) {
this.outputItem = outputItem; this.outputItem = outputItem;
this.dropGemstones = dropGemstones; this.dropGemstones = dropGemstones;
this.enchantmentTreatment = enchantmentTreatment; this.enchantmentTreatment = enchantmentTreatment;
this.upgradeTreatment = upgradeTreatment; } this.upgradeTreatment = upgradeTreatment;
this.outputAmount = outputAmount;
}
//region Advanced Variant
/**
* If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
@Nullable
MythicRecipe mainInputConsumption;
/**
* If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
@Nullable public MythicRecipe getMainInputConsumption() { return mainInputConsumption; }
/**
* @param mic If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
public void setMainInputConsumption(@Nullable MythicRecipe mic) { mainInputConsumption = nullifyIfEmpty(mic); }
/**
* @return If the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
public boolean hasInputConsumption() { return ingotInputConsumption != null || mainInputConsumption != null; }
/**
* @param mic Some mythic recipe
*
* @return <code>null</code> if there is not a single actual item in this MythicRecipe,
* or the MythicRecipe itself.
*/
@Nullable public MythicRecipe nullifyIfEmpty(@Nullable MythicRecipe mic) {
// Null is just null
if (mic == null) { return null; }
// Anything not air will count a success
for (MythicRecipeIngredient mri : mic.getIngredients()) {
if (mri == null) { continue; }
if (mri.getIngredient().isDefinesItem()) { return mic; } }
// Nope, nothing that wasn't air
return null;
}
/**
* If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
@Nullable
MythicRecipe ingotInputConsumption;
/**
* If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
@Nullable public MythicRecipe getIngotInputConsumption() { return ingotInputConsumption; }
/**
* @param mic If this is not null, then the ingredients themselves will change as this output resolves
* (like milk buckets turning into normal buckets when crafting a cake).
*/
public void setIngotInputConsumption(@Nullable MythicRecipe mic) { ingotInputConsumption = nullifyIfEmpty(mic); }
/**
* Generates a new, independent MythicRecipeInventory
* from the recipe, with random output where possible.
*
* @return A new result to be given to the player.
*/
@NotNull MythicRecipeInventory generateResultOf(@NotNull MythicRecipe mythicRecipe) {
// Rows yes
HashMap<Integer, ItemStack[]> rowsInformation = new HashMap<>();
// Ok it doesn't exist lets build it
for (MythicRecipeIngredient mmIngredient : mythicRecipe.getIngredients()) {
// Ignore
if (mmIngredient == null) { continue; }
// Identify Ingredient
ShapedIngredient shaped = ((ShapedIngredient) mmIngredient);
MythicIngredient ingredient = mmIngredient.getIngredient();
// Does not define an item? I sleep
if (!ingredient.isDefinesItem()) { continue; }
// Any errors yo?
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Recipe of " + getOutputItem().getType().getId() + " " + getOutputItem().getId());
/*
* First we must get the material of the base, a dummy
* item basically (since this is for display) which we
* may only display if its the only substitute of this
* ingredient.
*
* If the ingredient has more substitutes, the ingredient
* description will be used instead, replacing the meta of
* this item entirely.
*/
ItemStack gen = mmIngredient.getIngredient().getRandomSubstituteItem(ffp);
// Valid?
if (gen != null) {
// Get current row
ItemStack[] row = rowsInformation.get(-shaped.getVerticalOffset());
if (row == null) { row = new ItemStack[(shaped.getHorizontalOffset() + 1)]; }
if (row.length < (shaped.getHorizontalOffset() + 1)) {
ItemStack[] newRow = new ItemStack[(shaped.getHorizontalOffset() + 1)];
//noinspection ManualArrayCopy
for (int r = 0; r < row.length; r++) { newRow[r] = row[r]; }
row = newRow;
}
// Yes
row[shaped.getHorizontalOffset()] = gen;
// Put
rowsInformation.put(-shaped.getVerticalOffset(), row);
// Log those
} else {
// All those invalid ones should log.
ffp.sendTo(FriendlyFeedbackCategory.ERROR, MythicLib.plugin.getServer().getConsoleSender());
}
}
// Add all rows into new
MythicRecipeInventory ret = new MythicRecipeInventory();
for (Integer h : rowsInformation.keySet()) { ret.setRow(h, rowsInformation.get(h)); }
// Yes
return ret;
}
//endregion
/**
* The amount of output produced by one smithing
*/
int outputAmount;
/**
* @return The amount of output produced by one smithing
*/
public int getOutputAmount() { return outputAmount; }
/**
* @param amount The amount of output produced by one smithing
*/
public void setOutputAmount(int amount) { outputAmount = amount; }
/** /**
* The MMOItem that results from the completion of these recipes. * The MMOItem that results from the completion of these recipes.
@ -169,9 +324,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
Ref<ArrayList<ItemStack>> droppedGemstones = new Ref<>(); Ref<ArrayList<ItemStack>> droppedGemstones = new Ref<>();
MMOItem display = fromCombinationWith(itemMMO, ingotMMO, player, droppedGemstones); MMOItem display = fromCombinationWith(itemMMO, ingotMMO, player, droppedGemstones);
// Result //RDR// MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Custom Smithing Recipe Result\u00a7e" + times + "\u00a77 times\u00a78 ~\u00a71 " + eventTrigger.getAction().toString());
MythicRecipeInventory result = otherInventories.getResultInventory().clone();
result.setItemAt(map.getResultWidth(map.getResultInventoryStart()), map.getResultHeight(map.getResultInventoryStart()), display.newBuilder().build());
/* /*
* Crafting the item only once allows to put it in the cursor. * Crafting the item only once allows to put it in the cursor.
@ -182,6 +335,10 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
*/ */
if (times == 1 && (eventTrigger.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY)) { if (times == 1 && (eventTrigger.getAction() != InventoryAction.MOVE_TO_OTHER_INVENTORY)) {
// Result
MythicRecipeInventory result = otherInventories.getResultInventory().clone();
result.setItemAt(map.getResultWidth(map.getResultInventoryStart()), map.getResultHeight(map.getResultInventoryStart()), display.newBuilder().build());
/* /*
* When crafting with the cursor, we must make sure that: * When crafting with the cursor, we must make sure that:
* *
@ -202,7 +359,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Apply the result // Apply the result
//RDR//MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Processing Result Inventory"); //RDR//MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Processing Result Inventory");
processInventory(resultInventory, result, times); processInventory(resultInventory, result, 1);
//RR//for (String str : resultInventory.toStrings("\u00a78Result \u00a79PR-")) { MythicCraftingManager.log(str); } //RR//for (String str : resultInventory.toStrings("\u00a78Result \u00a79PR-")) { MythicCraftingManager.log(str); }
//RDR//MythicCraftingManager.log("\u00a78RDR \u00a749\u00a77 Finding item to put on cursor"); //RDR//MythicCraftingManager.log("\u00a78RDR \u00a749\u00a77 Finding item to put on cursor");
@ -250,7 +407,56 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Apply result to the cursor // Apply result to the cursor
eventTrigger.getView().setCursor(actualCursor); eventTrigger.getView().setCursor(actualCursor);
// Player is crafting to completion - move to inventory style. // Consume ingredients
consumeIngredients(otherInventories, cache, eventTrigger.getInventory(), map, 1);
/*
* Ok now, the ingredients have been consumed, the item is now in the cursor of the player.
*
* We must now read each of the affected inventories again and apply them with changes.
*/
if (hasInputConsumption()) {
// Items to spit back to the player
ArrayList<ItemStack> inputConsumptionOverflow = new ArrayList<>();
// Changes in the main inventory?
if (getMainInputConsumption() != null) {
// Extract the new values
MythicRecipeInventory mainRead = map.getMainMythicInventory(eventTrigger.getInventory());
// Generate a result from the main input consumption
MythicRecipeInventory addedStuff = generateResultOf(getMainInputConsumption());
// Include overflow
inputConsumptionOverflow.addAll(MRORecipe.stackWhatsPossible(mainRead, addedStuff));
// Apply
map.applyToMainInventory(eventTrigger.getInventory(), mainRead, false);
}
// Changes in the main inventory?
if (getIngotInputConsumption() != null) {
// Extract the new values
MythicRecipeInventory sideRead = map.getSideMythicInventory("ingot", eventTrigger.getInventory());
// Generate a result from the main input consumption
MythicRecipeInventory addedStuff = generateResultOf(getIngotInputConsumption());
// Include overflow
inputConsumptionOverflow.addAll(MRORecipe.stackWhatsPossible(sideRead, addedStuff));
// Apply
map.applyToSideInventory(eventTrigger.getInventory(), sideRead, "ingot", false);
}
// Distribute in inventory call
MRORecipe.distributeInInventoryOrDrop(eventTrigger.getWhoClicked().getInventory(), inputConsumptionOverflow, eventTrigger.getWhoClicked().getLocation());
}
// Player is crafting to completion - move to inventory style.
} else { } else {
/* /*
@ -259,7 +465,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
//RDR//MythicCraftingManager.log("\u00a78RDR \u00a747\u00a77 Reading/Generating Result"); //RDR//MythicCraftingManager.log("\u00a78RDR \u00a747\u00a77 Reading/Generating Result");
// Build the result // Build the result
ArrayList<ItemStack> outputItems = MRORecipe.toItemsList(result);
HashMap<Integer, ItemStack> modifiedInventory = null; HashMap<Integer, ItemStack> modifiedInventory = null;
Inventory inven = player.getInventory(); Inventory inven = player.getInventory();
int trueTimes = 0; int trueTimes = 0;
@ -268,10 +473,41 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
for (int t = 1; t <= times; t++) { for (int t = 1; t <= times; t++) {
//RDR//MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Iteration \u00a7c#" + t); //RDR//MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Iteration \u00a7c#" + t);
// Result
MythicRecipeInventory result = otherInventories.getResultInventory().clone();
result.setItemAt(map.getResultWidth(map.getResultInventoryStart()), map.getResultHeight(map.getResultInventoryStart()), display.newBuilder().build());
ArrayList<ItemStack> localOutput = MRORecipe.toItemsList(result);
//RR//for (String str : localResult.toStrings("\u00a78Result \u00a79RR-")) { io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager.log(str); } //RR//for (String str : localResult.toStrings("\u00a78Result \u00a79RR-")) { io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager.log(str); }
/*
* Is this generating other kinds of output? Account for them
*/
if (hasInputConsumption()) {
// Changes in the main inventory?
if (getMainInputConsumption() != null) {
// Generate a result from the main input consumption
MythicRecipeInventory addedStuff = generateResultOf(getMainInputConsumption());
// Add these to the output
localOutput.addAll(MRORecipe.toItemsList(addedStuff));
}
// Changes in the ingot inventory?
if (getIngotInputConsumption() != null) {
// Generate a result from the main input consumption
MythicRecipeInventory addedStuff = generateResultOf(getIngotInputConsumption());
// Add these to the output
localOutput.addAll(MRORecipe.toItemsList(addedStuff));
}
}
// Send to // Send to
HashMap<Integer, ItemStack> localIterationResult = MRORecipe.distributeInInventory(inven, outputItems, modifiedInventory); HashMap<Integer, ItemStack> localIterationResult = MRORecipe.distributeInInventory(inven, localOutput, modifiedInventory);
// Failed? Break // Failed? Break
if (localIterationResult == null) { if (localIterationResult == null) {
@ -280,7 +516,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
//RR//io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager.log("\u00a78Result \u00a7cIC\u00a77 Iteration Cancelled: \u00a7cNo Inventory Space"); //RR//io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager.log("\u00a78Result \u00a7cIC\u00a77 Iteration Cancelled: \u00a7cNo Inventory Space");
break; break;
// Prepare for next iteration // Prepare for next iteration
} else { } else {
// Store changes // Store changes
@ -302,6 +538,9 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Set // Set
inven.setItem(s, putt); } inven.setItem(s, putt); }
// Consume ingredients
consumeIngredients(otherInventories, cache, eventTrigger.getInventory(), map, times);
} }
// Drop? // Drop?
@ -316,9 +555,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Drop to the world // Drop to the world
player.getWorld().dropItem(player.getLocation(), drop); } } player.getWorld().dropItem(player.getLocation(), drop); } }
// Consume ingredients
consumeIngredients(otherInventories, cache, eventTrigger.getInventory(), map, times);
} }
/** /**
@ -368,7 +604,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
GemStone.ApplyResult res = asGem.applyOntoItem(gen, gen.getType(), "", false, true); GemStone.ApplyResult res = asGem.applyOntoItem(gen, gen.getType(), "", false, true);
// None? // None?
if (res.getType().equals(GemStone.ResultType.SUCCESS) && (res.getResultAsMMOItem() != null)) { if (res.getType() == GemStone.ResultType.SUCCESS && (res.getResultAsMMOItem() != null)) {
// Success that's nice // Success that's nice
gen = res.getResultAsMMOItem(); gen = res.getResultAsMMOItem();
@ -383,7 +619,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
Ref.setValue(rem, remainingStones); Ref.setValue(rem, remainingStones);
// Enchantments? // Enchantments?
if (!getEnchantmentTreatment().equals(SmithingCombinationType.NONE)) { if (getEnchantmentTreatment() != SmithingCombinationType.NONE) {
// Get enchantment data // Get enchantment data
EnchantListData genEnchants = (EnchantListData) gen.getData(ItemStats.ENCHANTS); if (genEnchants == null) { genEnchants = (EnchantListData) ItemStats.ENCHANTS.getClearStatData(); } EnchantListData genEnchants = (EnchantListData) gen.getData(ItemStats.ENCHANTS); if (genEnchants == null) { genEnchants = (EnchantListData) ItemStats.ENCHANTS.getClearStatData(); }
@ -409,7 +645,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
} }
// All right whats the level stuff up now // All right whats the level stuff up now
if (gen.hasUpgradeTemplate() && !(getUpgradeTreatment().equals(SmithingCombinationType.NONE))) { if (gen.hasUpgradeTemplate() && getUpgradeTreatment() != SmithingCombinationType.NONE) {
// All right get the levels of them both // All right get the levels of them both
int itemLevel = 0; if (item != null) { itemLevel = item.getUpgradeLevel(); } int itemLevel = 0; if (item != null) { itemLevel = item.getUpgradeLevel(); }
@ -428,5 +664,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// That's it // That's it
return gen; return gen;
} }
} }

View File

@ -5,6 +5,7 @@ import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.droptable.item.BlockDropItem; import net.Indyuce.mmoitems.api.droptable.item.BlockDropItem;
import net.Indyuce.mmoitems.api.droptable.item.DropItem; import net.Indyuce.mmoitems.api.droptable.item.DropItem;
import net.Indyuce.mmoitems.api.droptable.item.MMOItemDropItem; import net.Indyuce.mmoitems.api.droptable.item.MMOItemDropItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.api.player.PlayerData;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
@ -21,18 +22,22 @@ public class DropTable {
private final Map<String, Subtable> subtables = new HashMap<>(); private final Map<String, Subtable> subtables = new HashMap<>();
public DropTable(ConfigurationSection config) { public DropTable(ConfigurationSection config) {
Validate.notNull(config, "Could not read the config"); Validate.notNull(config, "Could not read the drop table config");
for (String key : config.getKeys(false)) for (String key : config.getKeys(false))
try { try {
// add subtable to list & then to map // Add subtable to list & then to map
for (int j = 0; j < config.getInt(key + ".coef"); j++) for (int j = 0; j < config.getInt(key + ".coef"); j++)
subtablesList.add(key); subtablesList.add(key);
// Include parsed subtable
subtables.put(key, new Subtable(config.getConfigurationSection(key))); subtables.put(key, new Subtable(config.getConfigurationSection(key)));
// Ew
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING,
"Could not read subtable '" + key + "' from drop table '" + config.getName() + "': " + exception.getMessage()); // Print that error message
MMOItems.print(null, "Could not read subtable '$r{0}$b' from drop table '$e{1}$b';&f {2}", key, config.getName(), exception.getMessage());
} }
Validate.notEmpty(subtablesList, "Your droptable must contain at least one subtable"); Validate.notEmpty(subtablesList, "Your droptable must contain at least one subtable");

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api.edition; package net.Indyuce.mmoitems.api.edition;
import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.edition.input.AnvilGUI; import net.Indyuce.mmoitems.api.edition.input.AnvilGUI;
import net.Indyuce.mmoitems.api.edition.input.ChatEdition; import net.Indyuce.mmoitems.api.edition.input.ChatEdition;
@ -79,11 +80,27 @@ public class StatEdition implements Edition {
} }
try { try {
// Perform WhenInput Operation
stat.whenInput(inv, input, info); stat.whenInput(inv, input, info);
// Success
return true; return true;
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + exception.getMessage());
// Add message to the FFP
if (!exception.getMessage().isEmpty()) { inv.getFFP().log(FriendlyFeedbackCategory.ERROR, exception.getMessage()); }
MMOItems.log("\u00a73Sending \u00a7e" + inv.getFFP().messagesTotal() + "\u00a73 to \u00a7e" + inv.getPlayer().getName());
// Log all
inv.getFFP().sendTo(FriendlyFeedbackCategory.ERROR, inv.getPlayer());
inv.getFFP().sendTo(FriendlyFeedbackCategory.FAILURE, inv.getPlayer());
inv.getFFP().clearFeedback();
MMOItems.log("\u00a73Cleared to \u00a7e" + inv.getFFP().messagesTotal());
// No success
return false; return false;
} }
} }

View File

@ -13,6 +13,7 @@ import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate; import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.util.DynamicLore; import net.Indyuce.mmoitems.api.item.util.DynamicLore;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems; import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.stat.DisplayName;
import net.Indyuce.mmoitems.stat.Enchants; import net.Indyuce.mmoitems.stat.Enchants;
import net.Indyuce.mmoitems.stat.data.DoubleData; import net.Indyuce.mmoitems.stat.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.EnchantListData; import net.Indyuce.mmoitems.stat.data.EnchantListData;
@ -150,7 +151,7 @@ public class ItemStackBuilder {
builtMMOItem.setData(stat, s.recalculate(l)); builtMMOItem.setData(stat, s.recalculate(l));
// Add to NBT, if the gemstones were not purged // Add to NBT, if the gemstones were not purged
if ((!s.isClear() || stat instanceof Enchants)) { if ((!s.isClear() || stat instanceof Enchants || stat instanceof DisplayName)) {
//GEM//MMOItems.log("\u00a7a -+- \u00a77Recording History"); //GEM//MMOItems.log("\u00a7a -+- \u00a77Recording History");
addItemTag(new ItemTag(histroy_keyword + stat.getId(), s.toNBTString())); } addItemTag(new ItemTag(histroy_keyword + stat.getId(), s.toNBTString())); }

View File

@ -2,6 +2,7 @@ package net.Indyuce.mmoitems.api.item.mmoitem;
import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.MMOUtils; import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpgradeTemplate; import net.Indyuce.mmoitems.api.UpgradeTemplate;
@ -208,6 +209,12 @@ public class MMOItem implements ItemReference {
mergeableStatHistory.put(stat.getNBTPath(), hist); mergeableStatHistory.put(stat.getNBTPath(), hist);
} }
//region Upgrading API
/**
* @return The tier of this item, if it has one.
*/
@Nullable public ItemTier getTier() { return MMOItems.plugin.getTiers().findTier(this); }
/** /**
* Upgrades this MMOItem one level. * Upgrades this MMOItem one level.
* <p></p> * <p></p>

View File

@ -3,32 +3,36 @@ package net.Indyuce.mmoitems.api.recipe;
import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.version.VersionMaterial; import io.lumine.mythic.lib.version.VersionMaterial;
import net.Indyuce.mmoitems.MMOUtils; import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.manager.RecipeManager;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public enum CraftingType { public enum CraftingType {
SHAPED(21, "The C. Table Recipe (Shaped) for this item", VersionMaterial.CRAFTING_TABLE), SHAPED(21, "The C. Table Recipe (Shaped) for this item", VersionMaterial.CRAFTING_TABLE, null),
SHAPELESS(22, "The C. Table Recipe (Shapeless) for this item", VersionMaterial.CRAFTING_TABLE), SHAPELESS(22, "The C. Table Recipe (Shapeless) for this item", VersionMaterial.CRAFTING_TABLE, null),
FURNACE(23, "The Furnace Recipe for this item", Material.FURNACE), FURNACE(23, "The Furnace Recipe for this item", Material.FURNACE, RecipeManager.BurningRecipeType.FURNACE),
BLAST(29, "The Blast Furnace Recipe for this item", VersionMaterial.BLAST_FURNACE, 1, 14), BLAST(29, "The Blast Furnace Recipe for this item", VersionMaterial.BLAST_FURNACE, RecipeManager.BurningRecipeType.BLAST, 1, 14),
SMOKER(30, "The Smoker Recipe for this item", VersionMaterial.SMOKER, 1, 14), SMOKER(30, "The Smoker Recipe for this item", VersionMaterial.SMOKER, RecipeManager.BurningRecipeType.SMOKER, 1, 14),
CAMPFIRE(32, "The Campfire Recipe for this item", VersionMaterial.CAMPFIRE, 1, 14), CAMPFIRE(32, "The Campfire Recipe for this item", VersionMaterial.CAMPFIRE, RecipeManager.BurningRecipeType.CAMPFIRE, 1, 14),
SMITHING(33, "The Smithing Recipe for this item", VersionMaterial.SMITHING_TABLE, 1, 15); SMITHING(33, "The Smithing Recipe for this item", VersionMaterial.SMITHING_TABLE, null, 1, 15);
private final int slot; private final int slot;
private final String lore; private final String lore;
private final Material material; private final Material material;
private final int[] mustBeHigher; private final int[] mustBeHigher;
private final RecipeManager.BurningRecipeType burning;
private CraftingType(int slot, String lore, VersionMaterial material, int... mustBeHigher) { private CraftingType(int slot, String lore, VersionMaterial material, @Nullable RecipeManager.BurningRecipeType burn, int... mustBeHigher) {
this(slot, lore, material.toMaterial(), mustBeHigher); this(slot, lore, material.toMaterial(), burn, mustBeHigher);
} }
private CraftingType(int slot, String lore, Material material, int... mustBeHigher) { private CraftingType(int slot, String lore, Material material, @Nullable RecipeManager.BurningRecipeType burn, int... mustBeHigher) {
this.slot = slot; this.slot = slot;
this.lore = lore; this.lore = lore;
this.material = material; this.material = material;
this.mustBeHigher = mustBeHigher; this.mustBeHigher = mustBeHigher;
this.burning = burn;
} }
public ItemStack getItem() { public ItemStack getItem() {
@ -46,6 +50,7 @@ public enum CraftingType {
public String getLore() { public String getLore() {
return lore; return lore;
} }
public RecipeManager.BurningRecipeType getBurningType() { return burning; }
public boolean shouldAdd() { public boolean shouldAdd() {
return mustBeHigher.length == 0 || MythicLib.plugin.getVersion().isStrictlyHigher(mustBeHigher); return mustBeHigher.length == 0 || MythicLib.plugin.getVersion().isStrictlyHigher(mustBeHigher);

View File

@ -182,321 +182,4 @@ public class CustomRecipe implements Comparable<CustomRecipe> {
} }
return recipe; return recipe;
} }
/**
* Reads this list of strings as a Shapeless Recipe
* to craft this MMOItem.
*
* @param type The TYPE of the crafted MMOItem
* @param id The ID of the crafted MMOItem
* @param recipe The compactly-stored recipe information
* @return A baked recipe, ready to deploy.
* @throws IllegalArgumentException If the recipe is in incorrect format
*/
@NotNull public static MythicRecipeBlueprint generateShapeless(@NotNull Type type, @NotNull String id, @NotNull List<String> recipe, @NotNull String namespaceKey) throws IllegalArgumentException {
// Get it
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(type, id);
Validate.isTrue(template != null, "Unexpected Error Occurred: Template does not exist.");
// Identify the Provided UIFilters
ArrayList<MythicRecipeIngredient> poofs = new ArrayList<>();
// Error yes
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Recipe of $u" + type + " " + id);
// Read from the recipe
boolean nonAirFound = false;
for (String str : recipe) {
// Null is a sleeper
if (str == null || str.equals("AIR")) { continue; }
// Add
ProvidedUIFilter p = readIngredientFrom(str, ffp);
nonAirFound = true;
poofs.add(new MythicRecipeIngredient(p));
}
if (!nonAirFound) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Shapeless recipe containing only AIR, $fignored$b.")); }
// Build Main
ShapedRecipe shapedRecipe = ShapedRecipe.single(namespaceKey, new ProvidedUIFilter(MMOItemUIFilter.get(), type.getId(), id, Math.max(template.getCraftedAmount(), 1)));
// Make ingredients
ShapelessRecipe inputRecipe = new ShapelessRecipe(namespaceKey, poofs);
// Create Output
MythicRecipeOutput outputRecipe = new MRORecipe(shapedRecipe);
// That's our blueprint :)
return new MythicRecipeBlueprint(inputRecipe, outputRecipe);
}
/**
* Reads this list of strings as a Shaped Recipe
* to craft this MMOItem.
*
* @param type The TYPE of the crafted MMOItem
* @param id The ID of the crafted MMOItem
* @param recipe The compactly-stored recipe information
* @return A baked recipe, ready to deploy.
* @throws IllegalArgumentException If the recipe is in incorrect format
*/
@NotNull public static MythicRecipeBlueprint generateShaped(@NotNull Type type, @NotNull String id, @NotNull List<String> recipe, @NotNull String namespaceKey) throws IllegalArgumentException {
// Get it
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(type, id);
Validate.isTrue(template != null, "Unexpected Error Occurred: Template does not exist.");
// Identify the Provided UIFilters
ArrayList<ShapedIngredient> poofs = new ArrayList<>();
// Error yes
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Recipe of $u" + type + " " + id);
int rowNumber = 0;
// All right lets read them
boolean nonAirFound = false;
for (String row : recipe) {
/*
* This row could be in either legacy or new format, and we will assume no combination of them.
*
* Either:
* ANYTHING ANY.THING ANYTHING
*
* or
* A NYT THIN G|A NYT THING|A NYT THIN G
*/
// What are the three ingredients encoded in this row?
String[] positions;
if (row.contains("|")) {
// Split by |s
positions = row.split("\\|");
// Is legacy
} else {
// Split by spaces
positions = row.split(" ");
}
// Size not 3? BRUH
if (positions.length != 3) { throw new IllegalArgumentException("Invalid crafting table row $u" + row + "$b ($fNot exactly 3 ingredients wide$b)."); }
// Identify
ProvidedUIFilter left = readIngredientFrom(positions[0], ffp);
ProvidedUIFilter center = readIngredientFrom(positions[1], ffp);
ProvidedUIFilter right = readIngredientFrom(positions[2], ffp);
if (!left.isAir()) { nonAirFound = true; }
if (!center.isAir()) { nonAirFound = true; }
if (!right.isAir()) { nonAirFound = true; }
/*
* To detect if a recipe can be crafted in the survival inventory (and remove extra AIR),
* we must see that a whole row AND a whole column be air. Not any column or row though,
* but any of those that do not cross the center.
*
* If a single left item is not air, LEFT is no longer an unsharped column.
* If a single right item is not air, RIGHT is no longer an unsharped column.
*
* All items must be air in TOP or BOTTOM for they to be unsharped.
*/
// Bake
ShapedIngredient leftIngredient = new ShapedIngredient(left, 0, -rowNumber);
ShapedIngredient centerIngredient = new ShapedIngredient(center, 1, -rowNumber);
ShapedIngredient rightIngredient = new ShapedIngredient(right, 2, -rowNumber);
// Parse and add
poofs.add(leftIngredient);
poofs.add(centerIngredient);
poofs.add(rightIngredient);
// Prepare for next row
rowNumber++;
}
if (!nonAirFound) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Shaped recipe containing only AIR, $fignored$b.")); }
// Build Main
ShapedRecipe shapedRecipe = ShapedRecipe.single(namespaceKey, new ProvidedUIFilter(MMOItemUIFilter.get(), type.getId(), id, Math.max(template.getCraftedAmount(), 1)));
// Make ingredients
ShapedRecipe inputRecipe = ShapedRecipe.unsharpen((new ShapedRecipe(namespaceKey, poofs)));
// Create Output
MythicRecipeOutput outputRecipe = new MRORecipe(shapedRecipe);
// That's our blueprint :)
return new MythicRecipeBlueprint(inputRecipe, outputRecipe);
}
/**
* Reads this list of strings as a Smithing Recipe to craft this MMOItem.
*
* @param type The TYPE of the crafted MMOItem
* @param id The ID of the crafted MMOItem
* @param item First item you need to put in the Smithing Station
* @param ingot Second item to put in the smithing station
* @return A baked recipe, ready to deploy.
* @throws IllegalArgumentException If the recipe is in incorrect format
*/
@NotNull public static MythicRecipeBlueprint generateSmithing(@NotNull Type type, @NotNull String id, @NotNull String item, @NotNull String ingot, boolean dropGems, @NotNull String enchantmentBehaviour, @NotNull String upgradeBehaviour, @NotNull String namespaceKey) throws IllegalArgumentException {
// Get it
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(type, id);
Validate.isTrue(template != null, "Unexpected Error Occurred: Template does not exist.");
SmithingCombinationType upgradeEffect = SmithingCombinationType.valueOf(upgradeBehaviour.toUpperCase());
SmithingCombinationType enchantEffect = SmithingCombinationType.valueOf(enchantmentBehaviour.toUpperCase());
// Identify the Provided UIFilters
ArrayList<ShapedIngredient> poofs = new ArrayList<>();
// Error yes
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Recipe of $u" + type + " " + id);
int rowNumber = 0;
// All right lets read them
ProvidedUIFilter itemPoof = readIngredientFrom(item, ffp);
ProvidedUIFilter ingotPoof = readIngredientFrom(ingot, ffp);
if (itemPoof.isAir() || ingotPoof.isAir()) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Smithing recipe containing AIR, $fignored$b.")); }
MythicRecipeIngredient itemIngredient = new MythicRecipeIngredient(itemPoof);
MythicRecipeIngredient ingotIngredient = new MythicRecipeIngredient(ingotPoof);
// Make ingredients
ShapelessRecipe inputItem = new ShapelessRecipe(namespaceKey, itemIngredient);
ShapelessRecipe inputIngot = new ShapelessRecipe(namespaceKey, ingotIngredient);
// Create Output
MythicRecipeOutput outputRecipe = new CustomSmithingRecipe(template, dropGems, enchantEffect, upgradeEffect);
MythicRecipeBlueprint recipe = new MythicRecipeBlueprint(inputItem, outputRecipe);
recipe.addSideCheck("ingot", inputIngot);
// That's our blueprint :)
return recipe;
}
/**
* To support legacy formats, at least for now, we use this method
* to read individual ingredients.
* <p></p>
* It supports the formats:
* <p><code>MATERIAL</code> (legacy vanilla material)
* </p><code>TYPE.ID</code> (legacy MMOItem)
* <p><code>KEY ARGUMENT DATA AMOUNT</code> (current)
* </p>
*
* @param str String that's should be in one of the formats above.
* @param ffp To tell what happened
*
* @throws IllegalArgumentException If not in the correct format.
*
* @return An ingredient read from this string.
*/
@NotNull public static ProvidedUIFilter readIngredientFrom(@NotNull String str, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
/*
* This entry, is it a vanilla material?
*
* Then build it as material.
*/
Material asMaterial = null;
try { asMaterial = Material.valueOf(str.toUpperCase().replace(" ", "_").replace("-", "_")); } catch (IllegalArgumentException ignored) {}
if (asMaterial != null) {
// Is it AIR?
if (asMaterial.isAir()) {
ProvidedUIFilter result = new ProvidedUIFilter(VanillaUIFilter.get(), "AIR", "0");
result.setAmountRange(new QuickNumberRange(null, null));
return result; }
// We snooze if its AIR or such
if (!asMaterial.isItem()) { throw new IllegalArgumentException("Invalid Ingredient $u" + str + "$b ($fNot an Item$b)."); }
// All right create filter and go
ProvidedUIFilter poof = UIFilterManager.getUIFilter("v", asMaterial.toString(), "", "1..", ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffp.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
/*
* Not a vanilla material, lets try to read it as a Legacy MMOItem thing.
*
* It must have a dot, and no spaces.
*/
if (str.contains(".") && !str.contains(" ")) {
// Split by dot
String[] split = str.split("\\.");
// Exactly two?
if (split.length == 2) {
// Well
String iType = split[0];
String iID = split[1];
// All right create filter and go
ProvidedUIFilter poof = UIFilterManager.getUIFilter("m", iType, iID, "1..", ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendAllTo(MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
}
/*
* Not a vanilla Material, but what about a UIFilter itself?
*/
ProvidedUIFilter poof = UIFilterManager.getUIFilter(str, ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendAllTo(MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
} }

View File

@ -181,6 +181,9 @@ public class MMOItemReforger {
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public void update(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) { public void update(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) {
// Cancel if blacklisted
if (options.isBlacklisted(miID)) { return; }
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh) // Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(miTypeName, miID); ItemMeta meta = nbtItem.getItem().getItemMeta(); MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(miTypeName, miID); ItemMeta meta = nbtItem.getItem().getItemMeta();
if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", miTypeName.toString(), miID); mmoItem = null; return; } if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", miTypeName.toString(), miID); mmoItem = null; return; }
@ -419,6 +422,8 @@ public class MMOItemReforger {
*/ */
int regenerate(@Nullable RPGPlayer player, @NotNull MMOItemTemplate template) { int regenerate(@Nullable RPGPlayer player, @NotNull MMOItemTemplate template) {
if (mmoItem == null) { loadLiveMMOItem(); }
int determinedItemLevel; int determinedItemLevel;
if (player == null) { if (player == null) {
@ -497,6 +502,9 @@ public class MMOItemReforger {
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public void reforge(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) { public void reforge(@Nullable RPGPlayer player, @NotNull ReforgeOptions options) {
// Cancel if blacklisted
if (options.isBlacklisted(miID)) { return; }
// Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh) // Initialize as Volatile, find source template. GemStones require a Live MMOItem though (to correctly load all Stat Histories and sh)
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(miTypeName, miID); ItemMeta meta = nbtItem.getItem().getItemMeta(); MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplate(miTypeName, miID); ItemMeta meta = nbtItem.getItem().getItemMeta();
if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", miTypeName.toString(), miID); mmoItem = null; return; } if (template == null) { MMOItems.print(null, "Could not find template for $r{0} {1}$b. ", "MMOItems Reforger", miTypeName.toString(), miID); mmoItem = null; return; }

View File

@ -7,6 +7,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
public abstract class PluginInventory implements InventoryHolder { public abstract class PluginInventory implements InventoryHolder {
protected final PlayerData playerData; protected final PlayerData playerData;
@ -14,7 +15,7 @@ public abstract class PluginInventory implements InventoryHolder {
protected int page = 1; protected int page = 1;
public PluginInventory(Player player) { public PluginInventory(@NotNull Player player) {
this(PlayerData.get(player)); this(PlayerData.get(player));
} }

View File

@ -1,11 +1,13 @@
package net.Indyuce.mmoitems.gui.edition; package net.Indyuce.mmoitems.gui.edition;
import io.lumine.mythic.lib.api.util.AltChar; import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate; import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.template.TemplateModifier; import net.Indyuce.mmoitems.api.item.template.TemplateModifier;
import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.PluginInventory; import net.Indyuce.mmoitems.gui.PluginInventory;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData; import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
import net.Indyuce.mmoitems.stat.type.ItemStat; import net.Indyuce.mmoitems.stat.type.ItemStat;
@ -18,6 +20,7 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -46,11 +49,16 @@ public abstract class EditionInventory extends PluginInventory {
private TemplateModifier editedModifier; private TemplateModifier editedModifier;
private ItemStack cachedItem; private ItemStack cachedItem;
private int previousPage; int previousPage;
public EditionInventory(Player player, MMOItemTemplate template) { public EditionInventory(@NotNull Player player, @NotNull MMOItemTemplate template) {
super(player); super(player);
// For logging back to the player
ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Edition");
// For building the Inventory
this.template = template; this.template = template;
this.configFile = template.getType().getConfigFile(); this.configFile = template.getType().getConfigFile();
player.getOpenInventory(); player.getOpenInventory();
@ -158,4 +166,7 @@ public abstract class EditionInventory extends PluginInventory {
public int getPreviousPage() { public int getPreviousPage() {
return previousPage; return previousPage;
} }
@NotNull final FriendlyFeedbackProvider ffp;
@NotNull public FriendlyFeedbackProvider getFFP() { return ffp; }
} }

View File

@ -0,0 +1,269 @@
package net.Indyuce.mmoitems.gui.edition.recipe;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_Shaped;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_Shapeless;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_Smithing;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.RMGRR_LBBlast;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.RMGRR_LBCampfire;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.RMGRR_LBFurnace;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.RMGRR_LBSmoker;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Set;
/**
* The first menu when a player clicks the 'Crafting' stat is the
* Recipe Browser GUI. It shows all the loaded types of recipes.
* <br> <br>
* From there, the user will choose a type of recipe, and see the
* list of recipes of that type through a {@link RecipeListGUI}.
* <br> <br>
* Finally, the user will choose one of the recipes in the list to
* edit or delete or whatever.
*
* @author Gunging
*/
public class RecipeBrowserGUI extends EditionInventory {
/*
* Item Stacks used in this inven
*/
@NotNull final ItemStack nextPage = ItemFactory.of(Material.ARROW).name(FFPMMOItems.get().getExampleFormat() + "Next Page").build();
@NotNull final ItemStack prevPage = ItemFactory.of(Material.ARROW).name(FFPMMOItems.get().getExampleFormat() + "Previous Page").build();
@NotNull final ItemStack noRecipe = ItemFactory.of(Material.LIGHT_GRAY_STAINED_GLASS_PANE).name("").build();
int currentPage;
/**
* Not gonna lie, I think this class doesnt support concurrent edition of
* the same template. Lets just hope no two users decide to do crafting
* stuff for the same item at the same time. Its untested.
*
* @param player Player that is editing recipes
* @param template Template being edited
*/
public RecipeBrowserGUI(@NotNull Player player, @NotNull MMOItemTemplate template) {
super(player, template);
// Start with defaults
currentPage = 0;
}
@NotNull
@Override
public Inventory getInventory() {
// Create and prepare
Inventory inv = Bukkit.createInventory(this, 54, "Choose Recipe Type");
// Put buttons
addEditionInventoryItems(inv, true);
// Arrange yes
arrangeInventory(inv);
// That's it lets GOOOO
return inv;
}
/**
* A map containing a link between Recipe Type and Absolute Inventory Slot
* (to know that, when a player clicks the inventory thing, which recipe
* they are meaning)
*/
@NotNull final HashMap<Integer, RecipeRegistry> recipeTypeMap = new HashMap<>();
/**
* Updates the inventory, refreshes the page number whatever.
*
* @param inv Inventory object to edit
*/
void arrangeInventory(@NotNull Inventory inv) {
// Start fresh
recipeTypeMap.clear();
// Include page buttons
if (currentPage > 0) { inv.setItem(27, prevPage); }
if (registeredRecipes.size() >= ((currentPage + 1) * 21)) { inv.setItem(36, nextPage); }
// Well order them I guess
HashMap<Integer, RecipeRegistry> reg = new HashMap<>(); int op = 0;
for (RecipeRegistry r : registeredRecipes.values()) { reg.put(op, r); op++; }
// Fill the space I guess
for (int p = 21 * currentPage; p < (21 * (currentPage + 1)); p++) {
//CNT//MMOItems.log("\u00a77Running \u00a73" + p);
/*
* The job of this is to identify which slots of this
* inventory will trigger which action.
*
* If the slot has a recipe to edit, a connection will
* be made between clicking this and which recipe to
* edit via the HashMap 'recipeMap'
*
* But for that we must calculate which absolute slot
* of this inventory are we talking about...
*/
int absolute = page(p);
/*
* Going through the whole page, first thing
* to check is that there is a recipe here.
*
* Note that clicking the very next glass pane
* creates a new recipe.
*/
if (p >= registeredRecipes.size()) {
// Just snooze
inv.setItem(absolute, noRecipe);
// There exists a recipe for this slot
} else {
// Which will it be?
RecipeRegistry rr = reg.get(p);
// Display name
inv.setItem(absolute, rr.getDisplayListItem());
// Store
recipeTypeMap.put(absolute, rr);
}
}
}
public static int page(int p) {
// Remove multiples of 21
int red = SilentNumbers.floor(p / 21.00D);
p -= red * 21;
//CNT//MMOItems.log("\u00a73+\u00a77 Reduced to \u00a79" + p);
/*
* A page is the third, fourth, and fifth rows, excluding the first and last column.
*
* #1 Obtain the relative column, and relative row
*
* #2 Convert to absolute inventory positions
*/
int relRow = SilentNumbers.floor(p / 7.00D);
int relCol = p - (7 * relRow);
//CNT//MMOItems.log("\u00a73+\u00a77 Row \u00a79" + relRow + "\u00a77, Col\u00a79 " + relCol);
// Starting at the third row, each row adds 9 slots.
int rowAdditive = 18 + (relRow * 9);
int columnAdditive = relCol + 1;
// Sum to obtain final
//CNT//MMOItems.log("\u00a7a+\u00a77 Out \u00a7b" + (rowAdditive + columnAdditive));
return rowAdditive + columnAdditive;
}
@Override
public void whenClicked(InventoryClickEvent event) {
// Clicked inventory was not the observed inventory? Not our business
if ((event.getView().getTopInventory() != event.getClickedInventory())) { return; }
// Disallow any clicking.
event.setCancelled(true);
// Selecting a recipe type to browse
if (event.getAction() == InventoryAction.PICKUP_ALL) {
// Previous page
if (event.getSlot() == 27) {
// Retreat page
currentPage--;
arrangeInventory(event.getView().getTopInventory());
// Next Page
} else if (event.getSlot() == 36) {
// Advance page
currentPage++;
arrangeInventory(event.getView().getTopInventory());
// Create a new recipe
} else if (event.getSlot() > 18) {
// A recipe exists of this name?
RecipeRegistry recipeType = recipeTypeMap.get(event.getSlot());
// Well, found anything?
if (recipeType != null) {
// Open that menu for the player
new RecipeListGUI(player, template, recipeType).open(getPreviousPage());
}
}
}
}
//region As Recipe Type Manager
/**
* This is called when MMOItems loads and registers the recipes that
* come with the plugin.
*
* There is no reason to call it again thereafter so I wont even attempt
* to prevent duplicate registrations of these.
*/
public static void registerNativeRecipes() {
registerRecipe(new RMGRR_Smithing());
registerRecipe(new RMGRR_Shapeless());
registerRecipe(new RMGRR_Shaped());
/*
* These don't go through mythiclib, I, gunging, merely
* transcribed them to fit in the new crafting recipe
* code but don't really know how they work.
*
* After the RMGRR_LegacyBurning.sendToMythicLib method (that
* sends the recipes back to RecipeManager.registerBurningRecipe)
* I have no clue what happens; didn't even read that far.
*/
registerRecipe(new RMGRR_LBFurnace());
registerRecipe(new RMGRR_LBBlast());
registerRecipe(new RMGRR_LBSmoker());
registerRecipe(new RMGRR_LBCampfire());
}
@NotNull
static final HashMap<String, RecipeRegistry> registeredRecipes = new HashMap<>();
public static void registerRecipe(@NotNull RecipeRegistry recipe) {
// Register that yes
registeredRecipes.put(recipe.getRecipeConfigPath(), recipe);
}
@NotNull public static Set<String> getRegisteredRecipes() { return registeredRecipes.keySet(); }
/**
* @param name This is only guaranteed to be NotNull if you got it from {@link #getRegisteredRecipes()}.
*
* @return The loaded recipe attached to this path. Must be loaded.
*/
@NotNull public static RecipeRegistry getRegisteredRecipe(@NotNull String name) { return registeredRecipes.get(name); }
//endregion
}

View File

@ -0,0 +1,256 @@
package net.Indyuce.mmoitems.gui.edition.recipe;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
/**
* The Recipe List GUI is in charge of showing all the recipes
* of a specific kind, and allowing the user to choose which
* to edit, delete, or create.
* <br> <br>
* Because theoretically the recipes could overflow, this does
* support paging which is UGH who is ever going to make over 20
* recipes for a single item but whatever.
*
* @author Gunging
*/
public class RecipeListGUI extends EditionInventory {
@NotNull final ItemStack nextPage = ItemFactory.of(Material.ARROW).name("\u00a77Next Page").build();
@NotNull final ItemStack prevPage = ItemFactory.of(Material.ARROW).name("\u00a77Previous Page").build();
@NotNull final ItemStack noRecipe = ItemFactory.of(Material.BLACK_STAINED_GLASS_PANE).name("\u00a77No Recipe").build();
@NotNull final RecipeRegistry recipeType;
@NotNull public RecipeRegistry getRecipeRegistry() { return recipeType; }
@NotNull final ItemStack listedItem;
@NotNull public ItemStack getListedItem() { return listedItem; }
@NotNull final ArrayList<String> recipeNames = new ArrayList<>();
@NotNull public ArrayList<String> getRecipeNames() { return recipeNames; }
boolean invalidRecipe;
public RecipeListGUI(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull RecipeRegistry kind) {
super(player, template);
recipeType = kind;
// Which item to fill the area with?
listedItem = getRecipeRegistry().getDisplayListItem();
// Obtain the crafting section
ConfigurationSection section = RecipeMakerGUI.getSection(getEditedSection(), "crafting");
ConfigurationSection type = RecipeMakerGUI.getSection(section, kind.getRecipeConfigPath());
// What is all the recipes within this kind?
for (String recipeName : type.getValues(false).keySet()) {
if (recipeName == null || recipeName.isEmpty()) { continue; }
/*
* This is now a for loop going through all the recipes
* written onto this item's config.
*/
recipeNames.add(recipeName);
}
}
int currentPage;
int createSlot = -1;
@NotNull final HashMap<Integer, String> recipeMap = new HashMap<>();
@NotNull @Override public Inventory getInventory() {
// Create and prepare
Inventory inv = Bukkit.createInventory(this, 54, "Choose " + getRecipeRegistry().getRecipeTypeName() + " Recipe");
// Put buttons
addEditionInventoryItems(inv, true);
// Arrange yes
arrangeInventory(inv);
// That's it lets GOOOO
return inv;
}
/**
* Updates the inventory, refreshes the page number whatever.
*
* @param inv Inventory object to edit
*/
void arrangeInventory(@NotNull Inventory inv) {
// Start fresh
recipeMap.clear();
createSlot = -1;
// Include page buttons
if (currentPage > 0) { inv.setItem(27, prevPage); }
if (recipeNames.size() >= ((currentPage + 1) * 21)) { inv.setItem(36, nextPage); }
// Fill the space I guess
for (int p = 21 * currentPage; p < 21 * (currentPage + 1); p++) {
/*
* The job of this is to identify which slots of this
* inventory will trigger which action.
*
* If the slot has a recipe to edit, a connection will
* be made between clicking this and which recipe to
* edit via the HashMap 'recipeMap'
*
* But for that we must calculate which absolute slot
* of this inventory are we talking about...
*/
int absolute = page(p);
/*
* Going through the whole page, first thing
* to check is that there is a recipe here.
*
* Note that clicking the very next glass pane
* creates a new recipe.
*/
if (p == recipeNames.size()) {
// Rename list item...
inv.setItem(absolute, RecipeMakerGUI.rename(new ItemStack(Material.NETHER_STAR), FFPMMOItems.get().getBodyFormat() + "Create new " + SilentNumbers.getItemName(getListedItem(), false)));
// If this slot is clicked, a new recipe will be created.
createSlot = absolute;
// The current item is greater, fill with empty glass panes
} else if (p > recipeNames.size()) {
// Just snooze
inv.setItem(absolute, noRecipe);
// There exists a recipe for this slot
} else {
// Display name
inv.setItem(absolute, RecipeMakerGUI.rename(getListedItem().clone(), FFPMMOItems.get().getBodyFormat() + "Edit " + FFPMMOItems.get().getInputFormat() + recipeNames.get(p)));
// Store
recipeMap.put(absolute, recipeNames.get(p));
}
}
}
public static int page(int p) {
// Remove multiples of 21
int red = SilentNumbers.floor(p / 21.00D);
p -= red * 21;
/*
* A page is the third, fourth, and fifth rows, excluding the first and last column.
*
* #1 Obtain the relative column, and relative row
*
* #2 Convert to absolute inventory positions
*/
int relRow = SilentNumbers.floor(p / 7.00D);
int relCol = p - (7 * relRow);
// Starting at the third row, each row adds 9 slots.
int rowAdditive = 18 + (relRow * 9);
int columnAdditive = relCol + 1;
// Sum to obtain final
return rowAdditive + columnAdditive;
}
@Override public void whenClicked(InventoryClickEvent event) {
// Clicked inventory was not the observed inventory? Not our business
if ((event.getView().getTopInventory() != event.getClickedInventory())) { return; }
// Disallow any clicking.
event.setCancelled(true);
if (invalidRecipe) { return; }
// Selecting a recipe to edit (or creating?)
if (event.getAction() == InventoryAction.PICKUP_ALL) {
// Previous page
if (event.getSlot() == 27) {
// Retreat page
currentPage--;
arrangeInventory(event.getView().getTopInventory());
// Next Page
} else if (event.getSlot() == 36) {
// Advance page
currentPage++;
arrangeInventory(event.getView().getTopInventory());
// Create a new recipe
} else if (event.getSlot() == createSlot) {
// Well make sure tha name is not taken
String chadName = String.valueOf(recipeMap.size() + 1);
if (recipeMap.containsValue(chadName)) { chadName = chadName + "_" + UUID.randomUUID(); }
// Create a new one with that chad name
getRecipeRegistry().openForPlayer(this, chadName);
// Might be clicking a recipe to edit then
} else if (event.getSlot() > 18) {
// A recipe exists of this name?
String recipeName = recipeMap.get(event.getSlot());
// Well, found anything?
if (recipeName != null) {
// Open that menu for the player
getRecipeRegistry().openForPlayer(this, recipeName);
}
}
// Deleting a recipe
} else if (event.getAction() == InventoryAction.PICKUP_HALF) {
// A recipe exists of this name?
String recipeName = recipeMap.get(event.getSlot());
// Seems there was
if (recipeName != null) {
// Delete that
ConfigurationSection section = RecipeMakerGUI.getSection(getEditedSection(), "crafting");
ConfigurationSection type = RecipeMakerGUI.getSection(section, getRecipeRegistry().getRecipeConfigPath());
recipeNames.remove(recipeName);
type.set(recipeName, null);
// Refresh
arrangeInventory(event.getView().getTopInventory());
// Register edition
registerTemplateEdition();
}
}
}
}

View File

@ -0,0 +1,82 @@
package net.Indyuce.mmoitems.gui.edition.recipe.interpreters;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The legacy recipes that are not supported by MythicLib that all happen to have to do
* with burning stuff - furnaces, campfires, the other furnaces...
*
* @author Gunging
*/
public class RMGRI_BurningLegacy implements RMG_RecipeInterpreter{
/**
* Interestingly enough, they onl require one input.
*/
@NotNull ProvidedUIFilter input;
/**
* @return The stuff that must be smelted / cooked
*/
@NotNull public ProvidedUIFilter getInput() { return input; }
/**
* Setting it to null will make it into AIR tho but ok.
* This method does not update it in the Config Files.
*
* @param input The stuff that must be smelted
*/
public void setInput(@Nullable ProvidedUIFilter input) { this.input = input == null ? RecipeMakerGUI.AIR : input; }
@NotNull final ConfigurationSection section;
/**
* @return The recipe name section of this recipe. <br>
* <br>
* Basically <b><code>[ID].base.crafting.shaped.[name]</code></b> section
*/
@NotNull public ConfigurationSection getSection() { return section; }
/**
* Generate an interpreter from this configuration section.
*
* @param recipeNameSection <b><code>[ID].base.crafting.furnace.[name]</code></b> section
*/
public RMGRI_BurningLegacy(@NotNull ConfigurationSection recipeNameSection) {
// Save
section = recipeNameSection;
// Furnaces support only input
//noinspection ConstantConditions
input = ProvidedUIFilter.getFromString(RecipeMakerGUI.poofFromLegacy(recipeNameSection.getString(RecipeMakerGUI.INPUT_INGREDIENTS)), null);
if (input == null) { input = RecipeMakerGUI.AIR.clone(); }
}
@Override
public void editInput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
if (slot != 0) { return; }
// Just edit bro
setInput(input);
// Save
section.set(ITEM, input.toString());
}
@Override public void editOutput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) { }
@Override public void deleteInput(@NotNull ConfigurationSection section, int slot) { editInput(section, RecipeMakerGUI.AIR.clone(), slot); }
@Override public void deleteOutput(@NotNull ConfigurationSection section, int slot) { }
@Nullable @Override public ProvidedUIFilter getInput(int slot) { if (slot == 0) { return input; } return null; }
@Nullable @Override public ProvidedUIFilter getOutput(int slot) { return null; }
public static final String ITEM = "item";
public static final String TIME = "time";
public static final String EXPERIENCE = "experience";
}

View File

@ -0,0 +1,280 @@
package net.Indyuce.mmoitems.gui.edition.recipe.interpreters;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* This class is in charge of converting Shaped Recipes to and fro YML format,
* as well as editing it in a YML configuration and such. <br> <br>
*
* YML Save Format: <br> <code>
*
* - A|B|C <br>
* - D|E|F <br>
* - G|H|I
* </code>
*
* @author Gunging
*/
public class RMGRI_Shaped implements RMG_RecipeInterpreter {
/**
* Builds a valid 3x3 matrix of input/output recipe.
*
* @param config List as it is saved in the config.
*
* @return Transcribed into array of arrays.
*/
@NotNull ProvidedUIFilter[][] buildIngredientsFromList(@NotNull List<String> config) {
// Start with a base
ProvidedUIFilter[][] ret = new ProvidedUIFilter[3][3];
// Each row ig
for (int r = 0; r < 3; r++) {
// Get current row
String row = config.size() > r ? config.get(r) : null;
//READ//MMOItems.log("\u00a7b*\u00a77 Reading\u00a7b " + row);
// Update it ig
String s = updateRow(row);
//READ//MMOItems.log("\u00a7b*\u00a77 Updated to\u00a7b " + row);
// Split
String[] poofs = s.split("\\|");
// Parse
for (int p = 0; p < 3; p++) {
String poof = poofs.length > p ? poofs[p] : null;
//READ//MMOItems.log("\u00a7b*\u00a77 Coord\u00a7b " + r + " " + p + "\u00a77 as\u00a73 " + poof);
// Parse
ProvidedUIFilter parsed = ProvidedUIFilter.getFromString(poof, null);
if (parsed == null) { parsed = RecipeMakerGUI.AIR.clone(); }
// Add
ret[r][p] = parsed; } }
// And that's your result
return ret;
}
/**
* Turns something like <br> <code>
*
* [ A, B, C ], <br>
* [ D, E, F ], <br>
* [ G, H, I ] <br>
*
* </code> <br>
* into <br> <code>
*
* - A|B|C <br>
* - D|E|F <br>
* - G|H|I
* </code>
*
* @param ingredients Array of arrays of UIFilters
*
* @return A list of strings to save in a YML Config
*/
@NotNull ArrayList<String> toYML(@NotNull ProvidedUIFilter[][] ingredients) {
// Well, build it would ye?
ArrayList<String> ret = new ArrayList<>();
for (int r = 0; r < 3; r++) {
// Get row
ProvidedUIFilter[] poofs = ingredients.length > r ? ingredients[r] : new ProvidedUIFilter[3];
// Concatenate
StringBuilder sb = new StringBuilder();
// Build
for (ProvidedUIFilter poof : poofs) {
ProvidedUIFilter providedUIFilter = poof;
if (providedUIFilter == null) { providedUIFilter = RecipeMakerGUI.AIR.clone(); }
// Add bar
if (sb.length() != 0) { sb.append("|"); }
// Add poof
sb.append(providedUIFilter);
}
ret.add(sb.toString());
}
return ret;
}
@NotNull final ProvidedUIFilter[][] inputRecipe;
/**
* Sets the ingredient in the rows matrix.
*
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setInput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot < 0 || slot > 8) { return; }
inputRecipe[SilentNumbers.floor(slot / 3.0)][slot - (3 * SilentNumbers.floor(slot / 3.0))] = poof;
}
@Nullable
@Override public ProvidedUIFilter getInput(int slot) {
if (slot < 0 || slot > 8) { return null; }
return inputRecipe[SilentNumbers.floor(slot / 3.0)][slot - (3 * SilentNumbers.floor(slot / 3.0))];
}
@NotNull final ProvidedUIFilter[][] outputRecipe;
/**
* Sets the ingredient in the rows matrix.
*
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setOutput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot < 0 || slot > 8) { return; }
outputRecipe[SilentNumbers.floor(slot / 3.0)][slot - (3 * SilentNumbers.floor(slot / 3.0))] = poof;
}
@Nullable
@Override public ProvidedUIFilter getOutput(int slot) {
if (slot < 0 || slot > 8) { return null; }
return outputRecipe[SilentNumbers.floor(slot / 3.0)][slot - (3 * SilentNumbers.floor(slot / 3.0))];
}
@NotNull final ConfigurationSection section;
/**
* @return The recipe name section of this recipe. <br>
* <br>
* Basically <b><code>[ID].base.crafting.shaped.[name]</code></b> section
*/
@NotNull public ConfigurationSection getSection() { return section; }
/**
* Generate an interpreter from this <i>updated</i> configuration section.
* <br><br>
* By 'updated' I mean that, for now, we <b>should call {@link RecipeMakerGUI#moveInput()}
* on this configuration before passing it here</b>, to move the input list from being the recipe name
* section itself to the 'input' section within.
*
* @param recipeNameSection <b><code>[ID].base.crafting.shaped.[name]</code></b> section
*/
public RMGRI_Shaped(@NotNull ConfigurationSection recipeNameSection) {
// Save
section = recipeNameSection;
// Build Input list
inputRecipe = buildIngredientsFromList(section.getStringList(RecipeMakerGUI.INPUT_INGREDIENTS));
outputRecipe = buildIngredientsFromList(section.getStringList(RecipeMakerGUI.OUTPUT_INGREDIENTS));
}
@Override
public void editInput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setInput(slot, input);
// Save
section.set(RecipeMakerGUI.INPUT_INGREDIENTS, toYML(inputRecipe));
}
@Override
public void editOutput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setOutput(slot, input);
// Save
section.set(RecipeMakerGUI.OUTPUT_INGREDIENTS, toYML(outputRecipe));
}
@Override public void deleteInput(@NotNull ConfigurationSection section, int slot) { editInput(section, RecipeMakerGUI.AIR.clone(), slot); }
@Override public void deleteOutput(@NotNull ConfigurationSection section, int slot) { editOutput(section, RecipeMakerGUI.AIR.clone(), slot); }
//region Updater, to update old recipes
/**
* No matter what input, the output will always be three Provided UIFilters
* separated by bars, as expected in the current system, filling with AIR
* where necessary.
*
* @param curr Current string
*
* @return A row in correct format
*/
@NotNull public static String updateRow(@Nullable String curr) {
if (curr == null || curr.isEmpty()) { return emptyRow;}
// Bars used? I guess we can check that its written correctly
if (curr.contains("|")) {
// Split by bars
String[] curSplit = curr.split("\\|");
// Correct length?
if (curSplit.length == 3) {
// Assumed to be updated.
return curr;
} else {
// Make sure it is of size three
StringBuilder ret = new StringBuilder();
// Must append three
for (int r = 0; r < 3; r++) {
// Append a bar after the first
if (r != 0) { ret.append("|"); }
// Array has it?
if (r < curSplit.length) { ret.append(RecipeMakerGUI.poofFromLegacy(curSplit[r])); } else { ret.append("v AIR 0"); }
}
// Build and return
return ret.toString();
}
// Not bars, but spaces, might be old format
} else if (curr.contains(" ")) {
// Make string builder
StringBuilder ret = new StringBuilder();
String[] curSplit = curr.split(" ");
// Must append three
for (int r = 0; r < 3; r++) {
// Append a bar after the first
if (r != 0) { ret.append("|"); }
// Array has it?
if (r < curSplit.length) { ret.append(RecipeMakerGUI.poofFromLegacy(curSplit[r])); } else { ret.append("v AIR 0"); }
}
// Build and return
return ret.toString();
// No spaces nor bars, this will just be the first ingredient of the row I guess
} else {
// Just that i guess
return RecipeMakerGUI.poofFromLegacy(curr) + "|v AIR 0|v AIR 0";
}
}
public static final String emptyRow = "v AIR 0|v AIR 0|v AIR 0";
//endregion
}

View File

@ -0,0 +1,190 @@
package net.Indyuce.mmoitems.gui.edition.recipe.interpreters;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* This class is in charge of converting Shapeless Recipes to and fro YML format,
* as well as editing it in a YML configuration and such. <br> <br>
*
* YML Save Format: <br> <code>
*
* - A <br>
* - B <br>
* - C <br>
* - D <br>
* - E <br>
* - F <br>
* - G <br>
* - H <br>
* - I <br>
* </code>
*
* @author Gunging
*/
public class RMGRI_Shapeless implements RMG_RecipeInterpreter {
/**
* Builds a valid 3x3 matrix of input/output recipe.
*
* @param config List as it is saved in the config.
*
* @return Transcribed into array of arrays.
*/
@NotNull ProvidedUIFilter[] buildIngredientsFromList(@NotNull List<String> config) {
// Start with a base
ProvidedUIFilter[] ret = new ProvidedUIFilter[9];
// Each row ig
for (int r = 0; r < 9; r++) {
// Get current row
String row = config.size() > r ? config.get(r) : null;
// Update it ig
String poof = RecipeMakerGUI.poofFromLegacy(row);
// Parse
ProvidedUIFilter parsed = ProvidedUIFilter.getFromString(poof, null);
if (parsed == null) { parsed = RecipeMakerGUI.AIR.clone(); }
// Add
ret[r] = parsed;
}
// And that's your result
return ret;
}
/**
* Turns something like <br> <code>
*
* [ A, B, C, D, E, F, G, H, I ] <br>
*
* </code> <br>
* into <br> <code>
*
* - A <br>
* - B <br>
* - C <br>
* - D <br>
* - E <br>
* - F <br>
* - G <br>
* - H <br>
* - I <br>
* </code>
*
* @param ingredients Array of arrays of UIFilters
*
* @return A list of strings to save in a YML Config
*/
@NotNull ArrayList<String> toYML(@NotNull ProvidedUIFilter[] ingredients) {
// Well, build it would ye?
ArrayList<String> ret = new ArrayList<>();
for (int r = 0; r < 9; r++) {
// Get row
ProvidedUIFilter poof = ingredients.length > r ? ingredients[r] : RecipeMakerGUI.AIR.clone();
// Add poof
ret.add(poof.toString());
}
// Thats it
return ret;
}
@NotNull final ProvidedUIFilter[] inputRecipe;
/**
* Sets the ingredient in the rows matrix.
*
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setInput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot < 0 || slot > 8) { return; }
inputRecipe[slot] = poof;
}
@Nullable
@Override public ProvidedUIFilter getInput(int slot) {
if (slot < 0 || slot > 8) { return null; }
return inputRecipe[slot];
}
@NotNull final ProvidedUIFilter[] outputRecipe;
/**
* Sets the ingredient in the rows matrix.
*
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setOutput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot < 0 || slot > 8) { return; }
outputRecipe[slot] = poof;
}
@Nullable
@Override public ProvidedUIFilter getOutput(int slot) {
if (slot < 0 || slot > 8) { return null; }
return outputRecipe[slot];
}
@NotNull final ConfigurationSection section;
/**
* @return The recipe name section of this recipe. <br>
* <br>
* Basically <b><code>[ID].base.crafting.shapeless.[name]</code></b> section
*/
@NotNull public ConfigurationSection getSection() { return section; }
/**
* Generate an interpreter from this <i>updated</i> configuration section.
* <br><br>
* By 'updated' I mean that, for now, we <b>should call {@link RecipeMakerGUI#moveInput(ConfigurationSection, String)}
* on this configuration before passing it here</b>, to move the input list from being the recipe name
* section itself to the 'input' section within.
*
* @param recipeNameSection <b><code>[ID].base.crafting.shapeless.[name]</code></b> section
*/
public RMGRI_Shapeless(@NotNull ConfigurationSection recipeNameSection) {
// Save
section = recipeNameSection;
// Build Input list
inputRecipe = buildIngredientsFromList(section.getStringList(RecipeMakerGUI.INPUT_INGREDIENTS));
outputRecipe = buildIngredientsFromList(section.getStringList(RecipeMakerGUI.OUTPUT_INGREDIENTS));
}
@Override
public void editInput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setInput(slot, input);
// Save
section.set(RecipeMakerGUI.INPUT_INGREDIENTS, toYML(inputRecipe));
}
@Override
public void editOutput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setOutput(slot, input);
// Save
section.set(RecipeMakerGUI.OUTPUT_INGREDIENTS, toYML(outputRecipe));
}
@Override public void deleteInput(@NotNull ConfigurationSection section, int slot) { editInput(section, RecipeMakerGUI.AIR.clone(), slot); }
@Override public void deleteOutput(@NotNull ConfigurationSection section, int slot) { editOutput(section, RecipeMakerGUI.AIR.clone(), slot); }
}

View File

@ -0,0 +1,227 @@
package net.Indyuce.mmoitems.gui.edition.recipe.interpreters;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class is in charge of converting Smithing Recipes to and fro YML format,
* as well as editing it in a YML configuration and such. <br> <br>
*
* YML Save Format: <br> <code>
*
* - A|B <br>
* </code>
*
* @author Gunging
*/
public class RMGRI_Smithing implements RMG_RecipeInterpreter {
/**
* Turns something like <br> <code>
* [ A, B ]
*
* </code> <br> <br>
* into <br> <code>
*
* - A|B
* </code>
*
* @param item first input
* @param ingot second input
*
* @return A string to save in a YML Config
*/
@NotNull String toYML(@NotNull ProvidedUIFilter item, @NotNull ProvidedUIFilter ingot) {
// Well, build it would ye?
return item + "|" + ingot;
}
@NotNull ProvidedUIFilter inputItem;
@NotNull public ProvidedUIFilter getInputItem() { return inputItem; }
public void setInputItem(@NotNull ProvidedUIFilter inputItem) { this.inputItem = inputItem; }
@NotNull ProvidedUIFilter outputItem;
@NotNull public ProvidedUIFilter getOutputItem() { return outputItem; }
public void setOutputItem(@NotNull ProvidedUIFilter outputItem) { this.outputItem = outputItem; }
@NotNull ProvidedUIFilter inputIngot;
@NotNull public ProvidedUIFilter getInputIngot() { return inputIngot; }
public void setInputIngot(@NotNull ProvidedUIFilter inputIngot) { this.inputIngot = inputIngot; }
@NotNull ProvidedUIFilter outputIngot;
@NotNull public ProvidedUIFilter getOutputIngot() { return outputIngot; }
public void setOutputIngot(@NotNull ProvidedUIFilter outputIngot) { this.outputIngot = outputIngot; }
@NotNull final ConfigurationSection section;
/**
* @return The recipe name section of this recipe. <br>
* <br>
* Basically <b><code>[ID].base.crafting.shaped.[name]</code></b> section
*/
@NotNull public ConfigurationSection getSection() { return section; }
/**
* Generate an interpreter from this <i>updated</i> configuration section.
* <br><br>
* By 'updated' I mean that, for now, we <b>should call {@link RecipeMakerGUI#moveInput()}
* on this configuration before passing it here</b>, to move the input list from being the recipe name
* section itself to the 'input' section within.
*
* @param recipeNameSection <b><code>[ID].base.crafting.shaped.[name]</code></b> section
*/
public RMGRI_Smithing(@NotNull ConfigurationSection recipeNameSection) {
// Save
section = recipeNameSection;
/*
* Read input and output from the file
*/
String input = updateIngredients(section.getString(RecipeMakerGUI.INPUT_INGREDIENTS));
String output = updateIngredients(section.getString(RecipeMakerGUI.OUTPUT_INGREDIENTS));
// Split
String[] inputSplit = input.split("\\|");
String[] outputSplit = output.split("\\|");
ProvidedUIFilter inputItemParse = ProvidedUIFilter.getFromString(inputSplit[0], null);
ProvidedUIFilter outputItemParse = ProvidedUIFilter.getFromString(outputSplit[0], null);
ProvidedUIFilter inputIngotParse = ProvidedUIFilter.getFromString(inputSplit[1], null);
ProvidedUIFilter outputIngotParse = ProvidedUIFilter.getFromString(outputSplit[1], null);
// Build Input list
inputItem = inputItemParse != null ? inputItemParse : RecipeMakerGUI.AIR.clone();
inputIngot = inputIngotParse != null ? inputIngotParse : RecipeMakerGUI.AIR.clone();
outputItem = outputItemParse != null ? outputItemParse : RecipeMakerGUI.AIR.clone();
outputIngot = outputIngotParse != null ? outputIngotParse : RecipeMakerGUI.AIR.clone();
}
/**
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setInput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot == 0) { setInputItem(poof); } else if (slot == 1) { setInputIngot(poof); }
}
@Nullable
@Override public ProvidedUIFilter getInput(int slot) {
if (slot == 0) { return getInputItem(); } else if (slot == 1) { return getInputIngot(); }
return null;
}
/**
* @param slot The slot, which must be between 0 and 8 (or this method will do nothing)
* @param poof Ingredient to register
*/
public void setOutput(int slot, @NotNull ProvidedUIFilter poof) {
if (slot == 0) { setOutputItem(poof); } else if (slot == 1) { setOutputIngot(poof); }
}
@Nullable
@Override public ProvidedUIFilter getOutput(int slot) {
if (slot == 0) { return getOutputItem(); } else if (slot == 1) { return getOutputIngot(); }
return null;
}
@Override
public void editInput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setInput(slot, input);
// Save
section.set(RecipeMakerGUI.INPUT_INGREDIENTS, toYML(getInputItem(), getInputIngot()));
}
@Override
public void editOutput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot) {
// Just edit bro
setOutput(slot, input);
// Save
section.set(RecipeMakerGUI.OUTPUT_INGREDIENTS, toYML(getOutputItem(), getOutputIngot()));
}
@Override public void deleteInput(@NotNull ConfigurationSection section, int slot) { editInput(section, RecipeMakerGUI.AIR.clone(), slot); }
@Override public void deleteOutput(@NotNull ConfigurationSection section, int slot) { editOutput(section, RecipeMakerGUI.AIR.clone(), slot); }
//region Updater, to update old recipes
/**
* No matter what input, the output will always be three Provided UIFilters
* separated by bars, as expected in the current system, filling with AIR
* where necessary.
*
* @param curr Current string
*
* @return A row in correct format
*/
@NotNull public static String updateIngredients(@Nullable String curr) {
if (curr == null || curr.isEmpty()) { return emptyIngredients;}
// Bars used? I guess we can check that its written correctly
if (curr.contains("|")) {
// Split by bars
String[] curSplit = curr.split("\\|");
// Correct length?
if (curSplit.length == 2) {
// Assumed to be updated.
return curr;
} else {
// Make sure it is of size three
StringBuilder ret = new StringBuilder();
// Must append three
for (int r = 0; r < 2; r++) {
// Append a bar after the first
if (r != 0) { ret.append("|"); }
// Array has it?
if (r < curSplit.length) { ret.append(RecipeMakerGUI.poofFromLegacy(curSplit[r])); } else { ret.append("v AIR -"); }
}
// Build and return
return ret.toString();
}
// Not bars, but spaces, might be old format
} else if (curr.contains(" ")) {
// Make string builder
StringBuilder ret = new StringBuilder();
String[] curSplit = curr.split(" ");
// Must append three
for (int r = 0; r < 2; r++) {
// Append a bar after the first
if (r != 0) { ret.append("|"); }
// Array has it?
if (r < curSplit.length) { ret.append(RecipeMakerGUI.poofFromLegacy(curSplit[r])); } else { ret.append("v AIR -"); }
}
// Build and return
return ret.toString();
// No spaces nor bars, this will just be the first ingredient of the row I guess
} else {
// Just that i guess
return RecipeMakerGUI.poofFromLegacy(curr) + "|v AIR 0";
}
}
public static final String emptyIngredients = "v AIR -|v AIR -";
//endregion
}

View File

@ -0,0 +1,100 @@
package net.Indyuce.mmoitems.gui.edition.recipe.interpreters;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* When the user inputs an ingredient, after clicking the target slot number,
* it is the job of the Recipe Interpreter to edit the ConfigurationSection.
*
* @author Gunging
*/
public interface RMG_RecipeInterpreter {
/**
* Edits the configuration section's INPUT list.
*
* @param section <b>The 'crafting' section - [ID].base.crafting</b>
* <br><br>
* Note that this is not editing the recipe type nor name itself.
* It is up to the interpreter to create the sections if missing
* or edit them if they are already there.
* <br><br>
* Ex: [ID].base.crafting.shaped.1.input
*
* @param input The user's input, item that will be required
*
* @param slot Slot that the item is going into
*/
void editInput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot);
/**
* Edits the configuration section's OUTPUT list.
*
* @param section <b>The 'crafting' section - [ID].base.crafting</b>
* <br><br>
* Note that this is not editing the recipe type nor name itself.
* It is up to the interpreter to create the sections if missing
* or edit them if they are already there.
* <br><br>
* Ex: [ID].base.crafting.shaped.1.output
*
* @param input The user's input, item that will be required
*
* @param slot Slot that the item is going into
*/
void editOutput(@NotNull ConfigurationSection section, @NotNull ProvidedUIFilter input, int slot);
/**
* Edits the configuration section's INPUT list.
*
* @param section <b>The 'crafting' section - [ID].base.crafting</b>
* <br><br>
* Note that this is not editing the recipe type nor name itself.
* It is up to the interpreter to create the sections if missing
* or edit them if they are already there.
* <br><br>
* Ex: [ID].base.crafting.shaped.1.input
*
* @param slot Slot that is getting reset
*/
void deleteInput(@NotNull ConfigurationSection section, int slot);
/**
* Edits the configuration section's OUTPUT list.
*
* @param section <b>The 'crafting' section - [ID].base.crafting</b>
* <br><br>
* Note that this is not editing the recipe type nor name itself.
* It is up to the interpreter to create the sections if missing
* or edit them if they are already there.
* <br><br>
* Ex: [ID].base.crafting.shaped.1.output
*
* @param slot Slot that is getting reset
*/
void deleteOutput(@NotNull ConfigurationSection section, int slot);
/**
* Fetch the Provided UI Filter in the YML configuration
* that corresponds to this slot of the input.
*
* @param slot Slot
*
* @return Identified filter, if found and valid.
*/
@Nullable ProvidedUIFilter getInput(int slot);
/**
* Fetch the Provided UI Filter in the YML configuration
* that corresponds to this slot of the output.
*
* @param slot Slot
*
* @return Identified filter, if found and valid.
*/
@Nullable ProvidedUIFilter getOutput(int slot);
}

View File

@ -0,0 +1,150 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import io.lumine.mythic.utils.version.ServerVersion;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.edition.StatEdition;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BannerMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* The user will specify the output amount of a recipe on
* a per-recipe basis, and will use this button for that.
*
* @author Gunging
*/
public class RBA_AmountOutput extends RecipeButtonAction {
/**
* The button that displays how much output this recipe will produce.
*
* @param inv Inventory this button is part of
* @param resultItem Output item of this recipe
*/
public RBA_AmountOutput(@NotNull RecipeMakerGUI inv, @NotNull ItemStack resultItem) {
super(inv);
// Get item
button = RecipeMakerGUI.rename(ItemFactory.of(resultItem.getType()).lore(SilentNumbers.chop(
"The amount of items produced every time the player crafts."
, 65, "\u00a77")).build(), "\u00a7cChoose Output Amount");
// Update CMD ~ The stupid warning for 'ItemMEta might be false' is so annoying bruh
@NotNull ItemMeta buttonMeta = Objects.requireNonNull(button.getItemMeta());
@NotNull ItemMeta resultMeta = Objects.requireNonNull(resultItem.getItemMeta());
if (ServerVersion.get().getMinor() >= 14 && buttonMeta.hasCustomModelData()) { buttonMeta.setCustomModelData(resultMeta.getCustomModelData()); }
if (resultMeta instanceof LeatherArmorMeta) { ((LeatherArmorMeta) buttonMeta).setColor(((LeatherArmorMeta) resultMeta).getColor()); }
if (resultMeta instanceof BannerMeta) { ((BannerMeta) buttonMeta).setPatterns(((BannerMeta) resultMeta).getPatterns()); }
buttonMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_DYE, ItemFlag.HIDE_POTION_EFFECTS);
button.setItemMeta(buttonMeta);
}
@NotNull public final String[] amountLog = {
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Write in the chat the amount of output of this recipe."),
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "It must be an integer number, ex $e4$b.")};
/**
* When the player clicks the display item, it means they want to change
* the amount output of this recipe. Thus, they are queried to write
* an integer number in the chat.
*
* @return If the player clicked the display item
*/
@Override public boolean runPrimary() {
// Query user for input
new StatEdition(inv, ItemStats.CRAFTING, RecipeMakerGUI.PRIMARY, this).enable(amountLog);
// Success
return true;
}
/**
* The player has written a number that will be set as the output amount of this recipe.
* <br>
* The amount is saved in YML path {@code [ID].crafting.[recipe].[name].amount}
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException If the player did not write an integer number
*/
@Override public void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException {
// Parse
Integer val = SilentNumbers.IntegerParse(message);
if (val == null) { throw new IllegalArgumentException("Expected an integer number instead of $u" + message); }
if (val > 64) { throw new IllegalArgumentException("Max stack size is $e64$b, Minecraft doesnt support $u" + message); }
if (val <= 0) { throw new IllegalArgumentException("Min output stack size is $e0$b, you specified $u" + message); }
// Set value
getInv().getNameSection().set(AMOUNT_INGREDIENTS, val);
}
/**
* If the player left-clicks the display item, the behaviour is just reset
* the amount to 1 output. No need to query the user.
*
* @return If the player clicked the display item
*/
@Override public boolean runSecondary() {
// Set value
getInv().getNameSection().set(AMOUNT_INGREDIENTS, null);
clickSFX();
/*
* Register template edition. This is only done automatically
* on the input process methods, not on the run button ones.
*/
inv.registerTemplateEdition();
// Done
return true;
}
/**
* @return Straight from the file, the amount output of this recipe.
*/
public int getOutputAmount() { return getInv().getNameSection().getInt(AMOUNT_INGREDIENTS, 1); }
/**
* The user needs to input nothing; Thus this method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
@NotNull final ItemStack button;
@Override
@NotNull public ItemStack getButton() {
// Dupe and change amount
ItemStack ret = button.clone();
ret.setAmount(getOutputAmount());
// That's it
return RecipeMakerGUI.addLore(ret, SilentNumbers.toArrayList( "",
ChatColor.YELLOW + AltChar.listDash + " Right click to reset to 1.",
ChatColor.YELLOW + AltChar.listDash + " Left click to edit amount." ));
}
public static final String AMOUNT_INGREDIENTS = "amount";
}

View File

@ -0,0 +1,36 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_DoubleButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RBA_CookingTime extends RBA_DoubleButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_CookingTime(@NotNull RecipeMakerGUI inv) { super(inv); }
public static final String FURNACE_TIME = "time";
@NotNull @Override public String getDoubleConfigPath() { return FURNACE_TIME; }
@Nullable @Override public QuickNumberRange getRange() { return new QuickNumberRange(0D, null); }
@Override public boolean requireInteger() { return true; }
public static final double DEFAULT = 200;
@Override public double getDefaultValue() { return DEFAULT; }
@NotNull final ItemStack doubleButton = RecipeMakerGUI.addLore(ItemFactory.of(Material.CLOCK).name("\u00a7cDuration").lore(SilentNumbers.chop(
"How long it takes this recipe to finish 'cooking' x)"
, 65, "\u00a77")).build(), SilentNumbers.toArrayList(""));
@NotNull @Override public ItemStack getDoubleButton() { return doubleButton; }
}

View File

@ -0,0 +1,33 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_BooleanButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Will the extra gems (that didn't fit in the other item)
* drop to the ground (as opposed to being lost)?
*
* @author Gunging
*/
public class RBA_DropGems extends RBA_BooleanButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_DropGems(@NotNull RecipeMakerGUI inv) { super(inv); }
public static final String SMITH_GEMS = "drop-gems";
@NotNull @Override public String getBooleanConfigPath() { return SMITH_GEMS; }
@NotNull final ItemStack booleanButton = RecipeMakerGUI.addLore(ItemFactory.of(Material.EMERALD).name("\u00a7aDrop Gemstones").lore(SilentNumbers.chop(
"Usually, gemstones that dont fit the new item are lost. Enable this to make them drop (and be recovered) instead."
, 65, "\u00a77")).build(), SilentNumbers.toArrayList(""));
@NotNull @Override public ItemStack getBooleanButton() { return booleanButton; }
}

View File

@ -0,0 +1,41 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_DoubleButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Experience from furnace recipes and stuff
*
* @author Gunging
*/
public class RBA_Experience extends RBA_DoubleButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_Experience(@NotNull RecipeMakerGUI inv) { super(inv); }
public static final String FURNACE_EXPERIENCE = "exp";
@NotNull @Override public String getDoubleConfigPath() { return FURNACE_EXPERIENCE; }
@Nullable @Override public QuickNumberRange getRange() { return new QuickNumberRange(0D, null); }
@Override public boolean requireInteger() { return false; }
public static final double DEFAULT = 0.35;
@Override public double getDefaultValue() { return DEFAULT; }
@NotNull final ItemStack doubleButton = RecipeMakerGUI.addLore(ItemFactory.of(Material.EXPERIENCE_BOTTLE).name("\u00a7aExperience").lore(SilentNumbers.chop(
"This recipe gives experience when crafted, how much?"
, 65, "\u00a77")).build(), SilentNumbers.toArrayList(""));
@NotNull @Override public ItemStack getDoubleButton() { return doubleButton; }
}

View File

@ -0,0 +1,80 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_BooleanButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.KnowledgeBookMeta;
import org.jetbrains.annotations.NotNull;
/**
* Prevents the recipe from automatically being sent to players
* to see freely in the recipe book.
*
* @author Gunging
*/
public class RBA_HideFromBook extends RBA_BooleanButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_HideFromBook(@NotNull RecipeMakerGUI inv) { super(inv); }
public static final String BOOK_HIDDEN = "hidden";
@NotNull @Override public String getBooleanConfigPath() { return BOOK_HIDDEN; }
@Override public boolean runSecondary() {
// Set value
ItemStack book = new ItemStack(Material.KNOWLEDGE_BOOK);
ItemMeta iMeta = book.getItemMeta();
// Edit meta
if (iMeta instanceof KnowledgeBookMeta) {
// Add recipe
((KnowledgeBookMeta) iMeta).addRecipe(MMOItems.plugin.getRecipes().getRecipeKey(
getInv().getEdited().getType(),
getInv().getEdited().getId(),
getInv().getRecipeRegistry().getRecipeConfigPath(),
getInv().getRecipeName()));
}
// Set meta
book.setItemMeta(iMeta);
// Give it to the player
getInv().getPlayer().getInventory().addItem(book);
getInv().getPlayer().playSound(getInv().getPlayer().getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 2);
// Done
return true; }
@NotNull final ItemStack booleanButton = RecipeMakerGUI.addLore(ItemFactory.of(Material.KNOWLEDGE_BOOK).name("\u00a7cHide from Crafting Book").lore(SilentNumbers.chop(
"Even if the crafting book is enabled, this recipe wont be automatically unlocked by players."
, 65, "\u00a77")).build(), SilentNumbers.toArrayList(""));
@NotNull @Override public ItemStack getBooleanButton() { return booleanButton; }
@NotNull @Override public ItemStack getButton() {
// Dictate the correct one
String input = isEnabled() ? "\u00a7cNO\u00a78, it's hidden." : "\u00a7aYES\u00a78, it's shown.";
// Copy and send
return RecipeMakerGUI.addLore(getBooleanButton().clone(),
SilentNumbers.toArrayList(
"", "\u00a77Currently in Book? " + input, "",
ChatColor.YELLOW + AltChar.listDash + " Right click to generate recipe unlock book.",
ChatColor.YELLOW + AltChar.listDash + " Left click to toggle this option." ));
}
}

View File

@ -0,0 +1,82 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Button to switch between Input and Output modes of the station.
*
* @author Gunging
*/
public class RBA_InputOutput extends RecipeButtonAction {
boolean showingInput;
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_InputOutput(@NotNull RecipeMakerGUI inv) {
super(inv);
// By default, input is shown.
showingInput = true;
}
@Override
public boolean runPrimary() {
getInv().switchInput();
getInv().refreshInventory();
clickSFX();
return true;
}
/**
* This method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
/**
* Nothing happens
*/
@Override public boolean runSecondary() { return false; }
/**
* This method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
@NotNull final ItemStack button = RecipeMakerGUI.addLore(ItemFactory.of(Material.CRAFTING_TABLE).name("\u00a7cSwitch to Output Mode").lore(SilentNumbers.chop(
"INPUT is the ingredients of the recipe, but (like milk buckets when crafting a cake) these ingredients may not be entirely consumed. In such cases, use the OUTPUT mode to specify what the ingredients will turn into."
, 63, "\u00a77")).build(), SilentNumbers.toArrayList(""));
@NotNull
@Override
public ItemStack getButton() {
// Dictate the correct one
String input = getInv().isShowingInput() ? "\u00a76INPUT" : "\u00a73OUTPUT";
// Copy and send
return RecipeMakerGUI.addLore(button.clone(), SilentNumbers.toArrayList("\u00a77Currently Showing: " + input, "",
ChatColor.YELLOW + AltChar.listDash + " Left click to switch mode." ));
}
}

View File

@ -0,0 +1,57 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.api.crafting.recipe.SmithingCombinationType;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_ChooseableButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
/**
* Which behaviour do Enchantments follow when the player smiths items?
*
* @author Gunging
*/
public class RBA_SmithingEnchantments extends RBA_ChooseableButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_SmithingEnchantments(@NotNull RecipeMakerGUI inv) { super(inv); }
@NotNull final ItemStack chooseableButton = ItemFactory.of(Material.ENCHANTING_TABLE).name("\u00a7aEnchantment Transfer").lore(SilentNumbers.chop(
"What will happen to the enchantments of the ingredients? Will enchanted ingredients produce an enchanted output item?"
, 65, "\u00a77")).build();
@NotNull @Override public ItemStack getChooseableButton() { return chooseableButton; }
public static final String SMITH_ENCHANTS = "enchantments";
@NotNull @Override public String getChooseableConfigPath() { return SMITH_ENCHANTS; }
@NotNull @Override public ArrayList<String> getChooseableList() { return RBA_SmithingUpgrades.getSmithingList(); }
@NotNull @Override public String getDefaultValue() { return SmithingCombinationType.MAXIMUM.toString(); }
@NotNull @Override public String getChooseableDefinition(@NotNull String ofChooseable) {
SmithingCombinationType sct = SmithingCombinationType.MAXIMUM;
try { sct = SmithingCombinationType.valueOf(getCurrentChooseableValue()); } catch (IllegalArgumentException ignored) {}
switch (sct) {
case EVEN:
return "For each enchantment, will take the average of that enchantment's level across the ingredients.";
case NONE:
return "Will ignore the enchantments of any ingredients.";
case MAXIMUM:
return "Output will have the best enchantment from each ingredient";
case MINIMUM:
return "Output will have worst enchantment from each ingredient with that enchantment.";
case ADDITIVE:
return "The enchantments of all ingredients will add together.";
default: return "Unknown behaviour. Add description in net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_SmithingEnchantments";
}
}
}

View File

@ -0,0 +1,66 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.api.crafting.recipe.SmithingCombinationType;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.type.RBA_ChooseableButton;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
/**
* Which behaviour do Upgrades follow when the player smiths items?
*
* @author Gunging
*/
public class RBA_SmithingUpgrades extends RBA_ChooseableButton {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_SmithingUpgrades(@NotNull RecipeMakerGUI inv) { super(inv); }
@NotNull final ItemStack chooseableButton = ItemFactory.of(Material.ANVIL).name("\u00a7aUpgrades Transfer").lore(SilentNumbers.chop(
"What will happen to the upgrades of the ingredients? Will upgraded ingredients produce an upgraded output item?"
, 65, "\u00a77")).build();
@NotNull @Override public ItemStack getChooseableButton() { return chooseableButton; }
public static final String SMITH_UPGRADES = "upgrades";
@NotNull @Override public String getChooseableConfigPath() { return SMITH_UPGRADES; }
@NotNull @Override public ArrayList<String> getChooseableList() { return getSmithingList(); }
@NotNull @Override public String getDefaultValue() { return SmithingCombinationType.MAXIMUM.toString(); }
@NotNull @Override public String getChooseableDefinition(@NotNull String ofChooseable) {
SmithingCombinationType sct = SmithingCombinationType.MAXIMUM;
try { sct = SmithingCombinationType.valueOf(getCurrentChooseableValue()); } catch (IllegalArgumentException ignored) {}
switch (sct) {
case EVEN:
return "Will take the average of the upgrade levels of the combined items.";
case NONE:
return "Will ignore the upgrade levels of any ingredients.";
case MAXIMUM:
return "Output will have the upgrade level of the most upgraded ingredient.";
case MINIMUM:
return "Output will have the upgrade level of the least-upgraded upgradeable ingredient.";
case ADDITIVE:
return "The upgrade levels of the ingredients will be added, and the result will be the crafted item's level.";
default: return "Unknown behaviour. Add description in net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_SmithingUpgrades";
}
}
static ArrayList<String> smithingList;
/**
* @return The allowed values of the smithing combination type list
*/
@NotNull static ArrayList<String> getSmithingList() {
if (smithingList != null) { return smithingList; }
smithingList = new ArrayList<>();
for (SmithingCombinationType sct : SmithingCombinationType.values()) { smithingList.add(sct.toString()); }
return smithingList; }
}

View File

@ -0,0 +1,96 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba;
import net.Indyuce.mmoitems.api.edition.StatEdition;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.bukkit.Sound;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* When the user clicks the Edition Inventory of a Recipe Maker,
* it might be a special button specific to a recipe. In such a
* case, RMGs may register all their buttons as RBAs, and perform
* operations with low maintenance.
*
* @author Gunging
*/
public abstract class RecipeButtonAction {
/**
* The edition inventory this is a button of
*/
@NotNull final RecipeMakerGUI inv;
/**
* @return The edition inventory this is a button of
*/
@NotNull public RecipeMakerGUI getInv() { return inv; }
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RecipeButtonAction(@NotNull RecipeMakerGUI inv) { this.inv = inv; }
/**
* Called when the player left-clicks a slot. <br>
* <b>Important: When initializing a {@link StatEdition#StatEdition(EditionInventory, ItemStat, Object...)} you
* must pass {@link RecipeMakerGUI#PRIMARY} as the first <i>info</i> object!</b> Also, make sure to pass {@code this}
* as the second argument for {@link #primaryProcessInput(String, Object...)} to be called.
*
* @return <code>true</code> if and only if this action succeeded. Most importantly,
* indicates that the absolute slot the user clicked corresponds to this
* button being clicked.
*/
public abstract boolean runPrimary();
/**
* Run the function performed by this button, based on the user's input.
*
* This will be called when {@link #runPrimary()} (int, EditionInventory)} succeeds and calls
* {@link StatEdition#StatEdition(EditionInventory, ItemStat, Object...)}
* to query the user for input.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException If anything goes wrong.
*/
public abstract void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException;
/**
* Called when the player right-clicks a slot. <br>
* <b>Important: When initializing a {@link StatEdition#StatEdition(EditionInventory, ItemStat, Object...)} you
* must pass {@link RecipeMakerGUI#SECONDARY} as the first <i>info</i> object!</b> Also, make sure to pass {@code this}
* as the second argument for {@link #secondaryProcessInput(String, Object...)} to be called.
*
* @return <code>true</code> if and only if this action succeeded. Most importantly,
* indicates that the absolute slot the user clicked corresponds to this
* button being clicked.
*/
public abstract boolean runSecondary();
/**
* Run the function performed by this button, based on the user's input.
*
* This will be called when {@link #runSecondary()} succeeds and calls
* {@link StatEdition#StatEdition(EditionInventory, ItemStat, Object...)}
* to query the user for input.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException If anything goes wrong.
*/
public abstract void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException;
/**
* @return The ItemStack that will act as the button in the GUI, to activate this class.
*/
@NotNull public abstract ItemStack getButton();
/**
* Plays a clicking sound
*/
public void clickSFX() { getInv().getPlayer().playSound(getInv().getPlayer().getLocation(), Sound.UI_BUTTON_CLICK, 1, 1); }
}

View File

@ -0,0 +1,106 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba.type;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RecipeButtonAction;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* A button that toggles between true and false.
*
* @author Gunging
*/
public abstract class RBA_BooleanButton extends RecipeButtonAction {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_BooleanButton(@NotNull RecipeMakerGUI inv) {
super(inv);
}
/**
* @return Straight from the file, if this option is set to TRUE.
*/
public boolean isEnabled() { return getInv().getNameSection().getBoolean(getBooleanConfigPath(), false); }
/**
* @return The path to save this value in the config
*/
@NotNull public abstract String getBooleanConfigPath();
@Override public boolean runPrimary() {
// Flip value
getInv().getNameSection().set(getBooleanConfigPath(), !isEnabled());
clickSFX();
/*
* Register template edition. This is only done automatically
* on the input process methods, not on the run button ones.
*/
getInv().registerTemplateEdition();
// Done
return true;
}
/**
* The user needs to input nothing; Thus this method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
@Override public boolean runSecondary() {
// Remove value
getInv().getNameSection().set(getBooleanConfigPath(), null);
clickSFX();
/*
* Register template edition. This is only done automatically
* on the input process methods, not on the run button ones.
*/
getInv().registerTemplateEdition();
// Done
return true; }
/**
* The user needs to input nothing; Thus this method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
/**
* @return The button ItemStack with its name and description. To
* it, all the chooseable values will be appended (as well
* as the definition of the current value chosen) when asked
* for in {@link #getButton()}
*/
@NotNull public abstract ItemStack getBooleanButton();
/**
* @return Same as {@link #getBooleanButton()} but with
* the current value information appended to it.
*/
@NotNull @Override public ItemStack getButton() {
// Dictate the correct one
String input = isEnabled() ? "\u00a7aTRUE" : "\u00a7cFALSE";
// Copy and send
return RecipeMakerGUI.addLore(getBooleanButton().clone(),
SilentNumbers.toArrayList(
"", "\u00a77Current Value: " + input, "",
ChatColor.YELLOW + AltChar.listDash + " Right click to reset \u00a78(to\u00a74 FALSE\u00a78)\u00a7e.",
ChatColor.YELLOW + AltChar.listDash + " Left click to toggle this option." ));
}
}

View File

@ -0,0 +1,161 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba.type;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RecipeButtonAction;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
/**
* A button that cycles among a list of options, rather
* than just a number or TRUE/FALSE.
*
* @author Gunging
*/
public abstract class RBA_ChooseableButton extends RecipeButtonAction {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_ChooseableButton(@NotNull RecipeMakerGUI inv) { super(inv); }
/**
* Cycles to the next value
*
* @return True
*/
@Override public boolean runPrimary() {
// Get current
String current = getCurrentChooseableValue();
// Included?
int currentIndex = getChooseableList().indexOf(current);
// Invalid value? Cancel and default
if (currentIndex == -1) { return runSecondary(); }
// Increase and Cap
currentIndex++;
if (currentIndex >= getChooseableList().size()) { currentIndex = 0; }
// Get
String next = getChooseableList().get(currentIndex);
// Edits into persistent files
getInv().getNameSection().set(getChooseableConfigPath(), next);
clickSFX();
// Save
getInv().registerTemplateEdition();
return true;
}
/**
* Resets the list to the default value.
*
* @return True
*/
@Override public boolean runSecondary() {
// Clear the saved value
getInv().getNameSection().set(getChooseableConfigPath(), null);
clickSFX();
// Save
getInv().registerTemplateEdition();
return true;
}
/**
* @return The button ItemStack with its name and description. To
* it, all the chooseable values will be appended (as well
* as the definition of the current value chosen) when asked
* for in {@link #getButton()}
*/
@NotNull public abstract ItemStack getChooseableButton();
/**
* @return Same as {@link #getChooseableButton()} but with
* the chooseable information appended to it.
*/
@NotNull
@Override
public ItemStack getButton() {
// Whats the current?
String current = getCurrentChooseableValue();
// Build lore to add: Current value and definition
ArrayList<String> addedDefinitions = new ArrayList<>();
addedDefinitions.add("");
addedDefinitions.add("\u00a77Current Value:\u00a73 " + current);
addedDefinitions.addAll(SilentNumbers.chop(getChooseableDefinition(current), 50, " \u00a7b\u00a7o"));
addedDefinitions.add("");
addedDefinitions.add(ChatColor.YELLOW + AltChar.listDash + " Right click to return to default value.");
addedDefinitions.add(ChatColor.YELLOW + AltChar.listDash + " Left click to cycle through the options:");
for (String str : getChooseableList()) {
// Is it the one?
String pick = ChatColor.GOLD.toString();
if (str.equals(current)) { pick = ChatColor.RED.toString() + ChatColor.BOLD;}
addedDefinitions.add(pick + " " + AltChar.smallListDash + " \u00a77" + str); }
// Clone button and add the lore
return RecipeMakerGUI.addLore(getChooseableButton().clone(), addedDefinitions);
}
/**
* @return The path to save this value in the config
*/
@NotNull public abstract String getChooseableConfigPath();
/**
* @return The list of different options the player may choose from.
*/
@NotNull public abstract ArrayList<String> getChooseableList();
/**
* @return The value currently written onto the files.
*/
@NotNull public String getCurrentChooseableValue() {
// Get or default
String ret = getInv().getNameSection().getString(getChooseableConfigPath());
return ret != null ? ret : getDefaultValue();
}
/**
* @return Of al the entries in {@link #getChooseableList()}, which
* is the default / initial one?
*/
@NotNull public abstract String getDefaultValue();
/**
* @return Definition of what this choosing type does, for display in lore.
*
* @param ofChooseable Entry contained in the {@link #getChooseableList()} list.
*/
@NotNull public abstract String getChooseableDefinition(@NotNull String ofChooseable);
/**
* This method doesnt run
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
/**
* This method doesnt run
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
}

View File

@ -0,0 +1,145 @@
package net.Indyuce.mmoitems.gui.edition.recipe.rba.type;
import io.lumine.mythic.lib.api.util.AltChar;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.edition.StatEdition;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RecipeButtonAction;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A button that stores a numeric value yes.
*
* @author Gunging
*/
public abstract class RBA_DoubleButton extends RecipeButtonAction {
/**
* A button of an Edition Inventory. Nice!
*
* @param inv The edition inventory this is a button of
*/
public RBA_DoubleButton(@NotNull RecipeMakerGUI inv) { super(inv); }
/**
* @return Straight from the file, if this option is set to TRUE.
*/
public double getValue() { return getInv().getNameSection().getDouble(getDoubleConfigPath(), getDefaultValue()); }
/**
* @return The path to save this value in the config
*/
@NotNull public abstract String getDoubleConfigPath();
@NotNull public final String[] amountLog = {
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Write in the chat a number, ex $e2.5$b.")};
@NotNull public final String[] integerLog = {
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Write in the chat an integer number, ex $e8$b.")};
@Override public boolean runPrimary() {
// Query user for input
new StatEdition(getInv(), ItemStats.CRAFTING, RecipeMakerGUI.PRIMARY, this).enable(requireInteger() ? integerLog : amountLog);
// Success
return true;
}
/**
* The user needs to input nothing; Thus this method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@Override public void primaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException {
Double number;
if (requireInteger()) {
// Parse Integer
Integer asInteger = SilentNumbers.IntegerParse(message);
if (asInteger == null) { throw new IllegalArgumentException("Expected integer number instead of $u" + message); }
// ...
number = Double.valueOf(asInteger);
} else {
// Parse Double
number = SilentNumbers.DoubleParse(message);
if (number == null) { throw new IllegalArgumentException("Expected a number instead of $u" + message); }
}
// Out of range?
if (getRange() != null) {
// Out of range?
if (!getRange().inRange(number)) {
throw new IllegalArgumentException("Number $r" + number + "$b is out of range. Expected " + getRange().toStringColored());
} }
// Set value
getInv().getNameSection().set(getDoubleConfigPath(), number);
}
@Nullable public abstract QuickNumberRange getRange();
public abstract boolean requireInteger();
@Override public boolean runSecondary() {
// Remove value
getInv().getNameSection().set(getDoubleConfigPath(), null);
clickSFX();
/*
* Register template edition. This is only done automatically
* on the input process methods, not on the run button ones.
*/
getInv().registerTemplateEdition();
// Done
return true; }
/**
* The user needs to input nothing; Thus this method never runs.
*
* @param message Input from the user
* @param info Additional objects, specific to each case, provided.
*
* @throws IllegalArgumentException Never
*/
@SuppressWarnings("NoopMethodInAbstractClass")
@Override public void secondaryProcessInput(@NotNull String message, Object... info) throws IllegalArgumentException { }
public abstract double getDefaultValue();
/**
* @return The button ItemStack with its name and description. To
* it, all the chooseable values will be appended (as well
* as the definition of the current value chosen) when asked
* for in {@link #getButton()}
*/
@NotNull public abstract ItemStack getDoubleButton();
/**
* @return Same as {@link #getDoubleButton()} but with
* the current value information appended to it.
*/
@NotNull @Override public ItemStack getButton() {
// Copy and send
return RecipeMakerGUI.addLore(getDoubleButton().clone(),
SilentNumbers.toArrayList(
"", "\u00a77Current Value: " + getValue(), "",
ChatColor.YELLOW + AltChar.listDash + " Right click to reset \u00a78(to\u00a74 " + getDefaultValue() + "\u00a78)\u00a7e.",
ChatColor.YELLOW + AltChar.listDash + " Left click to toggle this option." ));
}
}

View File

@ -0,0 +1,75 @@
package net.Indyuce.mmoitems.gui.edition.recipe.recipes;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_BurningLegacy;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Shaped;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_CookingTime;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_Experience;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
/**
* The legacy recipes that are not supported by MythicLib that all happen to have to do
* with burning stuff - furnaces, campfires, the other furnaces...
*
* @author Gunging
*/
public class RMG_BurningLegacy extends RecipeMakerGUI {
@NotNull
HashMap<Integer, Integer> inputLinks = new HashMap<>();
/**
* An editor for a Shaped Recipe. Because the recipe is loaded from the YML when this is created,
* concurrent modifications of the same recipe are unsupported.
*
* @param player Player editing the recipe ig
* @param template Template of which a recipe is being edited
* @param recipeName Name of this recipe
*/
public RMG_BurningLegacy(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull String recipeName, @NotNull RecipeRegistry recipeRegistry) {
super(player, template, recipeName, recipeRegistry);
addButton(new RBA_HideFromBook(this));
addButton(new RBA_Experience(this));
addButton(new RBA_CookingTime(this));
// NO OUTPUT
if (!isShowingInput()) { switchInput(); }
// Get section and build interpreter
interpreter = new RMGRI_BurningLegacy(getNameSection());
// Bind inputs - Furnace only has which item to smelt
inputLinks.put(40, 0);
}
@Override public int getButtonsRow() { return 2; }
@Override
public void putRecipe(@NotNull Inventory target) {
// Fill inputs
for (Integer s : inputLinks.keySet()) { target.setItem(s, getDisplay(isShowingInput(), inputLinks.get(s))); }
}
@Override
int getInputSlot(int absolute) {
// Not an input? Not our business
@Nullable Integer found = inputLinks.get(absolute);
// Found or negative
return found != null ? found : -1;
}
@NotNull final RMGRI_BurningLegacy interpreter;
@NotNull @Override public RMG_RecipeInterpreter getInterpreter() { return interpreter; }
}

View File

@ -0,0 +1,83 @@
package net.Indyuce.mmoitems.gui.edition.recipe.recipes;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Shaped;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_InputOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
/**
* Edits shaped recipes, very nice.
* <br> <br> <code>
* - - - - - - - - - <br>
* 0 1 2 = 0 1 2 - - <br>
* 3 4 5 = 3 4 5 - R <br>
* 6 7 8 = 6 7 8 - - </code>
*
* @author Gunging
*/
public class RMG_Shaped extends RecipeMakerGUI {
@NotNull HashMap<Integer, Integer> inputLinks = new HashMap<>();
/**
* An editor for a Shaped Recipe. Because the recipe is loaded from the YML when this is created,
* concurrent modifications of the same recipe are unsupported.
*
* @param player Player editing the recipe ig
* @param template Template of which a recipe is being edited
* @param recipeName Name of this recipe
*/
public RMG_Shaped(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull String recipeName, @NotNull RecipeRegistry recipeRegistry) {
super(player, template, recipeName, recipeRegistry);
addButton(new RBA_InputOutput(this));
addButton(new RBA_HideFromBook(this));
// Get section and build interpreter
interpreter = new RMGRI_Shaped(getNameSection());
// Bind inputs
inputLinks.put(30, 0);
inputLinks.put(31, 1);
inputLinks.put(32, 2);
inputLinks.put(39, 3);
inputLinks.put(40, 4);
inputLinks.put(41, 5);
inputLinks.put(48, 6);
inputLinks.put(49, 7);
inputLinks.put(50, 8);
}
@Override public int getButtonsRow() { return 1; }
@Override
public void putRecipe(@NotNull Inventory target) {
// Fill inputs
for (Integer s : inputLinks.keySet()) { target.setItem(s, getDisplay(isShowingInput(), inputLinks.get(s))); }
}
@Override
int getInputSlot(int absolute) {
// Not an input? Not our business
@Nullable Integer found = inputLinks.get(absolute);
// Found or negative
return found != null ? found : -1;
}
@NotNull final RMGRI_Shaped interpreter;
@NotNull
@Override
public RMG_RecipeInterpreter getInterpreter() { return interpreter; }
}

View File

@ -0,0 +1,86 @@
package net.Indyuce.mmoitems.gui.edition.recipe.recipes;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Shapeless;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_InputOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
/**
* Edits shapeless recipes, very nice.
* <br> <br> <code>
* - - - - - - - - - <br>
* 0 1 2 = 0 1 2 - - <br>
* 3 4 5 = 3 4 5 - R <br>
* 6 7 8 = 6 7 8 - - </code>
*
* @author Gunging
*/
public class RMG_Shapeless extends RecipeMakerGUI {
@NotNull
final HashMap<Integer, Integer> inputLinks = new HashMap<>();
/**
* An editor for a Shapeless Recipe. Because the recipe is loaded from the YML when this is created,
* concurrent modifications of the same recipe are unsupported.
*
* @param player Player editing the recipe ig
* @param template Template of which a recipe is being edited
* @param recipeName Name of this recipe
*/
public RMG_Shapeless(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull String recipeName, @NotNull RecipeRegistry recipeRegistry) {
super(player, template, recipeName, recipeRegistry);
addButton(new RBA_InputOutput(this));
addButton(new RBA_HideFromBook(this));
// Get section and build interpreter
ConfigurationSection crafting = RecipeMakerGUI.getSection(getEditedSection(), "crafting");
ConfigurationSection recipe = RecipeMakerGUI.getSection(crafting, getRecipeRegistry().getRecipeConfigPath());
ConfigurationSection name = RecipeMakerGUI.getSection(recipe, getRecipeName());
interpreter = new RMGRI_Shapeless(name);
// Bind inputs
inputLinks.put(30, 0);
inputLinks.put(31, 1);
inputLinks.put(32, 2);
inputLinks.put(39, 3);
inputLinks.put(40, 4);
inputLinks.put(41, 5);
inputLinks.put(48, 6);
inputLinks.put(49, 7);
inputLinks.put(50, 8);
}
@Override public int getButtonsRow() { return 1; }
@Override
public void putRecipe(@NotNull Inventory target) {
// Fill inputs
for (Integer s : inputLinks.keySet()) { target.setItem(s, getDisplay(isShowingInput(), inputLinks.get(s))); }
}
@Override
int getInputSlot(int absolute) {
// Not an input? Not our business
@Nullable Integer found = inputLinks.get(absolute);
// Found or negative
return found != null ? found : -1;
}
@NotNull final RMGRI_Shapeless interpreter;
@NotNull @Override public RMG_RecipeInterpreter getInterpreter() { return interpreter; }
}

View File

@ -0,0 +1,85 @@
package net.Indyuce.mmoitems.gui.edition.recipe.recipes;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Smithing;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_DropGems;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_InputOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_SmithingEnchantments;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_SmithingUpgrades;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
/**
* Edits smithing recipes, very nice.
* <br> <br> <code>
* - - - - - - - - - <br>
* - - - = - - - - - <br>
* 0 - 1 = 0 - 1 - R <br>
* - - - = - - - - - </code>
*
* @author Gunging
*/
public class RMG_Smithing extends RecipeMakerGUI {
@NotNull
final HashMap<Integer, Integer> inputLinks = new HashMap<>();
/**
* An editor for a Shaped Recipe. Because the recipe is loaded from the YML when this is created,
* concurrent modifications of the same recipe are unsupported.
*
* @param player Player editing the recipe ig
* @param template Template of which a recipe is being edited
* @param recipeName Name of this recipe
*/
public RMG_Smithing(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull String recipeName, @NotNull RecipeRegistry recipeRegistry) {
super(player, template, recipeName, recipeRegistry);
// Get section and build interpreter
ConfigurationSection crafting = RecipeMakerGUI.getSection(getEditedSection(), "crafting");
ConfigurationSection recipe = RecipeMakerGUI.getSection(crafting, getRecipeRegistry().getRecipeConfigPath());
ConfigurationSection name = RecipeMakerGUI.getSection(recipe, getRecipeName());
interpreter = new RMGRI_Smithing(name);
// Bind inputs
inputLinks.put(39, 0);
inputLinks.put(41, 1);
// Extra buttons
addButton(new RBA_InputOutput(this));
addButton(new RBA_SmithingUpgrades(this));
addButton(new RBA_SmithingEnchantments(this));
addButton(new RBA_DropGems(this));
}
@Override
public void putRecipe(@NotNull Inventory target) {
// Fill inputs
for (Integer s : inputLinks.keySet()) { target.setItem(s, getDisplay(isShowingInput(), inputLinks.get(s))); }
}
@Override
int getInputSlot(int absolute) {
// Not an input? Not our business
@Nullable Integer found = inputLinks.get(absolute);
// Found or negative
return found != null ? found : -1;
}
@Override public int getButtonsRow() { return 1; }
@NotNull final RMGRI_Smithing interpreter;
@NotNull
@Override
public RMG_RecipeInterpreter getInterpreter() { return interpreter; }
}

View File

@ -0,0 +1,835 @@
package net.Indyuce.mmoitems.gui.edition.recipe.recipes;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.crafting.uifilters.VanillaUIFilter;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.crafting.uimanager.UIFilterManager;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import io.lumine.mythic.utils.items.ItemFactory;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.edition.StatEdition;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_AmountOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_InputOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RecipeButtonAction;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
/**
* Inventory displayed when the user edits one of the many
* recipes associated to an item. It has many functions:
* <br><br>
* * Choose amount of output <br>
* * Choose input (of course) <br>
* * Specify output in the input slots <br>
* * Preview the recipe <br>
* * Reload the recipe <br>
* * Make it auto-unlock in crafting book <br>
*
* @author Gunging
*/
@SuppressWarnings("unused")
public abstract class RecipeMakerGUI extends EditionInventory {
/**
* An editor for a recipe of this crafting system.
* <br> <br>
* Mind the difference between Recipe and Recipe Type: <br> <code>
*
* > Recipe: This specific recipe being edited. An item may have multiple recipes of the same type. <br><br>
* > Recipe Type: How to use the recipe, is it Shaped, Shapeless, Smithing, Smelting...?
* </code>
*
* @param player Player to display the Edition Inventory to
* @param template MMOItem Template being edited
* @param recipeName Name of this particular Recipe
* @param recipeRegistry Load/Save Information of this Recipe Type
*/
public RecipeMakerGUI(@NotNull Player player, @NotNull MMOItemTemplate template, @NotNull String recipeName, @NotNull RecipeRegistry recipeRegistry) {
super(player, template);
// Store name
this.recipeName = recipeName;
this.recipeRegistry = recipeRegistry;
// Create Inventory
myInventory = Bukkit.createInventory(this, 54, "Edit " + getRecipeRegistry().getRecipeTypeName() + " Recipe");
// Update old formats
moveInput();
// Identify sections
craftingSection = getSection(getEditedSection(), "crafting");
typeSection = getSection(craftingSection, getRecipeRegistry().getRecipeConfigPath());
nameSection = getSection(typeSection, recipeName);
// In general, they all have the amount output button
//noinspection NestedAssignment
addButton(amountButton = new RBA_AmountOutput(this, getCachedItem().clone()));
}
// Button Bar Buttons
@NotNull final ItemStack nextButtonPage = ItemFactory.of(Material.SPECTRAL_ARROW).name("\u00a7eMore Options \u00a7c»").build();
@NotNull final ItemStack prevButtonPage = ItemFactory.of(Material.SPECTRAL_ARROW).name("\u00a7c« \u00a7eMore Options").build();
@NotNull public final ItemStack noButton = ItemFactory.of(Material.IRON_BARS).name("\u00a78---").build();
// Ingredient-Related Buttons
@NotNull public final ItemStack emptySlot = ItemFactory.of(Material.BARRIER).name("\u00a77No Item").build();
@NotNull public final ItemStack airSlot = ItemFactory.of(Material.STRUCTURE_VOID).name("\u00a77No Item").build();
@NotNull final Inventory myInventory;
/**
* @return The inventory displayed to the player.
*/
@NotNull public Inventory getMyInventory() { return myInventory; }
/**
*[ID].base.crafting
*/
@NotNull final ConfigurationSection craftingSection;
/**
* @return [ID].base.crafting
*/
@NotNull public ConfigurationSection getCraftingSection() { return craftingSection; }
/**
*[ID].base.crafting.[TYPE]
*/
@NotNull final ConfigurationSection typeSection;
/**
* @return [ID].base.crafting.[TYPE]
*/
@NotNull public ConfigurationSection getTypeSection() { return typeSection; }
/**
*[ID].base.crafting.[TYPE].[NAME]
*/
@NotNull final ConfigurationSection nameSection;
/**
* @return [ID].base.crafting.[TYPE].[NAME]
*/
@NotNull public ConfigurationSection getNameSection() { return nameSection; }
@NotNull final RecipeRegistry recipeRegistry;
/**
* @return The information to save and load this recipe.
*/
@NotNull public RecipeRegistry getRecipeRegistry() { return recipeRegistry; }
/**
* The reference to the Amount Button, for ease of access
* of the ItemStack displayed for the output of this recipe.
*/
@NotNull final RBA_AmountOutput amountButton;
/**
* @return The reference to the Amount Button, for ease of access
* of the ItemStack displayed for the output of this recipe.
*/
@NotNull public RBA_AmountOutput getAmountButton() { return amountButton; }
@NotNull final String recipeName;
/**
* @return An item may have multiple recipes, this is the name
* of the one being edited. So far, historically, they
* have just been a number.
* <br>
* <br>
* In YML, <code>[ID].crafting.[recipe].[name]</code> this
* string is the value of [name]
* <br>
* Ex. <code>STEEL_SWORD.crafting.shaped.1</code>
*/
@NotNull public String getRecipeName() { return recipeName; }
int buttonsPage;
/**
* Map containing the absolute inventory slot links to the buttons placed there.
*/
@NotNull final HashMap<Integer, RecipeButtonAction> buttonsMap = new HashMap<>();
/**
* Puts the general buttons, used for any Recipe Maker variant.
* <br><br>
* The general template, where K is the edge, = is the equals edge,
* and r is the result item, looks like this:
* <br>
* <code>K K K K K K K K K </code><br>
* <code>K K K = K K K K K </code><br>
* <code>K K K = K K K K r </code><br>
* <code>K K K = K K K K K </code>
* <br><br>
* This is further edited, then, in {@link #putRecipe(Inventory)}, where
* for example, the crafting recipe, will show the items and empty slots
* in the correct places:
* <br>
* <code>K K K K K K K K K </code><br>
* <code>g g g = - - - K K </code><br>
* <code>- s - = - - - K r </code><br>
* <code>- s - = - - - K K </code>
*
* @param target Inventory being edited
*/
public void putButtons(@NotNull Inventory target) {
// Clear
buttonsMap.clear();
// Include page buttons
if (buttonsPage > 0) { myInventory.setItem((getButtonsRow() * 9) + 8, prevButtonPage); }
if (buttonsMap.size() >= ((buttonsPage + 1) * 7)) { myInventory.setItem((getButtonsRow() * 9), nextButtonPage); }
// Fill the space I guess
for (int p = 7 * buttonsPage; p < 7 * (buttonsPage + 1); p++) {
/*
* The job of this is to identify which slots of this
* inventory will trigger which action.
*
* If the slot has a recipe to edit, a connection will
* be made between clicking this and which recipe to
* edit via the HashMap 'recipeMap'
*
* But for that we must calculate which absolute slot
* of this inventory are we talking about...
*/
int absolute = buttonRowPageClamp(p);
/*
* Going through the whole page, first thing
* to check is that there is a recipe here.
*
* Note that clicking the very next glass pane
* creates a new recipe.
*/
if (p >= buttons.size()) {
// Just snooze
target.setItem(absolute, noButton);
// There exists a recipe for this slot
} else {
// Get button
RecipeButtonAction rmg = buttons.get(p);
// Display
target.setItem(absolute, rmg.getButton());
// Store
buttonsMap.put(absolute, rmg);
}
}
}
/**
* Restrains a number between 1 and 7, which is the allowed
* absolute slot values to put buttons onto.
* <br> <br>
* Basically, button #4 will be assigned to the fifth slot (#4) of
* the {@link #getButtonsRow()}th Row, just like button #11
*
* @param p Button number, Ex. 8
* @return Slot of the inventory it will be placed, Ex. 10
*/
public int buttonRowPageClamp(int p) {
/*
* A page is the seven center slots of the #getButtonsRow()
*
* #1 Obtain the relative column, and relative row
*
* #2 Convert to absolute inventory positions
*/
int red = SilentNumbers.floor(p / 7.00D);
p -= red * 7;
/*
* A page is the seven center slots of the #getButtonsRow()
*
* #1 Obtain the relative column
*
* #2 Convert to absolute inventory positions
*/
int rowAdditive = (getButtonsRow() * 9);
int columnAdditive = p + 1;
// Sum to obtain final
return rowAdditive + columnAdditive;
}
/**
* Should probably avoid this being row #0, since that will occlude
* the back button and those other edition inventory buttons.
*
* @return The inventory row at which the buttons will display.
*/
public abstract int getButtonsRow();
/**
*
* @param absolute Absolute slot clicked by the player, for example,
* 0 is the top left corner of the edition inventory.
*
* @return <code>-1</code> If the slot is not one of the <b>input ingredient</b>
* slots, or a number greater or equal to zero depending on which input
* ingredient it is.
*/
abstract int getInputSlot(int absolute);
/**
* Puts all the buttons onto this inventory.
*/
public void refreshInventory() {
addEditionInventoryItems(getMyInventory(), true);
putButtons(getMyInventory());
putRecipe(getMyInventory());
}
/**
* Puts the buttons specific for this kind of recipe, display
* in the correct places the input and output items.
*
* @param target The inventory being edited
*
* @see #putButtons(Inventory) for a better, more lengthy description.
*/
public abstract void putRecipe(@NotNull Inventory target);
/**
* Get the item stack associated with this slot, depending
* on Input or Output being edited. If it is air, it will
* return the chad {@link #emptySlot} ItemStack.
*
* @param input Should fetch from the INPUT section of the YML Config?
* @param slot Which slot of the crafting table?
*
* @return The correct stack to display.
*/
@NotNull public ItemStack getDisplay(boolean input, int slot) {
// Find poof
ProvidedUIFilter poof = input ? getInterpreter().getInput(slot) : getInterpreter().getOutput(slot);
// Null equals fail
if (poof == null || poof.isAir()) { return isShowingInput() ? emptySlot : airSlot; }
// Generate display
return poof.getDisplayStack(null);
}
/**
* All the buttons added by this recipe.
*/
@NotNull final ArrayList<RecipeButtonAction> buttons = new ArrayList<>();
/**
* Registers a button to check when clicking the Edition Inventory for this recipe.
*
* @param rba Method to run and evaluate the button click.
*/
public void addButton(@NotNull RecipeButtonAction rba) { buttons.add(rba); }
@NotNull public final String[] recipeLog = {
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Write in the chat the item you want, follow any format:"),
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Vanilla: $e[MATERIAL] [AMOUNT] $bex $eDIAMOND 2.."),
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "MMOItem: $e[TYPE].[ID] [AMOUNT] $bex $eSWORD.CUTLASS 1.."),
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "Other: $e[KEY] [ARG] [DAT] [AMOUNT]$b (check wiki)"),
FriendlyFeedbackProvider.quickForPlayer(FFPMMOItems.get(), "\u00a78Amount is in the range format, $e[min]..[max]\u00a78, assumed to be $r1..\u00a78 if unspecified.")};
/**
* @return The protocols to edit the ConfigurationSection based on the user input.
*/
@NotNull public abstract RMG_RecipeInterpreter getInterpreter();
@NotNull @Override public Inventory getInventory() {
// Put buttons
refreshInventory();
// That's it lets GOOOO
return myInventory;
}
@Override public void whenClicked(InventoryClickEvent event) {
// Clicked inventory was not the observed inventory? Not our business
if (event.getView().getTopInventory() != event.getClickedInventory()) { return; }
// Disallow any clicking.
event.setCancelled(true);
// Was it an ingredient slot?
int ingredient = getInputSlot(event.getRawSlot());
// Setting an ingredient?
if (event.getAction() == InventoryAction.PICKUP_ALL) {
// Is it an input slot?
if (ingredient >= 0) {
// Input or output?
if (isShowingInput()) {
// Query user for input
new StatEdition(this, ItemStats.CRAFTING, INPUT, getInterpreter(), ingredient).enable(recipeLog);
} else {
// Query user for output
new StatEdition(this, ItemStats.CRAFTING, OUTPUT, getInterpreter(), ingredient).enable(recipeLog); }
// Maybe its a button
} else {
// Find button
RecipeButtonAction rmg = buttonsMap.get(event.getRawSlot());
// Found?
if (rmg != null) { rmg.runPrimary(); }
}
// Removing an ingredient?
} else if (event.getAction() == InventoryAction.PICKUP_HALF) {
// Is it an input slot?
if (ingredient >= 0) {
// Input or output?
if (isShowingInput()) {
// Delete Input
getInterpreter().deleteInput(getSection(getEditedSection(), "crafting"), ingredient);
} else {
// Delete Output
getInterpreter().deleteOutput(getSection(getEditedSection(), "crafting"), getInputSlot(event.getRawSlot())); }
// Refresh yes
refreshInventory();
// Maybe its a button
} else {
// Find button
RecipeButtonAction rmg = buttonsMap.get(event.getRawSlot());
// Found?
if (rmg != null) { rmg.runSecondary(); }
}
}
}
//region ############------- Input/Output Switch -------############
/**
* There are several reasons why this was the best way, however
* unique looking. The best reason is that, for an user editing
* many recipes at once, its quicker to just toggle it globally.
*
* From a programmatic point of view, this is also easier to implement.
*/
@NotNull static final HashMap<UUID, Boolean> showingInput = new HashMap<>();
/**
* Change between showing input and output for some user.
*
* @param whom Player the inventory is built for
*/
public static void switchInputFor(@NotNull UUID whom) {
// Flip
showingInput.put(whom, !isShowingInputFor(whom));
}
/**
* @param whom Player the inventory is built for
*
* @return If the player wants to see the input recipes.
*/
public static boolean isShowingInputFor(@NotNull UUID whom) {
// The value, true by default
return showingInput.getOrDefault(whom, true);
}
/**
* @return If the player opening this inventory is looking at INPUT (as opposed to OUTPUT)
*/
public boolean isShowingInput() { return isShowingInputFor(getPlayer().getUniqueId()); }
/**
* Switch between input and output for this player.
*/
public void switchInput() { switchInputFor(getPlayer().getUniqueId()); }
//endregion
//region ############------- Constants -------############
/*
* Sure this could be in an enum but it annoys me to
* make a new file just such few constants.
*/
public static final int INPUT = 0;
public static final int OUTPUT = 1;
public static final int PRIMARY = 2;
public static final int SECONDARY = 3;
public static final String INPUT_INGREDIENTS = "input";
public static final String OUTPUT_INGREDIENTS = "output";
public static final ProvidedUIFilter AIR = new ProvidedUIFilter(new VanillaUIFilter(), "AIR", "0");
/**
* Why is it so cumbersome to have a one line renaming method?
*
* @param itm ItemStack.
*
* @param name Name to give to your item.
*
* @return The item, renamed.
*/
@NotNull public static ItemStack rename(@NotNull ItemStack itm, @NotNull String name) {
// Bruh
ItemMeta iMeta = itm.getItemMeta();
//noinspection ConstantConditions
iMeta.setDisplayName(MythicLib.plugin.parseColors(name));
itm.setItemMeta(iMeta);
return itm;
}
/**
* Why is it so cumbersome to have some lore lines method?
*
* @param itm ItemStack.
*
* @param lines Lore to add to the item
*
* @return The item, lored.
*/
@NotNull public static ItemStack addLore(@NotNull ItemStack itm, @NotNull ArrayList<String> lines) {
// Bruh
ItemMeta iMeta = itm.getItemMeta();
//noinspection ConstantConditions
List<String> currentLore = iMeta.getLore();
if (currentLore == null) { currentLore = new ArrayList<>(); }
// Add lore
for (String line : lines) { currentLore.add(MythicLib.plugin.parseColors(line)); }
iMeta.setLore(currentLore);
// That's it
itm.setItemMeta(iMeta);
return itm;
}
/**
* Because obviously getConfigurationSection had to be annotated with {@link Nullable} even
* through it does basically this same thing.
*
* @param root Root section
* @param path Path to get/create
*
* @return The subsection of this config.
*/
@NotNull public static ConfigurationSection getSection(@NotNull ConfigurationSection root, @NotNull String path) {
ConfigurationSection section = root.getConfigurationSection(path);
if (section == null) { section = root.createSection(path); }
return section; }
//endregion
//region ############------- Updating Legacy Formats -------############
/**
* In the past, crafting recipes only supported (for example) a 3x3 grid of input.
* This was stored under [ID].base.crafting.shaped.recipe
* <br> <br>
* This method moves that into [ID].crafting.shaped.recipe.input
*/
public void moveInput() {
ConfigurationSection crafting = getSection(getEditedSection(), "crafting");
ConfigurationSection shaped = getSection(crafting, getRecipeRegistry().getRecipeConfigPath());
// Move to generalized method
moveInput(shaped, recipeName);
}
/**
* Absolute brute force method that will alter this configuration section,
* note that code wont run the same after it has been called, as any variable
* that is not a ConfigurationSection within 3 levels deep of the passed
* section will be cleared.
*
* For advanced debug purposes only.
*
* @param section Section to mess up
*/
public static void tripleDebug(@NotNull ConfigurationSection section) {
MMOItems.print(null, "\u00a7d-\u00a77 Section \u00a75" + section.getCurrentPath(), null);
for (String key : section.getKeys(false)) {
MMOItems.print(null,"\u00a7d +\u00a77 " + key, null);
MMOItems.print(null,"\u00a7d-\u00a7e-\u00a77 As List \u00a75" + section.getCurrentPath() + "." + key + "\u00a77 {\u00a7d" + section.getStringList(key).size() + "\u00a77}", null);
for (String listKey : section.getStringList(key)) { MMOItems.print(null,"\u00a7d +\u00a7e-\u00a77" + listKey, null); }
ConfigurationSection asSection = getSection(section, key);
MMOItems.print(null,"\u00a78--\u00a7d-\u00a77 Section \u00a75" + asSection.getCurrentPath(), null);
for (String asKey : asSection.getKeys(false)) {
MMOItems.print(null,"\u00a78--\u00a7d +\u00a77 " + asKey, null);
MMOItems.print(null,"\u00a78--\u00a7d-\u00a7e-\u00a77 As List \u00a75" + asSection.getCurrentPath() + "." + asKey + "\u00a77 {\u00a7d" + asSection.getStringList(asKey).size() + "\u00a77}", null);
for (String listKey : asSection.getStringList(asKey)) { MMOItems.print(null,"\u00a78--\u00a7d +\u00a7e-\u00a77" + listKey, null); }
ConfigurationSection asESection = getSection(asSection, asKey);
MMOItems.print(null,"\u00a70--\u00a78--\u00a7d-\u00a77 Section \u00a75" + asESection.getCurrentPath(), null);
for (String asEKey : asESection.getKeys(false)) {
MMOItems.print(null,"\u00a70--\u00a78--\u00a7d +\u00a77 " + asEKey, null);
MMOItems.print(null,"\u00a70--\u00a78--\u00a7d-\u00a7e-\u00a77 As List \u00a75" + asESection.getCurrentPath() + "." + asEKey + "\u00a77 {\u00a7d" + asESection.getStringList(asEKey).size() + "\u00a77}", null);
for (String listKey : asESection.getStringList(asEKey)) { MMOItems.print(null,"\u00a70--\u00a78--\u00a7d +\u00a7e-\u00a77" + listKey, null); }
}
}
}
}
/**
* In the past, crafting recipes only supported (for example) a 3x3 grid of input.
* This was stored under [ID].base.crafting.shaped.recipe
* <br> <br>
* This method moves that into [ID].crafting.shaped.recipe.input
*
* @return The recipe name section result from this operation. <br>
* [ID].base.crafting.[TYPE].[NAME]
*/
public static ConfigurationSection moveInput(@NotNull ConfigurationSection recipeSection, @NotNull String nameOfRecipe) {
ConfigurationSection name;
/*
* This converts a recipe from the old format into the new.
*
* This is detected by the recipe name containing list information
*/
if (recipeSection.isConfigurationSection(nameOfRecipe)) {
//UPT//MMOItems.log("\u00a7a*\u00a77 Was config section");
// Get as config section
name = getSection(recipeSection, nameOfRecipe);
// Both must exist for smithing conversion
String item_yml = name.getString("input1");
String ingot_yml = name.getString("input2");
//UPT//MMOItems.log("\u00a7a*\u00a77 I1:\u00a76 " + item_yml + "\u00a77, I2:\u00a73 " + ingot_yml);
// Is it smithing?
if (item_yml != null && ingot_yml != null) {
//UPT//MMOItems.log("\u00a7a*\u00a77 Identified as \u00a7aSmithing");
// Build correctly
name.set("input1", null);
name.set("input2", null);
name.set(INPUT_INGREDIENTS, poofFromLegacy(item_yml) + "|" + poofFromLegacy(ingot_yml));
name.set(OUTPUT_INGREDIENTS, "v AIR 0|v AIR 0");
}
} else {
//UPT//MMOItems.log("\u00a7a*\u00a77 No config section");
// Get as String List
List<String> sc = recipeSection.getStringList(nameOfRecipe);
//UPT//MMOItems.log("\u00a78--\u00a7e-\u00a7d+\u00a77 Ingredients: \u00a75" + nameOfRecipe);
//UPT//for (String key : sc) { MMOItems.log("\u00a78--\u00a7e-\u00a7d +\u00a77" + key); }
// Clear
recipeSection.set(nameOfRecipe, null);
// Edit
name = getSection(recipeSection, nameOfRecipe);
name.set(INPUT_INGREDIENTS, sc);
}
// That's it
return name;
}
/**
* Converts legacy formats into ProvidedUIFilters
*
* @param legacy Legacy string
* @return Converted string as best as possible
*/
@NotNull public static String poofFromLegacy(@Nullable String legacy) {
if (legacy == null || "[]".equals(legacy)) {
//UPT//MMOItems.log("\u00a7b+\u00a77 Null, \u00a7b" + "v AIR - 1..");
return "v AIR - 1.."; }
// Spaces are assumed to be updated
if (legacy.contains(" ")) {
//UPT//MMOItems.log("\u00a7b+\u00a77 Mirror, \u00a7b" + legacy);
return legacy; }
// Split by amount
int aLoc = legacy.indexOf(':');
QuickNumberRange amount = new QuickNumberRange(1.0 , null);
if (aLoc > 0) {
String am = legacy.substring(aLoc + 1);
legacy = legacy.substring(0, aLoc);
Integer du = SilentNumbers.IntegerParse(am);
if (du == null) { du = 1; }
amount = new QuickNumberRange((double) du, null);
}
if (legacy.contains(".")) {
// Must be MMOItem
String[] mmo = legacy.split("\\.");
//UPT//MMOItems.log("\u00a7b+\u00a77 MMOItem, \u00a7bm " + mmo[0] + " " + mmo[1] + " " + amount);
// Build
return "m " + mmo[0] + " " + mmo[1] + " " + amount;
} else {
//UPT//MMOItems.log("\u00a7b+\u00a77 Vanilla, \u00a7bv " + legacy + " - " + amount);
// That's it
return "v " + legacy + " - " + amount;
}
}
/**
* To support legacy formats, at least for now, we use this method
* to read individual ingredients.
* <p></p>
* It supports the formats:
* <p><code>MATERIAL</code> (legacy vanilla material)
* </p><code>TYPE.ID</code> (legacy MMOItem)
* <p><code>KEY ARGUMENT DATA AMOUNT</code> (current)
* </p>
*
* @param str String that's should be in one of the formats above.
* @param ffp To tell what happened
*
* @throws IllegalArgumentException If not in the correct format.
*
* @return An ingredient read from this string.
*/
@NotNull public static ProvidedUIFilter readIngredientFrom(@NotNull String str, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
/*
* This entry, is it a vanilla material?
*
* Then build it as material.
*/
Material asMaterial = null;
try { asMaterial = Material.valueOf(str.toUpperCase().replace(" ", "_").replace("-", "_")); } catch (IllegalArgumentException ignored) {}
if (asMaterial != null) {
// Is it AIR?
if (asMaterial.isAir()) {
ProvidedUIFilter result = new ProvidedUIFilter(VanillaUIFilter.get(), "AIR", "0");
result.setAmountRange(new QuickNumberRange(null, null));
return result; }
// We snooze if its AIR or such
if (!asMaterial.isItem()) { throw new IllegalArgumentException("Invalid Ingredient $u" + str + "$b ($fNot an Item$b)."); }
// All right create filter and go
ProvidedUIFilter poof = UIFilterManager.getUIFilter("v", asMaterial.toString(), "", "1..", ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffp.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
/*
* Not a vanilla material, lets try to read it as a Legacy MMOItem thing.
*
* It must have a dot, and no spaces.
*/
if (str.contains(".") && !str.contains(" ")) {
// Split by dot
String[] split = str.split("\\.");
// Exactly two?
if (split.length == 2) {
// Well
String iType = split[0];
String iID = split[1];
// All right create filter and go
ProvidedUIFilter poof = UIFilterManager.getUIFilter("m", iType, iID, "1..", ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendAllTo(MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
}
/*
* Not a vanilla Material, but what about a UIFilter itself?
*/
ProvidedUIFilter poof = UIFilterManager.getUIFilter(str, ffp);
// Valid?
if (poof != null) {
// Add
return poof;
} else {
// Send all I guess
ffp.sendAllTo(MMOItems.getConsole());
// Ew
throw new IllegalArgumentException("Invalid Ingredient $u" + str);
}
}
//endregion
}

View File

@ -0,0 +1,99 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.recipe.CraftingType;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.WorkbenchIngredient;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_AmountOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_CookingTime;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_Experience;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RMG_BurningLegacy;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.BurningRecipeInformation;
import net.Indyuce.mmoitems.manager.RecipeManager;
import org.apache.commons.lang.Validate;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Recipes for furnaces. Ive never worked with these,
* and I don't claim to support them. They are just kinda
* compatible with the new crafting GUI and THAT'S IT.
*
* Still using Aria's code.
*
* @author Gunging
*/
public abstract class RMGRR_LegacyBurning implements RecipeRegistry {
@NotNull public abstract CraftingType getLegacyBurningType();
@NotNull public static String capitalizeFirst(@NotNull String str) { return str.substring(0, 1).toUpperCase() + str.substring(1); }
@NotNull @Override public String getRecipeConfigPath() { return getLegacyBurningType().name().toLowerCase(); }
@NotNull @Override public String getRecipeTypeName() { return "§8{§4§oL§8} " + capitalizeFirst(getRecipeConfigPath()); }
@SuppressWarnings("NotNullFieldNotInitialized")
@NotNull ItemStack displayListItem;
@NotNull @Override public ItemStack getDisplayListItem() {
//noinspection ConstantConditions
if (displayListItem == null) {
displayListItem = RecipeMakerGUI.rename(getLegacyBurningType().getItem(), FFPMMOItems.get().getExampleFormat() + capitalizeFirst(getRecipeConfigPath()) + " Recipe");
displayListItem = RecipeMakerGUI.addLore(displayListItem, SilentNumbers.toArrayList(" "));
displayListItem = RecipeMakerGUI.addLore(displayListItem, SilentNumbers.chop("\u00a74To accept input amounts, this recipe requires recipe-amounts to be enabled in the config.yml", 60, "\u00a74"));
}
return displayListItem; }
@Override public void openForPlayer(@NotNull EditionInventory inv, @NotNull String recipeName, Object... otherParams) { new RMG_BurningLegacy(inv.getPlayer(), inv.getEdited(), recipeName, this).open(inv.getPreviousPage()); }
/**
* Actually doesnt really send this thing to MythicLib
* its just guaranteed to throw an exception and uses
* the legacy MMOItems way of registering.
*/
@NotNull @Override public MythicRecipeBlueprint sendToMythicLib(@NotNull MMOItemTemplate template, @NotNull ConfigurationSection recipeTypeSection, @NotNull String recipeName, @NotNull Ref<NamespacedKey> namespace, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
// Never happening
Validate.isTrue(namespace.getValue() != null);
// Get correct section
ConfigurationSection recipeSection = RecipeMakerGUI.getSection(recipeTypeSection, recipeName);
// Get ingredient
String itemIngredient = recipeSection.getString("item");
if (itemIngredient == null) { throw new IllegalArgumentException("Missing input ingredient"); }
WorkbenchIngredient ingredient = RecipeManager.getWorkbenchIngredient(itemIngredient);
// Read amount from configuration
int outputAmount = recipeSection.getInt(RBA_AmountOutput.AMOUNT_INGREDIENTS, 1);
double experience = recipeSection.getDouble(RBA_Experience.FURNACE_EXPERIENCE, RBA_Experience.DEFAULT);
int time = recipeSection.getInt(RBA_CookingTime.FURNACE_TIME, SilentNumbers.round(RBA_CookingTime.DEFAULT));
boolean hideBook = recipeSection.getBoolean(RBA_HideFromBook.BOOK_HIDDEN, false);
// Make that recipe yes
BurningRecipeInformation info = new BurningRecipeInformation(ingredient, (float) experience, time);
// Yes
MMOItems.plugin.getRecipes().registerBurningRecipe(
getLegacyBurningType().getBurningType(),
template.newBuilder(0, null).build(),
info, outputAmount, namespace.getValue(), hideBook);
throw new IllegalArgumentException("");
}
}

View File

@ -0,0 +1,180 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry;
import io.lumine.mythic.lib.api.crafting.ingredients.ShapedIngredient;
import io.lumine.mythic.lib.api.crafting.outputs.MRORecipe;
import io.lumine.mythic.lib.api.crafting.outputs.MythicRecipeOutput;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeStation;
import io.lumine.mythic.lib.api.crafting.recipes.ShapedRecipe;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.crafting.MMOItemUIFilter;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Shaped;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_AmountOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RMG_Shaped;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
public class RMGRR_Shaped implements RecipeRegistry {
@NotNull @Override public String getRecipeConfigPath() { return "shaped"; }
@NotNull @Override public String getRecipeTypeName() { return "Shaped"; }
@NotNull final ItemStack displayListItem = RecipeMakerGUI.rename(new ItemStack(Material.CRAFTING_TABLE), FFPMMOItems.get().getExampleFormat() + "Shaped Recipe");
@NotNull @Override public ItemStack getDisplayListItem() { return displayListItem; }
@Override public void openForPlayer(@NotNull EditionInventory inv, @NotNull String recipeName, Object... otherParams) {
new RMG_Shaped(inv.getPlayer(), inv.getEdited(), recipeName, this).open(inv.getPreviousPage());
}
@NotNull
@Override
public MythicRecipeBlueprint sendToMythicLib(@NotNull MMOItemTemplate template, @NotNull ConfigurationSection recipeTypeSection, @NotNull String recipeName, @NotNull Ref<NamespacedKey> namespace, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
// Read some values
ConfigurationSection recipeSection = RecipeMakerGUI.moveInput(recipeTypeSection, recipeName);
NamespacedKey nk = namespace.getValue();
if (nk == null) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Illegal (Null) Namespace")); }
// Identify the input
ShapedRecipe input = shapedRecipeFromList(nk.getKey(), new ArrayList<>(recipeSection.getStringList(RecipeMakerGUI.INPUT_INGREDIENTS)), ffp);
if (input == null) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Shaped recipe containing only AIR, $fignored$b.")); }
// Read the options and output
ShapedRecipe output = shapedRecipeFromList(nk.getKey(), new ArrayList<>(recipeSection.getStringList(RecipeMakerGUI.OUTPUT_INGREDIENTS)), ffp);
int outputAmount = recipeSection.getInt(RBA_AmountOutput.AMOUNT_INGREDIENTS, 1);
boolean hideBook = recipeSection.getBoolean(RBA_HideFromBook.BOOK_HIDDEN, false);
// Build Output
ShapedRecipe outputItem = ShapedRecipe.single(nk.getKey(), new ProvidedUIFilter(MMOItemUIFilter.get(), template.getType().getId(), template.getId(), Math.max(outputAmount, 1)));
MythicRecipeOutput outputRecipe = new MRORecipe(outputItem, output);
// That's our blueprint :)
MythicRecipeBlueprint ret = new MythicRecipeBlueprint(input, outputRecipe, nk);
// Enable it
ret.deploy(MythicRecipeStation.WORKBENCH, namespace);
// Hide book if specified
if (hideBook) { namespace.setValue(null); }
// That's it
return ret;
}
/**
* Shorthand for reading list of strings that are intended to be shaped recipes: <br><br>
* <code>
* m MATERIAL AMETHYST|m MATERIAL AMETHYST|m MATERIAL AMETHYST <br>
* v AIR 0|v STICK 0|v AIR 0 <br>
* v AIR 0|v STICK 0|v AIR 0
* </code>
*
* @param namespace Some name to give to this thing, it can be anything really.
*
* @param recipe The list of strings, probably directly from your YML Config
*
* @param ffp Provider of failure text
*
* @return The most optimized version of this recipe, ready to be put into a Blueprint.
* <br> <br>
* Will be <code>null</code> if it would have been only AIR.
*
* @throws IllegalArgumentException If any ingredient is illegal (wrong syntax or something).
*/
@Nullable public static ShapedRecipe shapedRecipeFromList(@NotNull String namespace, @NotNull ArrayList<String> recipe, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
// All right lets read them
ArrayList<ShapedIngredient> poofs = new ArrayList<>();
boolean nonAirFound = false;
int rowNumber = 0;
//UPT//MMOItems.log("\u00a7e" + namespace + "\u00a77 loading:");
// Read through the recipe
for (String row : recipe) {
//UPT//MMOItems.log("\u00a7e-\u00a77 " + row);
// Update
String updatedRow = RMGRI_Shaped.updateRow(row);
//UPT//MMOItems.log("\u00a7eU-\u00a77 " + updatedRow);
/*
* This row could be in either legacy or new format, and we will assume no combination of them.
*
* Either:
* ANYTHING ANY.THING ANYTHING
*
* or
* A NYT THIN G|A NYT THING|A NYT THIN G
*/
// What are the three ingredients encoded in this row?
String[] positions;
if (updatedRow.contains("|")) {
// Split by |s
positions = updatedRow.split("\\|");
// Is legacy
} else {
// Split by spaces
positions = updatedRow.split(" ");
}
// Size not 3? BRUH
if (positions.length != 3) { throw new IllegalArgumentException("Invalid crafting table row $u" + updatedRow + "$b ($fNot exactly 3 ingredients wide$b)."); }
// Identify
ProvidedUIFilter left = RecipeMakerGUI.readIngredientFrom(positions[0], ffp);
ProvidedUIFilter center = RecipeMakerGUI.readIngredientFrom(positions[1], ffp);
ProvidedUIFilter right = RecipeMakerGUI.readIngredientFrom(positions[2], ffp);
if (!left.isAir()) { nonAirFound = true; }
if (!center.isAir()) { nonAirFound = true; }
if (!right.isAir()) { nonAirFound = true; }
/*
* To detect if a recipe can be crafted in the survival inventory (and remove extra AIR),
* we must see that a whole row AND a whole column be air. Not any column or row though,
* but any of those that do not cross the center.
*
* If a single left item is not air, LEFT is no longer an unsharped column.
* If a single right item is not air, RIGHT is no longer an unsharped column.
*
* All items must be air in TOP or BOTTOM for they to be unsharped.
*/
// Bake
ShapedIngredient leftIngredient = new ShapedIngredient(left, 0, -rowNumber);
ShapedIngredient centerIngredient = new ShapedIngredient(center, 1, -rowNumber);
ShapedIngredient rightIngredient = new ShapedIngredient(right, 2, -rowNumber);
// Parse and add
poofs.add(leftIngredient);
poofs.add(centerIngredient);
poofs.add(rightIngredient);
// Prepare for next row
rowNumber++;
}
if (!nonAirFound) { return null; }
// Make ingredients
return ShapedRecipe.unsharpen((new ShapedRecipe(namespace, poofs)));
}
}

View File

@ -0,0 +1,94 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicRecipeIngredient;
import io.lumine.mythic.lib.api.crafting.outputs.MRORecipe;
import io.lumine.mythic.lib.api.crafting.outputs.MythicRecipeOutput;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeStation;
import io.lumine.mythic.lib.api.crafting.recipes.ShapedRecipe;
import io.lumine.mythic.lib.api.crafting.recipes.ShapelessRecipe;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.api.crafting.MMOItemUIFilter;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_AmountOutput;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RBA_HideFromBook;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RMG_Shapeless;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class RMGRR_Shapeless implements RecipeRegistry {
@NotNull @Override public String getRecipeTypeName() { return "Shapeless"; }
@NotNull @Override public String getRecipeConfigPath() { return "shapeless"; }
@NotNull final ItemStack displayListItem = RecipeMakerGUI.rename(new ItemStack(Material.OAK_LOG), FFPMMOItems.get().getExampleFormat() + "Shapeless Recipe");
@NotNull @Override public ItemStack getDisplayListItem() { return displayListItem; }
@Override public void openForPlayer(@NotNull EditionInventory inv, @NotNull String recipeName, Object... otherParams) {
new RMG_Shapeless(inv.getPlayer(), inv.getEdited(), recipeName, this).open(inv.getPreviousPage());
}
@NotNull
@Override
public MythicRecipeBlueprint sendToMythicLib(@NotNull MMOItemTemplate template, @NotNull ConfigurationSection recipeTypeSection, @NotNull String recipeName, @NotNull Ref<NamespacedKey> namespace, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
// Prior Preparations (update old formats)
RecipeMakerGUI.moveInput(recipeTypeSection, recipeName);
// Read some values
ConfigurationSection recipeSection = RecipeMakerGUI.getSection(recipeTypeSection, recipeName);
NamespacedKey nk = namespace.getValue();
if (nk == null) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Illegal (Null) Namespace")); }
//region Identify the input
ArrayList<MythicRecipeIngredient> poofs = new ArrayList<>();
ArrayList<String> recipe = new ArrayList<>(recipeSection.getStringList(RecipeMakerGUI.INPUT_INGREDIENTS));
// Read from the recipe
boolean nonAirFound = false;
for (String str : recipe) {
// Null is a sleeper
if (str == null || "AIR".equals(str)) { continue; }
// Add
ProvidedUIFilter p = RecipeMakerGUI.readIngredientFrom(str, ffp);
nonAirFound = true;
poofs.add(new MythicRecipeIngredient(p));
}
if (!nonAirFound) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Shapeless recipe containing only AIR, $fignored$b.")); }
ShapelessRecipe input = new ShapelessRecipe(nk.getKey(), poofs);
//endregion
// Read the options and output
ShapedRecipe output = RMGRR_Shaped.shapedRecipeFromList(nk.getKey(), new ArrayList<>(recipeSection.getStringList(RecipeMakerGUI.OUTPUT_INGREDIENTS)), ffp);
int outputAmount = recipeSection.getInt(RBA_AmountOutput.AMOUNT_INGREDIENTS, 1);
boolean hideBook = recipeSection.getBoolean(RBA_HideFromBook.BOOK_HIDDEN, false);
// Build Output
ShapedRecipe outputItem = ShapedRecipe.single(nk.getKey(), new ProvidedUIFilter(MMOItemUIFilter.get(), template.getType().getId(), template.getId(), Math.max(outputAmount, 1)));
MythicRecipeOutput outputRecipe = new MRORecipe(outputItem, output);
// That's our blueprint :)
MythicRecipeBlueprint ret = new MythicRecipeBlueprint(input, outputRecipe, nk);
// Enable it
ret.deploy(MythicRecipeStation.WORKBENCH, namespace);
// Hide book if specified
if (hideBook) { namespace.setValue(null); }
// That's it
return ret;
}
}

View File

@ -0,0 +1,116 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicRecipeIngredient;
import io.lumine.mythic.lib.api.crafting.outputs.MythicRecipeOutput;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeStation;
import io.lumine.mythic.lib.api.crafting.recipes.ShapedRecipe;
import io.lumine.mythic.lib.api.crafting.recipes.ShapelessRecipe;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.api.crafting.recipe.CustomSmithingRecipe;
import net.Indyuce.mmoitems.api.crafting.recipe.SmithingCombinationType;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMGRI_Smithing;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.*;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RMG_Smithing;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class RMGRR_Smithing implements RecipeRegistry {
@NotNull @Override public String getRecipeTypeName() { return "Smithing"; }
@NotNull @Override public String getRecipeConfigPath() { return "smithing"; }
@NotNull final ItemStack displayListItem = RecipeMakerGUI.rename(new ItemStack(Material.SMITHING_TABLE), FFPMMOItems.get().getExampleFormat() + "Smithing Recipe");
@NotNull @Override public ItemStack getDisplayListItem() { return displayListItem; }
@Override public void openForPlayer(@NotNull EditionInventory inv, @NotNull String recipeName, Object... otherParams) {
new RMG_Smithing(inv.getPlayer(), inv.getEdited(), recipeName, this).open(inv.getPreviousPage());
}
@NotNull
@Override
public MythicRecipeBlueprint sendToMythicLib(@NotNull MMOItemTemplate template, @NotNull ConfigurationSection recipeTypeSection, @NotNull String recipeName, @NotNull Ref<NamespacedKey> namespace, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException {
// Prior Preparations (update old formats)
RecipeMakerGUI.moveInput(recipeTypeSection, recipeName);
// Read some values
ConfigurationSection recipeSection = RecipeMakerGUI.getSection(recipeTypeSection, recipeName);
NamespacedKey nk = namespace.getValue();
if (nk == null) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Illegal (Null) Namespace")); }
//region Identify the input
// Find value in files
String input = RMGRI_Smithing.updateIngredients(recipeSection.getString(RecipeMakerGUI.INPUT_INGREDIENTS));
String[] inputSplit = input.split("\\|");
// All right lets read them
ProvidedUIFilter itemPoof = RecipeMakerGUI.readIngredientFrom(inputSplit[0], ffp);
ProvidedUIFilter ingotPoof = RecipeMakerGUI.readIngredientFrom(inputSplit[1], ffp);
if (itemPoof.isAir() || ingotPoof.isAir()) { throw new IllegalArgumentException(FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Smithing recipe containing AIR, $fignored$b.")); }
// Make ingredients
MythicRecipeIngredient itemIngredient = new MythicRecipeIngredient(itemPoof);
MythicRecipeIngredient ingotIngredient = new MythicRecipeIngredient(ingotPoof);
// Make input recipes
ShapelessRecipe inputItem = new ShapelessRecipe(nk.getKey(), itemIngredient);
ShapelessRecipe inputIngot = new ShapelessRecipe(nk.getKey(), ingotIngredient);
//endregion
//region Identify the output of ingredients
// Find value in files
String output = RMGRI_Smithing.updateIngredients(recipeSection.getString(RecipeMakerGUI.OUTPUT_INGREDIENTS));
String[] outputSplit = output.split("\\|");
// All right lets read them
ProvidedUIFilter itemOPoof = RecipeMakerGUI.readIngredientFrom(outputSplit[0], ffp);
ProvidedUIFilter ingotOPoof = RecipeMakerGUI.readIngredientFrom(outputSplit[1], ffp);
// Make output recipes
ShapedRecipe outputItem = itemOPoof.isAir() ? null : ShapedRecipe.single(nk.getKey(), itemOPoof);
ShapedRecipe outputIngot = ingotOPoof.isAir() ? null : ShapedRecipe.single(nk.getKey(), ingotOPoof);
//endregion
// Read the options and output
int outputAmount = recipeSection.getInt(RBA_AmountOutput.AMOUNT_INGREDIENTS, 1);
boolean dropGems = recipeSection.getBoolean(RBA_DropGems.SMITH_GEMS, false);
SmithingCombinationType upgradeEffect = readSCT(recipeSection.getString(RBA_SmithingEnchantments.SMITH_ENCHANTS));
SmithingCombinationType enchantEffect = readSCT(recipeSection.getString(RBA_SmithingUpgrades.SMITH_UPGRADES));
// Build Output
CustomSmithingRecipe outputRecipe = new CustomSmithingRecipe(template, dropGems, enchantEffect, upgradeEffect, outputAmount);
outputRecipe.setMainInputConsumption(outputItem);
outputRecipe.setIngotInputConsumption(outputIngot);
// That's our blueprint :)
MythicRecipeBlueprint ret = new MythicRecipeBlueprint(inputItem, outputRecipe, nk);
ret.addSideCheck("ingot", inputIngot);
// Enable it
ret.deploy(MythicRecipeStation.SMITHING, namespace);
// That's it
return ret;
}
@NotNull SmithingCombinationType readSCT(@Nullable String str) {
// Default value is max
if (str == null) { return SmithingCombinationType.MAXIMUM; }
// Correct syntax or default
try { return SmithingCombinationType.valueOf(str); } catch (IllegalArgumentException ignored) { return SmithingCombinationType.MAXIMUM; }
}
}

View File

@ -0,0 +1,82 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* With information on displaying and creating Recipes.
*
* @author Gunging
*/
public interface RecipeRegistry {
/**
* @return In item YML configurations, recipes are saved under
* <code>[ID].crafting.[recipe]</code>, where this string
* is the value of [recipe].
* <br>
* Ex. <code>STEEL_SWORD.crafting.shaped</code>
*/
@NotNull String getRecipeConfigPath();
/**
* When making an inventory, the chest name reads '{@code Recipe Editor - ######}' <br>
* This method retuns the value of that '######'.
* <br><br>
* For example: <br>
* {@code Recipe Editor - Brewing}
*
* @return The type of recipe this is for.
*/
@NotNull
String getRecipeTypeName();
/**
* @return The item that means this type of recipes.
* For example a workbench for shaped recipes,
* or a furnace for smelting.
*/
@NotNull ItemStack getDisplayListItem();
/**
* Opens the correct recipe to the player.
*
* @param inv Edition Inventory by which the player is opening this
* @param recipeName Name of the recipe
* @param otherParams Whatever else required by the constructor of the {@link net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI}
*/
void openForPlayer(@NotNull EditionInventory inv, @NotNull String recipeName, Object... otherParams);
/**
* This is the part that sends the recipe to mythiclib and what not.
*
* @param recipeTypeSection The configuration section [ID].base.crafting.[TYPE]
*
* You kind of have access to all other loaded recipes of this type,
* not only the one being loaded, but please just load the one passed
* as 'recipeName' parameter.
*
* @param recipeName Name of <u>the</u> recipe that is being loaded.
*
* @param namespace Namespace under which you should save this recipe.
*
* It will initially have the Namespaced Key you should use, but
* when you pass it to MythicLib, MythicLib will make it null if
* the recipe fails to register onto the crafting book, which is
* the expected behaviour.
*
* @return The Activated Recipe Blueprint (so that it can be unloaded when reloading recipes)
*
* @throws IllegalArgumentException If anything goes wrong. THIS MEANS THE RECIPE WAS NOT ENABLED.
*
*/
@NotNull MythicRecipeBlueprint sendToMythicLib(@NotNull MMOItemTemplate template, @NotNull ConfigurationSection recipeTypeSection, @NotNull String recipeName, @NotNull Ref<NamespacedKey> namespace, @NotNull FriendlyFeedbackProvider ffp) throws IllegalArgumentException;
}

View File

@ -0,0 +1,48 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.WorkbenchIngredient;
import net.Indyuce.mmoitems.manager.RecipeManager;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
/**
* Used to handle furnace/smoker/campfire/furnace
* extra crafting recipe parameters
*
* @author ASangarin
*/
public class BurningRecipeInformation {
private final WorkbenchIngredient choice;
private final float exp;
private final int burnTime;
public BurningRecipeInformation(@NotNull ConfigurationSection config) {
// Get item
String itemIngredient = config.getString("item");
if (itemIngredient == null) { throw new IllegalArgumentException("Invalid input ingredient"); }
// Get
choice = RecipeManager.getWorkbenchIngredient(itemIngredient);
exp = (float) config.getDouble("exp", 0.35);
burnTime = config.getInt("time", 200);
}
public BurningRecipeInformation(@NotNull WorkbenchIngredient ingredient, float exp, int burnTime) {
choice = ingredient;
this.exp = exp;
this.burnTime = burnTime;
}
public int getBurnTime() {
return burnTime;
}
public WorkbenchIngredient getChoice() {
return choice;
}
public float getExp() {
return exp;
}
}

View File

@ -0,0 +1,12 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy;
import net.Indyuce.mmoitems.api.recipe.CraftingType;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_LegacyBurning;
import org.jetbrains.annotations.NotNull;
/**
* The blast furnace yes
*/
public class RMGRR_LBBlast extends RMGRR_LegacyBurning {
@NotNull @Override public CraftingType getLegacyBurningType() { return CraftingType.BLAST; }
}

View File

@ -0,0 +1,12 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy;
import net.Indyuce.mmoitems.api.recipe.CraftingType;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_LegacyBurning;
import org.jetbrains.annotations.NotNull;
/**
* The campfire yes
*/
public class RMGRR_LBCampfire extends RMGRR_LegacyBurning {
@NotNull @Override public CraftingType getLegacyBurningType() { return CraftingType.CAMPFIRE; }
}

View File

@ -0,0 +1,13 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy;
import net.Indyuce.mmoitems.api.recipe.CraftingType;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_LegacyBurning;
import org.jetbrains.annotations.NotNull;
/**
* The furnace yes
*/
public class RMGRR_LBFurnace extends RMGRR_LegacyBurning {
@NotNull @Override public CraftingType getLegacyBurningType() { return CraftingType.FURNACE; }
}

View File

@ -0,0 +1,12 @@
package net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy;
import net.Indyuce.mmoitems.api.recipe.CraftingType;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RMGRR_LegacyBurning;
import org.jetbrains.annotations.NotNull;
/**
* The smoker yes
*/
public class RMGRR_LBSmoker extends RMGRR_LegacyBurning {
@NotNull @Override public CraftingType getLegacyBurningType() { return CraftingType.SMOKER; }
}

View File

@ -8,8 +8,11 @@ import net.Indyuce.mmoitems.gui.ItemBrowser;
import net.Indyuce.mmoitems.gui.PluginInventory; import net.Indyuce.mmoitems.gui.PluginInventory;
import net.Indyuce.mmoitems.gui.edition.EditionInventory; import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.ItemEdition; import net.Indyuce.mmoitems.gui.edition.ItemEdition;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeBrowserGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeEdition; import net.Indyuce.mmoitems.gui.edition.recipe.RecipeEdition;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeListEdition; import net.Indyuce.mmoitems.gui.edition.recipe.RecipeListEdition;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeListGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -61,12 +64,18 @@ public class GuiListener implements Listener {
MMOItemTemplate template = ((EditionInventory) inventory).getEdited(); MMOItemTemplate template = ((EditionInventory) inventory).getEdited();
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + AltChar.rightArrow + " Back")) { if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + AltChar.rightArrow + " Back")) {
if (inventory instanceof ItemEdition)
new ItemBrowser(player, template.getType()).open(); // Open the Item Browser yes
else if (inventory instanceof RecipeEdition) if (inventory instanceof ItemEdition) { new ItemBrowser(player, template.getType()).open(); }
new RecipeListEdition(player, template).open(((EditionInventory) inventory).getPreviousPage());
else // Open the RECIPE TYPE BROWSER stat thing
new ItemEdition(player, template).onPage(((EditionInventory) inventory).getPreviousPage()).open(); else if ((inventory instanceof RecipeListGUI)) { new RecipeBrowserGUI(player, template).open(((EditionInventory) inventory).getPreviousPage()); }
// Open the RECIPE LIST thing
else if ((inventory instanceof RecipeMakerGUI)) { new RecipeListGUI(player, template, ((RecipeMakerGUI) inventory).getRecipeRegistry()).open(((EditionInventory) inventory).getPreviousPage()); }
// Just open the ITEM EDITION I guess
else { new ItemEdition(player, template).onPage(((EditionInventory) inventory).getPreviousPage()).open(); }
} }
} }
} }

View File

@ -229,6 +229,9 @@ public class ConfigManager implements Reloadable {
revisionOptions = keepData != null ? new ReforgeOptions(keepData) : new ReforgeOptions(false, false, false, false, false, false, false, true); revisionOptions = keepData != null ? new ReforgeOptions(keepData) : new ReforgeOptions(false, false, false, false, false, false, false, true);
phatLootsOptions = phatLoots != null ? new ReforgeOptions(phatLoots) : new ReforgeOptions(false, false, false, false, false, false, false, true); phatLootsOptions = phatLoots != null ? new ReforgeOptions(phatLoots) : new ReforgeOptions(false, false, false, false, false, false, false, true);
List<String> exemptedPhatLoots = MMOItems.plugin.getConfig().getStringList("item-revision.disable-phat-loot");
for (String epl : exemptedPhatLoots) { phatLootsOptions.addToBlacklist(epl); }
try { try {
defaultItemCapacity = new NumericStatFormula(MMOItems.plugin.getConfig().getConfigurationSection("default-item-capacity")); defaultItemCapacity = new NumericStatFormula(MMOItems.plugin.getConfig().getConfigurationSection("default-item-capacity"));
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {

View File

@ -4,13 +4,19 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint; import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeBlueprint;
import io.lumine.mythic.lib.api.crafting.recipes.MythicRecipeStation; import io.lumine.mythic.lib.api.crafting.uifilters.VanillaUIFilter;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.util.Ref; import io.lumine.mythic.lib.api.util.Ref;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory; import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackMessage;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider; import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.api.crafting.recipe.SmithingCombinationType; import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.api.crafting.MMOItemUIFilter;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems; import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import org.apache.commons.lang.Validate; import net.Indyuce.mmoitems.gui.edition.recipe.RecipeBrowserGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.RecipeRegistry;
import net.Indyuce.mmoitems.gui.edition.recipe.registry.burninglegacy.BurningRecipeInformation;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Keyed; import org.bukkit.Keyed;
import org.bukkit.Material; import org.bukkit.Material;
@ -27,7 +33,6 @@ import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.RecipeChoice; import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.SmokingRecipe; import org.bukkit.inventory.SmokingRecipe;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
@ -37,7 +42,6 @@ import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.AirIngredient;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.MMOItemIngredient; import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.MMOItemIngredient;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.VanillaIngredient; import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.VanillaIngredient;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.WorkbenchIngredient; import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.WorkbenchIngredient;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.MythicLib;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -67,6 +71,7 @@ public class RecipeManager implements Reloadable {
*/ */
final HashMap<NamespacedKey, MythicRecipeBlueprint> customRecipes = new HashMap<>(); final HashMap<NamespacedKey, MythicRecipeBlueprint> customRecipes = new HashMap<>();
final ArrayList<MythicRecipeBlueprint> booklessRecipes = new ArrayList<>(); final ArrayList<MythicRecipeBlueprint> booklessRecipes = new ArrayList<>();
@NotNull ArrayList<NamespacedKey> blacklistedFromAutomaticDiscovery = new ArrayList<>();
private boolean book, amounts; private boolean book, amounts;
@ -101,50 +106,72 @@ public class RecipeManager implements Reloadable {
// Does it have a crafting recipe? // Does it have a crafting recipe?
if (config.contains(template.getId() + ".base.crafting")) { if (config.contains(template.getId() + ".base.crafting")) {
try { // Get section containing the crafting recipes
ConfigurationSection section = RecipeMakerGUI.getSection(config, template.getId() + ".base.crafting");
ConfigurationSection section = config.getConfigurationSection(template.getId() + ".base.crafting"); // All loaded recipes
for (String recipeType : RecipeBrowserGUI.getRegisteredRecipes()) {
// Delegate recipes to their parsers // Is it in-yo?
if (section.contains("shaped")) if (section.contains(recipeType)) {
section.getConfigurationSection("shaped").getKeys(false).forEach(
recipe -> registerRecipe(type, template.getId(), section.getStringList("shaped." + recipe), false, recipe));
if (section.contains("shapeless"))
section.getConfigurationSection("shapeless").getKeys(false).forEach(
recipe -> registerRecipe(type, template.getId(), section.getStringList("shapeless." + recipe), true, recipe));
if (section.contains("furnace"))
section.getConfigurationSection("furnace").getKeys(false)
.forEach(recipe -> registerBurningRecipe(BurningRecipeType.FURNACE, type, template.getId(),
new BurningRecipeInformation(section.getConfigurationSection("furnace." + recipe)), recipe));
if (section.contains("blast"))
section.getConfigurationSection("blast").getKeys(false)
.forEach(recipe -> registerBurningRecipe(BurningRecipeType.BLAST, type, template.getId(),
new BurningRecipeInformation(section.getConfigurationSection("blast." + recipe)), recipe));
if (section.contains("smoker"))
section.getConfigurationSection("smoker").getKeys(false)
.forEach(recipe -> registerBurningRecipe(BurningRecipeType.SMOKER, type, template.getId(),
new BurningRecipeInformation(section.getConfigurationSection("smoker." + recipe)), recipe));
if (section.contains("campfire"))
section.getConfigurationSection("campfire").getKeys(false)
.forEach(recipe -> registerBurningRecipe(BurningRecipeType.CAMPFIRE, type, template.getId(),
new BurningRecipeInformation(section.getConfigurationSection("campfire." + recipe)), recipe));
if (section.contains("smithing"))
section.getConfigurationSection("smithing").getKeys(false).forEach(recipe -> registerSmithingRecipe(type,
template.getId(), section.getConfigurationSection("smithing." + recipe), recipe));
// Uh heck // Get Registry
} catch (IllegalArgumentException exception) { RecipeRegistry rr = RecipeBrowserGUI.getRegisteredRecipe(recipeType);
// Add message // Get recipe type section
ffp.log(FriendlyFeedbackCategory.ERROR, "Could not load recipe of $f{0} {1}$b: {2}", ConfigurationSection typeSection = RecipeMakerGUI.getSection(section, recipeType);
type.getId(), template.getId(), exception.getMessage());
// Register dem
for (String recipeName : typeSection.getKeys(false)) {
// Generate its key
NamespacedKey nk = getRecipeKey(template.getType(), template.getId(), recipeType, recipeName);
// Wrap
Ref<NamespacedKey> nkRef = new Ref<>(nk);
// Error yes
FriendlyFeedbackProvider ffpMinor = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffpMinor.activatePrefix(true, "Recipe of $u" + template.getType() + " " + template.getId());
// Send to mythiclib
try {
// The result of sending to MythicLib
MythicRecipeBlueprint blueprint = rr.sendToMythicLib(template, typeSection, recipeName, nkRef, ffpMinor);
// Was it registered in the book, then?
if (nkRef.getValue() != null) {
customRecipes.put(nkRef.getValue(), blueprint);
// Bookless, include in the other list.
} else { booklessRecipes.add(blueprint); }
// Well something went wrong...
} catch (IllegalArgumentException error) {
// Empty message? Snooze that
if (!error.getMessage().isEmpty()) {
// Log error
MMOItems.print(null, "Cannot register custom recipe '$u{2}$b' for $e{0} {1}$b;$f {3}", "Custom Crafting", type.getId(), template.getId(), recipeName, error.getMessage());
// Include failures in the report
ffpMinor.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffpMinor.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
}
}
}
}
} }
} }
} }
} }
// Log all // Log relevant messages
ffp.sendAllTo(MMOItems.getConsole()); ffp.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffp.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
// Sort recipes // Sort recipes
sortRecipes(); sortRecipes();
@ -153,97 +180,19 @@ public class RecipeManager implements Reloadable {
Bukkit.getScheduler().runTask(MMOItems.plugin, () -> getLoadedLegacyRecipes().forEach(Bukkit::addRecipe)); Bukkit.getScheduler().runTask(MMOItems.plugin, () -> getLoadedLegacyRecipes().forEach(Bukkit::addRecipe));
} }
public void registerBurningRecipe(BurningRecipeType recipeType, Type type, String id, BurningRecipeInformation info, String recipeId) { public void registerBurningRecipe(@NotNull BurningRecipeType recipeType, @NotNull MMOItem mmo, @NotNull BurningRecipeInformation info, int amount, @NotNull NamespacedKey key, boolean hidden) {
NamespacedKey key = getRecipeKey(type, id, recipeType.getPath(), recipeId);
MMOItem mmo = MMOItems.plugin.getMMOItem(type, id); // Build its item stacc
final int amount = mmo.hasData(ItemStats.CRAFT_AMOUNT) ? (int) ((DoubleData) mmo.getData(ItemStats.CRAFT_AMOUNT)).getValue() : 1;
ItemStack stack = mmo.newBuilder().build(); ItemStack stack = mmo.newBuilder().build();
stack.setAmount(amount); stack.setAmount(amount);
// Do whatever this is / I just wont touch it
CookingRecipe<?> recipe = recipeType.provideRecipe(key, stack, info.getChoice().toBukkit(), info.getExp(), info.getBurnTime()); CookingRecipe<?> recipe = recipeType.provideRecipe(key, stack, info.getChoice().toBukkit(), info.getExp(), info.getBurnTime());
// Register that recipe lets goo
loadedLegacyRecipes.add(recipe); loadedLegacyRecipes.add(recipe);
}
public void registerSmithingRecipe(@NotNull Type type, @NotNull String id, @NotNull ConfigurationSection section, @NotNull String number) throws IllegalArgumentException { if (hidden) { blacklistedFromAutomaticDiscovery.add(key); }
Validate.isTrue(section.isString("input1") && section.isString("input2"), "Invalid smithing recipe for '" + type.getId() + " . " + id + "'");
String item = section.getString("input1");
String ingot = section.getString("input2");
boolean dropGems = section.getBoolean("drop-gems", false);
String upgrade = section.getString("upgrades" );
String enchants = section.getString("enchantments" );
if (item == null) { item = ""; }
if (ingot == null) { ingot = ""; }
if (upgrade == null) { upgrade = SmithingCombinationType.MAXIMUM.toString(); }
if (enchants == null) { enchants = SmithingCombinationType.MAXIMUM.toString(); }
MythicRecipeBlueprint blueprint = CustomRecipe.generateSmithing(type, id, item, ingot, dropGems, enchants, upgrade, getRecipeKey(type, id, "smithing", number).getKey());
// Enable it
Ref<NamespacedKey> nk = new Ref<>();
blueprint.deploy(MythicRecipeStation.SMITHING, nk);
// Remember it
if (nk.getValue() != null) { customRecipes.put(nk.getValue(), blueprint); } else { booklessRecipes.add(blueprint); }
}
/**
* Parses a shapeless or shaped workbench crafting recipe and registers it.
*
* @param type The item type
* @param id The item ID
* @param list The list of items (3 lines or 3 ingredients, separated
* by spaces)
* @param shapeless If the recipe is shapeless or not
* @param recipeID Every item can have multiple recipe, there's one number
* per recipe to differenciate them
*/
public void registerRecipe(@NotNull Type type, @NotNull String id, @NotNull List<String> list, boolean shapeless, @NotNull String recipeID) throws IllegalArgumentException {
/*
* The output of the recipe will be the MMOItem of this Type and ID which
* is guaranteed to be loaded.
*
* The input is defined in the list in the following formats:
*
* SHAPELESS:
* + A list of 9 entries, which can be in any order
* + Each entry is one item, may be vanilla, MMOItem, or UIFilter.
*
* SHAPED
* + A list of 3 entries, which are in order.
* + Each entry is 3 items, separated by spaces, except if UIFilters are used,
* which can cause more than 3 items to be apparent.
* * Logic to parse UIFilters is included.
* + They indicate the rows of the crafting table.
*/
MythicRecipeBlueprint blueprint;
if (shapeless) {
// Generate with no shape
blueprint = CustomRecipe.generateShapeless(type, id, list, getRecipeKey(type, id, "shapeless", recipeID).getKey());
} else {
// Generate shaped
blueprint = CustomRecipe.generateShaped(type, id, list, getRecipeKey(type, id, "shaped", recipeID).getKey());
}
// Enable it
Ref<NamespacedKey> nk = new Ref<>();
blueprint.deploy(MythicRecipeStation.WORKBENCH, nk);
// Remember it
if (nk.getValue() != null) { customRecipes.put(nk.getValue(), blueprint); } else { booklessRecipes.add(blueprint);
if (book) { MMOItems.print(null, "Cannot register custom {2} recipe for $e{0} {1}$b into crafting book", "Custom Crafting", type.getId(), id, (shapeless ? "shapeless" : "shaped"));} }
/*
CustomRecipe recipe = new CustomRecipe(type, id, list, shapeless);
if (amounts)
registerRecipeAsCustom(recipe);
else
registerRecipeAsBukkit(recipe, number);
*/
} }
public void registerRecipeAsCustom(CustomRecipe recipe) { public void registerRecipeAsCustom(CustomRecipe recipe) {
@ -266,7 +215,8 @@ public class RecipeManager implements Reloadable {
} }
public HashMap<NamespacedKey, MythicRecipeBlueprint> getCustomRecipes() { return customRecipes; } public HashMap<NamespacedKey, MythicRecipeBlueprint> getCustomRecipes() { return customRecipes; }
ArrayList<NamespacedKey> generatedNKs = null; @Nullable
ArrayList<NamespacedKey> generatedNKs;
public ArrayList<NamespacedKey> getNamespacedKeys() { public ArrayList<NamespacedKey> getNamespacedKeys() {
if (generatedNKs != null) { return generatedNKs; } if (generatedNKs != null) { return generatedNKs; }
@ -305,6 +255,7 @@ public class RecipeManager implements Reloadable {
// Clear loaded recipes // Clear loaded recipes
loadedLegacyRecipes.clear(); loadedLegacyRecipes.clear();
blacklistedFromAutomaticDiscovery.clear();
// Disable and forget all blueprints // Disable and forget all blueprints
for (NamespacedKey b : customRecipes.keySet()) { for (NamespacedKey b : customRecipes.keySet()) {
@ -330,11 +281,21 @@ public class RecipeManager implements Reloadable {
public void refreshRecipeBook(Player player) { public void refreshRecipeBook(Player player) {
/*
* todo For some reason, we have to refresh the book every time
* the player joins the server or something; the thing is
* that recipes that are hidden from the book are lost when
* doing this (if they had unlocked them somehow).
* -
* Kind of need to somehow remember what recipes have been
* unlocked by who so that they don't get lost...
*/
// Book disabled? // Book disabled?
if (!book) { if (!book) {
// Hide all recipes // Hide all recipes
for (NamespacedKey key : player.getDiscoveredRecipes()) { if (key.getNamespace().equals("mmoitems")) { player.undiscoverRecipe(key); } } for (NamespacedKey key : player.getDiscoveredRecipes()) { if ("mmoitems".equals(key.getNamespace())) { player.undiscoverRecipe(key); } }
// Done woah // Done woah
return; return;
@ -345,11 +306,19 @@ public class RecipeManager implements Reloadable {
// Undiscovers the recipes apparently // Undiscovers the recipes apparently
for (NamespacedKey key : player.getDiscoveredRecipes()) { for (NamespacedKey key : player.getDiscoveredRecipes()) {
if (key.getNamespace().equals("mmoitems") && !getNamespacedKeys().contains(key)) { player.undiscoverRecipe(key); } } if ("mmoitems".equals(key.getNamespace()) && !getNamespacedKeys().contains(key)) { player.undiscoverRecipe(key); } }
// And discovers them again, sweet! // And discovers them again
for (NamespacedKey recipe : getNamespacedKeys()) { for (NamespacedKey recipe : getNamespacedKeys()) {
if (recipe == null) { continue; } if (recipe == null) { continue; }
// Not blacklisted right
boolean blacklisted = false;
for (NamespacedKey black : blacklistedFromAutomaticDiscovery) {
if (recipe.equals(black)) { blacklisted = true; break; } }
if (blacklisted) { continue; }
try { if (!player.hasDiscoveredRecipe(recipe)) { player.discoverRecipe(recipe); } } try { if (!player.hasDiscoveredRecipe(recipe)) { player.discoverRecipe(recipe); } }
catch (Throwable e) { MMOItems.print(null, "Could not register crafting book recipe for $r{0}$b:$f {1}", "MMOItems Custom Crafting", recipe.getKey(), e.getMessage()); } catch (Throwable e) { MMOItems.print(null, "Could not register crafting book recipe for $r{0}$b:$f {1}", "MMOItems Custom Crafting", recipe.getKey(), e.getMessage()); }
} }
@ -361,33 +330,66 @@ public class RecipeManager implements Reloadable {
// Discovers all recipes // Discovers all recipes
for (NamespacedKey recipe : getNamespacedKeys()) { for (NamespacedKey recipe : getNamespacedKeys()) {
if (recipe == null) { continue; } if (recipe == null) { continue; }
// Not blacklisted aight
boolean blacklisted = false;
for (NamespacedKey black : blacklistedFromAutomaticDiscovery) {
if (recipe.equals(black)) { blacklisted = true; break; } }
if (blacklisted) { continue; }
try { player.discoverRecipe(recipe); } try { player.discoverRecipe(recipe); }
catch (Throwable e) { MMOItems.print(null, "Could not register crafting book recipe for $r{0}$b:$f {1}", "MMOItems Custom Crafting", recipe.getKey(), e.getMessage()); } catch (Throwable e) { MMOItems.print(null, "Could not register crafting book recipe for $r{0}$b:$f {1}", "MMOItems Custom Crafting", recipe.getKey(), e.getMessage()); }
} }
} }
public WorkbenchIngredient getWorkbenchIngredient(String input) { @NotNull public static WorkbenchIngredient getWorkbenchIngredient(@NotNull String input) throws IllegalArgumentException {
String[] split = input.split(":");
int amount = split.length > 1 ? Integer.parseInt(split[1]) : 1;
if (split[0].contains(".")) { // Read it this other way ~
String[] split1 = split[0].split("\\."); ProvidedUIFilter poof = ProvidedUIFilter.getFromString(RecipeMakerGUI.poofFromLegacy(input), null);
Type type = MMOItems.plugin.getTypes().getOrThrow(split1[0].toUpperCase().replace("-", "_").replace(" ", "_"));
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplateOrThrow(type, // Air is AIR
split1[1].toUpperCase().replace("-", "_").replace(" ", "_")); if (poof == null) { return new AirIngredient(); }
return new MMOItemIngredient(type, template.getId(), amount);
// With class, obviously - no need for prefix tho
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
// Valid right
if (!poof.isValid(ffp)) {
// Snooze that
//noinspection ConstantConditions
throw new IllegalArgumentException(SilentNumbers.collapseList(SilentNumbers.transcribeList(ffp.getFeedbackOf(FriendlyFeedbackCategory.ERROR), s -> ((FriendlyFeedbackMessage) s).forConsole(FFPMMOItems.get())), ". "));
} }
if (split[0].equalsIgnoreCase("air")) // Get amount
return new AirIngredient(); int amount = poof.getAmount(0);
return new VanillaIngredient(Material.valueOf(split[0].toUpperCase().replace("-", "_").replace(" ", "_")), amount); // MMOItem?
if (poof.getParent() instanceof MMOItemUIFilter) {
// Get those
Type miType = MMOItems.plugin.getTypes().getOrThrow(poof.getArgument());
// Find template
MMOItemTemplate mmo = MMOItems.plugin.getTemplates().getTemplateOrThrow(miType, poof.getData());
// Treat is as MMOItem :pogyoo:
return new MMOItemIngredient(miType, mmo.getId(), amount);
// Must be vanilla
} else if (poof.getParent() instanceof VanillaUIFilter) {
return new VanillaIngredient(Material.valueOf(poof.getArgument().toUpperCase().replace("-", "_").replace(" ", "_")), amount);
}
throw new IllegalArgumentException("Unsupported ingredient, you may only specify vanilla or mmoitems.");
} }
/** /**
* Easier control of furnace, smoker, campfire and blast recipes so there is * Easier control of furnace, smoker, campfire and blast recipes so there is
* no need to have four time the same method to register this type of recipe * no need to have four time the same method to register this type of recipe
* *
* @author cympe * @author cympe
*/ */
public enum BurningRecipeType { public enum BurningRecipeType {
@ -412,37 +414,5 @@ public class RecipeManager implements Reloadable {
} }
@FunctionalInterface @FunctionalInterface
public interface RecipeProvider { public interface RecipeProvider { CookingRecipe<?> provide(NamespacedKey key, ItemStack result, RecipeChoice source, float experience, int cookTime);}
CookingRecipe<?> provide(NamespacedKey key, ItemStack result, RecipeChoice source, float experience, int cookTime);
}
/**
* Used to handle furnace/smoker/campfire/furnace extra crafting recipe
* parameters
*
* @author ASangarin
*/
public class BurningRecipeInformation {
private final WorkbenchIngredient choice;
private final float exp;
private final int burnTime;
public BurningRecipeInformation(ConfigurationSection config) {
choice = getWorkbenchIngredient(config.getString("item"));
exp = (float) config.getDouble("exp", 0.35);
burnTime = config.getInt("time", 200);
}
public int getBurnTime() {
return burnTime;
}
public WorkbenchIngredient getChoice() {
return choice;
}
public float getExp() {
return exp;
}
}
} }

View File

@ -1,16 +1,22 @@
package net.Indyuce.mmoitems.manager; package net.Indyuce.mmoitems.manager;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.ItemTier; import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
public class TierManager implements Reloadable{ public class TierManager implements Reloadable{
private final Map<String, ItemTier> tiers = new HashMap<>(); private final Map<String, ItemTier> tiers = new HashMap<>();
@ -22,42 +28,105 @@ public class TierManager implements Reloadable{
public void reload() { public void reload() {
tiers.clear(); tiers.clear();
// For logging
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "Tiers");
ConfigFile config = new ConfigFile("item-tiers"); ConfigFile config = new ConfigFile("item-tiers");
for (String key : config.getConfig().getKeys(false)) for (String tierName : config.getConfig().getKeys(false)) {
// Get section (Using RecipeMakerGUI for @NotNull attribute)
ConfigurationSection tierSection = RecipeMakerGUI.getSection(config.getConfig(), tierName);
// Attempt to register
try { try {
register(new ItemTier(config.getConfig().getConfigurationSection(key))); register(new ItemTier(tierSection));
// Any errors?
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not load item tier '" + key + "': " + exception.getMessage());
// Log error
ffp.log(FriendlyFeedbackCategory.ERROR, "Cannot register tier '$u{0}$b';$f {1}", tierName, exception.getMessage());
} }
}
// Log relevant messages
ffp.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffp.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
} }
public void register(ItemTier tier) { /**
tiers.put(tier.getId(), tier); * Set a tier live to be used everywhere in the plugin.
} *
* @param tier Tier to register
*/
public void register(@NotNull ItemTier tier) { tiers.put(tier.getId(), tier); }
public boolean has(String id) { /**
* @param id Tier name
*
* @return If a tier of this name is loaded
*/
public boolean has(@Nullable String id) {
/*
* No null tiers, but for flexibility of use, just
* make it @Nullable and make it return false always.
*/
if (id == null) { return false; }
// Is the tier loaded?
return tiers.containsKey(id); return tiers.containsKey(id);
} }
public ItemTier getOrThrow(String id) { /**
Validate.isTrue(tiers.containsKey(id), "Could not find tier with ID '" + id + "'"); *
* @param id Tier name, technically Nullable but this guarantees
* that an IllegalArgumentException will be thrown.
*
* @throws IllegalArgumentException When there is no tier of such name loaded.
*
* @return Tier of this name.
*/
@NotNull public ItemTier getOrThrow(@Nullable String id) throws IllegalArgumentException {
// Well, is it loaded?
Validate.isTrue(tiers.containsKey(id), FriendlyFeedbackProvider.quickForConsole(FFPMMOItems.get(), "Could not find tier with ID '$r{0}$b'", id));
// Well its guaranteed to not be null.
return tiers.get(id); return tiers.get(id);
} }
public ItemTier get(String id) { /**
* Will get the tier of this name, if there is one
*
* @param id Name of the tier.
*/
@Nullable public ItemTier get(@Nullable String id) {
if (id == null) { return null; }
return tiers.get(id); return tiers.get(id);
} }
public Collection<ItemTier> getAll() { /**
* @return An iterable of all the tiers loaded
*/
@NotNull public Collection<ItemTier> getAll() {
return tiers.values(); return tiers.values();
} }
public ItemTier findTier(MMOItem item) { /**
* @param item Item you seek the tier of
*
* @return The tier of this item, it it has any
*/
@Nullable public ItemTier findTier(@NotNull MMOItem item) {
try { try {
// Has that data?
return item.hasData(ItemStats.TIER) ? get(item.getData(ItemStats.TIER).toString()) : null; return item.hasData(ItemStats.TIER) ? get(item.getData(ItemStats.TIER).toString()) : null;
} catch (IllegalArgumentException exception) {
return null; // Pretty sure there is no way for this exception to be thrown tho
} } catch (IllegalArgumentException exception) { return null; }
} }
} }

View File

@ -80,7 +80,7 @@ public class TypeManager implements Reloadable {
return map.get(id); return map.get(id);
} }
public Type getOrThrow(String id) { @NotNull public Type getOrThrow(@Nullable String id) {
Validate.isTrue(map.containsKey(id), "Could not find item type with ID '" + id + "'"); Validate.isTrue(map.containsKey(id), "Could not find item type with ID '" + id + "'");
return map.get(id); return map.get(id);
} }

View File

@ -4,19 +4,25 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import io.lumine.mythic.lib.api.crafting.uimanager.ProvidedUIFilter;
import io.lumine.mythic.lib.api.crafting.uimanager.UIFilterManager;
import io.lumine.mythic.lib.api.item.ItemTag; import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.util.ui.QuickNumberRange;
import io.lumine.mythic.lib.api.util.ui.SilentNumbers;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeBrowserGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.recipes.RecipeMakerGUI;
import net.Indyuce.mmoitems.gui.edition.recipe.interpreters.RMG_RecipeInterpreter;
import net.Indyuce.mmoitems.gui.edition.recipe.rba.RecipeButtonAction;
import net.Indyuce.mmoitems.stat.data.StringData; import net.Indyuce.mmoitems.stat.data.StringData;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder; import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem;
import net.Indyuce.mmoitems.gui.edition.EditionInventory; import net.Indyuce.mmoitems.gui.edition.EditionInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeListEdition;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData; import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
import net.Indyuce.mmoitems.stat.data.type.StatData; import net.Indyuce.mmoitems.stat.data.type.StatData;
import net.Indyuce.mmoitems.stat.type.ItemStat; import net.Indyuce.mmoitems.stat.type.ItemStat;
@ -34,7 +40,7 @@ public class Crafting extends ItemStat {
@Override @Override
public void whenClicked(@NotNull EditionInventory inv, @NotNull InventoryClickEvent event) { public void whenClicked(@NotNull EditionInventory inv, @NotNull InventoryClickEvent event) {
if (event.getAction() == InventoryAction.PICKUP_ALL) if (event.getAction() == InventoryAction.PICKUP_ALL)
new RecipeListEdition(inv.getPlayer(), inv.getEdited()).open(inv.getPage()); new RecipeBrowserGUI(inv.getPlayer(), inv.getEdited()).open(inv.getPage());
else if (event.getAction() == InventoryAction.PICKUP_HALF && inv.getEditedSection().contains("crafting")) { else if (event.getAction() == InventoryAction.PICKUP_HALF && inv.getEditedSection().contains("crafting")) {
inv.getEditedSection().set("crafting", null); inv.getEditedSection().set("crafting", null);
@ -63,90 +69,164 @@ public class Crafting extends ItemStat {
@Override @Override
public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) { public void whenInput(@NotNull EditionInventory inv, @NotNull String message, Object... info) {
String type = (String) info[0];
switch (type) {
/* /*
* Handles shaped and shapeless crafting recipes * #1 Type - Is it input, output, or a button being pressed?
*/ */
case "recipe": int type = (int) info[0];
int slot = (int) info[2];
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(message), "Invalid ingredient");
/* switch (type) {
* Handles shaped crafting recipes case RecipeMakerGUI.INPUT:
*/ case RecipeMakerGUI.OUTPUT:
if ((info[1]).equals("shaped")) {
List<String> newList = inv.getEditedSection().getStringList("crafting.shaped.1");
String[] newArray = newList.get(slot / 3).split(" ");
newArray[slot % 3] = message;
newList.set(slot / 3, (newArray[0] + " " + newArray[1] + " " + newArray[2]));
for (String s : newList) { //region Transcribe from old format to new
if (s.equals("AIR AIR AIR")) int spc = message.indexOf(' ');
continue; QuickNumberRange qnr = null;
if (spc > 0) {
inv.getEditedSection().set("crafting.shaped.1", newList); // Any space? attempt to parse that as a number
inv.registerTemplateEdition(); String qnrp = message.substring(spc + 1);
break;
// Is it just a number 'X' ?
if (SilentNumbers.DoubleTryParse(qnrp)) {
/*
* In technical QNR jargon, X means "requires exactly this",
* however, many times when crafting, specifying X means that
* crafting it once requires that many ingredients.
*
* Translating the crafting intention into QNR outputs X..
*
* If anyone truly means that the recipe can only be crafted
* having X in the same slot of the crafting table, they will
* have to write X..X
*/
qnrp += "..";
}
// Parse QNR
qnr = QuickNumberRange.getFromString(qnrp);
} }
/* /*
* Handles shapeless crafting recipes * Changes easy MMOItems input into MythicLib NBT Filter.
*/ */
} else { if (spc <= 0 || qnr != null) {
List<String> newList = inv.getEditedSection().getStringList("crafting.shapeless.1");
newList.set(slot, message);
for (String s : newList) { // No amount specified=
if (s.equals("AIR")) if (qnr == null) {
continue;
inv.getEditedSection().set("crafting.shapeless.1", newList); // Default is one and onward, 1..
inv.registerTemplateEdition(); qnr = new QuickNumberRange(1D, null);
break;
// Amount was specified
} else {
// Crop from message
message = message.substring(0, spc);
}
// MMOItem?
if (message.contains(".")) {
// Split
String[] midSplit = message.split("\\.");
// MMOItem UIFilter
message = "m " + midSplit[0] + " " + midSplit[1] + " " + qnr;
// Vanilla material
} else {
// Vanilla UIFilter
message = "v " + message + " - " + qnr;
}
} }
} //endregion
break; /*
* #2 Recipe Interpreter - Correctly edits the configuration section in the files,
* depending on how the recipe is supposed to be saved.
*/
RMG_RecipeInterpreter interpreter = (RMG_RecipeInterpreter) info[1];
/*
* #3 Slot - Which slot was pressed?
*/
int slot = (int) info[2];
// Attempt to get
ProvidedUIFilter read = UIFilterManager.getUIFilter(message, inv.getFFP());
// Null? Cancel
if (read == null) { throw new IllegalArgumentException(""); }
if (!read.isValid(inv.getFFP())) { throw new IllegalArgumentException(""); }
// Find section
ConfigurationSection section = RecipeMakerGUI.getSection(inv.getEditedSection(), "crafting");
section = RecipeMakerGUI.getSection(section, ((RecipeMakerGUI) inv).getRecipeRegistry().getRecipeConfigPath());
section = RecipeMakerGUI.getSection(section, ((RecipeMakerGUI) inv).getRecipeName());
// Redirect
if (type == RecipeMakerGUI.INPUT) {
interpreter.editInput(section, read, slot);
// It must be output
} else {
interpreter.editOutput(section, read, slot); }
// Save changes
inv.registerTemplateEdition();
break;
case RecipeMakerGUI.PRIMARY:
case RecipeMakerGUI.SECONDARY:
/*
* No Button Action? That's the end, and is not necessarily
* an error (the button might have done what it had to do
* already when pressed, if it needed no user input).
*/
if (info.length < 2) { return; }
if (!(info[1] instanceof RecipeButtonAction)) { return; }
// Delegate
if (type == RecipeMakerGUI.PRIMARY) {
((RecipeButtonAction) info[1]).primaryProcessInput(message, info);
} else {
((RecipeButtonAction) info[1]).secondaryProcessInput(message, info); }
// Save changes
inv.registerTemplateEdition();
break;
default: inv.registerTemplateEdition(); break;
}
/* /*
* Handles burning recipes ie furnace, campfire, smoker and blast * Handles burning recipes ie furnace, campfire, smoker and blast
* furnace recipes * furnace recipes
*/ *
case "item": { case "item": {
String[] args = message.split(" "); String[] args = message.split(" ");
Validate.isTrue(args.length == 3, "Invalid format"); Validate.isTrue(args.length == 3, "Invalid format");
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[0]), "Invalid ingredient"); Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[0]), "Invalid ingredient");
int time = Integer.parseInt(args[1]); int time = Integer.parseInt(args[1]);
double exp = MMOUtils.parseDouble(args[2]); double exp = MMOUtils.parseDouble(args[2]);
inv.getEditedSection().set("crafting." + info[1] + ".1.item", args[0]); inv.getEditedSection().set("crafting." + info[1] + ".1.item", args[0]);
inv.getEditedSection().set("crafting." + info[1] + ".1.time", time); inv.getEditedSection().set("crafting." + info[1] + ".1.time", time);
inv.getEditedSection().set("crafting." + info[1] + ".1.experience", exp); inv.getEditedSection().set("crafting." + info[1] + ".1.experience", exp);
inv.registerTemplateEdition(); inv.registerTemplateEdition();
break; break;
} }
/** */
* Handles smithing recipes
*/
case "smithing": {
String[] args = message.split(" ");
Validate.isTrue(args.length == 2, "Invalid format");
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[0]), "Invalid first ingredient");
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[1]), "Invalid second ingredient");
inv.getEditedSection().set("crafting.smithing.1.input1", args[0]);
inv.getEditedSection().set("crafting.smithing.1.input2", args[1]);
inv.registerTemplateEdition();
break;
}
default:
throw new IllegalArgumentException("Recipe type not recognized");
}
} }
@Nullable
@Override @Override
public RandomStatData whenInitialized(Object object) { public RandomStatData whenInitialized(Object object) {
return null; return null;

View File

@ -20,6 +20,8 @@ import java.util.*;
* You could consider them a more advanced DisableStat, while DisableStat only * You could consider them a more advanced DisableStat, while DisableStat only
* allows to choose <b>true</b> or <b>false</b>, alternating when clicked, Choose * allows to choose <b>true</b> or <b>false</b>, alternating when clicked, Choose
* Stats cycle through a list instead. * Stats cycle through a list instead.
*
* @author Gunging
*/ */
public abstract class ChooseStat extends StringStat { public abstract class ChooseStat extends StringStat {