From 33ef5577715b896ea9feee1bceb3d3cfff09a1f8 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Tue, 7 May 2024 01:45:48 +0200 Subject: [PATCH] Fixes some savage fail problems Fixes item not being returned when salvage fails Removes some redundancy when giving back items Makes extended salvage return 50% of the salvage (no items if only one item would be returned) Adds a better message when failing to salvage extended salvage items Fixes a bug in the caching of smith presets --- README.md | 61 +++++++++--------- .../blacksmith/config/SmithPreset.java | 9 +-- .../config/scrapper/ScrapperNPCSettings.java | 12 +++- .../config/scrapper/ScrapperSetting.java | 7 ++ .../blacksmith/trait/ReforgeSession.java | 41 ++---------- .../blacksmith/trait/SalvageSession.java | 64 ++++++++----------- .../blacksmith/trait/ScrapperTrait.java | 21 ++---- .../knarcraft/blacksmith/trait/Session.java | 48 ++++++++++++++ .../knarcraft/blacksmith/util/ItemHelper.java | 6 +- .../blacksmith/util/SalvageHelper.java | 30 +++++++-- src/main/resources/config.yml | 3 + 11 files changed, 170 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 4b2d63c..bf9004b 100644 --- a/README.md +++ b/README.md @@ -169,15 +169,15 @@ All currently supported presets, and available filters for each preset: ### Blacksmith global-only options -| Key | Value type | Default | Description | -|---------------------------|-------------------------|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| basePrice | positive decimal number | \[default: 10.0] | The base price which has to be paid regardless of the durability remaining for an item. Setting this without specifying a material sets the basePrice for any item the basePrice has not been set for. You can use for example "netherite*: 10" to set the value for any material beginning with "netherite". | -| pricePerDurabilityPoint | positive decimal number | \[default: 0.005] | The price added for each durability point present/missing (depends on whether natural cost is set to true or false). Setting this without specifying a material sets the pricePerDurabilityPoint for any item the pricePerDurabilityPoint has not been set for. You can use for example "netherite*: 10" to set the value for any material beginning with "netherite". | -| enchantmentCost | positive decimal number | \[default: 5] | The added cost for each level of an enchantment present on the item. The cost can be set for specific enchantments. Not specifying an enchantment sets the value for all enchantments without a set value. | -| useNaturalCost | true/false | true | If true, each missing durability will add to the cost (price = basePrice + missingDurability * pricePerDurabilityPoint + enchantmentCost). If false, durability will be used to calculate the cost instead of missingDurability (this was the behavior before natural cost was added). | -| showExactTime | true/false | false | If true, blacksmiths will display exact time remaining in minutes and seconds, instead of vague expressions. | -| chippedAnvilReforgingCost | positive decimal number | 10.0 | The price for reforging a chipped anvil (slightly damaged). No other costs apply! | -| damagedAnvilReforgingCost | positive decimal number | 20.0 | The price for reforging a damaged anvil (very damaged). No other costs apply! | +| Key | Value type | Default | Description | +|---------------------------|-------------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| basePrice | positive decimal number | default: 10.0 | The base price which has to be paid regardless of the durability remaining for an item. Setting this without specifying a material sets the basePrice for any item the basePrice has not been set for. You can use for example "netherite*: 10" to set the value for any material beginning with "netherite". | +| pricePerDurabilityPoint | positive decimal number | default: 0.005 | The price added for each durability point present/missing (depends on whether natural cost is set to true or false). Setting this without specifying a material sets the pricePerDurabilityPoint for any item the pricePerDurabilityPoint has not been set for. You can use for example "netherite*: 10" to set the value for any material beginning with "netherite". | +| enchantmentCost | positive decimal number | default: 5 | The added cost for each level of an enchantment present on the item. The cost can be set for specific enchantments. Not specifying an enchantment sets the value for all enchantments without a set value. | +| useNaturalCost | true/false | true | If true, each missing durability will add to the cost (price = basePrice + missingDurability * pricePerDurabilityPoint + enchantmentCost). If false, durability will be used to calculate the cost instead of missingDurability (this was the behavior before natural cost was added). | +| showExactTime | true/false | false | If true, blacksmiths will display exact time remaining in minutes and seconds, instead of vague expressions. | +| chippedAnvilReforgingCost | positive decimal number | 10.0 | The price for reforging a chipped anvil (slightly damaged). No other costs apply! | +| damagedAnvilReforgingCost | positive decimal number | 20.0 | The price for reforging a damaged anvil (very damaged). No other costs apply! | ### Blacksmith per-npc (with default values set in config.yml) @@ -245,27 +245,28 @@ All currently supported presets, and available filters for each preset: #### Messages -| Message Key | Default | Explanation | -|---------------------------------|-----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| -| busyPlayerMessage | &cI'm busy at the moment. Come back later! | The message displayed when the scrapper is serving another player. | -| busySalvageMessage | &cI'm working on it. Be patient! I'll finish {time}! | The message displayed when the scrapper is busy salvaging an item. | -| coolDownUnexpiredMessage | &cYou've already had your chance! Give me a break! I'll be ready {time}! | The message displayed when the player has to wait for the cool-down to expire before using the scrapper again. | -| invalidItemMessage | &cI'm sorry, but I'm a/an {title}, I don't know how to salvage that! | The message displayed when a scrapper is presented an item which it cannot salvage. | -| tooDamagedForSalvageMessage | &cThat item is too damaged to be salvaged into anything useful | The message displayed when a scrapper is presented with an item too damaged to produce salvage. | -| successSalvagedMessage | There you go! | The message displayed when a scrapper successfully repairs an item. | -| failSalvageMessage | &cWhoops! The item broke! Maybe next time? | The message displayed when a scrapper fails to salvage an item. | -| itemChangedMessage | &cThat's not the item you wanted to salvage before! | The message displayed when a player changes their item after being shown the salvage cost. | -| startSalvageMessage | &eOk, let's see what I can do... | The message displayed when a scrapper starts salvaging an item. | -| insufficientFundsMessage | &cYou don't have enough money to salvage an item! | The message displayed when a player is unable to pay for salvaging an item. | -| costMessage | &eIt will cost &a{cost}&e to salvage that &a{item}&e! {yield} &eClick again to salvage! | The message displayed when telling a player about the cost of salvaging an item. | -| costMessageArmorTrim | &eIt will cost &a{cost}&e to salvage that &a{item}&e's armor trim! | The message displayed when telling a player about the cost of salvaging an item's armor trim. | -| costMessageNetherite | &eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond! | The message displayed when telling a player about the cost of salvaging a netherite item into a diamond item. | -| fullSalvageMessage | &aI should be able to extract all components from that pristine item.&r | The message displayed as part of costMessage's yield placeholder if all components will be returned. | -| partialSalvageMessage | &cI cannot extract all components from that damaged item.&r | The message displayed as part of costMessage's yield placeholder if only some components will be returned. | -| cannotSalvageEnchantedMessage | &cI'm sorry, but I'm unable to salvage enchanted items! | The message displayed when telling a player that the scrapper cannot salvage enchanted items. | -| cannotSalvageArmorTrimMessage | &cI'm sorry, but I'm unable to salvage armor trims! | The message displayed when telling a player that the scrapper cannot salvage items with armor trim. | -| armorTrimSalvageNotFoundMessage | &cI'm sorry, but I don't know how to salvage that armor trim! | The message displayed when telling a player that the scrapper cannot find the correct items to return as armor trim salvage. | -| cannotSalvageNetheriteMessage | &cI'm sorry, but I'm unable to salvage netherite items! | The message displayed when telling a player that the scrapper cannot salvage netherite items. | +| Message Key | Default | Explanation | +|---------------------------------|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| busyPlayerMessage | &cI'm busy at the moment. Come back later! | The message displayed when the scrapper is serving another player. | +| busySalvageMessage | &cI'm working on it. Be patient! I'll finish {time}! | The message displayed when the scrapper is busy salvaging an item. | +| coolDownUnexpiredMessage | &cYou've already had your chance! Give me a break! I'll be ready {time}! | The message displayed when the player has to wait for the cool-down to expire before using the scrapper again. | +| invalidItemMessage | &cI'm sorry, but I'm a/an {title}, I don't know how to salvage that! | The message displayed when a scrapper is presented an item which it cannot salvage. | +| tooDamagedForSalvageMessage | &cThat item is too damaged to be salvaged into anything useful | The message displayed when a scrapper is presented with an item too damaged to produce salvage. | +| successSalvagedMessage | There you go! | The message displayed when a scrapper successfully repairs an item. | +| failSalvageMessage | &cWhoops! The item broke! Maybe next time? | The message displayed when a scrapper fails to salvage an item. | +| failExtendedSalvageMessage | &cWhoops! I was unable to extract much, if any, salvage! Maybe next time? | The message displayed when a scrapper fails to salvage an item without durability (only relevant is extended salvage is enabled). | +| itemChangedMessage | &cThat's not the item you wanted to salvage before! | The message displayed when a player changes their item after being shown the salvage cost. | +| startSalvageMessage | &eOk, let's see what I can do... | The message displayed when a scrapper starts salvaging an item. | +| insufficientFundsMessage | &cYou don't have enough money to salvage an item! | The message displayed when a player is unable to pay for salvaging an item. | +| costMessage | &eIt will cost &a{cost}&e to salvage that &a{item}&e! {yield} &eClick again to salvage! | The message displayed when telling a player about the cost of salvaging an item. | +| costMessageArmorTrim | &eIt will cost &a{cost}&e to salvage that &a{item}&e's armor trim! | The message displayed when telling a player about the cost of salvaging an item's armor trim. | +| costMessageNetherite | &eIt will cost &a{cost}&e to salvage that &a{item}&e into diamond! | The message displayed when telling a player about the cost of salvaging a netherite item into a diamond item. | +| fullSalvageMessage | &aI should be able to extract all components from that pristine item.&r | The message displayed as part of costMessage's yield placeholder if all components will be returned. | +| partialSalvageMessage | &cI cannot extract all components from that damaged item.&r | The message displayed as part of costMessage's yield placeholder if only some components will be returned. | +| cannotSalvageEnchantedMessage | &cI'm sorry, but I'm unable to salvage enchanted items! | The message displayed when telling a player that the scrapper cannot salvage enchanted items. | +| cannotSalvageArmorTrimMessage | &cI'm sorry, but I'm unable to salvage armor trims! | The message displayed when telling a player that the scrapper cannot salvage items with armor trim. | +| armorTrimSalvageNotFoundMessage | &cI'm sorry, but I don't know how to salvage that armor trim! | The message displayed when telling a player that the scrapper cannot find the correct items to return as armor trim salvage. This will happen if more armor trim materials are added, or the Spigot API is changed. | +| cannotSalvageNetheriteMessage | &cI'm sorry, but I'm unable to salvage netherite items! | The message displayed when telling a player that the scrapper cannot salvage netherite items. | ## Language customization diff --git a/src/main/java/net/knarcraft/blacksmith/config/SmithPreset.java b/src/main/java/net/knarcraft/blacksmith/config/SmithPreset.java index b8c8e8f..2c71b02 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/SmithPreset.java +++ b/src/main/java/net/knarcraft/blacksmith/config/SmithPreset.java @@ -46,7 +46,7 @@ public enum SmithPreset { private final SmithPresetFilter[] filters; private static final Map> presetMaterialNames = new HashMap<>(); - private static final Map> filterMaterialNames = new HashMap<>(); + private static final Map>> filterMaterialNames = new HashMap<>(); private static Set armor = null; private static Set ranged = null; private static Set weapons = null; @@ -145,8 +145,8 @@ public enum SmithPreset { materialNames = presetMaterialNames.get(preset); } } else { - if (filterMaterialNames.containsKey(filter)) { - materialNames = filterMaterialNames.get(filter); + if (filterMaterialNames.containsKey(preset) && filterMaterialNames.get(preset).containsKey(filter)) { + materialNames = filterMaterialNames.get(preset).get(filter); } } @@ -154,7 +154,8 @@ public enum SmithPreset { if (materialNames == null) { if (filter != null) { materialNames = preset.getMaterialNames(filter); - filterMaterialNames.put(filter, materialNames); + filterMaterialNames.putIfAbsent(preset, new HashMap<>()); + filterMaterialNames.get(preset).put(filter, materialNames); } else { materialNames = preset.getMaterialNames(); presetMaterialNames.put(preset, materialNames); diff --git a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java index 579b47d..436fb21 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperNPCSettings.java @@ -127,10 +127,20 @@ public class ScrapperNPCSettings implements TraitSettings { * @return

The salvage fail message

*/ @NotNull - public String getFailMessage() { + public String getFailSalvageMessage() { return asString(ScrapperSetting.FAIL_SALVAGE_MESSAGE); } + /** + * Gets the message to display when a scrapper fails to salvage an item without durability + * + * @return

The extended salvage fail message

+ */ + @NotNull + public String getFailExtendedSalvageMessage() { + return asString(ScrapperSetting.FAIL_SALVAGE_EXTENDED_MESSAGE); + } + /** * Gets the message to use for displaying salvage cost * 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 6eb3804..a315d6b 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java @@ -146,6 +146,13 @@ public enum ScrapperSetting implements Setting { "next time?", "The message to display when a scrapper fails to salvage an item because of the " + "fail chance.", true, true), + /** + * The message displayed if a salvage of an item without durability is unsuccessful + */ + FAIL_SALVAGE_EXTENDED_MESSAGE("failExtendedSalvageMessage", SettingValueType.STRING, + "&cWhoops! I was unable to extract much, if any, salvage! Maybe next time?", + "The message to display when the scrapper fails to salvage an item without durability", true, true), + /** * The message displayed if a player presents a different item after seeing the price to reforge an item */ diff --git a/src/main/java/net/knarcraft/blacksmith/trait/ReforgeSession.java b/src/main/java/net/knarcraft/blacksmith/trait/ReforgeSession.java index 4f574e0..ab72f31 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/ReforgeSession.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/ReforgeSession.java @@ -34,7 +34,6 @@ public class ReforgeSession extends Session implements Runnable { private final ItemStack itemToReforge; private final BlacksmithNPCSettings config; private static List enchantments = null; - private static final Random random = new Random(); /** * Instantiates a new session @@ -123,17 +122,8 @@ public class ReforgeSession extends Session implements Runnable { * Gives the reforged item back to the player */ private void giveReforgedItem() { - if (this.config.getMaxReforgeDelay() > 0) { - //If the player isn't online, or the player cannot fit the item, drop the item to prevent it from disappearing - if (this.config.getDropItem() || !this.player.isOnline() || this.player.getInventory().firstEmpty() == -1) { - this.player.getWorld().dropItemNaturally(this.npc.getEntity().getLocation(), this.itemToReforge); - } else { - this.player.getInventory().addItem(this.itemToReforge); - } - } else { - //It can be assumed as this happens instantly, that the player still has the item's previous slot selected - this.player.getInventory().setItemInMainHand(this.itemToReforge); - } + giveResultingItem(this.config.getMaxReforgeDelay() > 0, this.config.getDropItem(), this.npc, + this.itemToReforge); } /** @@ -156,7 +146,10 @@ public class ReforgeSession extends Session implements Runnable { */ private void succeedReforge() { // Remove any damage done to the item - updateDamage(this.itemToReforge, 0); + if (ItemHelper.updateDamage(this.itemToReforge, 0)) { + BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to update damage for " + + this.itemToReforge); + } //Replace damaged anvils with a normal anvil if (ItemHelper.isAnvil(this.itemToReforge.getType(), true)) { @@ -216,15 +209,7 @@ public class ReforgeSession extends Session implements Runnable { } //Damage the item - short currentItemDurability = ItemHelper.getDurability(this.itemToReforge); - short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8))); - short maxDurability = this.itemToReforge.getType().getMaxDurability(); - if (newDurability <= 0) { - newDurability = (short) (maxDurability / 3); - } else if (currentItemDurability + newDurability > maxDurability) { - newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); - } - updateDamage(this.itemToReforge, maxDurability - newDurability); + damageItemRandomly(this.itemToReforge); } /** @@ -243,16 +228,4 @@ public class ReforgeSession extends Session implements Runnable { } } - /** - * Updates the damage done to an item - * - * @param item

The item to update damage for

- * @param newDamage

The new damage done

- */ - private void updateDamage(ItemStack item, int newDamage) { - if (!ItemHelper.updateDamage(item, newDamage)) { - BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to change damage of " + item); - } - } - } diff --git a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java index d17837a..e127e04 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java @@ -6,19 +6,18 @@ import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.manager.EconomyManager; import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.SalvageHelper; -import org.bukkit.entity.Damageable; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Objects; import java.util.Random; import static net.knarcraft.blacksmith.formatting.BlacksmithStringFormatter.sendNPCMessage; -import static net.knarcraft.blacksmith.util.ItemHelper.updateDamage; /** * A representation of the session between a player and a scrapper @@ -30,6 +29,7 @@ public class SalvageSession extends Session implements Runnable { private final ItemStack itemToSalvage; private final ScrapperNPCSettings config; private final List salvage; + private final int itemsConsumed; private final int enchantmentLevels; protected final SalvageMethod salvageMethod; private static final Random random = new Random(); @@ -42,18 +42,20 @@ public class SalvageSession extends Session implements Runnable { * @param npc

The scrapper NPC involved in the session

* @param config

The config to use for the session

* @param salvageMethod

The salvage method performed in this session

+ * @param itemsConsumed

The number of items actually consumed, in case a salvaging fails

*/ SalvageSession(@NotNull ScrapperTrait scrapperTrait, @NotNull Player player, @NotNull NPC npc, @NotNull ScrapperNPCSettings config, @NotNull List salvage, - @NotNull SalvageMethod salvageMethod) { + @NotNull SalvageMethod salvageMethod, int itemsConsumed) { super(player); this.scrapperTrait = scrapperTrait; this.npc = npc; - this.itemToSalvage = player.getInventory().getItemInMainHand(); + this.itemToSalvage = player.getInventory().getItemInMainHand().clone(); this.config = config; this.salvage = salvage; this.enchantmentLevels = SalvageHelper.getTotalEnchantmentLevels(this.itemToSalvage); this.salvageMethod = salvageMethod; + this.itemsConsumed = itemsConsumed; } @Override @@ -88,7 +90,7 @@ public class SalvageSession extends Session implements Runnable { */ @Override public void run() { - sendNPCMessage(this.npc, this.player, salvageItem() ? this.config.getSuccessMessage() : this.config.getFailMessage()); + salvageItem(); //Stop the reforged item from displaying in the scrapper's hand if (this.npc.getEntity() instanceof Player) { @@ -108,17 +110,13 @@ public class SalvageSession extends Session implements Runnable { /** * Trues to salvage an item, and gives the return item - * - * @return

True if the salvage was successful. False otherwise.

*/ - private boolean salvageItem() { + private void salvageItem() { if (random.nextInt(100) < this.config.getFailChance()) { failSalvage(); - return false; } else { - // Give the player the result of the salvage giveSalvage(); - return true; + sendNPCMessage(this.npc, this.player, this.config.getSuccessMessage()); } } @@ -126,19 +124,22 @@ public class SalvageSession extends Session implements Runnable { * The method to run when a crapper fails salvaging an item */ private void failSalvage() { - if (this.itemToSalvage.getItemMeta() instanceof Damageable) { - //Damage the item - short currentItemDurability = ItemHelper.getDurability(this.itemToSalvage); - short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8))); - short maxDurability = this.itemToSalvage.getType().getMaxDurability(); - if (newDurability <= 0) { - newDurability = (short) (maxDurability / 3); - } else if (currentItemDurability + newDurability > maxDurability) { - newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); - } - updateDamage(this.itemToSalvage, maxDurability - newDurability); + // Make sure the given amount is the same amount taken for salvage to avoid duplication + this.itemToSalvage.setAmount(itemsConsumed); + + if (ItemHelper.getMaxDurability(this.itemToSalvage) > 0) { + //Damage the item if possible + damageItemRandomly(this.itemToSalvage); + giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, + this.itemToSalvage); + sendNPCMessage(this.npc, this.player, this.config.getFailSalvageMessage()); } else { - this.itemToSalvage.setAmount(Math.max(this.itemToSalvage.getAmount() - 1, 1)); + // Give half the salvage + List halfSalvage = SalvageHelper.pickRandomSalvage(this.salvage, new ArrayList<>(), 0.5); + for (ItemStack item : halfSalvage) { + giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, item); + } + sendNPCMessage(this.npc, this.player, this.config.getFailExtendedSalvageMessage()); } } @@ -149,21 +150,8 @@ public class SalvageSession extends Session implements Runnable { // TODO: Find a better calculation than 1 enchantment level = 1 exp level // Gives the player back some of the EXP used on an item this.player.giveExpLevels(this.enchantmentLevels); - if (this.config.getDropItem() || !this.player.isOnline() || this.player.getInventory().firstEmpty() == -1) { - // If the player isn't online, or the player cannot fit the item, drop the item to prevent it from - // disappearing, even if drop item is disabled. - for (ItemStack item : this.salvage) { - this.player.getWorld().dropItemNaturally(this.npc.getEntity().getLocation(), item); - } - } else { - for (ItemStack item : this.salvage) { - if (ItemHelper.canFitItem(this.player.getInventory(), item)) { - this.player.getInventory().addItem(item); - } else { - // If the player cannot fit all the salvage, drop it on the ground - this.player.getWorld().dropItemNaturally(this.player.getLocation(), item); - } - } + for (ItemStack item : this.salvage) { + giveResultingItem(this.config.getMaxSalvageDelay() > 0, this.config.getDropItem(), this.npc, item); } } diff --git a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java index e59ff5d..77262b9 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java @@ -80,7 +80,7 @@ public class ScrapperTrait extends CustomTrait { * @param player

The player to start the session for

*/ public void startSession(@NotNull Player player) { - ItemStack itemInHand = player.getInventory().getItemInMainHand(); + ItemStack itemInHand = player.getInventory().getItemInMainHand().clone(); List salvageAbleItems = getSettings().getSalvageAbleItems(); boolean extended = getSettings().extendedSalvageEnabled(); @@ -123,6 +123,7 @@ public class ScrapperTrait extends CustomTrait { if (salvageMethod == SalvageMethod.SALVAGE && !SalvageHelper.isSalvageable(player.getServer(), itemInHand)) { sendNPCMessage(this.npc, player, StringFormatter.replacePlaceholder(getSettings().getInvalidItemMessage(), "{title}", getSettings().getScrapperTitle())); + return; } // Check if the item is enchanted, and whether this blacksmith can salvage it @@ -142,7 +143,9 @@ public class ScrapperTrait extends CustomTrait { } //Start a new scrapper session for the player - startSession(player, salvage, salvageMethod); + currentSessionStartTime = System.currentTimeMillis(); + int itemsConsumed = SalvageHelper.getRequiredAmountForSalvage(player.getServer(), itemInHand); + session = new SalvageSession(this, player, npc, getSettings(), salvage, salvageMethod, itemsConsumed); // Print the cost to the player printCostMessage(player, itemInHand, EconomyManager.formatSalvageCost(salvageMethod), salvageMethod); } @@ -176,20 +179,6 @@ public class ScrapperTrait extends CustomTrait { } } - /** - * Starts a new scrapper session - * - * @param player

The player to start the session for

- * @param salvage

The salvage to return to the player

- * @param salvageMethod

The type of salvage performed

- */ - private void startSession(@NotNull Player player, @NotNull List salvage, - @NotNull SalvageMethod salvageMethod) { - //Start a new scrapper session for the player - currentSessionStartTime = System.currentTimeMillis(); - session = new SalvageSession(this, player, npc, getSettings(), salvage, salvageMethod); - } - @Override protected boolean showExactTime() { return BlacksmithPlugin.getInstance().getGlobalScrapperSettings().showExactTime(); diff --git a/src/main/java/net/knarcraft/blacksmith/trait/Session.java b/src/main/java/net/knarcraft/blacksmith/trait/Session.java index 91c0d70..3fcd50d 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/Session.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/Session.java @@ -1,15 +1,22 @@ package net.knarcraft.blacksmith.trait; +import net.citizensnpcs.api.npc.NPC; import net.knarcraft.blacksmith.BlacksmithPlugin; +import net.knarcraft.blacksmith.util.ItemHelper; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitScheduler; import org.jetbrains.annotations.NotNull; +import java.util.Random; +import java.util.logging.Level; + /** * A runnable session for performing a reforging/salvage task */ public abstract class Session implements Runnable { + protected static final Random random = new Random(); protected final Player player; protected long finishTime; protected int taskId; @@ -85,6 +92,47 @@ public abstract class Session implements Runnable { taskId = scheduler.scheduleSyncDelayedTask(BlacksmithPlugin.getInstance(), this, actionDelay * 20L); } + /** + * Gives the resulting item to the player + * + * @param hasDelay

Whether the session was delayed, or if the item was processed instantly

+ * @param dropItem

Whether the item should be dropped on the ground

+ * @param npc

The NPC holding the item

+ * @param itemToReturn

The item to return to the player

+ */ + protected void giveResultingItem(boolean hasDelay, boolean dropItem, @NotNull NPC npc, @NotNull ItemStack itemToReturn) { + if (hasDelay) { + //If the player isn't online, or the player cannot fit the item, drop the item to prevent it from disappearing + if (dropItem || !this.player.isOnline() || !ItemHelper.canFitItem(this.player.getInventory(), itemToReturn)) { + this.player.getWorld().dropItemNaturally(npc.getEntity().getLocation(), itemToReturn); + } else { + this.player.getInventory().addItem(itemToReturn); + } + } else { + //It can be assumed as this happens instantly, that the player still has the item's previous slot selected + this.player.getInventory().setItemInMainHand(itemToReturn); + } + } + + /** + * Randomly damages the given item + * + * @param item

The item to damage

+ */ + protected void damageItemRandomly(@NotNull ItemStack item) { + short currentItemDurability = ItemHelper.getDurability(item); + short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8))); + short maxDurability = item.getType().getMaxDurability(); + if (newDurability <= 0) { + newDurability = (short) (maxDurability / 3); + } else if (currentItemDurability + newDurability > maxDurability) { + newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); + } + if (ItemHelper.updateDamage(item, maxDurability - newDurability)) { + BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to update damage for " + item); + } + } + /** * Gets the delay for this session's action to finish * diff --git a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java index ff8f360..c0dbae6 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/ItemHelper.java @@ -83,16 +83,16 @@ public final class ItemHelper { * * @param item

The item to update damage for

* @param newDamage

The new damage done

- * @return

True if the damage was updated. False if not damageable.

+ * @return

False if the damage was updated. False if not damageable.

*/ public static boolean updateDamage(@NotNull ItemStack item, int newDamage) { ItemMeta meta = item.getItemMeta(); if (!(meta instanceof Damageable damageable)) { - return false; + return true; } damageable.setDamage(newDamage); item.setItemMeta(meta); - return true; + return false; } /** diff --git a/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java b/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java index 71ad1bd..31a48c1 100644 --- a/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java +++ b/src/main/java/net/knarcraft/blacksmith/util/SalvageHelper.java @@ -246,13 +246,32 @@ public final class SalvageHelper { } double percentageRemaining = (double) durability / maxDurability; + return pickRandomSalvage(recipeItems, trashSalvage, percentageRemaining); + } - int totalItems = totalItemAmount(recipeItems); + /** + * Picks random salvage from the given items + * + * @param itemsToChooseFrom

The salvage to choose from

+ * @param trashSalvage

The salvage considered trash

+ * @param percentageRemaining

The percentage remaining for the salvaged item (0-1)

+ * @return

Random salvage

+ */ + @NotNull + public static List pickRandomSalvage(@NotNull List itemsToChooseFrom, + @NotNull Collection trashSalvage, + double percentageRemaining) { + // Avoid an infinite loop + if (percentageRemaining > 1 || percentageRemaining < 0) { + percentageRemaining = 1; + } + + int totalItems = totalItemAmount(itemsToChooseFrom); //Get the amount of recipe items to be returned int itemsToReturn = (int) Math.floor(percentageRemaining * totalItems); - int bound = recipeItems.size(); + int bound = itemsToChooseFrom.size(); - List goodItems = copyItems(recipeItems); + List goodItems = copyItems(itemsToChooseFrom); goodItems.removeIf((item) -> trashSalvage.contains(item.getType())); int goodSalvage = totalItemAmount(goodItems); @@ -260,7 +279,7 @@ public final class SalvageHelper { for (int i = 0; i < itemsToReturn; i++) { // Pick random item int itemIndex = SalvageHelper.random.nextInt(bound); - ItemStack itemStack = recipeItems.get(itemIndex); + ItemStack itemStack = itemsToChooseFrom.get(itemIndex); // The selected item is trash, so skip it if (trashSalvage.contains(itemStack.getType()) && i < goodSalvage) { @@ -275,7 +294,6 @@ public final class SalvageHelper { } itemStack.setAmount(itemStack.getAmount() - 1); - salvage.add(new ItemStack(itemStack.getType(), 1)); } return salvage; @@ -287,7 +305,7 @@ public final class SalvageHelper { * @param items

The items to get the sum of

* @return

The total number of items

*/ - private static int totalItemAmount(@NotNull List items) { + public static int totalItemAmount(@NotNull List items) { int sum = 0; for (ItemStack itemStack : items) { sum += itemStack.getAmount(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ac96af8..7a9a96d 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -202,6 +202,9 @@ scrapper: # The message to display when the scrapper fails to salvage an item failSalvageMessage: "&cWhoops! The item broke! Maybe next time?" + # The message to display when the scrapper fails to salvage an item without durability + failExtendedSalvageMessage: "&cWhoops! I was unable to extract much, if any, salvage! Maybe next time?" + # The message to display when presenting a different item than the one just evaluated itemChangedMessage: "&cThat's not the item you wanted to salvage before!"