Add AutoSell feature with commands and tasks for automated item selling

This commit is contained in:
akastijn 2025-07-23 03:11:46 +02:00
parent 36c92fcdb3
commit 1909c6d5bc
8 changed files with 406 additions and 7 deletions

View File

@ -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() {

View File

@ -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<SubCommand> 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());

View File

@ -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<Material> materials = autoSellTask.getAutoSellList(uuid);
List<Component> 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<String> getTabComplete(CommandSender commandSender, String[] args) {
if (args.length != 2) {
return List.of();
}
ArrayList<String> 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 "";
}
}

View File

@ -68,6 +68,7 @@ public final class Config extends AbstractConfig {
public static String HELP_MESSAGE_WRAPPER = "<gold>VillagerShopUI help:\n<commands></gold>";
public static String HELP_MESSAGE = "<green>Show this menu: <gold>/villagerui help</gold></green>";
public static String POINTS_MESSAGE = "<green>Show points: <gold>/villagerui points [villagerType]</green>";
public static String AUTO_SELL_MESSAGE = "<green>Toggle auto sell per item: <gold>/villagerui autosell <block></green>";
public static String BUY_MESSAGE = "<green>Check where/if you can buy an item: <gold>/villagerui buy <item_name></green>";
public static String SELL_MESSAGE = "<green>Check where/if you can sell an item: <gold>/villagerui sell <item_name></green>";
public static String RELOAD_MESSAGE = "<green>Reload configs: <gold>/villagerui reload</gold></green>";
@ -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 = "<red>Loading your economy data, please wait...</red>";
public static String NO_VILLAGER_POINTS = "<red>You don't have any villager points.</red>";
public static String NOTIFY_POINTS_RESET = "<green>Your points for <villager_type> reset to 0!</green>";
public static String NO_AUTO_SELL_ITEMS = "<green>You don't have any items set to auto sell!</green>";
public static String LIST_AUTO_SELL_ITEMS = "<green>You have the following items enabled to auto sell</green>";
public static String LIST_AUTO_SELL_COMPONENT = "<yellow><material></yellow>";
public static String INVALID_AUTO_SELL_ITEM = "<red><material> is not a valid auto sell item</red>";
public static String TURNED_ON_AUTO_SELL_FOR = "<yellow>Turned <green>on</green> auto sell for <material></yellow>";
public static String TURNED_OFF_AUTO_SELL_FOR = "<yellow>Turned <red>off</red> auto sell for <material></yellow>";
public static String AUTO_SOLD = "<green>You made <total_money_made> from auto selling</green>";
public static String MAX_AUTO_SELL_REACHED = "<red>You can only auto sell up to <max_auto_sell> items at a time</red>";
public static String DISABLED_AUTO_SELL = "<green>You have disabled auto sell</green>";
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() {

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<Material> 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();
}
}

View File

@ -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<UUID, Set<Material>> autoSellMap = new HashMap<>();
private final Set<UUID> 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<Material> 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<Material> materials = autoSellMap.computeIfAbsent(uuid, k -> new HashSet<>());
if (materials.contains(material)) {
materials.remove(material);
return false;
} else {
materials.add(material);
return true;
}
}
public Set<Material> getAutoSellList(UUID uuid) {
return autoSellMap.getOrDefault(uuid, new HashSet<>());
}
public boolean autoSellEnabled(UUID uuid, @NotNull Material type) {
if (!autoSellMap.containsKey(uuid)) {
return false;
}
Set<Material> materials = autoSellMap.getOrDefault(uuid, new HashSet<>());
if (materials.isEmpty()) {
return false;
}
return materials.contains(type);
}
public int getEntries(UUID uuid) {
Set<Material> materials = autoSellMap.get(uuid);
if (materials == null || materials.isEmpty()) {
return 0;
}
return materials.size();
}
public void removeAutoSell(UUID uuid, Material material) {
Set<Material> 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());
}
}