容量与缩容
本页说明 EmakiStorage 的容量领域模型:config.yml 的 capacity 段、四来源容量、三级 stackLimit,以及容量调小时的缩容与溢出处理。
capacity
capacity:
base_slots: 45
max_slots: 1000
warn_entry_count: 5000
default_stack_limit: 100| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
base_slots | int | 45 | 默认开放槽位数(45 = 5 × 9,与 gui.storage_rows 默认值一致)。 |
max_slots | int | 1000 | 槽位总量硬上限,0 表示无限。 |
warn_entry_count | int | 5000 | 条目数超过此值时向控制台记 warning,0 表示不提醒。 |
default_stack_limit | long | 100 | 配置级单槽堆叠上限。此层的 0 表示不限制。 |
四来源容量
effectiveSlots 由四个独立持久化的来源相加后钳制:
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 | 否 |
页数由槽位数推导,不可反向配置:
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 后,同一物品可自动占用新槽位继续存储。
behavior:
multi_slot_stacking: false # true = 允许一个条目跨多格单槽上限 100、手上 120 个橡木原木:
| 开关 | 结果 |
|---|---|
false(默认) | 存入 100,剩余 20 被拒绝(slot_full) |
true | 存入 120,显示为 [100] [20] 两格 |
占用格数如何推导
占用格数不持久化,每次读取时由数量推导:
span = ceil(条目数量 / 单槽上限)
usedSlots = 所有条目 span 之和usedSlots 必须计入 span,否则玩家能用 45 个跨格条目占满 90 格却只被记 45 格,直接突破 max_slots。
单槽上限为无限(default_stack_limit: 0 且未被玩家级/条目级覆盖)时 span 恒为 1:无限的百分比没有意义,也就没有可溢出到第二格的部分。
取出与合并
跨格条目的多个格子共享同一个数量池,点任意一格取出都从条目总量扣除。因为 span 是推导值,收缩是自动的,不存在「合并」这一步:
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 段。
四种 overflow_policy
| 取值 | 行为 |
|---|---|
lock_readonly | 超出容量边界的条目只读:可取出、不可存入,取空后自动释放。推荐。 |
compact | 超出条目前滚填补空位。条目本身已存储在无空洞的紧凑列表中,因此「前滚」就是列表的自然状态;仍然装不下的部分退化为 lock_readonly。 |
return_inventory | 尝试把超出条目退回玩家背包,装不下的退化为 lock_readonly。玩家离线时不退还,直接锁定。 |
reject_change | 直接拒绝本次缩容。 |
四种策略全部零丢失,不提供 drop 与 delete:不可逆数据丢失不应由调整配置隐式触发。
溢出状态不持久化。它由容量与占用推导,在登录、重载、权限变化、命令授予等容量变化时机重新计算,因此不会出现存储标记与事实不同步的情况。