From 757fcdf13990c2c2de15331b71ea4cda02e6908b Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sat, 4 May 2024 17:40:21 +0200 Subject: [PATCH] Improves configuration migration, and fixes some issues Fixes incorrect dropItems instead of dropItem key in config.yml Adds some missing dropper messages to config.yml Adds proper migration keys for all known configuration options Adds a modified version of Stargate's StargateYamlConfiguration to allow retaining comments during configuration migration. Fixes the annoying "[]" is not a valid repairable item Removes the use of data keys for the global configuration --- .../blacksmith/BlacksmithPlugin.java | 111 ++++---- .../config/StargateYamlConfiguration.java | 246 ++++++++++++++++++ .../blacksmith/GlobalBlacksmithSettings.java | 164 ++++++++---- .../scrapper/GlobalScrapperSettings.java | 42 ++- .../config/scrapper/ScrapperSetting.java | 2 +- .../blacksmith/util/ConfigHelper.java | 112 ++++++++ .../knarcraft/blacksmith/util/ItemHelper.java | 2 +- src/main/resources/config-migrations.txt | 47 +++- src/main/resources/config.yml | 13 +- 9 files changed, 587 insertions(+), 152 deletions(-) create mode 100644 src/main/java/net/knarcraft/blacksmith/config/StargateYamlConfiguration.java diff --git a/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java b/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java index 15fb73c..29dea58 100644 --- a/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java +++ b/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java @@ -13,6 +13,7 @@ import net.knarcraft.blacksmith.command.scrapper.ScrapperConfigCommand; import net.knarcraft.blacksmith.command.scrapper.ScrapperConfigTabCompleter; import net.knarcraft.blacksmith.command.scrapper.ScrapperEditCommand; import net.knarcraft.blacksmith.command.scrapper.ScrapperEditTabCompleter; +import net.knarcraft.blacksmith.config.StargateYamlConfiguration; import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings; import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings; import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; @@ -21,15 +22,15 @@ import net.knarcraft.blacksmith.listener.PlayerListener; import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.trait.BlacksmithTrait; import net.knarcraft.blacksmith.trait.ScrapperTrait; +import net.knarcraft.blacksmith.util.ConfigHelper; import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.TranslatableTimeUnit; import net.knarcraft.knarlib.formatting.Translator; -import net.knarcraft.knarlib.property.ColorConversion; -import net.knarcraft.knarlib.util.FileHelper; import net.knarcraft.knarlib.util.UpdateChecker; import org.bukkit.command.CommandExecutor; import org.bukkit.command.PluginCommand; import org.bukkit.command.TabCompleter; +import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; @@ -40,9 +41,6 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Map; import java.util.logging.Level; /** @@ -50,11 +48,13 @@ import java.util.logging.Level; */ public class BlacksmithPlugin extends JavaPlugin { + private static final String CONFIG_FILE_NAME = "config.yml"; private static BlacksmithPlugin instance; private GlobalBlacksmithSettings blacksmithConfig; private GlobalScrapperSettings scrapperConfig; private static Translator translator; private static StringFormatter stringFormatter; + private FileConfiguration configuration; /** * Constructor required for MockBukkit @@ -107,8 +107,18 @@ public class BlacksmithPlugin extends JavaPlugin { this.reloadConfig(); blacksmithConfig.load(); scrapperConfig.load(); - translator.loadLanguages(this.getDataFolder(), "en", - this.getConfig().getString("language", "en")); + translator.loadLanguages(this.getDataFolder(), "en", this.getConfiguration().getString( + "language", "en")); + } + + /** + * Gets the raw configuration + * + * @return

The raw configuration

