简体中文
配置详解
config.yml 的配置逐键说明如下. EmakiStorage 的配置分成三个文件, 另外两个文件以及容量的整体结构各有专页:
| 主题 | 页面 |
|---|---|
gui/storage_gui.yml 模板, gui 与 display 段 | 界面与展示 |
capacity 段, 四来源容量, 三级 stackLimit, 缩容溢出 | 容量与缩容 |
unlock_costs.yml 付费档位 | 付费扩容档位 |
改完配置, 执行 /estorage reload 重载配置, 语言与 GUI 模板
config.yml 顶层
yaml
version: "1.1.5"
language: "zh_CN"
release_default_data: true
op_bypass: false
debug: false| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
version | string | 否 | 1.1.5 (缺键时回退 1.0.6) | — (不校验) | 默认资源的版本标记, 由资源同步流程维护. 缺键时代码会退回 AppConfig.CURRENT_VERSION (配置迁移用的常量, 值是 1.0.6), 和发货时写的值不一样, 两者不要混用 |
language | string | 否 | zh_CN | 对应 lang/<language>.yml 的文件名 | 界面和命令文案用的语言 |
release_default_data | boolean | 否 | true | true / false | 内置的业务数据 (unlock_costs.yml) 缺失时是否释放一次; 已经存在的文件不受影响 |
op_bypass | boolean | 否 | false | true / false | 为 true 时 OP 视为持有 emakistorage.admin, 可执行全部管理子命令并代替 reload / debug 专用权限 |
debug | boolean | 否 | false | true / false | 这个键会被读进配置对象, 但目前代码里没有任何地方真正用它; 调试开关请用 /estorage debug 管理 |
举个例子: 把语言切到英文并临时放开 OP 绕过权限
yaml
language: "en_US"
op_bypass: trueunlock
yaml
unlock:
overflow_policy: lock_readonly
purchase_enabled: true
cost_file: "unlock_costs.yml"| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
overflow_policy | enum | 否 | lock_readonly | lock_readonly / compact / return_inventory / reject_change | 容量缩小导致占用超出上限时的处理策略, 四种取值的差别见容量与缩容. 填了不认识的值会退回默认值 |
purchase_enabled | boolean | 否 | true | true / false | 是否开放 GUI 里的付费扩容. 还需要 emakistorage.unlock.purchase 权限; 设为 false 时点扩容按钮会回报"未开放" |
cost_file | string | 否 | unlock_costs.yml | 插件数据目录下的相对路径, 不校验存在性 | 付费档位文件的名字. 文件缺失或内容为空时, 付费扩容整体不可用. 每个键的说明见付费扩容档位 |
比如这样关闭付费扩容, 并把缩容策略改为退回背包
yaml
unlock:
overflow_policy: return_inventory
purchase_enabled: falsebehavior
yaml
behavior:
overflow_on_withdraw: return
withdraw_amounts:
left: 1
right: 16
shift_left: 32
shift_right: 64
withdraw_prompt:
enabled: true
mode: "auto"
dialog:
title: "dialog.withdraw.title"
can_close_with_escape: true
pause: false
body:
- "dialog.withdraw.body"
inputs:
- type: "text"
key: "amount"
label: "dialog.withdraw.label"
max_length: 32
buttons:
- label: "dialog.confirm"
deposit_filter:
mode: blacklist
entries: []
allow_unique_items: true
multi_slot_stacking: false
batch_max_ops: 200
default_sort: amount_desc
player_sort_enabled: true| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
overflow_on_withdraw | enum | 否 | return | return / drop | 取出时背包装不下的剩余部分怎么处理: return 把剩余放回仓库, 并同步把这次扣掉的数量退回去; drop 让剩余掉到地上, 且不算进这次取出的数量. 填了不认识的值会退回 return |
withdraw_amounts.left | long | 否 | 1 | ≥ 0 (负值按 0 处理) | 左键一次取出的数量. 设为 0 时这种点击方式不做任何事 |
withdraw_amounts.right | long | 否 | 16 | ≥ 0 (负值按 0 处理) | 右键一次取出的数量 |
withdraw_amounts.shift_left | long | 否 | 32 | ≥ 0 (负值按 0 处理) | Shift 加左键一次取出的数量 |
withdraw_amounts.shift_right | long | 否 | 64 | ≥ 0 (负值按 0 处理) | Shift 加右键一次取出的数量 |
withdraw_prompt.enabled | boolean | 否 | true | true / false | 中键是否允许输入自定义数量. 设为 false 时中键不做任何事, 条目的 lore 里也不再追加自定义数量的提示行 |
withdraw_prompt.mode | enum | 否 | auto | auto / dialog / chat | 输入方式: auto 是有对话框就用对话框, 没有就退回聊天输入; dialog 强制用对话框, 对话框不可用时这个交互会被禁用; chat 强制走聊天输入. 填了不认识的值会退回 auto |
withdraw_prompt.dialog | section | 否 | 见示例 | CoreLib 对话框键集, 见对话框配置块 | 对话框的配置. 必须保留 key: amount 这个输入框, 否则对话框会被判定为不可用 |
deposit_filter.mode | enum | 否 | blacklist | blacklist / whitelist / off | 存入过滤的模式. 填了不认识的值会退回 blacklist |
deposit_filter.entries | list<string> | 否 | [] | CoreLib ItemSource token 列表 | 只要命中其中一个 token 就算命中; 物品源用连字符, 组件 ID 和 path 用冒号 |
deposit_filter.matcher | section | 否 | 无 | CoreLib 物品匹配器键集 | 按组件, PDC, Lore 等条件来判断物品. 它和 entries 是或者的关系: 任意一个命中就算命中, 再交给 mode 决定放行还是拦下. 见 物品匹配器 |
allow_unique_items | boolean | 否 | true | true / false | 是否允许存入带唯一标记的物品 (物品自身的 PDC 不为空). 只在需要新建条目时判断, 已有条目继续累加不受影响 |
multi_slot_stacking | boolean | 否 | false | true / false | 同一种物品单槽存满后, 是否自动占用新槽位接着存. 设为 false 时, 存满就把剩下的部分拒绝掉. 详见容量与缩容 |
batch_max_ops | int | 否 | 200 | 1–2000 (超出会夹紧到区间内) | 一次 applyBatchAsync 调用允许的最大操作数. 请求超过生效上限时会整批返回 batch_too_large, 一条都不执行; 它只影响批量 API, 不影响界面和命令 |
default_sort | enum | 否 | amount_desc | material_asc / material_desc / name_asc / name_desc / amount_asc / amount_desc | 默认的排序方式, 对新玩家和没自己改过排序的玩家生效. 填了不认识的值会退回 amount_desc |
player_sort_enabled | boolean | 否 | true | true / false | 是否允许玩家自己在界面里切换排序. 设为 false 时点排序按钮会回报已禁用, 并强制使用 default_sort |
两种空配置的结果要分清: deposit_filter.mode 为 whitelist 而 entries, matcher 都为空时, 所有物品都存不进去; 为 blacklist 且两者都为空时, 相当于关掉了过滤, 全部放行. 唯一标记的判断只看物品自身的 PersistentDataContainer 是否非空, 不会去读显示名或 lore
注意: withdraw_amounts 只接受非负值, 写成负数时会在解析阶段按 0 处理, 于是这种点击方式变成不做任何事, 而不是退回默认数量
这些键决定各种点击各取多少, 以及允许不允许; 完整的点击含义表, 被拒绝的点击方式和搜索状态下的限制见界面与展示
再来一个例子: 右键一次取 1 个, 关闭中键自定义数量, 并允许同一物品跨槽位存储
yaml
behavior:
withdraw_amounts:
left: 64
right: 1
shift_left: 2304
shift_right: 576
withdraw_prompt:
enabled: false
multi_slot_stacking: truesearch
yaml
search:
enabled: true
operators:
name: "@"
lore: "#"
id: "$"
exclude: "!"
input:
mode: "auto"
timeout: 30
cancel_keywords: ["取消", "cancel"]
dialog:
title: "dialog.search.title"
can_close_with_escape: true
pause: false
body:
- "dialog.search.body"
inputs:
- type: "text"
key: "query"
label: "dialog.search.label"
max_length: 64
buttons:
- label: "dialog.confirm"| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
enabled | boolean | 否 | true | true / false | 是否开放搜索. 设为 false 时点搜索按钮会回报"搜索已关闭" |
operators.name | string | 否 | @ | 任意非空前缀; 空串表示停用该作用域 | 只用来匹配显示名 |
operators.lore | string | 否 | # | 任意非空前缀; 空串表示停用该作用域 | 只用来匹配 lore |
operators.id | string | 否 | $ | 任意非空前缀; 空串表示停用该作用域 | 只用来匹配材质 key 或 ItemSource id |
operators.exclude | string | 否 | ! | 任意非空前缀; 空串表示停用排除 | 排除用的前缀: 命中就拒绝这个条目 |
input.mode | enum | 否 | auto | auto / dialog / chat | 输入方式, 取值含义和 behavior.withdraw_prompt.mode 相同 |
input.timeout | long | 否 | 30 | ≥ 0 (小于 0 按 0 处理) | 聊天输入的等待秒数. 中键自定义取出数量也共用这条超时时间, 见下面的说明 |
input.cancel_keywords | list<string> | 否 | ["取消", "cancel"] | 关键词列表; 空列表回退默认 | 取消聊天输入的关键词, 自定义取出数量的聊天输入也用它 |
input.dialog | section | 否 | 见示例 | CoreLib 对话框键集, 见对话框配置块 | 对话框的配置. 必须保留 key: query 这个输入框, 否则对话框会被判定为不可用 |
这两个键是两处共用的: search.input.timeout 与 search.input.cancel_keywords 同时用于搜索输入和中键自定义取出数量的聊天输入. 所以你把超时改成 5, 这两种输入的等待时间会一起缩短
处于搜索状态时展示区存不进去; 搜索过程中在搜索按钮上右键可以清除过滤
比如这样改用英文取消关键词, 并强制走聊天输入
yaml
search:
operators:
name: "n:"
lore: "l:"
input:
mode: "chat"
timeout: 15
cancel_keywords: ["cancel", "quit"]对话框配置块
behavior.withdraw_prompt.dialog 与 search.input.dialog 用的键, 和 EmakiCoreLib 对话框定义文件 (EmakiCoreLib/dialogs/*.yml) 完全一样, 包括 type, external_title, after_action, columns, exit_button, 以及输入项的 width, initial, label_visible 等. 只有一点不同: id 由插件自己提供, 配置里不用写
文本怎么解析: title, body 条目的文本, 输入项的 label, 按钮的 label 与 tooltip 会先按语言键来解析. 你填 dialog.withdraw.title 这类键名时它会去读 lang/*.yml, 键不存在就按原样输出, 所以也可以直接写 MiniMessage 文本. 搜索对话框的正文支持 %name%, %lore%, %id%, %exclude% 这四个操作符占位符
先看前提: 对话框需要客户端 1.21.6 及以上, 并且 EmakiCoreLib 的 dialog.enabled 为 true. 如果 inputs 里缺少业务需要的 key (取出是 amount, 搜索是 query), 或者一个按钮都没有, 这个对话框会被判定为不可用: mode: auto 会退回聊天输入, mode: dialog 则会禁用这个交互并提示 dialog.unavailable
你还可以改操作符前缀, 用来避免和物品名撞上. 搜索的语法规则:
- 用空白分隔多个词, 所有词必须同时满足 (AND 关系)
- 一个词可以同时带一个作用域前缀和一个排除前缀, 顺序随便, 比如
!@钻石和@!钻石是一样的 - 不带作用域前缀时会去匹配合并文本. 合并文本由"显示名 + lore + 标识符"三段拼起来, 所以不加前缀也能搜到材质 key
- 所有匹配都是子串匹配, 匹配的是一份已经转成小写, 去掉格式, 提前算好的文本
注意: 这里不提供正则搜索, 也没有管理员开关. 恶意的正则会让服务器因为灾难性回溯卡住, 而子串匹配已经够用
auto_pickup
yaml
auto_pickup:
enabled: true
mode: on_pickup
radius: 8
max_radius: 16
scan_interval_ticks: 10
default_enabled: false
notify_cooldown_ms: 3000| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
enabled | boolean | 否 | true | true / false | 自动拾取的总开关. 关掉后整个功能不可用, /estorage autopickup 会被拒绝, 扫描任务也不会启动 |
mode | enum | 否 | on_pickup | on_pickup (别名 pickup) / radius (别名 range) | 工作模式: on_pickup 只在原版拾取事件触发时才拾取 (没有任何额外开销); radius 会另外启动一个定时扫描任务, 把玩家周围指定半径内的掉落物都捡走 (更强, 但会消耗性能). 填了不认识的值会退回 on_pickup |
radius | int | 否 | 8 | 夹紧到 1–max_radius | 只对 radius 模式有效. 扫描半径 (单位: 格), 全服统一. on_pickup 模式下这个值被忽略 |
max_radius | int | 否 | 16 | ≥ 1 (小于 1 按 1 处理) | 只对 radius 模式有效. radius 的上限. 解析时先算出 max_radius, 再用它限制 radius, 所以把 max_radius 调小会立刻把 radius 收紧 |
scan_interval_ticks | int | 否 | 10 | ≥ 1 (小于 1 按 1 处理) | 只对 radius 模式有效. 全服统一的扫描间隔 (单位: 1/20 秒, 也就是一个游戏刻). 20 个游戏刻 = 1 秒; 10 就是每 0.5 秒扫一次. 越小反应越快, 但 CPU 开销越大. on_pickup 模式下这个值被忽略 |
default_enabled | boolean | 否 | false | true / false | 新玩家第一次载入时, 个人开关的默认值. true 表示默认开启, 玩家可以用 /estorage autopickup off 关掉; false 表示默认关闭. 已经存到文件里的个人开关不会被这个键覆盖 |
notify_cooldown_ms | long | 否 | 3000 | ≥ 0 (负值按 0 处理) | 拾取被拒绝时提示的冷却时间 (毫秒). 冷却时间内的后续拒绝不再发消息, 免得刷屏; 0 表示每次都提示 |
玩家用 /estorage autopickup 命令管理自己的开关 (需要 emakistorage.autopickup 权限, 默认 true). 半径没有个人设置: radius 是全服统一的值, 你只能改配置
开销要留意: mode: radius 会每隔 scan_interval_ticks 对所有在线玩家做一次范围扫描, 单服 100 人在线的例子, scan_interval_ticks: 20 时相当于每秒做 5 次全员扫描. 请按服务器性能调整间隔, 或者改用 on_pickup 模式
比如这样改用半径扫描, 每 2 秒扫 6 格范围内掉落物
yaml
auto_pickup:
mode: "radius"
radius: 6
max_radius: 16
scan_interval_ticks: 40persistence
yaml
persistence:
autosave_interval_seconds: 300
drain_timeout_seconds: 10| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
autosave_interval_seconds | long | 否 | 300 | ≥ 0 (负值按 0 处理) | 自动保存的间隔 (秒). 0 表示不启用定时保存, 只在关闭界面和下线时保存. 旧键名 autosave_interval 仍然能用, 启动时会自动迁移到新键 |
drain_timeout_seconds | long | 否 | 10 | ≥ 1 (小于 1 按 1 处理) | 插件停用时, 等待文件写完的超时时间 (秒). 旧键名 drain_timeout 仍然能用, 启动时会自动迁移到新键 |
你会看到自动保存期间发生的多次数量变化被合并成一次写文件. 每次保存都是把 storage.dat 整份重写: 先在同一个目录写一个 .tmp, 读回来核对记录条数, 最后整体替换. 写入失败时旧文件保持不变, 并把 tmp 删掉
比如这样改成每 10 分钟保存一次, 停机最多等 30 秒
yaml
persistence:
autosave_interval_seconds: 600
drain_timeout_seconds: 30logging
yaml
logging:
enabled: true
retention_days: 30
sources: []| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
enabled | boolean | 否 | true | true / false | 是否记录常规流水. 不影响那些一定会记的管理员操作 |
retention_days | int | 否 | 30 | ≥ 0 (负值按 0 处理) | 日志保留的天数. <= 0 时不做过期清理 |
sources | list<string> | 否 | [] | gui / command / api / action / auto_pickup; 空列表表示全部 | 只记录这些来源的流水, 按来源 id 精确匹配. 发货 config.yml 的注释只列了前四个, 其实还有第 5 个来源 auto_pickup (自动拾取存入) |
日志目录按"每个玩家一个目录, 每天一个文件"组织: logs/<uuid>/<yyyy-MM-dd>.log. 过期的文件按天删除; 某个玩家的日志全部过期后, 他的目录也一起删掉. 清理发生在重载和停用这两个流程里
记住: ADMIN_SET, ADMIN_GIVE, ADMIN_CLEAR 这三类管理员操作一定会被记录, 不受 enabled 与 sources 影响. 管理员为了省磁盘关掉日志, 并不是把管理员改动记录弄丢的理由
流水日志只写不读: 插件的业务逻辑从不读这些文件, 所以不需要索引, 不需要压实, 不需要数据结构版本, 也不需要跨文件事务. 写入失败只会记一条 warning, 不会让产生这条日志的那次存取失败. 同一个玩家排队的记录会合并成一次追加写, 所以你一次存 36 格只产生一次文件操作, 而不是 36 次
比如这样只保留 7 天, 并且只记录玩家直接在界面上的操作
yaml
logging:
enabled: true
retention_days: 7
sources: ["gui"]