package net.knarcraft.blacksmith.container; import net.knarcraft.blacksmith.BlacksmithPlugin; import net.knarcraft.blacksmith.manager.EconomyManager; import net.md_5.bungee.api.ChatColor; import org.bukkit.NamespacedKey; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; /** * The cost of performing an action * * @param monetaryCost

The monetary cost of the action

* @param expCost

The experience cost of the action

* @param itemCost

The item-based cost of the action

* @param requiredPermissions

The permission required for the action

*/ public record ActionCost(double monetaryCost, int expCost, @Nullable ItemStack itemCost, @NotNull Set requiredPermissions) implements ConfigurationSerializable { /** * Changes the monetary cost of this action * * @param monetaryCost

The new monetary cost

* @return

The resulting action cost

*/ public ActionCost changeMonetaryCost(double monetaryCost) { return new ActionCost(monetaryCost, this.expCost, this.itemCost, this.requiredPermissions); } /** * Changes the experience cost of this action * * @param expCost

The new experience cost

* @return

The resulting action cost

*/ public ActionCost changeExpCost(int expCost) { return new ActionCost(this.monetaryCost, expCost, this.itemCost, this.requiredPermissions); } /** * Changes the item cost of this action * * @param itemCost

The new item cost

* @return

The resulting action cost

*/ public ActionCost changeItemCost(@Nullable ItemStack itemCost) { return new ActionCost(this.monetaryCost, this.expCost, itemCost, this.requiredPermissions); } /** * Changes the permission cost of this action * * @param requiredPermissions

The new permission cost

* @return

The resulting action cost

*/ public ActionCost changePermissionCost(@NotNull Set requiredPermissions) { return new ActionCost(this.monetaryCost, this.expCost, this.itemCost, requiredPermissions); } /** * Displays this action cost as a string * * @param player

The player to calculate the cost for

* @return

The string representation of this action cost

*/ @NotNull public String displayCost(@NotNull Player player) { StringBuilder builder = new StringBuilder(); if (monetaryCost > 0) { builder.append(EconomyManager.format(monetaryCost)).append(", ").append("\n"); } if (expCost > 0) { builder.append(expCost).append("exp, ").append("\n"); } if (itemCost != null) { NamespacedKey itemName = itemCost.getType().getKeyOrNull(); if (itemName != null) { builder.append(itemCost.getAmount()).append(" x ").append(itemName); ItemMeta itemMeta = itemCost.getItemMeta(); if (itemMeta != null && itemMeta.hasDisplayName()) { builder.append("(").append(itemMeta.getDisplayName()).append(")"); } if (itemMeta != null && itemMeta.hasLore() && itemMeta.getLore() != null) { for (String lore : itemMeta.getLore()) { builder.append("\n").append(lore); } } builder.append("\n"); } } if (!requiredPermissions().isEmpty()) { for (String permission : requiredPermissions()) { if (player.hasPermission(permission)) { builder.append(ChatColor.DARK_GREEN).append("O ").append(permission).append("\n"); } else { builder.append(ChatColor.DARK_RED).append("X ").append(permission).append("\n"); } } } return builder.toString(); } /** * Checks whether the given player is able to pay this action cost * * @param player

The player to check

* @return

True if the player is able to pay

*/ public boolean canPay(@NotNull Player player) { for (String permission : this.requiredPermissions) { if (!player.hasPermission(permission)) { return false; } } if (player.getExp() < this.expCost || !EconomyManager.hasEnough(player, this.monetaryCost)) { return false; } return hasEnoughValidItemsInInventory(player); } /** * Takes exp, money and items from the player, according to this cost * * @param player

The player to take the payment from

*/ public void takePayment(@NotNull Player player) { player.giveExp(-expCost); EconomyManager.withdraw(player, monetaryCost); takeItemCost(player); } /** * Checks whether the given player has enough items specified as the item cost * * @param player

The player to check

* @return

True if the player has enough items in their inventory

*/ private boolean hasEnoughValidItemsInInventory(@NotNull Player player) { if (this.itemCost == null) { return true; } int amountInInventory = 0; for (Map.Entry entry : getValidItems(player).entrySet()) { amountInInventory += entry.getValue(); } return this.itemCost.getAmount() >= amountInInventory; } /** * Gets all items in a player's inventory equal to the specified item * * @param player

The player to get valid items for

* @return

All valid items in the format: Inventory id -> Amount

*/ @NotNull private Map getValidItems(@NotNull Player player) { if (this.itemCost == null) { return new HashMap<>(); } PlayerInventory playerInventory = player.getInventory(); ItemMeta targetMeta = this.itemCost.getItemMeta(); String displayName = null; List lore = null; if (targetMeta != null) { displayName = targetMeta.hasDisplayName() ? targetMeta.getDisplayName() : null; lore = targetMeta.hasLore() ? targetMeta.getLore() : null; } Map output = new HashMap<>(); HashMap itemsOfType = playerInventory.all(this.itemCost.getType()); for (Map.Entry entry : itemsOfType.entrySet()) { if (targetMeta != null) { // Only consider item if the name and lore is the same ItemMeta meta = entry.getValue().getItemMeta(); if (meta == null || (displayName != null && (!meta.hasDisplayName() || !meta.getDisplayName().equals(displayName)) || lore != null && (!meta.hasLore() || meta.getLore() == null || !meta.getLore().equals(lore)))) { continue; } } output.put(entry.getKey(), entry.getValue().getAmount()); } return output; } /** * Takes the amount of items specified as the cost for this action * * @param player

The player to take the items from

*/ private void takeItemCost(@NotNull Player player) { if (this.itemCost == null) { return; } int clearedAmount = 0; for (Map.Entry entry : getValidItems(player).entrySet()) { int inventory = entry.getKey(); int amount = entry.getValue(); if (amount <= this.itemCost.getAmount() - clearedAmount) { clearedAmount += amount; player.getInventory().clear(entry.getKey()); } else { clearedAmount = this.itemCost.getAmount(); ItemStack item = player.getInventory().getItem(inventory); if (item != null) { item.setAmount(amount - clearedAmount); } else { BlacksmithPlugin.error("An item changed after calculating item cost. Was unable to take " + amount + " items"); } } if (clearedAmount >= this.itemCost.getAmount()) { break; } } } /** * Loads an action cost from a configuration section * * @param configurationSection

The configuration section to load from

* @param key

The key of the cost to load

* @return

The loaded cost

*/ private static ActionCost loadActionCost(@NotNull ConfigurationSection configurationSection, @NotNull String key) { double cost = configurationSection.getDouble(key, Double.MIN_VALUE); if (cost != Double.MIN_VALUE) { return new ActionCost(cost, 0, null, Set.of()); } else { return (ActionCost) configurationSection.get(key); } } /** * Deserializes an action cost * * @param serialized

The serialized action cost

* @return

The deserialized action cost

*/ @SuppressWarnings({"unused", "unchecked"}) public static ActionCost deserialize(Map serialized) { double monetaryCost = (double) serialized.get("monetaryCost"); int expCost = (int) serialized.get("expCost"); ItemStack itemCost = (ItemStack) serialized.get("itemCost"); Set requiredPermissions = (Set) serialized.get("requiredPermissions"); return new ActionCost(monetaryCost, expCost, itemCost, requiredPermissions); } @Override public @NotNull Map serialize() { Map serialized = new HashMap<>(); serialized.put("monetaryCost", Optional.of(this.monetaryCost)); serialized.put("expCost", Optional.of(this.expCost)); serialized.put("itemCost", itemCost); serialized.put("requiredPermissions", this.requiredPermissions); return serialized; } }