commit ac78d4cac6cb1305189af3f0e1e93abc1c3a88a6 Author: Xelara Networks Date: Sun Jun 14 15:39:56 2026 -0400 yep diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..218212f --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.yourname + DirtTrades + 1.0-SNAPSHOT + jar + + DirtTrades + + + 21 + UTF-8 + 21 + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + + + io.papermc.paper + paper-api + 1.21.1-R0.1-SNAPSHOT + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/yourname/dirttrades/DirtTradesPlugin.java b/src/main/java/com/yourname/dirttrades/DirtTradesPlugin.java new file mode 100644 index 0000000..845740d --- /dev/null +++ b/src/main/java/com/yourname/dirttrades/DirtTradesPlugin.java @@ -0,0 +1,44 @@ +package com.yourname.dirttrades; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +public class DirtTradesPlugin extends JavaPlugin { + + private TradeManager tradeManager; + + @Override + public void onEnable() { + saveDefaultConfig(); + + this.tradeManager = new TradeManager(this); + + TradeCommand tradeCommand = new TradeCommand(this, tradeManager); + getCommand("trade").setExecutor(tradeCommand); + getCommand("trade").setTabCompleter(tradeCommand); + + Bukkit.getPluginManager().registerEvents(new TradeListener(this, tradeManager), this); + + getLogger().info("DirtTrades enabled."); + } + + @Override + public void onDisable() { + if (tradeManager != null) { + tradeManager.shutdown(); + } + + getLogger().info("DirtTrades disabled."); + } + + public TradeManager getTradeManager() { + return tradeManager; + } + + public void reloadPlugin() { + reloadConfig(); + if (tradeManager != null) { + tradeManager.reload(); + } + } +} diff --git a/src/main/java/com/yourname/dirttrades/TradeCommand.java b/src/main/java/com/yourname/dirttrades/TradeCommand.java new file mode 100644 index 0000000..8ac090a --- /dev/null +++ b/src/main/java/com/yourname/dirttrades/TradeCommand.java @@ -0,0 +1,112 @@ +package com.yourname.dirttrades; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TradeCommand implements CommandExecutor, TabCompleter { + + private final DirtTradesPlugin plugin; + private final TradeManager tradeManager; + + public TradeCommand(DirtTradesPlugin plugin, TradeManager tradeManager) { + this.plugin = plugin; + this.tradeManager = tradeManager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + if (!sender.hasPermission("dirttrades.reload") && !sender.hasPermission("dirttrades.admin")) { + sender.sendMessage(tradeManager.msg("no-permission")); + return true; + } + + plugin.reloadPlugin(); + sender.sendMessage(tradeManager.msg("config-reloaded")); + return true; + } + + sender.sendMessage(tradeManager.msg("player-only")); + return true; + } + + if (!player.hasPermission("dirttrades.use") && !player.hasPermission("dirttrades.admin")) { + player.sendMessage(tradeManager.msg("no-permission")); + return true; + } + + if (args.length == 0) { + player.sendMessage(tradeManager.msg("usage")); + return true; + } + + String sub = args[0]; + + if (sub.equalsIgnoreCase("accept")) { + tradeManager.acceptRequest(player); + return true; + } + + if (sub.equalsIgnoreCase("deny")) { + tradeManager.denyRequest(player); + return true; + } + + if (sub.equalsIgnoreCase("reload")) { + if (!player.hasPermission("dirttrades.reload") && !player.hasPermission("dirttrades.admin")) { + player.sendMessage(tradeManager.msg("no-permission")); + return true; + } + + plugin.reloadPlugin(); + player.sendMessage(tradeManager.msg("config-reloaded")); + return true; + } + + Player target = Bukkit.getPlayerExact(sub); + if (target == null || !target.isOnline()) { + player.sendMessage(tradeManager.msg("player-not-found")); + return true; + } + + tradeManager.sendTradeRequest(player, target); + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + List options = new ArrayList<>(); + options.add("accept"); + options.add("deny"); + if (sender.hasPermission("dirttrades.reload") || sender.hasPermission("dirttrades.admin")) { + options.add("reload"); + } + + for (Player player : Bukkit.getOnlinePlayers()) { + options.add(player.getName()); + } + + String input = args[0].toLowerCase(); + List matches = new ArrayList<>(); + for (String option : options) { + if (option.toLowerCase().startsWith(input)) { + matches.add(option); + } + } + Collections.sort(matches); + return matches; + } + + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/yourname/dirttrades/TradeListener.java b/src/main/java/com/yourname/dirttrades/TradeListener.java new file mode 100644 index 0000000..f6519df --- /dev/null +++ b/src/main/java/com/yourname/dirttrades/TradeListener.java @@ -0,0 +1,182 @@ +package com.yourname.dirttrades; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class TradeListener implements Listener { + + private final DirtTradesPlugin plugin; + private final TradeManager manager; + private final Set closingPlayers = new HashSet<>(); + private BukkitTask rangeTask; + + public TradeListener(DirtTradesPlugin plugin, TradeManager manager) { + this.plugin = plugin; + this.manager = manager; + startRangeTask(); + } + + private void startRangeTask() { + rangeTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + Set checked = new HashSet<>(); + for (Player player : Bukkit.getOnlinePlayers()) { + TradeSession session = manager.getSession(player); + if (session == null || checked.contains(session)) { + continue; + } + + checked.add(session); + if (session.shouldCancelForDistance()) { + manager.cancelTrade(session, null, false); + + Player p1 = Bukkit.getPlayer(session.getPlayer1()); + Player p2 = Bukkit.getPlayer(session.getPlayer2()); + + if (p1 != null && p1.isOnline()) { + p1.sendMessage(manager.msg("too-far")); + } + if (p2 != null && p2.isOnline()) { + p2.sendMessage(manager.msg("too-far")); + } + } + } + }, 20L, 20L); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) { + return; + } + + TradeSession session = manager.getSession(player); + if (session == null) { + return; + } + + Inventory top = event.getView().getTopInventory(); + if (!top.equals(session.getInventory())) { + return; + } + + int rawSlot = event.getRawSlot(); + + if (rawSlot < top.getSize()) { + if (session.isOfferSlot(player, rawSlot)) { + session.resetAccepts(); + return; + } + + if (session.isAnyButtonSlot(rawSlot)) { + event.setCancelled(true); + session.handleButtonClick(player, rawSlot); + return; + } + + event.setCancelled(true); + return; + } + + InventoryAction action = event.getAction(); + switch (action) { + case MOVE_TO_OTHER_INVENTORY -> event.setCancelled(true); + default -> { + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onInventoryClose(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player player)) { + return; + } + + TradeSession session = manager.getSession(player); + if (session == null) { + return; + } + + if (!event.getInventory().equals(session.getInventory())) { + return; + } + + if (session.isClosing()) { + return; + } + + if (closingPlayers.contains(player.getUniqueId())) { + return; + } + + closingPlayers.add(player.getUniqueId()); + Bukkit.getScheduler().runTask(plugin, () -> { + try { + TradeSession latest = manager.getSession(player); + if (latest != null && latest == session && !latest.isClosing()) { + manager.cancelTrade(latest, player.getUniqueId(), false); + } + } finally { + closingPlayers.remove(player.getUniqueId()); + } + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + TradeSession session = manager.getSession(player); + if (session != null) { + manager.cancelTrade(session, player.getUniqueId(), true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onMove(PlayerMoveEvent event) { + if (!plugin.getConfig().getBoolean("settings.close-on-move-out-of-range", false)) { + return; + } + + if (event.getTo() == null) { + return; + } + + if (event.getFrom().getBlockX() == event.getTo().getBlockX() + && event.getFrom().getBlockY() == event.getTo().getBlockY() + && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) { + return; + } + + TradeSession session = manager.getSession(event.getPlayer()); + if (session == null) { + return; + } + + if (session.shouldCancelForDistance()) { + manager.cancelTrade(session, event.getPlayer().getUniqueId(), false); + + Player p1 = Bukkit.getPlayer(session.getPlayer1()); + Player p2 = Bukkit.getPlayer(session.getPlayer2()); + + if (p1 != null && p1.isOnline()) { + p1.sendMessage(manager.msg("too-far")); + } + if (p2 != null && p2.isOnline()) { + p2.sendMessage(manager.msg("too-far")); + } + } + } +} diff --git a/src/main/java/com/yourname/dirttrades/TradeManager.java b/src/main/java/com/yourname/dirttrades/TradeManager.java new file mode 100644 index 0000000..5b65b7f --- /dev/null +++ b/src/main/java/com/yourname/dirttrades/TradeManager.java @@ -0,0 +1,406 @@ +package com.yourname.dirttrades; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; + +public class TradeManager { + + private final DirtTradesPlugin plugin; + + private final Map pendingRequests = new HashMap<>(); + private final Map activeTrades = new HashMap<>(); + + public TradeManager(DirtTradesPlugin plugin) { + this.plugin = plugin; + } + + public void reload() { + for (TradeRequest request : new ArrayList<>(pendingRequests.values())) { + if (request.expireTask != null) { + request.expireTask.cancel(); + } + } + pendingRequests.clear(); + + for (TradeSession session : new HashSet<>(activeTrades.values())) { + cancelTrade(session, null, true); + } + + activeTrades.clear(); + } + + public void shutdown() { + reload(); + } + + public void sendTradeRequest(Player sender, Player target) { + if (plugin.getConfig().getBoolean("settings.prevent-self-trade", true) + && sender.getUniqueId().equals(target.getUniqueId())) { + sender.sendMessage(msg("cannot-trade-self")); + return; + } + + if (!target.hasPermission("dirttrades.use") && !target.hasPermission("dirttrades.admin")) { + sender.sendMessage(msg("target-no-permission")); + return; + } + + if (plugin.getConfig().getBoolean("settings.block-creative-mode-trading", true)) { + if (sender.getGameMode() == GameMode.CREATIVE || target.getGameMode() == GameMode.CREATIVE) { + sender.sendMessage(msg("creative-blocked")); + return; + } + } + + if (hasPendingRequestBetween(sender.getUniqueId(), target.getUniqueId())) { + sender.sendMessage(msg("already-in-trade")); + return; + } + + if (isBusy(sender.getUniqueId()) || isBusy(target.getUniqueId())) { + sender.sendMessage(msg("already-in-trade")); + return; + } + + TradeRequest existing = pendingRequests.remove(target.getUniqueId()); + if (existing != null && existing.expireTask != null) { + existing.expireTask.cancel(); + } + + int expireSeconds = plugin.getConfig().getInt("settings.request-expire-seconds", 30); + TradeRequest request = new TradeRequest(sender.getUniqueId(), target.getUniqueId()); + + request.expireTask = Bukkit.getScheduler().runTaskLater(plugin, () -> { + TradeRequest current = pendingRequests.get(target.getUniqueId()); + if (current != null && current.sender.equals(sender.getUniqueId())) { + pendingRequests.remove(target.getUniqueId()); + + Player onlineSender = Bukkit.getPlayer(sender.getUniqueId()); + Player onlineTarget = Bukkit.getPlayer(target.getUniqueId()); + + if (onlineSender != null && onlineSender.isOnline()) { + onlineSender.sendMessage(msg("request-expired-sender") + .replace("%target%", target.getName())); + } + if (onlineTarget != null && onlineTarget.isOnline()) { + onlineTarget.sendMessage(msg("request-expired-target") + .replace("%sender%", sender.getName())); + } + } + }, expireSeconds * 20L); + + pendingRequests.put(target.getUniqueId(), request); + + sender.sendMessage(msg("request-sent").replace("%target%", target.getName())); + target.sendMessage(msg("request-received").replace("%sender%", sender.getName())); + target.sendMessage(msg("request-received-hover")); + + playSound(sender, Sound.UI_BUTTON_CLICK); + playSound(target, Sound.UI_BUTTON_CLICK); + } + + public void acceptRequest(Player target) { + TradeRequest request = pendingRequests.remove(target.getUniqueId()); + if (request == null) { + target.sendMessage(msg("no-pending-request")); + return; + } + + if (request.expireTask != null) { + request.expireTask.cancel(); + } + + Player sender = Bukkit.getPlayer(request.sender); + if (sender == null || !sender.isOnline()) { + target.sendMessage(msg("player-not-found")); + return; + } + + if (plugin.getConfig().getBoolean("settings.block-creative-mode-trading", true)) { + if (sender.getGameMode() == GameMode.CREATIVE || target.getGameMode() == GameMode.CREATIVE) { + target.sendMessage(msg("creative-blocked")); + return; + } + } + + if (isBusy(sender.getUniqueId()) || isBusy(target.getUniqueId())) { + target.sendMessage(msg("already-in-trade")); + return; + } + + target.sendMessage(msg("request-accepted").replace("%sender%", sender.getName())); + sender.sendMessage(msg("trade-started").replace("%player%", target.getName())); + target.sendMessage(msg("trade-started").replace("%player%", sender.getName())); + + startTrade(sender, target); + } + + public void denyRequest(Player target) { + TradeRequest request = pendingRequests.remove(target.getUniqueId()); + if (request == null) { + target.sendMessage(msg("no-pending-request")); + return; + } + + if (request.expireTask != null) { + request.expireTask.cancel(); + } + + Player sender = Bukkit.getPlayer(request.sender); + target.sendMessage(msg("request-denied").replace("%sender%", sender != null ? sender.getName() : "Unknown")); + + if (sender != null && sender.isOnline()) { + sender.sendMessage(msg("request-denied-sender").replace("%target%", target.getName())); + } + } + + public void startTrade(Player player1, Player player2) { + TradeSession session = new TradeSession(plugin, this, player1.getUniqueId(), player2.getUniqueId()); + activeTrades.put(player1.getUniqueId(), session); + activeTrades.put(player2.getUniqueId(), session); + session.open(); + } + + public boolean isBusy(UUID uuid) { + if (activeTrades.containsKey(uuid)) { + return true; + } + + for (TradeRequest request : pendingRequests.values()) { + if (request.sender.equals(uuid) || request.target.equals(uuid)) { + return true; + } + } + + return false; + } + + private boolean hasPendingRequestBetween(UUID a, UUID b) { + for (TradeRequest request : pendingRequests.values()) { + if ((request.sender.equals(a) && request.target.equals(b)) + || (request.sender.equals(b) && request.target.equals(a))) { + return true; + } + } + return false; + } + + public TradeSession getSession(Player player) { + return activeTrades.get(player.getUniqueId()); + } + + public void removeSession(TradeSession session) { + activeTrades.remove(session.getPlayer1()); + activeTrades.remove(session.getPlayer2()); + } + + public void cancelTrade(TradeSession session, UUID cancelledBy, boolean silent) { + session.returnItemsToOwners(); + removeSession(session); + + Player p1 = Bukkit.getPlayer(session.getPlayer1()); + Player p2 = Bukkit.getPlayer(session.getPlayer2()); + + clearActionBar(p1); + clearActionBar(p2); + + if (p1 != null && p1.isOnline() && p1.getOpenInventory().getTopInventory().equals(session.getInventory())) { + p1.closeInventory(); + } + if (p2 != null && p2.isOnline() && p2.getOpenInventory().getTopInventory().equals(session.getInventory())) { + p2.closeInventory(); + } + + if (!silent) { + if (p1 != null && p1.isOnline()) { + if (cancelledBy != null && cancelledBy.equals(p1.getUniqueId())) { + p1.sendMessage(msg("trade-cancelled")); + } else { + p1.sendMessage(msg("trade-cancelled-other")); + } + } + + if (p2 != null && p2.isOnline()) { + if (cancelledBy != null && cancelledBy.equals(p2.getUniqueId())) { + p2.sendMessage(msg("trade-cancelled")); + } else { + p2.sendMessage(msg("trade-cancelled-other")); + } + } + } + } + + public void completeTrade(TradeSession session) { + Player p1 = Bukkit.getPlayer(session.getPlayer1()); + Player p2 = Bukkit.getPlayer(session.getPlayer2()); + + if (p1 == null || p2 == null) { + cancelTrade(session, null, true); + return; + } + + List p1Items = session.collectPlayer1Items(); + List p2Items = session.collectPlayer2Items(); + + giveItems(p1, p2Items); + giveItems(p2, p1Items); + + removeSession(session); + + clearActionBar(p1); + clearActionBar(p2); + + if (p1.getOpenInventory().getTopInventory().equals(session.getInventory())) { + p1.closeInventory(); + } + if (p2.getOpenInventory().getTopInventory().equals(session.getInventory())) { + p2.closeInventory(); + } + + p1.sendMessage(msg("trade-completed")); + p2.sendMessage(msg("trade-completed")); + + playSound(p1, Sound.ENTITY_PLAYER_LEVELUP); + playSound(p2, Sound.ENTITY_PLAYER_LEVELUP); + } + + private void giveItems(Player player, List items) { + boolean droppedAny = false; + + for (ItemStack item : items) { + if (item == null || item.getType().isAir()) { + continue; + } + + HashMap leftover = player.getInventory().addItem(item); + if (!leftover.isEmpty()) { + for (ItemStack left : leftover.values()) { + if (plugin.getConfig().getBoolean("settings.drop-items-if-inventory-full", true)) { + player.getWorld().dropItemNaturally(player.getLocation(), left); + droppedAny = true; + } + } + } + } + + if (droppedAny) { + player.sendMessage(msg("inventory-full-drop")); + } + } + + public void sendWaitingActionBars(TradeSession session, boolean player1Accepted, boolean player2Accepted) { + if (!plugin.getConfig().getBoolean("settings.actionbar-enabled", true)) { + return; + } + + Player p1 = Bukkit.getPlayer(session.getPlayer1()); + Player p2 = Bukkit.getPlayer(session.getPlayer2()); + + if (p1 == null || p2 == null) { + return; + } + + if (player1Accepted && !player2Accepted) { + sendActionBar(p1, rawMessage("actionbar-waiting-self")); + sendActionBar(p2, rawMessage("actionbar-waiting-other")); + return; + } + + if (!player1Accepted && player2Accepted) { + sendActionBar(p2, rawMessage("actionbar-waiting-self")); + sendActionBar(p1, rawMessage("actionbar-waiting-other")); + return; + } + + clearActionBar(p1); + clearActionBar(p2); + } + + public void clearActionBar(Player player) { + if (player == null || !player.isOnline()) { + return; + } + + player.sendActionBar(Component.text(color(plugin.getConfig().getString("messages.actionbar-cleared", "")))); + } + + public void sendActionBar(Player player, String message) { + if (player == null || !player.isOnline()) { + return; + } + + player.sendActionBar(Component.text(color(message))); + } + + public String rawMessage(String path) { + return plugin.getConfig().getString("messages." + path, ""); + } + + public String msg(String path) { + String prefix = color(plugin.getConfig().getString("messages.prefix", "&6[DirtTrades] &r")); + String text = color(plugin.getConfig().getString("messages." + path, "&cMissing message: " + path)); + return prefix + text; + } + + public String color(String text) { + return text == null ? "" : text.replace("&", "ยง"); + } + + public ItemStack createConfiguredItem(String path) { + ConfigurationSection section = plugin.getConfig().getConfigurationSection("items." + path); + if (section == null) { + return new ItemStack(Material.BARRIER); + } + + Material material = Material.matchMaterial(section.getString("material", "BARRIER")); + if (material == null) { + material = Material.BARRIER; + } + + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.displayName(Component.text(color(section.getString("name", path)))); + List loreLines = section.getStringList("lore"); + if (!loreLines.isEmpty()) { + List lore = new ArrayList<>(); + for (String line : loreLines) { + lore.add(Component.text(color(line))); + } + meta.lore(lore); + } + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + item.setItemMeta(meta); + } + + return item; + } + + public void playSound(Player player, Sound sound) { + if (plugin.getConfig().getBoolean("settings.sound-enabled", true)) { + player.playSound(player.getLocation(), sound, 1f, 1f); + } + } + + private static class TradeRequest { + private final UUID sender; + private final UUID target; + private BukkitTask expireTask; + + private TradeRequest(UUID sender, UUID target) { + this.sender = sender; + this.target = target; + } + } +} diff --git a/src/main/java/com/yourname/dirttrades/TradeSession.java b/src/main/java/com/yourname/dirttrades/TradeSession.java new file mode 100644 index 0000000..05db5d0 --- /dev/null +++ b/src/main/java/com/yourname/dirttrades/TradeSession.java @@ -0,0 +1,252 @@ +package com.yourname.dirttrades; + +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class TradeSession { + + private final DirtTradesPlugin plugin; + private final TradeManager manager; + private final UUID player1; + private final UUID player2; + private final Inventory inventory; + + private boolean player1Accepted = false; + private boolean player2Accepted = false; + private boolean closing = false; + + public TradeSession(DirtTradesPlugin plugin, TradeManager manager, UUID player1, UUID player2) { + this.plugin = plugin; + this.manager = manager; + this.player1 = player1; + this.player2 = player2; + + String p1Name = Bukkit.getOfflinePlayer(player1).getName(); + String p2Name = Bukkit.getOfflinePlayer(player2).getName(); + String title = manager.color(plugin.getConfig().getString("gui.title", "&8Trade: %player1% &7<-> &f%player2%")) + .replace("%player1%", p1Name == null ? "Player1" : p1Name) + .replace("%player2%", p2Name == null ? "Player2" : p2Name); + + int size = plugin.getConfig().getInt("gui.size", 54); + this.inventory = Bukkit.createInventory(null, size, Component.text(title)); + + refreshButtons(); + } + + public void open() { + Player p1 = Bukkit.getPlayer(player1); + Player p2 = Bukkit.getPlayer(player2); + + if (p1 == null || p2 == null) { + manager.cancelTrade(this, null, true); + return; + } + + refreshButtons(); + p1.openInventory(inventory); + p2.openInventory(inventory); + manager.clearActionBar(p1); + manager.clearActionBar(p2); + } + + public Inventory getInventory() { + return inventory; + } + + public UUID getPlayer1() { + return player1; + } + + public UUID getPlayer2() { + return player2; + } + + public boolean isClosing() { + return closing; + } + + public boolean isPlayer1(Player player) { + return player.getUniqueId().equals(player1); + } + + public boolean isPlayer2(Player player) { + return player.getUniqueId().equals(player2); + } + + public boolean isOfferSlot(Player player, int slot) { + if (isPlayer1(player)) { + return plugin.getConfig().getIntegerList("gui.player1-slots").contains(slot); + } + if (isPlayer2(player)) { + return plugin.getConfig().getIntegerList("gui.player2-slots").contains(slot); + } + return false; + } + + public boolean isAnyButtonSlot(int slot) { + return slot == plugin.getConfig().getInt("gui.accept-slot", 22) + || slot == plugin.getConfig().getInt("gui.decline-slot", 31); + } + + public void handleButtonClick(Player player, int slot) { + int acceptSlot = plugin.getConfig().getInt("gui.accept-slot", 22); + int declineSlot = plugin.getConfig().getInt("gui.decline-slot", 31); + + if (slot == acceptSlot) { + if (isPlayer1(player)) { + player1Accepted = true; + } else if (isPlayer2(player)) { + player2Accepted = true; + } + + manager.playSound(player, Sound.UI_BUTTON_CLICK); + refreshButtons(); + manager.sendWaitingActionBars(this, player1Accepted, player2Accepted); + tryComplete(); + return; + } + + if (slot == declineSlot) { + manager.cancelTrade(this, player.getUniqueId(), false); + } + } + + public void resetAccepts() { + if (!player1Accepted && !player2Accepted) { + return; + } + + player1Accepted = false; + player2Accepted = false; + refreshButtons(); + manager.sendWaitingActionBars(this, player1Accepted, player2Accepted); + } + + private void tryComplete() { + if (player1Accepted && player2Accepted) { + closing = true; + manager.completeTrade(this); + } + } + + public void refreshButtons() { + for (int slot : plugin.getConfig().getIntegerList("gui.divider-slots")) { + inventory.setItem(slot, manager.createConfiguredItem("divider")); + } + + int acceptSlot = plugin.getConfig().getInt("gui.accept-slot", 22); + int declineSlot = plugin.getConfig().getInt("gui.decline-slot", 31); + + if (player1Accepted || player2Accepted) { + inventory.setItem(acceptSlot, manager.createConfiguredItem("accepted")); + } else { + inventory.setItem(acceptSlot, manager.createConfiguredItem("accept")); + } + + inventory.setItem(declineSlot, manager.createConfiguredItem("decline")); + } + + public void returnItemsToOwners() { + List p1Items = collectPlayer1Items(); + List p2Items = collectPlayer2Items(); + + Player p1 = Bukkit.getPlayer(player1); + Player p2 = Bukkit.getPlayer(player2); + + if (p1 != null && p1.isOnline()) { + giveBack(p1, p1Items); + } + + if (p2 != null && p2.isOnline()) { + giveBack(p2, p2Items); + } + + clearOfferSlots(); + } + + private void giveBack(Player player, List items) { + boolean droppedAny = false; + + for (ItemStack item : items) { + if (item == null || item.getType().isAir()) { + continue; + } + + var leftover = player.getInventory().addItem(item); + if (!leftover.isEmpty()) { + for (ItemStack left : leftover.values()) { + if (plugin.getConfig().getBoolean("settings.drop-items-if-inventory-full", true)) { + player.getWorld().dropItemNaturally(player.getLocation(), left); + droppedAny = true; + } + } + } + } + + if (droppedAny) { + player.sendMessage(manager.msg("inventory-full-drop")); + } + } + + public List collectPlayer1Items() { + List items = new ArrayList<>(); + for (int slot : plugin.getConfig().getIntegerList("gui.player1-slots")) { + ItemStack item = inventory.getItem(slot); + if (item != null && !item.getType().isAir()) { + items.add(item.clone()); + } + } + return items; + } + + public List collectPlayer2Items() { + List items = new ArrayList<>(); + for (int slot : plugin.getConfig().getIntegerList("gui.player2-slots")) { + ItemStack item = inventory.getItem(slot); + if (item != null && !item.getType().isAir()) { + items.add(item.clone()); + } + } + return items; + } + + public void clearOfferSlots() { + for (int slot : plugin.getConfig().getIntegerList("gui.player1-slots")) { + inventory.setItem(slot, new ItemStack(Material.AIR)); + } + for (int slot : plugin.getConfig().getIntegerList("gui.player2-slots")) { + inventory.setItem(slot, new ItemStack(Material.AIR)); + } + } + + public boolean shouldCancelForDistance() { + if (!plugin.getConfig().getBoolean("settings.close-on-move-out-of-range", false)) { + return false; + } + + Player p1 = Bukkit.getPlayer(player1); + Player p2 = Bukkit.getPlayer(player2); + if (p1 == null || p2 == null) { + return true; + } + + if (!p1.getWorld().equals(p2.getWorld())) { + return true; + } + + Location l1 = p1.getLocation(); + Location l2 = p2.getLocation(); + double max = plugin.getConfig().getDouble("settings.max-trade-distance", 10.0); + return l1.distanceSquared(l2) > (max * max); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..85a8b60 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,131 @@ +settings: + request-expire-seconds: 30 + prevent-self-trade: true + block-creative-mode-trading: true + close-on-move-out-of-range: false + max-trade-distance: 10.0 + drop-items-if-inventory-full: true + sound-enabled: true + actionbar-enabled: true + +gui: + title: "&8Dirt Trade: %player1% &7<-> &f%player2%" + size: 54 + + divider-slots: + - 4 + - 13 + - 40 + - 49 + + player1-slots: + - 0 + - 1 + - 2 + - 3 + - 9 + - 10 + - 11 + - 12 + - 18 + - 19 + - 20 + - 21 + - 27 + - 28 + - 29 + - 30 + - 36 + - 37 + - 38 + - 39 + - 45 + - 46 + - 47 + - 48 + + player2-slots: + - 5 + - 6 + - 7 + - 8 + - 14 + - 15 + - 16 + - 17 + - 23 + - 24 + - 25 + - 26 + - 32 + - 33 + - 34 + - 35 + - 41 + - 42 + - 43 + - 44 + - 50 + - 51 + - 52 + - 53 + + accept-slot: 22 + decline-slot: 31 + +items: + divider: + material: BLACK_STAINED_GLASS_PANE + name: "&0" + lore: [] + + accept: + material: LIME_CONCRETE + name: "&aAccept Trade" + lore: + - "&7Click to accept." + - "&7If items change, accept resets." + + accepted: + material: GREEN_CONCRETE + name: "&aAccepted" + lore: + - "&7Waiting for the other player..." + + decline: + material: RED_CONCRETE + name: "&cDecline Trade" + lore: + - "&7Click to cancel this trade." + +messages: + prefix: "&6[DirtTrades] &r" + + no-permission: "&cYou do not have permission." + player-only: "&cOnly players can use this command." + usage: "&eUsage: /trade " + player-not-found: "&cThat player is not online." + cannot-trade-self: "&cYou cannot trade with yourself." + target-no-permission: "&cThat player cannot use trades." + already-in-trade: "&cYou or that player is already in a trade." + request-sent: "&aTrade request sent to &e%target%&a." + request-received: "&e%sender% &ahas sent you a trade request." + request-received-hover: "&7Use &f/trade accept &7or &f/trade deny" + no-pending-request: "&cYou have no pending trade request." + request-accepted: "&aYou accepted the trade request from &e%sender%&a." + request-denied: "&cYou denied the trade request from &e%sender%&c." + request-denied-sender: "&c%target% denied your trade request." + request-expired-sender: "&cYour trade request to &e%target% &cexpired." + request-expired-target: "&cThe trade request from &e%sender% &cexpired." + trade-started: "&aTrade started with &e%player%&a." + trade-cancelled: "&cTrade cancelled." + trade-cancelled-other: "&cThe trade was cancelled by the other player." + trade-completed: "&aTrade completed successfully." + inventory-full-drop: "&eYour inventory was full, so some items were dropped." + config-reloaded: "&aDirtTrades config reloaded." + creative-blocked: "&cYou cannot trade while in creative mode." + too-far: "&cTrade cancelled because a player moved too far away." + + actionbar-waiting-self: "&aYou accepted &7- waiting for the other player..." + actionbar-waiting-other: "&eOther player accepted &7- click accept to finish." + actionbar-cleared: "" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..5ce9578 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,25 @@ +name: DirtTrades +version: 1.0-SNAPSHOT +main: com.yourname.dirttrades.DirtTradesPlugin +api-version: '1.21' + +commands: + trade: + description: Main trade command + usage: /trade + aliases: [dtrade, trades] + +permissions: + dirttrades.use: + description: Allows using trade commands + default: true + + dirttrades.reload: + description: Allows reloading the plugin config + default: op + + dirttrades.admin: + description: Admin access to DirtTrades + default: op + children: + dirttrades.reload: true diff --git a/target/DirtTrades-1.0-SNAPSHOT.jar b/target/DirtTrades-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..3475474 Binary files /dev/null and b/target/DirtTrades-1.0-SNAPSHOT.jar differ diff --git a/target/classes/com/yourname/dirttrades/DirtTradesPlugin.class b/target/classes/com/yourname/dirttrades/DirtTradesPlugin.class new file mode 100644 index 0000000..8976bff Binary files /dev/null and b/target/classes/com/yourname/dirttrades/DirtTradesPlugin.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeCommand.class b/target/classes/com/yourname/dirttrades/TradeCommand.class new file mode 100644 index 0000000..0076892 Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeCommand.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeListener$1.class b/target/classes/com/yourname/dirttrades/TradeListener$1.class new file mode 100644 index 0000000..bde2f49 Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeListener$1.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeListener.class b/target/classes/com/yourname/dirttrades/TradeListener.class new file mode 100644 index 0000000..e2dec21 Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeListener.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeManager$TradeRequest.class b/target/classes/com/yourname/dirttrades/TradeManager$TradeRequest.class new file mode 100644 index 0000000..cf52302 Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeManager$TradeRequest.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeManager.class b/target/classes/com/yourname/dirttrades/TradeManager.class new file mode 100644 index 0000000..9a47c6b Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeManager.class differ diff --git a/target/classes/com/yourname/dirttrades/TradeSession.class b/target/classes/com/yourname/dirttrades/TradeSession.class new file mode 100644 index 0000000..08c9b51 Binary files /dev/null and b/target/classes/com/yourname/dirttrades/TradeSession.class differ diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..85a8b60 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,131 @@ +settings: + request-expire-seconds: 30 + prevent-self-trade: true + block-creative-mode-trading: true + close-on-move-out-of-range: false + max-trade-distance: 10.0 + drop-items-if-inventory-full: true + sound-enabled: true + actionbar-enabled: true + +gui: + title: "&8Dirt Trade: %player1% &7<-> &f%player2%" + size: 54 + + divider-slots: + - 4 + - 13 + - 40 + - 49 + + player1-slots: + - 0 + - 1 + - 2 + - 3 + - 9 + - 10 + - 11 + - 12 + - 18 + - 19 + - 20 + - 21 + - 27 + - 28 + - 29 + - 30 + - 36 + - 37 + - 38 + - 39 + - 45 + - 46 + - 47 + - 48 + + player2-slots: + - 5 + - 6 + - 7 + - 8 + - 14 + - 15 + - 16 + - 17 + - 23 + - 24 + - 25 + - 26 + - 32 + - 33 + - 34 + - 35 + - 41 + - 42 + - 43 + - 44 + - 50 + - 51 + - 52 + - 53 + + accept-slot: 22 + decline-slot: 31 + +items: + divider: + material: BLACK_STAINED_GLASS_PANE + name: "&0" + lore: [] + + accept: + material: LIME_CONCRETE + name: "&aAccept Trade" + lore: + - "&7Click to accept." + - "&7If items change, accept resets." + + accepted: + material: GREEN_CONCRETE + name: "&aAccepted" + lore: + - "&7Waiting for the other player..." + + decline: + material: RED_CONCRETE + name: "&cDecline Trade" + lore: + - "&7Click to cancel this trade." + +messages: + prefix: "&6[DirtTrades] &r" + + no-permission: "&cYou do not have permission." + player-only: "&cOnly players can use this command." + usage: "&eUsage: /trade " + player-not-found: "&cThat player is not online." + cannot-trade-self: "&cYou cannot trade with yourself." + target-no-permission: "&cThat player cannot use trades." + already-in-trade: "&cYou or that player is already in a trade." + request-sent: "&aTrade request sent to &e%target%&a." + request-received: "&e%sender% &ahas sent you a trade request." + request-received-hover: "&7Use &f/trade accept &7or &f/trade deny" + no-pending-request: "&cYou have no pending trade request." + request-accepted: "&aYou accepted the trade request from &e%sender%&a." + request-denied: "&cYou denied the trade request from &e%sender%&c." + request-denied-sender: "&c%target% denied your trade request." + request-expired-sender: "&cYour trade request to &e%target% &cexpired." + request-expired-target: "&cThe trade request from &e%sender% &cexpired." + trade-started: "&aTrade started with &e%player%&a." + trade-cancelled: "&cTrade cancelled." + trade-cancelled-other: "&cThe trade was cancelled by the other player." + trade-completed: "&aTrade completed successfully." + inventory-full-drop: "&eYour inventory was full, so some items were dropped." + config-reloaded: "&aDirtTrades config reloaded." + creative-blocked: "&cYou cannot trade while in creative mode." + too-far: "&cTrade cancelled because a player moved too far away." + + actionbar-waiting-self: "&aYou accepted &7- waiting for the other player..." + actionbar-waiting-other: "&eOther player accepted &7- click accept to finish." + actionbar-cleared: "" diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..5ce9578 --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,25 @@ +name: DirtTrades +version: 1.0-SNAPSHOT +main: com.yourname.dirttrades.DirtTradesPlugin +api-version: '1.21' + +commands: + trade: + description: Main trade command + usage: /trade + aliases: [dtrade, trades] + +permissions: + dirttrades.use: + description: Allows using trade commands + default: true + + dirttrades.reload: + description: Allows reloading the plugin config + default: op + + dirttrades.admin: + description: Admin access to DirtTrades + default: op + children: + dirttrades.reload: true diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..f5df43e --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sun Jun 14 15:02:31 EDT 2026 +artifactId=DirtTrades +groupId=com.yourname +version=1.0-SNAPSHOT diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..4848ac4 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,7 @@ +com/yourname/dirttrades/TradeListener$1.class +com/yourname/dirttrades/TradeCommand.class +com/yourname/dirttrades/TradeListener.class +com/yourname/dirttrades/DirtTradesPlugin.class +com/yourname/dirttrades/TradeManager$TradeRequest.class +com/yourname/dirttrades/TradeManager.class +com/yourname/dirttrades/TradeSession.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..bbbf191 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,5 @@ +/home/bitnix/Desktop/DirtTrades/src/main/java/com/yourname/dirttrades/DirtTradesPlugin.java +/home/bitnix/Desktop/DirtTrades/src/main/java/com/yourname/dirttrades/TradeCommand.java +/home/bitnix/Desktop/DirtTrades/src/main/java/com/yourname/dirttrades/TradeListener.java +/home/bitnix/Desktop/DirtTrades/src/main/java/com/yourname/dirttrades/TradeManager.java +/home/bitnix/Desktop/DirtTrades/src/main/java/com/yourname/dirttrades/TradeSession.java