Unlock Costs
unlock_costs.yml defines the price tiers for in-GUI paid expansion. It is a standalone business data file: release_default_data releases it only when the file is missing, and a version upgrade never overwrites existing admin content.
The file name comes from unlock.cost_file in config.yml (default unlock_costs.yml), and whether purchasing is offered at all depends on unlock.purchase_enabled together with the emakistorage.unlock.purchase permission — see Configuration.
unlock_costs.yml Structure
tiers:
- count_range: "1-9"
currency:
type: vault
amount: 1000
- count_range: "10-19"
item:
source: "diamond"
amount: 16
- count_range: "20-49"
currency:
type: vault
amount: 50000
item:
source: "netherite_ingot"
amount: 4
fallback:
currency:
type: vault
amount: "100000 * 1.05 ^ (%count% - 50)"
max_amount: 1000000000
batch:
enabled: true
options: [1, 5, 10, 64]| Field | Type | Description |
|---|---|---|
tiers[].count_range | string | The "which slot number" range this tier covers, such as 1-9. Earlier tiers take priority. |
tiers[].currency.type | enum | Currency backend: vault / excellent. |
tiers[].currency.amount | number or string | The price. A string is evaluated by CoreLib's ExpressionEngine and may use %count%. |
tiers[].item.source | string | CoreLib ItemSource token. |
tiers[].item.amount | int | Required item count. |
fallback.currency | section | Price for every slot beyond all tiers. |
fallback.max_amount | number | Per-slot price cap. Required. |
batch.enabled | boolean | Whether batch purchase is available. When false, only the single-slot option is offered. |
batch.options | list | Batch purchase options. |
Per-Slot Pricing
A batch purchase is settled by pricing each slot and summing, not by unit price times count. When a player buys slot N, the count_range that N falls into decides that slot's price; N starts at purchasedSlots + 1 and increments. Charging the current unit price times the count would let a player buy a large batch at the cheapest tier and lock in that price for slots that belong in far more expensive tiers.
In expressions, %count% is the ordinal of the slot about to be bought.
The max_amount Guard Rail
fallback.max_amount is required: when it is missing or non-positive the fallback tier is disabled entirely and slots beyond tiers cannot be bought (reported as no_price_defined). Its purpose is to stop an exponential expression from overflowing into Infinity.
When a slot's computed price exceeds max_amount, the implementation explicitly refuses the quote (price_over_cap) instead of silently clamping to the cap: the admin's formula produced a price outside their own declared guard rail, so the purchase must not proceed.
Payment Ordering and Reverse Compensation
Purchase flow: quote, fire the cancellable StorageUnlockEvent, take currency, take items, then increase purchasedSlots.
Any failure after a successful charge is compensated in reverse order: applied item removal plans are rolled back last-to-first, then the currency is refunded, and only then does the call return a failure. purchasedSlots is never touched on the compensation path.
Failure is fail-closed: when no tier matches and no usable fallback exists, the purchase is refused rather than treated as free.
Related Pages
- Configuration:
unlock.purchase_enabledandunlock.cost_file. - Capacity: where
purchasedSlotssits in the four-source capacity model. - GUI and Display: the slot configuration of the
unlockbutton. - Event API: the fields and cancellation semantics of
StorageUnlockEvent.