From 1909c6d5bc643008a52da9a1ec0e95e5d13d86bd Mon Sep 17 00:00:00 2001 From: akastijn Date: Wed, 23 Jul 2025 03:11:46 +0200 Subject: [PATCH] Add AutoSell feature with commands and tasks for automated item selling --- src/main/java/com/alttd/VillagerUI.java | 18 ++- .../com/alttd/commands/CommandManager.java | 4 +- .../commands/subcommands/CommandAutoSell.java | 130 ++++++++++++++++++ src/main/java/com/alttd/config/Config.java | 22 +++ .../com/alttd/events/ItemPickupEvent.java | 32 +++++ .../java/com/alttd/events/SpawnShopEvent.java | 13 +- .../com/alttd/util/auto_sell/AutoSell.java | 100 ++++++++++++++ .../alttd/util/auto_sell/AutoSellTask.java | 94 +++++++++++++ 8 files changed, 406 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/alttd/commands/subcommands/CommandAutoSell.java create mode 100644 src/main/java/com/alttd/events/ItemPickupEvent.java create mode 100644 src/main/java/com/alttd/util/auto_sell/AutoSell.java create mode 100644 src/main/java/com/alttd/util/auto_sell/AutoSellTask.java diff --git a/src/main/java/com/alttd/VillagerUI.java b/src/main/java/com/alttd/VillagerUI.java index d0065ef..59833ac 100644 --- a/src/main/java/com/alttd/VillagerUI.java +++ b/src/main/java/com/alttd/VillagerUI.java @@ -11,6 +11,7 @@ import com.alttd.datalock.DataLockAPI; import com.alttd.events.*; import com.alttd.logging.LogInOut; import com.alttd.objects.EconUser; +import com.alttd.util.auto_sell.AutoSellTask; import com.alttd.util.Logger; import com.alttd.util.SaveTask; import net.milkbowl.vault.economy.Economy; @@ -36,15 +37,16 @@ public class VillagerUI extends JavaPlugin { @Override public void onEnable() { logInOut = new LogInOut(); - registerEvents(); - new CommandManager(); + AutoSellTask autoSellTask = new AutoSellTask(); + registerEvents(autoSellTask); + new CommandManager(autoSellTask); Config.reload(); VillagerConfig.reload(); WorthConfig.reload(); if (!setupEconomy()) return; Database.getDatabase().init(); - scheduleTasks(); + scheduleTasks(autoSellTask); DataLockAPI dataLockAPI = DataLockAPI.get(); if (dataLockAPI == null) { Logger.severe("Unable to load datalockapi"); @@ -68,7 +70,7 @@ public class VillagerUI extends JavaPlugin { logInOut.run(); } - private void scheduleTasks() { + private void scheduleTasks(AutoSellTask autoSellTask) { new SaveTask().runTaskTimerAsynchronously( this, 0, @@ -77,9 +79,14 @@ public class VillagerUI extends JavaPlugin { this, 0, 20); + autoSellTask.runTaskTimerAsynchronously( + this, + 10 * 20, + 10 * 20 + ); } - private void registerEvents() { + private void registerEvents(AutoSellTask autoSellTask) { getServer().getPluginManager().registerEvents(new GUIListener(), this); getServer().getPluginManager().registerEvents(new VillagerEvents(), this); getServer().getPluginManager().registerEvents(new LogoutEvent(), this); @@ -87,6 +94,7 @@ public class VillagerUI extends JavaPlugin { getServer().getPluginManager().registerEvents(new VehicleEvent(), this); getServer().getPluginManager().registerEvents(new SpawnShopListener(logInOut), this); getServer().getPluginManager().registerEvents(new DataLock(), this); + getServer().getPluginManager().registerEvents(new ItemPickupEvent(autoSellTask), this); } public Economy getEconomy() { diff --git a/src/main/java/com/alttd/commands/CommandManager.java b/src/main/java/com/alttd/commands/CommandManager.java index 1d158ff..58f28b2 100644 --- a/src/main/java/com/alttd/commands/CommandManager.java +++ b/src/main/java/com/alttd/commands/CommandManager.java @@ -3,6 +3,7 @@ package com.alttd.commands; import com.alttd.VillagerUI; import com.alttd.commands.subcommands.*; import com.alttd.config.Config; +import com.alttd.util.auto_sell.AutoSellTask; import com.alttd.util.Logger; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.command.*; @@ -18,7 +19,7 @@ public class CommandManager implements CommandExecutor, TabExecutor { private final List subCommands; private final MiniMessage miniMessage; - public CommandManager() { + public CommandManager(AutoSellTask autoSellTask) { VillagerUI villagerUI = VillagerUI.getInstance(); PluginCommand command = villagerUI.getCommand("villagerui"); @@ -36,6 +37,7 @@ public class CommandManager implements CommandExecutor, TabExecutor { new CommandPoints(), new CommandBuy(), new CommandSell(), + new CommandAutoSell(autoSellTask), new CommandCreateVillager(), new CommandReload(), new CommandRemoveVillager()); diff --git a/src/main/java/com/alttd/commands/subcommands/CommandAutoSell.java b/src/main/java/com/alttd/commands/subcommands/CommandAutoSell.java new file mode 100644 index 0000000..0eebc85 --- /dev/null +++ b/src/main/java/com/alttd/commands/subcommands/CommandAutoSell.java @@ -0,0 +1,130 @@ +package com.alttd.commands.subcommands; + +import com.alttd.commands.SubCommand; +import com.alttd.config.Config; +import com.alttd.objects.VillagerTypeManager; +import com.alttd.util.auto_sell.AutoSellTask; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class CommandAutoSell extends SubCommand { + + private final AutoSellTask autoSellTask; + private final MiniMessage miniMessage = MiniMessage.miniMessage(); + + public CommandAutoSell(AutoSellTask autoSellTask) { + this.autoSellTask = autoSellTask; + } + + @Override + public boolean onCommand(CommandSender commandSender, String[] args) { + if (!(commandSender instanceof Player player)) { + commandSender.sendRichMessage(Config.NO_CONSOLE); + return true; + } + if (args.length == 1) { + sendActiveList(player); + } else if (args.length == 2) { + if (args[1].equalsIgnoreCase("off")) { + turnOffAutoSell(player); + } else { + toggleMaterial(player, args[1]); + } + } else { + player.sendRichMessage(getHelpMessage()); + } + return true; + } + + private void sendActiveList(Player player) { + UUID uuid = player.getUniqueId(); + if (!autoSellTask.hasAutoSellEntry(uuid)) { + player.sendRichMessage(Config.NO_AUTO_SELL_ITEMS); + return; + } + Set materials = autoSellTask.getAutoSellList(uuid); + List materialComponents = materials.stream().map(Material::name) + .map(material -> miniMessage.deserialize( + Config.LIST_AUTO_SELL_COMPONENT, + Placeholder.parsed("material", material))).toList(); + + Component deserialize = miniMessage.deserialize(Config.LIST_AUTO_SELL_ITEMS); + Component joinedMaterialComponents = Component.join(JoinConfiguration.newlines(), materialComponents); + ComponentLike message = Component.join(JoinConfiguration.newlines(), deserialize, joinedMaterialComponents); + player.sendMessage(message); + } + + private void turnOffAutoSell(Player player) { + autoSellTask.clearAutoSell(player); + player.sendRichMessage(Config.DISABLED_AUTO_SELL); + } + + private void toggleMaterial(Player player, String stringMaterial) { + Material material = Material.getMaterial(stringMaterial.toUpperCase()); + if (material == null) { + player.sendRichMessage(Config.INVALID_AUTO_SELL_ITEM, Placeholder.parsed("material", stringMaterial)); + return; + } + UUID uuid = player.getUniqueId(); + + boolean autoSellToggledOn = autoSellTask.toggleAutoSell(uuid, material); + + TagResolver.Single materialPlaceholder = Placeholder.parsed("material", material.name()); + + if (autoSellToggledOn && autoSellTask.getEntries(uuid) > Config.MAX_AUTO_SELL) { + autoSellTask.removeAutoSell(uuid, material); + player.sendRichMessage(Config.MAX_AUTO_SELL_REACHED, + Placeholder.parsed("max_auto_sell", String.valueOf(Config.MAX_AUTO_SELL))); + return; + } + if (autoSellToggledOn) { + player.sendRichMessage(Config.TURNED_ON_AUTO_SELL_FOR, materialPlaceholder); + } else { + player.sendRichMessage(Config.TURNED_OFF_AUTO_SELL_FOR, materialPlaceholder); + } + } + + @Override + public String getName() { + return "autosell"; + } + + @Override + public List getTabComplete(CommandSender commandSender, String[] args) { + if (args.length != 2) { + return List.of(); + } + ArrayList tabComplete = new ArrayList<>(); + tabComplete.add("off"); + tabComplete.addAll(VillagerTypeManager + .getVillagerTypes() + .stream() + .flatMap(villagerType -> villagerType + .getSelling() + .stream() + .map(ItemStack::getType) + ) + .map(material -> material.name().toLowerCase()) + .toList()); + tabComplete.removeIf(string -> !string.toLowerCase().startsWith(args[1].toLowerCase())); + return tabComplete; + } + + @Override + public String getHelpMessage() { + return ""; + } +} diff --git a/src/main/java/com/alttd/config/Config.java b/src/main/java/com/alttd/config/Config.java index 2384e9c..4a51d93 100644 --- a/src/main/java/com/alttd/config/Config.java +++ b/src/main/java/com/alttd/config/Config.java @@ -68,6 +68,7 @@ public final class Config extends AbstractConfig { public static String HELP_MESSAGE_WRAPPER = "VillagerShopUI help:\n"; public static String HELP_MESSAGE = "Show this menu: /villagerui help"; public static String POINTS_MESSAGE = "Show points: /villagerui points [villagerType]"; + public static String AUTO_SELL_MESSAGE = "Toggle auto sell per item: /villagerui autosell "; public static String BUY_MESSAGE = "Check where/if you can buy an item: /villagerui buy "; public static String SELL_MESSAGE = "Check where/if you can sell an item: /villagerui sell "; public static String RELOAD_MESSAGE = "Reload configs: /villagerui reload"; @@ -78,6 +79,7 @@ public final class Config extends AbstractConfig { HELP_MESSAGE_WRAPPER = config.getString("help.help-wrapper", HELP_MESSAGE_WRAPPER); HELP_MESSAGE = config.getString("help.help", HELP_MESSAGE); POINTS_MESSAGE = config.getString("help.points", POINTS_MESSAGE); + AUTO_SELL_MESSAGE = config.getString("help.auto-sell", AUTO_SELL_MESSAGE); BUY_MESSAGE = config.getString("help.buy", BUY_MESSAGE); SELL_MESSAGE = config.getString("help.sell", SELL_MESSAGE); RELOAD_MESSAGE = config.getString("help.reload", RELOAD_MESSAGE); @@ -140,6 +142,15 @@ public final class Config extends AbstractConfig { public static String LOADING_ECON_DATA = "Loading your economy data, please wait..."; public static String NO_VILLAGER_POINTS = "You don't have any villager points."; public static String NOTIFY_POINTS_RESET = "Your points for reset to 0!"; + public static String NO_AUTO_SELL_ITEMS = "You don't have any items set to auto sell!"; + public static String LIST_AUTO_SELL_ITEMS = "You have the following items enabled to auto sell"; + public static String LIST_AUTO_SELL_COMPONENT = ""; + public static String INVALID_AUTO_SELL_ITEM = " is not a valid auto sell item"; + public static String TURNED_ON_AUTO_SELL_FOR = "Turned on auto sell for "; + public static String TURNED_OFF_AUTO_SELL_FOR = "Turned off auto sell for "; + public static String AUTO_SOLD = "You made from auto selling"; + public static String MAX_AUTO_SELL_REACHED = "You can only auto sell up to items at a time"; + public static String DISABLED_AUTO_SELL = "You have disabled auto sell"; private static void loadMessages() { NOT_ENOUGH_MONEY = config.getString("messages.not-enough-money", NOT_ENOUGH_MONEY); @@ -159,12 +170,23 @@ public final class Config extends AbstractConfig { LOADING_ECON_DATA = config.getString("messages.loading-econ-data", LOADING_ECON_DATA); NO_VILLAGER_POINTS = config.getString("messages.no-villager-points", NO_VILLAGER_POINTS); NOTIFY_POINTS_RESET = config.getString("messages.notify-points-reset", NOTIFY_POINTS_RESET); + NO_AUTO_SELL_ITEMS = config.getString("messages.no-auto-sell-items", NO_AUTO_SELL_ITEMS); + LIST_AUTO_SELL_ITEMS = config.getString("messages.list-auto-sell-items", LIST_AUTO_SELL_ITEMS); + LIST_AUTO_SELL_COMPONENT = config.getString("messages.list-auto-sell-component", LIST_AUTO_SELL_COMPONENT); + INVALID_AUTO_SELL_ITEM = config.getString("messages.invalid-auto-sell-item", INVALID_AUTO_SELL_ITEM); + TURNED_ON_AUTO_SELL_FOR = config.getString("messages.turned-on-auto-sell-for", TURNED_ON_AUTO_SELL_FOR); + TURNED_OFF_AUTO_SELL_FOR = config.getString("messages.turned-off-auto-sell-for", TURNED_OFF_AUTO_SELL_FOR); + AUTO_SOLD = config.getString("messages.auto-sold", AUTO_SOLD); + MAX_AUTO_SELL_REACHED = config.getString("messages.max-auto-sell-reached", MAX_AUTO_SELL_REACHED); + DISABLED_AUTO_SELL = config.getString("messages.disabled-auto-sell", DISABLED_AUTO_SELL); } public static boolean DEBUG = false; + public static int MAX_AUTO_SELL = 5; private static void loadSettings() { DEBUG = config.getBoolean("settings.debug", DEBUG); + MAX_AUTO_SELL = config.getInt("settings.max-auto-sell", MAX_AUTO_SELL); } private static void loadVillagerTypes() { diff --git a/src/main/java/com/alttd/events/ItemPickupEvent.java b/src/main/java/com/alttd/events/ItemPickupEvent.java new file mode 100644 index 0000000..700f1e2 --- /dev/null +++ b/src/main/java/com/alttd/events/ItemPickupEvent.java @@ -0,0 +1,32 @@ +package com.alttd.events; + +import com.alttd.util.auto_sell.AutoSellTask; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPickupItemEvent; + +import java.util.UUID; + +public class ItemPickupEvent implements Listener { + + private final AutoSellTask autoSellTask; + + public ItemPickupEvent(AutoSellTask autoSellTask) { + this.autoSellTask = autoSellTask; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onItemPickupEvent(EntityPickupItemEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + UUID uuid = player.getUniqueId(); + if (!autoSellTask.autoSellEnabled(uuid, event.getItem().getItemStack().getType())) { + return; + } + autoSellTask.putAutoSellPlayer(uuid); + } + +} diff --git a/src/main/java/com/alttd/events/SpawnShopEvent.java b/src/main/java/com/alttd/events/SpawnShopEvent.java index 0ee1012..4d26e5f 100644 --- a/src/main/java/com/alttd/events/SpawnShopEvent.java +++ b/src/main/java/com/alttd/events/SpawnShopEvent.java @@ -31,6 +31,7 @@ public final class SpawnShopEvent extends Event { return player; } + //Trade public SpawnShopEvent(Player player, Material material, int amount, double price, boolean buy) { this.player = player; this.amount = amount; @@ -41,6 +42,17 @@ public final class SpawnShopEvent extends Event { this.buy = buy; } + //Auto sell + public SpawnShopEvent(Player player, Purchase purchase, int pointsBefore, int pointsAfter, double price) { + this.player = player; + this.amount = purchase.amount(); + this.price = price; + this.item = purchase.material(); + this.pointsBefore = pointsBefore; + this.pointsAfter = pointsAfter; + this.buy = false; + } + public int amount() { return amount; } @@ -75,4 +87,3 @@ public final class SpawnShopEvent extends Event { return handlers; } } - diff --git a/src/main/java/com/alttd/util/auto_sell/AutoSell.java b/src/main/java/com/alttd/util/auto_sell/AutoSell.java new file mode 100644 index 0000000..3bdd251 --- /dev/null +++ b/src/main/java/com/alttd/util/auto_sell/AutoSell.java @@ -0,0 +1,100 @@ +package com.alttd.util.auto_sell; + +import com.alttd.VillagerUI; +import com.alttd.config.Config; +import com.alttd.config.WorthConfig; +import com.alttd.events.SpawnShopEvent; +import com.alttd.objects.*; +import com.alttd.util.Logger; +import com.alttd.util.Utilities; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitScheduler; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +public class AutoSell { + + private static final MiniMessage miniMessage = MiniMessage.miniMessage(); + + public static void autoSell(Player player, Set materials) { + EconUser econUser = EconUser.getUser(player.getUniqueId()); + if (econUser == null) { + player.sendRichMessage(Config.LOADING_ECON_DATA); + return; + } + double totalMoneyMade = VillagerTypeManager + .getVillagerTypes() + .stream() + .flatMap(villagerType -> villagerType + .getSelling() + .stream() + .map(ItemStack::getType) + .filter(materials::contains) + .map(material -> sell(player, econUser, villagerType, material))) + .mapToDouble(Double::doubleValue) + .sum(); + player.sendActionBar(miniMessage.deserialize(Config.AUTO_SOLD, + Placeholder.parsed("total_money_made", String.valueOf(totalMoneyMade)))); + } + + private static double sell(Player player, EconUser econUser, VillagerType villagerType, Material material) { + int amount = countTotalBlocksInInventory(player.getInventory(), material); + if (amount == 0) { + return 0; + } + Price price = Utilities.getPrice(new ItemStack(material, amount), WorthConfig.sell); + if (price == null) { + Logger.warning("Unable to sell %s for % due to missing worth data.", + material.name(), villagerType.getName()); + return 0; + } + + int oldPoints = econUser.getPointsMap().getOrDefault(villagerType.getName(), 0); + int itemPts = price.getPoints(); + int transPts = (itemPts * amount) * -1; + double cost = price.calculatePriceThing(oldPoints, transPts, false, itemPts); + + Purchase purchase = new Purchase(material, cost, itemPts, transPts, amount); + + Economy econ = VillagerUI.getInstance().getEconomy(); + double finalPrice = purchase.price(); + if (!player.hasPermission("villagerui.autosell.full-price")) { + finalPrice /= 2; + } + econ.depositPlayer(player, finalPrice); + econUser.addPoints(villagerType.getName(), purchase.totalPointCost()); + + player.getInventory().remove(material); + player.updateInventory(); + + int newPoints = econUser.getPointsMap().get(villagerType.getName()); + + BukkitScheduler scheduler = Bukkit.getServer().getScheduler(); + double effectivelyFinalPrice = finalPrice; + scheduler.callSyncMethod(VillagerUI.getInstance(), () -> { + Bukkit.getServer().getPluginManager() + .callEvent(new SpawnShopEvent(player, purchase, oldPoints, newPoints, effectivelyFinalPrice)); + return null; + }); + + return finalPrice; + } + + private static int countTotalBlocksInInventory(Inventory inventory, Material material) { + return Arrays.stream(inventory.getContents()) + .filter(Objects::nonNull) + .filter(itemStack -> itemStack.getType().equals(material)) + .mapToInt(ItemStack::getAmount) + .sum(); + } + +} diff --git a/src/main/java/com/alttd/util/auto_sell/AutoSellTask.java b/src/main/java/com/alttd/util/auto_sell/AutoSellTask.java new file mode 100644 index 0000000..bcdadcb --- /dev/null +++ b/src/main/java/com/alttd/util/auto_sell/AutoSellTask.java @@ -0,0 +1,94 @@ +package com.alttd.util.auto_sell; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AutoSellTask extends BukkitRunnable { + + private final HashMap> autoSellMap = new HashMap<>(); + private final Set autoSellPlayers = new HashSet<>(); + + @Override + public void run() { + runAutoSell(); + } + + public synchronized void putAutoSellPlayer(UUID uuid) { + autoSellPlayers.add(uuid); + } + + public synchronized void runAutoSell() { + Bukkit.getOnlinePlayers() + .stream() + .filter(player -> autoSellPlayers.contains(player.getUniqueId())) + .forEach(player -> { + Set materials = autoSellMap.get(player.getUniqueId()); + if (materials != null && !materials.isEmpty()) { + AutoSell.autoSell(player, materials); + } + }); + autoSellPlayers.clear(); + } + + public boolean hasAutoSellEntry(UUID uuid) { + if (!autoSellMap.containsKey(uuid)) { + return false; + } + return !autoSellMap.get(uuid).isEmpty(); + } + + public boolean toggleAutoSell(UUID uuid, Material material) { + Set materials = autoSellMap.computeIfAbsent(uuid, k -> new HashSet<>()); + if (materials.contains(material)) { + materials.remove(material); + return false; + } else { + materials.add(material); + return true; + } + } + + public Set getAutoSellList(UUID uuid) { + return autoSellMap.getOrDefault(uuid, new HashSet<>()); + } + + public boolean autoSellEnabled(UUID uuid, @NotNull Material type) { + if (!autoSellMap.containsKey(uuid)) { + return false; + } + Set materials = autoSellMap.getOrDefault(uuid, new HashSet<>()); + if (materials.isEmpty()) { + return false; + } + return materials.contains(type); + } + + public int getEntries(UUID uuid) { + Set materials = autoSellMap.get(uuid); + if (materials == null || materials.isEmpty()) { + return 0; + } + return materials.size(); + } + + public void removeAutoSell(UUID uuid, Material material) { + Set materials = autoSellMap.get(uuid); + if (materials == null) { + return; + } + materials.remove(material); + } + + public synchronized void clearAutoSell(Player player) { + autoSellPlayers.remove(player.getUniqueId()); + autoSellMap.remove(player.getUniqueId()); + } +}