ZephMC Docs

Examples

Three complete integrations — a sale log, a category tax, and listing on a player's behalf.

Three whole plugins, not fragments. Each compiles against ZyAuctions 1.0.0.

1. A sale history

ZyAuctions deliberately keeps no record of completed sales. If you want one, it is about forty lines: listen for ListingSoldEvent and write rows.

SaleLogPlugin.java
package com.example.salelog;

import com.zephrynis.zyauctions.api.event.ListingSoldEvent;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.List;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

public final class SaleLogPlugin extends JavaPlugin implements Listener {

    private Path log;

    @Override
    public void onEnable() {
        this.log = getDataFolder().toPath().resolve("sales.csv");
        try {
            Files.createDirectories(getDataFolder().toPath());
            if (Files.notExists(log)) {
                write("time,seller,buyer,material,amount,price,seller_received,auction");
            }
        } catch (IOException e) {
            getSLF4JLogger().error("Could not open the sale log; disabling.", e);
            getServer().getPluginManager().disablePlugin(this);
            return;
        }
        getServer().getPluginManager().registerEvents(this, this);
    }

    // MONITOR: we only observe, and we never change anything.
    @EventHandler(priority = EventPriority.MONITOR)
    public void onSold(ListingSoldEvent event) {
        var item = event.listing().item();
        String row = String.join(",",
                Instant.now().toString(),
                event.listing().seller().toString(),
                event.buyer().toString(),
                item.getType().name(),
                String.valueOf(item.getAmount()),
                String.valueOf(event.price()),
                String.valueOf(event.sellerReceived()),
                String.valueOf(event.wonAtAuction()));

        // Disk I/O never happens on the tick.
        getServer().getAsyncScheduler().runNow(this, task -> write(row));
    }

    private void write(String line) {
        try {
            Files.writeString(log, line + System.lineSeparator(), StandardCharsets.UTF_8,
                    StandardOpenOption.CREATE, StandardOpenOption.APPEND);
        } catch (IOException e) {
            getSLF4JLogger().warn("Could not append to the sale log: {}", e.getMessage());
        }
    }
}
plugin.yml
name: SaleLog
version: 1.0.0
main: com.example.salelog.SaleLogPlugin
api-version: '26.2'
depend: [ZyAuctions]

The important bit is the last method. ListingSoldEvent is synchronous and fires on the tick, so the file write is pushed onto the async scheduler rather than blocking the server for the length of a disk flush.

2. A per-category tax

ZyAuctions has one flat tax rate. This charges more on weapons and armour, and nothing on food, by adjusting ListingPurchaseEvent before the money moves.

CategoryTaxPlugin.java
package com.example.categorytax;

import com.zephrynis.zyauctions.api.event.ListingPurchaseEvent;
import com.zephrynis.zyauctions.api.model.Category;
import java.util.EnumMap;
import java.util.Map;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

public final class CategoryTaxPlugin extends JavaPlugin implements Listener {

    /** Extra tax on top of the server's rate, as a percentage of the sale price. */
    private final Map<Category, Double> surcharge = new EnumMap<>(Category.class);

    @Override
    public void onEnable() {
        surcharge.put(Category.WEAPONS, 10.0);
        surcharge.put(Category.ARMOR, 10.0);
        surcharge.put(Category.FOOD, -100.0);   // enough to wipe the base tax out
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    public void onPurchase(ListingPurchaseEvent event) {
        Double extra = surcharge.get(event.listing().category());
        if (extra == null) {
            return;
        }
        double adjusted = event.salesTax() + event.price() * (extra / 100.0);
        // The setter clamps to [0, price] for us, so a wild value cannot invert a sale.
        event.salesTax(adjusted);
    }
}

Two things worth noticing. The tax comes out of the seller's payout, not the buyer's price — the buyer still pays price() either way. And salesTax(double) clamps its input, so there is no way to make a sale pay the seller a negative amount.

3. Listing on a player's behalf

A "sell chest" that lists everything inside it when a player breaks it. This shows the two things people most often get wrong: the item comes from your hand, not the player's inventory, and it must only be removed once the listing has actually been created.

SellChestPlugin.java
package com.example.sellchest;

import com.zephrynis.zyauctions.api.ZyAuctions;
import com.zephrynis.zyauctions.api.ZyAuctionsApi;
import com.zephrynis.zyauctions.api.model.ListingRequest;
import com.zephrynis.zyauctions.api.result.ListingResult;
import java.time.Duration;
import net.kyori.adventure.text.Component;
import org.bukkit.block.Container;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

public final class SellChestPlugin extends JavaPlugin implements Listener {

    private static final double PRICE_PER_STACK = 100.0;

    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler(ignoreCancelled = true)
    public void onBreak(BlockBreakEvent event) {
        if (!(event.getBlock().getState() instanceof Container container)) {
            return;
        }
        if (!ZyAuctions.available()) {
            return;
        }
        ZyAuctionsApi auctions = ZyAuctions.get();
        Player player = event.getPlayer();

        // BlockBreakEvent is on the main thread, so the mutating calls below are legal.
        for (ItemStack stack : container.getInventory().getContents()) {
            if (stack == null || stack.isEmpty()) {
                continue;
            }
            ListingRequest request = ListingRequest
                    .buyNow(stack, PRICE_PER_STACK)
                    .withDuration(Duration.ofDays(1));

            ListingResult result = auctions.createListing(player, request);
            if (result.successful()) {
                // Only now. If this ran first and the listing failed, the item would be
                // gone from the chest and not on sale anywhere.
                container.getInventory().remove(stack);
            } else {
                player.sendMessage(Component.text(
                        "Couldn't list " + stack.getType() + ": " + result.status()));
                break;   // the cap is reached, or they cannot pay the fee — stop trying
            }
        }
    }
}
plugin.yml
name: SellChest
version: 1.0.0
main: com.example.sellchest.SellChestPlugin
api-version: '26.2'
depend: [ZyAuctions]

createListing takes a copy of the item. If you remove it from its container first and the listing then fails — the seller is at their cap, cannot afford the fee, or another plugin cancelled the event — you have destroyed it. Create first, remove second, always.

Handing a player something they cannot receive

Not an auction feature at all, but the collection bin is a good place to put anything you cannot deliver right now. It survives restarts and does not care whether the player is online.

// Instead of dropping a reward on the floor or losing it:
ZyAuctions.get().deposit(playerId, reward, CollectibleReason.WON);

They will be told they have something waiting the next time they log in, and they claim it with /ah collect like anything else.

On this page