package net.knarcraft.blacksmith.trait; import net.citizensnpcs.api.npc.NPC; import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.config.NPCSettings; import net.knarcraft.blacksmith.manager.EconomyManager; import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import java.util.Calendar; import java.util.Objects; import java.util.Random; import java.util.logging.Level; /** * A representation of the session between a player and a blacksmith */ public class ReforgeSession implements Runnable { private final BlacksmithTrait blacksmithTrait; private final Player player; private final NPC npc; private final ItemStack itemToReforge; private int taskId; private final NPCSettings config; private static final String[] enchantments = new String[Enchantment.values().length]; private static final Random random = new Random(); /** * Instantiates a new session * * @param blacksmithTrait

A reference to the blacksmith trait

* @param player

The player initiating the session

* @param npc

The Blacksmith NPC involved in the session

* @param config

The config to use for the session

*/ ReforgeSession(BlacksmithTrait blacksmithTrait, Player player, NPC npc, NPCSettings config) { this.blacksmithTrait = blacksmithTrait; this.player = player; this.npc = npc; itemToReforge = player.getInventory().getItemInMainHand(); this.config = config; int i = 0; for (Enchantment enchantment : Enchantment.values()) { enchantments[i++] = enchantment.getKey().toString(); } } /** * Runs the actual re-forge which fixes the item and gives it back to the player */ @Override public void run() { player.sendMessage(reforgeItem() ? config.getSuccessMessage() : config.getFailMessage()); //Stop the re-forged item from displaying in the blacksmith's hand if (npc.getEntity() instanceof Player) { ((Player) npc.getEntity()).getInventory().setItemInMainHand(null); } else { Objects.requireNonNull(((LivingEntity) npc.getEntity()).getEquipment()).setItemInMainHand(null); } //Give the item back to the player if (!config.getDisableDelay()) { //If the player isn't online, or the player cannot fit the item, drop the item to prevent it from disappearing if (config.getDropItem() || !player.isOnline() || player.getInventory().firstEmpty() == -1) { player.getWorld().dropItemNaturally(npc.getEntity().getLocation(), itemToReforge); } else { player.getInventory().addItem(itemToReforge); } } else { //It can be assumed as this happens instantly, that the player still has the item's previous slot selected player.getInventory().setItemInMainHand(itemToReforge); } //Mark this blacksmith as available blacksmithTrait.unsetSession(); // Start cool-down Calendar wait = Calendar.getInstance(); wait.add(Calendar.SECOND, config.getReforgeCoolDown()); blacksmithTrait.addCoolDown(player.getUniqueId(), wait); } /** * Performs the actual re-forge where the item's damage is reduced * * @return

Whether the re-forge was successful. False if the blacksmith failed to fully repair the item.

*/ private boolean reforgeItem() { if (random.nextInt(100) < config.getFailChance()) { failReforge(); return false; } else { succeedReforge(); return true; } } /** * The method to run when a blacksmith successfully re-forges an item */ private void succeedReforge() { // Remove any damage done to the item updateDamage(itemToReforge, 0); // Add random enchantments int roll = random.nextInt(100); if (!(roll < config.getExtraEnchantmentChance() && itemToReforge.getEnchantments().keySet().size() < config.getMaxEnchantments())) { // Abort if randomness isn't on our side, or if max enchantments has been reached return; } // Choose a random enchantment Enchantment enchantment; int maxRetries = 100; int retries = 0; do { // Try to find a working enchantment for the re-forged item up to maxRetries times enchantment = Enchantment.getByKey(NamespacedKey.fromString(enchantments[random.nextInt(enchantments.length)])); } while ((enchantment == null || !enchantment.canEnchantItem(itemToReforge)) && (retries++ < maxRetries)); if (enchantment != null && enchantment.canEnchantItem(itemToReforge)) { int randomBound = enchantment.getMaxLevel() - enchantment.getStartLevel(); //A workaround for the random method's bound sometimes being negative if (randomBound >= 0) { itemToReforge.addEnchantment(enchantment, random.nextInt(randomBound) + enchantment.getStartLevel()); } } } /** * The method to run when a blacksmith fails re-forging an item */ private void failReforge() { // Remove or downgrade existing enchantments for (Enchantment enchantment : itemToReforge.getEnchantments().keySet()) { if (random.nextBoolean()) { itemToReforge.removeEnchantment(enchantment); } else { if (itemToReforge.getEnchantmentLevel(enchantment) > 1) { itemToReforge.removeEnchantment(enchantment); itemToReforge.addEnchantment(enchantment, 1); } } } // Damage the item short currentItemDurability = EconomyManager.getDurability(itemToReforge); short newDurability = (short) (currentItemDurability + (currentItemDurability * random.nextInt(8))); short maxDurability = itemToReforge.getType().getMaxDurability(); if (newDurability <= 0) { newDurability = (short) (maxDurability / 3); } else if (currentItemDurability + newDurability > maxDurability) { newDurability = (short) (maxDurability - random.nextInt(maxDurability - 25)); } updateDamage(itemToReforge, maxDurability - newDurability); } /** * 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) { ItemMeta meta = item.getItemMeta(); Damageable damageable = (Damageable) meta; if (damageable != null) { damageable.setDamage(newDamage); } else { BlacksmithPlugin.getInstance().getLogger().log(Level.WARNING, "Unable to change damage of " + item); } item.setItemMeta(meta); } /** * Gets whether to end the current session * *

If the player has switched their item, or the player cannot pay, this returns true.

* * @return

True if the current session should end

*/ public boolean endSession() { // Prevent player from switching items during session ItemStack itemInHand = player.getInventory().getItemInMainHand(); if (!itemToReforge.equals(itemInHand)) { player.sendMessage(config.getItemChangedMessage()); return true; } // The player is unable to pay if (!EconomyManager.canPay(player)) { player.sendMessage(config.getInsufficientFundsMessage()); return true; } return false; } /** * Gets whether the current session is still running * * @return

True if the current session is still running

*/ public boolean isRunning() { return BlacksmithPlugin.getInstance().getServer().getScheduler().isQueued(taskId); } /** * Gets whether the given player is currently in a re-forge session * * @param other

The player to check if is in session

* @return

True if the given player is in a re-forge session

*/ public boolean isInSession(Player other) { return player.getName().equals(other.getName()); } /** * Begins the actual item reforging */ public void beginReforge() { if (!config.getDisableCoolDown()) { //Finish the re-forge after a random delay between the max and min taskId = BlacksmithPlugin.getInstance().getServer().getScheduler().scheduleSyncDelayedTask( BlacksmithPlugin.getInstance(), this, (new Random().nextInt(config.getMaxReforgeDelay()) + config.getMinReforgeDelay()) * 20L); } else { //Finish the re-forge as soon as possible taskId = BlacksmithPlugin.getInstance().getServer().getScheduler().scheduleSyncDelayedTask( BlacksmithPlugin.getInstance(), this, 0); } } /** * Gets the player currently in this reforge session * * @return

The player currently in this reforge session

*/ public Player getPlayer() { return player; } }