Developer API
AstralPlayerShops exposes its state through two services hung off the main plugin instance (AstralPlayerShops.get()), three custom Bukkit events fired around shop creation and removal, and a SyncAPI snapshot adapter for the owner's cross-server recap preference. There is no static *API façade — external plugins reach everything through the plugin instance.
Maven coordinates
Repository: https://maven.astralrealms.fr/repository/maven-public/.
Plugin handle
Custom Bukkit events
All three events live in com.astralrealms.playershops.event. PlayerShopCreateEvent and PlayerShopRemoveEvent share a common base, PlayerShopEvent, which extends AstralCore's AbstractEvent and carries the shop (PlayerShop) the event concerns.
PlayerShopPreCreateEvent
Cancellable, not a PlayerShopEvent (there is no PlayerShop yet at this point — the shop hasn't been built). Fired by ShopListener#onCreate the moment a sneak-right-click on an eligible container passes every placement check (not blacklisted, not already a shop, no block in front, town BLOCK_PLACE permission), but before the playershops-creation dialog opens.
Getter | Type | Value |
|---|---|---|
|
| The player attempting to create the shop. |
|
| The clicked container block. |
|
|
|
|
| The item held in the player's main hand at the time of the click. |
Cancelling this event silently aborts creation — the creation dialog never opens and no message is sent.
PlayerShopCreateEvent
Cancellable PlayerShopEvent. Fired synchronously on the main thread by ShopService#create, after the per-player shop limit and duplicate-location checks pass but before the shop is persisted to the database or its sign/hologram are created. The PlayerShop carried by getShop() already has its final uniqueId(), price, type, and location — it just hasn't been saved yet.
Getter | Type | Value |
|---|---|---|
|
| The creating player. |
|
| The container block. |
|
|
|
|
| The shop about to be saved. |
Cancelling sends PSMessages.CREATION_CANCELLED to the player and aborts the save — no row is written, no sign or hologram is created.
PlayerShopRemoveEvent
Not cancellable. A PlayerShopEvent constructed with isAsync = true, fired after the shop row has already been deleted from the database — it is a notification, not a gate. Called from both overloads of ShopService#delete:
Getter | Type | Value |
|---|---|---|
|
| The shop that was just removed. |
|
| The player who broke the container, when known. |
|
| The broken container block. |
Because it fires async and after the fact, use it for logging/analytics rather than trying to veto the removal.
ShopService
plugin.shops(). Owns the in-memory shop cache (by UUID and by chunk), the buy/sell trade flow, and the sign/hologram lifecycle.
Method | Returns | Use |
|---|---|---|
|
| Validates the per-player limit and duplicate-location, fires |
|
| Re-saves an already-existing shop (e.g. after editing price/type). |
|
| Opens the |
|
| Executes a purchase: checks stock, transfers funds via |
|
| Opens the |
|
| Executes a sale: checks chest space and player item count, transfers funds via |
|
| Player-initiated removal (block break path): deletes the row, despawns the hologram, fires |
|
| Admin/system removal: deletes the row, despawns the hologram, optionally mails the chest's contents to the owner (or just breaks the block naturally), destroys the sign and chest, then fires |
|
| Resolves the container at a location (also matches when the location is the sign, via the sign's facing). |
|
| Same resolution as |
|
| Cached lookup by shop UUID. |
|
| Every cached shop in a chunk. |
|
| Checks the four horizontal neighbors (N/E/S/W) of a location for a shop; used to reject adjacent-shop placement. |
|
| Delegates to |
|
| Re-resolves the shop's block on the main thread and re-renders its sign. |
|
| Re-renders the sign attached to the given chest block from the current stock/space and |
buy/sell and their -amount overloads are the same methods the buy-playershop-item and sell-playershop-item actions call — see Shops for the full trade flow, including the atomic stock/space re-check and refund-on-failure behavior.
TransactionService
plugin.transactions(). Owns the transactions table, a 5-minute-access-expiry Caffeine cache of per-player recap aggregates, and a background sweep that deletes transactions older than a week (runs every 12 hours).
Method | Returns | Use |
|---|---|---|
|
| Inserts a new |
|
| Bulk-deletes a shop's transaction history — called by |
|
| Full transaction history for one shop. |
|
| Daily/weekly earnings, transaction count, and item count for one shop. |
|
| Every trade across the player's shops since |
Model classes
PlayerShop
@Entity("shops"), implements Unique and ComplexPlaceholder (namespace shop). Backed by an AtomicReference<ItemStack> lazily deserialized from the stored item blob on first itemStack() call (double-checked locking).
Field / accessor | Type | Notes |
|---|---|---|
|
|
|
|
| Snapshotted at creation time. |
|
| Per-item price; mutable via the generated setter. |
|
| Base |
|
| Lazily deserialized from the stored |
|
|
|
|
| Container location. |
|
| Convenience: rebuilds a |
|
| The network server that created the shop. |
|
| Mutable; whether other town members with |
|
|
|
|
|
|
|
|
|
get(PlaceholderContext) sub-keys (%shop_<key>%): id, owner, ownerName, price, material, type, typeFormatted, item, location, townShared, townEconomy. See Placeholders.
ShopTransaction
@Entity("transactions"), implements Unique, ComplexPlaceholder (namespace transaction), and Comparable<ShopTransaction> (sorted newest-first by createdAt).
Field | Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
get(PlaceholderContext) sub-keys (%transaction_<key>%): id, buyer, amount, price, date.
ShopRecap
record ShopRecap(UUID shopId, ItemStack itemStack, ShopType type, int itemsCount, double earnings), implements ComplexPlaceholder (namespace entry). One instance per distinct item traded across a player's shops within the recap window — shopId is null on the aggregated instances TransactionService builds (the aggregate spans potentially many shops for the same item).
get(PlaceholderContext) sub-keys (%entry_<key>%): id, item, type, typeFormatted, count, earnings.
ShopStats
record ShopStats(double dailyEarnings, double weeklyEarnings, int dailyTransactions, int weeklyTransactions, int dailyAmount, int weeklyAmount), implements ComplexPlaceholder (namespace stats). Sub-keys mirror the record components 1:1 (%stats_dailyEarnings%, %stats_weeklyAmount%, etc.).
ShopType
BUY = the owner stocks the chest and sells to players; SELL = the owner buys from players. next() is what the creation/edit dialogs use to toggle the type. The ordinal (0 = BUY, 1 = SELL) is what's stored in the shops.type column.
Cross-server player data
PSSnapshotAdapter (key astralplayershops:data) is registered with SyncAPI in AstralPlayerShops#onEnable, making PSPlayerData a cross-server-synced snapshot for every online player. It implements UnloadableSnapshotAdapter<PSPlayerData>: create(Player) seeds a new player with recapEnabled = true, lastLogout = -1; unload(...) stamps lastLogout with the current time when the player fully disconnects (used to compute the away-recap window on their next join).
recapEnabled gates the real-time buy/sell chat notification sent to a shop owner (see ShopService#buy/#sell); lastLogout is what /ps internalRecap reads to compute the away-recap window. See Activity Recaps and AstralSync's Adapter Interfaces for the base contract.
Registered actions
AstralPlayerShops registers seven executable actions into AstralCore's action registry — six via registerAction (menu/dialog-scoped) and one via registerActionGlobally (toggle-playershops-recap). Any plugin's menus or dialogs can invoke them by name. See Actions for the full list with their argument shapes.
Database schema
Two tables, created from schema.sql on connect:
transactions.shop_id cascades on delete — removing a shops row (via either ShopService#delete overload) removes its transaction history at the database level, in addition to the explicit TransactionService#deleteByShopId call made from the same code path.