+ */ + @NotNull + public FileConfiguration getConfiguration() { + return this.configuration; } /** @@ -139,18 +149,23 @@ public class BlacksmithPlugin extends JavaPlugin { instance = this; //Copy default config to disk - FileConfiguration fileConfiguration = this.getConfig(); this.saveDefaultConfig(); - if (fileConfiguration.getString("defaults.dropItem") != null) { - migrateConfig(fileConfiguration); - this.saveConfig(); + this.getConfig(); + this.configuration = new StargateYamlConfiguration(); + try { + this.configuration.load(new File(getDataFolder(), CONFIG_FILE_NAME)); + } catch (IOException | InvalidConfigurationException exception) { + getLogger().log(Level.SEVERE, exception.getMessage()); } - fileConfiguration.options().copyDefaults(true); - this.reloadConfig(); - this.saveConfig(); + this.configuration.options().copyDefaults(true); // Initialize custom configuration files - initializeConfigurations(fileConfiguration); + this.reloadConfig(); + if (this.configuration.getString("scrapper.defaults.dropItem") == null) { + ConfigHelper.migrateConfig(this.getDataFolder().getPath().replaceAll("\\\\", "/"), this.configuration); + this.reloadConfig(); + } + initializeConfigurations(this.configuration); //Set up Vault integration if (!setUpVault()) { @@ -174,6 +189,28 @@ public class BlacksmithPlugin extends JavaPlugin { () -> this.getDescription().getVersion(), null); } + @Override + public void reloadConfig() { + super.reloadConfig(); + this.configuration = new StargateYamlConfiguration(); + this.configuration.options().copyDefaults(true); + try { + this.configuration.load(new File(getDataFolder(), CONFIG_FILE_NAME)); + } catch (IOException | InvalidConfigurationException exception) { + getLogger().log(Level.SEVERE, "Unable to load the configuration! Message: " + exception.getMessage()); + } + } + + @Override + public void saveConfig() { + super.saveConfig(); + try { + this.configuration.save(new File(getDataFolder(), CONFIG_FILE_NAME)); + } catch (IOException exception) { + getLogger().log(Level.SEVERE, "Unable to save the configuration! Message: " + exception.getMessage()); + } + } + /** * Initializes custom configuration and translation * @@ -256,48 +293,4 @@ public class BlacksmithPlugin extends JavaPlugin { } } - /** - * Changes all configuration values from the old name to the new name - * - * @param fileConfiguration

The config to read from and write to

- */ - private void migrateConfig(@NotNull FileConfiguration fileConfiguration) { - //Save the old config just in case something goes wrong - try { - fileConfiguration.save(getDataFolder() + "/config.yml.old"); - } catch (IOException exception) { - BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, Arrays.toString(exception.getStackTrace())); - return; - } - - //Read all available config migrations - Map migrationFields = null; - try { - InputStream inputStream = FileHelper.getInputStreamForInternalFile("/config-migrations.txt"); - if (inputStream != null) { - migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(inputStream), - "=", ColorConversion.NORMAL); - } - } catch (IOException exception) { - BlacksmithPlugin.getInstance().getLogger().log(Level.SEVERE, Arrays.toString(exception.getStackTrace())); - return; - } - - if (migrationFields == null) { - return; - } - - //Replace old config names with the new ones - for (String key : migrationFields.keySet()) { - if (fileConfiguration.contains(key)) { - String newPath = migrationFields.get(key); - Object oldValue = fileConfiguration.get(key); - if (!newPath.trim().isEmpty()) { - fileConfiguration.set(newPath, oldValue); - } - fileConfiguration.set(key, null); - } - } - } - } diff --git a/src/main/java/net/knarcraft/blacksmith/config/StargateYamlConfiguration.java b/src/main/java/net/knarcraft/blacksmith/config/StargateYamlConfiguration.java new file mode 100644 index 0000000..05aa5b0 --- /dev/null +++ b/src/main/java/net/knarcraft/blacksmith/config/StargateYamlConfiguration.java @@ -0,0 +1,246 @@ +package net.knarcraft.blacksmith.config; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A YAML configuration which retains all comments + * + *

This configuration converts all comments to YAML values when loaded, which all start with comment_. When saved, + * those YAML values are converted to normal text comments. This ensures that the comments aren't removed by the + * YamlConfiguration during its parsing.

+ * + *

Note: When retrieving a configuration section, that will include comments that start with "comment_". You should + * filter those before parsing input.

+ * + * @author Kristian Knarvik + * @author Thorin + */ +public class StargateYamlConfiguration extends YamlConfiguration { + + private static final String START_OF_COMMENT_LINE = "[HASHTAG]"; + private static final String END_OF_COMMENT = "_endOfComment_"; + private static final String START_OF_COMMENT = "comment_"; + + @Override + @NotNull + @SuppressWarnings("deprecation") + protected String buildHeader() { + return ""; + } + + @Override + @NotNull + public String saveToString() { + // Convert YAML comments to normal comments + return this.convertYAMLMappingsToComments(super.saveToString()); + } + + @Override + public void loadFromString(@NotNull String contents) throws InvalidConfigurationException { + // Convert normal comments to YAML comments to prevent them from disappearing + super.loadFromString(this.convertCommentsToYAMLMappings(contents)); + } + + /** + * Gets a configuration section's keys, without any comment entries + * + * @param configurationSection

The configuration section to get keys for

+ * @param deep

Whether to get keys for child elements as well

+ * @return

The configuration section's keys, with comment entries removed

+ */ + public static Set getKeysWithoutComments(@NotNull ConfigurationSection configurationSection, boolean deep) { + Set keys = new HashSet<>(configurationSection.getKeys(deep)); + keys.removeIf(key -> key.matches(START_OF_COMMENT + "[0-9]+")); + return keys; + } + + /** + * Reads a file with comments, and recreates them into yaml mappings + * + *

A mapping follows this format: comment_{CommentNumber}: "The comment" + * This needs to be done as comments otherwise get removed using + * the {@link FileConfiguration#save(File)} method. The config + * needs to be saved if a config value has changed.

+ */ + @NotNull + private String convertCommentsToYAMLMappings(@NotNull String configString) { + StringBuilder yamlBuilder = new StringBuilder(); + List currentComment = new ArrayList<>(); + int commentId = 0; + int previousIndentation = 0; + + for (String line : configString.split("\n")) { + String trimmed = line.trim(); + if (trimmed.startsWith("#")) { + // Store the indentation of the block + if (currentComment.isEmpty()) { + previousIndentation = getIndentation(line); + } + //Temporarily store the comment line + addComment(currentComment, trimmed); + } else { + addYamlString(yamlBuilder, currentComment, line, previousIndentation, commentId); + commentId++; + previousIndentation = 0; + } + } + return yamlBuilder.toString(); + } + + /** + * Adds a YAML string to the given string builder + * + * @param yamlBuilder

The string builder used for building YAML

+ * @param currentComment

The comment to add as a YAML string

+ * @param line

The current line

+ * @param previousIndentation

The indentation of the current block comment

+ * @param commentId

The id of the comment

+ */ + private void addYamlString(@NotNull StringBuilder yamlBuilder, @NotNull List currentComment, + @NotNull String line, int previousIndentation, int commentId) { + String trimmed = line.trim(); + //Write the full formatted comment to the StringBuilder + if (!currentComment.isEmpty()) { + int indentation = trimmed.isEmpty() ? previousIndentation : getIndentation(line); + generateCommentYAML(yamlBuilder, currentComment, commentId, indentation); + currentComment.clear(); + } + //Add the non-comment line assuming it isn't empty + if (!trimmed.isEmpty()) { + yamlBuilder.append(line).append("\n"); + } + } + + /** + * Adds the given comment to the given list + * + * @param commentParts

The list to add to

+ * @param comment

The comment to add

+ */ + private void addComment(@NotNull List commentParts, @NotNull String comment) { + if (comment.startsWith("# ")) { + commentParts.add(comment.replaceFirst("# ", START_OF_COMMENT_LINE)); + } else { + commentParts.add(comment.replaceFirst("#", START_OF_COMMENT_LINE)); + } + } + + /** + * Generates a YAML-compatible string for one comment block + * + * @param yamlBuilder

The string builder to add the generated YAML to

+ * @param commentLines

The lines of the comment to convert into YAML

+ * @param commentId

The unique id of the comment

+ * @param indentation

The indentation to add to every line

+ */ + private void generateCommentYAML(@NotNull StringBuilder yamlBuilder, @NotNull List commentLines, + int commentId, int indentation) { + String subIndentation = this.addIndentation(indentation + 2); + //Add the comment start marker + yamlBuilder.append(this.addIndentation(indentation)).append(START_OF_COMMENT).append(commentId).append(": |\n"); + for (String commentLine : commentLines) { + //Add each comment line with the proper indentation + yamlBuilder.append(subIndentation).append(commentLine).append("\n"); + } + //Add the comment end marker + yamlBuilder.append(subIndentation).append(subIndentation).append(END_OF_COMMENT).append("\n"); + } + + /** + * Converts the internal YAML mapping format to a readable config file + * + *

The internal YAML structure is converted to a string with the same format as a standard configuration file. + * The internal structure has comments in the format: START_OF_COMMENT + id + multi-line YAML string + + * END_OF_COMMENT.

+ * + * @param yamlString

A string using the YAML format

+ * @return

The corresponding comment string

+ */ + @NotNull + private String convertYAMLMappingsToComments(@NotNull String yamlString) { + StringBuilder finalText = new StringBuilder(); + + String[] lines = yamlString.split("\n"); + for (int currentIndex = 0; currentIndex < lines.length; currentIndex++) { + String line = lines[currentIndex]; + String possibleComment = line.trim(); + + if (possibleComment.startsWith(START_OF_COMMENT)) { + //Add an empty line before every comment block + finalText.append("\n"); + currentIndex = readComment(finalText, lines, currentIndex + 1, getIndentation(line)); + } else { + //Output the configuration key + finalText.append(line).append("\n"); + } + } + + return finalText.toString().trim(); + } + + /** + * Fully reads a comment + * + * @param builder

The string builder to write to

+ * @param lines

The lines to read from

+ * @param startIndex

The index to start reading from

+ * @param commentIndentation

The indentation of the read comment

+ * @return

The index containing the next non-comment line

+ */ + private int readComment(@NotNull StringBuilder builder, @NotNull String[] lines, int startIndex, + int commentIndentation) { + for (int currentIndex = startIndex; currentIndex < lines.length; currentIndex++) { + String line = lines[currentIndex]; + String possibleComment = line.trim(); + if (!line.contains(END_OF_COMMENT)) { + possibleComment = possibleComment.replace(START_OF_COMMENT_LINE, ""); + builder.append(addIndentation(commentIndentation)).append("# ").append(possibleComment).append("\n"); + } else { + return currentIndex; + } + } + + return startIndex; + } + + /** + * Gets a string containing the given indentation + * + * @param indentationSpaces

The number spaces to use for indentation

+ * @return

A string containing the number of spaces specified

+ */ + @NotNull + private String addIndentation(int indentationSpaces) { + return " ".repeat(Math.max(0, indentationSpaces)); + } + + + /** + * Gets the indentation (number of spaces) of the given line + * + * @param line

The line to get indentation of

+ * @return

The number of spaces in the line's indentation

+ */ + private int getIndentation(@NotNull String line) { + int spacesFound = 0; + for (char aCharacter : line.toCharArray()) { + if (aCharacter == ' ') { + spacesFound++; + } else { + break; + } + } + return spacesFound; + } + +} \ 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 c3cb514..0173001 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/blacksmith/GlobalBlacksmithSettings.java @@ -1,20 +1,21 @@ package net.knarcraft.blacksmith.config.blacksmith; -import net.citizensnpcs.api.util.DataKey; -import net.citizensnpcs.api.util.YamlStorage; import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.SettingValueType; import net.knarcraft.blacksmith.config.Settings; +import net.knarcraft.blacksmith.config.StargateYamlConfiguration; 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.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.enchantments.Enchantment; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,28 +32,21 @@ public class GlobalBlacksmithSettings implements Settings { private final Map settings = new HashMap<>(); private final List defaultReforgeAbleMaterials = new ArrayList<>(); private final List defaultEnchantmentBlockList = new ArrayList<>(); - - private final YamlStorage defaultConfig; + private final BlacksmithPlugin instance; /** - * Instantiates a new "Settings" + * Instantiates a new "Global Blacksmith Settings" * - * @param plugin

A reference to the blacksmith plugin

+ * @param instance

The blacksmith plugin to load and save configurations for

*/ - public GlobalBlacksmithSettings(BlacksmithPlugin plugin) { - 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."); + public GlobalBlacksmithSettings(BlacksmithPlugin instance) { + this.instance = instance; } /** * Loads all configuration values from the config file */ public void load() { - // Load the config from disk - this.defaultConfig.load(); - DataKey root = this.defaultConfig.getKey(""); - // Just in case, clear existing values this.settings.clear(); this.materialBasePrices.clear(); @@ -60,10 +54,10 @@ public class GlobalBlacksmithSettings implements Settings { this.enchantmentCosts.clear(); // Load/Save settings - loadSettings(root); + loadSettings(); // Save any modified values to disk - this.defaultConfig.save(); + instance.saveConfig(); } /** @@ -314,34 +308,51 @@ public class GlobalBlacksmithSettings implements Settings { /** * Loads all global settings - * - * @param root

The root node of all global settings

*/ - private void loadSettings(DataKey root) { + private void loadSettings() { + instance.reloadConfig(); + FileConfiguration configuration = instance.getConfiguration(); + for (BlacksmithSetting blacksmithSetting : BlacksmithSetting.values()) { - if (!root.keyExists(blacksmithSetting.getPath())) { + if (!configuration.contains(blacksmithSetting.getPath())) { //If the setting does not exist in the config file, add it - root.setRaw(blacksmithSetting.getPath(), blacksmithSetting.getDefaultValue()); + configuration.set(blacksmithSetting.getPath(), blacksmithSetting.getDefaultValue()); } else { //Set the setting to the value found in the path - settings.put(blacksmithSetting, root.getRaw(blacksmithSetting.getPath())); + settings.put(blacksmithSetting, configuration.get(blacksmithSetting.getPath())); } } loadReforgeAbleItems(); loadEnchantmentBlockList(); //Load all base prices - loadBasePrices(root); + loadBasePrices(configuration); //Load all per-durability-point prices - loadPricesPerDurabilityPoint(root); + loadPricesPerDurabilityPoint(configuration); //Load all enchantment prices - DataKey enchantmentCostNode = root.getRelative(BlacksmithSetting.ENCHANTMENT_COST.getPath()); + loadEnchantmentPrices(configuration); + } + + /** + * Loads all prices for enchantments + * + * @param fileConfiguration

The configuration to read

+ */ + private void loadEnchantmentPrices(@NotNull FileConfiguration fileConfiguration) { + ConfigurationSection enchantmentCostNode = fileConfiguration.getConfigurationSection( + getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath())); + if (enchantmentCostNode == null) { + instance.getLogger().log(Level.WARNING, "Could not load enchantment prices. because the " + + "configuration section doesn't exist"); + return; + } Map relevantKeys = getRelevantKeys(enchantmentCostNode); for (String key : relevantKeys.keySet()) { String enchantmentName = relevantKeys.get(key); Enchantment enchantment = InputParsingHelper.matchEnchantment(enchantmentName); + instance.getLogger().log(Level.WARNING, "loadEnchantmentPrices " + enchantmentName); setItemPrice(this.enchantmentCosts, enchantmentName, enchantment, enchantmentCostNode.getDouble(key)); } } @@ -349,10 +360,16 @@ public class GlobalBlacksmithSettings implements Settings { /** * Loads all prices per durability point for all materials * - * @param root

The configuration root node to search from

+ * @param fileConfiguration

The configuration to read

*/ - private void loadPricesPerDurabilityPoint(DataKey root) { - DataKey basePerDurabilityPriceNode = root.getRelative(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath()); + private void loadPricesPerDurabilityPoint(@NotNull FileConfiguration fileConfiguration) { + ConfigurationSection basePerDurabilityPriceNode = fileConfiguration.getConfigurationSection( + getBase(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath())); + if (basePerDurabilityPriceNode == null) { + instance.getLogger().log(Level.WARNING, "Could not load per durability prices. because the " + + "configuration section doesn't exist"); + return; + } Map relevantKeys = getRelevantKeys(basePerDurabilityPriceNode); for (String key : relevantKeys.keySet()) { @@ -371,10 +388,16 @@ public class GlobalBlacksmithSettings implements Settings { /** * Loads base prices for all materials * - * @param root

The configuration root node to search from

+ * @param fileConfiguration

The configuration to read

*/ - private void loadBasePrices(DataKey root) { - DataKey basePriceNode = root.getRelative(BlacksmithSetting.BASE_PRICE.getPath()); + private void loadBasePrices(@NotNull FileConfiguration fileConfiguration) { + ConfigurationSection basePriceNode = fileConfiguration.getConfigurationSection( + getBase(BlacksmithSetting.BASE_PRICE.getPath())); + if (basePriceNode == null) { + instance.getLogger().log(Level.WARNING, "Could not load base prices, because the configuration " + + "section doesn't exist"); + return; + } Map relevantKeys = getRelevantKeys(basePriceNode); for (String key : relevantKeys.keySet()) { @@ -397,7 +420,7 @@ public class GlobalBlacksmithSettings implements Settings { * @param materialName

The material name to match

* @param price

The price to set for the matched materials

*/ - private void setMatchedMaterialPrices(Map prices, String materialName, double price) { + private void setMatchedMaterialPrices(@NotNull Map prices, @NotNull String materialName, double price) { String search = InputParsingHelper.regExIfy(materialName); for (Material material : ItemHelper.getAllReforgeAbleMaterials()) { if (material.name().matches(search)) { @@ -414,31 +437,31 @@ public class GlobalBlacksmithSettings implements Settings { * @param item

The material parsed from the name

* @param price

The price to set

*/ - private void setItemPrice(Map prices, String itemName, K item, double price) { + private void setItemPrice(@NotNull Map prices, @NotNull String itemName, @Nullable K item, + double price) { if (item != null) { prices.put(item, price); } else { - BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, - "Unable to find a material/enchantment matching " + itemName); + instance.getLogger().log(Level.WARNING, "Unable to find a material/enchantment matching " + itemName); } } /** * Gets a map between relevant keys and their normalized name * - * @param rootKey

The root data key containing sub-keys

+ * @param configurationSection

The configuration section to search

* @return

Any sub-keys found that aren't the default

*/ - private Map getRelevantKeys(DataKey rootKey) { + @NotNull + private Map getRelevantKeys(@NotNull ConfigurationSection configurationSection) { Map relevant = new HashMap<>(); - for (DataKey dataKey : rootKey.getSubKeys()) { - String keyName = dataKey.name(); + for (String dataKey : StargateYamlConfiguration.getKeysWithoutComments(configurationSection, false)) { //Skip the default value - if (keyName.equals("default")) { + if (dataKey.equals("default")) { continue; } - String normalizedName = keyName.toUpperCase().replace("-", "_"); - relevant.put(keyName, normalizedName); + String normalizedName = dataKey.toUpperCase().replace("-", "_"); + relevant.put(dataKey, normalizedName); } return relevant; } @@ -449,7 +472,8 @@ public class GlobalBlacksmithSettings implements Settings { * @param normalizedName

The normalized name to un-normalize

* @return

The un-normalized name

*/ - private String unNormalizeName(String normalizedName) { + @NotNull + private String unNormalizeName(@NotNull String normalizedName) { return normalizedName.toLowerCase().replace("_", "-"); } @@ -481,32 +505,66 @@ public class GlobalBlacksmithSettings implements Settings { * Saves all current settings to the config file */ private void save() { - DataKey root = this.defaultConfig.getKey(""); + FileConfiguration fileConfiguration = instance.getConfiguration(); + //Save all default settings for (BlacksmithSetting setting : BlacksmithSetting.values()) { - root.setRaw(setting.getPath(), this.settings.get(setting)); + fileConfiguration.set(setting.getPath(), this.settings.get(setting)); } //Save all base prices - DataKey basePriceNode = root.getRelative(BlacksmithSetting.BASE_PRICE.getPath()); + ConfigurationSection basePriceNode = getAndCreateSection(fileConfiguration, getBase(BlacksmithSetting.BASE_PRICE.getPath())); for (Material material : this.materialBasePrices.keySet()) { - basePriceNode.setRaw(unNormalizeName(material.name()), this.materialBasePrices.get(material)); + basePriceNode.set(unNormalizeName(material.name()), this.materialBasePrices.get(material)); } //Save all per-durability-point prices - DataKey basePerDurabilityPriceNode = root.getRelative(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath()); + ConfigurationSection basePerDurabilityPriceNode = getAndCreateSection(fileConfiguration, getBase(BlacksmithSetting.PRICE_PER_DURABILITY_POINT.getPath())); for (Material material : this.materialPricePerDurabilityPoints.keySet()) { - basePerDurabilityPriceNode.setRaw(unNormalizeName(material.name()), this.materialPricePerDurabilityPoints.get(material)); + basePerDurabilityPriceNode.set(unNormalizeName(material.name()), this.materialPricePerDurabilityPoints.get(material)); } //Load all enchantment prices - DataKey enchantmentCostNode = root.getRelative(BlacksmithSetting.ENCHANTMENT_COST.getPath()); + ConfigurationSection enchantmentCostNode = getAndCreateSection(fileConfiguration, getBase(BlacksmithSetting.ENCHANTMENT_COST.getPath())); for (Enchantment enchantment : this.enchantmentCosts.keySet()) { - enchantmentCostNode.setRaw(unNormalizeName(enchantment.getKey().getKey()), this.enchantmentCosts.get(enchantment)); + enchantmentCostNode.set(unNormalizeName(enchantment.getKey().getKey()), this.enchantmentCosts.get(enchantment)); } //Perform the actual save to disk - this.defaultConfig.save(); + instance.saveConfig(); + } + + /** + * Gets a configuration section, creating it if necessary + * + * @param fileConfiguration

The file configuration to read

+ * @param sectionPath

The path to the configuration section to get

+ * @return

The configuration section

+ */ + @NotNull + private ConfigurationSection getAndCreateSection(@NotNull FileConfiguration fileConfiguration, @NotNull String sectionPath) { + ConfigurationSection configurationSection = fileConfiguration.getConfigurationSection(sectionPath); + if (configurationSection == null) { + fileConfiguration.createSection(sectionPath); + configurationSection = fileConfiguration.getConfigurationSection(sectionPath); + } + if (configurationSection == null) { + throw new RuntimeException("Unable to create configuration section!"); + } + return configurationSection; + } + + /** + * Gets the base path of a configuration path pointing to a specific value + * + * @param configurationPath

The configuration path to get the base of

+ * @return

The base configuration path

+ */ + @NotNull + private String getBase(@NotNull String configurationPath) { + String[] parts = configurationPath.split("\\."); + String[] base = Arrays.copyOfRange(parts, 0, parts.length - 1); + return String.join(".", base); } } 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 3b25055..80dcce9 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java @@ -1,17 +1,15 @@ package net.knarcraft.blacksmith.config.scrapper; -import net.citizensnpcs.api.util.DataKey; -import net.citizensnpcs.api.util.YamlStorage; 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.bukkit.configuration.file.FileConfiguration; 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; @@ -29,35 +27,29 @@ public class GlobalScrapperSettings implements Settings { private final List defaultSalvageableMaterials = new ArrayList<>(); private final Map> ignoredSalvage = new HashMap<>(); - private final YamlStorage defaultConfig; + private final BlacksmithPlugin instance; /** * Instantiates a new "Settings" * - * @param plugin

A reference to the blacksmith plugin

+ * @param instance

A reference to the blacksmith plugin

*/ - public GlobalScrapperSettings(BlacksmithPlugin plugin) { - 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."); + public GlobalScrapperSettings(BlacksmithPlugin instance) { + this.instance = instance; } /** * Loads all configuration values from the config file */ public void load() { - //Load the config from disk - this.defaultConfig.load(); - DataKey root = this.defaultConfig.getKey(""); - //Just in case, clear existing values this.settings.clear(); //Load/Save global settings - loadSettings(root); + loadSettings(); //Save any modified values to disk - this.defaultConfig.save(); + instance.saveConfig(); } /** @@ -137,17 +129,18 @@ public class GlobalScrapperSettings implements Settings { /** * Loads all global settings - * - * @param root

The root node of all global settings

*/ - private void loadSettings(DataKey root) { + private void loadSettings() { + instance.reloadConfig(); + FileConfiguration configuration = instance.getConfiguration(); + for (ScrapperSetting setting : ScrapperSetting.values()) { - if (!root.keyExists(setting.getPath())) { + if (!configuration.contains(setting.getPath())) { //If the setting does not exist in the config file, add it - root.setRaw(setting.getPath(), setting.getDefaultValue()); + configuration.set(setting.getPath(), setting.getDefaultValue()); } else { //Set the setting to the value found in the path - this.settings.put(setting, root.getRaw(setting.getPath())); + this.settings.put(setting, configuration.get(setting.getPath())); } } loadSalvageAbleItems(); @@ -158,14 +151,15 @@ public class GlobalScrapperSettings implements Settings { * Saves all current settings to the config file */ private void save() { - DataKey root = this.defaultConfig.getKey(""); + FileConfiguration fileConfiguration = instance.getConfiguration(); + //Save all default settings for (ScrapperSetting setting : ScrapperSetting.values()) { - root.setRaw(setting.getPath(), this.settings.get(setting)); + fileConfiguration.set(setting.getPath(), this.settings.get(setting)); } //Perform the actual save to disk - this.defaultConfig.save(); + instance.saveConfig(); } /** 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 1d5b035..e05cbc7 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java @@ -131,7 +131,7 @@ public enum ScrapperSetting implements Setting { * The message displayed if a player presents a different item after seeing the price to reforge an item */ ITEM_UNEXPECTEDLY_CHANGED_MESSAGE("itemChangedMessage", SettingValueType.STRING, "&cThat's not the item" + - " you wanted to reforge before!", "The message to display when presenting a different item than" + + " you wanted to salvage before!", "The message to display when presenting a different item than" + " the one just evaluated", true, true), /** diff --git a/src/main/java/net/knarcraft/blacksmith/util/ConfigHelper.java b/src/main/java/net/knarcraft/blacksmith/util/ConfigHelper.java index 8ae38e7..395d418 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/ConfigHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/ConfigHelper.java @@ -1,10 +1,23 @@ package net.knarcraft.blacksmith.util; +import net.knarcraft.blacksmith.BlacksmithPlugin; +import net.knarcraft.blacksmith.config.StargateYamlConfiguration; +import net.knarcraft.knarlib.property.ColorConversion; +import net.knarcraft.knarlib.util.FileHelper; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemorySection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.logging.Level; /** * A helper class for getting an object value as the correct type @@ -94,4 +107,103 @@ public final class ConfigHelper { } } + /** + * Changes all configuration values from the old name to the new name + * + * @param currentConfiguration

The current config to back up

+ */ + public static void migrateConfig(@NotNull String dataFolderPath, @NotNull FileConfiguration currentConfiguration) { + BlacksmithPlugin instance = BlacksmithPlugin.getInstance(); + + //Save the old config just in case something goes wrong + try { + currentConfiguration.save(new File(dataFolderPath, "config.yml.old")); + } catch (IOException exception) { + instance.getLogger().log(Level.WARNING, "Unable to save old backup and do migration"); + return; + } + + //Load old and new configuration + instance.reloadConfig(); + FileConfiguration oldConfiguration = instance.getConfig(); + InputStream configStream = FileHelper.getInputStreamForInternalFile("/config.yml"); + if (configStream == null) { + instance.getLogger().log(Level.SEVERE, "Could not migrate the configuration, as the internal " + + "configuration could not be read!"); + return; + } + YamlConfiguration newConfiguration = StargateYamlConfiguration.loadConfiguration( + FileHelper.getBufferedReaderFromInputStream(configStream)); + + //Read all available config migrations + Map migrationFields; + try { + InputStream migrationStream = FileHelper.getInputStreamForInternalFile("/config-migrations.txt"); + if (migrationStream == null) { + instance.getLogger().log(Level.SEVERE, "Could not migrate the configuration, as the internal migration paths could not be read!"); + return; + } + migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(migrationStream), + "=", ColorConversion.NORMAL); + } catch (IOException exception) { + instance.getLogger().log(Level.WARNING, "Unable to load config migration file"); + return; + } + + //Replace old config names with the new ones + for (String key : migrationFields.keySet()) { + if (oldConfiguration.contains(key)) { + migrateProperty(migrationFields, key, oldConfiguration); + } + } + + // Copy all keys to the new config + for (String key : StargateYamlConfiguration.getKeysWithoutComments(oldConfiguration, true)) { + if (oldConfiguration.get(key) instanceof MemorySection) { + continue; + } + newConfiguration.set(key, oldConfiguration.get(key)); + } + + try { + newConfiguration.save(new File(dataFolderPath, "config.yml")); + } catch (IOException exception) { + instance.getLogger().log(Level.WARNING, "Unable to save migrated config"); + } + + instance.reloadConfig(); + } + + /** + * Migrates one configuration property + * + * @param migrationFields

The configuration fields to be migrated

+ * @param key

The key/path of the property to migrate

+ * @param oldConfiguration

The original pre-migration configuration

+ */ + private static void migrateProperty(@NotNull Map migrationFields, @NotNull String key, + @NotNull FileConfiguration oldConfiguration) { + String newPath = migrationFields.get(key); + Object oldValue = oldConfiguration.get(key); + if (!newPath.trim().isEmpty()) { + if (oldConfiguration.isConfigurationSection(key)) { + // Copy each value of a configuration section + ConfigurationSection sourceSection = oldConfiguration.getConfigurationSection(key); + ConfigurationSection destinationSection = oldConfiguration.createSection(newPath); + if (sourceSection == null) { + return; + } + for (String path : StargateYamlConfiguration.getKeysWithoutComments(sourceSection, true)) { + destinationSection.set(path, sourceSection.get(path)); + } + } else { + // Copy the value to the new path + oldConfiguration.set(newPath, oldValue); + } + } + + // Remove the old path's value + oldConfiguration.set(key, null); + } + } diff --git a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java index 232bd39..26d295b 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java @@ -244,7 +244,7 @@ public final class ItemHelper { //Parse every material, and add to reforgeAble items for (String item : itemList) { //Ignore ",," - if (InputParsingHelper.isEmpty(item)) { + if (InputParsingHelper.isEmpty(item) || item.matches("\\[]")) { continue; } diff --git a/src/main/resources/config-migrations.txt b/src/main/resources/config-migrations.txt index 8b9f634..177ca80 100644 --- a/src/main/resources/config-migrations.txt +++ b/src/main/resources/config-migrations.txt @@ -1,9 +1,38 @@ -defaults=blacksmith.defaults -global=blacksmith.global -scrapper.defaults.delaysInSeconds.minimum=scrapper.defaults.minSalvageWaitTimeSeconds -scrapper.defaults.delaysInSeconds.maximum=scrapper.defaults.maxSalvageWaitTimeSeconds -scrapper.defaults.delaysInSeconds.salvageCoolDown=scrapper.defaults.salvageCoolDownSeconds -blacksmith.defaults.delaysInSeconds.minimum=blacksmith.defaults.minReforgeWaitTimeSeconds -blacksmith.defaults.delaysInSeconds.maximum=blacksmith.defaults.maxReforgeWaitTimeSeconds -blacksmith.defaults.delaysInSeconds.reforgeCoolDown=blacksmith.defaults.reforgeCoolDownSeconds -blacksmith.defaults.enchantmentBlocklist=blacksmith.defaults.enchantmentBlockList \ No newline at end of file +global.basePrice=blacksmith.global.basePrice +global.pricePerDurabilityPoint=blacksmith.global.pricePerDurabilityPoint +global.enchantmentCost=blacksmith.global.enchantmentCost +global.useNaturalCost=blacksmith.global.useNaturalCost +global.showExactTime=blacksmith.global.showExactTime +global.chippedAnvilRepairCost=blacksmith.global.chippedAnvilReforgingCost +global.damagedAnvilRepairCost=blacksmith.global.damagedAnvilReforgingCost +global.chippedAnvilReforgingCost=blacksmith.global.chippedAnvilReforgingCost +global.damagedAnvilReforgingCost=blacksmith.global.damagedAnvilReforgingCost +global.disableMaterialLimitation= +defaults.dropItem=blacksmith.defaults.dropItem +defaults.reforgeAbleItems=blacksmith.defaults.reforgeAbleItems +defaults.disableCoolDown= +defaults.disableDelay= +defaults.failReforgeChance=blacksmith.defaults.failReforgeChance +defaults.extraEnchantmentChance=blacksmith.defaults.extraEnchantmentChance +defaults.maxEnchantments=blacksmith.defaults.maxEnchantments +defaults.delaysInSeconds.maximum=blacksmith.defaults.maxReforgeWaitTimeSeconds +defaults.delaysInSeconds.minimum=blacksmith.defaults.minReforgeWaitTimeSeconds +defaults.delaysInSeconds.reforgeCoolDown=blacksmith.defaults.reforgeCoolDownSeconds +defaults.delaysInSeconds.blacksmithTitle= +defaults.messages.busyPlayerMessage=blacksmith.defaults.messages.busyPlayerMessage +defaults.messages.busyReforgeMessage=blacksmith.defaults.messages.busyReforgeMessage +defaults.messages.coolDownUnexpiredMessage=blacksmith.defaults.messages.coolDownUnexpiredMessage +defaults.messages.costMessage=blacksmith.defaults.messages.costMessage +defaults.messages.failReforgeMessage=blacksmith.defaults.messages.failReforgeMessage +defaults.messages.insufficientFundsMessage=blacksmith.defaults.messages.insufficientFundsMessage +defaults.messages.invalidItemMessage=blacksmith.defaults.messages.invalidItemMessage +defaults.messages.itemChangedMessage=blacksmith.defaults.messages.itemChangedMessage +defaults.messages.startReforgeMessage=blacksmith.defaults.messages.startReforgeMessage +defaults.messages.successMessage=blacksmith.defaults.messages.successMessage +defaults.messages.notDamagedMessage=blacksmith.defaults.messages.notDamagedMessage +defaults.blacksmithTitle=blacksmith.defaults.blacksmithTitle +defaults.enchantmentBlocklist=blacksmith.defaults.enchantmentBlockList +defaults.repairAnvils=blacksmith.defaults.reforgeAnvils +defaults.repairAnvils= +defaults.reforgeAnvils=blacksmith.defaults.reforgeAnvils +defaults.failReforgeRemovesEnchantments=blacksmith.defaults.failReforgeRemovesEnchantments \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 744a129..533a637 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -139,7 +139,7 @@ scrapper: defaults: # Whether the item will drop materials resulting from scrapping on the ground, instead of putting them into the user's inventory - dropItems: true + dropItem: true # The chance to fail a salvage, thus destroying the item (0-100) failSalvageChance: 0 @@ -175,8 +175,8 @@ scrapper: # The message to display when the scrapper is still on a cool-down from the previous salvaging coolDownUnexpiredMessage: "&cYou've already had your chance! Give me a break! I'll be ready {time}!" - # The message to display if the player tries to salvage an item the scrapper cannot salvage - cannotSalvageMessage: "&cI'm unable to salvage that item" + # 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!" # The message to display if salvaging the player's item would result in no salvage tooDamagedForSalvageMessage: "&cThat item is too damaged to be salvaged into anything useful" @@ -193,5 +193,8 @@ scrapper: # The message to display once the scrapper starts salvaging 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 + # The message to display if the player is unable to pay the scrapper's fee + insufficientFundsMessage: "&cYou don't have enough money to salvage an item!" + + # The message to display when explaining the shown item's salvage cost + costMessage: "&eIt will cost &a{cost}&e to salvage that item! Click again to salvage!" \ No newline at end of file