跳转到正文

容器、末影箱与批量转移 ​

ctx.containers 在机器人附近已有容器上执行原生开窗、读取、转移和关窗。它不会建箱、传送或操作任意玩家的背包。所有操作都通过 Operations 留下回执;成功 ACK 与独立读回是两种证据。

独立入口是 neomega_runtime.containers.Containers(operations)。ContainerKind 为 Literal["chest", "ender_chest", "double_chest", "barrel", "shulker_box"];TransferDirection 为 Literal["deposit", "withdraw"],可从该模块导入。

公共参数与权限 ​

参数要求
pos三个整数的方块坐标,SDK 转为列表;目标必须在机器人 6 格内且已有可核验方块。
dimension: int0..2,必须与实际交互维度一致。
kind默认 "chest";实际方块必须与指定类型相符,不自动切换。
idempotency_key: str独立业务动作的持久唯一标识,所有意图方法必传。
deadline: str带时区的绝对截止时间,所有意图方法必传。

inspect、transfer、merge 分别需要同名 operation 权限 framework.container.inspect、framework.container.transfer、framework.container.merge,均须清单声明、管理员授权和世界写入门允许。inspect 也会真实开关窗口,不是被动缓存查询。

类型槽位额外约束
chest0..26单箱;遇到双箱不会自动扩展。
double_chest0..53要求配对方块证据和本次完整 54 槽窗口内容。
ender_chest0..26内容属于当前连接机器人,不能指定其他玩家。
barrel0..26使用木桶原生窗口类型。
shulker_box0..26使用潜影盒原生窗口类型。

检查内容与保存会话证据 ​

text
ctx.containers.inspect(pos, *, dimension: int,
    kind: ContainerKind = "chest", idempotency_key: str, deadline: str) -> OperationIntent

该方法只准备意图;执行时 Host 打开真实容器、读取按序内容并关闭窗口。成功为 succeeded/verified,结果提供本次容器内容。保留完整结果,而不只保存 slots;原生连接绑定是否可用仍须检查 native_connection_verified。

python
intent = ctx.containers.inspect(
    container_position, dimension=dimension, kind="ender_chest",
    idempotency_key=inspection_key, deadline=deadline,
)
await save_intent(intent.payload())  # 调用方的持久化函数
operation_id = await ctx.operations.submit(intent)
await save_operation_id(operation_id)
receipt = await ctx.operations.wait(operation_id)
if receipt["state"] == "succeeded":
    observation = receipt["result"]["result"]
    await save_observation(observation)

容器原生有序内容与结果中的 inventory_slots 不是同一种来源:后者是 bot_resource_cache,不能独立证明主背包的新鲜状态。主背包对账另用 ctx.inventory.observe(0, ...)。已知空槽须有明确空证据;没有返回槽位或观察缺失均不能当空气。

容器槽记录含 slot、stack_id;空槽有 empty: true,非空槽有 item、fingerprint、attributes_fingerprint、nbt_types、reconciliation_supported。它不是 InventorySlot,没有主背包的 known/state 字段。

结果还可能附带 native_inventory 原生背包快照,缺失时为 null。只有 native_connection_verified: true 及相应 connection_id、connected_at、observation_started_at、sampled_at 才提供连接绑定证据;不能把未绑定观察提升为严格对账依据。

分别保存 Host 会话标识与原生 connection_id / connected_at,不要互相替代。重新连接后,旧物品栈 ID、修订号及容器证据不能用于新动作。末影箱也需要重新观察,不能因为仍站在同一方块旁就沿用旧引用。

移入空槽、拆堆与合堆 ​

text
ctx.containers.transfer(pos, *, dimension: int,
    direction: TransferDirection, inventory_slot: int, container_slot: int,
    count: int, expected_source_stack_id: int,
    expected_destination_stack_id: int, expected_source_fingerprint: str,
    idempotency_key: str, deadline: str,
    kind: ContainerKind = "chest") -> OperationIntent

ctx.containers.merge(pos, *, dimension: int,
    direction: TransferDirection, inventory_slot: int, container_slot: int,
    count: int, expected_source_stack_id: int,
    expected_destination_stack_id: int, expected_source_fingerprint: str,
    expected_destination_fingerprint: str, expected_source_count: int,
    expected_destination_count: int, destination_count_limit: int,
    idempotency_key: str, deadline: str,
    kind: ContainerKind = "chest") -> OperationIntent
项目transfermerge
目标已观察空槽,目的栈 ID 必须为 0。已观察非空兼容栈,两端栈 ID 均大于 0。
源与目的deposit 为背包 → 容器;withdraw 为容器 → 背包。方向相同,指纹和数量始终按实际源/目的填写。
数量count: 1..64,不超过源数量;部分数量形成拆堆。同样要求,另需精确的两端原数量及目的数量上界。
指纹原生源完整指纹。原生两端完整指纹;不自行重建或用属性指纹替换。
槽编号inventory_slot: 0..35;容器槽由种类决定。相同。

