Skip to content

Instantly share code, notes, and snippets.

@rexlManu
Last active December 25, 2024 07:03
Show Gist options
  • Save rexlManu/fddc083c621480abf943c8a7dad10989 to your computer and use it in GitHub Desktop.
Save rexlManu/fddc083c621480abf943c8a7dad10989 to your computer and use it in GitHub Desktop.
Example Trade GUI in InvUI
package de.rexlmanu.pluginbase.trade;
import de.rexlmanu.pluginbase.utils.PlayerUtils;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import xyz.xenondevs.invui.gui.Gui;
import xyz.xenondevs.invui.inventory.VirtualInventory;
import xyz.xenondevs.invui.inventory.event.PlayerUpdateReason;
import xyz.xenondevs.invui.item.Item;
import xyz.xenondevs.invui.item.builder.ItemBuilder;
import xyz.xenondevs.invui.item.impl.SimpleItem;
import xyz.xenondevs.invui.window.Window;
import java.util.*;
/**
* This class manages creating trades between multiple players using
* a GUI-based system. Each TradeSide is responsible for tracking
* a player's inventory interactions and confirmation status.
*/
public class TradeGui {
/**
* This inner class represents one side of the trade. It holds a
* VirtualInventory and the associated Player, along with a flag
* to track whether that player has confirmed the trade.
*/
public static class TradeSide {
private final VirtualInventory virtualInventory;
private final Player player;
private boolean confirmed;
public TradeSide(VirtualInventory virtualInventory, Player player) {
this.virtualInventory = virtualInventory;
this.player = player;
this.confirmed = false;
}
public VirtualInventory getVirtualInventory() {
return virtualInventory;
}
public Player getPlayer() {
return player;
}
public boolean isConfirmed() {
return confirmed;
}
public void setConfirmed(boolean confirmed) {
this.confirmed = confirmed;
}
}
/**
* The Trade class holds references to all sides of the trade,
* the confirm buttons in the GUI, and any open windows for the trade.
*/
public static class Trade {
private final List<TradeSide> sides;
private final Set<Item> confirmButtons;
private final Set<Window> windows;
public Trade() {
this.sides = new ArrayList<>();
this.confirmButtons = new HashSet<>();
this.windows = new HashSet<>();
}
public List<TradeSide> getSides() {
return sides;
}
public Set<Item> getConfirmButtons() {
return confirmButtons;
}
public Set<Window> getWindows() {
return windows;
}
public Optional<TradeSide> getSide(int index) {
if (index < 0 || index >= sides.size()) {
return Optional.empty();
}
return Optional.of(sides.get(index));
}
public boolean isConfirmed() {
// True only if all TradeSide objects are confirmed
return sides.stream().allMatch(TradeSide::isConfirmed);
}
public Optional<TradeSide> getOppositeSide(TradeSide side) {
return sides.stream().filter(s -> s != side).findFirst();
}
public Optional<TradeSide> getSide(Player player) {
return sides.stream().filter(s -> s.getPlayer().equals(player)).findFirst();
}
}
/**
* Creates a new Trade object for the supplied players.
* Each player gets a VirtualInventory and their updates
* are restricted to their own side of the inventory.
*
* @param players the players who will participate in the trade
* @return a Trade containing all participants
*/
public static Trade createTrade(Player... players) {
Trade trade = new Trade();
for (Player player : players) {
VirtualInventory virtualInventory = new VirtualInventory(27);
TradeSide side = new TradeSide(virtualInventory, player);
// Restrict updates so only the owner's inventory changes.
virtualInventory.setPreUpdateHandler(event -> {
if (event.getUpdateReason() instanceof PlayerUpdateReason reason) {
if (!player.equals(reason.getPlayer())) {
event.setCancelled(true);
}
}
});
trade.getSides().add(side);
}
return trade;
}
/**
* Opens the GUI for a specific viewer and configures
* the layout to show both sides of the trade.
*
* @param viewer the player who will see the GUI
* @param trade the trade to be shown
*/
public void openGui(Player viewer, Trade trade) {
// Basic layout structure for 6 rows
Gui.Builder.Normal layout = Gui.normal()
.setStructure(
"y y y y 1 p p p p",
"y y y y x p p p p",
"y y y y x p p p p",
"y y y y x p p p p",
"y y y y x p p p p",
"y y y y 2 p p p p"
)
.addIngredient('x', new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE));
// Find the correct participant side and opposite side
trade.getSide(viewer).ifPresent(tradeSide -> {
drawSide(layout, tradeSide, trade, 'y', '1');
trade.getOppositeSide(tradeSide).ifPresent(opposite -> {
drawSide(layout, opposite, trade, 'p', '2');
});
});
// Build the Window and open it for the viewer
Window window = Window.single()
.setTitle("Trade")
.setGui(layout)
.setViewer(viewer)
.build();
// Keep track of this window for closure after the trade completes
trade.getWindows().add(window);
window.open();
}
/**
* Configures the GUI's item symbols for a specific side of the trade.
* Sets up the confirm button that the user can click to finalize.
*/
private void drawSide(Gui.Builder.Normal builder, TradeSide tradeSide, Trade trade, char invSymbol, char confirmSymbol) {
// The inventory area is mapped to the provided invSymbol
builder.addIngredient(invSymbol, tradeSide.getVirtualInventory());
// Prepare the SimpleItem for the confirm button
SimpleItem confirmButton = new SimpleItem(
state -> new ItemBuilder(tradeSide.isConfirmed() ? Material.LIME_DYE : Material.GRAY_DYE).get(),
click -> {
// Only the owner of the side can toggle confirmation
if (click.getClickType().isLeftClick() && tradeSide.getPlayer().equals(click.getPlayer())) {
tradeSide.setConfirmed(!tradeSide.isConfirmed());
// Update all confirm buttons to reflect new state
for (Item item : trade.getConfirmButtons()) {
item.notifyWindows();
}
// If every side is confirmed, finalize the trade
if (trade.isConfirmed()) {
executeTrade(trade);
}
}
}
);
// Add the confirm button to the GUI, then store the button reference
builder.addIngredient(confirmSymbol, confirmButton);
trade.getConfirmButtons().add(confirmButton);
}
/**
* Finalizes the trade by closing all open windows, then
* transferring items from each participant's inventory
* to their opposite side.
*/
private void executeTrade(Trade trade) {
// Close all windows first
for (Window window : trade.getWindows()) {
window.close();
}
// Distribute items between opposite sides
for (TradeSide side : trade.getSides()) {
trade.getOppositeSide(side).ifPresent(opposite -> {
Arrays.stream(side.getVirtualInventory().getItems())
.filter(Objects::nonNull)
.forEach(itemStack -> PlayerUtils.giveOrDropItem(opposite.getPlayer(), itemStack));
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment