E
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.yourname</groupId>
|
||||
<artifactId>dirt-auctions</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Dirt Auctions</name>
|
||||
<description>Premium GUI-first auction house plugin for modern Paper servers.</description>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.21.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.46.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>DirtAuctions-${project.version}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<release>21</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.yourname.premiumah;
|
||||
|
||||
import com.yourname.premiumah.command.AhAdminCommand;
|
||||
import com.yourname.premiumah.command.AhCommand;
|
||||
import com.yourname.premiumah.config.ConfigManager;
|
||||
import com.yourname.premiumah.config.MessageManager;
|
||||
import com.yourname.premiumah.economy.EconomyService;
|
||||
import com.yourname.premiumah.economy.NoopEconomyService;
|
||||
import com.yourname.premiumah.economy.VaultEconomyService;
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import com.yourname.premiumah.listener.ChatInputListener;
|
||||
import com.yourname.premiumah.listener.InventoryGuiListener;
|
||||
import com.yourname.premiumah.listener.PlayerSessionListener;
|
||||
import com.yourname.premiumah.manager.AuctionHouseManager;
|
||||
import com.yourname.premiumah.manager.ClaimManager;
|
||||
import com.yourname.premiumah.manager.ListingManager;
|
||||
import com.yourname.premiumah.storage.StorageManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public final class PremiumAHPlugin extends JavaPlugin {
|
||||
private ConfigManager configManager;
|
||||
private MessageManager messageManager;
|
||||
private StorageManager storageManager;
|
||||
private ListingManager listingManager;
|
||||
private ClaimManager claimManager;
|
||||
private EconomyService economyService;
|
||||
private AuctionHouseManager auctionHouseManager;
|
||||
private GuiManager guiManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.configManager = new ConfigManager(this);
|
||||
configManager.reload();
|
||||
this.messageManager = new MessageManager(this, configManager);
|
||||
messageManager.reload();
|
||||
|
||||
this.storageManager = new StorageManager(this);
|
||||
storageManager.load();
|
||||
this.listingManager = new ListingManager(storageManager);
|
||||
this.claimManager = new ClaimManager(storageManager);
|
||||
listingManager.loadFromStorage();
|
||||
claimManager.loadFromStorage();
|
||||
|
||||
this.economyService = createEconomyService();
|
||||
economyService.reload();
|
||||
|
||||
this.auctionHouseManager = new AuctionHouseManager(this, configManager, messageManager, storageManager, listingManager, claimManager, economyService);
|
||||
this.guiManager = new GuiManager(this, configManager, messageManager, listingManager, claimManager, auctionHouseManager);
|
||||
|
||||
registerCommands();
|
||||
registerListeners();
|
||||
auctionHouseManager.startTasks();
|
||||
|
||||
if (!auctionHouseManager.marketplaceReady() && configManager.requireEconomy()) {
|
||||
getLogger().warning("No usable economy provider was found. Players can open GUIs and claims, but listing and buying are disabled.");
|
||||
}
|
||||
getLogger().info("Dirt Auctions enabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (auctionHouseManager != null) {
|
||||
auctionHouseManager.stopTasks();
|
||||
}
|
||||
Bukkit.getScheduler().cancelTasks(this);
|
||||
if (guiManager != null) {
|
||||
guiManager.shutdown();
|
||||
}
|
||||
if (auctionHouseManager != null) {
|
||||
auctionHouseManager.saveAll();
|
||||
}
|
||||
if (storageManager != null) {
|
||||
storageManager.close();
|
||||
}
|
||||
if (listingManager != null) {
|
||||
listingManager.clear();
|
||||
}
|
||||
if (claimManager != null) {
|
||||
claimManager.clear();
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
getLogger().info("Dirt Auctions disabled cleanly.");
|
||||
}
|
||||
|
||||
public void reloadPluginState() {
|
||||
if (auctionHouseManager != null) {
|
||||
auctionHouseManager.stopTasks();
|
||||
auctionHouseManager.saveAll();
|
||||
}
|
||||
if (guiManager != null) {
|
||||
guiManager.shutdown();
|
||||
}
|
||||
|
||||
configManager.reload();
|
||||
messageManager.reload();
|
||||
storageManager.load();
|
||||
listingManager.loadFromStorage();
|
||||
claimManager.loadFromStorage();
|
||||
economyService.reload();
|
||||
auctionHouseManager.startTasks();
|
||||
}
|
||||
|
||||
public ConfigManager configManager() {
|
||||
return configManager;
|
||||
}
|
||||
|
||||
public MessageManager messageManager() {
|
||||
return messageManager;
|
||||
}
|
||||
|
||||
public ListingManager listingManager() {
|
||||
return listingManager;
|
||||
}
|
||||
|
||||
public ClaimManager claimManager() {
|
||||
return claimManager;
|
||||
}
|
||||
|
||||
public AuctionHouseManager auctionHouseManager() {
|
||||
return auctionHouseManager;
|
||||
}
|
||||
|
||||
public GuiManager guiManager() {
|
||||
return guiManager;
|
||||
}
|
||||
|
||||
private EconomyService createEconomyService() {
|
||||
if (!configManager.economyEnabled()) {
|
||||
getLogger().warning("Economy is disabled in config.yml. Marketplace listing and buying are disabled.");
|
||||
return new NoopEconomyService();
|
||||
}
|
||||
try {
|
||||
return new VaultEconomyService(this);
|
||||
} catch (NoClassDefFoundError error) {
|
||||
getLogger().warning("Vault API is not available. Install Vault or a compatible injector for CMI economy support.");
|
||||
return new NoopEconomyService();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
AhCommand ahCommand = new AhCommand(guiManager, auctionHouseManager, messageManager);
|
||||
PluginCommand ah = getCommand("ah");
|
||||
if (ah != null) {
|
||||
ah.setExecutor(ahCommand);
|
||||
ah.setTabCompleter(ahCommand);
|
||||
}
|
||||
|
||||
AhAdminCommand ahAdminCommand = new AhAdminCommand(this, guiManager, auctionHouseManager, listingManager, messageManager);
|
||||
PluginCommand ahAdmin = getCommand("ahadmin");
|
||||
if (ahAdmin != null) {
|
||||
ahAdmin.setExecutor(ahAdminCommand);
|
||||
ahAdmin.setTabCompleter(ahAdminCommand);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
Bukkit.getPluginManager().registerEvents(new InventoryGuiListener(guiManager), this);
|
||||
Bukkit.getPluginManager().registerEvents(new ChatInputListener(guiManager), this);
|
||||
Bukkit.getPluginManager().registerEvents(new PlayerSessionListener(guiManager), this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.yourname.premiumah.command;
|
||||
|
||||
import com.yourname.premiumah.PremiumAHPlugin;
|
||||
import com.yourname.premiumah.config.MessageManager;
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import com.yourname.premiumah.manager.AuctionHouseManager;
|
||||
import com.yourname.premiumah.manager.ListingManager;
|
||||
import com.yourname.premiumah.model.ActionResult;
|
||||
import com.yourname.premiumah.model.Listing;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class AhAdminCommand implements TabExecutor {
|
||||
private final PremiumAHPlugin plugin;
|
||||
private final GuiManager guiManager;
|
||||
private final AuctionHouseManager auctionHouse;
|
||||
private final ListingManager listingManager;
|
||||
private final MessageManager messages;
|
||||
|
||||
public AhAdminCommand(PremiumAHPlugin plugin,
|
||||
GuiManager guiManager,
|
||||
AuctionHouseManager auctionHouse,
|
||||
ListingManager listingManager,
|
||||
MessageManager messages) {
|
||||
this.plugin = plugin;
|
||||
this.guiManager = guiManager;
|
||||
this.auctionHouse = auctionHouse;
|
||||
this.listingManager = listingManager;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
|
||||
messages.send(sender, "admin-help");
|
||||
return true;
|
||||
}
|
||||
String sub = args[0].toLowerCase(Locale.ROOT);
|
||||
switch (sub) {
|
||||
case "reload" -> reload(sender);
|
||||
case "remove" -> remove(sender, args);
|
||||
case "forceexpire" -> forceExpire(sender, args);
|
||||
case "view" -> view(sender, args);
|
||||
default -> messages.send(sender, "admin-help");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reload(CommandSender sender) {
|
||||
if (!sender.hasPermission("premiumah.admin.reload")) {
|
||||
messages.send(sender, "no-permission");
|
||||
return;
|
||||
}
|
||||
plugin.reloadPluginState();
|
||||
messages.send(sender, "reload-complete");
|
||||
}
|
||||
|
||||
private void remove(CommandSender sender, String[] args) {
|
||||
if (!sender.hasPermission("premiumah.admin.remove")) {
|
||||
messages.send(sender, "no-permission");
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
messages.send(sender, "admin-help");
|
||||
return;
|
||||
}
|
||||
ActionResult result = auctionHouse.adminRemoveListing(args[1]);
|
||||
messages.send(sender, result.messageKey(), result.placeholders());
|
||||
}
|
||||
|
||||
private void forceExpire(CommandSender sender, String[] args) {
|
||||
if (!sender.hasPermission("premiumah.admin.forceexpire")) {
|
||||
messages.send(sender, "no-permission");
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
messages.send(sender, "admin-help");
|
||||
return;
|
||||
}
|
||||
ActionResult result = auctionHouse.forceExpireListing(args[1]);
|
||||
messages.send(sender, result.messageKey(), result.placeholders());
|
||||
}
|
||||
|
||||
private void view(CommandSender sender, String[] args) {
|
||||
if (!sender.hasPermission("premiumah.admin.view")) {
|
||||
messages.send(sender, "no-permission");
|
||||
return;
|
||||
}
|
||||
if (!(sender instanceof Player player)) {
|
||||
messages.send(sender, "player-only");
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
guiManager.openAdmin(player, 0);
|
||||
return;
|
||||
}
|
||||
guiManager.openAdminForSeller(player, args[1], 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return filter(List.of("reload", "remove", "view", "forceexpire", "help"), args[0]);
|
||||
}
|
||||
if (args.length == 2 && (args[0].equalsIgnoreCase("remove") || args[0].equalsIgnoreCase("forceexpire"))) {
|
||||
return filter(listingManager.all().stream().map(Listing::id).limit(50).toList(), args[1]);
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("view")) {
|
||||
return filter(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(), args[1]);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private List<String> filter(List<String> values, String prefix) {
|
||||
String normalized = prefix.toLowerCase(Locale.ROOT);
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String value : values) {
|
||||
if (value.toLowerCase(Locale.ROOT).startsWith(normalized)) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.yourname.premiumah.command;
|
||||
|
||||
import com.yourname.premiumah.config.MessageManager;
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import com.yourname.premiumah.manager.AuctionHouseManager;
|
||||
import com.yourname.premiumah.model.ListingCreationResult;
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import com.yourname.premiumah.util.InventoryUtil;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public final class AhCommand implements TabExecutor {
|
||||
private final GuiManager guiManager;
|
||||
private final AuctionHouseManager auctionHouse;
|
||||
private final MessageManager messages;
|
||||
|
||||
public AhCommand(GuiManager guiManager, AuctionHouseManager auctionHouse, MessageManager messages) {
|
||||
this.guiManager = guiManager;
|
||||
this.auctionHouse = auctionHouse;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
messages.send(sender, "player-only");
|
||||
return true;
|
||||
}
|
||||
if (!player.hasPermission("premiumah.use")) {
|
||||
messages.send(player, "no-permission");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
guiManager.openMain(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
String sub = args[0].toLowerCase(Locale.ROOT);
|
||||
switch (sub) {
|
||||
case "browse" -> guiManager.openBrowse(player, 0);
|
||||
case "listings", "my", "mine" -> {
|
||||
if (!player.hasPermission("premiumah.listings")) {
|
||||
messages.send(player, "no-permission");
|
||||
return true;
|
||||
}
|
||||
guiManager.openMyListings(player, 0);
|
||||
}
|
||||
case "expired", "claims", "claim" -> {
|
||||
if (!player.hasPermission("premiumah.expired")) {
|
||||
messages.send(player, "no-permission");
|
||||
return true;
|
||||
}
|
||||
guiManager.openClaims(player, 0);
|
||||
}
|
||||
case "sell" -> handleSell(player, args);
|
||||
case "sort" -> handleSort(player, args);
|
||||
default -> guiManager.openMain(player);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleSell(Player player, String[] args) {
|
||||
if (!player.hasPermission("premiumah.sell")) {
|
||||
messages.send(player, "no-permission");
|
||||
return;
|
||||
}
|
||||
if (args.length == 1) {
|
||||
guiManager.openSell(player, true);
|
||||
return;
|
||||
}
|
||||
double price;
|
||||
try {
|
||||
price = Double.parseDouble(args[1].replace(",", ""));
|
||||
} catch (NumberFormatException exception) {
|
||||
messages.send(player, "usage-sell");
|
||||
return;
|
||||
}
|
||||
ItemStack item = player.getInventory().getItemInMainHand();
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
messages.send(player, "no-item-in-hand");
|
||||
return;
|
||||
}
|
||||
ListingCreationResult result = auctionHouse.createListing(player, item, price);
|
||||
if (result.success()) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
}
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
}
|
||||
|
||||
private void handleSort(Player player, String[] args) {
|
||||
if (args.length < 2) {
|
||||
messages.send(player, "invalid-sort", Map.of());
|
||||
return;
|
||||
}
|
||||
SortMode mode = SortMode.fromString(args[1], null);
|
||||
if (mode == null) {
|
||||
messages.send(player, "invalid-sort", Map.of());
|
||||
return;
|
||||
}
|
||||
guiManager.setSort(player, mode);
|
||||
guiManager.openBrowse(player, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (args.length == 1) {
|
||||
return filter(List.of("browse", "sell", "listings", "expired", "claims", "sort"), args[0]);
|
||||
}
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("sort")) {
|
||||
return filter(List.of("newest", "oldest", "lowest_price", "highest_price"), args[1]);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private List<String> filter(List<String> values, String prefix) {
|
||||
String normalized = prefix.toLowerCase(Locale.ROOT);
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String value : values) {
|
||||
if (value.toLowerCase(Locale.ROOT).startsWith(normalized)) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.yourname.premiumah.config;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ButtonConfig(Material material, String name, List<String> lore) {
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package com.yourname.premiumah.config;
|
||||
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import com.yourname.premiumah.util.MaterialUtil;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public final class ConfigManager {
|
||||
private final JavaPlugin plugin;
|
||||
private FileConfiguration config;
|
||||
private Set<Material> restrictedMaterials = Set.of();
|
||||
private List<LimitPermission> limitPermissions = List.of();
|
||||
private List<Integer> listingSlots = List.of();
|
||||
|
||||
public ConfigManager(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
this.config = plugin.getConfig();
|
||||
this.restrictedMaterials = loadRestrictedMaterials();
|
||||
this.limitPermissions = loadLimitPermissions();
|
||||
this.listingSlots = loadListingSlots();
|
||||
}
|
||||
|
||||
public FileConfiguration raw() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public boolean debug() {
|
||||
return config.getBoolean("settings.debug", false);
|
||||
}
|
||||
|
||||
public String prefix() {
|
||||
return config.getString("settings.command-prefix", "<#c79542>Dirt Auctions <#6b7280>»");
|
||||
}
|
||||
|
||||
public boolean economyEnabled() {
|
||||
return config.getBoolean("economy.enabled", true);
|
||||
}
|
||||
|
||||
public boolean requireEconomy() {
|
||||
return config.getBoolean("economy.require-economy", true);
|
||||
}
|
||||
|
||||
public boolean allowSellerSelfPurchase() {
|
||||
return config.getBoolean("settings.allow-seller-self-purchase", false);
|
||||
}
|
||||
|
||||
public boolean requireInventorySpaceToBuy() {
|
||||
return config.getBoolean("settings.require-inventory-space-to-buy", false);
|
||||
}
|
||||
|
||||
public boolean claimFullInventoryPurchases() {
|
||||
return config.getString("claims.buyer-full-inventory-action", "CLAIM").equalsIgnoreCase("CLAIM");
|
||||
}
|
||||
|
||||
public boolean instantSellerPayment() {
|
||||
String action = config.getString("claims.seller-payment-action", "INSTANT");
|
||||
return config.getBoolean("economy.instant-seller-payment", true) && !"CLAIM".equalsIgnoreCase(action);
|
||||
}
|
||||
|
||||
public int chatPriceTimeoutSeconds() {
|
||||
return Math.max(5, config.getInt("settings.chat-price-timeout-seconds", 45));
|
||||
}
|
||||
|
||||
public long clickDebounceMillis() {
|
||||
return Math.max(0L, config.getLong("settings.click-debounce-millis", 350L));
|
||||
}
|
||||
|
||||
public long listingExpireCheckTicks() {
|
||||
long seconds = Math.max(10L, config.getLong("settings.listing-expire-check-seconds", 60L));
|
||||
return seconds * 20L;
|
||||
}
|
||||
|
||||
public SortMode defaultSort() {
|
||||
return SortMode.fromString(config.getString("settings.default-sort", "NEWEST"), SortMode.NEWEST);
|
||||
}
|
||||
|
||||
public long defaultListingDurationMillis() {
|
||||
long seconds = Math.max(60L, config.getLong("listings.default-duration-seconds", 604800L));
|
||||
return seconds * 1000L;
|
||||
}
|
||||
|
||||
public boolean allowCancelActiveListings() {
|
||||
return config.getBoolean("listings.allow-cancel-active-listings", true);
|
||||
}
|
||||
|
||||
public boolean reclaimAdminRemovedItems() {
|
||||
return config.getBoolean("listings.reclaim-admin-removed-items", true);
|
||||
}
|
||||
|
||||
public double minPrice() {
|
||||
return Math.max(0.0D, config.getDouble("economy.price.min", 1.0D));
|
||||
}
|
||||
|
||||
public double maxPrice() {
|
||||
return Math.max(minPrice(), config.getDouble("economy.price.max", 1_000_000_000.0D));
|
||||
}
|
||||
|
||||
public boolean listingFeeEnabled() {
|
||||
return config.getBoolean("economy.listing-fee.enabled", false);
|
||||
}
|
||||
|
||||
public double listingFee() {
|
||||
return Math.max(0.0D, config.getDouble("economy.listing-fee.amount", 0.0D));
|
||||
}
|
||||
|
||||
public boolean salesTaxEnabled() {
|
||||
return config.getBoolean("economy.sales-tax.enabled", false);
|
||||
}
|
||||
|
||||
public double salesTaxPercent() {
|
||||
return Math.max(0.0D, Math.min(100.0D, config.getDouble("economy.sales-tax.percent", 0.0D)));
|
||||
}
|
||||
|
||||
public boolean soundsEnabled() {
|
||||
return config.getBoolean("sounds.enabled", true);
|
||||
}
|
||||
|
||||
public String soundName(String key) {
|
||||
return config.getString("sounds." + key, "");
|
||||
}
|
||||
|
||||
public int guiSize(String key) {
|
||||
int requested = config.getInt("gui.size." + key, 54);
|
||||
int clamped = Math.max(9, Math.min(54, requested));
|
||||
return clamped - (clamped % 9);
|
||||
}
|
||||
|
||||
public String guiTitle(String key) {
|
||||
return config.getString("gui.titles." + key, key);
|
||||
}
|
||||
|
||||
public boolean fillerEnabled() {
|
||||
return config.getBoolean("gui.filler.enabled", true);
|
||||
}
|
||||
|
||||
public Material fillerMaterial() {
|
||||
return MaterialUtil.parse(config.getString("gui.filler.material", "BLACK_STAINED_GLASS_PANE"))
|
||||
.orElse(Material.BLACK_STAINED_GLASS_PANE);
|
||||
}
|
||||
|
||||
public String fillerName() {
|
||||
return config.getString("gui.filler.name", " ");
|
||||
}
|
||||
|
||||
public List<Integer> listingSlots() {
|
||||
return listingSlots;
|
||||
}
|
||||
|
||||
public ButtonConfig button(String key) {
|
||||
String path = "gui.buttons." + key;
|
||||
Material material = MaterialUtil.parse(config.getString(path + ".material", "STONE")).orElse(Material.STONE);
|
||||
String name = config.getString(path + ".name", key);
|
||||
List<String> lore = config.getStringList(path + ".lore");
|
||||
return new ButtonConfig(material, name, lore);
|
||||
}
|
||||
|
||||
public List<String> guiLore(String key) {
|
||||
return config.getStringList("gui-lore." + key);
|
||||
}
|
||||
|
||||
public int listingLimit(Player player) {
|
||||
int limit = Math.max(0, config.getInt("listing-limits.default", 5));
|
||||
for (LimitPermission permission : limitPermissions) {
|
||||
if (player.hasPermission(permission.permission())) {
|
||||
limit = Math.max(limit, permission.amount());
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
public boolean isItemAllowed(Player player, Material material) {
|
||||
if (player.hasPermission("premiumah.bypass.restrictions")) {
|
||||
return true;
|
||||
}
|
||||
String mode = config.getString("item-restrictions.mode", "BLACKLIST").toUpperCase(Locale.ROOT);
|
||||
if ("WHITELIST".equals(mode)) {
|
||||
return restrictedMaterials.contains(material);
|
||||
}
|
||||
return !restrictedMaterials.contains(material);
|
||||
}
|
||||
|
||||
private Set<Material> loadRestrictedMaterials() {
|
||||
Set<Material> materials = new HashSet<>();
|
||||
for (String entry : config.getStringList("item-restrictions.materials")) {
|
||||
MaterialUtil.parse(entry).ifPresent(materials::add);
|
||||
}
|
||||
return Collections.unmodifiableSet(materials);
|
||||
}
|
||||
|
||||
private List<LimitPermission> loadLimitPermissions() {
|
||||
List<LimitPermission> permissions = new ArrayList<>();
|
||||
for (var map : config.getMapList("listing-limits.permissions")) {
|
||||
Object permission = map.get("permission");
|
||||
Object amount = map.get("amount");
|
||||
if (permission instanceof String permissionString && amount instanceof Number number) {
|
||||
permissions.add(new LimitPermission(permissionString, Math.max(0, number.intValue())));
|
||||
}
|
||||
}
|
||||
return List.copyOf(permissions);
|
||||
}
|
||||
|
||||
private List<Integer> loadListingSlots() {
|
||||
List<Integer> slots = new ArrayList<>();
|
||||
for (int slot : config.getIntegerList("gui.listing-slots")) {
|
||||
if (slot >= 0 && slot < 54) {
|
||||
slots.add(slot);
|
||||
}
|
||||
}
|
||||
if (slots.isEmpty()) {
|
||||
for (int slot = 10; slot <= 34; slot++) {
|
||||
int column = slot % 9;
|
||||
if (column > 0 && column < 8) {
|
||||
slots.add(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return List.copyOf(slots);
|
||||
}
|
||||
|
||||
public ConfigurationSection section(String path) {
|
||||
return config.getConfigurationSection(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.yourname.premiumah.config;
|
||||
|
||||
public record LimitPermission(String permission, int amount) {
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.yourname.premiumah.config;
|
||||
|
||||
import com.yourname.premiumah.util.TextUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class MessageManager {
|
||||
private final JavaPlugin plugin;
|
||||
private final ConfigManager configManager;
|
||||
private File file;
|
||||
private YamlConfiguration messages;
|
||||
|
||||
public MessageManager(JavaPlugin plugin, ConfigManager configManager) {
|
||||
this.plugin = plugin;
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.file = new File(plugin.getDataFolder(), "messages.yml");
|
||||
if (!file.exists()) {
|
||||
plugin.saveResource("messages.yml", false);
|
||||
}
|
||||
this.messages = YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
public void send(CommandSender sender, String key) {
|
||||
send(sender, key, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public void send(CommandSender sender, String key, Map<String, String> placeholders) {
|
||||
sender.sendMessage(message(key, placeholders));
|
||||
}
|
||||
|
||||
public void action(Player player, String key, Map<String, String> placeholders) {
|
||||
player.sendActionBar(message(key, placeholders));
|
||||
}
|
||||
|
||||
public Component message(String key, Map<String, String> placeholders) {
|
||||
String raw = messages.getString("messages." + key, "<#ef4444>Missing message: " + key);
|
||||
return component(apply(raw, placeholders));
|
||||
}
|
||||
|
||||
public List<Component> guiLore(String key, Map<String, String> placeholders) {
|
||||
return messages.getStringList("gui-lore." + key).stream()
|
||||
.map(line -> component(apply(line, placeholders)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<String> rawGuiLore(String key) {
|
||||
return messages.getStringList("gui-lore." + key);
|
||||
}
|
||||
|
||||
public Component component(String raw) {
|
||||
return TextUtil.component(apply(raw, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
public String legacy(String raw) {
|
||||
return TextUtil.legacy(apply(raw, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
public String legacyWithPlaceholders(String raw, Map<String, String> placeholders) {
|
||||
return TextUtil.legacy(apply(raw, placeholders));
|
||||
}
|
||||
|
||||
public Map<String, String> placeholders(String... pairs) {
|
||||
Map<String, String> placeholders = new HashMap<>();
|
||||
for (int i = 0; i + 1 < pairs.length; i += 2) {
|
||||
placeholders.put(pairs[i], pairs[i + 1]);
|
||||
}
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
public String apply(String raw, Map<String, String> placeholders) {
|
||||
String result = raw == null ? "" : raw;
|
||||
result = result.replace("{prefix}", configManager.prefix());
|
||||
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
|
||||
result = result.replace("{" + entry.getKey() + "}", entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yourname.premiumah.economy;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
public interface EconomyService {
|
||||
void reload();
|
||||
|
||||
boolean isAvailable();
|
||||
|
||||
String providerName();
|
||||
|
||||
boolean has(OfflinePlayer player, double amount);
|
||||
|
||||
boolean withdraw(OfflinePlayer player, double amount);
|
||||
|
||||
boolean deposit(OfflinePlayer player, double amount);
|
||||
|
||||
String format(double amount);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.yourname.premiumah.economy;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public final class NoopEconomyService implements EconomyService {
|
||||
private static final DecimalFormat FORMAT = new DecimalFormat("#,##0.##");
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String providerName() {
|
||||
return "Unavailable";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(OfflinePlayer player, double amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean withdraw(OfflinePlayer player, double amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deposit(OfflinePlayer player, double amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(double amount) {
|
||||
return "$" + FORMAT.format(amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.yourname.premiumah.economy;
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import net.milkbowl.vault.economy.EconomyResponse;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public final class VaultEconomyService implements EconomyService {
|
||||
private static final DecimalFormat FALLBACK_FORMAT = new DecimalFormat("#,##0.##");
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private Economy economy;
|
||||
|
||||
public VaultEconomyService(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
this.economy = null;
|
||||
if (Bukkit.getPluginManager().getPlugin("Vault") == null) {
|
||||
plugin.getLogger().warning("Vault is not installed. Dirt Auctions marketplace functions are disabled.");
|
||||
return;
|
||||
}
|
||||
RegisteredServiceProvider<Economy> registration = Bukkit.getServicesManager().getRegistration(Economy.class);
|
||||
if (registration == null || registration.getProvider() == null) {
|
||||
plugin.getLogger().warning("Vault is installed, but no economy provider is registered. Dirt Auctions marketplace functions are disabled.");
|
||||
return;
|
||||
}
|
||||
this.economy = registration.getProvider();
|
||||
plugin.getLogger().info("Hooked economy provider: " + economy.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return economy != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String providerName() {
|
||||
return economy == null ? "Vault" : economy.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(OfflinePlayer player, double amount) {
|
||||
if (amount <= 0.0D) {
|
||||
return true;
|
||||
}
|
||||
return economy != null && economy.has(player, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean withdraw(OfflinePlayer player, double amount) {
|
||||
if (amount <= 0.0D) {
|
||||
return true;
|
||||
}
|
||||
if (economy == null) {
|
||||
return false;
|
||||
}
|
||||
EconomyResponse response = economy.withdrawPlayer(player, amount);
|
||||
return response.transactionSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deposit(OfflinePlayer player, double amount) {
|
||||
if (amount <= 0.0D) {
|
||||
return true;
|
||||
}
|
||||
if (economy == null) {
|
||||
return false;
|
||||
}
|
||||
EconomyResponse response = economy.depositPlayer(player, amount);
|
||||
return response.transactionSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(double amount) {
|
||||
if (economy == null) {
|
||||
return "$" + FALLBACK_FORMAT.format(amount);
|
||||
}
|
||||
return economy.format(amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yourname.premiumah.gui;
|
||||
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import org.bukkit.Material;
|
||||
|
||||
public final class BrowseState {
|
||||
private SortMode sortMode;
|
||||
private Material filter;
|
||||
|
||||
public BrowseState(SortMode sortMode, Material filter) {
|
||||
this.sortMode = sortMode;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public SortMode sortMode() {
|
||||
return sortMode;
|
||||
}
|
||||
|
||||
public void sortMode(SortMode sortMode) {
|
||||
this.sortMode = sortMode;
|
||||
}
|
||||
|
||||
public Material filter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void filter(Material filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.yourname.premiumah.gui;
|
||||
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class GuiHolder implements InventoryHolder {
|
||||
private final GuiType type;
|
||||
private final UUID viewer;
|
||||
private final int page;
|
||||
private final String context;
|
||||
private final SortMode sortMode;
|
||||
private final Material filter;
|
||||
private final Map<Integer, String> listingSlots = new HashMap<>();
|
||||
private final Map<Integer, String> claimSlots = new HashMap<>();
|
||||
private Inventory inventory;
|
||||
|
||||
public GuiHolder(GuiType type, UUID viewer, int page, String context, SortMode sortMode, Material filter) {
|
||||
this.type = type;
|
||||
this.viewer = viewer;
|
||||
this.page = page;
|
||||
this.context = context;
|
||||
this.sortMode = sortMode;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public GuiType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public UUID viewer() {
|
||||
return viewer;
|
||||
}
|
||||
|
||||
public int page() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public String context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public SortMode sortMode() {
|
||||
return sortMode;
|
||||
}
|
||||
|
||||
public Material filter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public Map<Integer, String> listingSlots() {
|
||||
return listingSlots;
|
||||
}
|
||||
|
||||
public Map<Integer, String> claimSlots() {
|
||||
return claimSlots;
|
||||
}
|
||||
|
||||
public void inventory(Inventory inventory) {
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
package com.yourname.premiumah.gui;
|
||||
|
||||
import com.yourname.premiumah.config.ButtonConfig;
|
||||
import com.yourname.premiumah.config.ConfigManager;
|
||||
import com.yourname.premiumah.config.MessageManager;
|
||||
import com.yourname.premiumah.manager.AuctionHouseManager;
|
||||
import com.yourname.premiumah.manager.ClaimManager;
|
||||
import com.yourname.premiumah.manager.ListingManager;
|
||||
import com.yourname.premiumah.model.ActionResult;
|
||||
import com.yourname.premiumah.model.ClaimRecord;
|
||||
import com.yourname.premiumah.model.ClaimType;
|
||||
import com.yourname.premiumah.model.Listing;
|
||||
import com.yourname.premiumah.model.ListingCreationResult;
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import com.yourname.premiumah.util.InventoryUtil;
|
||||
import com.yourname.premiumah.util.MaterialUtil;
|
||||
import com.yourname.premiumah.util.TextUtil;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class GuiManager {
|
||||
private static final int SELL_ITEM_SLOT = 22;
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final ConfigManager config;
|
||||
private final MessageManager messages;
|
||||
private final ListingManager listings;
|
||||
private final ClaimManager claims;
|
||||
private final AuctionHouseManager auctionHouse;
|
||||
private final Map<UUID, BrowseState> browseStates = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, SellSession> sellSessions = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Long> clickDebounce = new ConcurrentHashMap<>();
|
||||
|
||||
public GuiManager(JavaPlugin plugin,
|
||||
ConfigManager config,
|
||||
MessageManager messages,
|
||||
ListingManager listings,
|
||||
ClaimManager claims,
|
||||
AuctionHouseManager auctionHouse) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.messages = messages;
|
||||
this.listings = listings;
|
||||
this.claims = claims;
|
||||
this.auctionHouse = auctionHouse;
|
||||
}
|
||||
|
||||
public void openMain(Player player) {
|
||||
GuiHolder holder = new GuiHolder(GuiType.MAIN, player.getUniqueId(), 0, null, null, null);
|
||||
Inventory inventory = create(holder, config.guiSize("main"), config.guiTitle("main"), Map.of());
|
||||
fill(inventory);
|
||||
inventory.setItem(11, button("browse", Map.of()));
|
||||
inventory.setItem(13, button("sell", Map.of()));
|
||||
inventory.setItem(15, button("my-listings", Map.of()));
|
||||
inventory.setItem(29, button("claims", Map.of()));
|
||||
if (player.hasPermission("premiumah.admin")) {
|
||||
inventory.setItem(31, button("admin", Map.of()));
|
||||
}
|
||||
inventory.setItem(33, button("close", Map.of()));
|
||||
player.openInventory(inventory);
|
||||
play(player, "open");
|
||||
}
|
||||
|
||||
public void openBrowse(Player player, int requestedPage) {
|
||||
BrowseState state = browseState(player);
|
||||
List<Listing> active = listings.activeListings(state.sortMode(), state.filter());
|
||||
openListingView(player, GuiType.BROWSE, active, requestedPage, "browse", null, state.sortMode(), state.filter());
|
||||
}
|
||||
|
||||
public void openMyListings(Player player, int requestedPage) {
|
||||
List<Listing> own = listings.activeListingsBySeller(player.getUniqueId(), SortMode.NEWEST);
|
||||
openListingView(player, GuiType.MY_LISTINGS, own, requestedPage, "my-listings", null, SortMode.NEWEST, null);
|
||||
}
|
||||
|
||||
public void openAdmin(Player player, int requestedPage) {
|
||||
BrowseState state = browseState(player);
|
||||
List<Listing> active = listings.activeListings(state.sortMode(), state.filter());
|
||||
openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", null, state.sortMode(), state.filter());
|
||||
}
|
||||
|
||||
public void openAdminForSeller(Player player, String sellerName, int requestedPage) {
|
||||
List<Listing> active = listings.activeListingsBySellerName(sellerName, SortMode.NEWEST);
|
||||
openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", sellerName, SortMode.NEWEST, null);
|
||||
}
|
||||
|
||||
public void openClaims(Player player, int requestedPage) {
|
||||
List<ClaimRecord> playerClaims = claims.claimsFor(player.getUniqueId());
|
||||
int page = normalizePage(requestedPage, playerClaims.size());
|
||||
GuiHolder holder = new GuiHolder(GuiType.CLAIMS, player.getUniqueId(), page, null, SortMode.NEWEST, null);
|
||||
Inventory inventory = create(holder, config.guiSize("claims"), config.guiTitle("claims"), Map.of("page", String.valueOf(page + 1)));
|
||||
fill(inventory);
|
||||
List<Integer> slots = config.listingSlots();
|
||||
int start = page * slots.size();
|
||||
for (int i = 0; i < slots.size(); i++) {
|
||||
int index = start + i;
|
||||
if (index >= playerClaims.size()) {
|
||||
break;
|
||||
}
|
||||
ClaimRecord claim = playerClaims.get(index);
|
||||
int slot = slots.get(i);
|
||||
holder.claimSlots().put(slot, claim.id());
|
||||
inventory.setItem(slot, claimItem(claim));
|
||||
}
|
||||
addPagedControls(inventory, page, playerClaims.size(), slots.size(), "back");
|
||||
player.openInventory(inventory);
|
||||
play(player, "open");
|
||||
}
|
||||
|
||||
public void openSell(Player player, boolean takeHeldItem) {
|
||||
if (!player.hasPermission("premiumah.sell")) {
|
||||
messages.send(player, "no-permission");
|
||||
play(player, "fail");
|
||||
return;
|
||||
}
|
||||
SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
|
||||
if (takeHeldItem && InventoryUtil.isAir(session.rawItem())) {
|
||||
ItemStack held = player.getInventory().getItemInMainHand();
|
||||
if (!InventoryUtil.isAir(held)) {
|
||||
session.item(held);
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
}
|
||||
}
|
||||
session.awaitingPrice(false);
|
||||
session.completed(false);
|
||||
|
||||
GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null);
|
||||
Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of());
|
||||
fill(inventory);
|
||||
inventory.setItem(SELL_ITEM_SLOT, session.item());
|
||||
inventory.setItem(20, customItem(Material.GOLD_INGOT, "<#f5d58a>Set Price", List.of(
|
||||
"<#9ca3af>Current: <#ffffff>" + (session.price() <= 0.0D ? "Not set" : auctionHouse.formatMoney(session.price())),
|
||||
"<#6ee7b7>Click to type a price."
|
||||
), Map.of()));
|
||||
inventory.setItem(24, button("confirm", Map.of()));
|
||||
inventory.setItem(36, button("back", Map.of()));
|
||||
inventory.setItem(40, button("cancel", Map.of()));
|
||||
player.openInventory(inventory);
|
||||
play(player, "open");
|
||||
}
|
||||
|
||||
public void openConfirmBuy(Player player, String listingId) {
|
||||
Listing listing = listings.get(listingId).orElse(null);
|
||||
if (listing == null || !listing.isActive(System.currentTimeMillis())) {
|
||||
messages.send(player, "listing-no-longer-available");
|
||||
play(player, "fail");
|
||||
openBrowse(player, 0);
|
||||
return;
|
||||
}
|
||||
GuiHolder holder = new GuiHolder(GuiType.CONFIRM_BUY, player.getUniqueId(), 0, listing.id(), null, null);
|
||||
Inventory inventory = create(holder, config.guiSize("confirm-buy"), config.guiTitle("confirm-buy"), Map.of());
|
||||
fill(inventory);
|
||||
inventory.setItem(11, button("confirm", Map.of()));
|
||||
inventory.setItem(13, listingItem(listing, "confirm-buy"));
|
||||
inventory.setItem(15, button("cancel", Map.of()));
|
||||
player.openInventory(inventory);
|
||||
play(player, "open");
|
||||
}
|
||||
|
||||
public void handleClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) {
|
||||
return;
|
||||
}
|
||||
int topSize = event.getView().getTopInventory().getSize();
|
||||
int rawSlot = event.getRawSlot();
|
||||
|
||||
if (holder.type() != GuiType.SELL && rawSlot >= topSize) {
|
||||
if (event.isShiftClick()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (holder.type() == GuiType.SELL && rawSlot >= topSize) {
|
||||
if (event.isShiftClick()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (holder.type() == GuiType.SELL && rawSlot == SELL_ITEM_SLOT) {
|
||||
event.setCancelled(false);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory()));
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
if (rawSlot < 0 || !canClick(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (holder.type()) {
|
||||
case MAIN -> handleMainClick(player, rawSlot);
|
||||
case BROWSE -> handleBrowseClick(player, holder, rawSlot, event.getClick());
|
||||
case MY_LISTINGS -> handleMyListingsClick(player, holder, rawSlot);
|
||||
case CLAIMS -> handleClaimsClick(player, holder, rawSlot);
|
||||
case SELL -> handleSellClick(player, event.getView().getTopInventory(), rawSlot);
|
||||
case CONFIRM_BUY -> handleConfirmClick(player, holder, rawSlot);
|
||||
case ADMIN -> handleAdminClick(player, holder, rawSlot, event.getClick());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDrag(InventoryDragEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) {
|
||||
return;
|
||||
}
|
||||
int topSize = event.getView().getTopInventory().getSize();
|
||||
boolean touchesTop = event.getRawSlots().stream().anyMatch(slot -> slot < topSize);
|
||||
if (!touchesTop) {
|
||||
return;
|
||||
}
|
||||
if (holder.type() == GuiType.SELL && event.getRawSlots().stream().allMatch(slot -> slot == SELL_ITEM_SLOT || slot >= topSize)) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory()));
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public void handleClose(InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player) || !(event.getInventory().getHolder() instanceof GuiHolder holder)) {
|
||||
return;
|
||||
}
|
||||
if (holder.type() != GuiType.SELL) {
|
||||
return;
|
||||
}
|
||||
SellSession session = sellSessions.get(player.getUniqueId());
|
||||
if (session == null || session.awaitingPrice() || session.completed()) {
|
||||
return;
|
||||
}
|
||||
ItemStack item = event.getInventory().getItem(SELL_ITEM_SLOT);
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
item = session.item();
|
||||
}
|
||||
returnItem(player, item);
|
||||
sellSessions.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void handleChat(AsyncChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
SellSession session = sellSessions.get(player.getUniqueId());
|
||||
if (session == null || !session.awaitingPrice()) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
String input = PlainTextComponentSerializer.plainText().serialize(event.message()).trim();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> completePricePrompt(player, input));
|
||||
}
|
||||
|
||||
public void setSort(Player player, SortMode sortMode) {
|
||||
browseState(player).sortMode(sortMode);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (player.getOpenInventory().getTopInventory().getHolder() instanceof GuiHolder) {
|
||||
player.closeInventory();
|
||||
}
|
||||
}
|
||||
for (UUID uuid : new ArrayList<>(sellSessions.keySet())) {
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
SellSession session = sellSessions.remove(uuid);
|
||||
if (player != null && session != null && !InventoryUtil.isAir(session.rawItem())) {
|
||||
returnItem(player, session.rawItem());
|
||||
}
|
||||
if (session != null) {
|
||||
session.cancelTimeout();
|
||||
}
|
||||
}
|
||||
sellSessions.clear();
|
||||
browseStates.clear();
|
||||
clickDebounce.clear();
|
||||
}
|
||||
|
||||
public void handleQuit(Player player) {
|
||||
SellSession session = sellSessions.remove(player.getUniqueId());
|
||||
if (session != null) {
|
||||
session.cancelTimeout();
|
||||
if (!InventoryUtil.isAir(session.rawItem())) {
|
||||
returnItem(player, session.rawItem());
|
||||
}
|
||||
}
|
||||
browseStates.remove(player.getUniqueId());
|
||||
clickDebounce.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
private void handleMainClick(Player player, int slot) {
|
||||
play(player, "click");
|
||||
if (slot == 11) {
|
||||
openBrowse(player, 0);
|
||||
} else if (slot == 13) {
|
||||
openSell(player, true);
|
||||
} else if (slot == 15) {
|
||||
openMyListings(player, 0);
|
||||
} else if (slot == 29) {
|
||||
openClaims(player, 0);
|
||||
} else if (slot == 31 && player.hasPermission("premiumah.admin")) {
|
||||
openAdmin(player, 0);
|
||||
} else if (slot == 33) {
|
||||
player.closeInventory();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBrowseClick(Player player, GuiHolder holder, int slot, ClickType click) {
|
||||
if (holder.listingSlots().containsKey(slot)) {
|
||||
Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null);
|
||||
if (listing == null) {
|
||||
messages.send(player, "listing-no-longer-available");
|
||||
play(player, "fail");
|
||||
openBrowse(player, holder.page());
|
||||
return;
|
||||
}
|
||||
if (click.isRightClick() && player.hasPermission("premiumah.admin.remove")) {
|
||||
ActionResult result = auctionHouse.adminRemoveListing(listing.id());
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, result.success() ? "success" : "fail");
|
||||
openBrowse(player, holder.page());
|
||||
return;
|
||||
}
|
||||
if (click.isShiftClick()) {
|
||||
browseState(player).filter(listing.rawItem().getType());
|
||||
openBrowse(player, 0);
|
||||
return;
|
||||
}
|
||||
openConfirmBuy(player, listing.id());
|
||||
return;
|
||||
}
|
||||
handlePagedControl(player, holder, slot, GuiType.BROWSE);
|
||||
}
|
||||
|
||||
private void handleMyListingsClick(Player player, GuiHolder holder, int slot) {
|
||||
if (holder.listingSlots().containsKey(slot)) {
|
||||
ActionResult result = auctionHouse.cancelListing(player, holder.listingSlots().get(slot));
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, result.success() ? "success" : "fail");
|
||||
openMyListings(player, holder.page());
|
||||
return;
|
||||
}
|
||||
handlePagedControl(player, holder, slot, GuiType.MY_LISTINGS);
|
||||
}
|
||||
|
||||
private void handleClaimsClick(Player player, GuiHolder holder, int slot) {
|
||||
if (holder.claimSlots().containsKey(slot)) {
|
||||
ActionResult result = auctionHouse.claim(player, holder.claimSlots().get(slot));
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, result.success() ? "success" : "fail");
|
||||
openClaims(player, holder.page());
|
||||
return;
|
||||
}
|
||||
handlePagedControl(player, holder, slot, GuiType.CLAIMS);
|
||||
}
|
||||
|
||||
private void handleSellClick(Player player, Inventory inventory, int slot) {
|
||||
play(player, "click");
|
||||
if (slot == 20) {
|
||||
beginPricePrompt(player, inventory);
|
||||
} else if (slot == 24) {
|
||||
confirmSell(player, inventory);
|
||||
} else if (slot == 36) {
|
||||
closeSellReturningItem(player, inventory, true);
|
||||
openMain(player);
|
||||
} else if (slot == 40) {
|
||||
closeSellReturningItem(player, inventory, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConfirmClick(Player player, GuiHolder holder, int slot) {
|
||||
if (slot == 11) {
|
||||
ActionResult result = auctionHouse.buyListing(player, holder.context());
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, result.success() ? "success" : "fail");
|
||||
if (result.success()) {
|
||||
openBrowse(player, 0);
|
||||
} else {
|
||||
player.closeInventory();
|
||||
}
|
||||
} else if (slot == 15) {
|
||||
play(player, "click");
|
||||
openBrowse(player, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAdminClick(Player player, GuiHolder holder, int slot, ClickType click) {
|
||||
if (holder.listingSlots().containsKey(slot)) {
|
||||
Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null);
|
||||
if (listing == null) {
|
||||
messages.send(player, "listing-no-longer-available");
|
||||
openAdmin(player, holder.page());
|
||||
return;
|
||||
}
|
||||
if (click.isRightClick() || click.isShiftClick()) {
|
||||
ActionResult result = auctionHouse.adminRemoveListing(listing.id());
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, result.success() ? "success" : "fail");
|
||||
openAdmin(player, holder.page());
|
||||
} else {
|
||||
openConfirmBuy(player, listing.id());
|
||||
}
|
||||
return;
|
||||
}
|
||||
handlePagedControl(player, holder, slot, GuiType.ADMIN);
|
||||
}
|
||||
|
||||
private void handlePagedControl(Player player, GuiHolder holder, int slot, GuiType type) {
|
||||
play(player, "click");
|
||||
if (slot == 45) {
|
||||
openByType(player, type, Math.max(0, holder.page() - 1), holder.context());
|
||||
} else if (slot == 49) {
|
||||
openMain(player);
|
||||
} else if (slot == 53) {
|
||||
openByType(player, type, holder.page() + 1, holder.context());
|
||||
} else if (slot == 50 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) {
|
||||
BrowseState state = browseState(player);
|
||||
state.sortMode(state.sortMode().next());
|
||||
openByType(player, type, 0, holder.context());
|
||||
} else if (slot == 48 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) {
|
||||
browseState(player).filter(null);
|
||||
openByType(player, type, 0, holder.context());
|
||||
}
|
||||
}
|
||||
|
||||
private void openByType(Player player, GuiType type, int page, String context) {
|
||||
switch (type) {
|
||||
case BROWSE -> openBrowse(player, page);
|
||||
case MY_LISTINGS -> openMyListings(player, page);
|
||||
case CLAIMS -> openClaims(player, page);
|
||||
case ADMIN -> {
|
||||
if (context == null || context.isBlank()) {
|
||||
openAdmin(player, page);
|
||||
} else {
|
||||
openAdminForSeller(player, context, page);
|
||||
}
|
||||
}
|
||||
default -> openMain(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void openListingView(Player player,
|
||||
GuiType type,
|
||||
List<Listing> source,
|
||||
int requestedPage,
|
||||
String titleKey,
|
||||
String context,
|
||||
SortMode sortMode,
|
||||
Material filter) {
|
||||
int page = normalizePage(requestedPage, source.size());
|
||||
GuiHolder holder = new GuiHolder(type, player.getUniqueId(), page, context, sortMode, filter);
|
||||
Inventory inventory = create(holder, config.guiSize(titleKey), config.guiTitle(titleKey), Map.of("page", String.valueOf(page + 1)));
|
||||
fill(inventory);
|
||||
List<Integer> slots = config.listingSlots();
|
||||
int start = page * slots.size();
|
||||
String loreKey = type == GuiType.MY_LISTINGS ? "my-listing" : "listing";
|
||||
for (int i = 0; i < slots.size(); i++) {
|
||||
int index = start + i;
|
||||
if (index >= source.size()) {
|
||||
break;
|
||||
}
|
||||
Listing listing = source.get(index);
|
||||
int slot = slots.get(i);
|
||||
holder.listingSlots().put(slot, listing.id());
|
||||
inventory.setItem(slot, listingItem(listing, loreKey));
|
||||
}
|
||||
addPagedControls(inventory, page, source.size(), slots.size(), "back");
|
||||
if (type == GuiType.BROWSE || type == GuiType.ADMIN) {
|
||||
BrowseState state = browseState(player);
|
||||
inventory.setItem(48, button("filter", Map.of("filter", state.filter() == null ? "All" : MaterialUtil.pretty(state.filter()))));
|
||||
inventory.setItem(50, button("sort", Map.of("sort", state.sortMode().displayName())));
|
||||
}
|
||||
player.openInventory(inventory);
|
||||
play(player, "open");
|
||||
}
|
||||
|
||||
private void addPagedControls(Inventory inventory, int page, int totalItems, int pageSize, String backKey) {
|
||||
if (page > 0) {
|
||||
inventory.setItem(45, button("previous", Map.of()));
|
||||
}
|
||||
inventory.setItem(49, button(backKey, Map.of()));
|
||||
if ((page + 1) * pageSize < totalItems) {
|
||||
inventory.setItem(53, button("next", Map.of()));
|
||||
}
|
||||
}
|
||||
|
||||
private Inventory create(GuiHolder holder, int size, String title, Map<String, String> placeholders) {
|
||||
Inventory inventory = Bukkit.createInventory(holder, size, messages.component(messages.apply(title, placeholders)));
|
||||
holder.inventory(inventory);
|
||||
return inventory;
|
||||
}
|
||||
|
||||
private void fill(Inventory inventory) {
|
||||
if (!config.fillerEnabled()) {
|
||||
return;
|
||||
}
|
||||
ItemStack filler = customItem(config.fillerMaterial(), config.fillerName(), List.of(), Map.of());
|
||||
for (int i = 0; i < inventory.getSize(); i++) {
|
||||
if (inventory.getItem(i) == null) {
|
||||
inventory.setItem(i, filler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack button(String key, Map<String, String> placeholders) {
|
||||
ButtonConfig button = config.button(key);
|
||||
return customItem(button.material(), button.name(), button.lore(), placeholders);
|
||||
}
|
||||
|
||||
private ItemStack customItem(Material material, String name, List<String> lore, Map<String, String> placeholders) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(messages.component(messages.apply(name, placeholders)));
|
||||
if (lore != null && !lore.isEmpty()) {
|
||||
meta.lore(lore.stream().map(line -> messages.component(messages.apply(line, placeholders))).toList());
|
||||
}
|
||||
meta.addItemFlags(ItemFlag.values());
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack listingItem(Listing listing, String loreKey) {
|
||||
ItemStack item = listing.item();
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
List<net.kyori.adventure.text.Component> lore = new ArrayList<>();
|
||||
if (meta.lore() != null && !meta.lore().isEmpty()) {
|
||||
lore.addAll(meta.lore());
|
||||
lore.add(TextUtil.component(""));
|
||||
}
|
||||
lore.addAll(messages.guiLore(loreKey, listingPlaceholders(listing)));
|
||||
meta.lore(lore);
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack claimItem(ClaimRecord claim) {
|
||||
ItemStack item;
|
||||
if (claim.type() == ClaimType.MONEY) {
|
||||
item = customItem(Material.EMERALD, "<#6ee7b7>Sale Payment <#ffffff>{amount}", messages.rawGuiLore("claim"), claimPlaceholders(claim));
|
||||
} else {
|
||||
item = claim.item();
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
List<net.kyori.adventure.text.Component> lore = new ArrayList<>();
|
||||
if (meta.lore() != null && !meta.lore().isEmpty()) {
|
||||
lore.addAll(meta.lore());
|
||||
lore.add(TextUtil.component(""));
|
||||
}
|
||||
lore.addAll(messages.guiLore("claim", claimPlaceholders(claim)));
|
||||
meta.lore(lore);
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private Map<String, String> listingPlaceholders(Listing listing) {
|
||||
return Map.of(
|
||||
"seller", listing.sellerName(),
|
||||
"price", auctionHouse.formatMoney(listing.price()),
|
||||
"remaining", com.yourname.premiumah.util.TimeUtil.remaining(listing.expiresAt()),
|
||||
"id", listing.id()
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, String> claimPlaceholders(ClaimRecord claim) {
|
||||
String itemName = claim.type() == ClaimType.MONEY ? auctionHouse.formatMoney(claim.moneyAmount()) : auctionHouse.itemName(claim.item());
|
||||
Map<String, String> placeholders = new HashMap<>();
|
||||
placeholders.put("reason", claim.reason().displayName());
|
||||
placeholders.put("id", claim.listingId() == null ? "N/A" : claim.listingId());
|
||||
placeholders.put("age", com.yourname.premiumah.util.TimeUtil.age(claim.createdAt()));
|
||||
placeholders.put("item", itemName);
|
||||
placeholders.put("amount", itemName);
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
private BrowseState browseState(Player player) {
|
||||
return browseStates.computeIfAbsent(player.getUniqueId(), ignored -> new BrowseState(config.defaultSort(), null));
|
||||
}
|
||||
|
||||
private int normalizePage(int requestedPage, int totalItems) {
|
||||
int pageSize = Math.max(1, config.listingSlots().size());
|
||||
int maxPage = Math.max(0, (int) Math.ceil(totalItems / (double) pageSize) - 1);
|
||||
return Math.max(0, Math.min(requestedPage, maxPage));
|
||||
}
|
||||
|
||||
private boolean canClick(Player player) {
|
||||
long now = System.currentTimeMillis();
|
||||
long last = clickDebounce.getOrDefault(player.getUniqueId(), 0L);
|
||||
if (now - last < config.clickDebounceMillis()) {
|
||||
return false;
|
||||
}
|
||||
clickDebounce.put(player.getUniqueId(), now);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void syncSellSlot(Player player, Inventory inventory) {
|
||||
SellSession session = sellSessions.get(player.getUniqueId());
|
||||
if (session != null) {
|
||||
session.item(inventory.getItem(SELL_ITEM_SLOT));
|
||||
}
|
||||
}
|
||||
|
||||
private void beginPricePrompt(Player player, Inventory inventory) {
|
||||
SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
|
||||
session.item(inventory.getItem(SELL_ITEM_SLOT));
|
||||
inventory.setItem(SELL_ITEM_SLOT, null);
|
||||
session.awaitingPrice(true);
|
||||
session.completed(false);
|
||||
session.cancelTimeout();
|
||||
session.timeoutTask(Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
SellSession current = sellSessions.get(player.getUniqueId());
|
||||
if (current != null && current.awaitingPrice()) {
|
||||
current.awaitingPrice(false);
|
||||
returnItem(player, current.rawItem());
|
||||
current.cancelTimeout();
|
||||
sellSessions.remove(player.getUniqueId());
|
||||
messages.send(player, "price-prompt-timeout");
|
||||
play(player, "fail");
|
||||
}
|
||||
}, config.chatPriceTimeoutSeconds() * 20L));
|
||||
player.closeInventory();
|
||||
messages.send(player, "price-prompt");
|
||||
}
|
||||
|
||||
private void completePricePrompt(Player player, String input) {
|
||||
SellSession session = sellSessions.get(player.getUniqueId());
|
||||
if (session == null || !session.awaitingPrice()) {
|
||||
return;
|
||||
}
|
||||
session.cancelTimeout();
|
||||
if (input.equalsIgnoreCase("cancel")) {
|
||||
session.awaitingPrice(false);
|
||||
returnItem(player, session.rawItem());
|
||||
sellSessions.remove(player.getUniqueId());
|
||||
messages.send(player, "price-prompt-cancelled");
|
||||
play(player, "click");
|
||||
return;
|
||||
}
|
||||
double price;
|
||||
try {
|
||||
price = Double.parseDouble(input.replace(",", ""));
|
||||
} catch (NumberFormatException exception) {
|
||||
messages.send(player, "invalid-price");
|
||||
play(player, "fail");
|
||||
beginPricePrompt(player, createTempSellInventory(player, session));
|
||||
return;
|
||||
}
|
||||
if (!Double.isFinite(price) || price <= 0.0D) {
|
||||
messages.send(player, "invalid-price");
|
||||
play(player, "fail");
|
||||
beginPricePrompt(player, createTempSellInventory(player, session));
|
||||
return;
|
||||
}
|
||||
session.price(price);
|
||||
session.awaitingPrice(false);
|
||||
messages.send(player, "price-set", Map.of("price", auctionHouse.formatMoney(price)));
|
||||
openSell(player, false);
|
||||
}
|
||||
|
||||
private Inventory createTempSellInventory(Player player, SellSession session) {
|
||||
GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null);
|
||||
Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of());
|
||||
inventory.setItem(SELL_ITEM_SLOT, session.item());
|
||||
return inventory;
|
||||
}
|
||||
|
||||
private void confirmSell(Player player, Inventory inventory) {
|
||||
SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession());
|
||||
ItemStack item = inventory.getItem(SELL_ITEM_SLOT);
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
messages.send(player, "no-item-in-hand");
|
||||
play(player, "fail");
|
||||
return;
|
||||
}
|
||||
if (session.price() <= 0.0D) {
|
||||
messages.send(player, "invalid-price");
|
||||
play(player, "fail");
|
||||
return;
|
||||
}
|
||||
inventory.setItem(SELL_ITEM_SLOT, null);
|
||||
ListingCreationResult result = auctionHouse.createListing(player, item, session.price());
|
||||
if (!result.success()) {
|
||||
inventory.setItem(SELL_ITEM_SLOT, item);
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, "fail");
|
||||
return;
|
||||
}
|
||||
session.completed(true);
|
||||
session.cancelTimeout();
|
||||
sellSessions.remove(player.getUniqueId());
|
||||
messages.send(player, result.messageKey(), result.placeholders());
|
||||
play(player, "success");
|
||||
openBrowse(player, 0);
|
||||
}
|
||||
|
||||
private void closeSellReturningItem(Player player, Inventory inventory, boolean keepOpen) {
|
||||
SellSession session = sellSessions.remove(player.getUniqueId());
|
||||
ItemStack item = inventory.getItem(SELL_ITEM_SLOT);
|
||||
inventory.setItem(SELL_ITEM_SLOT, null);
|
||||
if (session != null) {
|
||||
session.completed(true);
|
||||
session.cancelTimeout();
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
item = session.rawItem();
|
||||
}
|
||||
}
|
||||
returnItem(player, item);
|
||||
if (!keepOpen) {
|
||||
player.closeInventory();
|
||||
}
|
||||
}
|
||||
|
||||
private void returnItem(Player player, ItemStack item) {
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
return;
|
||||
}
|
||||
Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(player.getInventory(), item);
|
||||
for (ItemStack leftover : leftovers.values()) {
|
||||
player.getWorld().dropItemNaturally(player.getLocation(), leftover);
|
||||
}
|
||||
}
|
||||
|
||||
private void play(Player player, String key) {
|
||||
if (!config.soundsEnabled()) {
|
||||
return;
|
||||
}
|
||||
String soundName = config.soundName(key);
|
||||
if (soundName == null || soundName.isBlank()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String soundKey = soundName.toLowerCase(Locale.ROOT).replace('_', '.');
|
||||
Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundKey));
|
||||
if (sound == null) {
|
||||
return;
|
||||
}
|
||||
player.playSound(player.getLocation(), sound, 0.7F, 1.0F);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yourname.premiumah.gui;
|
||||
|
||||
public enum GuiType {
|
||||
MAIN,
|
||||
BROWSE,
|
||||
MY_LISTINGS,
|
||||
CLAIMS,
|
||||
SELL,
|
||||
CONFIRM_BUY,
|
||||
ADMIN
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.yourname.premiumah.gui;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
public final class SellSession {
|
||||
private ItemStack item;
|
||||
private double price;
|
||||
private volatile boolean awaitingPrice;
|
||||
private volatile boolean completed;
|
||||
private BukkitTask timeoutTask;
|
||||
|
||||
public ItemStack item() {
|
||||
return item == null ? null : item.clone();
|
||||
}
|
||||
|
||||
public ItemStack rawItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public void item(ItemStack item) {
|
||||
this.item = item == null ? null : item.clone();
|
||||
}
|
||||
|
||||
public double price() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void price(double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public boolean awaitingPrice() {
|
||||
return awaitingPrice;
|
||||
}
|
||||
|
||||
public void awaitingPrice(boolean awaitingPrice) {
|
||||
this.awaitingPrice = awaitingPrice;
|
||||
}
|
||||
|
||||
public boolean completed() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
public void completed(boolean completed) {
|
||||
this.completed = completed;
|
||||
}
|
||||
|
||||
public BukkitTask timeoutTask() {
|
||||
return timeoutTask;
|
||||
}
|
||||
|
||||
public void timeoutTask(BukkitTask timeoutTask) {
|
||||
this.timeoutTask = timeoutTask;
|
||||
}
|
||||
|
||||
public void cancelTimeout() {
|
||||
if (timeoutTask != null) {
|
||||
timeoutTask.cancel();
|
||||
timeoutTask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.yourname.premiumah.listener;
|
||||
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public final class ChatInputListener implements Listener {
|
||||
private final GuiManager guiManager;
|
||||
|
||||
public ChatInputListener(GuiManager guiManager) {
|
||||
this.guiManager = guiManager;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onAsyncChat(AsyncChatEvent event) {
|
||||
guiManager.handleChat(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yourname.premiumah.listener;
|
||||
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
|
||||
public final class InventoryGuiListener implements Listener {
|
||||
private final GuiManager guiManager;
|
||||
|
||||
public InventoryGuiListener(GuiManager guiManager) {
|
||||
this.guiManager = guiManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
guiManager.handleClick(event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryDrag(InventoryDragEvent event) {
|
||||
guiManager.handleDrag(event);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClose(InventoryCloseEvent event) {
|
||||
guiManager.handleClose(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yourname.premiumah.listener;
|
||||
|
||||
import com.yourname.premiumah.gui.GuiManager;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public final class PlayerSessionListener implements Listener {
|
||||
private final GuiManager guiManager;
|
||||
|
||||
public PlayerSessionListener(GuiManager guiManager) {
|
||||
this.guiManager = guiManager;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
guiManager.handleQuit(event.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
package com.yourname.premiumah.manager;
|
||||
|
||||
import com.yourname.premiumah.config.ConfigManager;
|
||||
import com.yourname.premiumah.config.MessageManager;
|
||||
import com.yourname.premiumah.economy.EconomyService;
|
||||
import com.yourname.premiumah.model.ActionResult;
|
||||
import com.yourname.premiumah.model.ClaimReason;
|
||||
import com.yourname.premiumah.model.ClaimRecord;
|
||||
import com.yourname.premiumah.model.ClaimType;
|
||||
import com.yourname.premiumah.model.Listing;
|
||||
import com.yourname.premiumah.model.ListingCreationResult;
|
||||
import com.yourname.premiumah.model.ListingStatus;
|
||||
import com.yourname.premiumah.storage.StorageManager;
|
||||
import com.yourname.premiumah.util.IdGenerator;
|
||||
import com.yourname.premiumah.util.InventoryUtil;
|
||||
import com.yourname.premiumah.util.MaterialUtil;
|
||||
import com.yourname.premiumah.util.TextUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class AuctionHouseManager {
|
||||
private final JavaPlugin plugin;
|
||||
private final ConfigManager config;
|
||||
private final MessageManager messages;
|
||||
private final StorageManager storage;
|
||||
private final ListingManager listings;
|
||||
private final ClaimManager claims;
|
||||
private final EconomyService economy;
|
||||
private BukkitTask expirationTask;
|
||||
|
||||
public AuctionHouseManager(JavaPlugin plugin,
|
||||
ConfigManager config,
|
||||
MessageManager messages,
|
||||
StorageManager storage,
|
||||
ListingManager listings,
|
||||
ClaimManager claims,
|
||||
EconomyService economy) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.messages = messages;
|
||||
this.storage = storage;
|
||||
this.listings = listings;
|
||||
this.claims = claims;
|
||||
this.economy = economy;
|
||||
}
|
||||
|
||||
public void startTasks() {
|
||||
stopTasks();
|
||||
expirationTask = Bukkit.getScheduler().runTaskTimer(plugin, this::expireListings, 100L, config.listingExpireCheckTicks());
|
||||
}
|
||||
|
||||
public void stopTasks() {
|
||||
if (expirationTask != null) {
|
||||
expirationTask.cancel();
|
||||
expirationTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean marketplaceReady() {
|
||||
return config.economyEnabled() && economy.isAvailable();
|
||||
}
|
||||
|
||||
public synchronized void saveAll() {
|
||||
listings.saveToStorage();
|
||||
claims.saveToStorage();
|
||||
storage.save();
|
||||
}
|
||||
|
||||
public synchronized ListingCreationResult createListing(Player seller, ItemStack item, double price) {
|
||||
if (!seller.hasPermission("premiumah.sell")) {
|
||||
return ListingCreationResult.fail("no-permission", Map.of());
|
||||
}
|
||||
if (!marketplaceReady()) {
|
||||
return ListingCreationResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
return ListingCreationResult.fail("no-item-in-hand", Map.of());
|
||||
}
|
||||
if (!Double.isFinite(price) || price <= 0.0D) {
|
||||
return ListingCreationResult.fail("invalid-price", Map.of());
|
||||
}
|
||||
|
||||
boolean bypassRestrictions = seller.hasPermission("premiumah.bypass.restrictions");
|
||||
if (!bypassRestrictions && price < config.minPrice()) {
|
||||
return ListingCreationResult.fail("price-too-low", Map.of("min", economy.format(config.minPrice())));
|
||||
}
|
||||
if (!bypassRestrictions && price > config.maxPrice()) {
|
||||
return ListingCreationResult.fail("price-too-high", Map.of("max", economy.format(config.maxPrice())));
|
||||
}
|
||||
if (!config.isItemAllowed(seller, item.getType())) {
|
||||
return ListingCreationResult.fail("item-blocked", Map.of());
|
||||
}
|
||||
int limit = config.listingLimit(seller);
|
||||
if (listings.activeCount(seller.getUniqueId()) >= limit) {
|
||||
return ListingCreationResult.fail("listing-limit", Map.of("limit", String.valueOf(limit)));
|
||||
}
|
||||
|
||||
double fee = seller.hasPermission("premiumah.bypass.fees") ? 0.0D : (config.listingFeeEnabled() ? config.listingFee() : 0.0D);
|
||||
if (fee > 0.0D) {
|
||||
if (!economy.has(seller, fee)) {
|
||||
return ListingCreationResult.fail("not-enough-money", Map.of("price", economy.format(fee)));
|
||||
}
|
||||
if (!economy.withdraw(seller, fee)) {
|
||||
return ListingCreationResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
}
|
||||
|
||||
String id = nextListingId();
|
||||
long now = System.currentTimeMillis();
|
||||
Listing listing = new Listing(
|
||||
id,
|
||||
seller.getUniqueId(),
|
||||
seller.getName(),
|
||||
item.clone(),
|
||||
price,
|
||||
now,
|
||||
now + config.defaultListingDurationMillis(),
|
||||
ListingStatus.ACTIVE,
|
||||
null,
|
||||
null,
|
||||
now
|
||||
);
|
||||
listings.add(listing);
|
||||
saveAll();
|
||||
|
||||
Map<String, String> placeholders = placeholders(
|
||||
"id", listing.id(),
|
||||
"item", itemName(item),
|
||||
"price", economy.format(price),
|
||||
"fee", economy.format(fee)
|
||||
);
|
||||
return ListingCreationResult.success(listing, fee > 0.0D ? "listing-created-fee" : "listing-created", placeholders);
|
||||
}
|
||||
|
||||
public synchronized ActionResult buyListing(Player buyer, String listingId) {
|
||||
if (!buyer.hasPermission("premiumah.buy")) {
|
||||
return ActionResult.fail("no-permission", Map.of());
|
||||
}
|
||||
if (!marketplaceReady()) {
|
||||
return ActionResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
|
||||
Optional<Listing> optional = listings.get(listingId);
|
||||
if (optional.isEmpty()) {
|
||||
return ActionResult.fail("listing-not-found", Map.of());
|
||||
}
|
||||
Listing listing = optional.get();
|
||||
long now = System.currentTimeMillis();
|
||||
if (!listing.isActive(now)) {
|
||||
if (listing.status() == ListingStatus.ACTIVE && listing.expiresAt() <= now) {
|
||||
expireListingInternal(listing);
|
||||
saveAll();
|
||||
}
|
||||
return ActionResult.fail("listing-no-longer-available", Map.of());
|
||||
}
|
||||
if (!config.allowSellerSelfPurchase() && listing.sellerUuid().equals(buyer.getUniqueId())) {
|
||||
return ActionResult.fail("cannot-buy-own", Map.of());
|
||||
}
|
||||
|
||||
ItemStack item = listing.item();
|
||||
boolean fitsInventory = InventoryUtil.canFit(buyer.getInventory(), item);
|
||||
if (!fitsInventory && (config.requireInventorySpaceToBuy() || !config.claimFullInventoryPurchases())) {
|
||||
return ActionResult.fail("inventory-full", Map.of());
|
||||
}
|
||||
if (!economy.has(buyer, listing.price())) {
|
||||
return ActionResult.fail("not-enough-money", Map.of("price", economy.format(listing.price())));
|
||||
}
|
||||
if (!economy.withdraw(buyer, listing.price())) {
|
||||
return ActionResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
|
||||
double sellerAmount = sellerProceeds(listing);
|
||||
OfflinePlayer seller = Bukkit.getOfflinePlayer(listing.sellerUuid());
|
||||
if (config.instantSellerPayment()) {
|
||||
if (!economy.deposit(seller, sellerAmount)) {
|
||||
economy.deposit(buyer, listing.price());
|
||||
return ActionResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
} else {
|
||||
addMoneyClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), sellerAmount, ClaimReason.SALE_PAYMENT);
|
||||
}
|
||||
|
||||
listing.soldTo(buyer.getUniqueId(), buyer.getName());
|
||||
|
||||
boolean deliveredToInventory = true;
|
||||
if (fitsInventory) {
|
||||
Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(buyer.getInventory(), item);
|
||||
if (!leftovers.isEmpty()) {
|
||||
deliveredToInventory = false;
|
||||
for (ItemStack leftover : leftovers.values()) {
|
||||
addItemClaim(buyer.getUniqueId(), buyer.getName(), listing.id(), leftover, ClaimReason.PURCHASE_DELIVERY);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deliveredToInventory = false;
|
||||
addItemClaim(buyer.getUniqueId(), buyer.getName(), listing.id(), item, ClaimReason.PURCHASE_DELIVERY);
|
||||
}
|
||||
|
||||
saveAll();
|
||||
Player onlineSeller = Bukkit.getPlayer(listing.sellerUuid());
|
||||
if (onlineSeller != null) {
|
||||
messages.send(onlineSeller, "sold-notify", placeholders(
|
||||
"buyer", buyer.getName(),
|
||||
"price", economy.format(listing.price()),
|
||||
"item", itemName(item),
|
||||
"id", listing.id()
|
||||
));
|
||||
}
|
||||
|
||||
return ActionResult.success(deliveredToInventory ? "purchase-success" : "purchase-claim", placeholders(
|
||||
"price", economy.format(listing.price()),
|
||||
"item", itemName(item),
|
||||
"seller", listing.sellerName(),
|
||||
"id", listing.id()
|
||||
));
|
||||
}
|
||||
|
||||
public synchronized ActionResult cancelListing(Player player, String listingId) {
|
||||
if (!config.allowCancelActiveListings()) {
|
||||
return ActionResult.fail("listing-no-longer-available", Map.of());
|
||||
}
|
||||
Optional<Listing> optional = listings.get(listingId);
|
||||
if (optional.isEmpty()) {
|
||||
return ActionResult.fail("listing-not-found", Map.of());
|
||||
}
|
||||
Listing listing = optional.get();
|
||||
if (!listing.sellerUuid().equals(player.getUniqueId()) && !player.hasPermission("premiumah.admin.remove")) {
|
||||
return ActionResult.fail("no-permission", Map.of());
|
||||
}
|
||||
if (!listing.isActive(System.currentTimeMillis())) {
|
||||
return ActionResult.fail("listing-no-longer-available", Map.of());
|
||||
}
|
||||
listing.status(ListingStatus.CANCELLED);
|
||||
addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.CANCELLED_LISTING);
|
||||
saveAll();
|
||||
return ActionResult.success("listing-cancelled", placeholders("id", listing.id()));
|
||||
}
|
||||
|
||||
public synchronized ActionResult adminRemoveListing(String listingId) {
|
||||
Optional<Listing> optional = listings.get(listingId);
|
||||
if (optional.isEmpty()) {
|
||||
return ActionResult.fail("listing-not-found", Map.of());
|
||||
}
|
||||
Listing listing = optional.get();
|
||||
if (listing.status() == ListingStatus.ACTIVE) {
|
||||
listing.status(ListingStatus.REMOVED);
|
||||
if (config.reclaimAdminRemovedItems()) {
|
||||
addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.ADMIN_REMOVED);
|
||||
}
|
||||
} else {
|
||||
listing.status(ListingStatus.REMOVED);
|
||||
}
|
||||
saveAll();
|
||||
Player seller = Bukkit.getPlayer(listing.sellerUuid());
|
||||
if (seller != null) {
|
||||
messages.send(seller, "listing-removed-admin", placeholders("id", listing.id()));
|
||||
}
|
||||
return ActionResult.success("listing-removed-admin", placeholders("id", listing.id()));
|
||||
}
|
||||
|
||||
public synchronized ActionResult forceExpireListing(String listingId) {
|
||||
Optional<Listing> optional = listings.get(listingId);
|
||||
if (optional.isEmpty()) {
|
||||
return ActionResult.fail("listing-not-found", Map.of());
|
||||
}
|
||||
Listing listing = optional.get();
|
||||
if (listing.status() != ListingStatus.ACTIVE) {
|
||||
return ActionResult.fail("listing-no-longer-available", Map.of());
|
||||
}
|
||||
expireListingInternal(listing);
|
||||
saveAll();
|
||||
return ActionResult.success("listing-expired", placeholders("id", listing.id()));
|
||||
}
|
||||
|
||||
public synchronized ActionResult claim(Player player, String claimId) {
|
||||
if (!player.hasPermission("premiumah.expired")) {
|
||||
return ActionResult.fail("no-permission", Map.of());
|
||||
}
|
||||
Optional<ClaimRecord> optional = claims.get(claimId);
|
||||
if (optional.isEmpty()) {
|
||||
return ActionResult.fail("nothing-to-claim", Map.of());
|
||||
}
|
||||
ClaimRecord claim = optional.get();
|
||||
if (!claim.ownerUuid().equals(player.getUniqueId())) {
|
||||
return ActionResult.fail("no-permission", Map.of());
|
||||
}
|
||||
|
||||
if (claim.type() == ClaimType.MONEY) {
|
||||
if (!marketplaceReady()) {
|
||||
return ActionResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
if (!economy.deposit(player, claim.moneyAmount())) {
|
||||
return ActionResult.fail("economy-unavailable", Map.of());
|
||||
}
|
||||
claims.remove(claim.id());
|
||||
saveAll();
|
||||
return ActionResult.success("item-reclaimed", placeholders("item", economy.format(claim.moneyAmount()), "id", claim.listingId()));
|
||||
}
|
||||
|
||||
ItemStack item = claim.item();
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
claims.remove(claim.id());
|
||||
saveAll();
|
||||
return ActionResult.fail("nothing-to-claim", Map.of());
|
||||
}
|
||||
if (!InventoryUtil.canFit(player.getInventory(), item)) {
|
||||
return ActionResult.fail("inventory-full", Map.of());
|
||||
}
|
||||
Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(player.getInventory(), item);
|
||||
if (!leftovers.isEmpty()) {
|
||||
return ActionResult.fail("inventory-full", Map.of());
|
||||
}
|
||||
claims.remove(claim.id());
|
||||
markListingClaimedIfComplete(claim.listingId());
|
||||
saveAll();
|
||||
return ActionResult.success("item-reclaimed", placeholders("item", itemName(item), "id", claim.listingId()));
|
||||
}
|
||||
|
||||
public synchronized void expireListings() {
|
||||
long now = System.currentTimeMillis();
|
||||
boolean changed = false;
|
||||
for (Listing listing : listings.expiredActiveListings(now)) {
|
||||
expireListingInternal(listing);
|
||||
Player seller = Bukkit.getPlayer(listing.sellerUuid());
|
||||
if (seller != null) {
|
||||
messages.send(seller, "listing-expired", placeholders("id", listing.id()));
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
saveAll();
|
||||
}
|
||||
}
|
||||
|
||||
public ClaimRecord addItemClaim(UUID ownerUuid, String ownerName, String listingId, ItemStack item, ClaimReason reason) {
|
||||
ClaimRecord claim = new ClaimRecord(nextClaimId(), ownerUuid, ownerName, listingId, ClaimType.ITEM, reason, item, 0.0D, System.currentTimeMillis());
|
||||
claims.add(claim);
|
||||
return claim;
|
||||
}
|
||||
|
||||
public ClaimRecord addMoneyClaim(UUID ownerUuid, String ownerName, String listingId, double amount, ClaimReason reason) {
|
||||
ClaimRecord claim = new ClaimRecord(nextClaimId(), ownerUuid, ownerName, listingId, ClaimType.MONEY, reason, null, amount, System.currentTimeMillis());
|
||||
claims.add(claim);
|
||||
return claim;
|
||||
}
|
||||
|
||||
public double sellerProceeds(Listing listing) {
|
||||
double amount = listing.price();
|
||||
if (!config.salesTaxEnabled()) {
|
||||
return amount;
|
||||
}
|
||||
Player seller = Bukkit.getPlayer(listing.sellerUuid());
|
||||
if (seller != null && seller.hasPermission("premiumah.bypass.tax")) {
|
||||
return amount;
|
||||
}
|
||||
double tax = amount * (config.salesTaxPercent() / 100.0D);
|
||||
return Math.max(0.0D, amount - tax);
|
||||
}
|
||||
|
||||
public String formatMoney(double amount) {
|
||||
return economy.format(amount);
|
||||
}
|
||||
|
||||
public String itemName(ItemStack item) {
|
||||
if (InventoryUtil.isAir(item)) {
|
||||
return "Air";
|
||||
}
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
String name;
|
||||
if (meta != null && meta.hasDisplayName()) {
|
||||
name = TextUtil.plain(meta.displayName());
|
||||
} else {
|
||||
name = MaterialUtil.pretty(item.getType());
|
||||
}
|
||||
if (item.getAmount() > 1) {
|
||||
return name + " x" + item.getAmount();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private void expireListingInternal(Listing listing) {
|
||||
if (listing.status() != ListingStatus.ACTIVE) {
|
||||
return;
|
||||
}
|
||||
listing.status(ListingStatus.EXPIRED);
|
||||
if (!claims.hasItemClaimForListing(listing.id())) {
|
||||
addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.EXPIRED_LISTING);
|
||||
}
|
||||
}
|
||||
|
||||
private void markListingClaimedIfComplete(String listingId) {
|
||||
if (listingId == null || claims.hasItemClaimForListing(listingId)) {
|
||||
return;
|
||||
}
|
||||
listings.get(listingId).ifPresent(listing -> {
|
||||
if (listing.status() == ListingStatus.EXPIRED
|
||||
|| listing.status() == ListingStatus.CANCELLED
|
||||
|| listing.status() == ListingStatus.REMOVED) {
|
||||
listing.status(ListingStatus.CLAIMED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String nextListingId() {
|
||||
String id;
|
||||
do {
|
||||
id = IdGenerator.listingId();
|
||||
} while (listings.contains(id));
|
||||
return id;
|
||||
}
|
||||
|
||||
private String nextClaimId() {
|
||||
String id;
|
||||
do {
|
||||
id = IdGenerator.claimId();
|
||||
} while (claims.get(id).isPresent());
|
||||
return id;
|
||||
}
|
||||
|
||||
private Map<String, String> placeholders(String... pairs) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (int i = 0; i + 1 < pairs.length; i += 2) {
|
||||
map.put(pairs[i], pairs[i + 1] == null ? "" : pairs[i + 1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.yourname.premiumah.manager;
|
||||
|
||||
import com.yourname.premiumah.model.ClaimRecord;
|
||||
import com.yourname.premiumah.model.ClaimType;
|
||||
import com.yourname.premiumah.storage.StorageManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ClaimManager {
|
||||
private final StorageManager storageManager;
|
||||
private final Map<String, ClaimRecord> claims = new LinkedHashMap<>();
|
||||
|
||||
public ClaimManager(StorageManager storageManager) {
|
||||
this.storageManager = storageManager;
|
||||
}
|
||||
|
||||
public synchronized void loadFromStorage() {
|
||||
claims.clear();
|
||||
claims.putAll(storageManager.claimsSnapshot());
|
||||
}
|
||||
|
||||
public synchronized void saveToStorage() {
|
||||
storageManager.replaceClaims(claims);
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
claims.clear();
|
||||
}
|
||||
|
||||
public synchronized void add(ClaimRecord claim) {
|
||||
claims.put(claim.id(), claim);
|
||||
}
|
||||
|
||||
public synchronized Optional<ClaimRecord> get(String id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(claims.get(id));
|
||||
}
|
||||
|
||||
public synchronized void remove(String id) {
|
||||
claims.remove(id);
|
||||
}
|
||||
|
||||
public synchronized List<ClaimRecord> claimsFor(UUID ownerUuid) {
|
||||
return claims.values().stream()
|
||||
.filter(claim -> claim.ownerUuid().equals(ownerUuid))
|
||||
.sorted(Comparator.comparingLong(ClaimRecord::createdAt).reversed())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
public synchronized boolean hasClaims(UUID ownerUuid) {
|
||||
return claims.values().stream().anyMatch(claim -> claim.ownerUuid().equals(ownerUuid));
|
||||
}
|
||||
|
||||
public synchronized boolean hasItemClaimForListing(String listingId) {
|
||||
return claims.values().stream()
|
||||
.anyMatch(claim -> claim.type() == ClaimType.ITEM && listingId != null && listingId.equals(claim.listingId()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.yourname.premiumah.manager;
|
||||
|
||||
import com.yourname.premiumah.model.Listing;
|
||||
import com.yourname.premiumah.model.ListingStatus;
|
||||
import com.yourname.premiumah.model.SortMode;
|
||||
import com.yourname.premiumah.storage.StorageManager;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ListingManager {
|
||||
private final StorageManager storageManager;
|
||||
private final Map<String, Listing> listings = new LinkedHashMap<>();
|
||||
|
||||
public ListingManager(StorageManager storageManager) {
|
||||
this.storageManager = storageManager;
|
||||
}
|
||||
|
||||
public synchronized void loadFromStorage() {
|
||||
listings.clear();
|
||||
listings.putAll(storageManager.listingsSnapshot());
|
||||
}
|
||||
|
||||
public synchronized void saveToStorage() {
|
||||
storageManager.replaceListings(listings);
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
listings.clear();
|
||||
}
|
||||
|
||||
public synchronized Optional<Listing> get(String id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(listings.get(id.toUpperCase()));
|
||||
}
|
||||
|
||||
public synchronized boolean contains(String id) {
|
||||
return listings.containsKey(id.toUpperCase());
|
||||
}
|
||||
|
||||
public synchronized void add(Listing listing) {
|
||||
listings.put(listing.id(), listing);
|
||||
}
|
||||
|
||||
public synchronized Collection<Listing> all() {
|
||||
return new ArrayList<>(listings.values());
|
||||
}
|
||||
|
||||
public synchronized List<Listing> activeListings(SortMode sortMode, Material filter) {
|
||||
long now = System.currentTimeMillis();
|
||||
return listings.values().stream()
|
||||
.filter(listing -> listing.isActive(now))
|
||||
.filter(listing -> filter == null || listing.rawItem().getType() == filter)
|
||||
.sorted(comparator(sortMode))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public synchronized List<Listing> activeListingsBySeller(UUID sellerUuid, SortMode sortMode) {
|
||||
long now = System.currentTimeMillis();
|
||||
return listings.values().stream()
|
||||
.filter(listing -> listing.sellerUuid().equals(sellerUuid))
|
||||
.filter(listing -> listing.isActive(now))
|
||||
.sorted(comparator(sortMode))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public synchronized List<Listing> activeListingsBySellerName(String sellerName, SortMode sortMode) {
|
||||
long now = System.currentTimeMillis();
|
||||
String normalized = sellerName == null ? "" : sellerName.toLowerCase();
|
||||
return listings.values().stream()
|
||||
.filter(listing -> listing.sellerName().toLowerCase().equals(normalized))
|
||||
.filter(listing -> listing.isActive(now))
|
||||
.sorted(comparator(sortMode))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public synchronized int activeCount(UUID sellerUuid) {
|
||||
long now = System.currentTimeMillis();
|
||||
return (int) listings.values().stream()
|
||||
.filter(listing -> listing.sellerUuid().equals(sellerUuid))
|
||||
.filter(listing -> listing.isActive(now))
|
||||
.count();
|
||||
}
|
||||
|
||||
public synchronized List<Listing> expiredActiveListings(long now) {
|
||||
return listings.values().stream()
|
||||
.filter(listing -> listing.status() == ListingStatus.ACTIVE)
|
||||
.filter(listing -> listing.expiresAt() <= now)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Comparator<Listing> comparator(SortMode sortMode) {
|
||||
return switch (sortMode == null ? SortMode.NEWEST : sortMode) {
|
||||
case OLDEST -> Comparator.comparingLong(Listing::createdAt);
|
||||
case LOWEST_PRICE -> Comparator.comparingDouble(Listing::price).thenComparing(Comparator.comparingLong(Listing::createdAt).reversed());
|
||||
case HIGHEST_PRICE -> Comparator.comparingDouble(Listing::price).reversed().thenComparing(Comparator.comparingLong(Listing::createdAt).reversed());
|
||||
case NEWEST -> Comparator.comparingLong(Listing::createdAt).reversed();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public record ActionResult(boolean success, String messageKey, Map<String, String> placeholders) {
|
||||
public static ActionResult success(String messageKey, Map<String, String> placeholders) {
|
||||
return new ActionResult(true, messageKey, placeholders == null ? Collections.emptyMap() : placeholders);
|
||||
}
|
||||
|
||||
public static ActionResult fail(String messageKey, Map<String, String> placeholders) {
|
||||
return new ActionResult(false, messageKey, placeholders == null ? Collections.emptyMap() : placeholders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
public enum ClaimReason {
|
||||
EXPIRED_LISTING("Expired Listing"),
|
||||
ADMIN_REMOVED("Admin Removed"),
|
||||
CANCELLED_LISTING("Cancelled Listing"),
|
||||
PURCHASE_DELIVERY("Purchased Item"),
|
||||
SALE_PAYMENT("Sale Payment");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
ClaimReason(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimRecord {
|
||||
private final String id;
|
||||
private final UUID ownerUuid;
|
||||
private final String ownerName;
|
||||
private final String listingId;
|
||||
private final ClaimType type;
|
||||
private final ClaimReason reason;
|
||||
private ItemStack item;
|
||||
private double moneyAmount;
|
||||
private final long createdAt;
|
||||
|
||||
public ClaimRecord(String id,
|
||||
UUID ownerUuid,
|
||||
String ownerName,
|
||||
String listingId,
|
||||
ClaimType type,
|
||||
ClaimReason reason,
|
||||
ItemStack item,
|
||||
double moneyAmount,
|
||||
long createdAt) {
|
||||
this.id = Objects.requireNonNull(id, "id");
|
||||
this.ownerUuid = Objects.requireNonNull(ownerUuid, "ownerUuid");
|
||||
this.ownerName = Objects.requireNonNullElse(ownerName, "Unknown");
|
||||
this.listingId = listingId;
|
||||
this.type = Objects.requireNonNull(type, "type");
|
||||
this.reason = Objects.requireNonNull(reason, "reason");
|
||||
this.item = item == null ? null : item.clone();
|
||||
this.moneyAmount = moneyAmount;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public UUID ownerUuid() {
|
||||
return ownerUuid;
|
||||
}
|
||||
|
||||
public String ownerName() {
|
||||
return ownerName;
|
||||
}
|
||||
|
||||
public String listingId() {
|
||||
return listingId;
|
||||
}
|
||||
|
||||
public ClaimType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ClaimReason reason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public ItemStack item() {
|
||||
return item == null ? null : item.clone();
|
||||
}
|
||||
|
||||
public ItemStack rawItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public void item(ItemStack item) {
|
||||
this.item = item == null ? null : item.clone();
|
||||
}
|
||||
|
||||
public double moneyAmount() {
|
||||
return moneyAmount;
|
||||
}
|
||||
|
||||
public void moneyAmount(double moneyAmount) {
|
||||
this.moneyAmount = moneyAmount;
|
||||
}
|
||||
|
||||
public long createdAt() {
|
||||
return createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
public enum ClaimType {
|
||||
ITEM,
|
||||
MONEY
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class Listing {
|
||||
private final String id;
|
||||
private final UUID sellerUuid;
|
||||
private final String sellerName;
|
||||
private final ItemStack item;
|
||||
private final double price;
|
||||
private final long createdAt;
|
||||
private final long expiresAt;
|
||||
private ListingStatus status;
|
||||
private UUID buyerUuid;
|
||||
private String buyerName;
|
||||
private long updatedAt;
|
||||
|
||||
public Listing(String id,
|
||||
UUID sellerUuid,
|
||||
String sellerName,
|
||||
ItemStack item,
|
||||
double price,
|
||||
long createdAt,
|
||||
long expiresAt,
|
||||
ListingStatus status,
|
||||
UUID buyerUuid,
|
||||
String buyerName,
|
||||
long updatedAt) {
|
||||
this.id = Objects.requireNonNull(id, "id");
|
||||
this.sellerUuid = Objects.requireNonNull(sellerUuid, "sellerUuid");
|
||||
this.sellerName = Objects.requireNonNullElse(sellerName, "Unknown");
|
||||
this.item = Objects.requireNonNull(item, "item").clone();
|
||||
this.price = price;
|
||||
this.createdAt = createdAt;
|
||||
this.expiresAt = expiresAt;
|
||||
this.status = Objects.requireNonNull(status, "status");
|
||||
this.buyerUuid = buyerUuid;
|
||||
this.buyerName = buyerName;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public UUID sellerUuid() {
|
||||
return sellerUuid;
|
||||
}
|
||||
|
||||
public String sellerName() {
|
||||
return sellerName;
|
||||
}
|
||||
|
||||
public ItemStack item() {
|
||||
return item.clone();
|
||||
}
|
||||
|
||||
public ItemStack rawItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public double price() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public long createdAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public long expiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public ListingStatus status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void status(ListingStatus status) {
|
||||
this.status = Objects.requireNonNull(status, "status");
|
||||
touch();
|
||||
}
|
||||
|
||||
public UUID buyerUuid() {
|
||||
return buyerUuid;
|
||||
}
|
||||
|
||||
public String buyerName() {
|
||||
return buyerName;
|
||||
}
|
||||
|
||||
public long updatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public boolean isActive(long now) {
|
||||
return status == ListingStatus.ACTIVE && expiresAt > now;
|
||||
}
|
||||
|
||||
public void soldTo(UUID buyerUuid, String buyerName) {
|
||||
this.status = ListingStatus.SOLD;
|
||||
this.buyerUuid = buyerUuid;
|
||||
this.buyerName = buyerName;
|
||||
touch();
|
||||
}
|
||||
|
||||
private void touch() {
|
||||
this.updatedAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public record ListingCreationResult(boolean success, Listing listing, String messageKey, Map<String, String> placeholders) {
|
||||
public static ListingCreationResult success(Listing listing, String messageKey, Map<String, String> placeholders) {
|
||||
return new ListingCreationResult(true, listing, messageKey, placeholders == null ? Collections.emptyMap() : placeholders);
|
||||
}
|
||||
|
||||
public static ListingCreationResult fail(String messageKey, Map<String, String> placeholders) {
|
||||
return new ListingCreationResult(false, null, messageKey, placeholders == null ? Collections.emptyMap() : placeholders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
public enum ListingStatus {
|
||||
ACTIVE,
|
||||
SOLD,
|
||||
EXPIRED,
|
||||
CLAIMED,
|
||||
REMOVED,
|
||||
CANCELLED
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.yourname.premiumah.model;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum SortMode {
|
||||
NEWEST("Newest"),
|
||||
OLDEST("Oldest"),
|
||||
LOWEST_PRICE("Lowest Price"),
|
||||
HIGHEST_PRICE("Highest Price");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
SortMode(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public SortMode next() {
|
||||
SortMode[] values = values();
|
||||
return values[(ordinal() + 1) % values.length];
|
||||
}
|
||||
|
||||
public static SortMode fromString(String value, SortMode fallback) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
return SortMode.valueOf(value.trim().toUpperCase(Locale.ROOT).replace('-', '_'));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.yourname.premiumah.storage;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
final class ItemStackSerializer {
|
||||
private ItemStackSerializer() {
|
||||
}
|
||||
|
||||
static String toBase64(ItemStack item) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try (BukkitObjectOutputStream data = new BukkitObjectOutputStream(output)) {
|
||||
data.writeObject(item);
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(output.toByteArray());
|
||||
}
|
||||
|
||||
static ItemStack fromBase64(String encoded) throws IOException, ClassNotFoundException {
|
||||
byte[] bytes = Base64.getDecoder().decode(encoded);
|
||||
try (BukkitObjectInputStream data = new BukkitObjectInputStream(new ByteArrayInputStream(bytes))) {
|
||||
Object object = data.readObject();
|
||||
if (!(object instanceof ItemStack item)) {
|
||||
throw new IOException("Serialized object was not an ItemStack.");
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package com.yourname.premiumah.storage;
|
||||
|
||||
import com.yourname.premiumah.model.ClaimReason;
|
||||
import com.yourname.premiumah.model.ClaimRecord;
|
||||
import com.yourname.premiumah.model.ClaimType;
|
||||
import com.yourname.premiumah.model.Listing;
|
||||
import com.yourname.premiumah.model.ListingStatus;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class StorageManager {
|
||||
private static final String LEGACY_IMPORT_KEY = "legacy_yaml_imported";
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private File databaseFile;
|
||||
private File legacyYamlFile;
|
||||
private Connection connection;
|
||||
private final Map<String, Listing> listings = new LinkedHashMap<>();
|
||||
private final Map<String, ClaimRecord> claims = new LinkedHashMap<>();
|
||||
|
||||
public StorageManager(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public synchronized void load() {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
String databaseName = plugin.getConfig().getString("storage.sqlite-file", "auctionhouse.db");
|
||||
if (databaseName == null || databaseName.isBlank()) {
|
||||
databaseName = "auctionhouse.db";
|
||||
}
|
||||
this.databaseFile = new File(plugin.getDataFolder(), databaseName);
|
||||
this.legacyYamlFile = new File(plugin.getDataFolder(), "data.yml");
|
||||
|
||||
listings.clear();
|
||||
claims.clear();
|
||||
try {
|
||||
connect();
|
||||
createSchema();
|
||||
loadListingsFromDatabase();
|
||||
loadClaimsFromDatabase();
|
||||
importLegacyYamlIfNeeded();
|
||||
plugin.getLogger().info("Loaded " + listings.size() + " listings and " + claims.size() + " claims from SQLite.");
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().severe("Failed to load SQLite storage: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save() {
|
||||
try {
|
||||
connect();
|
||||
connection.setAutoCommit(false);
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.executeUpdate("DELETE FROM listings");
|
||||
statement.executeUpdate("DELETE FROM claims");
|
||||
}
|
||||
saveListings();
|
||||
saveClaims();
|
||||
connection.commit();
|
||||
} catch (SQLException | IOException exception) {
|
||||
rollbackQuietly();
|
||||
plugin.getLogger().severe("Failed to save SQLite storage: " + exception.getMessage());
|
||||
} finally {
|
||||
setAutoCommitQuietly(true);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Failed to close SQLite connection: " + exception.getMessage());
|
||||
} finally {
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Map<String, Listing> listingsSnapshot() {
|
||||
return new LinkedHashMap<>(listings);
|
||||
}
|
||||
|
||||
public synchronized Map<String, ClaimRecord> claimsSnapshot() {
|
||||
return new LinkedHashMap<>(claims);
|
||||
}
|
||||
|
||||
public synchronized void replaceListings(Map<String, Listing> replacement) {
|
||||
listings.clear();
|
||||
listings.putAll(replacement);
|
||||
}
|
||||
|
||||
public synchronized void replaceClaims(Map<String, ClaimRecord> replacement) {
|
||||
claims.clear();
|
||||
claims.putAll(replacement);
|
||||
}
|
||||
|
||||
private void connect() throws SQLException {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
} catch (ClassNotFoundException exception) {
|
||||
throw new SQLException("SQLite JDBC driver is missing from the plugin jar.", exception);
|
||||
}
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("PRAGMA busy_timeout = 5000");
|
||||
statement.execute("PRAGMA foreign_keys = ON");
|
||||
statement.execute("PRAGMA journal_mode = WAL");
|
||||
}
|
||||
}
|
||||
|
||||
private void createSchema() throws SQLException {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.executeUpdate("""
|
||||
CREATE TABLE IF NOT EXISTS metadata (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.executeUpdate("""
|
||||
CREATE TABLE IF NOT EXISTS listings (
|
||||
id TEXT PRIMARY KEY,
|
||||
seller_uuid TEXT NOT NULL,
|
||||
seller_name TEXT NOT NULL,
|
||||
item TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
buyer_uuid TEXT,
|
||||
buyer_name TEXT,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_listings_status_expires ON listings(status, expires_at)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_listings_seller ON listings(seller_uuid, status)");
|
||||
statement.executeUpdate("""
|
||||
CREATE TABLE IF NOT EXISTS claims (
|
||||
id TEXT PRIMARY KEY,
|
||||
owner_uuid TEXT NOT NULL,
|
||||
owner_name TEXT NOT NULL,
|
||||
listing_id TEXT,
|
||||
type TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
item TEXT,
|
||||
money_amount REAL NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_claims_owner ON claims(owner_uuid, created_at)");
|
||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_claims_listing ON claims(listing_id)");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadListingsFromDatabase() throws SQLException {
|
||||
String sql = """
|
||||
SELECT id, seller_uuid, seller_name, item, price, created_at, expires_at, status,
|
||||
buyer_uuid, buyer_name, updated_at
|
||||
FROM listings
|
||||
ORDER BY created_at DESC
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql);
|
||||
ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
String id = result.getString("id");
|
||||
try {
|
||||
ItemStack item = ItemStackSerializer.fromBase64(result.getString("item"));
|
||||
Listing listing = new Listing(
|
||||
id,
|
||||
UUID.fromString(result.getString("seller_uuid")),
|
||||
result.getString("seller_name"),
|
||||
item,
|
||||
result.getDouble("price"),
|
||||
result.getLong("created_at"),
|
||||
result.getLong("expires_at"),
|
||||
parseEnum(ListingStatus.class, result.getString("status"), ListingStatus.ACTIVE),
|
||||
parseUuid(result.getString("buyer_uuid")),
|
||||
result.getString("buyer_name"),
|
||||
result.getLong("updated_at")
|
||||
);
|
||||
listings.put(id, listing);
|
||||
} catch (RuntimeException | IOException | ClassNotFoundException exception) {
|
||||
plugin.getLogger().warning("Skipping invalid SQLite listing " + id + ": " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadClaimsFromDatabase() throws SQLException {
|
||||
String sql = """
|
||||
SELECT id, owner_uuid, owner_name, listing_id, type, reason, item, money_amount, created_at
|
||||
FROM claims
|
||||
ORDER BY created_at DESC
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql);
|
||||
ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
String id = result.getString("id");
|
||||
try {
|
||||
ClaimType type = parseEnum(ClaimType.class, result.getString("type"), ClaimType.ITEM);
|
||||
ItemStack item = null;
|
||||
String encodedItem = result.getString("item");
|
||||
if (encodedItem != null && !encodedItem.isBlank()) {
|
||||
item = ItemStackSerializer.fromBase64(encodedItem);
|
||||
}
|
||||
if (type == ClaimType.ITEM && item == null) {
|
||||
plugin.getLogger().warning("Skipping SQLite item claim " + id + " because its item is missing.");
|
||||
continue;
|
||||
}
|
||||
ClaimRecord claim = new ClaimRecord(
|
||||
id,
|
||||
UUID.fromString(result.getString("owner_uuid")),
|
||||
result.getString("owner_name"),
|
||||
result.getString("listing_id"),
|
||||
type,
|
||||
parseEnum(ClaimReason.class, result.getString("reason"), ClaimReason.EXPIRED_LISTING),
|
||||
item,
|
||||
result.getDouble("money_amount"),
|
||||
result.getLong("created_at")
|
||||
);
|
||||
claims.put(id, claim);
|
||||
} catch (RuntimeException | IOException | ClassNotFoundException exception) {
|
||||
plugin.getLogger().warning("Skipping invalid SQLite claim " + id + ": " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveListings() throws SQLException, IOException {
|
||||
String sql = """
|
||||
INSERT INTO listings
|
||||
(id, seller_uuid, seller_name, item, price, created_at, expires_at, status, buyer_uuid, buyer_name, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (Listing listing : listings.values()) {
|
||||
statement.setString(1, listing.id());
|
||||
statement.setString(2, listing.sellerUuid().toString());
|
||||
statement.setString(3, listing.sellerName());
|
||||
statement.setString(4, ItemStackSerializer.toBase64(listing.rawItem()));
|
||||
statement.setDouble(5, listing.price());
|
||||
statement.setLong(6, listing.createdAt());
|
||||
statement.setLong(7, listing.expiresAt());
|
||||
statement.setString(8, listing.status().name());
|
||||
statement.setString(9, listing.buyerUuid() == null ? null : listing.buyerUuid().toString());
|
||||
statement.setString(10, listing.buyerName());
|
||||
statement.setLong(11, listing.updatedAt());
|
||||
statement.addBatch();
|
||||
}
|
||||
statement.executeBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClaims() throws SQLException, IOException {
|
||||
String sql = """
|
||||
INSERT INTO claims
|
||||
(id, owner_uuid, owner_name, listing_id, type, reason, item, money_amount, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (ClaimRecord claim : claims.values()) {
|
||||
statement.setString(1, claim.id());
|
||||
statement.setString(2, claim.ownerUuid().toString());
|
||||
statement.setString(3, claim.ownerName());
|
||||
statement.setString(4, claim.listingId());
|
||||
statement.setString(5, claim.type().name());
|
||||
statement.setString(6, claim.reason().name());
|
||||
statement.setString(7, claim.rawItem() == null ? null : ItemStackSerializer.toBase64(claim.rawItem()));
|
||||
statement.setDouble(8, claim.moneyAmount());
|
||||
statement.setLong(9, claim.createdAt());
|
||||
statement.addBatch();
|
||||
}
|
||||
statement.executeBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private void importLegacyYamlIfNeeded() throws SQLException {
|
||||
if (!plugin.getConfig().getBoolean("storage.import-legacy-yaml", true)
|
||||
|| !listings.isEmpty()
|
||||
|| !claims.isEmpty()
|
||||
|| getMetadata(LEGACY_IMPORT_KEY).equals("true")) {
|
||||
return;
|
||||
}
|
||||
if (legacyYamlFile == null || !legacyYamlFile.exists() || legacyYamlFile.length() == 0L) {
|
||||
return;
|
||||
}
|
||||
|
||||
YamlConfiguration data = YamlConfiguration.loadConfiguration(legacyYamlFile);
|
||||
int beforeListings = listings.size();
|
||||
int beforeClaims = claims.size();
|
||||
loadLegacyListings(data.getConfigurationSection("listings"));
|
||||
loadLegacyClaims(data.getConfigurationSection("claims"));
|
||||
setMetadata(LEGACY_IMPORT_KEY, "true");
|
||||
|
||||
int importedListings = listings.size() - beforeListings;
|
||||
int importedClaims = claims.size() - beforeClaims;
|
||||
if (importedListings > 0 || importedClaims > 0) {
|
||||
plugin.getLogger().info("Imported " + importedListings + " listings and " + importedClaims + " claims from legacy data.yml.");
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadLegacyListings(ConfigurationSection root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
for (String id : root.getKeys(false)) {
|
||||
ConfigurationSection section = root.getConfigurationSection(id);
|
||||
if (section == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ItemStack item = section.getItemStack("item");
|
||||
if (item == null) {
|
||||
plugin.getLogger().warning("Skipping legacy listing " + id + " because its item is missing.");
|
||||
continue;
|
||||
}
|
||||
Listing listing = new Listing(
|
||||
id,
|
||||
UUID.fromString(section.getString("seller-uuid", "")),
|
||||
section.getString("seller-name", "Unknown"),
|
||||
item,
|
||||
section.getDouble("price"),
|
||||
section.getLong("created-at"),
|
||||
section.getLong("expires-at"),
|
||||
parseEnum(ListingStatus.class, section.getString("status"), ListingStatus.ACTIVE),
|
||||
parseUuid(section.getString("buyer-uuid")),
|
||||
section.getString("buyer-name", null),
|
||||
section.getLong("updated-at", section.getLong("created-at"))
|
||||
);
|
||||
listings.put(id, listing);
|
||||
} catch (RuntimeException exception) {
|
||||
plugin.getLogger().warning("Skipping invalid legacy listing " + id + ": " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadLegacyClaims(ConfigurationSection root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
for (String id : root.getKeys(false)) {
|
||||
ConfigurationSection section = root.getConfigurationSection(id);
|
||||
if (section == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ClaimType type = parseEnum(ClaimType.class, section.getString("type"), ClaimType.ITEM);
|
||||
ItemStack item = section.getItemStack("item");
|
||||
if (type == ClaimType.ITEM && item == null) {
|
||||
plugin.getLogger().warning("Skipping legacy item claim " + id + " because its item is missing.");
|
||||
continue;
|
||||
}
|
||||
ClaimRecord claim = new ClaimRecord(
|
||||
id,
|
||||
UUID.fromString(section.getString("owner-uuid", "")),
|
||||
section.getString("owner-name", "Unknown"),
|
||||
section.getString("listing-id", null),
|
||||
type,
|
||||
parseEnum(ClaimReason.class, section.getString("reason"), ClaimReason.EXPIRED_LISTING),
|
||||
item,
|
||||
section.getDouble("money-amount"),
|
||||
section.getLong("created-at")
|
||||
);
|
||||
claims.put(id, claim);
|
||||
} catch (RuntimeException exception) {
|
||||
plugin.getLogger().warning("Skipping invalid legacy claim " + id + ": " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getMetadata(String key) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT value FROM metadata WHERE key = ?")) {
|
||||
statement.setString(1, key);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
if (result.next()) {
|
||||
return result.getString("value");
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void setMetadata(String key, String value) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("""
|
||||
INSERT INTO metadata(key, value)
|
||||
VALUES(?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
||||
""")) {
|
||||
statement.setString(1, key);
|
||||
statement.setString(2, value);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void rollbackQuietly() {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.rollback();
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void setAutoCommitQuietly(boolean autoCommit) {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.setAutoCommit(autoCommit);
|
||||
}
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private UUID parseUuid(String raw) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return UUID.fromString(raw);
|
||||
}
|
||||
|
||||
private <E extends Enum<E>> E parseEnum(Class<E> type, String raw, E fallback) {
|
||||
if (raw == null || raw.isBlank()) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
return Enum.valueOf(type, raw);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yourname.premiumah.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public final class IdGenerator {
|
||||
private static final char[] ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray();
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private IdGenerator() {
|
||||
}
|
||||
|
||||
public static String listingId() {
|
||||
return "AH-" + random(8);
|
||||
}
|
||||
|
||||
public static String claimId() {
|
||||
return "CL-" + random(10);
|
||||
}
|
||||
|
||||
private static String random(int length) {
|
||||
char[] chars = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
chars[i] = ALPHABET[RANDOM.nextInt(ALPHABET.length)];
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.yourname.premiumah.util;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class InventoryUtil {
|
||||
private InventoryUtil() {
|
||||
}
|
||||
|
||||
public static boolean isAir(ItemStack item) {
|
||||
return item == null || item.getType() == Material.AIR || item.getAmount() <= 0;
|
||||
}
|
||||
|
||||
public static boolean canFit(Inventory inventory, ItemStack item) {
|
||||
if (isAir(item)) {
|
||||
return true;
|
||||
}
|
||||
int remaining = item.getAmount();
|
||||
int max = item.getMaxStackSize();
|
||||
for (ItemStack slot : inventory.getStorageContents()) {
|
||||
if (isAir(slot)) {
|
||||
remaining -= max;
|
||||
} else if (slot.isSimilar(item)) {
|
||||
remaining -= Math.max(0, max - slot.getAmount());
|
||||
}
|
||||
if (remaining <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Map<Integer, ItemStack> addItem(Inventory inventory, ItemStack item) {
|
||||
if (isAir(item)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return inventory.addItem(item.clone());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.yourname.premiumah.util;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MaterialUtil {
|
||||
private MaterialUtil() {
|
||||
}
|
||||
|
||||
public static Optional<Material> parse(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String normalized = value.trim().toUpperCase(Locale.ROOT).replace(' ', '_').replace('-', '_');
|
||||
try {
|
||||
Material material = Material.valueOf(normalized);
|
||||
return Optional.of(material);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static String pretty(Material material) {
|
||||
String lower = material.name().toLowerCase(Locale.ROOT).replace('_', ' ');
|
||||
StringBuilder builder = new StringBuilder(lower.length());
|
||||
boolean cap = true;
|
||||
for (char c : lower.toCharArray()) {
|
||||
if (cap && Character.isLetter(c)) {
|
||||
builder.append(Character.toUpperCase(c));
|
||||
cap = false;
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
if (c == ' ') {
|
||||
cap = true;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.yourname.premiumah.util;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class TextUtil {
|
||||
private static final MiniMessage MINI = MiniMessage.miniMessage();
|
||||
private static final LegacyComponentSerializer LEGACY_AMPERSAND = LegacyComponentSerializer.legacyAmpersand();
|
||||
private static final LegacyComponentSerializer LEGACY_SECTION_HEX = LegacyComponentSerializer.builder()
|
||||
.character('§')
|
||||
.hexColors()
|
||||
.useUnusualXRepeatedCharacterHexFormat()
|
||||
.build();
|
||||
|
||||
private TextUtil() {
|
||||
}
|
||||
|
||||
public static Component component(String raw) {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return Component.empty();
|
||||
}
|
||||
String normalized = normalizeHexAmpersand(raw);
|
||||
if (normalized.contains("<")) {
|
||||
return MINI.deserialize(normalized);
|
||||
}
|
||||
return LEGACY_AMPERSAND.deserialize(normalized);
|
||||
}
|
||||
|
||||
public static String legacy(String raw) {
|
||||
return LEGACY_SECTION_HEX.serialize(component(raw));
|
||||
}
|
||||
|
||||
public static String plain(Component component) {
|
||||
if (component == null) {
|
||||
return "";
|
||||
}
|
||||
return PlainTextComponentSerializer.plainText().serialize(component);
|
||||
}
|
||||
|
||||
public static String normalizeHexAmpersand(String raw) {
|
||||
StringBuilder builder = new StringBuilder(raw.length() + 16);
|
||||
for (int i = 0; i < raw.length(); i++) {
|
||||
char c = raw.charAt(i);
|
||||
if (c == '&' && i + 7 < raw.length() && raw.charAt(i + 1) == '#') {
|
||||
String hex = raw.substring(i + 2, i + 8);
|
||||
if (hex.chars().allMatch(TextUtil::isHex)) {
|
||||
builder.append("<#").append(hex.toLowerCase(Locale.ROOT)).append(">");
|
||||
i += 7;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (c == '&' && i + 1 < raw.length()) {
|
||||
String tag = legacyTag(raw.charAt(i + 1));
|
||||
if (tag != null) {
|
||||
builder.append(tag);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static boolean isHex(int c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
private static String legacyTag(char code) {
|
||||
return switch (Character.toLowerCase(code)) {
|
||||
case '0' -> "<black>";
|
||||
case '1' -> "<dark_blue>";
|
||||
case '2' -> "<dark_green>";
|
||||
case '3' -> "<dark_aqua>";
|
||||
case '4' -> "<dark_red>";
|
||||
case '5' -> "<dark_purple>";
|
||||
case '6' -> "<gold>";
|
||||
case '7' -> "<gray>";
|
||||
case '8' -> "<dark_gray>";
|
||||
case '9' -> "<blue>";
|
||||
case 'a' -> "<green>";
|
||||
case 'b' -> "<aqua>";
|
||||
case 'c' -> "<red>";
|
||||
case 'd' -> "<light_purple>";
|
||||
case 'e' -> "<yellow>";
|
||||
case 'f' -> "<white>";
|
||||
case 'k' -> "<obfuscated>";
|
||||
case 'l' -> "<bold>";
|
||||
case 'm' -> "<strikethrough>";
|
||||
case 'n' -> "<underlined>";
|
||||
case 'o' -> "<italic>";
|
||||
case 'r' -> "<reset>";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.yourname.premiumah.util;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public final class TimeUtil {
|
||||
private TimeUtil() {
|
||||
}
|
||||
|
||||
public static String remaining(long untilMillis) {
|
||||
long millis = Math.max(0L, untilMillis - System.currentTimeMillis());
|
||||
return compact(Duration.ofMillis(millis));
|
||||
}
|
||||
|
||||
public static String age(long sinceMillis) {
|
||||
long millis = Math.max(0L, System.currentTimeMillis() - sinceMillis);
|
||||
return compact(Duration.ofMillis(millis));
|
||||
}
|
||||
|
||||
public static String compact(Duration duration) {
|
||||
long seconds = Math.max(0L, duration.toSeconds());
|
||||
long days = seconds / 86400;
|
||||
seconds %= 86400;
|
||||
long hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
long minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
|
||||
if (days > 0) {
|
||||
return days + "d " + hours + "h";
|
||||
}
|
||||
if (hours > 0) {
|
||||
return hours + "h " + minutes + "m";
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return minutes + "m " + seconds + "s";
|
||||
}
|
||||
return seconds + "s";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
settings:
|
||||
debug: false
|
||||
command-prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ&r <#6A3F24>»"
|
||||
allow-seller-self-purchase: false
|
||||
require-inventory-space-to-buy: false
|
||||
chat-price-timeout-seconds: 45
|
||||
click-debounce-millis: 350
|
||||
listing-expire-check-seconds: 60
|
||||
default-sort: "NEWEST"
|
||||
|
||||
storage:
|
||||
sqlite-file: "auctionhouse.db"
|
||||
import-legacy-yaml: true
|
||||
|
||||
economy:
|
||||
enabled: true
|
||||
require-economy: true
|
||||
provider: "Vault"
|
||||
instant-seller-payment: true
|
||||
listing-fee:
|
||||
enabled: false
|
||||
amount: 0.0
|
||||
sales-tax:
|
||||
enabled: false
|
||||
percent: 0.0
|
||||
price:
|
||||
min: 1.0
|
||||
max: 1000000000.0
|
||||
|
||||
listings:
|
||||
default-duration-seconds: 604800
|
||||
allow-cancel-active-listings: true
|
||||
reclaim-admin-removed-items: true
|
||||
cleanup-claimed-after-days: 30
|
||||
|
||||
listing-limits:
|
||||
default: 5
|
||||
permissions:
|
||||
- permission: "premiumah.limit.10"
|
||||
amount: 10
|
||||
- permission: "premiumah.limit.25"
|
||||
amount: 25
|
||||
- permission: "premiumah.limit.50"
|
||||
amount: 50
|
||||
|
||||
item-restrictions:
|
||||
mode: "BLACKLIST"
|
||||
materials:
|
||||
- BEDROCK
|
||||
- BARRIER
|
||||
- COMMAND_BLOCK
|
||||
- CHAIN_COMMAND_BLOCK
|
||||
- REPEATING_COMMAND_BLOCK
|
||||
- STRUCTURE_BLOCK
|
||||
- STRUCTURE_VOID
|
||||
- JIGSAW
|
||||
- DEBUG_STICK
|
||||
|
||||
claims:
|
||||
buyer-full-inventory-action: "CLAIM"
|
||||
seller-payment-action: "INSTANT"
|
||||
|
||||
sounds:
|
||||
enabled: true
|
||||
open: "BLOCK_CHEST_OPEN"
|
||||
click: "UI_BUTTON_CLICK"
|
||||
success: "ENTITY_PLAYER_LEVELUP"
|
||||
fail: "ENTITY_VILLAGER_NO"
|
||||
|
||||
gui:
|
||||
filler:
|
||||
enabled: true
|
||||
material: "BROWN_STAINED_GLASS_PANE"
|
||||
name: " "
|
||||
titles:
|
||||
main: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ &#D4AF37&lᴀᴜᴄᴛɪᴏɴs"
|
||||
browse: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Auctions <#7A4A2A>• Page {page}"
|
||||
my-listings: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>My Listings <#7A4A2A>• Page {page}"
|
||||
claims: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#B9C63F>Claims <#7A4A2A>• Page {page}"
|
||||
sell: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Create Listing"
|
||||
confirm-buy: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Confirm Purchase"
|
||||
admin: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#C1432E>Admin <#7A4A2A>• Page {page}"
|
||||
size:
|
||||
main: 45
|
||||
browse: 54
|
||||
my-listings: 54
|
||||
claims: 54
|
||||
sell: 54
|
||||
confirm-buy: 27
|
||||
listing-slots:
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
||||
- 16
|
||||
- 19
|
||||
- 20
|
||||
- 21
|
||||
- 22
|
||||
- 23
|
||||
- 24
|
||||
- 25
|
||||
- 28
|
||||
- 29
|
||||
- 30
|
||||
- 31
|
||||
- 32
|
||||
- 33
|
||||
- 34
|
||||
buttons:
|
||||
browse:
|
||||
material: "CHEST"
|
||||
name: "<#D4AF37>Browse Auctions"
|
||||
lore:
|
||||
- "<#C7BCA8>View every active listing."
|
||||
- "<#B9C63F>Click to browse."
|
||||
sell:
|
||||
material: "EMERALD"
|
||||
name: "<#B9C63F>Sell Held Item"
|
||||
lore:
|
||||
- "<#C7BCA8>Create a listing from your hand."
|
||||
- "<#D4AF37>GUI flow with price confirmation."
|
||||
my-listings:
|
||||
material: "BOOK"
|
||||
name: "<#A8873F>My Listings"
|
||||
lore:
|
||||
- "<#C7BCA8>Manage your active listings."
|
||||
claims:
|
||||
material: "ENDER_CHEST"
|
||||
name: "<#B9C63F>Claims"
|
||||
lore:
|
||||
- "<#C7BCA8>Reclaim expired or undelivered items."
|
||||
admin:
|
||||
material: "ANVIL"
|
||||
name: "<#C1432E>Admin Panel"
|
||||
lore:
|
||||
- "<#C7BCA8>Moderate active listings."
|
||||
close:
|
||||
material: "BARRIER"
|
||||
name: "<#C1432E>Close"
|
||||
back:
|
||||
material: "ARROW"
|
||||
name: "<#D4AF37>Back"
|
||||
next:
|
||||
material: "SPECTRAL_ARROW"
|
||||
name: "<#B9C63F>Next Page"
|
||||
previous:
|
||||
material: "ARROW"
|
||||
name: "<#D4AF37>Previous Page"
|
||||
sort:
|
||||
material: "HOPPER"
|
||||
name: "<#A8873F>Sort: <#D4AF37>{sort}"
|
||||
lore:
|
||||
- "<#C7BCA8>Click to rotate sorting."
|
||||
filter:
|
||||
material: "COMPASS"
|
||||
name: "<#A8873F>Filter: <#D4AF37>{filter}"
|
||||
lore:
|
||||
- "<#C7BCA8>Shift-click a listing to filter."
|
||||
- "<#C7BCA8>Right-click to clear filter."
|
||||
confirm:
|
||||
material: "LIME_STAINED_GLASS_PANE"
|
||||
name: "<#B9C63F>Confirm"
|
||||
cancel:
|
||||
material: "RED_STAINED_GLASS_PANE"
|
||||
name: "<#C1432E>Cancel"
|
||||
@@ -0,0 +1,65 @@
|
||||
messages:
|
||||
no-permission: "{prefix} <#C1432E>You do not have permission."
|
||||
player-only: "{prefix} <#C1432E>Only players can use that."
|
||||
economy-unavailable: "{prefix} <#C1432E>The auction economy is unavailable. Try again later."
|
||||
no-item-in-hand: "{prefix} <#C1432E>Hold the item you want to list."
|
||||
invalid-price: "{prefix} <#C1432E>Enter a valid positive price."
|
||||
invalid-sort: "{prefix} <#C1432E>Use newest, oldest, lowest_price, or highest_price."
|
||||
price-too-low: "{prefix} <#C1432E>The minimum listing price is <#D4AF37>{min}<#C1432E>."
|
||||
price-too-high: "{prefix} <#C1432E>The maximum listing price is <#D4AF37>{max}<#C1432E>."
|
||||
item-blocked: "{prefix} <#C1432E>That item cannot be listed."
|
||||
listing-limit: "{prefix} <#C1432E>You have reached your active listing limit of <#D4AF37>{limit}<#C1432E>."
|
||||
listing-created: "{prefix} <#B9C63F>Listed <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
listing-created-fee: "{prefix} <#B9C63F>Listed <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>. Fee charged: <#D4AF37>{fee}<#B9C63F>."
|
||||
listing-cancelled: "{prefix} <#D4AF37>Your listing was cancelled and moved to claims."
|
||||
listing-expired: "{prefix} <#D4AF37>A listing expired and was moved to your claims."
|
||||
listing-removed-admin: "{prefix} <#D4AF37>Listing <#A8873F>{id} <#D4AF37>was removed."
|
||||
listing-no-longer-available: "{prefix} <#C1432E>That listing is no longer available."
|
||||
cannot-buy-own: "{prefix} <#C1432E>You cannot buy your own listing."
|
||||
not-enough-money: "{prefix} <#C1432E>You need <#D4AF37>{price}<#C1432E> to buy this."
|
||||
purchase-success: "{prefix} <#B9C63F>You bought <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
purchase-claim: "{prefix} <#B9C63F>Purchase complete. Your inventory was full, so the item was moved to claims."
|
||||
sold-notify: "{prefix} <#B9C63F>Your listing sold to <#D4AF37>{buyer} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
inventory-full: "{prefix} <#C1432E>Your inventory is full."
|
||||
item-reclaimed: "{prefix} <#B9C63F>Claimed <#D4AF37>{item}<#B9C63F>."
|
||||
nothing-to-claim: "{prefix} <#D4AF37>You do not have anything to claim."
|
||||
reload-complete: "{prefix} <#B9C63F>Configuration, messages, and SQLite storage were reloaded."
|
||||
listing-not-found: "{prefix} <#C1432E>Listing not found."
|
||||
admin-help: "{prefix} <#D4AF37>/ahadmin reload<#7A4A2A>, <#D4AF37>/ahadmin remove <id><#7A4A2A>, <#D4AF37>/ahadmin view <player><#7A4A2A>, <#D4AF37>/ahadmin forceexpire <id>"
|
||||
price-prompt: "{prefix} <#D4AF37>Type the listing price in chat. Type <#B9C63F>cancel <#D4AF37>to stop."
|
||||
price-prompt-cancelled: "{prefix} <#D4AF37>Listing price entry cancelled."
|
||||
price-prompt-timeout: "{prefix} <#C1432E>Listing price entry timed out."
|
||||
price-set: "{prefix} <#B9C63F>Price set to <#D4AF37>{price}<#B9C63F>."
|
||||
usage-sell: "{prefix} <#D4AF37>Usage: /ah sell <price>"
|
||||
storage-save-failed: "{prefix} <#C1432E>Storage could not be saved. Check console."
|
||||
seller-paid-claim: "{prefix} <#B9C63F>Your sale funds were moved to claims."
|
||||
gui-lore:
|
||||
listing:
|
||||
- "<#7A4A2A>Seller: <#C7BCA8>{seller}"
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Remaining: <#D4AF37>{remaining}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
- ""
|
||||
- "<#B9C63F>Click to inspect and buy."
|
||||
- "<#C1432E>Admin right-click removes."
|
||||
my-listing:
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Remaining: <#D4AF37>{remaining}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
- ""
|
||||
- "<#D4AF37>Click to cancel and reclaim."
|
||||
claim:
|
||||
- "<#7A4A2A>Reason: <#C7BCA8>{reason}"
|
||||
- "<#7A4A2A>From Listing: <#A8873F>{id}"
|
||||
- "<#7A4A2A>Added: <#D4AF37>{age} ago"
|
||||
- ""
|
||||
- "<#B9C63F>Click to claim."
|
||||
confirm-buy:
|
||||
- "<#7A4A2A>Seller: <#C7BCA8>{seller}"
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
sell-item:
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Fee: <#D4AF37>{fee}"
|
||||
- ""
|
||||
- "<#C7BCA8>Use Set Price, then Confirm."
|
||||
@@ -0,0 +1,64 @@
|
||||
name: DirtAuctions
|
||||
version: ${project.version}
|
||||
main: com.yourname.premiumah.PremiumAHPlugin
|
||||
api-version: '1.21'
|
||||
author: yourname
|
||||
description: Premium GUI-first auction house for Paper SMP servers.
|
||||
softdepend:
|
||||
- Vault
|
||||
- CMI
|
||||
commands:
|
||||
ah:
|
||||
description: Open the Dirt Auctions GUI.
|
||||
usage: /ah [browse|sell|listings|expired|claims|sort]
|
||||
aliases:
|
||||
- auctionhouse
|
||||
- auctions
|
||||
ahadmin:
|
||||
description: Dirt Auctions administration.
|
||||
usage: /ahadmin <reload|remove|view|forceexpire|help>
|
||||
permissions:
|
||||
premiumah.use:
|
||||
description: Allows opening the auction house.
|
||||
default: true
|
||||
premiumah.sell:
|
||||
description: Allows creating auction listings.
|
||||
default: true
|
||||
premiumah.buy:
|
||||
description: Allows buying listings.
|
||||
default: true
|
||||
premiumah.listings:
|
||||
description: Allows opening personal listings.
|
||||
default: true
|
||||
premiumah.expired:
|
||||
description: Allows reclaiming expired and claimable items.
|
||||
default: true
|
||||
premiumah.admin:
|
||||
description: Full Dirt Auctions administration.
|
||||
default: op
|
||||
children:
|
||||
premiumah.admin.remove: true
|
||||
premiumah.admin.reload: true
|
||||
premiumah.admin.view: true
|
||||
premiumah.admin.forceexpire: true
|
||||
premiumah.admin.remove:
|
||||
description: Allows removing listings.
|
||||
default: op
|
||||
premiumah.admin.reload:
|
||||
description: Allows reloading Dirt Auctions.
|
||||
default: op
|
||||
premiumah.admin.view:
|
||||
description: Allows viewing player listings.
|
||||
default: op
|
||||
premiumah.admin.forceexpire:
|
||||
description: Allows expiring listings.
|
||||
default: op
|
||||
premiumah.bypass.restrictions:
|
||||
description: Bypass configured item and price restrictions.
|
||||
default: op
|
||||
premiumah.bypass.fees:
|
||||
description: Bypass listing fees.
|
||||
default: op
|
||||
premiumah.bypass.tax:
|
||||
description: Bypass sales tax.
|
||||
default: op
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,168 @@
|
||||
settings:
|
||||
debug: false
|
||||
command-prefix: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛA4A2A&lʙB6B35&lᴀ&#A8873F&lɢ&#D4AF37&lᴍ&#B9C63F&lᴄ&r <#6A3F24>»"
|
||||
allow-seller-self-purchase: false
|
||||
require-inventory-space-to-buy: false
|
||||
chat-price-timeout-seconds: 45
|
||||
click-debounce-millis: 350
|
||||
listing-expire-check-seconds: 60
|
||||
default-sort: "NEWEST"
|
||||
|
||||
storage:
|
||||
sqlite-file: "auctionhouse.db"
|
||||
import-legacy-yaml: true
|
||||
|
||||
economy:
|
||||
enabled: true
|
||||
require-economy: true
|
||||
provider: "Vault"
|
||||
instant-seller-payment: true
|
||||
listing-fee:
|
||||
enabled: false
|
||||
amount: 0.0
|
||||
sales-tax:
|
||||
enabled: false
|
||||
percent: 0.0
|
||||
price:
|
||||
min: 1.0
|
||||
max: 1000000000.0
|
||||
|
||||
listings:
|
||||
default-duration-seconds: 604800
|
||||
allow-cancel-active-listings: true
|
||||
reclaim-admin-removed-items: true
|
||||
cleanup-claimed-after-days: 30
|
||||
|
||||
listing-limits:
|
||||
default: 5
|
||||
permissions:
|
||||
- permission: "premiumah.limit.10"
|
||||
amount: 10
|
||||
- permission: "premiumah.limit.25"
|
||||
amount: 25
|
||||
- permission: "premiumah.limit.50"
|
||||
amount: 50
|
||||
|
||||
item-restrictions:
|
||||
mode: "BLACKLIST"
|
||||
materials:
|
||||
- BEDROCK
|
||||
- BARRIER
|
||||
- COMMAND_BLOCK
|
||||
- CHAIN_COMMAND_BLOCK
|
||||
- REPEATING_COMMAND_BLOCK
|
||||
- STRUCTURE_BLOCK
|
||||
- STRUCTURE_VOID
|
||||
- JIGSAW
|
||||
- DEBUG_STICK
|
||||
|
||||
claims:
|
||||
buyer-full-inventory-action: "CLAIM"
|
||||
seller-payment-action: "INSTANT"
|
||||
|
||||
sounds:
|
||||
enabled: true
|
||||
open: "BLOCK_CHEST_OPEN"
|
||||
click: "UI_BUTTON_CLICK"
|
||||
success: "ENTITY_PLAYER_LEVELUP"
|
||||
fail: "ENTITY_VILLAGER_NO"
|
||||
|
||||
gui:
|
||||
filler:
|
||||
enabled: true
|
||||
material: "BROWN_STAINED_GLASS_PANE"
|
||||
name: " "
|
||||
titles:
|
||||
main: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ &#D4AF37&lᴀᴜᴄᴛɪᴏɴs"
|
||||
browse: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Auctions <#7A4A2A>• Page {page}"
|
||||
my-listings: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>My Listings <#7A4A2A>• Page {page}"
|
||||
claims: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#B9C63F>Claims <#7A4A2A>• Page {page}"
|
||||
sell: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Create Listing"
|
||||
confirm-buy: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#D4AF37>Confirm Purchase"
|
||||
admin: "A2416&lᴅA2D1B&lɪA351F&lʀA3F24&lᴛ <#C1432E>Admin <#7A4A2A>• Page {page}"
|
||||
size:
|
||||
main: 45
|
||||
browse: 54
|
||||
my-listings: 54
|
||||
claims: 54
|
||||
sell: 54
|
||||
confirm-buy: 27
|
||||
listing-slots:
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
- 14
|
||||
- 15
|
||||
- 16
|
||||
- 19
|
||||
- 20
|
||||
- 21
|
||||
- 22
|
||||
- 23
|
||||
- 24
|
||||
- 25
|
||||
- 28
|
||||
- 29
|
||||
- 30
|
||||
- 31
|
||||
- 32
|
||||
- 33
|
||||
- 34
|
||||
buttons:
|
||||
browse:
|
||||
material: "CHEST"
|
||||
name: "<#D4AF37>Browse Auctions"
|
||||
lore:
|
||||
- "<#C7BCA8>View every active listing."
|
||||
- "<#B9C63F>Click to browse."
|
||||
sell:
|
||||
material: "EMERALD"
|
||||
name: "<#B9C63F>Sell Held Item"
|
||||
lore:
|
||||
- "<#C7BCA8>Create a listing from your hand."
|
||||
- "<#D4AF37>GUI flow with price confirmation."
|
||||
my-listings:
|
||||
material: "BOOK"
|
||||
name: "<#A8873F>My Listings"
|
||||
lore:
|
||||
- "<#C7BCA8>Manage your active listings."
|
||||
claims:
|
||||
material: "ENDER_CHEST"
|
||||
name: "<#B9C63F>Claims"
|
||||
lore:
|
||||
- "<#C7BCA8>Reclaim expired or undelivered items."
|
||||
admin:
|
||||
material: "ANVIL"
|
||||
name: "<#C1432E>Admin Panel"
|
||||
lore:
|
||||
- "<#C7BCA8>Moderate active listings."
|
||||
close:
|
||||
material: "BARRIER"
|
||||
name: "<#C1432E>Close"
|
||||
back:
|
||||
material: "ARROW"
|
||||
name: "<#D4AF37>Back"
|
||||
next:
|
||||
material: "SPECTRAL_ARROW"
|
||||
name: "<#B9C63F>Next Page"
|
||||
previous:
|
||||
material: "ARROW"
|
||||
name: "<#D4AF37>Previous Page"
|
||||
sort:
|
||||
material: "HOPPER"
|
||||
name: "<#A8873F>Sort: <#D4AF37>{sort}"
|
||||
lore:
|
||||
- "<#C7BCA8>Click to rotate sorting."
|
||||
filter:
|
||||
material: "COMPASS"
|
||||
name: "<#A8873F>Filter: <#D4AF37>{filter}"
|
||||
lore:
|
||||
- "<#C7BCA8>Shift-click a listing to filter."
|
||||
- "<#C7BCA8>Right-click to clear filter."
|
||||
confirm:
|
||||
material: "LIME_STAINED_GLASS_PANE"
|
||||
name: "<#B9C63F>Confirm"
|
||||
cancel:
|
||||
material: "RED_STAINED_GLASS_PANE"
|
||||
name: "<#C1432E>Cancel"
|
||||
@@ -0,0 +1,65 @@
|
||||
messages:
|
||||
no-permission: "{prefix} <#C1432E>You do not have permission."
|
||||
player-only: "{prefix} <#C1432E>Only players can use that."
|
||||
economy-unavailable: "{prefix} <#C1432E>The auction economy is unavailable. Try again later."
|
||||
no-item-in-hand: "{prefix} <#C1432E>Hold the item you want to list."
|
||||
invalid-price: "{prefix} <#C1432E>Enter a valid positive price."
|
||||
invalid-sort: "{prefix} <#C1432E>Use newest, oldest, lowest_price, or highest_price."
|
||||
price-too-low: "{prefix} <#C1432E>The minimum listing price is <#D4AF37>{min}<#C1432E>."
|
||||
price-too-high: "{prefix} <#C1432E>The maximum listing price is <#D4AF37>{max}<#C1432E>."
|
||||
item-blocked: "{prefix} <#C1432E>That item cannot be listed."
|
||||
listing-limit: "{prefix} <#C1432E>You have reached your active listing limit of <#D4AF37>{limit}<#C1432E>."
|
||||
listing-created: "{prefix} <#B9C63F>Listed <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
listing-created-fee: "{prefix} <#B9C63F>Listed <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>. Fee charged: <#D4AF37>{fee}<#B9C63F>."
|
||||
listing-cancelled: "{prefix} <#D4AF37>Your listing was cancelled and moved to claims."
|
||||
listing-expired: "{prefix} <#D4AF37>A listing expired and was moved to your claims."
|
||||
listing-removed-admin: "{prefix} <#D4AF37>Listing <#A8873F>{id} <#D4AF37>was removed."
|
||||
listing-no-longer-available: "{prefix} <#C1432E>That listing is no longer available."
|
||||
cannot-buy-own: "{prefix} <#C1432E>You cannot buy your own listing."
|
||||
not-enough-money: "{prefix} <#C1432E>You need <#D4AF37>{price}<#C1432E> to buy this."
|
||||
purchase-success: "{prefix} <#B9C63F>You bought <#D4AF37>{item} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
purchase-claim: "{prefix} <#B9C63F>Purchase complete. Your inventory was full, so the item was moved to claims."
|
||||
sold-notify: "{prefix} <#B9C63F>Your listing sold to <#D4AF37>{buyer} <#B9C63F>for <#D4AF37>{price}<#B9C63F>."
|
||||
inventory-full: "{prefix} <#C1432E>Your inventory is full."
|
||||
item-reclaimed: "{prefix} <#B9C63F>Claimed <#D4AF37>{item}<#B9C63F>."
|
||||
nothing-to-claim: "{prefix} <#D4AF37>You do not have anything to claim."
|
||||
reload-complete: "{prefix} <#B9C63F>Configuration, messages, and SQLite storage were reloaded."
|
||||
listing-not-found: "{prefix} <#C1432E>Listing not found."
|
||||
admin-help: "{prefix} <#D4AF37>/ahadmin reload<#7A4A2A>, <#D4AF37>/ahadmin remove <id><#7A4A2A>, <#D4AF37>/ahadmin view <player><#7A4A2A>, <#D4AF37>/ahadmin forceexpire <id>"
|
||||
price-prompt: "{prefix} <#D4AF37>Type the listing price in chat. Type <#B9C63F>cancel <#D4AF37>to stop."
|
||||
price-prompt-cancelled: "{prefix} <#D4AF37>Listing price entry cancelled."
|
||||
price-prompt-timeout: "{prefix} <#C1432E>Listing price entry timed out."
|
||||
price-set: "{prefix} <#B9C63F>Price set to <#D4AF37>{price}<#B9C63F>."
|
||||
usage-sell: "{prefix} <#D4AF37>Usage: /ah sell <price>"
|
||||
storage-save-failed: "{prefix} <#C1432E>Storage could not be saved. Check console."
|
||||
seller-paid-claim: "{prefix} <#B9C63F>Your sale funds were moved to claims."
|
||||
gui-lore:
|
||||
listing:
|
||||
- "<#7A4A2A>Seller: <#C7BCA8>{seller}"
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Remaining: <#D4AF37>{remaining}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
- ""
|
||||
- "<#B9C63F>Click to inspect and buy."
|
||||
- "<#C1432E>Admin right-click removes."
|
||||
my-listing:
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Remaining: <#D4AF37>{remaining}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
- ""
|
||||
- "<#D4AF37>Click to cancel and reclaim."
|
||||
claim:
|
||||
- "<#7A4A2A>Reason: <#C7BCA8>{reason}"
|
||||
- "<#7A4A2A>From Listing: <#A8873F>{id}"
|
||||
- "<#7A4A2A>Added: <#D4AF37>{age} ago"
|
||||
- ""
|
||||
- "<#B9C63F>Click to claim."
|
||||
confirm-buy:
|
||||
- "<#7A4A2A>Seller: <#C7BCA8>{seller}"
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>ID: <#A8873F>{id}"
|
||||
sell-item:
|
||||
- "<#7A4A2A>Price: <#B9C63F>{price}"
|
||||
- "<#7A4A2A>Fee: <#D4AF37>{fee}"
|
||||
- ""
|
||||
- "<#C7BCA8>Use Set Price, then Confirm."
|
||||
@@ -0,0 +1,64 @@
|
||||
name: DirtAuctions
|
||||
version: 1.0.0
|
||||
main: com.yourname.premiumah.PremiumAHPlugin
|
||||
api-version: '1.21'
|
||||
author: yourname
|
||||
description: Premium GUI-first auction house for Paper SMP servers.
|
||||
softdepend:
|
||||
- Vault
|
||||
- CMI
|
||||
commands:
|
||||
ah:
|
||||
description: Open the Dirt Auctions GUI.
|
||||
usage: /ah [browse|sell|listings|expired|claims|sort]
|
||||
aliases:
|
||||
- auctionhouse
|
||||
- auctions
|
||||
ahadmin:
|
||||
description: Dirt Auctions administration.
|
||||
usage: /ahadmin <reload|remove|view|forceexpire|help>
|
||||
permissions:
|
||||
premiumah.use:
|
||||
description: Allows opening the auction house.
|
||||
default: true
|
||||
premiumah.sell:
|
||||
description: Allows creating auction listings.
|
||||
default: true
|
||||
premiumah.buy:
|
||||
description: Allows buying listings.
|
||||
default: true
|
||||
premiumah.listings:
|
||||
description: Allows opening personal listings.
|
||||
default: true
|
||||
premiumah.expired:
|
||||
description: Allows reclaiming expired and claimable items.
|
||||
default: true
|
||||
premiumah.admin:
|
||||
description: Full Dirt Auctions administration.
|
||||
default: op
|
||||
children:
|
||||
premiumah.admin.remove: true
|
||||
premiumah.admin.reload: true
|
||||
premiumah.admin.view: true
|
||||
premiumah.admin.forceexpire: true
|
||||
premiumah.admin.remove:
|
||||
description: Allows removing listings.
|
||||
default: op
|
||||
premiumah.admin.reload:
|
||||
description: Allows reloading Dirt Auctions.
|
||||
default: op
|
||||
premiumah.admin.view:
|
||||
description: Allows viewing player listings.
|
||||
default: op
|
||||
premiumah.admin.forceexpire:
|
||||
description: Allows expiring listings.
|
||||
default: op
|
||||
premiumah.bypass.restrictions:
|
||||
description: Bypass configured item and price restrictions.
|
||||
default: op
|
||||
premiumah.bypass.fees:
|
||||
description: Bypass listing fees.
|
||||
default: op
|
||||
premiumah.bypass.tax:
|
||||
description: Bypass sales tax.
|
||||
default: op
|
||||
@@ -0,0 +1,5 @@
|
||||
#Generated by Maven
|
||||
#Tue Jun 23 17:40:08 EDT 2026
|
||||
artifactId=dirt-auctions
|
||||
groupId=com.yourname
|
||||
version=1.0.0
|
||||
@@ -0,0 +1,38 @@
|
||||
com/yourname/premiumah/util/IdGenerator.class
|
||||
com/yourname/premiumah/model/ClaimReason.class
|
||||
com/yourname/premiumah/gui/GuiManager$1.class
|
||||
com/yourname/premiumah/listener/PlayerSessionListener.class
|
||||
com/yourname/premiumah/manager/ListingManager$1.class
|
||||
com/yourname/premiumah/model/SortMode.class
|
||||
com/yourname/premiumah/command/AhAdminCommand.class
|
||||
com/yourname/premiumah/storage/ItemStackSerializer.class
|
||||
com/yourname/premiumah/gui/GuiManager.class
|
||||
com/yourname/premiumah/manager/AuctionHouseManager.class
|
||||
com/yourname/premiumah/listener/InventoryGuiListener.class
|
||||
com/yourname/premiumah/util/InventoryUtil.class
|
||||
com/yourname/premiumah/util/TextUtil.class
|
||||
com/yourname/premiumah/model/ListingCreationResult.class
|
||||
com/yourname/premiumah/PremiumAHPlugin.class
|
||||
com/yourname/premiumah/config/MessageManager.class
|
||||
com/yourname/premiumah/config/ConfigManager.class
|
||||
com/yourname/premiumah/listener/ChatInputListener.class
|
||||
com/yourname/premiumah/model/ActionResult.class
|
||||
com/yourname/premiumah/gui/GuiHolder.class
|
||||
com/yourname/premiumah/model/ClaimRecord.class
|
||||
com/yourname/premiumah/gui/BrowseState.class
|
||||
com/yourname/premiumah/storage/StorageManager.class
|
||||
com/yourname/premiumah/manager/ListingManager.class
|
||||
com/yourname/premiumah/command/AhCommand.class
|
||||
com/yourname/premiumah/manager/ClaimManager.class
|
||||
com/yourname/premiumah/gui/GuiType.class
|
||||
com/yourname/premiumah/gui/SellSession.class
|
||||
com/yourname/premiumah/economy/NoopEconomyService.class
|
||||
com/yourname/premiumah/economy/EconomyService.class
|
||||
com/yourname/premiumah/model/ClaimType.class
|
||||
com/yourname/premiumah/economy/VaultEconomyService.class
|
||||
com/yourname/premiumah/config/LimitPermission.class
|
||||
com/yourname/premiumah/util/TimeUtil.class
|
||||
com/yourname/premiumah/util/MaterialUtil.class
|
||||
com/yourname/premiumah/config/ButtonConfig.class
|
||||
com/yourname/premiumah/model/ListingStatus.class
|
||||
com/yourname/premiumah/model/Listing.class
|
||||
@@ -0,0 +1,36 @@
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/PremiumAHPlugin.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/command/AhAdminCommand.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/command/AhCommand.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/config/ButtonConfig.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/config/ConfigManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/config/LimitPermission.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/config/MessageManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/economy/EconomyService.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/economy/NoopEconomyService.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/economy/VaultEconomyService.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/gui/BrowseState.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/gui/GuiHolder.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/gui/GuiManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/gui/GuiType.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/gui/SellSession.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/listener/ChatInputListener.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/listener/InventoryGuiListener.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/listener/PlayerSessionListener.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/manager/AuctionHouseManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/manager/ClaimManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/manager/ListingManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ActionResult.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ClaimReason.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ClaimRecord.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ClaimType.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/Listing.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ListingCreationResult.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/ListingStatus.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/model/SortMode.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/storage/ItemStackSerializer.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/storage/StorageManager.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/util/IdGenerator.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/util/InventoryUtil.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/util/MaterialUtil.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/util/TextUtil.java
|
||||
/home/bitnix/Desktop/DirtAuctions/src/main/java/com/yourname/premiumah/util/TimeUtil.java
|
||||
Binary file not shown.
Reference in New Issue
Block a user