destination_count_limit 为 1..64,来自已知原数量或玩法约束,不是自动识别的物品最大堆叠数。必须满足 count <= expected_source_count 及 count + expected_destination_count <= destination_count_limit。SDK 不会在合堆失败后自动退回空槽转移,也不选择替代槽。

python
# source 是新鲜主背包槽,destination 是同连接容器检查所得空槽。
# 业务先验证数量、来源、连接、新鲜度,并保存两份完整观察。
if not (source["known"] and source["state"] == "occupied"
        and destination.get("empty") is True
        and destination.get("stack_id") == 0 and "item" not in destination):
    raise ValueError("转移需要真实源物品和明确空目的槽")

intent = ctx.containers.transfer(
    container_position, dimension=dimension, kind="chest", direction="deposit",
    inventory_slot=source["slot"], container_slot=destination["slot"], count=1,
    expected_source_stack_id=source["stack_id"],
    expected_destination_stack_id=destination["stack_id"],
    expected_source_fingerprint=source["fingerprint"],
    idempotency_key=transfer_key, deadline=deadline,
)
# 保存意图后提交、保存 operation_id,再等待回执;流程同 inspect 示例。

转移保留原物品,不能用命令生成一个“看起来相同”的物品替代。成功通常是 succeeded/acknowledged,结果含 server_acknowledged: true、independent_readback: false;inventory_before 是供读回核对使用的游标,可能为空,不是完整物品快照。下一步重新观察主背包与容器,结合typed NBT 比较验证数量和属性。

顺序批量执行与部分完成 ​

text
await ctx.containers.batch(intents, *, journal, timeout: float = 30) -> dict

await neomega_runtime.container_batch.execute_batch(
    operations, intents, *, journal, timeout=30
) -> dict

intents 是 1..32 个已准备的 transfer / merge 意图,各 key 必须不同。不是 OperationIntent 的元素抛 TypeError;数量、种类、重复 key、journal 或超时参数不合法时抛 ValueError。timeout 为有限正数,不能超过 300 秒。它不是单条 Host 原子操作:SDK 顺序提交、等待、记录每步,第一项失败、未知或等待超时便停止,没有回滚。

journal(record) 必须是异步持久化函数。调用前由业务保存一次性准入;SDK 在提交前、取得 ID 后、取得回执后等待 journal 完成。重启、取消或重复业务请求都读取原 journal,不再次执行整个 batch。

python
# intents 只能包含彼此不依赖已变化栈 ID 的已准备操作。
# save_batch 必须持久化;本次业务已完成一次性准入。
batch_result = await ctx.containers.batch(
    intents, journal=save_batch, timeout=30,
)
await save_batch(batch_result)

返回对象含 state、completed、steps。每步保留 intent、状态以及已取得的 operation_id 和 receipt;未提交项为 not_started。全部成功为 succeeded,中途停止为 partial;completed 只统计成功回执,不表示独立读回通过或整批原子提交。取消会保存未知步骤后传播取消异常。journal 失败也不会授权重跑。

相邻步骤若复用刚变化的栈,需要重新观察再准备后续意图;不能预先猜测下一步栈 ID。此类依赖链应由业务逐步执行与核对,而不是把推测引用塞进一个 batch。

核对未声明变动的容器槽 ​

text
from neomega_runtime.item_reconciliation import compare_container_unchanged

compare_container_unchanged(*, before, after, changed_slots,
                            session_before, session_after) -> bool

这是纯比较函数,仅支持完整 27 槽 chest / ender_chest 的 ordered_inventory_content 观察。前后必须有 native_connection_verified: true,原生连接及起点、种类、位置、维度一致;session_before、session_after 是业务保留的同一非空 Host 会话标识。

python
from neomega_runtime.item_reconciliation import compare_container_unchanged

others_unchanged = compare_container_unchanged(
    before=container_before, after=container_after,
    changed_slots=[container_slot],
    session_before=host_session_before, session_after=host_session_after,
)

该函数检查所有未列入 changed_slots 的槽位保持不变。它不检查列入的槽位,所以还要用 compare_transfer 验证实际源/目的变化;它也不检查 inventory_slots 缓存、不认证来源、不证明因果。不支持 double_chest、barrel、shulker_box,不能裁成 27 槽后套用。格式或支持范围不符抛 ValueError;有效证据下发现差异返回 False。

失败与恢复 ​

