requiredPermissions) implements ConfigurationSerializable {
+
+ @NotNull
+ public String displayCost() {
+ StringBuilder builder = new StringBuilder();
+ if (monetaryCost > 0) {
+ builder.append(EconomyManager.format(monetaryCost)).append(", ");
+ }
+ if (expCost > 0) {
+ builder.append(expCost).append("exp, ");
+ }
+ if (itemCost != null) {
+ // TODO: Present name, amount and name + lore
+ builder.append(itemCost);
+ }
+ // TODO: Display required permissions if the player doesn't have them?
+ return builder.toString();
+ }
/**
* Checks whether the given player is able to pay this action cost
@@ -32,68 +48,161 @@ public record ActionCost(double monetaryCost, int expCost, @Nullable ItemStack i
* @return True if the player is able to pay
*/
public boolean canPay(@NotNull Player player) {
- for (String permission : requiredPermissions) {
+ for (String permission : this.requiredPermissions) {
if (!player.hasPermission(permission)) {
return false;
}
}
- if (player.getExp() < expCost || !EconomyManager.hasEnough(player, monetaryCost)) {
+ if (player.getExp() < this.expCost || !EconomyManager.hasEnough(player, this.monetaryCost)) {
return false;
}
- return itemCost == null || player.getInventory().contains(itemCost);
- }
-
- public void takePayment(@NotNull Player player) {
- player.giveExp(-expCost);
- EconomyManager.withdraw(monetaryCost);
-
+ return hasEnoughValidItemsInInventory(player);
}
/**
- * Takes payment for printing a number of books by withdrawing the correct item
+ * Takes exp, money and items from the player, according to this cost
*
- * @param player The player which needs to pay
- * @param item The item to pay
+ * @param player The player to take the payment from
*/
- private static void payForBookPrintingItem(Player player, ItemStack item) {
+ 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 = item.getItemMeta();
+ 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;
}
-
- HashMap itemsOfType = playerInventory.all(item.getType());
+
+ 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(), (Integer) 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 : itemsOfType.entrySet()) {
- if (targetMeta == null) {
- if (Objects.requireNonNull(entry.getValue()).getAmount() <= item.getAmount() - clearedAmount) {
- clearedAmount += entry.getValue().getAmount();
- player.getInventory().clear(entry.getKey());
- } else {
- clearedAmount = item.getAmount();
- entry.getValue().setAmount(entry.getValue().getAmount() - (clearedAmount));
- }
+ 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 {
- // TODO: Only consider item if the name and lore is the same
- ItemMeta meta = entry.getValue().getItemMeta();
- if (meta == null) {
- break;
- }
- if (displayName != null && (!meta.hasDisplayName() || !meta.getDisplayName().equals(displayName))) {
- break;
+ 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 <= item.getAmount()) {
+ 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;
+ }
+
}
diff --git a/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java b/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java
index 8b6fd5c..bd8050c 100644
--- a/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java
+++ b/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java
@@ -151,6 +151,20 @@ public class EconomyManager {
}
}
+ /**
+ * Withdraws money from a player
+ *
+ * @param player The player to withdraw from
+ * @param monetaryCost The cost to withdraw
+ * @throws IllegalArgumentException If a negative cost is given
+ */
+ public static void withdraw(@NotNull Player player, double monetaryCost) {
+ if (monetaryCost < 0) {
+ throw new IllegalArgumentException("Cannot withdraw a negative amount");
+ }
+ economy.withdrawPlayer(player, monetaryCost);
+ }
+
/**
* Gets the cost of the item in the given player's main hand
*
diff --git a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java
index d5511e8..5a0c115 100644
--- a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java
+++ b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java
@@ -328,13 +328,14 @@ public class ScrapperTrait extends CustomTrait {
GlobalScrapperSettings scrapperSettings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings();
String moneyCost = EconomyManager.format(scrapperSettings.getEnchantedBookSalvageCost());
- ItemStack itemCost = scrapperSettings.
+ //ItemStack itemCost = scrapperSettings.
if (scrapperSettings.requireMoneyAndItemForEnchantedBookSalvage()) {
// TODO: Print both with a + between them (if item has a special name, use that instead of the material name)
} else {
// TODO: If the item is not null, print it, otherwise print the monetary cost
}
+ return "";
}
@Override
diff --git a/src/main/java/net/knarcraft/blacksmith/util/TabCompleteValuesHelper.java b/src/main/java/net/knarcraft/blacksmith/util/TabCompleteValuesHelper.java
index 066abe9..ea9347a 100644
--- a/src/main/java/net/knarcraft/blacksmith/util/TabCompleteValuesHelper.java
+++ b/src/main/java/net/knarcraft/blacksmith/util/TabCompleteValuesHelper.java
@@ -38,6 +38,8 @@ public final class TabCompleteValuesHelper {
case MATERIAL -> getAllReforgeAbleMaterialNames();
case ENCHANTMENT, ENCHANTMENT_LIST -> getAllEnchantments();
case STRING_LIST -> List.of("*_SHOVEL,*_PICKAXE,*_AXE,*_HOE,*_SWORD:STICK,SMITHING_TABLE:*_PLANKS");
+ // TODO: Change this to something that makes sense
+ case ADVANCED_COST -> List.of();
};
}
diff --git a/src/main/java/net/knarcraft/blacksmith/util/TypeValidationHelper.java b/src/main/java/net/knarcraft/blacksmith/util/TypeValidationHelper.java
index 144c44d..3a67a99 100644
--- a/src/main/java/net/knarcraft/blacksmith/util/TypeValidationHelper.java
+++ b/src/main/java/net/knarcraft/blacksmith/util/TypeValidationHelper.java
@@ -36,7 +36,8 @@ public final class TypeValidationHelper {
case STRING -> isNonEmptyString(value, sender);
case POSITIVE_INTEGER -> isPositiveInteger(value, sender);
case PERCENTAGE -> isPercentage(value, sender);
- case BOOLEAN -> true;
+ // TODO: Implement proper advanced cost checking
+ case BOOLEAN, ADVANCED_COST -> true;
case REFORGE_ABLE_ITEMS, STRING_LIST -> isStringList(value, sender);
case MATERIAL -> isMaterial(value, sender);
case ENCHANTMENT -> isEnchantment(value, sender);