From 42ca42c571cf254f18a0345386f1f7527724534c Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sat, 21 Dec 2024 19:48:39 +0100 Subject: [PATCH] Adds increased scrapper cost when used excessively --- pom.xml | 15 ++++ .../blacksmith/BlacksmithPlugin.java | 6 ++ .../blacksmith/command/CostCommand.java | 42 ++++++++--- .../scrapper/GlobalScrapperSettings.java | 36 ++++----- .../config/scrapper/ScrapperSetting.java | 26 ++----- .../BlacksmithTranslatableMessage.java | 7 +- .../blacksmith/manager/EconomyManager.java | 35 ++++++--- .../manager/PlayerUsageManager.java | 75 +++++++++++++++++++ .../blacksmith/trait/SalvageSession.java | 12 ++- .../blacksmith/trait/ScrapperTrait.java | 6 +- src/main/resources/config.yml | 16 ++-- src/main/resources/strings.yml | 4 +- 12 files changed, 210 insertions(+), 70 deletions(-) create mode 100644 src/main/java/net/knarcraft/blacksmith/manager/PlayerUsageManager.java diff --git a/pom.xml b/pom.xml index 50e7e4f..c0adb07 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,11 @@ 5.10.2 test + + net.objecthunter + exp4j + 0.4.8 + @@ -127,6 +132,10 @@ org.jetbrains.annotations net.knarcraft.blacksmith.lib.annotations + + net.objecthunter.exp4j + net.knarcraft.blacksmith.lib.exp4j + @@ -141,6 +150,12 @@ org/jetbrains/annotations/** + + net.objecthunter:exp4j + + net/objecthunter/exp4j/** + + diff --git a/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java b/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java index ec09cd0..f5f5fd7 100644 --- a/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java +++ b/src/main/java/net/knarcraft/blacksmith/BlacksmithPlugin.java @@ -20,6 +20,7 @@ import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import net.knarcraft.blacksmith.listener.NPCClickListener; import net.knarcraft.blacksmith.listener.PlayerListener; import net.knarcraft.blacksmith.manager.EconomyManager; +import net.knarcraft.blacksmith.manager.PlayerUsageManager; import net.knarcraft.blacksmith.trait.BlacksmithTrait; import net.knarcraft.blacksmith.trait.ScrapperTrait; import net.knarcraft.blacksmith.util.ConfigHelper; @@ -27,6 +28,7 @@ import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.TranslatableTimeUnit; import net.knarcraft.knarlib.formatting.Translator; import net.knarcraft.knarlib.util.UpdateChecker; +import org.bukkit.Bukkit; import org.bukkit.command.CommandExecutor; import org.bukkit.command.PluginCommand; import org.bukkit.command.TabCompleter; @@ -183,6 +185,10 @@ public class BlacksmithPlugin extends JavaPlugin { //Alert about an update in the console UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=105938", () -> this.getDescription().getVersion(), null); + + // Remove expired scrapper usage data + Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> PlayerUsageManager.removeExpiredData( + System.currentTimeMillis() - 3600000), 36000, 36000); } @Override diff --git a/src/main/java/net/knarcraft/blacksmith/command/CostCommand.java b/src/main/java/net/knarcraft/blacksmith/command/CostCommand.java index f9bcef8..d372438 100644 --- a/src/main/java/net/knarcraft/blacksmith/command/CostCommand.java +++ b/src/main/java/net/knarcraft/blacksmith/command/CostCommand.java @@ -1,6 +1,8 @@ package net.knarcraft.blacksmith.command; import net.knarcraft.blacksmith.BlacksmithPlugin; +import net.knarcraft.blacksmith.container.ActionCost; +import net.knarcraft.blacksmith.formatting.BlacksmithTranslatableMessage; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; @@ -22,17 +24,13 @@ public class CostCommand implements TabExecutor { if (arguments.length < 2) { return false; } + + ActionCost actionCost; + switch (arguments[0]) { case "simple": - // TODO: Expect the next argument to be the cost - try { - Double cost = Double.parseDouble(arguments[1]); - - } catch (NumberFormatException exception) { - // TODO: Make this translatable? - BlacksmithPlugin.getStringFormatter().displayErrorMessage(commandSender, "You must supply a numeric (double) cost"); - return true; - } + // TODO: Do something with the cost + double cost = parseSimpleCost(commandSender, arguments[1]); break; case "advanced": switch (arguments[1]) { @@ -57,8 +55,32 @@ public class CostCommand implements TabExecutor { } @Override - public @Nullable List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { + public @Nullable List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, + @NotNull String s, @NotNull String[] strings) { return List.of(); } + /** + * Parses a simple double cost + * + * @param commandSender

The command sender to notify of any problems

+ * @param costString

The string to parse into a cost

+ * @return

The parsed cost

+ * @throws IllegalArgumentException

If the specified cost is invalid

+ */ + private double parseSimpleCost(@NotNull CommandSender commandSender, + @NotNull String costString) throws IllegalArgumentException { + try { + double cost = Double.parseDouble(costString); + if (cost < 0) { + throw new NumberFormatException(); + } + return cost; + } catch (NumberFormatException exception) { + BlacksmithPlugin.getStringFormatter().displayErrorMessage(commandSender, + BlacksmithTranslatableMessage.DOUBLE_COST_REQUIRED); + throw new IllegalArgumentException("Invalid cost given"); + } + } + } diff --git a/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java b/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java index ce53805..a94df02 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/GlobalScrapperSettings.java @@ -213,30 +213,21 @@ public class GlobalScrapperSettings implements Settings { } /** - * Gets the base cost of salvaging an enchanted book + * Gets the math formula for the increase in salvage cost * - * @return

The enchanted book salvage base cost

+ * @return

The salvage cost increase formula

*/ - public double getEnchantedBookSalvageCost() { - return asDouble(ScrapperSetting.ENCHANTED_BOOK_SALVAGE_BASE_COST); + public String getSalvageCostIncrease() { + return asString(ScrapperSetting.SALVAGE_COST_INCREASE); } /** - * Whether to multiply the cost of salvaging enchanted books based on the number of enchantments + * Gets the math formula for the increase in salvage cooldown * - * @return

Whether to multiply the cost of salvaging enchanted books based on the number of enchantments

+ * @return

The salvage cooldown increase formula

*/ - public boolean multiplyEnchantedBookSalvageCost() { - return asBoolean(ScrapperSetting.ENCHANTED_BOOK_SALVAGE_MULTIPLY); - } - - /** - * Whether to require both a monetary and item-based cost when salvaging enchanted books - * - * @return

Whether to require both a monetary and item-based cost when salvaging enchanted books

- */ - public boolean requireMoneyAndItemForEnchantedBookSalvage() { - return asBoolean(ScrapperSetting.ENCHANTED_BOOK_SALVAGE_REQUIRE_BOTH); + public String getSalvageCooldownIncrease() { + return asString(ScrapperSetting.SALVAGE_COOLDOWN_INCREASE); } /** @@ -302,4 +293,15 @@ public class GlobalScrapperSettings implements Settings { } } + /** + * Gets the string value of the given setting + * + * @param setting

The setting to get the value of

+ * @return

The value of the given setting as a string

+ */ + @NotNull + private String asString(@NotNull ScrapperSetting setting) { + return getValue(setting).toString(); + } + } 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 7225a85..3f1d849 100644 --- a/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java +++ b/src/main/java/net/knarcraft/blacksmith/config/scrapper/ScrapperSetting.java @@ -292,28 +292,18 @@ public enum ScrapperSetting implements Setting { "The cost of using the scrapper to remove netherite from an item", false, false), /** - * The setting for the enchanted book salvage cost + * The mathematical formula for increasing salvage cost */ - ENCHANTED_BOOK_SALVAGE_BASE_COST("enchantedBookSalvageBasePrice", SettingValueType.POSITIVE_DOUBLE, 10, - "The per-enchantment cost of splitting an enchanted book", false, false), + SALVAGE_COST_INCREASE("salvageCostIncrease", SettingValueType.STRING, "{cost}*{timesUsed}", + "The mathematical formula for salvage cost increase when continually used within the same hour. " + + "This is necessary for some servers where items can be easily farmed. Set to {cost} to disable behavior.", false, false), /** - * The setting for the enchanted book item cost + * The mathematical formula for increasing salvage cooldown */ - ENCHANTED_BOOK_SALVAGE_ITEM_COST("enchantedBookSalvageCost", SettingValueType.ADVANCED_COST, null, - "The cost to pay in order to salvage an enchanted book", false, false), - - /** - * The setting for whether to multiply enchanted book cost - */ - ENCHANTED_BOOK_SALVAGE_MULTIPLY("enchantedBookSalvageMultiplyByEnchantmentNumber", SettingValueType.BOOLEAN, - true, "Whether to multiply the cost of salvaging an enchanted book by the number of enchantments on the book", false, false), - - /** - * The setting for whether to require both monetary and item payment for salvaging enchanted books - */ - ENCHANTED_BOOK_SALVAGE_REQUIRE_BOTH("enchantedBookSalvageRequireMoneyAndItem", SettingValueType.BOOLEAN, - true, "Whether salvaging an enchanted book will cost both the configured money amount and the configured item", false, false), + SALVAGE_COOLDOWN_INCREASE("salvageCooldownIncrease", SettingValueType.STRING, "{cooldown}*{timesUsed}", + "The mathematical formula for salvage cooldown increase when continually used within the same hour. " + + "This is necessary for some servers where items can be easily farmed. Set to {cooldown} to disable behavior.", false, false), /** * Whether to display exact time in minutes and seconds when displaying a remaining cool-down diff --git a/src/main/java/net/knarcraft/blacksmith/formatting/BlacksmithTranslatableMessage.java b/src/main/java/net/knarcraft/blacksmith/formatting/BlacksmithTranslatableMessage.java index dfd2844..f31647f 100644 --- a/src/main/java/net/knarcraft/blacksmith/formatting/BlacksmithTranslatableMessage.java +++ b/src/main/java/net/knarcraft/blacksmith/formatting/BlacksmithTranslatableMessage.java @@ -143,7 +143,12 @@ public enum BlacksmithTranslatableMessage implements TranslatableMessage { /** * The format to use for formatting any message spoken by a blacksmith NPC */ - NPC_TALK_FORMAT; + NPC_TALK_FORMAT, + + /** + * The text to display when explaining that a cost must be a positive double + */ + DOUBLE_COST_REQUIRED; /** * Gets the message to display when displaying the raw value of messages diff --git a/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java b/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java index 7bbce34..9e137ee 100644 --- a/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java +++ b/src/main/java/net/knarcraft/blacksmith/manager/EconomyManager.java @@ -5,8 +5,10 @@ import net.knarcraft.blacksmith.config.blacksmith.GlobalBlacksmithSettings; import net.knarcraft.blacksmith.config.scrapper.GlobalScrapperSettings; import net.knarcraft.blacksmith.property.SalvageMethod; import net.knarcraft.blacksmith.util.ItemHelper; -import net.knarcraft.blacksmith.util.SalvageHelper; +import net.knarcraft.knarlib.formatting.StringReplacer; import net.milkbowl.vault.economy.Economy; +import net.objecthunter.exp4j.Expression; +import net.objecthunter.exp4j.ExpressionBuilder; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -59,7 +61,8 @@ public class EconomyManager { * @return

Whether the player cannot pay for the salvage

*/ public static boolean cannotPayForSalvage(@NotNull Player player, @NotNull SalvageMethod salvageMethod, @NotNull ItemStack item) { - return economy.getBalance(player) - getSalvageCost(salvageMethod, item) < 0; + // TODO: Account for advanced cost options + return economy.getBalance(player) - getSalvageCost(salvageMethod, item, player) < 0; } /** @@ -79,11 +82,13 @@ public class EconomyManager { * * @param salvageMethod

The salvage method to get the cost for

* @param item

The item to be salvaged

+ * @param player

The player to provide the cost to

* @return

The formatted cost

*/ @NotNull - public static String formatSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item) { - return economy.format(getSalvageCost(salvageMethod, item)); + public static String formatSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item, + @NotNull Player player) { + return economy.format(getSalvageCost(salvageMethod, item, player)); } /** @@ -112,16 +117,25 @@ public class EconomyManager { * Gets the cost of salvaging using the specified method * * @param salvageMethod

The salvage method to get cost for

+ * @param item

The item to be salvaged

* @return

The salvage cost

*/ - private static double getSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item) { + private static double getSalvageCost(@NotNull SalvageMethod salvageMethod, @NotNull ItemStack item, + @NotNull Player player) { GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings(); - return switch (salvageMethod) { + double baseCost = switch (salvageMethod) { case SALVAGE, EXTENDED_SALVAGE -> settings.getSalvageCost(); case NETHERITE -> settings.getNetheriteSalvageCost(); case ARMOR_TRIM -> settings.getArmorTrimSalvageCost(); case ENCHANTED_BOOK -> getEnchantedBookSalvageCost(item); }; + + StringReplacer replacer = new StringReplacer(settings.getSalvageCostIncrease()); + replacer.add("{cost}", String.valueOf(baseCost)); + replacer.add("{timesUsed}", String.valueOf(PlayerUsageManager.getUsages(player, + System.currentTimeMillis() - 3600000))); + Expression expression = new ExpressionBuilder(replacer.replace()).build(); + return Math.max(expression.evaluate(), baseCost); } /** @@ -145,7 +159,7 @@ public class EconomyManager { * @param salvageMethod

The salvage method to withdraw for

*/ public static void withdrawScrapper(@NotNull Player player, @NotNull SalvageMethod salvageMethod) { - double cost = getSalvageCost(salvageMethod, player.getInventory().getItemInMainHand()); + double cost = getSalvageCost(salvageMethod, player.getInventory().getItemInMainHand(), player); if (cost > 0) { economy.withdrawPlayer(player, cost); } @@ -230,12 +244,15 @@ public class EconomyManager { * @return

The cost of scrapping the enchanted book

*/ private static double getEnchantedBookSalvageCost(@NotNull ItemStack item) { - GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings(); + // TODO: Properly implement this + /*GlobalScrapperSettings settings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings(); + double cost = settings.getEnchantedBookSalvageCost(); if (settings.multiplyEnchantedBookSalvageCost()) { cost *= SalvageHelper.getEnchantmentCount(item) - 1; } - return SalvageHelper.getEnchantmentCount(item) * cost; + return SalvageHelper.getEnchantmentCount(item) * cost;*/ + return 1000000; } /** diff --git a/src/main/java/net/knarcraft/blacksmith/manager/PlayerUsageManager.java b/src/main/java/net/knarcraft/blacksmith/manager/PlayerUsageManager.java new file mode 100644 index 0000000..b38070d --- /dev/null +++ b/src/main/java/net/knarcraft/blacksmith/manager/PlayerUsageManager.java @@ -0,0 +1,75 @@ +package net.knarcraft.blacksmith.manager; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A manager for keeping track of players using scrappers + */ +public class PlayerUsageManager { + + private static final Map> playerUsages = new HashMap<>(); + + /** + * Register that a player has used a scrapper + * + * @param player

The player using a scrapper

+ */ + public static void registerUsage(@NotNull Player player) { + playerUsages.putIfAbsent(player, new HashSet<>()); + Set usages = playerUsages.get(player); + usages.add(System.currentTimeMillis()); + } + + /** + * Gets how many times the given player has used a scrapper after the given timestamp + * + * @param player

The player to check usages for

+ * @param afterTimestamp

The timestamp of when to start looking for usages

+ * @return

The number of scrapper uses for the given player

+ */ + public static int getUsages(@NotNull Player player, @NotNull Long afterTimestamp) { + Set usages = playerUsages.get(player); + if (usages == null) { + return 0; + } + + int uses = 0; + Set expired = new HashSet<>(); + for (Long usageTime : usages) { + if (usageTime < afterTimestamp) { + expired.add(usageTime); + } else { + uses++; + } + } + + // Remove expired data + usages.removeAll(expired); + + return uses; + } + + /** + * Removes all expired timestamps + * + * @param afterTimestamp

The timestamp for the earliest time of valid timestamps

+ */ + public static void removeExpiredData(@NotNull Long afterTimestamp) { + for (Set usageSet : playerUsages.values()) { + Set expired = new HashSet<>(); + for (Long item : usageSet) { + if (item < afterTimestamp) { + expired.add(item); + } + } + usageSet.removeAll(expired); + } + } + +} diff --git a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java index 7adb8d3..05ca831 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/SalvageSession.java @@ -6,9 +6,13 @@ import net.knarcraft.blacksmith.config.scrapper.ScrapperNPCSettings; import net.knarcraft.blacksmith.event.ScrapperSalvageFailEvent; import net.knarcraft.blacksmith.event.ScrapperSalvageSucceedEvent; import net.knarcraft.blacksmith.manager.EconomyManager; +import net.knarcraft.blacksmith.manager.PlayerUsageManager; import net.knarcraft.blacksmith.property.SalvageMethod; import net.knarcraft.blacksmith.util.ItemHelper; import net.knarcraft.blacksmith.util.SalvageHelper; +import net.knarcraft.knarlib.formatting.StringReplacer; +import net.objecthunter.exp4j.Expression; +import net.objecthunter.exp4j.ExpressionBuilder; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; @@ -124,8 +128,14 @@ public class SalvageSession extends Session implements Runnable { // Start cool-down Calendar wait = Calendar.getInstance(); - wait.add(Calendar.SECOND, this.config.getSalvageCoolDown()); + StringReplacer replacer = new StringReplacer(BlacksmithPlugin.getInstance().getGlobalScrapperSettings().getSalvageCooldownIncrease()); + replacer.add("{cooldown}", String.valueOf(this.config.getSalvageCoolDown())); + replacer.add("{timesUsed}", String.valueOf(PlayerUsageManager.getUsages(player, System.currentTimeMillis() - 3600000))); + Expression expression = new ExpressionBuilder(replacer.replace()).build(); + + wait.add(Calendar.SECOND, Math.max((int) expression.evaluate(), this.config.getSalvageCoolDown())); this.scrapperTrait.addCoolDown(this.player.getUniqueId(), wait); + PlayerUsageManager.registerUsage(this.player); } /** diff --git a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java index 0eff1ba..50f03d7 100644 --- a/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java +++ b/src/main/java/net/knarcraft/blacksmith/trait/ScrapperTrait.java @@ -119,7 +119,7 @@ public class ScrapperTrait extends CustomTrait { return; } // Print the cost to the player - printCostMessage(player, itemInHand, EconomyManager.formatSalvageCost(result.salvageMethod(), itemInHand), + printCostMessage(player, itemInHand, EconomyManager.formatSalvageCost(result.salvageMethod(), itemInHand, player), result.salvageMethod()); } @@ -327,14 +327,14 @@ public class ScrapperTrait extends CustomTrait { // TODO: If an item requirement is set, print the item requirement GlobalScrapperSettings scrapperSettings = BlacksmithPlugin.getInstance().getGlobalScrapperSettings(); - String moneyCost = EconomyManager.format(scrapperSettings.getEnchantedBookSalvageCost()); + /*String moneyCost = EconomyManager.format(scrapperSettings.getEnchantedBookSalvageCost()); //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 ""; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b8ea34c..b35b87e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -142,17 +142,13 @@ scrapper: # The cost of using the scrapper to remove netherite from an item netheriteSalvagePrice: 15 - # The per-enchantment cost of splitting an enchanted book - enchantedBookSalvageBasePrice: 10 + # The mathematical formula for salvage cost increase when continually used within the same hour. This is necessary + # for some servers where items can be easily farmed. Set to {cost} to disable behavior. + salvageCostIncrease: "{cost}*{timesUsed}" - # The item that needs to be provided to pay for splitting an enchanted book - enchantedBookSalvageItemCost: null - - # Whether to multiply the initial cost with the number of enchantments on the book - enchantedBookSalvageMultiplyByEnchantmentNumber: true - - # Whether to require both a monetary sum and providing an item in order to pay for salvaging an enchanted book - enchantedBookSalvageRequireMoneyAndItem: false + # The mathematical formula for salvage cooldown increase when continually used within the same hour. This is + # necessary for some servers where items can be easily farmed. Set to {cooldown} to disable behavior. + salvageCooldownIncrease: "{cooldown}*{timesUsed}" # The settings which are set to any new scrapper NPC. To change any of these settings for an existing NPC, you must # change the Citizens NPC file, or use the /scrapper command diff --git a/src/main/resources/strings.yml b/src/main/resources/strings.yml index 11fddef..b85735e 100644 --- a/src/main/resources/strings.yml +++ b/src/main/resources/strings.yml @@ -89,4 +89,6 @@ en: # The text shown if the player has to wait for more than 5 minutes INTERVAL_MORE_THAN_5_MINUTES: "in quite a while" # The marker shown when displaying values overridden for the selected NPC (not shown if the NPC inherits the default value) - SETTING_OVERRIDDEN_MARKER: " [NPC]" \ No newline at end of file + SETTING_OVERRIDDEN_MARKER: " [NPC]" + # The translation of the text displayed when a cost is expected, but a non-number is provided + DOUBLE_COST_REQUIRED: "You must supply a numeric (double) cost" \ No newline at end of file