Dynamic Pricing
This page explains EzShops’ dynamic pricing system for server owners: what it does, how it behaves, how to configure it, and how to test it in-game.
Summary
- Dynamic pricing makes item buy/sell prices change over time based on player trades.
- Two systems use it in the plugin:
- Shop pricing (
shop.yml) - per-materialDynamicSettingsthat modify a storedmultiplier. - Stock market (
stockcommands / GUI) - a market price per product that moves on trades.
- Shop pricing (
- The plugin uses per-unit multiplicative updates and provides a non-mutating estimator to compute bulk totals (the sum of progressively changing per-unit prices) for previews and GUI displays.
Key concepts
- Base unit price: the configured buy/sell price for a single item.
- Multiplier: a factor that scales the base price. Multipliers are clamped between configured
minMultiplierandmaxMultiplier. - Per-unit change: each unit traded updates the multiplier (or market price) multiplicatively. For example, a 1% buyChange per unit will multiply the multiplier by 1.01 for each unit bought.
- Bulk estimate: when showing totals for N items, the plugin computes the sum of per-unit prices using the current multiplier/price and simulating N per-unit updates - without changing saved state. This produces a larger (progressive) total than simply
unit_price * Nwhen change ≠ 0.
Configuration
Dynamic pricing configuration for shop items is defined in shop.yml under each item using dynamic-pricing settings. Fields are:
starting-multiplier(double): initial multiplier for the item (default 1.0).min-multiplier(double): minimum allowed multiplier (non-zero positive).max-multiplier(double): maximum allowed multiplier.buy-change(double): per-unit fractional change applied when buying. Example:0.01= +1% per unit.sell-change(double): per-unit fractional change applied when selling. Example:0.01= -1% per unit when selling.
Example shop.yml snippet:
DIAMOND:
buy: 100.0
At a glance
- Dynamic pricing makes buy/sell prices move based on player trades.
- Shop items use per-item
DynamicSettings(configured inshop.yml). - The stock market (via
/stockand stock GUIs) simulates market prices and also supports per-unit effects. - The plugin shows progressive bulk totals (sum of changing per-unit prices) rather than
unit_price * amount.
Why this matters
Players expect bulk purchases to reflect rising prices when demand increases. Showing unit_price * amount can be misleading when each purchased unit raises the price of the next unit. EzShops simulates per-unit effects and shows accurate bulk totals so players see a consistent preview and are charged the expected amount.
Quick example (recommended)
In shop.yml:
DIAMOND:
buy: 100.0
sell: 80.0
dynamic-pricing:
starting-multiplier: 1.0
min-multiplier: 0.5
max-multiplier: 5.0
buy-change: 0.01 # +1% per unit bought
sell-change: 0.01 # -1% per unit sold
With the settings above, buying 10 diamonds does not simply charge 100 * 10. Instead the plugin charges a progressive total where each unit is slightly more expensive than the previous one.
Configuration reference
Edit shop.yml entries under each material. The relevant dynamic-pricing fields:
starting-multiplier(double) - initial multiplier (default:1.0).min-multiplier(double) - minimum allowed multiplier.max-multiplier(double) - maximum allowed multiplier.buy-change(double) - per-unit fractional increase when buying (e.g.0.01= +1%).sell-change(double) - per-unit fractional decrease when selling (e.g.0.01= -1%).
Per-item price keys (price-id) (optional)
To allow multiple independent price entries for the same Minecraft Material (for example two different shop items that both use EXPERIENCE_BOTTLE), items may declare an optional price-id string in their configuration. When present, the plugin uses this price-id as the pricing key (and as the dynamic-pricing state key stored in shop-dynamic.yml) instead of the material name.
Notes:
price-idis optional; if omitted the material name (e.g.DIAMOND,EXPERIENCE_BOTTLE) is used and pricing remains material-scoped (backwards compatible).price-idmust be unique across your shop configuration if you want independent pricing/multipliers for two items that share the same material.- Dynamic state (multipliers) are saved by
price-idinshop-dynamic.ymlwhen present; legacy material keys continue to be supported.
Example (category item):
my-exp-bottle-item:
material: EXPERIENCE_BOTTLE
price-id: exotic_exp_1 # optional unique key to separate pricing
buy: 10.0
sell: 5.0
dynamic-pricing:
starting-multiplier: 1.0
min-multiplier: 0.5
max-multiplier: 3.0
buy-change: 0.01
sell-change: 0.01
Troubleshooting tip: if two shop items using the same Material show the same price or share dynamic changes, add distinct price-id values to each item to separate them.
Stock market tuning is in stock-gui.yml / config.yml (see StockMarketConfig). The stock market uses a deterministic per-unit demand factor (default 0.02) plus a configurable random component for flavor.
How it works (technical summary)
-
The shop stores a
multiplierper item and applies multiplicative updates per traded unit:multiplier := clamp(multiplier * (1 + buyChange)) on buys
multiplier := clamp(multiplier * (1 - sellChange)) on sells
-
Bulk totals are computed by simulating N per-unit updates and summing the per-unit prices. The estimator does not mutate saved state - it is only used for previews and GUI displays.
Formulae
-
After
Nbuys (starting multiplierM0):M_N = clamp(M0 * (1 + buyChange)^N)
-
Bulk total for base unit price
P:total = sum_{i=0..N-1} normalizeCurrency( P * M0 * (1 + buyChange)^i )
normalizeCurrency is the plugin’s rounding/normalization for currency values.
Commands & testing (in-game)
Build & install:
mvn clean package -DskipTests
cp target/*.jar /path/to/paper/plugins/
# restart or reload server
Basic checks:
- Confirm plugin loaded:
/pluginsand look forEzShops. - Open the shop GUI and inspect items with
dynamic-pricingconfigured - bulk lines use{buy_bulk_total}and{sell_bulk_total}placeholders.
Preview (stock market):
/stock preview buy DIAMOND 64
This prints per-unit price and an estimated total. The estimator is deterministic (no randomness) so previews are stable.
Execute trade:
/stock buy DIAMOND 64
The plugin charges the estimated total (based on progressive per-unit prices) and updates the stored market price accordingly.
Admin commands & maintenance
Administrators can inspect and manage dynamic pricing state using the /pricingadmin admin command (also documented in the main commands reference). The command provides the following subcommands:
/pricingadmin set <item> <price>- Set the configured base price for a configured shop item. This updates the configured buy/sell price while preserving any saved dynamic multiplier state. Permission:ezshops.pricing.admin.set./pricingadmin reset <item>- Reset the dynamic pricing state (multiplier) for a single configured item, returning it to its configured base behavior. Permission:ezshops.pricing.admin.reset./pricingadmin resetall- Reset dynamic pricing for all configured items. This clears saved multipliers for every item and returns them to their configured base prices. Permission:ezshops.pricing.admin.resetall.
Examples:
/pricingadmin set DIAMOND 100.0
/pricingadmin reset DIAMOND
/pricingadmin resetall
Notes:
- The dynamic state is stored in
shop-dynamic.yml. Theresetandresetallcommands remove saved multipliers from this file (or the in-memory representation), so items revert to the values defined inshop.yml. - Operators have access to admin commands by default; use a permissions plugin to grant fine-grained access to the
ezshops.pricing.admin.*nodes. - See the main commands reference for full usage and tab-completion behavior: docs/commands.md.
UI notes
- The confirmation GUI for stock transactions shows totals for the following amounts by default:
1, 8, 16, 32, 64. - Administrators can change the GUI files or request an enhancement to make these amounts configurable via
stock-gui.yml.
Admin tips & tuning
- Use smaller
buy-change/sell-changefor gentle price movement (e.g.0.002). - Use
min-multiplier/max-multiplierto limit extreme swings. - Cap preview amounts in GUI to avoid heavy computation on very large inputs.
Migration & compatibility
- Existing
shop-dynamic.ymlfiles are preserved. Multipliers already stored will be used as the starting point after upgrade. - If you rely on additive semantics, consider adding a config flag
dynamic-pricing.mode(not currently present) or keep a fallback branch.
Troubleshooting
- If bulk totals appear incorrect:
- Ensure
shop-dynamic.ymlexists and contains expected multipliers. - Check console logs for errors during startup or trade handling.
- Verify
shop.ymlentries are valid and that the material keys match (DIAMOND,STONE, etc.).
- Ensure
- If players report surprising totals, reduce
buy-change/sell-changeand test again.
Contributing & support
If you want enhancements (config-mode toggle for additive vs multiplicative, configurable GUI amounts, extra debug commands, unit tests), open an issue or a PR in the repository. Include example configs and expected behavior.
For quick debugging: check shop-dynamic.yml on the server and use /stock preview to verify estimator outputs.