外观
物品选择、使用与动作组合
ctx.item_actions 将快捷栏选择、攻击、对实体交互、对空使用、点击方块和释放物品准备成可追踪的一次性动作。它不会自动选槽、等待蓄力、补发释放或推断命中。需要持续瞄准、近战组合和弩状态观察时,继续阅读瞄准与战斗。
下面的插件 SDK 接口使用 ctx.operations 受理动作。先从当前连接观察物品与目标,按各方法声明操作和包权限,再提交意图。
一次动作的输入与权限
text
held_item_ref(snapshot, slot) -> dict
ActionAttempt(intent, operation_id=None, receipt=None, started=False)
ItemActions(operations)从 neomega_runtime.item_actions 导入。snapshot 必须是 source='native_inventory_packets' 的原生背包窗口 0 快照。快捷栏 slot 为 0..8;槽位必须明确 known=True,有正 observed_revision 和合法 stack_id。未知槽位不能假定为空气。
held_item_ref() 返回 ConnectionID、Slot、ObservedRevision、StackNetworkID。Host 写出前会重新校验这个引用;发生背包变化时应停止当前计划,再观察,而不是用一个任意 stack ID 修补请求。
| 动作 | typed packet | 权限 |
|---|---|---|
select | mob_equipment.hotbar,包 31 | framework.packet.send_typed,packet_send_ids 包含 31 |
attack、interact_entity | inventory_transaction.use_item_on_entity_attack / inventory_transaction.use_item_on_entity_interact,包 30 | 同一操作权限,包号 30 |
use_air、use_block、release | inventory_transaction.use_item_air / inventory_transaction.use_item_block / inventory_transaction.release_item,包 30 | 同上 |
| 观察选槽 | 原始包流中的入站 MobEquipment | 另申请所用观察流的权限;不是发送包授权 |
| 使用后中性输入 | player_auth_input.movement,包 144 | 另授权 144 |
世界写入还要求已进游戏、原生连接可用且 Host 允许写入。库存观察、目标观察和包观察各有自己的权限,见权限与 manifest。
完整动作方法
以下方法都只准备 ActionAttempt,不执行 IO;idempotency_key 和带时区的固定 deadline 由调用者提供。
text
ItemActions.select(snapshot, slot, *, entity_runtime_id,
idempotency_key, deadline)
ItemActions.attack(snapshot, slot, *, target_runtime_id, position,
clicked_position, idempotency_key, deadline)
ItemActions.interact_entity(snapshot, slot, *, target_runtime_id, position,
clicked_position, idempotency_key, deadline)
ItemActions.use_air(snapshot, slot, *, trigger_type, block_position, block_face,
position, clicked_position, block_runtime_id,
client_prediction, idempotency_key, deadline)
ItemActions.use_block(snapshot, slot, *, trigger_type, block_position, block_face,
position, clicked_position, block_runtime_id,
client_prediction, idempotency_key, deadline)
ItemActions.release(snapshot, slot, *, head_position, idempotency_key, deadline)
async ItemActions.dispatch(attempt, *, timeout=10, on_admitted=None,
poll_interval=.05)entity_runtime_id 是当前机器人运行时 ID;target_runtime_id 必须来自当前目标观察。position、clicked_position、head_position、block_position、block_face 与触发参数都由调用者根据具体协议动作和实际场景提供。use_block 的 block_runtime_id 必须来自实际方块,不能猜;use_air 也不提供通用协议默认值。SDK 没有隐式朝向、命中点或物品专用交互策略。
dispatch() 最多提交一次,受理前就将 attempt.started=True,受理后写入 operation_id。on_admitted(attempt) 是异步回调,适合立即保存 ID;回调失败不会撤销已受理动作。成功返回完整 receipt,必须同时为 state='succeeded' 和 result.evidence='dispatched',否则抛 ProtocolError。取消或网络异常后,原 attempt 仍是恢复入口,不能再次 dispatch。
可复制的单次选择封装
此函数需要真实库存快照、机器人 runtime ID、业务固定 key/deadline 和持久化回调。它只报告发送回执;若产品需要“选槽已被观察到”,使用下一节的 HotbarSelection。control 是该 worker 所有控制动作共用的 ActionControl。
python
async def select_once(ctx, inventory, slot, runtime_id, *,
control, journal, key, deadline):
async with control:
attempt = ctx.item_actions.select(
inventory, slot, entity_runtime_id=runtime_id,
idempotency_key=key, deadline=deadline,
)
def record():
return dict(intent=attempt.intent.payload(), started=attempt.started,
operation_id=attempt.operation_id, receipt=attempt.receipt)
async def admitted(_):
await journal(record())
await journal(record())
try:
return await ctx.item_actions.dispatch(attempt, on_admitted=admitted)
finally:
if attempt.started and (attempt.receipt is None or
attempt.receipt.get('state') == 'unknown'):
control.block(record())
await journal(record())journal 的返回代表保存完成;不要用 print() 替代。如果提交响应丢失,可能没有 operation ID,但已经保存的 intent 含原始 key;按原 key 核对受理状态,而不是生成新 key。控制恢复条件见移动页。
观察选槽与归档检查
text
equipment_matches(event, *, entity_runtime_id, slot, stack_network_id,
after_sequence) -> bool
async wait_equipment(stream, *, entity_runtime_id, slot, stack_network_id,
after_sequence, timeout=5) -> event
HotbarSelection.prepare(actions, inventory, slot, *, entity_runtime_id,
idempotency_key, deadline) -> HotbarSelection
selection.record() -> dict
async selection.dispatch(*, observer_packets, journal, output,
expected_epoch=None, observer_session=None,
timeout=5, max_events=1000, max_bytes=8388608) -> dict
inspect_selection_recording(source) -> dict前两个函数在 neomega_runtime.item_actions,其余在 neomega_runtime.selection_recipe。equipment_matches 检查新的 sequence、包 31、neomega-json-v1 和匹配的 runtime ID、槽位、stack ID;它本身不替你维护完整流的连接与缺口边界。wait_equipment 消费调用方拥有的有序流,超时不触发发送;调用方在 finally 关闭流,禁止多个协程争读同一流。
HotbarSelection 将独立观察与一次发送组合:先打开 observer_packets.open([31], text_profile='selected_only'),清空旧事件,持久化 intent,再设定 sequence 边界并发送,直到同时拿到 dispatched 回执和匹配入站观察。observer_packets 使用 SDK 的 Packets 兼容接口;观察者可以是另一机器人,写入者不保证收到自己的 MobEquipment 回显。
output 是调用方打开的私有文本文件对象,写入有界 JSONL 录制;文件关闭由调用方负责,观察流由 recipe 关闭。expected_epoch 可固定观察连接;observer_session 未填时使用观察上下文的 operations session。流缺口、溢出、重连、日志失败或截断都会阻止 observed=True。
python
from neomega_runtime.selection_recipe import HotbarSelection
async def select_and_observe(ctx, inventory, slot, runtime_id, *, control,
observer_packets, journal, output, key, deadline):
async with control:
selection = HotbarSelection.prepare(
ctx.item_actions, inventory, slot,
entity_runtime_id=runtime_id, idempotency_key=key, deadline=deadline,
)
try:
result = await selection.dispatch(
observer_packets=observer_packets, journal=journal, output=output,
)
await journal(selection.record()) # 保存关闭流后确认的 observed。
return result
finally:
state = selection.record()
action = selection.action
if action.started and (action.receipt is None or
action.receipt.get('state') == 'unknown'):
control.block(state)
await journal(state)record() 包含 intent、operation_id、receipt、started、observed、dispatched;成功返回还带 recording。超时或异常时保留同一 selection/action,重建 recipe 不属于恢复。
已有录制可以完全离线检查:
sh
python -m neomega_runtime.selection_recipe selection.jsonl或 inspect_selection_recording(source),其中 source 是录制路径/可读文本流。返回 complete、observed、dispatched、confirmed、issues;confirmed 需要完整观察和成功回执同时成立。CLI 确认成功退出 0,未确认 1,输入文件无效 2。录制证明历史观察,不是当前选槽状态,也不是重放指令。
对空使用后发送中性输入
text
UseAirWithNeutralInput.prepare(actions, use, *, neutral_input,
idempotency_key, deadline)
async pair.dispatch(*, journal, timeout=10, poll_interval=.05,
before_dispatch=None) -> UseAirWithNeutralInput从 neomega_runtime.item_use_input 导入。use 必须是未启动的 ItemActions.use_air();中性输入是另一个独立 key 的动作。journal(payload) 在任何世界 IO 前保存两份完整 intent;before_dispatch(stage, intent_payload) 可在 'use' 和 'neutral' 各阶段重新检查观察。调用者持有共享锁并保存 pair.use、pair.neutral 的 ID 和回执。
neutral_input 必须完整含 Position、Pitch、Yaw、HeadYaw、MoveVector、InputMode、PlayMode、InteractionModel、Tick、Delta、InputFlags;可再同时提供 PitchRepeat、YawRepeat、InteractPitch、InteractYaw。MoveVector=[0,0]、Delta=[0,0,0]、InputFlags=[];InputMode 为 1/2/3,PlayMode 为 0,InteractionModel 为 0/1/2。tick 必须来自真实原生时钟证据,不能以等待时间推算。
这是环境特定的两步组合,不是通用弩 API。它不选槽、不自动蓄力、不释放、不重试,也不在失败后补发剩余步骤。release() 本身是独立协议动作,不是所有弩发射流程必需的步骤。