原因意义与后续动作
container_observation_failed未取得可用观察;检查目标、范围、窗口与连接状态,不推断为空。
container_transfer_not_sent / container_merge_not_sent未进入转移;核对旧引用是否已变化。
container_transfer_rejected / container_merge_rejected服务端明确拒绝,保留 ACK。
container_transfer_uncertain / container_merge_uncertain转移已进入但结果不确定,保留原 key 和 ID,重新读回对账。
container_lifecycle_uncertain窗口开关生命周期不明,保留恢复隔离,停止后续写入。

权限拒绝、未进服、未连接及世界写入门关闭也可能阻止动作。不要捕获所有异常后直接返回“箱子为空”或自动换 key 重试。

管理员端 POST /runtime/admin/v1/recovery-container-inspection 是资源恢复接口,不是 ctx.containers 方法。在旧容器入口范围内,请求只接受明确的 confirm: true 与全部待恢复记录的精确 operation_ids,仅覆盖旧 Host 会话中 framework.container.inspect 的 container_lifecycle_uncertain、无部分效果且原参数有效的记录,排除 transfer/merge。

恢复还要求新连接独占、没有当前动作、屏幕已关闭等证明。它只释放旧屏幕资源,不修改原 unknown,不证明旧检查成功,更不证明物品未变化。插件作者应保留回执供管理员处理;业务物品结果仍按错误与结果核对单独核对。

原生窗口计划与租约 ​

本节接口自配套 1.0.0 Host 与 SDK 提供。先从 Bus 目录取得 major 1 的完整合同;下列成员由同一原生窗口模块提供。托管插件必须分别声明并获准 permissions.services 中实际调用的 window.run、window.open、window.read、window.move、window.close;旧容器 operation 权限和 window.run 授权不会自动覆盖其他成员。

window.run 接受 typed Plan{target, steps}:目标包含 kind、三个 int32 坐标及 int32 维度;非空 steps 使用完整的 Read | Transfer | Merge union 合同,不能拿普通 JSON 或只有一个变体的类型代替。Transfer 要求已观察的空目的槽;Merge 要求两端真实 stack ID、指纹、精确数量及明确的目的数量上界。后续步骤不会自动从前一步 ACK 推导新 stack ID。

一次计划只开一个目标窗口,依次执行步骤并等待实际关闭。纯 Read 计划同样开关窗口,需要世界写入条件。接纳只返回持久 operation ID;最终回执 result.steps 记录逐步的 index、kind、entered 与 receipt。首个步骤错误后停止,已成功的写入不回滚;失败时的 partial_effects.acknowledged_steps 保留已确认写入索引。转移或合并的 ACK 仍不等于独立读回。明细超过记录上限时会明确给出 steps_recorded 和 results_omitted: true。

原有 ctx.containers.inspect/transfer/merge 保留原权限、参数与回执结构;ctx.containers.batch 仍是多个独立操作和 ID,不会自动变为单个 window.run 计划,也不提供原子回滚。

读后再决定下一步 ​

成员请求与结果
window.openOpenArgs{target};成功回执的 result.handle 返回窗口句柄。
window.readHandleArgs{handle};读取当前窗口,保留 typed NBT 与物品证据,不会自动关闭窗口。
window.moveMoveArgs{handle, step};使用完整 Step union,但只接受 Transfer 或 Merge。
window.closeHandleArgs{handle};明确关闭,确认实际关闭或实际连接 epoch 已变化后释放租约。

完整保存句柄的 lease_id、epoch、owner 和 generation,不要只存 lease ID,也不能修改字段转交给另一安装。Host 另行核对可信调用者;这里的 epoch 是实际游戏连接身份。每次调用各自持久接纳并查询原回执,先确认 open,再 read、按证据决定 move,最后显式 close。

租约存在时,其他会发送的操作返回 resource_busy 与占用句柄,持有者自己的其他发包或主动命令查询也不例外;纯缓存读取与 库存 View 不受该租约检查影响。取消等待不会关闭窗口或释放租约。开关窗或发送结果为 unknown 时,保留原意图、完整句柄和 operation ID 先对账,不重新开窗或重放未知动作;恢复成功也不会改写历史 unknown。详见错误与结果核对。

候选中的容器检查恢复还接受合同精确匹配 window.run major 1、schema digest 一致且参数有效的纯 Read 计划。仍要求明确确认、完整原 operation IDs、暂停接纳、实际排空、新连接与关闭证明;写入计划及带 partial_effects 的记录不在此恢复范围,恢复不修改历史 unknown。

多个受管动作需要连续占用同一组资源时,可使用 1.1.0 的资源会话 Scope。关闭会话与动作结果分开处理,资源占用不会增加本页成员权限,也不会回滚游戏效果。

Neomega · 使用、开发与参考文档