Astral Realms Documentation Help

Overview

AstralPlayerShops turns any un-blacklisted container into a player-run chest shop. A player sneak-right- clicks a container while holding the item they want to trade, sets a price and a direction (buy or sell) in a creation dialog, and the plugin auto-places a sign and a floating item hologram over the chest. Shops can only be created on land claimed by an AstralTown town — the plugin refuses creation outside a town regardless of whether the shop is town-shared or purely personal.

What it does

  • Two shop types (ShopType): BUY — the owner stocks the chest and sells items to players; SELL — the owner buys items from players, paying them and depositing the items into the chest.

  • Zero-command placement. Sneak-right-click a container holding the traded item; a creation dialog collects the price, type, and (if the block sits in a town) whether the shop is town-shared and whether its proceeds route through the town bank.

  • Visual presentation. An OAK_WALL_SIGN is auto-placed on the container's facing side with configurable lines (config.yml: sign), refreshed on trade, inventory close, and a periodic chunk-tick sweep. A floating item is rendered above the chest as a packet-only PacketItem — no real entity is spawned.

  • Persistence & caching. Shops and their transactions live in MariaDB (shops, transactions); each shop row is tagged with the creating server's server_id. Shops are cached in memory (by UUID and by chunk key) once loaded, and transaction/recap/stats lookups are cached with Caffeine.

  • Town integration. A shop can be marked town-shared (any town member with the USE_PLAYER_SHOPS permission may trade at it, not just the owner) and/or routed through the town's economy account (requires BANK_WITHDRAW); creating in a town at all requires BLOCK_PLACE.

  • Away recaps. Trades gate an in-game chat ping to the owner on the owner's own recap preference (PSPlayerData.recapEnabled, synced via AstralSync). Absent owners get an automatic recap prompt on login; anyone can pull one on demand with /ps recap.

  • Custom-item aware. Stock counting, removal, and insertion (ContainerUtils) understand AstralItems custom items, AstralPets items, and AstralScrolls scrolls, so a shop for a custom item only matches that exact item, not the base material.

Requirements

Dependency

Required

Notes

Paper 1.21+

Yes

api-version: '1.21'

AstralCore

Yes

Dialogs, menus, actions, placeholders, configuration, database, mailbox, economy service.

AstralTown

Yes

Town lookup/permissions gate shop creation, town-shared trading, and town-economy routing.

AstralSync

Yes

Persists PSPlayerData (recap preference, last logout) for the recap system.

AstralItems

Yes

Custom-item identity used when matching stock in ContainerUtils.

AstralPets

Yes

Pet items are matched by their custom type, not raw material.

AstralScrolls

Yes

Scrolls are matched by their custom type, not raw material.

MariaDB / PostgreSQL

Yes

Two tables: shops, transactions (schema.sql).

Architecture at a glance

Sneak + right-click container (holding item, not blacklisted/ignored, inside a town, BLOCK_PLACE) │ ▼ ShopListener.onCreate ──► PlayerShopPreCreateEvent (cancellable) ──► "playershops-creation" dialog │ ▼ [create-playershop] ──► CreateShopAction │ town-economy → requires BANK_WITHDRAW │ ▼ ShopService.create │ limit check ── existsAt check ── PlayerShopCreateEvent (cancellable) │ ▼ ShopRepository.save (MariaDB + UUID/chunk cache) │ ▼ OAK_WALL_SIGN placed + PacketItem hologram spawned Non-owner right-clicks shop / opens its sign │ ▼ InteractionListener ──► ShopService.buy / sell ──► "playershops-buy" / "playershops-sell" menu │ [buy-playershop-item] / [sell-playershop-item] │ ▼ EconomyService.transfer(player ⇄ shop.economyAccount()) │ main-thread re-check stock/space ── ContainerUtils move items │ sign refreshed ── buyer notified ── owner notified (if recapEnabled) │ ▼ TransactionService.recordTransaction ──► `transactions` row

Storage at a glance

shops ├── id UUID PK ├── owner_id UUID ├── owner_name VARCHAR ├── material VARCHAR ← base material, exposed via `%shop_material%` only ├── item BLOB ← serialized ItemStack (full NBT) ├── price INT ├── type INT ← ShopType ordinal (0 = BUY, 1 = SELL) ├── x / y / z INT ├── world VARCHAR ├── server_id UUID ← which network server owns this shop ├── town_shared BOOLEAN ├── economy_account UUID NULL ← non-null = town bank account, null = owner's own balance └── created_at TIMESTAMP transactions ├── id UUID PK ├── shop_id UUID ← FK shops(id) ON DELETE CASCADE ├── buyer_id UUID ├── buyer_name VARCHAR ├── quantity INT ├── total_price DECIMAL(10,2) └── created_at TIMESTAMP

ShopRepository loads every shop for the current server_id at startup into an in-memory map keyed by UUID plus a chunk-key index, refreshed as chunks load/unload; TransactionRepository and TransactionService layer Caffeine caches (5-minute access expiry) over per-shop transaction history, per-shop daily/weekly ShopStats, and per-player recap aggregates. A week-old transaction sweep runs every 12 hours.

Where to go next

  • Configurationconfig.yml keys: recap interval, shop limits, sign lines, blacklisted containers, ignored items, chunk-update tuning.

  • Commands/ps subcommands.

  • Shops — creation flow, trading, editing, deletion, sign/hologram mechanics in depth.

  • Activity Recaps — away recap and /ps recap in depth.

  • Actions — the seven registered AstralCore actions.

  • Placeholders%shop_*%, %entry_*%, %stats_*%, %transaction_*%, %playershops_*%.

  • Developer APIPlayerShopPreCreateEvent, PlayerShopCreateEvent, PlayerShopRemoveEvent, and the service getters exposed off AstralPlayerShops.

Last modified: 25 July 2026