货币系统
Malkuth 支持三种货币模式,并提供独立的 currency.yml 文件统一管理货币定义。
三种货币模式
| 模式 | 说明 |
|---|---|
vault | Hook Vault 金币插件,使用服务器经济系统 |
playerpoints | Hook PlayerPoints 点券插件,价格会被解析为整数(Int) |
%papi_var% | 任意 PlaceholderAPI 变量作为自定义货币 |
currency.yml — 货币定义文件
plugins/Malkuth/currency.yml 是货币的统一定义文件。你可以在这里预定义多种货币,然后在各个商店配置中通过 key 直接引用。
默认内容:
# Vault 金币 (需安装 Vault + 经济插件)
vault:
mode: vault
deny: |-
tell color inline "&c货币不足! 需要 &e{{ &need-money }}&c,你只有 &e{{ &money }}"
# PlayerPoints 点券 (需安装 PlayerPoints)
playerpoints:
mode: playerpoints
deny: |-
tell color inline "&c点券不足! 需要 &e{{ &need-money }}&c,你只有 &e{{ &money }}"
# 自定义货币示例 (PAPI 占位符 + Kether 脚本)
# custom_points:
# mode: "%custom_points%"
# give: |-
# command inline "custompoints give {{ sender }} {{ &money }}" as console
# tell inline "&a{{ &buyer }} 购买了 {{ &amount }} 个 &f{{ &display-name }}&a,你收到 &e{{ &money }}"
# take: |-
# command inline "custompoints take {{ sender }} {{ &money }}" as console
# tell inline "&a你从 {{ &seller }} 购买了 {{ &amount }} 个 &f{{ &display-name }}&a,花费了 &e{{ &money }}"
# deny: |-
# tell color "&c货币不足! 需要 &e{{ &need-money }}&c,你只有 &e{{ &money }}"
每个货币定义包含以下字段:
| 字段 | 说明 |
|---|---|
mode | 货币类型:vault、playerpoints 或 PAPI 占位符 |
give | 卖家收款脚本(Kether) |
take | 买家扣款脚本(Kether) |
deny | 余额不足时执行的脚本(Kether) |
Vault 和 PlayerPoints 模式无需定义脚本,插件会直接调用对应 API。仅当使用 PAPI 自定义货币时才需要编写 give/take/deny 脚本。
商店中引用货币
在商店配置中有两种方式使用货币:
方式一:引用 currency.yml 中的预定义货币
直接写货币的 key 名称:
# 引用 currency.yml 中定义的 vault
currency: vault
# 引用 currency.yml 中定义的 custom_points
currency: custom_points
这种方式最简洁,多个商店可以共用同一个货币定义,修改 currency.yml 即可全局生效。
方式二:在商店中内联定义
直接在商店配置中写完整的货币配置:
# 内联定义(新格式,直接写在 currency 下)
currency:
mode: "%custom_points%"
take: |-
command inline "custompoints take {{ sender }} {{ &money }}" as console
tell inline "&a花费了 &e{{ &money }} &f点券"
deny: |-
tell color "&c点券不足! 需要 &e{{ &need-money }}&c,你只有 &e{{ &money }}"
# 内联定义(兼容旧格式,写在 actions 下)
currency:
mode: "%custom_points%"
actions:
take: |-
command inline "custompoints take {{ sender }} {{ &money }}" as console
deny: |-
tell color "&c货币不足"
两种内联格式等价,插件优先读取直接字段(take),找不到时回退到 actions.take。
如果多个商店共用同一种货币,建议在 currency.yml 中统一定义,商店中用 key 引用。这样修改货币脚本时只需改一处。
商品级货币覆盖(goods.*.currency)
购买型商店的单个商品条目支持单独指定 currency。源码会优先读取商品级货币;没写时才回退到商店顶层 currency。
当前支持的写法
goods.*.currency 当前只支持字符串写法,不能像商店顶层那样直接内联一个完整 currency: 配置节。
可用值有四类:
| 写法 | 说明 |
|---|---|
vault | 使用 Vault 金币 |
playerpoints | 使用 PlayerPoints 点券 |
custom_points | 引用 currency.yml 中预定义的货币 key |
%custom_points% | 直接写 PAPI 占位符;能读取余额,但没有配套 take / deny 脚本时不适合直接用于真实扣款 |
默认示例
默认 shop/item_trade_example.yml 已包含商品级货币覆盖:
currency:
mode: vault
goods:
# 单个商品改用 playerpoints
2:
id: item1
amount: 3
price: 150
currency: playerpoints
# 混合模式也可以叠加商品级货币
6:
id: item1
amount: 1
price: 200
currency: playerpoints
buy:
mode: items
items:
- check:
material: DIAMOND
amount: 3
自定义货币怎么写更稳妥
如果你想让某个商品单独改用自定义 PAPI 货币,推荐先在 currency.yml 里定义一个货币 key,再在商品里引用这个 key:
# currency.yml
custom_points:
mode: "%custom_points%"
take: |-
command inline "custompoints take {{ sender }} {{ &money }}" as console
deny: |-
tell color "&c点券不足"
# shop/*.yml
goods:
7:
id: vip_ticket
amount: 1
price: 300
currency: custom_points
如果你在 goods.*.currency 里直接写 %custom_points%,插件可以读取余额,但不会自动附带 take / deny 脚本。购买时仍然需要真实的扣款脚本,因此自定义货币商品更推荐引用 currency.yml 中已经定义好的 key。
当前实现注意点
- 商品展示里的
{has_currency}和{currency_type}会跟随商品级货币变化。 - 购买与赠送时,实际扣款走商品级货币;没写商品级
currency才回退商店顶层currency。 - 确认购买界面顶部的
balance目前仍读取商店顶层currency,所以如果你大量混用商品级货币,建议在确认模板文案里写清楚当前商品货币类型。
货币动作详解
自定义 PAPI 货币需要通过 Kether 脚本定义三个动作。不同商店模式用到的动作不同:
三个动作
| 动作 | 说明 | 使用场景 |
|---|---|---|
take | 买家扣款脚本 | 所有模式(买家购买时扣除货币) |
give | 卖家收款脚本 | 玩家商店(买家购买后,卖家收到货币) |
deny | 余额不足提示 | 所有模式(买家货币不够时执行) |
普通商店 / 限时商店 / 回收商店
只需定义 take(买家扣款)和 deny(余额不足):
currency:
mode: "%custom_points%"
take: |-
command inline "custompoints take {{ sender }} {{ &money }}" as console
tell inline "&a花费了 &e{{ &money }} &f点券"
deny: |-
tell color "&c点券不足! 需要 &e{{ &need-money }}&c,你只有 &e{{ &money }}"
玩家商店
玩家商店涉及买卖双方,需要定义全部三个动作:
currency:
mode: "%custom_points%"
give: |-
command inline "custompoints give {{ sender }} {{ &money }}" as console
tell inline "&a买家 &f{{ &buyer }} &a购买了 {{ &amount }} 个 &f{{ &display-name }}&a,你收到 &e{{ &money }} &a点券"
take: |-
command inline "custompoints take {{ sender }} {{ &money }}" as console
tell inline "&a你从 &f{{ &seller }} &a购买了 {{ &amount }} 个 &f{{ &display-name }}&a,花费了 &e{{ &money }} &a点券"
deny: |-
tell color "&c点券不足! 需要 &e{{ &need-money }} &c点券,你只有 &e{{ &money }}"
Kether 变量
不同动作中可用的变量及含义:
take(买家扣款)
| 变量 | 说明 |
|---|---|
{{ sender }} | 买家名称 |
{{ &money }} | 扣除的总价(单价 x 数量) |
{{ &balance }} | 买家当前余额 |
{{ &seller }} | 卖家名称 |
{{ &amount }} | 购买数量 |
{{ &display-name }} | 商品显示名 |
give(卖家收款)
| 变量 | 说明 |
|---|---|
{{ sender }} | 卖家名称 |
{{ &money }} | 收到的总金额(单价 x 数量) |
{{ &balance }} | 卖家当前余额 |
{{ &buyer }} | 买家名称 |
{{ &amount }} | 购买数量 |
{{ &display-name }} | 商品显示名 |
deny(余额不足)
| 变量 | 说明 |
|---|---|
{{ sender }} | 买家名称 |
{{ &money }} | 买家当前余额 |
{{ &need-money }} | 所需总价 |
{{ &balance }} | 买家当前余额(与 &money 相同) |
{{ &seller }} | 卖家名称 |
{{ &amount }} | 购买数量 |
{{ &display-name }} | 商品显示名 |
{{ &money }} 在 take 和 give 中表示交易金额,但在 deny 中表示玩家当前余额。如果需要在 deny 中获取所需金额,请使用 {{ &need-money }}。
动态定价 (dynamic_price)
动态定价功能可以让商品价格根据购买热度自动调整。配置在商店文件的单个商品 goods 条目下。
配置字段
| 字段 | 类型 | 说明 |
|---|---|---|
enable | 布尔 | 是否启用动态定价 |
base | 数字 | 基础价格 |
min | 数字 | 最低价格 |
max | 数字 | 最高价格 |
window_hours | 数字 | 价格计数窗口配置字段(当前版本仅解析,不自动重置) |
sensitivity | 小数 | 价格敏感度系数,数值越大价格波动越剧烈 |
window_hours 字段会被读取并写入配置对象,但当前版本未在调度链路中调用重置逻辑(DynamicPriceDao.resetExpired)。实际价格会基于累计 trade_count 计算,除非你自行扩展重置流程。
示例
goods:
3:
id: mm_legendary_sword
amount: 1
price: 5000
limitation:
window: permanent
player: 1
global: 10
dynamic_price:
enable: true
base: 5000
min: 3000
max: 10000
window_hours: 24
sensitivity: 0.05
运作机制
动态价格的计算公式为:
实际价格 = base × (1 + trade_count × sensitivity)
结果被限制在 [min, max] 范围内。
- 商品初始价格为
base(基础价格) - 每次有玩家购买,交易计数
trade_count增加 - 购买次数越多,价格越高,最高不超过
max sensitivity控制价格上涨速度,例如0.05表示每次交易按base增加 5% 的阶梯值- 价格不会低于
min
这个功能适合用于稀有商品或限量商品,可以模拟真实的供需关系。