From 37b68085f8b45d874590813857f1140f84c8d250 Mon Sep 17 00:00:00 2001 From: blablubbabc Date: Sun, 18 Aug 2019 04:51:49 +0200 Subject: [PATCH] Changed how items are getting defined in the config. Internally this new format uses Bukkit's item serialization for parsing the item data, which allows it to support the specification of arbitrary item data and hopefully not require any major updating/maintenance for future minecraft versions. At the same time it tries to stay (slightly) more user-friendly than Bukkit's item serialization by omitting any data that can be restored by the plugin, by avoiding one level of nesting between the item type and item data, by translating ampersand ('&') color codes in display name and lore, and by offering a compact representation for specifying an item only by its type. This change also allows a more detailed specification of some of the editor button items. However, many editor buttons still miss corresponding config settings. Also keep in mind that the display name and lore for these button items get specified via corresponding message settings, so any specified item display name and lore will get replaced by that. When checking if an in-game item matches the item data specified in the config, only the specified data gets compared. So this does not check for item data equality, but instead the checked item is able to contain additional data but still get matched (like before, but previously this was limited to checking display name and lore). The previous item data gets automatically migrated to the new format (config version 2). Other changes: * Internal: Moved config migrations into a separate package. * Internal: Moved some function(s) into ConfigUtils. * Internal: Slightly changed how the plugin checks whether the high currency is enabled. * Internal: Avoiding ItemStack#hasItemMeta calls before getting an item's ItemMeta, since this might be heavier than simply getting the ItemMeta directly and performing only the relevant checks on that. Internally ItemStack#hasItemMeta checks emptiness for all item attributes and might (for CraftItemStacks) even first copy all the item's data into a new ItemMeta object. And even if the item actually has no data (Bukkit ItemStack with null ItemMeta), ItemStack#getItemMeta will simply create a new empty ItemMeta object without having to copy any data, so this is still a similarly lightweight operation anyways. * Internal: Added ItemData tests. Unfortunately this requires CraftBukkit as test dependency. --- .gitignore | 5 + CHANGELOG.md | 11 +- modules/main/pom.xml | 10 + modules/parent/pom.xml | 29 ++ pom.xml | 7 + .../shopkeepers/SKShopkeepersPlugin.java | 4 +- .../com/nisovin/shopkeepers/Settings.java | 299 +++++---------- .../config/migration/ConfigMigration.java | 8 + .../config/migration/ConfigMigration1.java | 125 +++++++ .../config/migration/ConfigMigration2.java | 106 ++++++ .../config/migration/ConfigMigrations.java | 43 +++ .../migration}/LegacyConversion.java | 2 +- .../player/AbstractPlayerShopkeeper.java | 16 +- .../player/PlayerShopEditorHandler.java | 8 +- .../player/book/SKBookPlayerShopkeeper.java | 4 +- .../buy/BuyingPlayerShopEditorHandler.java | 2 +- .../buy/BuyingPlayerShopTradingHandler.java | 2 +- .../ui/defaults/EditorHandler.java | 8 +- .../nisovin/shopkeepers/util/ConfigUtils.java | 54 +++ .../nisovin/shopkeepers/util/ItemData.java | 307 +++++++++++++++ .../nisovin/shopkeepers/util/ItemUtils.java | 189 ++++++++-- .../com/nisovin/shopkeepers/util/Utils.java | 28 ++ .../VillagerInteractionListener.java | 8 +- src/main/resources/config.yml | 60 +-- .../nisovin/shopkeepers/PerformanceTests.java | 140 +++++++ .../testutil/AbstractTestBase.java | 9 + .../shopkeepers/testutil/DummyServer.java | 98 +++++ .../shopkeepers/util/ItemDataTest.java | 352 ++++++++++++++++++ 28 files changed, 1625 insertions(+), 309 deletions(-) create mode 100644 src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration.java create mode 100644 src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration1.java create mode 100644 src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration2.java create mode 100644 src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigrations.java rename src/main/java/com/nisovin/shopkeepers/{ => config/migration}/LegacyConversion.java (98%) create mode 100644 src/main/java/com/nisovin/shopkeepers/util/ConfigUtils.java create mode 100644 src/main/java/com/nisovin/shopkeepers/util/ItemData.java create mode 100644 src/test/java/com/nisovin/shopkeepers/PerformanceTests.java create mode 100644 src/test/java/com/nisovin/shopkeepers/testutil/AbstractTestBase.java create mode 100644 src/test/java/com/nisovin/shopkeepers/testutil/DummyServer.java create mode 100644 src/test/java/com/nisovin/shopkeepers/util/ItemDataTest.java diff --git a/.gitignore b/.gitignore index 4ce87528..61179645 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,8 @@ Desktop.ini # Mac crap .DS_Store + +############ + +**/logs + diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ae9f83..528bcdf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Date format: (YYYY-MM-DD) ### Supported MC versions: 1.14.4 * Bumped Bukkit dependency from 1.14.3 to 1.14.4. +* Added: Changed how items are getting defined in the config. Internally this new format uses Bukkit's item serialization for parsing the item data, which allows it to support the specification of arbitrary item data and hopefully not require any major maintenance for future minecraft versions. At the same time it tries to stay (slightly) more user-friendly than Bukkit's item serialization by omitting any data that can be restored by the plugin, by avoiding one level of nesting between the item type and item data, by translating ampersand ('&') color codes in display name and lore, and by offering a compact representation for specifying an item only by its type. + * This change also allows a more detailed specification of some of the editor button items. However, many editor buttons still miss corresponding config settings. Also keep in mind that the display name and lore for these button items get specified via corresponding message settings, so any specified item display name and lore will get replaced by that. + * When checking if an in-game item matches the item data specified in the config, only the specified data gets compared. So this does not check for item data equality, but instead the checked item is able to contain additional data but still get matched (like before, but previously this was limited to checking display name and lore). + * The previous item data gets automatically migrated to the new format (config version 2). * Changed: All priorities and ignoring of cancelled events were reconsidered. * Event handlers potentially modifying or canceling the event and which don't depend on other plugins' event handling are called early (LOW or LOWEST), so that other plugins can react to / ignore those modified or canceled events. * All event handlers which simply cancel some event use the LOW priority now and more consistently ignore the event if already cancelled. Previously they mostly used NORMAL priority. @@ -24,7 +28,7 @@ Date format: (YYYY-MM-DD) * Interactions with regular villagers (blocking, hiring) are handled at HIGH priority (like before), so that hiring can be skipped if some other plugin has cancelled the interaction. * Changed: Replaced the 'bypass-shop-interaction-blocking' setting (default: false) with the new setting 'check-shop-interaction-result' (default: false). * Changed: The new 'check-shop-interaction-result' setting also applies to sign shops now. - +* Changed: Added a new 'loadbefore' entry for GriefPrevention as workaround to fix some compatibility issue caused by our changed event priorities and GriefPrevention reacting to entity interactions at LOWEST priority. * Fixed: Also cancelling the PlayerInteractAtEntityEvent for shopkeeper entity interactions. * Changed: When forcing an entity to spawn, the pitch and yaw of the expected and actual spawn location are ignored now. This avoids a warning message for some entity types (such as shulkers), which always spawn with fixed pitch and yaw. * Changed: Some entity attributes are setup prior to entity spawning now (such as metadata, non-persist flag and name (if it has/uses one)). This should help other plugins to identify Shopkeeper entities during spawning. @@ -36,12 +40,17 @@ Date format: (YYYY-MM-DD) * API: Minor javadoc changes. * API/Fixed: ShopkeepersAPI was missing getDefaultUITypes. +* Internal: Avoiding ItemStack#hasItemMeta calls before getting an item's ItemMeta, since this might be heavier than simply getting the ItemMeta directly and performing only the relevant checks on that. Internally ItemStack#hasItemMeta checks emptiness for all item attributes and might (for CraftItemStacks) even first copy all the item's data into a new ItemMeta object. And even if the item actually has no data (Bukkit ItemStack with null ItemMeta), ItemStack#getItemMeta will simply create a new empty ItemMeta object without having to copy any data, so this is still a similarly lightweight operation anyways. * Internal: Made all priorities and ignoring of cancelled events explicit. * Internal: Moved code for checking chest access into util package. +* Internal: Moved config migrations into a separate package. +* Internal: Moved some functions into ConfigUtils. +* Internal: Slightly changed how the plugin checks whether the high currency is enabled. * Internal: Metrics will also report now whether the settings 'check-shop-interaction-result', 'bypass-spawn-blocking', 'enable-spawn-verifier' and 'increment-villager-statistics' are used. * Internal: Skipping shopkeeper spawning requests for unloaded worlds (should usually not be the case, but we guard against this anyways now). * Internal: Spigot is stopping the conversion of zombie villagers on its own now if the corresponding transform event gets cancelled. * Internal: Added a test to ensure consistency between ShopkeepersPlugin and ShopkeepersAPI. +* Internal: Added ItemData tests. This requires CraftBukkit as new test dependency due to relying on item serialization. * Debugging: Small changes and additions to some debug messages, especially related to shopkeeper interactions and shopkeeper spawning. * Debugging: Added setting 'debug-options', which can be used to enable additional debugging tools. diff --git a/modules/main/pom.xml b/modules/main/pom.xml index f2a7fb4e..3a079ac7 100644 --- a/modules/main/pom.xml +++ b/modules/main/pom.xml @@ -44,6 +44,16 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + target/test-server + + com/nisovin/shopkeepers/PerformanceTests.java + + + org.apache.maven.plugins maven-shade-plugin diff --git a/modules/parent/pom.xml b/modules/parent/pom.xml index 53d26484..b2133868 100644 --- a/modules/parent/pom.xml +++ b/modules/parent/pom.xml @@ -104,6 +104,19 @@ maven-dependency-plugin 2.8 + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + + **/Test*.java + **/*Test.java + **/*Tests.java + **/*TestCase.java + + + @@ -147,6 +160,22 @@ 1.14.4-R0.1-SNAPSHOT provided + + + org.bukkit + craftbukkit + 1.14.4-R0.1-SNAPSHOT + jar + test + + + org.bukkit + bukkit + + + junit junit diff --git a/pom.xml b/pom.xml index 34ceaebe..116cde3e 100644 --- a/pom.xml +++ b/pom.xml @@ -96,13 +96,20 @@ org.bukkit bukkit + + org.bukkit + craftbukkit + test + junit junit + test org.hamcrest hamcrest-library + test com.sk89q.worldguard diff --git a/src/main/java/com/nisovin/shopkeepers/SKShopkeepersPlugin.java b/src/main/java/com/nisovin/shopkeepers/SKShopkeepersPlugin.java index edb8cb20..ba90c204 100644 --- a/src/main/java/com/nisovin/shopkeepers/SKShopkeepersPlugin.java +++ b/src/main/java/com/nisovin/shopkeepers/SKShopkeepersPlugin.java @@ -160,8 +160,8 @@ public class SKShopkeepersPlugin extends JavaPlugin implements ShopkeepersPlugin } // load config: - File file = new File(this.getDataFolder(), "config.yml"); - if (!file.exists()) { + File configFile = new File(this.getDataFolder(), "config.yml"); + if (!configFile.exists()) { this.saveDefaultConfig(); } this.reloadConfig(); diff --git a/src/main/java/com/nisovin/shopkeepers/Settings.java b/src/main/java/com/nisovin/shopkeepers/Settings.java index 18079e94..5a62b6d8 100644 --- a/src/main/java/com/nisovin/shopkeepers/Settings.java +++ b/src/main/java/com/nisovin/shopkeepers/Settings.java @@ -10,12 +10,14 @@ import java.util.Locale; import org.bukkit.Material; import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import com.nisovin.shopkeepers.config.migration.ConfigMigrations; +import com.nisovin.shopkeepers.util.ConfigUtils; import com.nisovin.shopkeepers.util.ConversionUtils; +import com.nisovin.shopkeepers.util.ItemData; import com.nisovin.shopkeepers.util.ItemUtils; import com.nisovin.shopkeepers.util.Log; import com.nisovin.shopkeepers.util.StringUtils; @@ -56,9 +58,7 @@ public class Settings { /* * Shop Creation (and removal) */ - public static Material shopCreationItem = Material.VILLAGER_SPAWN_EGG; - public static String shopCreationItemName = ""; - public static List shopCreationItemLore = new ArrayList<>(0); + public static ItemData shopCreationItem = new ItemData(Material.VILLAGER_SPAWN_EGG); public static boolean preventShopCreationItemRegularUsage = false; public static boolean deletingPlayerShopReturnsCreationItem = false; @@ -165,18 +165,17 @@ public class Settings { */ public static String editorTitle = "Shopkeeper Editor"; - public static Material previousPageItem = Material.WRITABLE_BOOK; - public static Material nextPageItem = Material.WRITABLE_BOOK; - public static Material currentPageItem = Material.WRITABLE_BOOK; - public static Material tradeSetupItem = Material.PAPER; + public static ItemData previousPageItem = new ItemData(Material.WRITABLE_BOOK); + public static ItemData nextPageItem = new ItemData(Material.WRITABLE_BOOK); + public static ItemData currentPageItem = new ItemData(Material.WRITABLE_BOOK); + public static ItemData tradeSetupItem = new ItemData(Material.PAPER); - public static Material nameItem = Material.NAME_TAG; - public static List nameItemLore = new ArrayList<>(0); + public static ItemData nameItem = new ItemData(Material.NAME_TAG); public static boolean enableChestOptionOnPlayerShop = true; - public static Material chestItem = Material.CHEST; + public static ItemData chestItem = new ItemData(Material.CHEST); - public static Material deleteItem = Material.BONE; + public static ItemData deleteItem = new ItemData(Material.BONE); /* * Non-shopkeeper villagers @@ -193,9 +192,7 @@ public class Settings { /* * Hiring */ - public static Material hireItem = Material.EMERALD; - public static String hireItemName = ""; - public static List hireItemLore = new ArrayList<>(0); + public static ItemData hireItem = new ItemData(Material.EMERALD); public static int hireOtherVillagersCosts = 1; public static String forHireTitle = "For Hire"; public static boolean hireRequireCreationPermission = true; @@ -215,26 +212,15 @@ public class Settings { /* * Currencies */ - public static Material currencyItem = Material.EMERALD; - public static String currencyItemName = ""; - public static List currencyItemLore = new ArrayList<>(0); - - public static Material zeroCurrencyItem = Material.BARRIER; - public static String zeroCurrencyItemName = ""; - public static List zeroCurrencyItemLore = new ArrayList<>(0); - - public static Material highCurrencyItem = Material.EMERALD_BLOCK; - public static String highCurrencyItemName = ""; - public static List highCurrencyItemLore = new ArrayList<>(0); + public static ItemData currencyItem = new ItemData(Material.EMERALD); + public static ItemData zeroCurrencyItem = new ItemData(Material.BARRIER); + public static ItemData highCurrencyItem = new ItemData(Material.EMERALD_BLOCK); + public static ItemData highZeroCurrencyItem = new ItemData(Material.BARRIER); // note: this can in general be larger than 64! public static int highCurrencyValue = 9; public static int highCurrencyMinCost = 20; - public static Material highZeroCurrencyItem = Material.BARRIER; - public static String highZeroCurrencyItemName = ""; - public static List highZeroCurrencyItemLore = new ArrayList<>(0); - /* * Messages */ @@ -466,13 +452,10 @@ public class Settings { public static boolean loadConfiguration(Configuration config) { boolean configChanged = false; - // perform config migrations (if the config is not empty): - if (!config.getKeys(false).isEmpty()) { - int configVersion = config.getInt("config-version", 0); // default value is important here - if (configVersion <= 0) { - migrateConfig_0_to_1(config); - configChanged = true; - } + // perform config migrations: + boolean migrated = ConfigMigrations.applyMigrations(config); + if (migrated) { + configChanged = true; } // exempt a few string / string list settings from color conversion: @@ -544,27 +527,23 @@ public class Settings { Log.warning("Config: 'gravity-chunk-range' cannot be negative."); gravityChunkRange = 0; } - if (highCurrencyValue <= 0 && highCurrencyItem != Material.AIR) { - Log.debug("Config: 'high-currency-item' disabled because of 'high-currency-value' being less than 1."); - highCurrencyItem = Material.AIR; - } // certain items cannot be of type AIR: - if (shopCreationItem == Material.AIR) { + if (shopCreationItem.getType() == Material.AIR) { Log.warning("Config: 'shop-creation-item' can not be AIR."); - shopCreationItem = Material.VILLAGER_SPAWN_EGG; + shopCreationItem = shopCreationItem.withType(Material.VILLAGER_SPAWN_EGG); } - if (hireItem == Material.AIR) { + if (hireItem.getType() == Material.AIR) { Log.warning("Config: 'hire-item' can not be AIR."); - hireItem = Material.EMERALD; + hireItem = hireItem.withType(Material.EMERALD); } - if (currencyItem == Material.AIR) { + if (currencyItem.getType() == Material.AIR) { Log.warning("Config: 'currency-item' can not be AIR."); - currencyItem = Material.EMERALD; + currencyItem = currencyItem.withType(Material.EMERALD); } if (namingOfPlayerShopsViaItem) { - if (nameItem == Material.AIR) { + if (nameItem.getType() == Material.AIR) { Log.warning("Config: 'name-item' can not be AIR if naming-of-player-shops-via-item is enabled!"); - nameItem = Material.NAME_TAG; + nameItem = nameItem.withType(Material.NAME_TAG); } } if (taxRate < 0) { @@ -575,6 +554,9 @@ public class Settings { taxRate = 100; } + // prepare derived settings: + DerivedSettings.setup(); + return configChanged; } @@ -594,12 +576,24 @@ public class Settings { return config.getBoolean(configKey); } else if (typeClass == Material.class) { // this assumes that legacy item conversion has already been performed - Material material = loadMaterial(config, configKey, false); + Material material = ConfigUtils.loadMaterial(config, configKey); if (material == null) { Log.warning("Config: Unknown material for config entry '" + configKey + "': " + config.get(configKey)); Log.warning("Config: All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html"); } return material; + } else if (typeClass == ItemData.class) { + ItemData itemData = ItemData.deserialize(config.get(configKey), (warning) -> { + Log.warning("Config: Couldn't load item data for config entry '" + configKey + "': " + warning); + if (warning.contains("Unknown item type")) { // TODO this is ugly + Log.warning("Config: All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html"); + } + }); + // normalize to not null: + if (itemData == null) { + itemData = new ItemData(Material.AIR); + } + return itemData; } else if (typeClass == List.class) { if (genericType == String.class) { List stringList = config.getStringList(configKey); @@ -616,6 +610,11 @@ public class Settings { } private static void setConfigValue(Configuration config, String configKey, List noColorConversionKeys, Class typeClass, Class genericType, Object value) { + if (value == null) { + config.set(configKey, value); // removes value + return; + } + if (typeClass == Material.class) { config.set(configKey, ((Material) value).name()); } else if (typeClass == String.class) { @@ -630,137 +629,13 @@ public class Settings { value = Utils.decolorize(ConversionUtils.toStringList((List) value)); } config.set(configKey, value); + } else if (typeClass == ItemData.class) { + config.set(configKey, ((ItemData) value).serialize()); } else { config.set(configKey, value); } } - private static Material loadMaterial(ConfigurationSection config, String key, boolean checkLegacy) { - String materialName = config.getString(key); // note: takes defaults into account - if (materialName == null) return null; - Material material = Material.matchMaterial(materialName); - if (material == null && checkLegacy) { - // check for legacy material: - String legacyMaterialName = Material.LEGACY_PREFIX + materialName; - material = Material.matchMaterial(legacyMaterialName); - } - return material; - } - - private static void migrateConfig_0_to_1(Configuration config) { - // pre 1.13 to 1.13: - Log.info("Migrating config to version 1 .."); - - // migrate shop creation item, if present: - String shopCreationItemTypeName = config.getString("shop-creation-item", null); - if (shopCreationItemTypeName != null) { - // note: this takes defaults into account: - Material shopCreationItem = loadMaterial(config, "shop-creation-item", true); - String shopCreationItemSpawnEggEntityType = config.getString("shop-creation-item-spawn-egg-entity-type"); - if (shopCreationItem == Material.LEGACY_MONSTER_EGG && !StringUtils.isEmpty(shopCreationItemSpawnEggEntityType)) { - // migrate spawn egg (ignores the data value): spawn eggs are different materials now - EntityType spawnEggEntityType = null; - try { - spawnEggEntityType = EntityType.valueOf(shopCreationItemSpawnEggEntityType); - } catch (IllegalArgumentException e) { - // unknown entity type - } - Material newShopCreationItem = LegacyConversion.fromLegacySpawnEgg(spawnEggEntityType); - - boolean usingDefault = false; - if (newShopCreationItem == null || newShopCreationItem == Material.AIR) { - // fallback to default: - newShopCreationItem = Material.VILLAGER_SPAWN_EGG; - usingDefault = true; - } - assert newShopCreationItem != null; - - Log.info(" Migrating 'shop-creation-item' from '" + shopCreationItemTypeName + "' and spawn egg entity type '" - + shopCreationItemSpawnEggEntityType + "' to '" + newShopCreationItem + "'" + (usingDefault ? " (default)" : "") + "."); - config.set("shop-creation-item", newShopCreationItem.name()); - } else { - // regular material + data value migration: - migrateLegacyItemData(config, "shop-creation-item", "shop-creation-item", "shop-creation-item-data", Material.VILLAGER_SPAWN_EGG); - } - } - - // remove shop-creation-item-spawn-egg-entity-type from config: - if (config.isSet("shop-creation-item-spawn-egg-entity-type")) { - Log.info(" Removing 'shop-creation-item-spawn-egg-entity-type' (previously '" + config.get("shop-creation-item-spawn-egg-entity-type", null) + "')."); - config.set("shop-creation-item-spawn-egg-entity-type", null); - } - - // remove shop-creation-item-data-value from config: - if (config.isSet("shop-creation-item-data")) { - Log.info(" Removing 'shop-creation-item-data' (previously '" + config.get("shop-creation-item-data", null) + "')."); - config.set("shop-creation-item-data", null); - } - - // name item: - migrateLegacyItemData(config, "name-item", "name-item", "name-item-data", Material.NAME_TAG); - - // chest item: - migrateLegacyItemData(config, "chest-item", "chest-item", "chest-item-data", Material.CHEST); - - // delete item: - migrateLegacyItemData(config, "delete-item", "delete-item", "delete-item-data", Material.BONE); - - // hire item: - migrateLegacyItemData(config, "hire-item", "hire-item", "hire-item-data", Material.EMERALD); - - // currency item: - migrateLegacyItemData(config, "currency-item", "currency-item", "currency-item-data", Material.EMERALD); - - // zero currency item: - migrateLegacyItemData(config, "zero-currency-item", "zero-currency-item", "zero-currency-item-data", Material.BARRIER); - - // high currency item: - migrateLegacyItemData(config, "high-currency-item", "high-currency-item", "high-currency-item-data", Material.EMERALD_BLOCK); - - // high zero currency item: - migrateLegacyItemData(config, "high-zero-currency-item", "high-zero-currency-item", "high-zero-currency-item-data", Material.BARRIER); - - // update config version: - config.set("config-version", 1); - Log.info("Config migration to version 1 done."); - } - - // convert legacy material + data value to new material, returns true if migrations took place - private static boolean migrateLegacyItemData(ConfigurationSection config, String migratedItemId, String itemTypeKey, String itemDataKey, Material defaultType) { - boolean migrated = false; - - // migrate material, if present: - String itemTypeName = config.getString(itemTypeKey, null); - if (itemTypeName != null) { - Material newItemType = null; - int itemData = config.getInt(itemDataKey, 0); - Material itemType = loadMaterial(config, itemTypeKey, true); - if (itemType != null) { - newItemType = LegacyConversion.fromLegacy(itemType, (byte) itemData); - } - boolean usingDefault = false; - if (newItemType == null || newItemType == Material.AIR) { - // fallback to default: - newItemType = defaultType; - usingDefault = true; - } - if (itemType != newItemType) { - Log.info(" Migrating '" + migratedItemId + "' from type '" + itemTypeName + "' and data value '" + itemData + "' to type '" - + (newItemType == null ? "" : newItemType.name()) + "'" + (usingDefault ? " (default)" : "") + "."); - config.set(itemTypeKey, (newItemType != null ? newItemType.name() : null)); - migrated = true; - } - } - - // remove data value from config: - if (config.isSet(itemDataKey)) { - Log.info(" Removing '" + itemDataKey + "' (previously '" + config.get(itemDataKey, null) + "')."); - config.set(itemDataKey, null); - migrated = true; - } - return migrated; - } - public static void loadLanguageConfiguration(Configuration config) { try { Field[] fields = Settings.class.getDeclaredFields(); @@ -789,91 +664,117 @@ public class Settings { // item utilities: + // stores derived settings that get setup after loading the config + public static class DerivedSettings { + + public static ItemData namingItemData = new ItemData(Material.AIR); + + // button items: + public static ItemData nameButtonItem = new ItemData(Material.AIR); + public static ItemData chestButtonItem = new ItemData(Material.AIR); + public static ItemData deleteButtonItem = new ItemData(Material.AIR); + public static ItemData hireButtonItem = new ItemData(Material.AIR); + + // gets called after the config has been loaded: + private static void setup() { + // ignore display name (which is used for specifying the new shopkeeper name): + namingItemData = new ItemData(ItemUtils.setItemStackName(nameItem.createItemStack(), null)); + + // button items: + nameButtonItem = new ItemData(ItemUtils.setItemStackNameAndLore(nameItem.createItemStack(), msgButtonName, msgButtonNameLore)); + chestButtonItem = new ItemData(ItemUtils.setItemStackNameAndLore(chestItem.createItemStack(), msgButtonChest, msgButtonChestLore)); + deleteButtonItem = new ItemData(ItemUtils.setItemStackNameAndLore(deleteItem.createItemStack(), msgButtonDelete, msgButtonDeleteLore)); + hireButtonItem = new ItemData(ItemUtils.setItemStackNameAndLore(hireItem.createItemStack(), msgButtonHire, msgButtonHireLore)); + } + } + // creation item: public static ItemStack createShopCreationItem() { - return ItemUtils.createItemStack(shopCreationItem, 1, shopCreationItemName, shopCreationItemLore); + return shopCreationItem.createItemStack(); } public static boolean isShopCreationItem(ItemStack item) { - return ItemUtils.isSimilar(item, Settings.shopCreationItem, Settings.shopCreationItemName, Settings.shopCreationItemLore); + return shopCreationItem.matches(item); } // naming item: - public static ItemStack createNameButtonItem() { - return ItemUtils.createItemStack(nameItem, 1, msgButtonName, msgButtonNameLore); + public static boolean isNamingItem(ItemStack item) { + return DerivedSettings.namingItemData.matches(item); } - public static boolean isNamingItem(ItemStack item) { - return ItemUtils.isSimilar(item, nameItem, null, Settings.nameItemLore); + public static ItemStack createNameButtonItem() { + return DerivedSettings.nameButtonItem.createItemStack(); } // chest button: public static ItemStack createChestButtonItem() { - return ItemUtils.createItemStack(chestItem, 1, msgButtonChest, msgButtonChestLore); + return DerivedSettings.chestButtonItem.createItemStack(); } // delete button: public static ItemStack createDeleteButtonItem() { - return ItemUtils.createItemStack(deleteItem, 1, msgButtonDelete, msgButtonDeleteLore); + return DerivedSettings.deleteButtonItem.createItemStack(); } // hire item: public static ItemStack createHireButtonItem() { - return ItemUtils.createItemStack(hireItem, 1, msgButtonHire, msgButtonHireLore); + return DerivedSettings.hireButtonItem.createItemStack(); } public static boolean isHireItem(ItemStack item) { - return ItemUtils.isSimilar(item, hireItem, hireItemName, hireItemLore); + return hireItem.matches(item); } + // CURRENCY + // currency item: public static ItemStack createCurrencyItem(int amount) { - return ItemUtils.createItemStack(Settings.currencyItem, amount, Settings.currencyItemName, Settings.currencyItemLore); + return currencyItem.createItemStack(amount); } public static boolean isCurrencyItem(ItemStack item) { - return ItemUtils.isSimilar(item, Settings.currencyItem, Settings.currencyItemName, Settings.currencyItemLore); + return currencyItem.matches(item); } // high currency item: public static boolean isHighCurrencyEnabled() { - return (Settings.highCurrencyItem != Material.AIR); + return (highCurrencyValue > 0 && highCurrencyItem.getType() != Material.AIR); } public static ItemStack createHighCurrencyItem(int amount) { if (!isHighCurrencyEnabled()) return null; - return ItemUtils.createItemStack(Settings.highCurrencyItem, amount, Settings.highCurrencyItemName, Settings.highCurrencyItemLore); + return highCurrencyItem.createItemStack(amount); } public static boolean isHighCurrencyItem(ItemStack item) { if (!isHighCurrencyEnabled()) return false; - return ItemUtils.isSimilar(item, Settings.highCurrencyItem, Settings.highCurrencyItemName, Settings.highCurrencyItemLore); + return highCurrencyItem.matches(item); } // zero currency item: public static ItemStack createZeroCurrencyItem() { - if (Settings.zeroCurrencyItem == Material.AIR) return null; - return ItemUtils.createItemStack(Settings.zeroCurrencyItem, 1, Settings.zeroCurrencyItemName, Settings.zeroCurrencyItemLore); + if (zeroCurrencyItem.getType() == Material.AIR) return null; + return zeroCurrencyItem.createItemStack(); } public static boolean isZeroCurrencyItem(ItemStack item) { - if (Settings.zeroCurrencyItem == Material.AIR) { + if (zeroCurrencyItem.getType() == Material.AIR) { return ItemUtils.isEmpty(item); } - return ItemUtils.isSimilar(item, Settings.zeroCurrencyItem, Settings.zeroCurrencyItemName, Settings.zeroCurrencyItemLore); + return zeroCurrencyItem.matches(item); } // high zero currency item: public static ItemStack createHighZeroCurrencyItem() { - if (Settings.highZeroCurrencyItem == Material.AIR) return null; - return ItemUtils.createItemStack(Settings.highZeroCurrencyItem, 1, Settings.highZeroCurrencyItemName, Settings.highZeroCurrencyItemLore); + if (highZeroCurrencyItem.getType() == Material.AIR) return null; + return highZeroCurrencyItem.createItemStack(); } public static boolean isHighZeroCurrencyItem(ItemStack item) { - if (Settings.highZeroCurrencyItem == Material.AIR) { + if (highZeroCurrencyItem.getType() == Material.AIR) { return ItemUtils.isEmpty(item); } - return ItemUtils.isSimilar(item, Settings.highZeroCurrencyItem, Settings.highZeroCurrencyItemName, Settings.highZeroCurrencyItemLore); + return highZeroCurrencyItem.matches(item); } // diff --git a/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration.java b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration.java new file mode 100644 index 00000000..9fc124d3 --- /dev/null +++ b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration.java @@ -0,0 +1,8 @@ +package com.nisovin.shopkeepers.config.migration; + +import org.bukkit.configuration.Configuration; + +public interface ConfigMigration { + + public void apply(Configuration config); +} diff --git a/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration1.java b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration1.java new file mode 100644 index 00000000..f3b2d1f5 --- /dev/null +++ b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration1.java @@ -0,0 +1,125 @@ +package com.nisovin.shopkeepers.config.migration; + +import org.bukkit.Material; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; + +import com.nisovin.shopkeepers.util.ConfigUtils; +import com.nisovin.shopkeepers.util.Log; +import com.nisovin.shopkeepers.util.StringUtils; + +/** + * Migrate the config from version <= 0 to version 1. + */ +public class ConfigMigration1 implements ConfigMigration { + + @Override + public void apply(Configuration config) { + // pre 1.13 to 1.13: + // migrate shop creation item, if present: + String shopCreationItemTypeName = config.getString("shop-creation-item", null); + if (shopCreationItemTypeName != null) { + // note: this takes defaults into account: + Material shopCreationItem = ConfigUtils.loadMaterial(config, "shop-creation-item", true); + String shopCreationItemSpawnEggEntityType = config.getString("shop-creation-item-spawn-egg-entity-type"); + if (shopCreationItem == Material.LEGACY_MONSTER_EGG && !StringUtils.isEmpty(shopCreationItemSpawnEggEntityType)) { + // migrate spawn egg (ignores the data value): spawn eggs are different materials now + EntityType spawnEggEntityType = null; + try { + spawnEggEntityType = EntityType.valueOf(shopCreationItemSpawnEggEntityType); + } catch (IllegalArgumentException e) { + // unknown entity type + } + Material newShopCreationItem = LegacyConversion.fromLegacySpawnEgg(spawnEggEntityType); + + boolean usingDefault = false; + if (newShopCreationItem == null || newShopCreationItem == Material.AIR) { + // fallback to default: + newShopCreationItem = Material.VILLAGER_SPAWN_EGG; + usingDefault = true; + } + assert newShopCreationItem != null; + + Log.info(" Migrating 'shop-creation-item' from '" + shopCreationItemTypeName + "' and spawn egg entity type '" + + shopCreationItemSpawnEggEntityType + "' to '" + newShopCreationItem + "'" + (usingDefault ? " (default)" : "") + "."); + config.set("shop-creation-item", newShopCreationItem.name()); + } else { + // regular material + data value migration: + migrateLegacyItemData(config, "shop-creation-item", "shop-creation-item", "shop-creation-item-data", Material.VILLAGER_SPAWN_EGG); + } + } + + // remove shop-creation-item-spawn-egg-entity-type from config: + if (config.isSet("shop-creation-item-spawn-egg-entity-type")) { + Log.info(" Removing 'shop-creation-item-spawn-egg-entity-type' (previously '" + config.get("shop-creation-item-spawn-egg-entity-type", null) + "')."); + config.set("shop-creation-item-spawn-egg-entity-type", null); + } + + // remove shop-creation-item-data-value from config: + if (config.isSet("shop-creation-item-data")) { + Log.info(" Removing 'shop-creation-item-data' (previously '" + config.get("shop-creation-item-data", null) + "')."); + config.set("shop-creation-item-data", null); + } + + // name item: + migrateLegacyItemData(config, "name-item", "name-item", "name-item-data", Material.NAME_TAG); + + // chest item: + migrateLegacyItemData(config, "chest-item", "chest-item", "chest-item-data", Material.CHEST); + + // delete item: + migrateLegacyItemData(config, "delete-item", "delete-item", "delete-item-data", Material.BONE); + + // hire item: + migrateLegacyItemData(config, "hire-item", "hire-item", "hire-item-data", Material.EMERALD); + + // currency item: + migrateLegacyItemData(config, "currency-item", "currency-item", "currency-item-data", Material.EMERALD); + + // zero currency item: + migrateLegacyItemData(config, "zero-currency-item", "zero-currency-item", "zero-currency-item-data", Material.BARRIER); + + // high currency item: + migrateLegacyItemData(config, "high-currency-item", "high-currency-item", "high-currency-item-data", Material.EMERALD_BLOCK); + + // high zero currency item: + migrateLegacyItemData(config, "high-zero-currency-item", "high-zero-currency-item", "high-zero-currency-item-data", Material.BARRIER); + } + + // convert legacy material + data value to new material, returns true if migrations took place + private static boolean migrateLegacyItemData(ConfigurationSection config, String migratedItemId, String itemTypeKey, String itemDataKey, Material defaultType) { + boolean migrated = false; + + // migrate material, if present: + String itemTypeName = config.getString(itemTypeKey, null); + if (itemTypeName != null) { + Material newItemType = null; + int itemData = config.getInt(itemDataKey, 0); + Material itemType = ConfigUtils.loadMaterial(config, itemTypeKey, true); + if (itemType != null) { + newItemType = LegacyConversion.fromLegacy(itemType, (byte) itemData); + } + boolean usingDefault = false; + if (newItemType == null || newItemType == Material.AIR) { + // fallback to default: + newItemType = defaultType; + usingDefault = true; + } + if (itemType != newItemType) { + Log.info(" Migrating '" + migratedItemId + "' from type '" + itemTypeName + "' and data value '" + itemData + "' to type '" + + (newItemType == null ? "" : newItemType.name()) + "'" + (usingDefault ? " (default)" : "") + "."); + config.set(itemTypeKey, (newItemType != null ? newItemType.name() : null)); + migrated = true; + } + } + + // remove data value from config: + if (config.isSet(itemDataKey)) { + Log.info(" Removing '" + itemDataKey + "' (previously '" + config.get(itemDataKey, null) + "')."); + config.set(itemDataKey, null); + migrated = true; + } + return migrated; + } +} diff --git a/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration2.java b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration2.java new file mode 100644 index 00000000..7a4e9a4d --- /dev/null +++ b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigration2.java @@ -0,0 +1,106 @@ +package com.nisovin.shopkeepers.config.migration; + +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.configuration.Configuration; + +import com.nisovin.shopkeepers.util.ConfigUtils; +import com.nisovin.shopkeepers.util.ItemData; +import com.nisovin.shopkeepers.util.Log; +import com.nisovin.shopkeepers.util.StringUtils; +import com.nisovin.shopkeepers.util.Utils; + +/** + * Migrate the config from version 1 to version 2. + */ +public class ConfigMigration2 implements ConfigMigration { + + @Override + public void apply(Configuration config) { + // Convert item data settings to ItemData: + // Due to the compact representation of ItemData, this is only required for items which previously supported + // further data (such as custom display name and/or lore). + + // shop-creation-item: + migrateItem(config, "shop-creation-item", "shop-creation-item-name", "shop-creation-item-lore"); + // name-item: + migrateItem(config, "name-item", null, "name-item-lore"); + // hire-item: + migrateItem(config, "hire-item", "hire-item-name", "hire-item-lore"); + // currency-item: + migrateItem(config, "currency-item", "currency-item-name", "currency-item-lore"); + // zero-currency-item: + migrateItem(config, "zero-currency-item", "zero-currency-item-name", "zero-currency-item-lore"); + // high-currency-item + migrateItem(config, "high-currency-item", "high-currency-item-name", "high-currency-item-lore"); + // zero-high-currency-item: + migrateItem(config, "zero-high-currency-item", "zero-high-currency-item-name", "zero-high-currency-item-lore"); + } + + // displayNameKey and loreKey can be null if they don't exist + private static void migrateItem(Configuration config, String itemTypeKey, String displayNameKey, String loreKey) { + assert config != null && itemTypeKey != null; + StringBuilder msgBuilder = new StringBuilder(); + msgBuilder.append(" Migrating item data for '") + .append(itemTypeKey) + .append("' (") + .append(config.get(itemTypeKey)) + .append(")"); + if (displayNameKey != null) { + msgBuilder.append(" and '") + .append(displayNameKey) + .append("' (") + .append(config.get(displayNameKey)) + .append(")"); + } + if (loreKey != null) { + msgBuilder.append(" and '") + .append(loreKey) + .append("' (") + .append(config.get(loreKey)) + .append(")"); + } + msgBuilder.append(" to new format."); + Log.info(msgBuilder.toString()); + + // item type: + Material itemType = ConfigUtils.loadMaterial(config, itemTypeKey); + if (itemType == null) { + Log.warning(" Skipping migration for item '" + itemTypeKey + "'! Unknown material: " + config.get(itemTypeKey)); + return; + } + + // display name: + String displayName = null; + if (displayNameKey != null) { + displayName = Utils.colorize(config.getString(displayNameKey)); + if (StringUtils.isEmpty(displayName)) { + displayName = null; // normalize empty display name to null + } + } + // lore: + List lore = null; + if (loreKey != null) { + lore = Utils.colorize(config.getStringList(loreKey)); + if (lore == null || lore.isEmpty()) { + lore = null; // normalize empty lore to null + } + } + + // create ItemData: + ItemData itemData = new ItemData(itemType, displayName, lore); + + // remove old data: + config.set(itemTypeKey, null); + if (displayNameKey != null) { + config.set(displayNameKey, null); + } + if (loreKey != null) { + config.set(loreKey, null); + } + + // save new data (under previous itemTypeKey): + config.set(itemTypeKey, itemData.serialize()); + } +} diff --git a/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigrations.java b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigrations.java new file mode 100644 index 00000000..be45452d --- /dev/null +++ b/src/main/java/com/nisovin/shopkeepers/config/migration/ConfigMigrations.java @@ -0,0 +1,43 @@ +package com.nisovin.shopkeepers.config.migration; + +import java.util.Arrays; +import java.util.List; + +import org.bukkit.configuration.Configuration; + +import com.nisovin.shopkeepers.util.Log; + +public class ConfigMigrations { + + private static final String CONFIG_VERSION_KEY = "config-version"; + // each index corresponds to a source config version and its migration to the next version + private static final List migrations = Arrays.asList(new ConfigMigration1(), new ConfigMigration2()); + + // returns true if any migrations got applied (if the config has potentially been modified) + public static boolean applyMigrations(Configuration config) { + // no migrations are required if the config is empty (missing entries will get generated from defaults): + if (config.getKeys(false).isEmpty()) return false; + + boolean migrated = false; + int configVersion = config.getInt(CONFIG_VERSION_KEY, 0); // default value is important here + if (configVersion < 0) { + configVersion = 0; // first version is 0 + } + for (int version = configVersion; version < migrations.size(); ++version) { + int nextVersion = (version + 1); + ConfigMigration migration = migrations.get(version); + Log.info("Migrating config from version " + version + " to version " + nextVersion + " .."); + if (migration != null) { + migration.apply(config); + } + // update config version: + config.set(CONFIG_VERSION_KEY, nextVersion); + migrated = true; + Log.info("Config migrated to version " + nextVersion + "." + (migration == null ? " (skipped)" : "")); + } + return migrated; + } + + private ConfigMigrations() { + } +} diff --git a/src/main/java/com/nisovin/shopkeepers/LegacyConversion.java b/src/main/java/com/nisovin/shopkeepers/config/migration/LegacyConversion.java similarity index 98% rename from src/main/java/com/nisovin/shopkeepers/LegacyConversion.java rename to src/main/java/com/nisovin/shopkeepers/config/migration/LegacyConversion.java index f0f78137..f0a92cde 100644 --- a/src/main/java/com/nisovin/shopkeepers/LegacyConversion.java +++ b/src/main/java/com/nisovin/shopkeepers/config/migration/LegacyConversion.java @@ -1,4 +1,4 @@ -package com.nisovin.shopkeepers; +package com.nisovin.shopkeepers.config.migration; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/AbstractPlayerShopkeeper.java b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/AbstractPlayerShopkeeper.java index e2784b42..3060aa61 100644 --- a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/AbstractPlayerShopkeeper.java +++ b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/AbstractPlayerShopkeeper.java @@ -147,13 +147,9 @@ public abstract class AbstractPlayerShopkeeper extends AbstractShopkeeper implem PlayerShopEditorHandler editorHandler = (PlayerShopEditorHandler) this.getUIHandler(DefaultUITypes.EDITOR()); if (editorHandler.canOpen(player)) { // rename with the player's item in hand: - String newName; - ItemMeta itemMeta; - if (!itemInMainHand.hasItemMeta() || (itemMeta = itemInMainHand.getItemMeta()) == null || !itemMeta.hasDisplayName()) { - newName = ""; - } else { - newName = itemMeta.getDisplayName(); - } + ItemMeta itemMeta = itemInMainHand.getItemMeta(); // can be null + String newName = (itemMeta != null && itemMeta.hasDisplayName()) ? itemMeta.getDisplayName() : ""; + assert newName != null; // ItemMeta#getDisplayName returns non-null in all cases // handled name changing: if (SKShopkeepersPlugin.getInstance().getShopkeeperNaming().requestNameChange(player, this, newName)) { @@ -281,7 +277,7 @@ public abstract class AbstractPlayerShopkeeper extends AbstractShopkeeper implem ItemStack item2 = null; if (Settings.isHighCurrencyEnabled() && price > Settings.highCurrencyMinCost) { - int highCurrencyAmount = Math.min(price / Settings.highCurrencyValue, Settings.highCurrencyItem.getMaxStackSize()); + int highCurrencyAmount = Math.min(price / Settings.highCurrencyValue, Settings.highCurrencyItem.getType().getMaxStackSize()); if (highCurrencyAmount > 0) { remainingPrice -= (highCurrencyAmount * Settings.highCurrencyValue); ItemStack highCurrencyItem = Settings.createHighCurrencyItem(highCurrencyAmount); @@ -290,7 +286,7 @@ public abstract class AbstractPlayerShopkeeper extends AbstractShopkeeper implem } if (remainingPrice > 0) { - if (remainingPrice > Settings.currencyItem.getMaxStackSize()) { + if (remainingPrice > Settings.currencyItem.getType().getMaxStackSize()) { // cannot represent this price with the used currency items: Log.warning("Shopkeeper at " + this.getPositionString() + " owned by " + ownerName + " has an invalid cost!"); return null; @@ -309,7 +305,7 @@ public abstract class AbstractPlayerShopkeeper extends AbstractShopkeeper implem // returns null (and logs a warning) if the price cannot be represented correctly by currency items protected TradingRecipe createBuyingRecipe(ItemStack itemBeingBought, int price, boolean outOfStock) { - if (price > Settings.currencyItem.getMaxStackSize()) { + if (price > Settings.currencyItem.getType().getMaxStackSize()) { // cannot represent this price with the used currency items: Log.warning("Shopkeeper at " + this.getPositionString() + " owned by " + ownerName + " has an invalid cost!"); return null; diff --git a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/PlayerShopEditorHandler.java b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/PlayerShopEditorHandler.java index 4488bfca..493e1f29 100644 --- a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/PlayerShopEditorHandler.java +++ b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/PlayerShopEditorHandler.java @@ -134,7 +134,7 @@ public abstract class PlayerShopEditorHandler extends EditorHandler { if (Settings.isHighCurrencyEnabled()) { int highCost = 0; if (remainingCost > Settings.highCurrencyMinCost) { - highCost = Math.min((remainingCost / Settings.highCurrencyValue), Settings.highCurrencyItem.getMaxStackSize()); + highCost = Math.min((remainingCost / Settings.highCurrencyValue), Settings.highCurrencyItem.getType().getMaxStackSize()); } if (highCost > 0) { remainingCost -= (highCost * Settings.highCurrencyValue); @@ -144,7 +144,7 @@ public abstract class PlayerShopEditorHandler extends EditorHandler { } } if (remainingCost > 0) { - if (remainingCost <= Settings.currencyItem.getMaxStackSize()) { + if (remainingCost <= Settings.currencyItem.getType().getMaxStackSize()) { lowCostItem = Settings.createCurrencyItem(remainingCost); } else { // cost is to large to represent: reset cost to zero: @@ -165,10 +165,10 @@ public abstract class PlayerShopEditorHandler extends EditorHandler { ItemStack lowCostItem = recipe.getItem1(); ItemStack highCostItem = recipe.getItem2(); int price = 0; - if (lowCostItem != null && lowCostItem.getType() == Settings.currencyItem && lowCostItem.getAmount() > 0) { + if (lowCostItem != null && lowCostItem.getType() == Settings.currencyItem.getType() && lowCostItem.getAmount() > 0) { price += lowCostItem.getAmount(); } - if (Settings.isHighCurrencyEnabled() && highCostItem != null && highCostItem.getType() == Settings.highCurrencyItem && highCostItem.getAmount() > 0) { + if (Settings.isHighCurrencyEnabled() && highCostItem != null && highCostItem.getType() == Settings.highCurrencyItem.getType() && highCostItem.getAmount() > 0) { price += (highCostItem.getAmount() * Settings.highCurrencyValue); } return price; diff --git a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/book/SKBookPlayerShopkeeper.java b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/book/SKBookPlayerShopkeeper.java index 36b638c8..e89af7b2 100644 --- a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/book/SKBookPlayerShopkeeper.java +++ b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/book/SKBookPlayerShopkeeper.java @@ -147,9 +147,7 @@ public class SKBookPlayerShopkeeper extends AbstractPlayerShopkeeper implements protected static BookMeta getBookMeta(ItemStack item) { if (ItemUtils.isEmpty(item)) return null; if (item.getType() != Material.WRITTEN_BOOK) return null; - if (!item.hasItemMeta()) return null; - - return (BookMeta) item.getItemMeta(); + return (BookMeta) item.getItemMeta(); // can be null } protected static Generation getBookGeneration(ItemStack item) { diff --git a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopEditorHandler.java b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopEditorHandler.java index 974de377..583717b0 100644 --- a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopEditorHandler.java +++ b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopEditorHandler.java @@ -73,7 +73,7 @@ public class BuyingPlayerShopEditorHandler extends PlayerShopEditorHandler { ItemStack priceItem = recipe.getResultItem(); assert priceItem != null; - if (priceItem.getType() != Settings.currencyItem) return; // checking this just in case + if (priceItem.getType() != Settings.currencyItem.getType()) return; // checking this just in case assert priceItem.getAmount() > 0; SKBuyingPlayerShopkeeper shopkeeper = this.getShopkeeper(); diff --git a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopTradingHandler.java b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopTradingHandler.java index 9e1f2a70..fa93bf57 100644 --- a/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopTradingHandler.java +++ b/src/main/java/com/nisovin/shopkeepers/shopkeeper/player/buy/BuyingPlayerShopTradingHandler.java @@ -162,7 +162,7 @@ public class BuyingPlayerShopTradingHandler extends PlayerShopTradingHandler { // add the remaining change into empty slots (all partial slots have already been cleared above): // TODO this could probably be replaced with Utils.addItems - int maxStackSize = Settings.currencyItem.getMaxStackSize(); + int maxStackSize = Settings.currencyItem.getType().getMaxStackSize(); for (int slot = 0; slot < contents.length; slot++) { ItemStack itemStack = contents[slot]; if (!ItemUtils.isEmpty(itemStack)) continue; diff --git a/src/main/java/com/nisovin/shopkeepers/ui/defaults/EditorHandler.java b/src/main/java/com/nisovin/shopkeepers/ui/defaults/EditorHandler.java index a4c7b5e1..f458dcc3 100644 --- a/src/main/java/com/nisovin/shopkeepers/ui/defaults/EditorHandler.java +++ b/src/main/java/com/nisovin/shopkeepers/ui/defaults/EditorHandler.java @@ -344,7 +344,7 @@ public abstract class EditorHandler extends UIHandler { "{prev_page}", prevPage, "{page}", String.valueOf(page), "{max_page}", String.valueOf(TRADES_MAX_PAGES)); - return ItemUtils.createItemStack(Settings.previousPageItem, 1, itemName, Settings.msgButtonPreviousPageLore); + return ItemUtils.setItemStackNameAndLore(Settings.previousPageItem.createItemStack(), itemName, Settings.msgButtonPreviousPageLore); } protected ItemStack createNextPageIcon(int page) { @@ -356,21 +356,21 @@ public abstract class EditorHandler extends UIHandler { "{next_page}", nextPage, "{page}", String.valueOf(page), "{max_page}", String.valueOf(TRADES_MAX_PAGES)); - return ItemUtils.createItemStack(Settings.nextPageItem, 1, itemName, Settings.msgButtonNextPageLore); + return ItemUtils.setItemStackNameAndLore(Settings.nextPageItem.createItemStack(), itemName, Settings.msgButtonNextPageLore); } protected ItemStack createCurrentPageIcon(int page) { String itemName = Utils.replaceArgs(Settings.msgButtonCurrentPage, "{page}", String.valueOf(page), "{max_page}", String.valueOf(TRADES_MAX_PAGES)); - return ItemUtils.createItemStack(Settings.currentPageItem, page, itemName, Settings.msgButtonCurrentPageLore); + return ItemUtils.setItemStackNameAndLore(Settings.currentPageItem.createItemStack(), itemName, Settings.msgButtonCurrentPageLore); } protected ItemStack createTradeSetupIcon() { ShopType shopType = this.getShopkeeper().getType(); String itemName = Utils.replaceArgs(Settings.msgTradeSetupDescHeader, "{shopType}", shopType.getDisplayName()); - return ItemUtils.createItemStack(Settings.tradeSetupItem, 1, itemName, shopType.getTradeSetupDescription()); + return ItemUtils.setItemStackNameAndLore(Settings.tradeSetupItem.createItemStack(), itemName, shopType.getTradeSetupDescription()); } private final List