Skip to content

API Reference

Other mods can subscribe to ShopEventBus.getInstance() to react to shop activity.

EventFires when
ShopCreateEventA new shop is persisted (player or admin)
ShopDeleteEventA shop is removed (carries reason)
ShopBuyEventA buy transaction completes
ShopSellEventA sell transaction completes
RentalStartedEventA rental slot is rented (fixed) or an auction won + payment cleared
RentalExpiredEventA rental ended (window elapsed, force-expire, release-early) - carries Reason enum
RentalBidPlacedEventA bid lands on an auction
RentalAuctionWonEventAuction finalised with a paying winner
import com.kyuubisoft.shops.event.ShopEventBus;
import com.kyuubisoft.shops.event.ShopCreateEvent;
ShopEventBus.getInstance().subscribe(ShopCreateEvent.class, evt -> {
UUID shopId = evt.getShopId();
UUID owner = evt.getOwnerUuid();
String name = evt.getName();
// your handler
});

The bus is singleton, plugin-survival, and fires on the world thread. Keep handlers fast.


The plugin exposes several services via ShopPlugin.getInstance().

ShopPlugin plugin = ShopPlugin.getInstance();
ShopManager mgr = plugin.getShopManager();
ShopData shop = mgr.getShop(shopId);
List<ShopData> ownedByPlayer = mgr.getShopsByOwner(playerUuid);
List<ShopData> all = mgr.getAllShops();
ShopData rentalShell = mgr.getShopByRentalSlotId(slotId);

The transactional layer. Returns typed result objects so your code can branch on specific failures.

ShopService svc = plugin.getShopService();
// Create a player shop
CreateShopResult r = svc.createPlayerShop(
playerRef, player, name, category, description, worldName, x, y, z);
// Buy with reasoned failure
PurchaseResult pr = svc.purchaseItemWithReason(
buyerRef, shopId, slotIndex, quantity);
if (pr.isSuccess()) { /* ... */ }
else if (pr.getErrorKey().equals("shop.buy.fail.no_stock")) { /* ... */ }
// Sell
boolean ok = svc.sellItem(sellerRef, shopId, itemId, quantity);
// Buy/extend a directory listing
ListingResult lr = svc.purchaseListing(player, playerRef, shopId, days);
RentalService rs = plugin.getRentalService();
RentalSlotData slot = rs.getSlot(slotId);
List<RentalSlotData> all = rs.getAllSlots();
List<RentalSlotData> renterRentals = rs.getRentalsForPlayer(playerUuid);
// Programmatic create
RentalSlotData created = rs.createFixedSlot(
"MyRental", worldName, x, y, z, rotY, pricePerDay, maxDays, stationId);
// Programmatic actions
RentResult rr = rs.rentSlot(playerRef, player, slotId, days);
BidResult br = rs.placeBid(bidderRef, player, slotId, amount);
boolean expired = rs.releaseEarly(playerRef, slotId);
boolean deleted = rs.deleteSlot(slotId);
MailboxService mb = plugin.getMailboxService();
mb.createItemMail(ownerUuid, shopId, shopName, buyerName, itemId, qty, bsonMeta);
mb.createMoneyMail(ownerUuid, shopId, shopName, amount, "buyback");
int unclaimed = mb.countUnclaimedForPlayer(playerUuid);

Reflection-based wrapper over Core’s ExternalEconomyBridge (preferred) or VaultUnlocked direct.

ShopEconomyBridge eco = plugin.getEconomyBridge();
if (eco.isAvailable()) {
boolean has = eco.has(playerUuid, 100);
boolean withdrew = eco.withdraw(playerUuid, 50);
boolean deposited = eco.deposit(playerUuid, 50);
String formatted = eco.format(123.45); // "123.45 Gold"
String currency = eco.getCurrencyName(); // "Gold"
}

PermissionLimits is the wildcard-aware permission resolver.

import com.kyuubisoft.shops.util.PermissionLimits;
int maxShops = PermissionLimits.resolveMaxShops(player, configDefault);
int maxItems = PermissionLimits.resolveMaxItems(player, configDefault);
int maxRentals = PermissionLimits.resolveMaxRentals(player, configDefault);
boolean hasWildcard = PermissionLimits.hasWildcard(player);
boolean hasPermanent = PermissionLimits.hasExplicit(player, "ks.shop.list.permanent");

hasExplicit returns false when the wildcard sentinel matches, so it only fires for genuine explicit grants.


Tables created with IF NOT EXISTS on init. Both SQLite + MySQL dialects are shipped.

TablePurpose
shop_shopsShops (player + admin). Columns include id, name, type, owner_uuid, position, listed_until, featured_until, open, packed, NPC fields, category, tags, rating + revenue counters, rental_slot_id + rental_expires_at mirrors
shop_itemsPer-shop item config: itemId, prices, stock, slot index, category, daily limits, BSON metadata
shop_transactionsBuy/sell history per shop
shop_ratingsPer-rater ratings (composite key)
shop_notificationsStored notifications per player UUID
shop_mailboxPending item + money mails
shop_rental_slotsRental slot config + runtime: position, mode, max_days, price_per_day or auction params, rented_by, rented_until, auction_ends_at, current high bidder
shop_rental_bidsBid history (slot_id, bidder, amount, timestamp). Indexed by (slot_id, timestamp DESC)
shop_player_flagsPer-player flags. Currently: free_listing_used (BOOLEAN). Persists across shop delete
ConcernAuthoritativeMirror
Rental expiryshop_rental_slots.rented_untilshop_shops.rental_expires_at
Listing windowshop_shops.listed_until(none)

For DB hand-edits while testing: stop server first, edit only the authoritative column, start server.


ShopConfig cfg = plugin.getShopConfig();
ShopConfig.ConfigData data = cfg.getData();
int maxShops = data.playerShops.maxShopsPerPlayer;
boolean rentalEnabled = data.rentalStations.enabled;
int delaySec = data.npc.spawnDelaySecondsOnJoin;
cfg.reload(); // reload from disk
cfg.save(); // persist current in-memory state

ShopI18n resolves keys per-player via the player’s preferred language.

ShopI18n i18n = plugin.getI18n();
String msg = i18n.get(playerRef, "shop.create.success", shopName);
String fallback = i18n.get("shop.error.no_permission"); // server default lang

i18n files live at <server>/mods/kyuubisoft_shops/localization/<lang>.json. The plugin auto-migrates per-locale files when _localization_version increments - your custom strings survive a version bump.


Subclasses of AbstractPlayerCommand or CommandBase can be registered via the existing command collections. Easiest is to extend an existing command pattern - see ShopCommand and ShopAdminCommand for examples. Use withRequiredArg / withOptionalArg and read via ctx.get(arg) / ctx.provided(arg).