Skip to content

配置详解 ​

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
字段类型必填默认值取值范围说明
versionstring否1.1.5 (缺键时回退 1.0.6)— (不校验)默认资源的版本标记, 由资源同步流程维护. 缺键时代码会退回 AppConfig.CURRENT_VERSION (配置迁移用的常量, 值是 1.0.6), 和发货时写的值不一样, 两者不要混用
languagestring否zh_CN对应 lang/<language>.yml 的文件名界面和命令文案用的语言
release_default_databoolean否truetrue / false内置的业务数据 (unlock_costs.yml) 缺失时是否释放一次; 已经存在的文件不受影响
op_bypassboolean否falsetrue / false为 true 时 OP 视为持有 emakistorage.admin, 可执行全部管理子命令并代替 reload / debug 专用权限
debugboolean否falsetrue / false这个键会被读进配置对象, 但目前代码里没有任何地方真正用它; 调试开关请用 /estorage debug 管理

举个例子: 把语言切到英文并临时放开 OP 绕过权限

yaml
language: "en_US"
op_bypass: true

unlock ​

yaml
unlock:
  overflow_policy: lock_readonly
  purchase_enabled: true
  cost_file: "unlock_costs.yml"
字段类型必填默认值取值范围说明
overflow_policyenum否lock_readonlylock_readonly / compact / return_inventory / reject_change容量缩小导致占用超出上限时的处理策略, 四种取值的差别见容量与缩容. 填了不认识的值会退回默认值
purchase_enabledboolean否truetrue / false是否开放 GUI 里的付费扩容. 还需要 emakistorage.unlock.purchase 权限; 设为 false 时点扩容按钮会回报"未开放"
cost_filestring否unlock_costs.yml插件数据目录下的相对路径, 不校验存在性付费档位文件的名字. 文件缺失或内容为空时, 付费扩容整体不可用. 每个键的说明见付费扩容档位

比如这样关闭付费扩容, 并把缩容策略改为退回背包

yaml
unlock:
  overflow_policy: return_inventory
  purchase_enabled: false

