跳到主要内容

货币系统

Malkuth 支持三种货币模式,并提供独立的 currency.yml 文件统一管理货币定义。

三种货币模式

模式说明
vaultHook Vault 金币插件,使用服务器经济系统
playerpointsHook 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货币类型:vaultplayerpoints 或 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
直接写 PAPI 变量的限制

如果你在 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 的含义

{{ &money }}takegive 中表示交易金额,但在 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] 范围内。

  1. 商品初始价格为 base(基础价格)
  2. 每次有玩家购买,交易计数 trade_count 增加
  3. 购买次数越多,价格越高,最高不超过 max
  4. sensitivity 控制价格上涨速度,例如 0.05 表示每次交易按 base 增加 5% 的阶梯值
  5. 价格不会低于 min

这个功能适合用于稀有商品或限量商品,可以模拟真实的供需关系。