简体中文
付费扩容档位
unlock_costs.yml 是 GUI 里付费扩容的价格档位. 它是独立的业务数据文件: release_default_data 只在文件缺失时释放一次, 版本升级不会覆盖你已经改过的内容
文件名由 config.yml 的 unlock.cost_file 指定, 默认就是 unlock_costs.yml; 玩家能不能购买, 由 unlock.purchase_enabled 与 emakistorage.unlock.purchase 权限共同决定, 见配置详解
unlock_costs.yml 结构
yaml
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]| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
tiers | list<map> | 否 | 见示例 (3 档) | 每个元素为下表的键集 | 付费档位的列表. 某一档的 count_range 不合法, 或者两种价格都没写, 这一档会被整档跳过并记 warning |
tiers[].count_range | string | 是 | — (无该键的档被跳过) | "N" 或 "min-max"; 要求 min ≥ 1 且 max ≥ min | 这一档覆盖的"第几格到第几格"区间, 比如 1-9. 排在前面的档优先 |
tiers[].currency | section | 否 | 发货档位部分含此项 | 见下方三行 | 用货币支付的代价. 和 item 至少要写一个, 否则这一档会被跳过 |
tiers[].currency.type | enum | 否 | vault | vault / excellent | 货币后端的 id, 填了不认识的值会退回 vault |
tiers[].currency.currency | string | 否 | "" (空) | 后端自身的货币标识 | 传给经济后端的货币 id; 留空表示用这个后端的默认货币. 发货文件里没有写这个键 |
tiers[].currency.amount | number 或 string | 是 (写了 currency 时) | — | 非负数, 或可被表达式引擎求值的字符串 | 金额. 写数字就直接用; 写字符串会交给 CoreLib 的 ExpressionEngine 求值, 里面可以用 %count%. 既不是数字也不是非空字符串时, 这个 currency 段会被当成没配置 |
tiers[].item | section | 否 | 发货档位部分含此项 | 见下方两行 | 用物品支付的代价 |
tiers[].item.source | string | 是 (写了 item 时) | — | CoreLib ItemSource token | 这个键缺失或者只有空白时, 整个 item 段会被当成没配置 |
tiers[].item.amount | int | 否 | 1 (没写或写得不是数字时) | > 0; ≤ 0 时该 item 段视为未配置 | 需要交多少个这种物品 |
fallback | section | 否 | 见示例 | 见下方三行 | 备用档: 超出所有 tiers 之后, 每一格的价格 |
fallback.currency | section | 否 | 见示例 | 键集同 tiers[].currency | 备用档用货币支付的代价 |
fallback.item | section | 否 | 无 | 键集同 tiers[].item | 备用档用物品支付的代价. fallback 下两种价格都没写时不生效 |
fallback.max_amount | number | 是 (要用备用档时) | — | > 0 | 单格价格的上限. 这个键缺失或者不是正数时, 备用档整体失效, 超出 tiers 的格子就买不了 |
batch | section | 否 | enabled: true, options: [1, 5, 10, 64] | 见下方两行 | 批量购买的选项 |
batch.enabled | boolean | 否 | true | true / false | 设为 false 时, 一次点击固定只买 1 格 |
batch.options | list<int> | 否 | [1, 5, 10, 64] | 只接受正整数元素; 过滤后为空则回退默认值 | 一次点击可以买几格的选项. 目前界面只取列表第一项: 点扩容按钮固定买 options[0] 格, 其余选项在这个模块里没有入口, 留着它们只是预先保留的能力 |
逐格计价
先弄清计价方式: 批量购买是按每一格分别算价再相加来结算的, 不是"单价乘数量". 玩家买第 N 格时, 由 N 落在哪个 count_range 决定这一格的价格; N 从 purchasedSlots + 1 开始往上走. 如果按当前单价乘数量来算, 玩家就能在最便宜的那一档一次买下一大批槽位, 把本该属于贵档的格子按低价锁住. 发货默认 batch.options: [1, 5, 10, 64] 而界面只取第一项, 所以按发货配置下单只有 1 格, 逐格相加就退化成单格报价; 你把 options[0] 改大就能一次买多格, 这时才会真正体现出逐格累加
表达式里的 %count% 表示"这次要买的第几格". 没有档位命中的时候就用 fallback
比如这样: 1–5 格用金币, 之后每格用钻石, 并放开一次买 10 格
yaml
tiers:
- count_range: "1-5"
currency:
type: vault
currency: "coin"
amount: 500
fallback:
item:
source: "diamond"
amount: 2
max_amount: 100000
batch:
enabled: true
options: [10]max_amount 护栏
注意: fallback.max_amount 是必须写的, 缺失或者不是正数时备用档整体失效, 超出 tiers 的格子就买不了 (回报 no_price_defined). 它的作用是防止指数表达式溢出成 Infinity
作用范围要分清: 这个上限只管备用档, 命中 tiers 的格子不受 max_amount 限制, 它们只受"金额必须有限且非负"这一条约束 (违反时报 price_overflow)
当某一格算出来的价格超过 max_amount 时, 插件会明确拒绝这次报价 (price_over_cap), 而不是不报错地把它压到上限: 你写的公式算出了越过自己设的上限的价格, 这次购买就不该继续
购买入口与前置校验
购买入口只有一个: GUI 里的 unlock 按钮. 你点击之后会依次做这些检查
unlock.purchase_enabled为true, 否则回报"未开放"- 玩家要有
emakistorage.unlock.purchase, 否则回报没有权限 unlock_costs.yml里要有能用的档位或者能用的备用档, 否则回报no_price_defined- 这次购买不会突破
capacity.max_slots(effectiveSlots + 购买格数 > max_slots时就回报max_slots_reached)
一次下单买几格, 取 batch.options 的第一项, 见上面 batch.options 的说明
支付顺序与逆序补偿
一次购买按这个顺序走: 报价 → 触发可取消的 StorageUnlockEvent → 扣货币 → 扣物品 → 增加 purchasedSlots
不用怕多扣: 扣款之后的任何一步失败, 都会按反过来的顺序补偿, 先把已经扣掉的物品按倒序退回去, 再退还货币, 然后才返回失败. purchasedSlots 在这条补偿路径上从来没被改过
失败时按"宁可拒绝也不放过"处理: 既没有匹配到档位, 也没有能用的备用档时拒绝购买, 不会当成免费