简体中文
容量与缩容
EmakiStorage 的容量这样算: config.yml 的 capacity 段, 由四个来源共同决定的容量, 三级 stackLimit (单槽上限), 以及你把容量调小之后的缩容与超出处理
capacity
yaml
capacity:
base_slots: 45
max_slots: 1000
warn_entry_count: 5000
default_stack_limit: 100| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
base_slots | int | 否 | 45 | ≥ 0 (负值按 0 处理) | 默认开放的槽位数量 (45 = 5 × 9, 和 gui.storage_rows 的默认值对得上). 这是每个玩家都有的基数, 不是总量上限 |
max_slots | int | 否 | 1000 | ≥ 0 (负值按 0 处理); 0 表示不设上限 | 槽位总量的硬上限, 在四个来源相加之后再限制. 调小它会立刻压缩所有超过这个值的玩家, 压缩的结果由 unlock.overflow_policy 决定 |
warn_entry_count | int | 否 | 5000 | ≥ 0 (负值按 0 处理); 0 表示不提醒 | 玩家数据载入时, 条目数超过这个值就往控制台记一条 warning. 它只是提醒, 不会拒绝存入, 也不改变容量 |
default_stack_limit | long | 否 | 100 | ≥ 0 (负值按 0 处理); 0 表示不限制 | 配置级的单槽上限. 玩家级和条目级都没设置时会用到它 |
比如这样给全服 90 格基础容量, 取消总量上限, 并把默认单槽上限提到 1000
yaml
capacity:
base_slots: 90
max_slots: 0
default_stack_limit: 1000四来源容量
玩家的 effectiveSlots (有效槽位数) 由四个各自单独存档的来源相加后再受上限限制:
text
effective = clamp (base_slots + 权限档位 + grantedSlots + purchasedSlots, 0, max_slots)你把 max_slots 设为 0 就是不设上限. 四个来源分开存储, 所以调小 base_slots 不会吃掉玩家已经授予或购买的槽位
| 来源 | 来自 | 可否为负 |
|---|---|---|
baseSlots | capacity.base_slots | 否 (负值按 0 处理) |
permissionSlots | emakistorage.slots.<n> 的最大档位 | 否 |
grantedSlots | /estorage slot grant, API grantSlotsAsync, Action storage_grant_slot | 是 |
purchasedSlots | GUI 付费扩容, Action storage_unlock_slot | 否 |
记住: 页数是从槽位数算出来的, 你不能反过来配置:
text
totalPages = max (1, ceil (effectiveSlots / 每页格数))
reachablePages = max (1, ceil (usedSlots / 每页格数))第 1 页总是能到达 (空仓库也得能打开, 才能收到第一件物品); 玩家完全没有条目的页翻不进去
提示: 把 max_slots 设为 0 (不设上限) 之前, 请先确认内存够用. 配置注释里给的量级是单玩家 10 万条目大约占 100–200MB 堆内存, 这是推算出来的, 没有实测
三级 stackLimit
单槽上限按"最具体优先"解析, 一共分三层:
| 层级 | 存储位置 | 设置方式 | 0 的含义 |
|---|---|---|---|
| 条目级 | meta.yml 对应条目 | /estorage stacklimit slot, API setSlotStackLimitAsync, Action storage_set_stacklimit 带 slot | 沿用玩家级的上限 |
| 玩家级 | meta.yml 的 defaultStackLimit | /estorage stacklimit player, API setStackLimitAsync, Action storage_set_stacklimit 不带 slot | 沿用配置级的上限 |
| 配置级 | config.yml 的 capacity.default_stack_limit | 配置文件 | 不作限制 (等价 Long.MAX_VALUE) |
同一个 0 在不同层级上的含义不一样, 别搞混: 条目级和玩家级的 0 表示"沿用上一级", 配置级的 0 表示"不限制". 解析顺序是条目级 > 0 → 玩家级 > 0 → 配置级, 配置级 <= 0 时返回 Long.MAX_VALUE
你还可以用 emakistorage.stacklimit.<n> 权限档位提供玩家级上限, 同样取最大值, 不支持通配
同物品跨槽位存储
默认情况下单槽存满就把剩下的部分拒绝掉. 你把 behavior.multi_slot_stacking 设为 true 后, 同一种物品会自动占用新槽位接着存
yaml
behavior:
multi_slot_stacking: false # true = 允许一个条目跨多格举个例子: 单槽上限 100, 手上 120 个橡木原木, 结果如下:
| 开关 | 结果 |
|---|---|
false (默认) | 存入 100, 剩余 20 被拒绝 (slot_full) |
true | 存入 120, 显示为 [100] [20] 两格 |
占用格数如何推导
占用格数不会存进文件, 玩家每次读取时按数量算出来:
text
span = ceil (条目数量 / 单槽上限)
usedSlots = 所有条目 span 之和为什么必须这么算: usedSlots 要把每个条目的 span 都算进去, 否则玩家用 45 个跨格条目就能占满 90 格, 却只被记成 45 格, 直接突破 max_slots
单槽上限为不限制 (default_stack_limit: 0 且没有被玩家级/条目级覆盖) 时, span 恒为 1: 上限无限时百分比没有意义, 也就没有能溢到第二格的部分
取出与合并
跨格条目的多个格子共用同一个数量池, 你点任何一格取出都从条目总量里扣. 因为 span 是算出来的, 收缩是自动的, 并没有"合并"这一步:
text
120 个 (上限 100) → [100] [20] 占用 2 格
点第 1 格取出 20 → [100] 占用 1 格, 后面的条目前滚填补为什么看起来是"往前补": 条目本身就存在一个没有空洞的紧凑列表里, 所以"后面的物品往前补"是列表本来的样子, 界面每次刷新都会重新算一遍
界面显示
你在每一格看到的是这一格自己的数量, 不是条目总量 (第 1 格 100/100, 第 2 格 20/100), 和玩家看原版容器时的习惯一致. 跨格时会额外多一行 gui.entry.span_total, 说明条目总量和占用的格数
与其他机制的关系
| 面 | 行为 |
|---|---|
| 总槽位限制 | 仍然生效. 装不下的部分照旧被拒绝, 不会突破 capacity.max_slots |
%used_slots% (占位符, /estorage info, 界面容量行) | 自动跟着新含义走: 开关打开后显示的是占用格数, 而不是条目数 |
/estorage stacklimit slot <n>, API setSlotStackLimitAsync, StorageEntrySnapshot.slotIndex | 仍然是条目下标, 不是界面上显示的格号. 上限属于条目本身, 条目下标才是稳定的身份. 开关关闭时两者一样 |
搜索命中数 %visible% | 仍然是命中的条目数, 因为玩家搜的是物品而不是格子 |
| 溢出锁定 | 按 span 累加出边界. 跨过容量边界的条目整条被锁定 (只读, 还能取出), 保证不丢 |
| 持久化 | 格式不变. 跨格只是条目数量变大, 而数量本来就存成变长整数 |
关掉开关后
把开关从 true 改回 false 不会丢数据: 已经超过单槽上限的条目还是占一格, 显示时按 100% 截断, 新的存入被拒绝, 但能正常取空
开销
开销对比: 开关关闭时, usedSlots 直接取条目数就能得到, O (1) (不需要遍历). 打开后要遍历全部条目累加 span, 单玩家条目数很大时, 每次刷新界面的开销会上升. 容量在每次刷新时只解析一次, 不是每格解析一次, 但遍历本身躲不掉
缩容与溢出
容量调小后占用超出边界时, 按 unlock.overflow_policy 处理; 这个键本身在配置详解的 unlock 段
四种取值的思路一致: 先算出要锁定的那些条目, 再决定要不要退还. 锁定哪些条目按 span 累加边界来判断, 刚好跨过容量边界的那条条目整条锁定, 而不是把条目劈成两半
四种 overflow_policy
| 取值 | 行为 |
|---|---|
lock_readonly | 超出边界的条目变成只读: 能取出, 不能存入, 取空之后条目被移除, 锁定也跟着消失. 推荐用这个 |
compact | 代码里和 lock_readonly 走同一条路径, 锁定的条目完全一样. 条目本来就存在没有空洞的紧凑列表里, 没有空位需要往前补, 所以两者现在的行为一致; 取值列表里保留 compact 只是保留了"整理"这个含义位置 |
return_inventory | 会试着把每条超出边界的条目按原版堆叠上限一叠一叠塞进玩家背包: 塞进去的数量记一条 OVERFLOW 流水并从条目里扣掉, 条目被清空后直接移除; 还有剩下的条目继续锁定. 玩家离线时不做任何退还, 全部锁定 |
reject_change | 算出的锁定条目和 lock_readonly 一样, 并把内部的 rejected 标记设为 true; 但目前没有任何地方读取这个标记, 所以实际效果和 lock_readonly 相同, "拒绝缩容"还没有真正生效 |
放心一点: 四种策略都不会丢数据, 没有提供 drop 与 delete, 不可逆的丢数据不该因为改了一下配置就悄悄发生
溢出状态只在刷新界面时才计算 (打开界面, 每次存取, 翻页, 排序, 搜索之后的刷新, 管理员打开目标仓库), 本身不存进文件. 界面没打开时不维护任何锁定集合, 所以不会出现存档里的标记和实际情况对不上的情况