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 = 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_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
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.RPGHandler;
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.listener.*;
import net.Indyuce.mmoitems.manager.*;
@ -140,6 +141,7 @@ public class MMOItems extends LuminePlugin {
new SpigotPlugin(39267, this).checkForUpdate();
new MMOItemsMetrics();
RecipeBrowserGUI.registerNativeRecipes();
abilityManager.initialize();
configManager = new ConfigManager();

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api;
import io.lumine.mythic.lib.MythicLib;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.droptable.DropTable;
import net.Indyuce.mmoitems.api.player.PlayerData;
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.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class ItemTier {
private final String name, id;
@NotNull private final String name, id;
// unidentification
private final UnidentificationInfo unidentificationInfo;
// Unidentification
@NotNull private final UnidentificationInfo unidentificationInfo;
// deconstruction
private final DropTable deconstruct;
@Nullable private final DropTable deconstruct;
// item glow options
private final TierColor color;
private final boolean hint;
@Nullable private TierColor color = null;
private boolean hint = false;
// item generation
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;
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("-", "_");
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 {
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) {
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");
capacity = config.contains("generation.capacity") ? new NumericStatFormula(config.getConfigurationSection("generation.capacity")) : null;
}
public String getId() {
return id;
}
@NotNull public String getId() { return id; }
public String getName() {
return name;
}
@NotNull public String getName() { return name; }
public boolean hasDropTable() {
return deconstruct != null;
}
public boolean hasDropTable() { return deconstruct != null; }
public DropTable getDropTable() {
return deconstruct;
}
@Nullable public DropTable getDropTable() { return deconstruct; }
public boolean hasColor() {
return color != null;
}
public boolean hasColor() { return color != null; }
public TierColor getColor() {
return color;
}
@Nullable public TierColor getColor() { return color; }
public boolean isHintEnabled() {
return hint;
}
public boolean isHintEnabled() { return hint; }
/**
* @return The chance of the tier being chosen when generating a random item
*/
public double getGenerationChance() {
return chance;
}
public double getGenerationChance() { return chance; }
/**
* @return If the item tier has a modifier capacity ie if this tier let
* generated items have modifiers
*/
public boolean hasCapacity() {
return capacity != null;
}
public boolean hasCapacity() { return capacity != null; }
/**
* @return The formula for modifier capacity which can be then rolled to
* generate a random amount of modifier capacity when generating a
* random item
*/
public NumericStatFormula getModifierCapacity() {
return capacity;
}
@Nullable public NumericStatFormula getModifierCapacity() { return capacity; }
public UnidentificationInfo getUnidentificationInfo() {
return unidentificationInfo;
}
@NotNull public UnidentificationInfo getUnidentificationInfo() { return unidentificationInfo; }
/**
* @return Reads the deconstruction drop table. This may return a list
* containing multiple items and they should all be added to the
* 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 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 {
private final String name, prefix;
@NotNull private final String unidentificationName, prefix;
private final int range;
public UnidentificationInfo(ConfigurationSection config) {
this(color(config.getString("name")), color(config.getString("prefix")), config.getInt("range"));
public static final String UNIDENT_NAME = "Unidentified Item";
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) {
this.name = name;
public UnidentificationInfo(@NotNull String name, @NotNull String prefix, int range) {
unidentificationName = name;
this.prefix = prefix;
this.range = range;
}
public String getPrefix() {
return prefix;
}
@NotNull public String getPrefix() { return prefix; }
public String getDisplayName() {
return name;
@NotNull public String getDisplayName() {
return unidentificationName;
}
public int[] calculateRange(int level) {
int min = (int) Math.max(1, (level - (double) range * RANDOM.nextDouble()));
return new int[] { min, min + range };
}
}
private String color(String str) {
private String color(@Nullable String 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.Nullable;
import java.util.ArrayList;
public class ReforgeOptions {
public static boolean dropRestoredGems;
@ -28,6 +30,42 @@ public class ReforgeOptions {
public void setKeepCase(@NotNull String kc) { keepCase = kc; }
@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) {
this.keepName = config.getBoolean("display-name");
this.keepLore = config.getBoolean("lore");

View File

@ -1,25 +1,27 @@
package net.Indyuce.mmoitems.api.crafting.recipe;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicBlueprintInventory;
import io.lumine.mythic.lib.api.crafting.ingredients.MythicRecipeInventory;
import io.lumine.mythic.lib.MythicLib;
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.MythicRecipeOutput;
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.item.NBTItem;
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 net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.interaction.GemStone;
import net.Indyuce.mmoitems.api.item.mmoitem.LiveMMOItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
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.GemSocketsData;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
@ -47,12 +49,165 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
* @param enchantmentTreatment Should enchantments be destroyed?
* @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.dropGemstones = dropGemstones;
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.
@ -169,9 +324,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
Ref<ArrayList<ItemStack>> droppedGemstones = new Ref<>();
MMOItem display = fromCombinationWith(itemMMO, ingotMMO, player, droppedGemstones);
// Result
MythicRecipeInventory result = otherInventories.getResultInventory().clone();
result.setItemAt(map.getResultWidth(map.getResultInventoryStart()), map.getResultHeight(map.getResultInventoryStart()), display.newBuilder().build());
//RDR// MythicCraftingManager.log("\u00a78RDR \u00a748\u00a77 Custom Smithing Recipe Result\u00a7e" + times + "\u00a77 times\u00a78 ~\u00a71 " + eventTrigger.getAction().toString());
/*
* 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)) {
// 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:
*
@ -202,7 +359,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Apply the result
//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); }
//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
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 {
/*
@ -259,7 +465,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
//RDR//MythicCraftingManager.log("\u00a78RDR \u00a747\u00a77 Reading/Generating Result");
// Build the result
ArrayList<ItemStack> outputItems = MRORecipe.toItemsList(result);
HashMap<Integer, ItemStack> modifiedInventory = null;
Inventory inven = player.getInventory();
int trueTimes = 0;
@ -268,10 +473,41 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
for (int t = 1; t <= times; 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); }
/*
* 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
HashMap<Integer, ItemStack> localIterationResult = MRORecipe.distributeInInventory(inven, outputItems, modifiedInventory);
HashMap<Integer, ItemStack> localIterationResult = MRORecipe.distributeInInventory(inven, localOutput, modifiedInventory);
// Failed? Break
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");
break;
// Prepare for next iteration
// Prepare for next iteration
} else {
// Store changes
@ -302,6 +538,9 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Set
inven.setItem(s, putt); }
// Consume ingredients
consumeIngredients(otherInventories, cache, eventTrigger.getInventory(), map, times);
}
// Drop?
@ -316,9 +555,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// Drop to the world
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);
// None?
if (res.getType().equals(GemStone.ResultType.SUCCESS) && (res.getResultAsMMOItem() != null)) {
if (res.getType() == GemStone.ResultType.SUCCESS && (res.getResultAsMMOItem() != null)) {
// Success that's nice
gen = res.getResultAsMMOItem();
@ -383,7 +619,7 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
Ref.setValue(rem, remainingStones);
// Enchantments?
if (!getEnchantmentTreatment().equals(SmithingCombinationType.NONE)) {
if (getEnchantmentTreatment() != SmithingCombinationType.NONE) {
// Get enchantment data
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
if (gen.hasUpgradeTemplate() && !(getUpgradeTreatment().equals(SmithingCombinationType.NONE))) {
if (gen.hasUpgradeTemplate() && getUpgradeTreatment() != SmithingCombinationType.NONE) {
// All right get the levels of them both
int itemLevel = 0; if (item != null) { itemLevel = item.getUpgradeLevel(); }
@ -428,5 +664,6 @@ public class CustomSmithingRecipe extends MythicRecipeOutput {
// That's it
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.DropItem;
import net.Indyuce.mmoitems.api.droptable.item.MMOItemDropItem;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
@ -21,18 +22,22 @@ public class DropTable {
private final Map<String, Subtable> subtables = new HashMap<>();
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))
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++)
subtablesList.add(key);
// Include parsed subtable
subtables.put(key, new Subtable(config.getConfigurationSection(key)));
// Ew
} 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");

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api.edition;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.edition.input.AnvilGUI;
import net.Indyuce.mmoitems.api.edition.input.ChatEdition;
@ -79,11 +80,27 @@ public class StatEdition implements Edition {
}
try {
// Perform WhenInput Operation
stat.whenInput(inv, input, info);
// Success
return true;
} 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;
}
}

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.util.DynamicLore;
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.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.EnchantListData;
@ -150,7 +151,7 @@ public class ItemStackBuilder {
builtMMOItem.setData(stat, s.recalculate(l));
// 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");
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.MMOItems;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.UpgradeTemplate;
@ -208,6 +209,12 @@ public class MMOItem implements ItemReference {
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.
* <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.version.VersionMaterial;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.manager.RecipeManager;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public enum CraftingType {
SHAPED(21, "The C. Table Recipe (Shaped) for this item", VersionMaterial.CRAFTING_TABLE),
SHAPELESS(22, "The C. Table Recipe (Shapeless) for this item", VersionMaterial.CRAFTING_TABLE),
FURNACE(23, "The Furnace Recipe for this item", Material.FURNACE),
BLAST(29, "The Blast Furnace Recipe for this item", VersionMaterial.BLAST_FURNACE, 1, 14),
SMOKER(30, "The Smoker Recipe for this item", VersionMaterial.SMOKER, 1, 14),
CAMPFIRE(32, "The Campfire Recipe for this item", VersionMaterial.CAMPFIRE, 1, 14),
SMITHING(33, "The Smithing Recipe for this item", VersionMaterial.SMITHING_TABLE, 1, 15);
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, null),
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, RecipeManager.BurningRecipeType.BLAST, 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, RecipeManager.BurningRecipeType.CAMPFIRE, 1, 14),
SMITHING(33, "The Smithing Recipe for this item", VersionMaterial.SMITHING_TABLE, null, 1, 15);
private final int slot;
private final String lore;
private final Material material;
private final int[] mustBeHigher;
private final RecipeManager.BurningRecipeType burning;
private CraftingType(int slot, String lore, VersionMaterial material, int... mustBeHigher) {
this(slot, lore, material.toMaterial(), mustBeHigher);
private CraftingType(int slot, String lore, VersionMaterial material, @Nullable RecipeManager.BurningRecipeType burn, int... 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.lore = lore;
this.material = material;
this.mustBeHigher = mustBeHigher;
this.burning = burn;
}
public ItemStack getItem() {
@ -46,6 +50,7 @@ public enum CraftingType {
public String getLore() {
return lore;
}
public RecipeManager.BurningRecipeType getBurningType() { return burning; }
public boolean shouldAdd() {
return mustBeHigher.length == 0 || MythicLib.plugin.getVersion().isStrictlyHigher(mustBeHigher);

View File

@ -182,321 +182,4 @@ public class CustomRecipe implements Comparable<CustomRecipe> {
}
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")
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)
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; }
@ -419,6 +422,8 @@ public class MMOItemReforger {
*/
int regenerate(@Nullable RPGPlayer player, @NotNull MMOItemTemplate template) {
if (mmoItem == null) { loadLiveMMOItem(); }
int determinedItemLevel;
if (player == null) {
@ -497,6 +502,9 @@ public class MMOItemReforger {
@SuppressWarnings("ConstantConditions")
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)
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; }

View File

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

View File

@ -1,11 +1,13 @@
package net.Indyuce.mmoitems.gui.edition;
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.api.ConfigFile;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.template.TemplateModifier;
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.stat.data.random.RandomStatData;
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.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@ -46,11 +49,16 @@ public abstract class EditionInventory extends PluginInventory {
private TemplateModifier editedModifier;
private ItemStack cachedItem;
private int previousPage;
int previousPage;
public EditionInventory(Player player, MMOItemTemplate template) {
public EditionInventory(@NotNull Player player, @NotNull MMOItemTemplate template) {
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.configFile = template.getType().getConfigFile();
player.getOpenInventory();
@ -158,4 +166,7 @@ public abstract class EditionInventory extends PluginInventory {
public int getPreviousPage() {
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.edition.EditionInventory;
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.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.entity.Player;
import org.bukkit.event.EventHandler;
@ -61,12 +64,18 @@ public class GuiListener implements Listener {
MMOItemTemplate template = ((EditionInventory) inventory).getEdited();
if (item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + AltChar.rightArrow + " Back")) {
if (inventory instanceof ItemEdition)
new ItemBrowser(player, template.getType()).open();
else if (inventory instanceof RecipeEdition)
new RecipeListEdition(player, template).open(((EditionInventory) inventory).getPreviousPage());
else
new ItemEdition(player, template).onPage(((EditionInventory) inventory).getPreviousPage()).open();
// Open the Item Browser yes
if (inventory instanceof ItemEdition) { new ItemBrowser(player, template.getType()).open(); }
// Open the RECIPE TYPE BROWSER stat thing
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);
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 {
defaultItemCapacity = new NumericStatFormula(MMOItems.plugin.getConfig().getConfigurationSection("default-item-capacity"));
} catch (IllegalArgumentException exception) {

View File

@ -4,13 +4,19 @@ import java.util.*;
import java.util.stream.Collectors;
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.ui.FriendlyFeedbackCategory;
import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackMessage;
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 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.Keyed;
import org.bukkit.Material;
@ -27,7 +33,6 @@ import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.RecipeChoice;
import org.bukkit.inventory.SmokingRecipe;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
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.VanillaIngredient;
import net.Indyuce.mmoitems.api.recipe.workbench.ingredients.WorkbenchIngredient;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import io.lumine.mythic.lib.MythicLib;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -67,6 +71,7 @@ public class RecipeManager implements Reloadable {
*/
final HashMap<NamespacedKey, MythicRecipeBlueprint> customRecipes = new HashMap<>();
final ArrayList<MythicRecipeBlueprint> booklessRecipes = new ArrayList<>();
@NotNull ArrayList<NamespacedKey> blacklistedFromAutomaticDiscovery = new ArrayList<>();
private boolean book, amounts;
@ -101,50 +106,72 @@ public class RecipeManager implements Reloadable {
// Does it have a crafting recipe?
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
if (section.contains("shaped"))
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));
// Is it in-yo?
if (section.contains(recipeType)) {
// Uh heck
} catch (IllegalArgumentException exception) {
// Get Registry
RecipeRegistry rr = RecipeBrowserGUI.getRegisteredRecipe(recipeType);
// Add message
ffp.log(FriendlyFeedbackCategory.ERROR, "Could not load recipe of $f{0} {1}$b: {2}",
type.getId(), template.getId(), exception.getMessage());
// Get recipe type section
ConfigurationSection typeSection = RecipeMakerGUI.getSection(section, recipeType);
// 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
ffp.sendAllTo(MMOItems.getConsole());
// Log relevant messages
ffp.sendTo(FriendlyFeedbackCategory.ERROR, MMOItems.getConsole());
ffp.sendTo(FriendlyFeedbackCategory.FAILURE, MMOItems.getConsole());
// Sort recipes
sortRecipes();
@ -153,97 +180,19 @@ public class RecipeManager implements Reloadable {
Bukkit.getScheduler().runTask(MMOItems.plugin, () -> getLoadedLegacyRecipes().forEach(Bukkit::addRecipe));
}
public void registerBurningRecipe(BurningRecipeType recipeType, Type type, String id, BurningRecipeInformation info, String recipeId) {
NamespacedKey key = getRecipeKey(type, id, recipeType.getPath(), recipeId);
MMOItem mmo = MMOItems.plugin.getMMOItem(type, id);
final int amount = mmo.hasData(ItemStats.CRAFT_AMOUNT) ? (int) ((DoubleData) mmo.getData(ItemStats.CRAFT_AMOUNT)).getValue() : 1;
public void registerBurningRecipe(@NotNull BurningRecipeType recipeType, @NotNull MMOItem mmo, @NotNull BurningRecipeInformation info, int amount, @NotNull NamespacedKey key, boolean hidden) {
// Build its item stacc
ItemStack stack = mmo.newBuilder().build();
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());
// Register that recipe lets goo
loadedLegacyRecipes.add(recipe);
}
public void registerSmithingRecipe(@NotNull Type type, @NotNull String id, @NotNull ConfigurationSection section, @NotNull String number) throws IllegalArgumentException {
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);
*/
if (hidden) { blacklistedFromAutomaticDiscovery.add(key); }
}
public void registerRecipeAsCustom(CustomRecipe recipe) {
@ -266,7 +215,8 @@ public class RecipeManager implements Reloadable {
}
public HashMap<NamespacedKey, MythicRecipeBlueprint> getCustomRecipes() { return customRecipes; }
ArrayList<NamespacedKey> generatedNKs = null;
@Nullable
ArrayList<NamespacedKey> generatedNKs;
public ArrayList<NamespacedKey> getNamespacedKeys() {
if (generatedNKs != null) { return generatedNKs; }
@ -305,6 +255,7 @@ public class RecipeManager implements Reloadable {
// Clear loaded recipes
loadedLegacyRecipes.clear();
blacklistedFromAutomaticDiscovery.clear();
// Disable and forget all blueprints
for (NamespacedKey b : customRecipes.keySet()) {
@ -330,11 +281,21 @@ public class RecipeManager implements Reloadable {
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?
if (!book) {
// 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
return;
@ -345,11 +306,19 @@ public class RecipeManager implements Reloadable {
// Undiscovers the recipes apparently
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()) {
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); } }
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
for (NamespacedKey recipe : getNamespacedKeys()) {
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); }
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) {
String[] split = input.split(":");
int amount = split.length > 1 ? Integer.parseInt(split[1]) : 1;
@NotNull public static WorkbenchIngredient getWorkbenchIngredient(@NotNull String input) throws IllegalArgumentException {
if (split[0].contains(".")) {
String[] split1 = split[0].split("\\.");
Type type = MMOItems.plugin.getTypes().getOrThrow(split1[0].toUpperCase().replace("-", "_").replace(" ", "_"));
MMOItemTemplate template = MMOItems.plugin.getTemplates().getTemplateOrThrow(type,
split1[1].toUpperCase().replace("-", "_").replace(" ", "_"));
return new MMOItemIngredient(type, template.getId(), amount);
// Read it this other way ~
ProvidedUIFilter poof = ProvidedUIFilter.getFromString(RecipeMakerGUI.poofFromLegacy(input), null);
// Air is AIR
if (poof == null) { return new AirIngredient(); }
// 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"))
return new AirIngredient();
// Get amount
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
* no need to have four time the same method to register this type of recipe
*
*
* @author cympe
*/
public enum BurningRecipeType {
@ -412,37 +414,5 @@ public class RecipeManager implements Reloadable {
}
@FunctionalInterface
public interface RecipeProvider {
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;
}
}
public interface RecipeProvider { CookingRecipe<?> provide(NamespacedKey key, ItemStack result, RecipeChoice source, float experience, int cookTime);}
}

View File

@ -1,16 +1,22 @@
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.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.ItemTier;
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.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
public class TierManager implements Reloadable{
private final Map<String, ItemTier> tiers = new HashMap<>();
@ -22,42 +28,105 @@ public class TierManager implements Reloadable{
public void reload() {
tiers.clear();
// For logging
FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get());
ffp.activatePrefix(true, "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 {
register(new ItemTier(config.getConfig().getConfigurationSection(key)));
register(new ItemTier(tierSection));
// Any errors?
} 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);
}
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);
}
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);
}
public Collection<ItemTier> getAll() {
/**
* @return An iterable of all the tiers loaded
*/
@NotNull public Collection<ItemTier> getAll() {
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 {
// Has that data?
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);
}
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 + "'");
return map.get(id);
}

View File

@ -4,19 +4,25 @@ import java.util.ArrayList;
import java.util.List;
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.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 org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.MMOUtils;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem;
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.type.StatData;
import net.Indyuce.mmoitems.stat.type.ItemStat;
@ -34,7 +40,7 @@ public class Crafting extends ItemStat {
@Override
public void whenClicked(@NotNull EditionInventory inv, @NotNull InventoryClickEvent event) {
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")) {
inv.getEditedSection().set("crafting", null);
@ -63,90 +69,164 @@ public class Crafting extends ItemStat {
@Override
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 slot = (int) info[2];
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(message), "Invalid ingredient");
int type = (int) info[0];
/*
* Handles shaped crafting recipes
*/
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]));
switch (type) {
case RecipeMakerGUI.INPUT:
case RecipeMakerGUI.OUTPUT:
for (String s : newList) {
if (s.equals("AIR AIR AIR"))
continue;
//region Transcribe from old format to new
int spc = message.indexOf(' ');
QuickNumberRange qnr = null;
if (spc > 0) {
inv.getEditedSection().set("crafting.shaped.1", newList);
inv.registerTemplateEdition();
break;
// Any space? attempt to parse that as a number
String qnrp = message.substring(spc + 1);
// 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 {
List<String> newList = inv.getEditedSection().getStringList("crafting.shapeless.1");
newList.set(slot, message);
if (spc <= 0 || qnr != null) {
for (String s : newList) {
if (s.equals("AIR"))
continue;
inv.getEditedSection().set("crafting.shapeless.1", newList);
inv.registerTemplateEdition();
break;
// No amount specified=
if (qnr == null) {
// Default is one and onward, 1..
qnr = new QuickNumberRange(1D, null);
// 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
* furnace recipes
*/
case "item": {
String[] args = message.split(" ");
Validate.isTrue(args.length == 3, "Invalid format");
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[0]), "Invalid ingredient");
int time = Integer.parseInt(args[1]);
double exp = MMOUtils.parseDouble(args[2]);
*
case "item": {
String[] args = message.split(" ");
Validate.isTrue(args.length == 3, "Invalid format");
Validate.notNull(MMOItems.plugin.getRecipes().getWorkbenchIngredient(args[0]), "Invalid ingredient");
int time = Integer.parseInt(args[1]);
double exp = MMOUtils.parseDouble(args[2]);
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.experience", exp);
inv.registerTemplateEdition();
break;
}
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.experience", exp);
inv.registerTemplateEdition();
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
public RandomStatData whenInitialized(Object object) {
return null;

View File

@ -20,6 +20,8 @@ import java.util.*;
* 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
* Stats cycle through a list instead.
*
* @author Gunging
*/
public abstract class ChooseStat extends StringStat {