behavior ​

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_withdrawenum否returnreturn / drop取出时背包装不下的剩余部分怎么处理: return 把剩余放回仓库, 并同步把这次扣掉的数量退回去; drop 让剩余掉到地上, 且不算进这次取出的数量. 填了不认识的值会退回 return
withdraw_amounts.leftlong否1≥ 0 (负值按 0 处理)左键一次取出的数量. 设为 0 时这种点击方式不做任何事
withdraw_amounts.rightlong否16≥ 0 (负值按 0 处理)右键一次取出的数量
withdraw_amounts.shift_leftlong否32≥ 0 (负值按 0 处理)Shift 加左键一次取出的数量
withdraw_amounts.shift_rightlong否64≥ 0 (负值按 0 处理)Shift 加右键一次取出的数量
withdraw_prompt.enabledboolean否truetrue / false中键是否允许输入自定义数量. 设为 false 时中键不做任何事, 条目的 lore 里也不再追加自定义数量的提示行
withdraw_prompt.modeenum否autoauto / dialog / chat输入方式: auto 是有对话框就用对话框, 没有就退回聊天输入; dialog 强制用对话框, 对话框不可用时这个交互会被禁用; chat 强制走聊天输入. 填了不认识的值会退回 auto
withdraw_prompt.dialogsection否见示例CoreLib 对话框键集, 见对话框配置块对话框的配置. 必须保留 key: amount 这个输入框, 否则对话框会被判定为不可用
deposit_filter.modeenum否blacklistblacklist / whitelist / off存入过滤的模式. 填了不认识的值会退回 blacklist
deposit_filter.entrieslist<string>否[]CoreLib ItemSource token 列表只要命中其中一个 token 就算命中; 物品源用连字符, 组件 ID 和 path 用冒号
deposit_filter.matchersection否无CoreLib 物品匹配器键集按组件, PDC, Lore 等条件来判断物品. 它和 entries 是或者的关系: 任意一个命中就算命中, 再交给 mode 决定放行还是拦下. 见 物品匹配器
allow_unique_itemsboolean否truetrue / false是否允许存入带唯一标记的物品 (物品自身的 PDC 不为空). 只在需要新建条目时判断, 已有条目继续累加不受影响
multi_slot_stackingboolean否falsetrue / false同一种物品单槽存满后, 是否自动占用新槽位接着存. 设为 false 时, 存满就把剩下的部分拒绝掉. 详见容量与缩容
batch_max_opsint否2001–2000 (超出会夹紧到区间内)一次 applyBatchAsync 调用允许的最大操作数. 请求超过生效上限时会整批返回 batch_too_large, 一条都不执行; 它只影响批量 API, 不影响界面和命令
default_sortenum否amount_descmaterial_asc / material_desc / name_asc / name_desc / amount_asc / amount_desc默认的排序方式, 对新玩家和没自己改过排序的玩家生效. 填了不认识的值会退回 amount_desc
player_sort_enabledboolean否truetrue / 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: true
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"
字段类型必填默认值取值范围说明
enabledboolean否truetrue / false是否开放搜索. 设为 false 时点搜索按钮会回报"搜索已关闭"
operators.namestring否@任意非空前缀; 空串表示停用该作用域只用来匹配显示名
operators.lorestring否#任意非空前缀; 空串表示停用该作用域只用来匹配 lore
operators.idstring否$任意非空前缀; 空串表示停用该作用域只用来匹配材质 key 或 ItemSource id
operators.excludestring否!任意非空前缀; 空串表示停用排除排除用的前缀: 命中就拒绝这个条目
input.modeenum否autoauto / dialog / chat输入方式, 取值含义和 behavior.withdraw_prompt.mode 相同
input.timeoutlong否30≥ 0 (小于 0 按 0 处理)聊天输入的等待秒数. 中键自定义取出数量也共用这条超时时间, 见下面的说明
input.cancel_keywordslist<string>否["取消", "cancel"]关键词列表; 空列表回退默认取消聊天输入的关键词, 自定义取出数量的聊天输入也用它
input.dialogsection否见示例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
字段类型必填默认值取值范围说明
enabledboolean否truetrue / false自动拾取的总开关. 关掉后整个功能不可用, /estorage autopickup 会被拒绝, 扫描任务也不会启动
modeenum否on_pickupon_pickup (别名 pickup) / radius (别名 range)工作模式: on_pickup 只在原版拾取事件触发时才拾取 (没有任何额外开销); radius 会另外启动一个定时扫描任务, 把玩家周围指定半径内的掉落物都捡走 (更强, 但会消耗性能). 填了不认识的值会退回 on_pickup
radiusint否8夹紧到 1–max_radius只对 radius 模式有效. 扫描半径 (单位: 格), 全服统一. on_pickup 模式下这个值被忽略
max_radiusint否16≥ 1 (小于 1 按 1 处理)只对 radius 模式有效. radius 的上限. 解析时先算出 max_radius, 再用它限制 radius, 所以把 max_radius 调小会立刻把 radius 收紧
scan_interval_ticksint否10≥ 1 (小于 1 按 1 处理)只对 radius 模式有效. 全服统一的扫描间隔 (单位: 1/20 秒, 也就是一个游戏刻). 20 个游戏刻 = 1 秒; 10 就是每 0.5 秒扫一次. 越小反应越快, 但 CPU 开销越大. on_pickup 模式下这个值被忽略
default_enabledboolean否falsetrue / false新玩家第一次载入时, 个人开关的默认值. true 表示默认开启, 玩家可以用 /estorage autopickup off 关掉; false 表示默认关闭. 已经存到文件里的个人开关不会被这个键覆盖
notify_cooldown_mslong否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: 40

persistence ​

yaml
persistence:
  autosave_interval_seconds: 300
  drain_timeout_seconds: 10
字段类型必填默认值取值范围说明
autosave_interval_secondslong否300≥ 0 (负值按 0 处理)自动保存的间隔 (秒). 0 表示不启用定时保存, 只在关闭界面和下线时保存. 旧键名 autosave_interval 仍然能用, 启动时会自动迁移到新键
drain_timeout_secondslong否10≥ 1 (小于 1 按 1 处理)插件停用时, 等待文件写完的超时时间 (秒). 旧键名 drain_timeout 仍然能用, 启动时会自动迁移到新键

你会看到自动保存期间发生的多次数量变化被合并成一次写文件. 每次保存都是把 storage.dat 整份重写: 先在同一个目录写一个 .tmp, 读回来核对记录条数, 最后整体替换. 写入失败时旧文件保持不变, 并把 tmp 删掉

比如这样改成每 10 分钟保存一次, 停机最多等 30 秒

yaml
persistence:
  autosave_interval_seconds: 600
  drain_timeout_seconds: 30

logging ​

yaml
logging:
  enabled: true
  retention_days: 30
  sources: []
字段类型必填默认值取值范围说明
enabledboolean否truetrue / false是否记录常规流水. 不影响那些一定会记的管理员操作
retention_daysint否30≥ 0 (负值按 0 处理)日志保留的天数. <= 0 时不做过期清理
sourceslist<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"]

相关页面 ​