diff --git a/src/main/java/net/knarcraft/blacksmith/command/blacksmith/BlackSmithConfigCommand.java b/src/main/java/net/knarcraft/blacksmith/command/blacksmith/BlackSmithConfigCommand.java index cbf4db1..e013e47 100644 --- a/src/main/java/net/knarcraft/blacksmith/command/blacksmith/BlackSmithConfigCommand.java +++ b/src/main/java/net/knarcraft/blacksmith/command/blacksmith/BlackSmithConfigCommand.java @@ -19,6 +19,8 @@ import org.bukkit.enchantments.Enchantment; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * The command used for changing global blacksmith configuration options */ @@ -213,12 +215,8 @@ public class BlackSmithConfigCommand implements CommandExecutor { private void updateAllMatchedPrices(@NotNull GlobalBlacksmithSettings settings, @NotNull BlacksmithSetting blacksmithSetting, @NotNull String materialName, double newPrice) { - String search = InputParsingHelper.regExIfy(materialName); - for (Material material : ItemHelper.getAllReforgeAbleMaterials()) { - if (!material.name().matches(search)) { - continue; - } - + List materials = ItemHelper.getWildcardMatch(materialName); + for (Material material : materials) { if (blacksmithSetting == BlacksmithSetting.BASE_PRICE) { settings.setBasePrice(material, newPrice); } else { diff --git a/src/main/java/net/knarcraft/blacksmith/config/blacksmith/BlacksmithNPCSettings.java b/src/main/java/net/knarcraft/blacksmith/config/blacksmith/BlacksmithNPCSettings.java index 86c5cab..a9523b1 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/blacksmith/BlacksmithNPCSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/blacksmith/BlacksmithNPCSettings.java @@ -3,21 +3,17 @@ package net.knarcraft.blacksmith.config.blacksmith; import net.citizensnpcs.api.util.DataKey; import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.SettingValueType; -import net.knarcraft.blacksmith.config.SmithPreset; import net.knarcraft.blacksmith.config.TraitSettings; import net.knarcraft.blacksmith.util.ConfigHelper; import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.ItemHelper; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; /** @@ -185,7 +181,7 @@ public class BlacksmithNPCSettings implements TraitSettings { * * @return

All items reforge-able by this NPC

*/ - public List getReforgeAbleItems() { + public List getItems() { Object currentValue = currentValues.get(BlacksmithSetting.REFORGE_ABLE_ITEMS); if (currentValue == null || String.valueOf(currentValue).isEmpty()) { return globalBlacksmithSettings.getReforgeAbleItems(); @@ -357,28 +353,6 @@ public class BlacksmithNPCSettings implements TraitSettings { return value; } - /** - * Replaces placeholders in the given reforge-able value - * - * @param stringList

The value specified by a user

- * @return

The value with placeholders replaced

- */ - private static List replaceReforgeAblePresets(List stringList) { - List newStrings = new ArrayList<>(); - for (String item : stringList) { - if (item == null) { - continue; - } - String replaced = SmithPreset.replacePreset(item); - if (!replaced.equals(item)) { - newStrings.addAll(List.of(replaced.split(","))); - } else { - newStrings.add(item); - } - } - return newStrings; - } - /** * Updates the list of blocked enchantments */ @@ -422,55 +396,8 @@ public class BlacksmithNPCSettings implements TraitSettings { this.reforgeAbleItems.clear(); List materialStrings = ConfigHelper.asStringList(getValue(BlacksmithSetting.REFORGE_ABLE_ITEMS)); if (materialStrings != null) { - this.reforgeAbleItems.addAll(getReforgeAbleItems(materialStrings)); + this.reforgeAbleItems.addAll(ItemHelper.getItems(materialStrings, true)); } } - /** - * Gets a list of the reforgeAbleItems described in the given item list - * - * @param itemList

The list of items defined by the user

- * @return

The materials contained in the item list

- */ - public static List getReforgeAbleItems(List itemList) { - List reforgeAbleItems = new ArrayList<>(); - if (itemList == null) { - return null; - } - - //Convert any presets with a list of materials - itemList = replaceReforgeAblePresets(itemList); - - Set blacklisted = new HashSet<>(); - //Parse every material, and add to reforgeAble items - for (String item : itemList) { - //Ignore ,, - if (InputParsingHelper.isEmpty(item)) { - continue; - } - - boolean blacklist = false; - if (item.startsWith("-")) { - blacklist = true; - item = item.substring(1); - } - - Material material = InputParsingHelper.matchMaterial(item); - if (material != null && ItemHelper.isRepairable(new ItemStack(material, 1))) { - if (!blacklist) { - reforgeAbleItems.add(material); - } else { - blacklisted.add(material); - } - } else { - BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to verify " + item + - " as a valid reforge-able item"); - } - } - - //Remove any blacklisted materials at the end to make sure order of arguments won't matter - reforgeAbleItems.removeAll(blacklisted); - return reforgeAbleItems; - } - } \ No newline at end of file diff --git a/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java b/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java index 923a076..bff6800 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java @@ -10,6 +10,7 @@ import net.knarcraft.blacksmith.util.InputParsingHelper; import net.knarcraft.blacksmith.util.ItemHelper; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.ArrayList; @@ -19,7 +20,7 @@ import java.util.Map; import java.util.logging.Level; /** - * A class which keeps track of all default NPC settings and all global settings + * A class which keeps track of all default blacksmith NPC settings and all global blacksmith settings */ public class GlobalBlacksmithSettings implements Settings { @@ -38,7 +39,7 @@ public class GlobalBlacksmithSettings implements Settings { * @param plugin

A reference to the blacksmith plugin

*/ public GlobalBlacksmithSettings(BlacksmithPlugin plugin) { - defaultConfig = new YamlStorage(new File(plugin.getDataFolder() + File.separator + "config.yml"), + this.defaultConfig = new YamlStorage(new File(plugin.getDataFolder() + File.separator + "config.yml"), "Blacksmith Configuration\nWarning: The values under defaults are the values set for a " + "blacksmith upon creation. To change any values for existing NPCs, edit the citizens NPC file."); } @@ -48,20 +49,20 @@ public class GlobalBlacksmithSettings implements Settings { */ public void load() { // Load the config from disk - defaultConfig.load(); - DataKey root = defaultConfig.getKey(""); + this.defaultConfig.load(); + DataKey root = this.defaultConfig.getKey(""); // Just in case, clear existing values - settings.clear(); - materialBasePrices.clear(); - materialPricePerDurabilityPoints.clear(); - enchantmentCosts.clear(); + this.settings.clear(); + this.materialBasePrices.clear(); + this.materialPricePerDurabilityPoints.clear(); + this.enchantmentCosts.clear(); // Load/Save settings - loadGlobalSettings(root); + loadSettings(root); // Save any modified values to disk - defaultConfig.save(); + this.defaultConfig.save(); } /** @@ -74,9 +75,9 @@ public class GlobalBlacksmithSettings implements Settings { if (blacksmithSetting.getValueType() == SettingValueType.STRING_LIST || blacksmithSetting.getValueType() == SettingValueType.REFORGE_ABLE_ITEMS) { //Workaround to make sure it's treated as the correct type - settings.put(blacksmithSetting, newValue == null ? null : ConfigHelper.asStringList(newValue)); + this.settings.put(blacksmithSetting, newValue == null ? null : ConfigHelper.asStringList(newValue)); } else { - settings.put(blacksmithSetting, newValue); + this.settings.put(blacksmithSetting, newValue); } save(); if (blacksmithSetting == BlacksmithSetting.REFORGE_ABLE_ITEMS) { @@ -93,7 +94,7 @@ public class GlobalBlacksmithSettings implements Settings { * @return

The current raw setting value

*/ public Object getRawValue(BlacksmithSetting blacksmithSetting) { - return settings.get(blacksmithSetting); + return this.settings.get(blacksmithSetting); } /** @@ -107,12 +108,12 @@ public class GlobalBlacksmithSettings implements Settings { if (newEnchantmentCost < 0) { throw new IllegalArgumentException("Enchantment cost cannot be negative!"); } - settings.put(BlacksmithSetting.ENCHANTMENT_COST, newEnchantmentCost); + this.settings.put(BlacksmithSetting.ENCHANTMENT_COST, newEnchantmentCost); } else { if (newEnchantmentCost < 0) { - enchantmentCosts.put(enchantment, null); + this.enchantmentCosts.put(enchantment, null); } else { - enchantmentCosts.put(enchantment, newEnchantmentCost); + this.enchantmentCosts.put(enchantment, newEnchantmentCost); } } save(); @@ -129,13 +130,13 @@ public class GlobalBlacksmithSettings implements Settings { if (newPrice < 0) { throw new IllegalArgumentException("Price per durability point cannot be negative!"); } - settings.put(BlacksmithSetting.PRICE_PER_DURABILITY_POINT, newPrice); + this.settings.put(BlacksmithSetting.PRICE_PER_DURABILITY_POINT, newPrice); } else { //Use a negative price to unset the per-item value if (newPrice < 0) { - materialPricePerDurabilityPoints.put(material, null); + this.materialPricePerDurabilityPoints.put(material, null); } else { - materialPricePerDurabilityPoints.put(material, newPrice); + this.materialPricePerDurabilityPoints.put(material, newPrice); } } save(); @@ -156,9 +157,9 @@ public class GlobalBlacksmithSettings implements Settings { } else { //Use a negative price to unset the per-item value if (newBasePrice < 0) { - materialBasePrices.put(material, null); + this.materialBasePrices.put(material, null); } else { - materialBasePrices.put(material, newBasePrice); + this.materialBasePrices.put(material, newBasePrice); } } save(); @@ -201,8 +202,8 @@ public class GlobalBlacksmithSettings implements Settings { * @return

The base price for the material

*/ public double getBasePrice(Material material) { - if (materialBasePrices.containsKey(material) && materialBasePrices.get(material) != null) { - return materialBasePrices.get(material); + if (this.materialBasePrices.containsKey(material) && this.materialBasePrices.get(material) != null) { + return this.materialBasePrices.get(material); } else { return asDouble(BlacksmithSetting.BASE_PRICE); } @@ -215,9 +216,9 @@ public class GlobalBlacksmithSettings implements Settings { * @return

The durability point price for the material

*/ public double getPricePerDurabilityPoint(Material material) { - if (materialPricePerDurabilityPoints.containsKey(material) && - materialPricePerDurabilityPoints.get(material) != null) { - return materialPricePerDurabilityPoints.get(material); + if (this.materialPricePerDurabilityPoints.containsKey(material) && + this.materialPricePerDurabilityPoints.get(material) != null) { + return this.materialPricePerDurabilityPoints.get(material); } else { return asDouble(BlacksmithSetting.PRICE_PER_DURABILITY_POINT); } @@ -230,8 +231,8 @@ public class GlobalBlacksmithSettings implements Settings { * @return

The cost of each enchantment level

*/ public double getEnchantmentCost(Enchantment enchantment) { - if (enchantmentCosts.containsKey(enchantment) && enchantmentCosts.get(enchantment) != null) { - return enchantmentCosts.get(enchantment); + if (this.enchantmentCosts.containsKey(enchantment) && this.enchantmentCosts.get(enchantment) != null) { + return this.enchantmentCosts.get(enchantment); } else { return asDouble(BlacksmithSetting.ENCHANTMENT_COST); } @@ -242,7 +243,7 @@ public class GlobalBlacksmithSettings implements Settings { * * @return

The value of reforgeAbleItems

*/ - public List getReforgeAbleItems() { + public @NotNull List getReforgeAbleItems() { return this.defaultReforgeAbleMaterials; } @@ -251,7 +252,7 @@ public class GlobalBlacksmithSettings implements Settings { * * @return

The value of enchantmentBlocklist

*/ - public List getEnchantmentBlocklist() { + public @NotNull List getEnchantmentBlocklist() { return this.defaultEnchantmentBlocklist; } @@ -302,7 +303,7 @@ public class GlobalBlacksmithSettings implements Settings { * @return

The current value

*/ private Object getValue(BlacksmithSetting setting) { - Object value = settings.get(setting); + Object value = this.settings.get(setting); //If not set in config.yml, use the default value from the enum if (value == null) { value = setting.getDefaultValue(); @@ -315,7 +316,7 @@ public class GlobalBlacksmithSettings implements Settings { * * @param root

The root node of all global settings

*/ - private void loadGlobalSettings(DataKey root) { + private void loadSettings(DataKey root) { for (BlacksmithSetting blacksmithSetting : BlacksmithSetting.values()) { if (!root.keyExists(blacksmithSetting.getPath())) { //If the setting does not exist in the config file, add it @@ -340,7 +341,7 @@ public class GlobalBlacksmithSettings implements Settings { for (String key : relevantKeys.keySet()) { String enchantmentName = relevantKeys.get(key); Enchantment enchantment = InputParsingHelper.matchEnchantment(enchantmentName); - setItemPrice(enchantmentCosts, enchantmentName, enchantment, enchantmentCostNode.getDouble(key)); + setItemPrice(this.enchantmentCosts, enchantmentName, enchantment, enchantmentCostNode.getDouble(key)); } } @@ -358,10 +359,10 @@ public class GlobalBlacksmithSettings implements Settings { double price = basePerDurabilityPriceNode.getDouble(key); if (materialName.contains("*")) { //Treat *CHESTPLATE as a regular expression to match all chest-plates - setMatchedMaterialPrices(materialPricePerDurabilityPoints, materialName, price); + setMatchedMaterialPrices(this.materialPricePerDurabilityPoints, materialName, price); } else { Material material = InputParsingHelper.matchMaterial(materialName); - setItemPrice(materialPricePerDurabilityPoints, materialName, material, price); + setItemPrice(this.materialPricePerDurabilityPoints, materialName, material, price); } } } @@ -380,10 +381,10 @@ public class GlobalBlacksmithSettings implements Settings { double price = basePriceNode.getDouble(key); if (materialName.contains("*")) { //Treat *CHESTPLATE as a regular expression to match all chest-plates - setMatchedMaterialPrices(materialBasePrices, materialName, price); + setMatchedMaterialPrices(this.materialBasePrices, materialName, price); } else { Material material = InputParsingHelper.matchMaterial(materialName); - setItemPrice(materialBasePrices, materialName, material, price); + setItemPrice(this.materialBasePrices, materialName, material, price); } } } @@ -455,11 +456,11 @@ public class GlobalBlacksmithSettings implements Settings { * Loads reforgeAble items from the current value */ private void loadReforgeAbleItems() { - defaultReforgeAbleMaterials.clear(); + this.defaultReforgeAbleMaterials.clear(); List materialNames = ConfigHelper.asStringList(settings.get( BlacksmithSetting.REFORGE_ABLE_ITEMS)); if (materialNames != null) { - defaultReforgeAbleMaterials.addAll(BlacksmithNPCSettings.getReforgeAbleItems(materialNames)); + this.defaultReforgeAbleMaterials.addAll(ItemHelper.getItems(materialNames, true)); } } @@ -467,11 +468,11 @@ public class GlobalBlacksmithSettings implements Settings { * Loads the enchantment blocklist from the current value */ private void loadEnchantmentBlocklist() { - defaultEnchantmentBlocklist.clear(); + this.defaultEnchantmentBlocklist.clear(); List enchantmentNames = ConfigHelper.asStringList(settings.get( BlacksmithSetting.ENCHANTMENT_BLOCKLIST)); if (enchantmentNames != null) { - defaultEnchantmentBlocklist.addAll(BlacksmithNPCSettings.getEnchantmentBlocklist(enchantmentNames)); + this.defaultEnchantmentBlocklist.addAll(BlacksmithNPCSettings.getEnchantmentBlocklist(enchantmentNames)); } } @@ -479,32 +480,32 @@ public class GlobalBlacksmithSettings implements Settings { * Saves all current settings to the config file */ private void save() { - DataKey root = defaultConfig.getKey(""); + DataKey root = this.defaultConfig.getKey(""); //Save all default settings for (BlacksmithSetting setting : BlacksmithSetting.values()) { - root.setRaw(setting.getPath(), settings.get(setting)); + root.setRaw(setting.getPath(), this.settings.get(setting)); } //Save all base prices DataKey basePriceNode = root.getRelative(BlacksmithSetting.BASE_PRICE.getPath()); - for (Material material : materialBasePrices.keySet()) { - basePriceNode.setRaw(unNormalizeName(material.name()), materialBasePrices.get(material)); + for (Material material : this.materialBasePrices.keySet()) { + basePriceNode.setRaw(unNormalizeName(material.name()), this.materialBasePrices.get(material)); } //Save all per-durability-point prices DataKey basePerDurabilityPriceNode = root.getRelative(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath()); - for (Material material : materialPricePerDurabilityPoints.keySet()) { - basePerDurabilityPriceNode.setRaw(unNormalizeName(material.name()), materialPricePerDurabilityPoints.get(material)); + for (Material material : this.materialPricePerDurabilityPoints.keySet()) { + basePerDurabilityPriceNode.setRaw(unNormalizeName(material.name()), this.materialPricePerDurabilityPoints.get(material)); } //Load all enchantment prices DataKey enchantmentCostNode = root.getRelative(BlacksmithSetting.ENCHANTMENT_COST.getPath()); - for (Enchantment enchantment : enchantmentCosts.keySet()) { - enchantmentCostNode.setRaw(unNormalizeName(enchantment.getKey().getKey()), enchantmentCosts.get(enchantment)); + for (Enchantment enchantment : this.enchantmentCosts.keySet()) { + enchantmentCostNode.setRaw(unNormalizeName(enchantment.getKey().getKey()), this.enchantmentCosts.get(enchantment)); } //Perform the actual save to disk - defaultConfig.save(); + this.defaultConfig.save(); } } diff --git a/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java b/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java index a5fe295..574f783 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java @@ -6,14 +6,28 @@ import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.Settings; import net.knarcraft.blacksmith.util.ConfigHelper; +import net.knarcraft.blacksmith.util.ItemHelper; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +/** + * A class which keeps track of all default scrapper NPC settings and all global scrapper settings + */ public class GlobalScrapperSettings implements Settings { private final Map settings = new HashMap<>(); + private final List defaultSalvageableMaterials = new ArrayList<>(); + private final Map> ignoredSalvage = new HashMap<>(); private final YamlStorage defaultConfig; @@ -23,7 +37,7 @@ public class GlobalScrapperSettings implements Settings { * @param plugin

A reference to the blacksmith plugin

*/ public GlobalScrapperSettings(BlacksmithPlugin plugin) { - defaultConfig = new YamlStorage(new File(plugin.getDataFolder() + File.separator + "config.yml"), + this.defaultConfig = new YamlStorage(new File(plugin.getDataFolder() + File.separator + "config.yml"), "Scrapper Configuration\nWarning: The values under defaults are the values set for a " + "scrapper upon creation. To change any values for existing NPCs, edit the citizens NPC file."); } @@ -33,17 +47,17 @@ public class GlobalScrapperSettings implements Settings { */ public void load() { //Load the config from disk - defaultConfig.load(); - DataKey root = defaultConfig.getKey(""); + this.defaultConfig.load(); + DataKey root = this.defaultConfig.getKey(""); //Just in case, clear existing values - settings.clear(); + this.settings.clear(); //Load/Save global settings loadSettings(root); //Save any modified values to disk - defaultConfig.save(); + this.defaultConfig.save(); } /** @@ -52,13 +66,16 @@ public class GlobalScrapperSettings implements Settings { * @param scrapperSetting

The default NPC setting to change

* @param newValue

The new value for the setting

*/ - public void changeValue(ScrapperSetting scrapperSetting, Object newValue) { + public void changeValue(@NotNull ScrapperSetting scrapperSetting, @Nullable Object newValue) { if (scrapperSetting.getValueType() == SettingValueType.STRING_LIST || scrapperSetting.getValueType() == SettingValueType.REFORGE_ABLE_ITEMS) { //Workaround to make sure it's treated as the correct type - settings.put(scrapperSetting, newValue == null ? null : ConfigHelper.asStringList(newValue)); + this.settings.put(scrapperSetting, newValue == null ? null : ConfigHelper.asStringList(newValue)); } else { - settings.put(scrapperSetting, newValue); + this.settings.put(scrapperSetting, newValue); + } + if (scrapperSetting == ScrapperSetting.SALVAGE_ABLE_ITEMS) { + loadSalvageAbleItems(); } save(); } @@ -70,7 +87,7 @@ public class GlobalScrapperSettings implements Settings { * @return

The current raw setting value

*/ public Object getRawValue(ScrapperSetting scrapperSetting) { - return settings.get(scrapperSetting); + return this.settings.get(scrapperSetting); } /** @@ -110,7 +127,7 @@ public class GlobalScrapperSettings implements Settings { * @return

The current value

*/ private Object getValue(ScrapperSetting setting) { - Object value = settings.get(setting); + Object value = this.settings.get(setting); //If not set in config.yml, use the default value from the enum if (value == null) { value = setting.getDefaultValue(); @@ -130,23 +147,97 @@ public class GlobalScrapperSettings implements Settings { root.setRaw(setting.getPath(), setting.getDefaultValue()); } else { //Set the setting to the value found in the path - settings.put(setting, root.getRaw(setting.getPath())); + this.settings.put(setting, root.getRaw(setting.getPath())); } } + loadSalvageAbleItems(); + loadIgnoredSalvage(); } /** * Saves all current settings to the config file */ private void save() { - DataKey root = defaultConfig.getKey(""); + DataKey root = this.defaultConfig.getKey(""); //Save all default settings for (ScrapperSetting setting : ScrapperSetting.values()) { - root.setRaw(setting.getPath(), settings.get(setting)); + root.setRaw(setting.getPath(), this.settings.get(setting)); } //Perform the actual save to disk - defaultConfig.save(); + this.defaultConfig.save(); + } + + /** + * Gets the value of salvageAbleItems + * + * @return

The value of salvageAbleItems

+ */ + public List getSalvageAbleItems() { + return this.defaultSalvageableMaterials; + } + + /** + * Gets ignored salvage for the given material + * + *

The ignored salvage should be ignored when calculating item salvage, in order to increase the probability of + * getting the more expensive materials back.

+ * + * @param material

The material to get ignored salvage for

+ * @return

The ignored salvage

+ */ + public @Nullable Set getIgnoredSalvage(@NotNull Material material) { + return this.ignoredSalvage.get(material); + } + + /** + * Loads reforgeAble items from the current value + */ + private void loadSalvageAbleItems() { + this.defaultSalvageableMaterials.clear(); + List materialNames = ConfigHelper.asStringList(this.settings.get(ScrapperSetting.SALVAGE_ABLE_ITEMS)); + if (materialNames != null) { + this.defaultSalvageableMaterials.addAll(ItemHelper.getItems(materialNames, true)); + } + } + + /** + * Loads all ignored salvage from the configuration file + */ + private void loadIgnoredSalvage() { + this.ignoredSalvage.clear(); + List allIgnoredSalvage = ConfigHelper.asStringList(this.settings.get(ScrapperSetting.IGNORED_SALVAGE)); + if (allIgnoredSalvage == null) { + return; + } + + for (String ignoredSalvageInfo : allIgnoredSalvage) { + // Ignore invalid lines + if (!ignoredSalvageInfo.contains(":")) { + BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, String.format("The ignored salvage " + + "configuration line %s is invalid", ignoredSalvageInfo)); + continue; + } + + // Parse all material names + String[] data = ignoredSalvageInfo.split(":"); + String[] materialStrings = data[0].split(","); + List materials = new ArrayList<>(); + for (String materialString : materialStrings) { + materials.addAll(ItemHelper.getWildcardMatch(materialString)); + } + String[] ignoredSalvageStrings = data[1].split(","); + List ignored = new ArrayList<>(); + for (String ignoredSalvageString : ignoredSalvageStrings) { + ignored.addAll(ItemHelper.getWildcardMatch(ignoredSalvageString)); + } + + // Add the ignored salvage to all the matched materials + for (Material material : materials) { + ignoredSalvage.computeIfAbsent(material, k -> new HashSet<>()); + ignoredSalvage.get(material).addAll(ignored); + } + } } } diff --git a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java index 3157430..8563d61 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java @@ -4,13 +4,19 @@ import net.citizensnpcs.api.util.DataKey; import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.TraitSettings; import net.knarcraft.blacksmith.util.ConfigHelper; +import net.knarcraft.blacksmith.util.ItemHelper; +import org.bukkit.Material; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ScrapperNPCSettings implements TraitSettings { + private final Map currentValues = new HashMap<>(); private final GlobalScrapperSettings globalScrapperSettings; + private final List salvageAbleItems = new ArrayList<>(); /** * Instantiates a new scrapper NPC settings object @@ -59,6 +65,20 @@ public class ScrapperNPCSettings implements TraitSettings { } else { currentValues.put(setting, newValue); } + if (setting == ScrapperSetting.SALVAGE_ABLE_ITEMS) { + updateSalvageAbleItems(); + } + } + + /** + * Updates the reforge-able items according to the current value of the setting + */ + private void updateSalvageAbleItems() { + this.salvageAbleItems.clear(); + List materialStrings = ConfigHelper.asStringList(getValue(ScrapperSetting.SALVAGE_ABLE_ITEMS)); + if (materialStrings != null) { + this.salvageAbleItems.addAll(ItemHelper.getItems(materialStrings, !extendedSalvageEnabled())); + } } /** @@ -87,14 +107,23 @@ public class ScrapperNPCSettings implements TraitSettings { } /** - * Gets the message to display when a blacksmith has successfully repaired an item + * Gets the message to display when a scrapper has successfully scrapped an item * - * @return

The reforge success message

+ * @return

The salvage success message

*/ public String getSuccessMessage() { return asString(ScrapperSetting.SUCCESS_SALVAGE_MESSAGE); } + /** + * Gets the message to display when a scrapper fails to salvage an item + * + * @return

The salvage fail message

+ */ + public String getFailMessage() { + return asString(ScrapperSetting.FAIL_SALVAGE_MESSAGE); + } + @Override public String getCoolDownUnexpiredMessage() { return asString(ScrapperSetting.COOL_DOWN_UNEXPIRED_MESSAGE); @@ -157,6 +186,15 @@ public class ScrapperNPCSettings implements TraitSettings { return asInt(ScrapperSetting.MAX_SALVAGE_DELAY) <= 0; } + /** + * Gets the chance of a salvaging to fail + * + * @return

The chance of a salvaging to fail

+ */ + public int getFailChance() { + return asInt(ScrapperSetting.FAIL_SALVAGE_CHANCE); + } + /** * Gets the given value as an integer * @@ -201,4 +239,51 @@ public class ScrapperNPCSettings implements TraitSettings { return value; } + /** + * Gets all item types this NPC is able to salvage + * + * @return

All salvageable items

+ */ + public List getSalvageAbleItems() { + Object currentValue = currentValues.get(ScrapperSetting.SALVAGE_ABLE_ITEMS); + if (currentValue == null || String.valueOf(currentValue).isEmpty()) { + return globalScrapperSettings.getSalvageAbleItems(); + } else { + return new ArrayList<>(this.salvageAbleItems); + } + } + + /** + * Whether extended salvaging is enabled + * + *

When not extended, only repairable items can be salvaged. When extended, any item produced using a crafting + * recipe or a smithing transformation can be salvaged. This does not include smelting or similar.

+ * + * @return

True if extended salvaging is enabled

+ */ + public boolean extendedSalvageEnabled() { + return ConfigHelper.asBoolean(getValue(ScrapperSetting.EXTENDED_SALVAGE_ENABLED)); + } + + /** + * Gets the title of this scrapper NPC + * + *

The title is used to specify scrapper sub-types in order to make it clear which items a scrapper can salvage. + * For example, an armor-scrapper would only be able to salvage armor pieces.

+ * + * @return

The title of this scrapper NPC

+ */ + public String getScrapperTitle() { + return asString(ScrapperSetting.SCRAPPER_TITLE); + } + + /** + * Gets the message to display if this NPC is given an item it cannot salvage + * + * @return

The message to display

+ */ + public String getInvalidItemMessage() { + return asString(ScrapperSetting.INVALID_ITEM_MESSAGE); + } + } diff --git a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java index 55ae7e2..075ffb0 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java @@ -5,6 +5,9 @@ import net.knarcraft.blacksmith.config.SettingValueType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * An enum representing all scrapper-related settings */ @@ -63,6 +66,12 @@ public enum ScrapperSetting implements Setting { */ SCRAPPER_TITLE("scrapperTitle", SettingValueType.STRING, "scrapper", "The title describing the scrapper's usage/speciality", true, false), + + /** + * The setting for whether the NPC should allow salvaging of any craft-able item instead of just repairable items + */ + EXTENDED_SALVAGE_ENABLED("extendedSalvageEnabled", SettingValueType.BOOLEAN, false, + "Whether to enable salvaging of non-repairable items, such as planks", true, false), /*----------- | Messages | @@ -91,11 +100,12 @@ public enum ScrapperSetting implements Setting { "on a cool-down from the previous re-forging", true, true), /** - * The message displayed if presented with an item that cannot be salvaged by the NPC + * The message displayed if the scrapper encounters an item they cannot salvage */ - CANNOT_SALVAGE_MESSAGE("cannotSalvageMessage", SettingValueType.STRING, - "&cI'm unable to salvage that item", "The message to display if the player tries to salvage" + - " an item the blacksmith cannot salvage", true, true), + INVALID_ITEM_MESSAGE("invalidItemMessage", SettingValueType.STRING, + "&cI'm sorry, but I'm a/an {title}, I don't know how to salvage that!", + "The message to display if the player tries to salvage" + + " an item the scrapper cannot salvage", true, true), /** * The message displayed if salvaging an item would return in no items @@ -146,6 +156,17 @@ public enum ScrapperSetting implements Setting { */ GIVE_EXPERIENCE("giveExperience", SettingValueType.BOOLEAN, "true", "Whether enchanted " + "salvaged items should return some amount of exp upon salvage", false, false), + + /** + * Which items are ignored when calculating salvage for a given material + */ + IGNORED_SALVAGE("ignoredSalvage", SettingValueType.STRING_LIST, + new ArrayList<>(List.of("*_SHOVEL,*_PICKAXE,*_AXE,*_HOE,*_SWORD:STICK")), + "Items ignored during salvage calculation. This follows the format: " + + "\"MATERIAL[,MATERIAL2][,MATERIAL3]:IGNORED\", so the material or materials listed will ignore " + + "the material specified after the \":\" when calculating salvage (* matches any character). This " + + "causes the player to lose some items during salvaging, but can prevent cases where a diamond " + + "pickaxe is salvaged and only sticks are returned.", false, false), ; private final String path; diff --git a/src/main/java/net/knarcraft/blacksmith/trait/BlacksmithTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/BlacksmithTrait.java index 33acc87..32e097c 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/BlacksmithTrait.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/BlacksmithTrait.java @@ -67,7 +67,7 @@ public class BlacksmithTrait extends CustomTrait { public void startSession(@NotNull Player player) { ItemStack hand = player.getInventory().getItemInMainHand(); //Refuse if not repairable, or if reforge-able items is set, but doesn't include the held item - List reforgeAbleItems = config.getReforgeAbleItems(); + List reforgeAbleItems = config.getItems(); boolean notHoldingAnvil = !this.config.getRepairAnvils() || !ItemHelper.isAnvil(hand.getType(), false); boolean notHoldingRepairable = !ItemHelper.isRepairable(hand) || diff --git a/src/main/java/net/knarcraft/blacksmith/trait/CustomTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/CustomTrait.java index aec9947..5623978 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/CustomTrait.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/CustomTrait.java @@ -186,6 +186,8 @@ public abstract class CustomTrait extends Trait { Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(heldItem); } //Remove the item from the player's inventory + // TODO: For a scrapper with extended salvaging enabled, the item needs to be reduced with the amount specified + // in the recipe, or removed as normal in the case where the player has exactly enough items to run the salvage. player.getInventory().setItemInMainHand(null); } diff --git a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java index d81c0aa..3a5d47b 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java @@ -5,18 +5,22 @@ import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.SalvageHelper; +import org.bukkit.Material; +import org.bukkit.entity.Damageable; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.Calendar; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Random; +import java.util.Set; import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage; +import static net.knarcraft.blacksmith.util.ItemHelper.updateDamage; /** * A representation of the session between a player and a blacksmith @@ -29,6 +33,7 @@ public class SalvageSession extends Session implements Runnable { private final ScrapperNPCSettings config; private final List salvage; private final int enchantmentLevels; + private static final Random random = new Random(); /** * Instantiates a new session @@ -45,9 +50,16 @@ public class SalvageSession extends Session implements Runnable { this.npc = npc; this.itemToSalvage = player.getInventory().getItemInMainHand(); this.config = config; - // TODO: Implement the list of items to ignore when calculating salvage + + // Get the salvage, for the item, but ignore some materials if set, and the item isn't at full durability + Set ignoredSalvage = BlacksmithPlugin.getInstance().getGlobalScrapperSettings().getIgnoredSalvage( + this.itemToSalvage.getType()); + if (ignoredSalvage == null || ItemHelper.getDamage(this.itemToSalvage) == 0) { + ignoredSalvage = new HashSet<>(); + } this.salvage = SalvageHelper.getSalvage(BlacksmithPlugin.getInstance().getServer(), this.itemToSalvage, - new ArrayList<>()); + ignoredSalvage); + this.enchantmentLevels = SalvageHelper.getTotalEnchantmentLevels(this.itemToSalvage); } @@ -79,10 +91,7 @@ public class SalvageSession extends Session implements Runnable { */ @Override public void run() { - // TODO: Tell the player that the salvaging was successful, or cancel giveSalvage + give the original item back? - - // Give the player the result of the salvage - giveSalvage(); + sendNPCMessage(this.npc, player, salvageItem() ? config.getSuccessMessage() : config.getFailMessage()); //Stop the reforged item from displaying in the blacksmith's hand if (npc.getEntity() instanceof Player) { @@ -100,6 +109,42 @@ public class SalvageSession extends Session implements Runnable { scrapperTrait.addCoolDown(player.getUniqueId(), wait); } + /** + * Trues to salvage an item, and gives the return item + * + * @return

True if the salvage was successful. False otherwise.

+ */ + private boolean salvageItem() { + if (random.nextInt(100) < config.getFailChance()) { + failSalvage(); + return false; + } else { + // Give the player the result of the salvage + giveSalvage(); + return true; + } + } + + /** + * The method to run when a blacksmith fails re-forging an item + */ + private void failSalvage() { + if (itemToSalvage instanceof Damageable) { + //Damage the item + short currentItemDurability = ItemHelper.getDurability(itemToSalvage); + short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8))); + short maxDurability = itemToSalvage.getType().getMaxDurability(); + if (newDurability <= 0) { + newDurability = (short) (maxDurability / 3); + } else if (currentItemDurability + newDurability > maxDurability) { + newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); + } + updateDamage(itemToSalvage, maxDurability - newDurability); + } else { + itemToSalvage.setAmount(Math.max(itemToSalvage.getAmount() - 1, 1)); + } + } + /** * Gives the player the calculated salvage */ diff --git a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java index 9bc9342..9e06130 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java @@ -4,11 +4,19 @@ import net.citizensnpcs.api.util.DataKey; import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.config.scrapper.ScrapperSetting; +import net.knarcraft.blacksmith.util.ItemHelper; +import net.knarcraft.knarlib.formatting.StringFormatter; import org.apache.commons.lang.NotImplementedException; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.List; + +import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage; + /** * The class representing a scrapper NPC trait */ @@ -64,9 +72,19 @@ public class ScrapperTrait extends CustomTrait { * @param player

The player to start the session for

*/ public void startSession(@NotNull Player player) { - // TODO: Need some method to check which items are salvageable by the specific NPC. It should be similar to - // reforge-able items. Optionally, extended mode can be enabled, which supports any recipe? - // TODO: Check if the held item is salvageable, and give appropriate messages + ItemStack itemInHand = player.getInventory().getItemInMainHand(); + List salvageAbleItems = this.config.getSalvageAbleItems(); + boolean extended = this.config.extendedSalvageEnabled(); + // If extended mode is disabled, only allow repairable items to be salvaged + boolean notHoldingSalvageable = !ItemHelper.isSalvageable(player.getServer(), itemInHand) || + (!salvageAbleItems.isEmpty() && !salvageAbleItems.contains(itemInHand.getType())) || + (!extended && !ItemHelper.isRepairable(itemInHand)); + if (notHoldingSalvageable) { + String invalidMessage = StringFormatter.replacePlaceholder(config.getInvalidItemMessage(), + "{title}", config.getScrapperTitle()); + sendNPCMessage(this.npc, player, invalidMessage); + return; + } // TODO: Mark the session start // TODO: Initialize a new session // TODO: Tell player about the required cost diff --git a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java index aa16736..93a0768 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java @@ -1,14 +1,24 @@ package net.knarcraft.blacksmith.util; +import net.knarcraft.blacksmith.BlacksmithPlugin; +import net.knarcraft.blacksmith.config.SmithPreset; import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.CraftingRecipe; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.SmithingTransformRecipe; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.logging.Level; /** * A helper class for getting information about items @@ -155,4 +165,109 @@ public final class ItemHelper { return false; } + /** + * Checks whether the given item can be salvaged, assuming no restrictions apply + * + * @param server

The server to get recipes from

+ * @param item

The item to check

+ * @return

True if the item can be salvaged

+ */ + public static boolean isSalvageable(@NotNull Server server, @NotNull ItemStack item) { + for (Recipe recipe : server.getRecipesFor(item)) { + // Only crafting recipes, and smithing transform recipes (diamond -> netherite) are allowed. + if (recipe instanceof CraftingRecipe || recipe instanceof SmithingTransformRecipe && + item.getAmount() >= recipe.getResult().getAmount()) { + return true; + } + } + return false; + } + + /** + * Gets all materials matching the given material wildcard + * + * @param materialName

The material name or material wildcard to match

+ * @return

The matched material(s)

+ */ + public static @NotNull List getWildcardMatch(@NotNull String materialName) { + String search = InputParsingHelper.regExIfy(materialName); + List materials = new ArrayList<>(); + for (Material material : ItemHelper.getAllReforgeAbleMaterials()) { + if (material.name().matches(search)) { + materials.add(material); + } + } + return materials; + } + + /** + * Gets a list of the items described in the given item list + * + * @param itemList

The list of items defined by the user

+ * @param requireRepairable

Whether a material must be repairable to be valid.

+ * @return

The materials contained in the item list

+ */ + public static List getItems(@Nullable List itemList, boolean requireRepairable) { + List items = new ArrayList<>(); + if (itemList == null) { + return null; + } + + //Convert any presets with a list of materials + itemList = replacePresets(itemList); + + Set blacklisted = new HashSet<>(); + //Parse every material, and add to reforgeAble items + for (String item : itemList) { + //Ignore ",," + if (InputParsingHelper.isEmpty(item)) { + continue; + } + + boolean blacklist = false; + if (item.startsWith("-")) { + blacklist = true; + item = item.substring(1); + } + + Material material = InputParsingHelper.matchMaterial(item); + if (material != null && (!requireRepairable || ItemHelper.isRepairable(new ItemStack(material, 1)))) { + if (!blacklist) { + items.add(material); + } else { + blacklisted.add(material); + } + } else { + BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to verify " + item + + " as a valid repairable item"); + } + } + + //Remove any blacklisted materials at the end to make sure order of arguments won't matter + items.removeAll(blacklisted); + return items; + } + + /** + * Replaces smoth presets in the given list of strings + * + * @param stringList

The value specified by a user

+ * @return

The value with smith presets replaced

+ */ + private static @NotNull List replacePresets(@NotNull List stringList) { + List newStrings = new ArrayList<>(); + for (String item : stringList) { + if (item == null) { + continue; + } + String replaced = SmithPreset.replacePreset(item); + if (!replaced.equals(item)) { + newStrings.addAll(List.of(replaced.split(","))); + } else { + newStrings.add(item); + } + } + return newStrings; + } + } diff --git a/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java b/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java index b0ff7bc..e25e411 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,7 +47,7 @@ public final class SalvageHelper { * @return

The items to return to the user, or null if not salvageable

*/ public static @Nullable List getSalvage(@NotNull Server server, @Nullable ItemStack salvagedItem, - @NotNull List ignoredSalvage) { + @NotNull Collection ignoredSalvage) { if (salvagedItem == null || salvagedItem.getAmount() < 1 || !ItemHelper.isRepairable(salvagedItem)) { return null; @@ -71,7 +72,7 @@ public final class SalvageHelper { * @return

A list of items, or null if not a valid type of recipe

*/ private static @Nullable List getRecipeSalvage(@NotNull Recipe recipe, @NotNull ItemStack salvagedItem, - @NotNull List ignoredSalvage) { + @NotNull Collection ignoredSalvage) { List ingredients; if (recipe instanceof ShapedRecipe shapedRecipe) { ingredients = getIngredients(shapedRecipe); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3c48a9f..1d1c7f2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -122,6 +122,13 @@ scrapper: # Whether enchanted salvaged items should return some amount of exp upon salvage giveExperience: true + + # Items ignored during salvage calculation. This follows the format: "MATERIAL[,MATERIAL2][,MATERIAL3]:IGNORED", so + # the material or materials listed will ignore the material specified after the ":" when calculating salvage + # (* matches any character). This causes the player to lose some items during salvaging, but can prevent cases + # where a diamond pickaxe is salvaged and only sticks are returned. + ignoredSalvage: + - "*_SHOVEL,*_PICKAXE,*_AXE,*_HOE,*_SWORD:STICK" # The settings which are set to any new scrapper NPC. To change any of these settings for an existing NPC, you must # change the Citizens NPC file, or use the /scrapper command @@ -150,6 +157,9 @@ scrapper: # The title describing the scrapper's usage/speciality (e.x armor-scrapper, tool-scrapper, weapon-scrapper) scrapperTitle: "scrapper" + # Whether to enable salvaging of non-repairable items, such as planks + extendedSalvageEnabled: false + # Default values for messages used by NPCs messages: # The message to display when another player is using the scrapper @@ -177,4 +187,7 @@ scrapper: itemChangedMessage: "&cThat's not the item you wanted to salvage before!" # The message to display once the scrapper starts salvaging - startSalvageMessage: "&eOk, let's see what I can do..." \ No newline at end of file + startSalvageMessage: "&eOk, let's see what I can do..." + + # The message to display when holding an item the blacksmith is unable to reforge + invalidItemMessage: "&cI'm sorry, but I'm a/an {title}, I don't know how to salvage that!" \ No newline at end of file