10 releases, newest first.
ShopMenuOpenEvent (cancellable). Fired when a player right-clicks a shop and
SnDisplayShops is about to open one of its menus. It carries the player, the shop and a
ShopMenuType of OWNER or BUYER - decided from who clicked, not from what the shop is.api-events.enabled (default true). With it off nothing is dispatched and
every cancellable event answers "not cancelled", so no other plugin can stop a menu from opening
SnDisplayShopsAPI.getApiVersion(). The four existing
facade methods are untouched: this release only adds.Nothing to do. The new key is merged into your config.yml on boot with your values and comments
preserved, and a server with no plugin listening for the event behaves exactly as 2.8.0 did.
logs/<date>.log only ever recorded completed purchases and sales, so every other way stock moves left no trace. All seven are recorded now:
| Action | What it is |
|---|---|
BUY | a player bought from a shop (stock went down) |
SELL | a player sold to a shop (stock went up) |
DEPOSIT | the owner put stock in (shift-click or the + button) |
WITHDRAW | the owner took stock out |
PICKUP | the shop was picked up and its stock returned |
DESTROY | a shop was removed with stock still in it and the stock was destroyed - the SuperiorSkyblock teardowns are the only paths that do this |
EXTERNAL | another plugin took stock through the API (a sellwand) |
BUY and SELL name the player's side, so a shop set to BUY produces SELL lines - the same wording every buyer-facing message already uses.
/dshop logs - read the log in gameNew command under the new permission sndisplayshops.admin.logs (default op, added to the sndisplayshops.admin children map). Filters are key:value tokens in any order; press TAB for the list.
/dshop logs days:7 action:withdraw,pickup,destroy player:Steve
/dshop logs shop:aaaaaaaa near:100 sort:oldest
/dshop logs min-unit:1 max-unit:1 days:30 # find a mispriced shop
/dshop logs material:hand days:7 stats # summary for what you are holdingaction: player: owner: shop: world: item: material: currency: days: from: to: min-qty: max-qty: min-unit: max-unit: min-total: max-total: near: sort: page: per-page:, plus a bare stats for a summary (movements per action, money per currency, top items, top players).
Notes: player:/owner: take a name or a uuid, shop: takes a uuid prefix, material:hand and near: read the sender, an unknown filter or an unreadable value stops the query and says which token was wrong, and days: together with from:/to: is refused rather than resolved by a hidden precedence. The scan runs off the main thread and is cached per sender, so paging does not re-read the files.
The actor column is now player= / player-uuid=; before 2.8.0 it was buyer= / buyer-uuid=, which stopped being true once the column carried depositors too. material= is new. /dshop logs reads both spellings, so your existing files stay fully searchable - but a script of your own that greps for buyer= needs updating. Every new file opens with a # SnDisplayShops log format 2 line so the two generations are told apart at a glance.
trade-log:
max-rows-per-event: 32 # per-variant lines one PICKUP or DESTROY may write
query:
default-days: 1
max-days: 30
max-results: 1000
max-scanned-lines: 500000
page-size: 8
cache-seconds: 60All auto-merged on boot; your existing values and comments are kept. The action words are restylable under status.action-* in the language file - they style the listing only and cannot change the file format.
SnDisplayShopsAPI is untouched: same constructor, same four methods, same return meanings. A successful removeFromStorage is simply recorded as an EXTERNAL line now.
A "deposit all" button in the owner menu, for clients that cannot shift-click.
A shift-click was the only way to put stock into a shop. Bedrock players, and anyone proxied through Geyser, cannot send one: their cursor click on the display cell assigns what the shop trades instead of depositing it. So stocking a shop was not awkward for them, it was impossible.
The new + control moves every matching stack from your inventory into stock in one click.
Your guis/owner.yml gets the new deposit: section merged in automatically, but your
layout: is never rewritten - the updater adds keys you are missing and never touches a value
you already have. So on an existing server the button exists but is not placed.
To turn it on, put a + on any free cell of your layout and run /dshop reload. The shipped
default puts it at slot 47, immediately left of the withdraw button:
layout:
- "fffffffff"
- "ffffdffff"
- "ffcfmfpff"
- "ffffeffff"
- "sssssssss"
- "<f+wkfif>"Leaving the + out is a legal choice and nothing warns about it. Check the letter against your
own menu size: a 45-slot menu has no slot 47.
Two lore lines and messages.shop-deposit-hint were reworded to name both gestures. Those are
values you already have on disk, so they are not rewritten either - edit them if you want the
new wording.
{stock}: everything the shop holds, every variant summed.
Available in the display-name and lore of every state-bound element, and shown on the
withdraw button - the button that empties exactly that.1.500.000), the same formatting the buyer menu's {stock} uses.
It is not {amount}, which stays one variant's stock, shortened, in the stock grid.integrations.superiorskyblock.deny-placement-on-foreign-islands (ships on, read live) and new message messages.shop-place-foreign-island. Both merge into your existing files on boot.limits.max-shops-per-player in config.yml is now the fallback for a player holding no permission node, instead of the only number.sndisplayshops.limit.<n> (for example sndisplayshops.limit.25). The HIGHEST matching node a player holds wins, and sndisplayshops.limit.unlimited beats every number. A node REPLACES the config default rather than adding to it.%sndisplayshops_shops_max% now reflects the viewer's own resolved limit (permission override if they hold one), refreshed every 5 seconds so a rank granted mid-session shows up with no relog.limits.max-shops-permission-prefix (default sndisplayshops.limit, blank disables the permission path). New permission nodes: sndisplayshops.limit (parent) and sndisplayshops.limit.unlimited.Deleting a currency no longer strands its shops. A currency you remove from currencies: used
to leave every shop that traded in it unusable, with no way out but editing them one by one. Now
those shops are moved to the first currency in the file, at the next start and after every
/dshop reload, and each move is logged at INFO with the shop, its owner and the id it left.
[!WARNING] The price number is carried over untouched. Nothing in
config.ymlsays what a gem is worth in coins, so a shop priced at 100 gems becomes a shop priced at 100 coins. Look at what your players are selling before you delete a currency.
A currency that is still declared but was SKIPPED this boot - EdTools down, a broken command template - is deliberately left alone, because it comes back the moment you fix it and a migration cannot be undone. If nothing is left to move shops to, the sweep does nothing and says so.
No more ghost shops. A shop whose block went missing while the plugin was not watching - broken
during downtime, removed by a world edit or by another plugin calling setType - used to stay alive
in every index, still counting against its owner's limit, hologram spinning over air, with recovery
a matter of putting a block back at coordinates nobody knew. The block is now re-placed
automatically: at startup for chunks that are already loaded, and otherwise the moment that chunk
loads.
Only air, water or lava counts as missing, so a standing block of any other material is left exactly
where it is (a mismatch means you reconfigured shop-item.material, not that a block vanished), and
nothing is ever placed into a player. Turn the whole thing off with shop.restore-missing-blocks.
A trade log. Every completed purchase and sale, one line each, appended to
plugins/SnDisplayShops/logs/<date>.log, one file per day like the server's own logs.
[2026-08-15 14:03:11] BUY buyer=Steve buyer-uuid=0a1b… owner=Alex owner-uuid=7f3c… shop=2d9e… loc=world:120:64:-338 item="Diamond Sword" qty=3 unit=1000 total=3000 currency=okicoinsBUY and SELL are the buyer's side of the trade. Refused and aborted trades write nothing, and
unit and total are what actually moved rather than what the shop advertises. Lines are written
off the main thread, so a trade never waits on the disk. trade-log.enabled turns it off and
trade-log.flush-seconds paces the writes. Old files are never pruned - that part is yours.
Two things it does not do: stock removed through the developer API (a sell wand, say) is not a trade
and is not logged, and an owner the server has not named yet can show as owner=- on the first line
after a restart, because resolving an offline name reads from disk and that is not done during a
click. The owner-uuid beside it is always right.
Both are added to your existing config.yml automatically, with your edits and comments kept.
shop:
restore-missing-blocks: true
trade-log:
enabled: true
flush-seconds: 10 # clamped to 1-300Drop the jar in and restart. No commands, permissions or menu layouts changed, and there is no migration step to run: the currency sweep and the block restore both happen on their own at the first boot. Still requires SnLib 1.28.0 or newer (API level 19), same as 2.2.0.
Read the currency warning above before you upgrade if you have been planning to delete a currency.
The owner menu is a SnLib menu now. SnLib 1.28.0 added menus that can receive an item
(input cells + an open player inventory + item offers), and this release moves the owner menu
onto it. The layout lives in guis/owner.yml, in the same dialect as the buyer menu; the old
owner-menu.yml at the plugin folder root is no longer read, and the plugin logs a pointed
warning if it finds one. "Your item is read, never consumed" is now enforced by SnLib itself.
What changed in how the menu behaves, each on purpose:
guis/owner.yml (SnLib menu dialect). The old
owner-menu.yml is ignored; delete it once restyled. To remove the withdraw button, delete
the w from the layout - there is no toggle key any more.hologram: in config.yml:
bounce-amplitude (0.1 blocks, 0 turns it off) and bounce-period-ticks (80). The bob is
carried by the same push the spin already sends, so it costs nothing extra no matter how many
shops are loaded.1k, 1.5m, 2b, up to Qi, and
1.500.000 typed back exactly as the menu prints it. Numbers written out in full stay exact at
any size. A line with more than one separator, or one past the largest price there is, is
refused instead of guessed at.config.yml ships two example currencies instead of six - one command-backed, one
EdTools-backed, one of each shape. currencies: is marked extensible, so nothing an operator
already declared is added, removed or touched by the update.Player-owned display shops: place a tagged item, set a price, and it trades on its own.
ItemDisplay spinning above a text line, with
DecentHolograms driving the text where it is installed and a native TextDisplay where it is not.%sndisplayshops_shops_count%, %sndisplayshops_shops_max%,
%sndisplayshops_shops_total%.Everything the plugin says and every number it uses lives in config.yml, lang/messages_en.yml,
guis/buyer.yml and owner-menu.yml. All four are managed: new keys are merged in on upgrade and
your edits are kept.
Two worth knowing before you tune them:
database.pool-size ships at 1 on purpose. Two writers can land a shop's stock updates out of
order, and the losing write is the one that survives a restart. The plugin logs a SEVERE if it
finds MySQL configured above 1.limits.max-pickup-stacks has no "off" value. Picking up a shop hands its whole stock back in
one tick, so an unbounded value is a hang rather than a preference.