This commit is contained in:
2026-06-14 15:39:56 -04:00
commit ac78d4cac6
22 changed files with 1372 additions and 0 deletions
View File
+47
View File
@@ -0,0 +1,47 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yourname</groupId>
<artifactId>DirtTrades</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>DirtTrades</name>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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();
}
}
}
@@ -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<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
List<String> 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<String> matches = new ArrayList<>();
for (String option : options) {
if (option.toLowerCase().startsWith(input)) {
matches.add(option);
}
}
Collections.sort(matches);
return matches;
}
return Collections.emptyList();
}
}
@@ -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<UUID> 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<TradeSession> 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"));
}
}
}
}
@@ -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<UUID, TradeRequest> pendingRequests = new HashMap<>();
private final Map<UUID, TradeSession> 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<ItemStack> p1Items = session.collectPlayer1Items();
List<ItemStack> 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<ItemStack> items) {
boolean droppedAny = false;
for (ItemStack item : items) {
if (item == null || item.getType().isAir()) {
continue;
}
HashMap<Integer, ItemStack> 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<String> loreLines = section.getStringList("lore");
if (!loreLines.isEmpty()) {
List<Component> 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;
}
}
}
@@ -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<ItemStack> p1Items = collectPlayer1Items();
List<ItemStack> 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<ItemStack> 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<ItemStack> collectPlayer1Items() {
List<ItemStack> 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<ItemStack> collectPlayer2Items() {
List<ItemStack> 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);
}
}
+131
View File
@@ -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|accept|deny|reload>"
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: ""
+25
View File
@@ -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 <player|accept|deny|reload>
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
Binary file not shown.
+131
View File
@@ -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|accept|deny|reload>"
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: ""
+25
View File
@@ -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 <player|accept|deny|reload>
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
+5
View File
@@ -0,0 +1,5 @@
#Generated by Maven
#Sun Jun 14 15:02:31 EDT 2026
artifactId=DirtTrades
groupId=com.yourname
version=1.0-SNAPSHOT
@@ -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
@@ -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