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.
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());
}
}
}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.
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.
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
}
}
}
}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.