Everything in plugins/OreoEssentials/shop/ — explained for beginners, step by step
A full GUI shop system — players buy and sell items, admins control prices, economy uses Vault or custom currencies (tokens), with dynamic pricing that reacts to player demand.
The shop module gives your server a clean, icon-based store. Players open it with /shop, browse categories, click items to buy or sell, and use an amount-selection screen to pick exactly how many they want. As an admin, you can set up as many shops as you like, wire them up to different currencies, and let prices drift with demand automatically.
plugins/OreoEssentials/shop/. The main settings are in config.yml, text messages in messages.yml, and each shop is a separate YAML file inside the shops/ sub-folder.
Ores, weapons, armor, tools, food, potions, enchanted books, building blocks, farming, nether, redstone, mob drops, end items, token shop, and a daily rotating shop — all pre-configured.
Each shop can use Vault (server money) or a custom currency (e.g. tokens). The token shop is a separate store where everything costs tokens instead of coins.
When players buy a lot of an item, its price goes up automatically. When nobody buys it for a while, the price drifts back down. This prevents market flooding.
Admins can give individual players discounts or markups — globally, per-shop, or per-item. Great for VIP ranks or punishments.
A special shop shows only 4 random items from a pool each day. The selection resets at midnight (configurable). Creates excitement and trading opportunities.
Built-in cooldowns, rate limits, transaction locking, and an auto-kick system for suspicious activity prevent item or money duplication exploits.
Get the shop working in under 5 minutes:
Install Vault and an economy plugin (e.g. EssentialsX, CMI, or any Vault-compatible economy). The shop needs Vault to handle money unless you're using only custom currencies.
Restart your server. OreoEssentials will automatically create the plugins/OreoEssentials/shop/ folder with all default files — 15 shop YAMLs, config.yml, and messages.yml.
Give players the oshopgui.shop permission (or put it in your default group). Without this they can't open the shop at all.
Give players sell permissions: oshopgui.sell.hand, oshopgui.sell.hand.all, oshopgui.sell.all — or all three at once.
Test in-game: type /shop to open the main menu. Click any category icon to browse items. Left-click an item to buy, right-click to sell.
Customize prices by editing the files in plugins/OreoEssentials/shop/shops/, then run /shop reload in-game to apply changes instantly.
Controls whether the shop is on, which language to use, anti-dupe safety, and how the main menu looks.
enabled: true # Set to false to disable the whole shop
auto-deploy-missing-shops: true # Automatically copy any missing default shop files on startup
language: en # Message language: "en" or "fr"
economy:
currency-symbol: "$" # Symbol shown when Vault isn't installed. With Vault, it uses Vault's format.
These settings stop players from exploiting the shop to duplicate items or money.
anti-dupe:
enabled: true # Master switch for anti-dupe
cooldown-ms: 500 # Minimum milliseconds between transactions (500 = 0.5 seconds)
verify-inventory: true # Double-check player has items before selling
log-suspicious: true # Log suspicious activity to console/file
max-transactions-per-second: 5 # Rate limit: more than 5 transactions/sec = suspicious
lock-during-transaction: true # Prevent a player from opening two transactions at once
max-transactions-per-second transactions in a rolling 1-second window, a "flag" is recorded. At 10 flags the player is automatically kicked. Flags reset on disconnect.
main-menu:
title: "&6&lShop" # Title shown at the top of the main menu GUI
rows: 4 # How many rows the main menu has (1–6)
fill-empty-slots: true # Fill unused slots with a decorative glass pane
fill-material: BLACK_STAINED_GLASS_PANE # Material for the filler
close-slot: 49 # Slot number for the close/back button (0 = top-left)
Each entry in main-menu-items is one icon in the main menu. Players click these icons to open a specific shop. The shop value must match the shop-id in the corresponding shop YAML file.
main-menu-items:
ores:
slot: 0 # Position in the GUI (counting left-to-right, top-to-bottom from 0)
material: DIAMOND # The icon material
name: "&b&lOres & Minerals" # Display name (supports & color codes)
lore:
- "&7Buy and sell ores" # Tooltip lines
shop: ores_minerals # Must match shop-id in ores_minerals.yml
amount-selection:
enabled: true # When true, clicking an item opens an amount-picker screen instead of buying x1 instantly
gui:
previous-page-slot: 45 # Slot for the "previous page" arrow in shop GUIs
next-page-slot: 53 # Slot for the "next page" arrow
back-button-slot: 49 # Slot for the "back to main menu" button
logging:
enabled: true
log-file: "shop/transactions.log" # Relative to plugins/OreoEssentials/
log-format: "{date} | {player} | {action} | x{amount} {item} | {price} ({economy})"
dynamic-pricing:
enabled: false # Set to true to enable demand-based price changes
demand-scale: 500 # How many purchases = maximum pressure (500 = prices max out at 500 buys)
max-buy-multiplier: 3.0 # Maximum buy price = base price × 3.0
min-sell-multiplier: 0.2 # Minimum sell price = base price × 0.2 (80% drop at max demand)
decay-interval-minutes: 60 # How often prices decay back towards normal
decay-rate: 0.05 # Fraction of pressure removed each decay tick (5%)
save-interval-minutes: 5 # How often buy-counts are saved to disk
show-trend-in-lore: true # Show ↑ / ↓ trend indicator in item lore
Every message players see is defined here. Supports both &color codes and MiniMessage <gradient> tags.
Messages are organized by language under en: and fr: keys. The language used is controlled by the language: setting in config.yml. If a message key is missing in your chosen language, it automatically falls back to en.
| Placeholder | Replaced with | Used in |
|---|---|---|
{prefix} | The shop's colored prefix (e.g. [Shop]) | Most messages |
{amount} | Number of items bought/sold | buy-success, sell-success, sell-all-success |
{item} | Item display name | buy-success, sell-success |
{price} | Formatted price (e.g. $1,250.00) | buy-success, sell-success, errors |
{shop} | Shop ID | shop-not-found, modifier messages |
{player} | Player name | modifier messages |
{material} | Bukkit material name (e.g. DIAMOND) | check-item messages |
{buy} | Buy price | check-item-buy |
{sell} | Sell price | check-item-sell |
{type} | Modifier type (global/shop/item) | modifier-added, modifier-reset |
{value} | Modifier value | modifier-added |
{page} / {total} | Current / total page numbers | GUI navigation buttons |
| Key | When it shows |
|---|---|
buy-success | Player successfully buys an item |
buy-not-enough-money | Player can't afford the item |
buy-inventory-full | No room in player's inventory |
buy-invalid-amount | Non-numeric amount typed in /sell hand <qty> |
sell-success | Player successfully sells an item |
sell-not-enough | Player doesn't have enough of the item |
sell-no-price | Item has no sell price (sell-price: -1) |
sell-hand-empty | Player runs /sell hand with nothing in hand |
sell-hand-not-sellable | Held item isn't in any shop |
sell-all-success | Successful /sell all or /sell handall |
sell-all-nothing | /sell all finds nothing sellable in inventory |
no-permission | Player lacks permission |
reload-success | Admin runs /shop reload successfully |
shop-not-found | Specified shop ID doesn't exist |
player-not-found | Target player is offline |
en:
buy-success: "<gradient:green:aqua>✔ Bought</gradient> &f{amount}x {item} &7for &a{price}&7."
sell-success: "&a✔ Sold &f{amount}x {item} &7for &2{price}&7."
buy-not-enough-money: "&cNot enough money! This costs &e{price}&c."
Each YAML file in shops/ defines one (or more) shops. 15 shops are included by default.
| File | Shop ID | Description | Economy |
|---|---|---|---|
ores_minerals.yml | ores_minerals | 44 ores and minerals (coal → netherrack) | Vault |
weapons_armor.yml | weapons_armor | Full set of swords, bows, armor, horse armor | Vault |
tools.yml | tools | Pickaxes, axes, shovels, hoes, fishing rod, shears | Vault |
food.yml | food | All food items | Vault |
farming_nature.yml | farming_nature | Seeds, crops, saplings, flowers | Vault |
building_blocks.yml | building_blocks | Stone, wood, sand, gravel, concrete and more | Vault |
mob_drops.yml | mob_drops | Drops from hostile and passive mobs | Vault |
nether.yml | nether | Nether-specific blocks and items | Vault |
end_misc.yml | end_misc | End items + miscellaneous | Vault |
redstone.yml | redstone | Redstone components, pistons, hoppers, etc. | Vault |
brewing.yml | brewing | Brewing ingredients | Vault |
potions.yml | potions | All drinkable and splash potions by type | Vault |
enchanted_books.yml | enchanted_books | 39 enchanted books (Protection IV → Mending) | Vault |
token_shop.yml | token_shop | Premium items bought with tokens (custom currency) | Custom: token |
daily_rotating_shop.yml | daily_shop | 4 random items from a pool, resets daily | Vault |
Every shop file starts with a header section, then an items: list:
shop-id: my_shop # Unique ID — used in /shop my_shop and config menu
title: "&6&lMy Shop" # GUI title (supports & color codes)
rows: 6 # GUI rows (1–6). 6 rows = 54 slots total
pages: 1 # Number of pages. With rows:6, each page has 45 item slots
currency: vault # "vault" (default) OR a custom currency ID like "token"
items:
# ... item entries go here (see Item Format section)
rows: 6, the navigation bar is row 6 (slots 45–53). That leaves slots 0–44 for items — 45 slots per page.
You can override the navigation bar buttons in any specific shop by adding a gui: section. If you leave it out, the global defaults from config.yml are used.
gui:
filler:
material: GRAY_STAINED_GLASS_PANE
name: " "
back-button:
enabled: true
slot: 49
material: ARROW
name: "&6&l↠Back to Menu"
lore: ["&7Return to main shop menu"]
prev-page:
slot: 45
material: ARROW
name: "&e↠Previous"
lore: ["&7Go to page {page}"]
next-page:
slot: 53
material: ARROW
name: "&eNext →"
lore: ["&7Go to page {page}"]
page-indicator:
slot: 48
material: PAPER
name: "&fPage {page} &7of &f{total}"
Every item in a shop file follows the same YAML structure. Here's every possible field explained.
items:
diamond_sword_p1s4: # Unique ID for this item — can be anything, no spaces
material: DIAMOND_SWORD # Bukkit material name (must be ALL_CAPS)
name: "&bDiamond Sword" # Optional custom display name. Leave out to use default.
lore: # Optional tooltip lines
- "&7A sharp blade"
- "&7Made of diamond"
slot: 4 # Slot position (0 = top-left). Bottom row = navigation bar!
page: 1 # Which page this item appears on
amount: 1 # How many items per "unit" (e.g. 64 = sell by the stack)
buy-price: 120000 # Cost for players to buy. Use -1 to disable buying.
sell-price: 19200.0 # Money players receive when selling. Use -1 to disable selling.
permission: "vip.shop" # Optional: only players with this permission see/use this item
custom-model-data: 0 # Optional: custom model data for resource packs
The ID format used in default shops is materialname_pPAGEsSLOT — for example diamond_sword_p1s4 means Diamond Sword, page 1, slot 4. This is just a convention to keep IDs unique and readable; you can use any ID you like as long as there are no duplicates within the same shop.
Works for enchanted books (ENCHANTED_BOOK) and any other item. Use the Bukkit enchantment key (lowercase with underscores).
ebook_fortune_1s32:
material: ENCHANTED_BOOK
name: "&6Fortune III"
slot: 32
page: 1
amount: 1
buy-price: 500000
sell-price: 50000
enchantments:
fortune: 3 # enchantment_name: level
unbreaking: 3 # Multiple enchantments are supported
material: ENCHANTED_BOOK, enchantments are stored as stored enchants inside the book (as they should be). This lets players use an anvil to apply them to equipment.
strength_potion_p1s0:
material: POTION # or SPLASH_POTION, LINGERING_POTION
name: "&cStrength II"
slot: 0
page: 1
amount: 1
buy-price: 3000
sell-price: 500
potion-type: STRENGTH # Bukkit PotionType enum name (uppercase)
If you have ItemsAdder or Nexo installed, you can sell their custom items directly from the shop.
custom_gem_p1s0:
material: DIAMOND # Fallback material if the hook is unavailable
name: "&dMystic Gem"
slot: 0
page: 1
amount: 1
buy-price: 50000
sell-price: 8000
itemsadder-id: "mypack:mystic_gem" # ItemsAdder namespaced ID
# OR for Nexo:
nexo-id: "mystic_gem" # Nexo item ID
# Buy-only (players cannot sell this item back)
buy-price: 1000
sell-price: -1 # -1 disables selling
# Sell-only (players cannot buy this from the shop)
buy-price: -1 # -1 disables buying
sell-price: 500
# "amount: 64" means the price shown is for 64 items at once
coal_p1s0:
material: COAL
slot: 0
page: 1
amount: 64 # Price listed = cost/value of a full stack (64 items)
buy-price: 640 # = $10 per coal (640 ÷ 64)
sell-price: 320 # = $5 per coal
Prices automatically rise when an item is bought often, and fall back when demand drops. This makes the economy feel alive.
Enable it in config.yml with dynamic-pricing.enabled: true. Once enabled, every time a player buys an item, an internal buy-count increments. This count drives a "pressure" value between 0.0 and 1.0.
price × (1 + pressure × (maxMult − 1))
With default settings: at max pressure a $1,000 item costs $3,000.
price × max(1 − pressure × (1 − minMult), minMult)
At max demand you only get 20% of the base sell price.
Every decay-interval-minutes (default 60min), each item's pressure is reduced by 5% of its current value. Low-demand items slowly return to base price.
# pressure = min(buy_count / demand_scale, 1.0)
# Example: 250 purchases of diamond, demand_scale=500
# pressure = 250/500 = 0.5 (50% of max pressure)
# Buy multiplier = 1 + 0.5 × (3.0 - 1) = 2.0 → diamond costs 2× base price
# Sell multiplier = max(1 - 0.5 × (1 - 0.2), 0.2) = 0.6 → sell for 60% of base
With show-trend-in-lore: true, items show a trend arrow in the shop GUI:
↑ High demand means buy price is elevated. ↓ Low demand means sell price is better than usual.
demand-scale based on your server size. A small server with 20 players should use a lower value (50–100). A large server with 200+ players can go up to 1000+. If demand-scale is too low, prices spike from just a few purchases.
Give specific players custom buy/sell prices — globally, for a specific shop, or for a specific item.
Price modifiers are applied on top of dynamic pricing. Three levels exist, applied in priority order:
Modifier values are percentages. 100 = normal price. 90 = 10% discount. 110 = 10% markup.
| Command | Description | Example |
|---|---|---|
/shop addmodifier global <player> <value> [buy|sell] |
Set a global buy or sell modifier for a player | /shop addmodifier global Steve 90 buy → Steve gets 10% off all buys |
/shop addmodifier shop <player> <shop> <value> [buy|sell] |
Set a modifier for one shop | /shop addmodifier shop Alex ores_minerals 80 sell → Alex sells ores for 20% less |
/shop addmodifier item <player> <shop> <item> <value> [buy|sell] |
Set a modifier for one specific item | /shop addmodifier item Notch enchanted_books mending_1s35 50 buy → Notch buys Mending at 50% off |
/shop resetmodifier global <player> |
Remove global modifier from player | /shop resetmodifier global Steve |
/shop resetmodifier shop <player> <shop> |
Remove shop modifier from player | /shop resetmodifier shop Alex ores_minerals |
/shop resetmodifier item <player> <shop> <item> |
Remove item modifier from player | |
/shop checkmodifiers <player> |
Show all active modifiers for a player | /shop checkmodifiers Steve |
A special shop that shows only a random subset of its item pool each day — creating scarcity and excitement.
The rotating shop is defined in shops/daily_rotating_shop.yml. By default, 4 items are randomly chosen from a pool of 8 each day at midnight. The selection is saved to disk so a server restart doesn't change today's rotation.
shop-id: daily_shop
rotating:
enabled: true # Must be true for rotation to work
display-count: 4 # How many items to show from the pool each day
reset-time: "00:00" # Time of day when the rotation resets (24-hour HH:mm format)
timezone: "Europe/Paris" # IANA timezone ID. See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
items: # ALL items in the pool (8 defined, only 4 shown per day)
diamond_sword:
material: DIAMOND_SWORD
slot: 0
page: 1
amount: 1
buy-price: 120000
sell-price: -1
# ... more items in pool ...
When the shop is first opened after the reset time, it checks if the saved rotation has expired.
If expired (or no rotation saved), it shuffles all items in the pool using a Fisher-Yates algorithm and picks the first display-count items.
The chosen item IDs are saved to disk so the selection persists across restarts.
The next reset time is computed: if the configured reset time has already passed today, the next reset is tomorrow at that time.
Admins can immediately reroll the daily shop with:
/shop reroll daily_shop # Forces a new random selection right now
timezone field matches your server's expected reset timezone. If left blank or wrong, the reset time may be off by hours. Use a standard IANA zone like Europe/Paris, America/New_York, or UTC.
A separate shop where everything costs a custom currency (tokens) instead of server money.
The token shop is defined in shops/token_shop.yml. The key difference is the currency: token line — this tells the shop to use the custom currency system instead of Vault.
shop-id: token_shop
title: "&6&lToken Shop"
rows: 6
pages: 1
currency: token # Must match the currency ID created with /currency create token
items:
diamond_p1s0:
material: DIAMOND
name: "&b&lDiamond"
slot: 0
page: 1
amount: 1
buy-price: 10 # 10 tokens
sell-price: -1 # -1 = cannot sell this item back
/currency create token Token Tokens T 0 (or whatever values you prefer). The currency ID must exactly match the currency: field in the shop file.
| Item | Token Cost |
|---|---|
| Diamond | 10 |
| Emerald | 5 |
| Netherite Ingot | 50 |
| Golden Apple | 15 |
| Enchanted Golden Apple (God Apple) | 200 |
| Experience Bottle | 3 |
| Elytra | 500 |
| Totem of Undying | 100 |
Multiple layers of protection prevent item and money duplication exploits.
While a player's transaction is processing, a lock prevents them from starting another. This stops double-click or lag exploits from triggering two purchases at once.
After each transaction, a configurable cooldown (default 500ms) blocks any new transaction. Prevents rapid-fire clicking from triggering dozens of purchases.
Tracks transaction timestamps in a 1-second window. More than max-transactions-per-second transactions in 1 second increments a flag counter. At 10 flags → auto-kick.
Before accepting a sell, the plugin counts the player's actual inventory to confirm they have enough items. Prevents selling items they no longer have due to lag.
Before a buy, confirms the player's inventory has room. If not, the buy is rejected and money is not charged — prevents losing money to a full inventory.
When a player disconnects or is kicked, all their locks, cooldowns, and rate-limit data are cleared. No stuck state remains after a player leaves.
All shop commands, what they do, and who can use them.
| Usage | Description | Permission |
|---|---|---|
/shop | Open the main shop menu GUI | oshopgui.shop |
/shop <shopId> | Open a specific shop directly by its ID | oshopgui.shop |
/shop <player> <shopId> | Open a shop for another player (admin) | oshopgui.others |
/shop reload | Reload all shop configs without restart | oshopgui.reload |
/shop reroll <shopId> | Force-reroll a rotating shop's selection | oshopgui.reroll |
/shop check | Check the buy/sell price of the item in your hand | oshopgui.check |
/shop addmodifier global <player> <value> [buy|sell] | Add a global price modifier to a player | oshopgui.shop.addmodifier |
/shop addmodifier shop <player> <shop> <value> [buy|sell] | Add a shop-specific modifier | oshopgui.shop.addmodifier |
/shop addmodifier item <player> <shop> <item> <value> [buy|sell] | Add an item-specific modifier | oshopgui.shop.addmodifier |
/shop resetmodifier global <player> | Remove global modifier from a player | oshopgui.shop.resetmodifier |
/shop resetmodifier shop <player> <shop> | Remove shop modifier | oshopgui.shop.resetmodifier |
/shop resetmodifier item <player> <shop> <item> | Remove item modifier | oshopgui.shop.resetmodifier |
/shop checkmodifiers <player> | See all price modifiers on a player | oshopgui.shop.checkmodifiers |
| Usage | Description | Permission |
|---|---|---|
/sell hand [quantity] | Sell the item in your main hand (specific quantity) | oshopgui.sell.hand |
/sell handall | Sell ALL items matching what's in your hand | oshopgui.sell.hand.all |
/sell all | Sell every sellable item in your entire inventory | oshopgui.sell.all |
All permissions required for the shop module.
| Permission | What it allows | Who needs it |
|---|---|---|
oshopgui.shop | Open the shop GUI with /shop | All players |
oshopgui.sell.hand | Use /sell hand [qty] | All players |
oshopgui.sell.hand.all | Use /sell handall | All players |
oshopgui.sell.all | Use /sell all | All players (optional) |
oshopgui.check | Use /shop check (price check on held item) | All players (optional) |
oshopgui.reload | Use /shop reload | Admins only |
oshopgui.reroll | Use /shop reroll | Admins only |
oshopgui.others | Open a shop for another player | Admins only |
oshopgui.shop.addmodifier | Add price modifiers to players | Admins only |
oshopgui.shop.resetmodifier | Remove price modifiers from players | Admins only |
oshopgui.shop.checkmodifiers | View a player's active price modifiers | Admins only |
permission: field on any item in the shop YAML. Players without that permission won't see or be able to interact with that item.
Step-by-step walkthroughs of common player interactions.
Steve wants a diamond sword. He types /shop, clicks the "Weapons & Armor" icon, and sees the GUI. He hovers over Diamond Sword and reads the price ($120,000). He left-clicks it.
Since amount-selection.enabled: true, a confirmation screen opens. Steve sees buttons to increase/decrease quantity (+1, +16, +32, +64). He clicks "Max Affordable" — it calculates he can afford 3 swords. He clicks Confirm Purchase.
Alex has a full inventory of iron ore after mining. She holds the iron ore and types /sell handall. The plugin scans her inventory, finds all iron ore stacks, and sells them all at once.
She then types /sell all to sell everything else sellable in her inventory (coal, gravel, etc.) in one command.
Notch has a stack of diamonds and wants to know if today's price is good. He holds the diamonds and types /shop check. The server replies with the current buy and sell prices, including any active dynamic pricing modifier.
Notch notices the high demand indicator. He decides to wait until demand drops before selling, knowing the price will return to its normal $40,000.
Herobrine has earned 600 tokens through gameplay. He types /shop token_shop to open the token shop directly. He sees an Elytra listed for 500 tokens. He left-clicks it, confirms on the amount screen, and gets the elytra in his inventory.
Notice: the currency shows as T (tokens) instead of $ (money) — because this shop uses the custom token currency.
Alex types /shop daily_shop every morning to see what's on sale. Today she finds a Trident and a Beacon in the 4 rotating items. She buys the Beacon before someone else does.
Tomorrow at midnight the selection resets — the Beacon might not be available again for days.
A player achieves VIP rank. The admin types:
Steve now gets a 10% discount on every buy in every shop. The admin can check it with:
If Steve's VIP expires, the admin removes it with:
The admin edits plugins/OreoEssentials/shop/shops/ores_minerals.yml to change the diamond price, then runs:
No restart needed. Players currently in the shop GUI will see updated prices when they next open it.
Make sure players have the oshopgui.shop permission. Also check that enabled: true is set in config.yml. If using LuckPerms: lp group default permission set oshopgui.shop true.
You need both Vault (the API plugin) AND an economy plugin (EssentialsX, CMI, etc.) installed. Vault alone is just an API — it doesn't provide any economy by itself. Install both, restart, and the error should disappear.
Run /shop reload after editing any shop file. Also check:
page field matches the page you're viewingslot is within bounds (not in the navigation row)material name is valid (use ALL_CAPS exactly as Bukkit defines it)permission field, the player has that permissionThis was a bug that has been fixed. When you buy an enchanted book from the shop, the enchantment is now correctly stored inside the book as a stored enchantment. To apply it to a tool, use an anvil. If you're on an older version where this was broken, update to get the fix.
Create a new file in plugins/OreoEssentials/shop/shops/, e.g. my_custom_shop.yml. Give it a unique shop-id, add items following the item format. Then add a button for it in config.yml under main-menu-items with shop: my_custom_shop. Run /shop reload.
Set the price to -1: buy-price: -1 disables buying, sell-price: -1 disables selling. An item with both set to -1 won't appear in the shop at all (no interactable action).
Set pages: 2 (or however many you need) in the shop header. Then set page: 2 on the items you want on the second page. Make sure those items have slot values within the valid range (0 to rows×9 - 10, avoiding the navigation bar).
Check that the currency: field in token_shop.yml exactly matches the currency ID you created. Open your currency configuration and verify the ID. Remember: it's case-sensitive and must match exactly (e.g. if you created it as token, the field must say currency: token).
Increase demand-scale so it takes more purchases to reach max pressure. For example, if 50 buys are spiking prices, try demand-scale: 500 so prices don't hit maximum until 500 purchases. You can also lower max-buy-multiplier — setting it to 1.5 caps the buy price increase at 50% above base instead of 200%.
Check the timezone in daily_rotating_shop.yml. If your server runs in UTC but you set Europe/Paris, the reset happens at midnight Paris time (1am UTC in winter, 2am in summer). Also confirm that the rotation file is being written correctly — check logs/latest.log for any errors from the shop module on startup.
The anti-dupe lock releases automatically when the transaction completes or when the player disconnects. If a player is permanently stuck (very rare edge case), have them reconnect — the onQuit event always clears all locks. If they're still stuck after reconnecting, use /shop reload which reinitializes the transaction processor.
Yes. Open the shop programmatically from your NPC plugin using the command /shop <player> <shopId> via player.performCommand() or dispatch a command. You can also set hide-back-button: true in the shop YAML to hide the "Back to Main Menu" button, making the shop feel self-contained.