外观
兼容运行域与语言桥接
Neomega 的插件开发主线是原生 Python SDK。公开 SDK 还保留了 Go、Lua 和 ToolDelta 的具名适配层,供已有插件的有界迁移使用。它们支持的是固定制品、固定框架版本和明确接通的接口,不承诺旧插件市场全兼容。完整 Go、Lua、ToolDelta 旧生态兼容路线已经取消;新功能以原生 Python 插件开发为主,不应等待兼容层补齐任意旧接口。
兼容层随工具包提供。运行兼容插件需准备下文指定的旧框架、插件制品及原生库,并取得目标 Host 授权;按对应适配层说明接入。
选择入口
| 需求 | 模块与入口 | 实际范围 |
|---|---|---|
| 运行已适配的 Go 组件 | go_domain.run(root) | 固定 component1、配套二进制和构建锁;单连接、独立进程。 |
| 运行已适配的 Lua 后台命令插件 | lua_domain.run(root) | 固定后台命令插件与配套二进制;支持命令菜单和执行身份切换。 |
| 运行 ToolDelta classic 插件组 | tooldelta_domain.run(root) | 固定版本 PluginGroup,每个进程一组;保留已接通的组内 API、广播、命令与生命周期。 |
| 让 ToolDelta 查询已观察玩家 | tooldelta_player_access、tooldelta_players | 以观察流维护旧 Player 对象,不登录、不自行请求完整名单。 |
| 让旧插件接收事件、包、Blob 或 NBT | tooldelta_events、tooldelta_packets、tooldelta_packet_send、tooldelta_blobs、tooldelta_native、tooldelta_item_bridge | 按下文分别启用和授权,没有一个“全部兼容”开关。 |
新插件直接使用原生接口即可。只有要保留旧插件既有调用方式时,才需要本页的运行域与桥接对象。逐项签名还可查阅工具包 SDK 索引。
共用的启动与授权约束
run(root) 是异步运行入口,由 Host 启动的 Worker 调用,root 指向已经准备好的完整兼容制品目录。运行期间占用标准输入/输出作为 IPC 通道;业务日志使用标准错误。它不是在普通 Python 控制台里连接任意服务器的函数。
候选验证检查制品与配置,不执行旧插件。通过验证后,Host 才激活运行域;安装、权限确认和启动是分开的步骤。底层动作仍经过 Neomega 的权限检查、会话检查和操作回执。兼容域没有独立登录权限,也不能绕过只读模式。
| 调用 | 必要权限或启用条件 |
|---|---|
| Go/Lua/ToolDelta 发命令 | permissions.operations 申请并授权 framework.command.execute;需要有效游戏会话。 |
| ToolDelta 仅观察玩家状态 | permissions.players 申请并授权 observe,且 Host 提供玩家观察流。 |
| Go 或 ToolDelta 混合玩家/Text 流 | Host 提供并授权玩家事件来源;现有 Host 支持 NEOMEGA_PLAYER_EVENT_INSTALLATIONS 精确安装白名单。 |
| ToolDelta 收原始包 | Host 为该安装授权所选入站包与混合观察流;选择必须匹配。发送权限不能替代观察权限。 |
ToolDelta sendPacket | framework.packet.send_json 和相应 permissions.packet_send_ids 同时申请、授权。 |
| Blob 读取/取数 | 分别依赖 NEOMEGA_BLOB_CACHE_INSTALLATIONS、NEOMEGA_BLOB_FETCH_INSTALLATIONS 的 Host 策略;两者不是同一授权。 |
| 原生 NBT 与物品桥 | 管理员提供经 SHA-256 校验的原生库;物品桥还需相应操作权限,见下文。 |
这些是不同层的条件。给 Worker 设置环境变量不会自动授予 Host 权限;manifest 中也不能虚构 tooldelta.* 或 lua.* 权限。具体声明见权限与 manifest,操作结果的处理见错误与恢复。
Go:固定组件运行域
neomega_runtime.go_domain 提供以下入口:
| 签名 | 返回与用途 |
|---|---|
validate_package(root) | 返回已验证的 go-domain 路径 Path;校验组件、二进制、来源及构建记录的一致性,不运行它们。 |
validate_config(value) | 成功返回 None,无效配置抛 ValueError。 |
await run(root) | 运行 Worker 至退出;拥有并关闭组件子进程、玩家观察流和 IPC 读取任务。 |
await Domain.invoke(peer, body) | 运行域的服务回调。body['arguments'] 必须恰含 input: str,UTF-8 不超过 65,536 字节;成功返回 {'accepted': True}。 |
制品必须匹配当前 SDK 的固定构建锁。把任意 .so 换名为 component1.so,或只提供一份 Go 源码,不构成可运行制品。激活时需要在线机器人身份与可观察玩家名单;没有这些来源时会失败,不使用虚构玩家补齐。
配置接受四个文本键 口癖、复读机、请求输入提示、启动时提示,以及两个触发词数组 终端触发词、游戏内触发词。文本各不超过 65,536 UTF-8 字节;数组各含 1–128 个非空字符串,每个不超过 256 UTF-8 字节。可选 Version 仅接受 0.0.1、0.0.2;填写它时必须同时提供两个触发词数组。
以下仅检查配置,不启动组件:
python
from neomega_runtime.go_domain import validate_config
validate_config({
'Version': '0.0.2',
'终端触发词': ['帮助'],
'游戏内触发词': ['帮助'],
})初次激活将 Host 配置写入旧组件的数据区;之后由旧加载器管理其升级后的配置。invoke 的 accepted 只表示输入已被旧分发器接受,不能当作命令执行成功。桥接断开或回执丢失时停止运行域,不自动重放输入。调用服务时使用实际制品声明的导出名,不猜测固定服务 ID。
Lua:后台命令运行域
neomega_runtime.lua_domain.validate_package(root) 返回已验证的 lua-domain 路径;await run(root) 运行并管理固定的后台命令插件。验证失败抛 ValueError;运行异常不被转换成成功。
配置只接受可选 default_identity,值为 ws、websocket、wo、setting 或 player。其他键或身份会被拒绝。服务回调为 await Domain.invoke(peer, body),其 body['arguments'] 使用如下形状:
json
{
"trigger": "cmdmod",
"arguments": ["ws"]
}cmdmod 切换执行身份;使用 trigger: "/" 时,arguments 是要传给后台命令菜单的字符串参数数组。参数最多 128 项,字符串长度合计不超过 65,536。cmdmod 拼合后的参数只能是上述五种身份之一。成功返回 {'accepted': true},表示菜单输入被接受,不是世界操作完成。
此域不订阅游戏事件。旧循环没有停止钩子,关闭时会终止并等待整个子进程退出;Host 仍负责已提交动作的最终状态和恢复。不要把 run(root) 当作任意 Lua 脚本的加载器,也不要在异常后直接重新发送同一条命令。
ToolDelta:classic 插件组
neomega_runtime.tooldelta_domain.validate_package(root) 返回解析并验证过的锁定清单 dict;await run(root) 接管一个 classic 插件组的加载、初始化和关闭。
制品包含 group.lock.json 和被逐一锁定的框架、插件文件。当前验证器允许 1–64 个插件、1–4,096 个锁定文件,单文件不超过 4 MiB、总计不超过 24 MiB。文件集合、摘要与固定框架版本必须吻合;Python 文件会做语法编译检查,不会在候选阶段导入。校验通过不代表插件调用的全部旧接口都已实现。
命令与机器人信息
域在激活时提供 Launcher(calls, session, output_factory)。旧插件通常通过其已有 frame.launcher 使用,无须自行构造。
| 方法 | 返回值与行为 |
|---|---|
sendwscmd(cmd, waitForResp=False, timeout=30) | 以 ws 身份提交命令。等待时返回原框架的 Packet_CommandOutput;不等待时返回 None。 |
sendcmd(cmd, waitForResp=False, timeout=30) | 同上,执行身份为 player。 |
sendwocmd(cmd) | 以 wo 身份提交,返回 None,不等待命令输出。 |
get_bot_name() / bot_name | 从已验证的 robot.status 读取机器人名称;离线或名称不可用时抛错。 |
blobHashHolder() | 返回已配置的旧 BlobHashHolder;未启用时抛 UnsupportedDomainAPI。 |
示例在已激活的旧插件回调中执行,需要命令权限:
python
def show_bot_name(frame):
name = frame.launcher.get_bot_name()
# 查询命令;返回对象沿用固定 ToolDelta 版本的命令输出类型。
output = frame.launcher.sendwscmd('list', waitForResp=True, timeout=10)
return name, outputNone 只保留旧接口“不等待响应”的语义,不表示服务端执行成功。旧插件如果依赖登录、任意原始字节发送、热重载或文件系统回滚模拟,会遇到不支持接口。域不是 Python 沙箱,插件组里的代码必须可信。
玩家查询与玩家对象投影
tooldelta_player_access.make_player_maintainer(frame) 在激活阶段构造带同步查询边界的旧玩家管理器。tooldelta_players.ToolDeltaPlayers(maintainer, snapshot) 把初始观察快照投影为旧 Player 对象;管理器必须是尚未接入其他名单来源的新实例。
| 入口 | 用途与返回 |
|---|---|
maintainer.getAllPlayers()、迭代管理器 | 查询当前已观察玩家;集合来自观察,不保证是全服完整名单。 |
getBotInfo()、getPlayerByName(...)、getPlayerByUniqueID(...)、getPlayerByUUID(...)、getPlayerByXUID(...)、getPlayerByDeviceID(...)、getPlayerByRuntimeID(...) | 保留固定框架的查询参数与返回行为,并串行保护查询过程;缺失身份信息不能靠推测补齐。 |
get_player_ability(player) | 返回旧框架 Abilities 对象;最多等待 20 秒。玩家已移除、关闭、排空或等待超时会失败。 |
projection.apply(observation, *, epoch) | 按序应用观察文档,成功返回 None;校验 epoch 与修订连续性。 |
projection.close() | 关闭投影并将保留的玩家引用标记离线、清理名单;不发离服历史事件。 |
下面是自定义桥接所有者的调用顺序;snapshot、observation 来自已授权的原生观察流,不由插件编造:
python
from neomega_runtime.tooldelta_player_access import make_player_maintainer
from neomega_runtime.tooldelta_players import ToolDeltaPlayers
def project_one_update(frame, snapshot, observation):
maintainer = make_player_maintainer(frame)
projection = ToolDeltaPlayers(maintainer, snapshot)
try:
projection.apply(observation, epoch=snapshot['epoch'])
# 在投影存活期间使用查询结果,不修改玩家管理器字典。
return len(maintainer.getAllPlayers())
finally:
projection.close()ToolDeltaPlayers 不拥有订阅或后台线程。调用方必须串行应用文档并在结束时关闭;由 tooldelta_domain.run 托管时这些工作由运行域完成。相同 UUID 和 unique ID 的玩家保留对象身份,但返回的是持续更新的对象引用,不是多个字段的原子快照。不要直接修改管理器字典。
block_init()、hook_packet_handler(...)、add_player(...)、remove_player(...) 会明确拒绝:观察流不能证明完整初始名单,也不能与另一条名单写入来源混用。
玩家事件与 Text
tooldelta_events.ToolDeltaEvents(group, snapshot) 在已有插件组上安装玩家投影;apply(event, *, epoch) 同步处理下一项混合事件,成功返回 None;close() 关闭投影。
处理玩家变更时先更新玩家对象,再调用旧组的 PlayerList 语义处理器;Text 转换后交给旧文本处理器。初始快照不制造历史入服回调。这里的 PlayerList 是保留所需语义的最小结构,不能当作完整原始协议包。
若由自己的桥接代码持有已加载的 group,同步处理一项事件可写为:
python
from neomega_runtime.tooldelta_events import ToolDeltaEvents
def dispatch_one_event(group, snapshot, event):
dispatcher = ToolDeltaEvents(group, snapshot)
try:
dispatcher.apply(event, epoch=snapshot['epoch'])
finally:
dispatcher.close()此示例的 group.linked_frame.players_maintainer 必须尚未接入其他投影,event 必须是此快照之后的第一项事件。正常运行应保留同一个分发器持续处理后续事件,不能每一项重建。
每次回调真正结束后才能推进下一项;调用方不得并发或重入 apply,也不得从回调内调用 close()。序号、epoch、修订或回调异常会使分发器关闭,不能跳过坏事件继续伪造连续历史。
域支持三种互斥的观察接入方式:
| Worker 侧设置 | 行为 |
|---|---|
NEOMEGA_TOOLDELTA_PLAYER_OBSERVATIONS=1 | 仅维护玩家观察对象。 |
NEOMEGA_TOOLDELTA_PLAYER_EVENTS=1 | 维护玩家对象并分发混合玩家/Text 语义事件。 |
NEOMEGA_TOOLDELTA_ORIGINAL_PACKET_IDS | 逗号分隔的 1–3 个不同包 ID;使用下面的原始包桥,并要求 Host 的选择完全一致。 |
这些设置不能叠加。原生 OriginalPackets 允许动态选择最多 32 个包 ID,并不意味着此兼容域的环境变量也接受 32 个。
原始包回调和等待
tooldelta_packets 在同一有序流上投影玩家并分发旧包回调:
| 签名 | 用途 |
|---|---|
make_packet_handler(frame, packet_ids) | 创建只接受已选包号的同步处理器。 |
hook_group_packets(group, handler) | 按旧组注册顺序安装包监听器;未选中的监听类型会失败。 |
ToolDeltaPackets(group, snapshot, packet_handler) | 混合分发器,使用 apply(event, *, epoch)、close()。 |
decode_dictionary(data) | 把受限 UTF-8 JSON bytes 解为 dict;无效输入抛 ProtocolError。 |
OriginalPacketPump.open(peer, session, install=..., apply=..., reader_options=...) | 异步创建并拥有读取循环,需监督 wait() 并等待 close()。 |
处理器支持 add_dict_packet_listener(packet_id, cb, priority=0)、add_bytes_packet_listener(packet_id, cb, priority=0) 和 wait_next_packet(packet_id, timeout=30)。等待只适用于已选 JSON 字典包;不能在同步包回调里等待下一包,否则会阻塞正在负责交付包的线程。
在域已经配置的处理器上注册字典监听器:
python
def attach_text_listener(frame, packet_id, callback):
# packet_id 须已由 Host 授权,且列在本域选择的 JSON 字典包中。
frame.packet_handler.add_dict_packet_listener(packet_id, callback, priority=0)callback 收到旧字典结构,不在回调内调用 wait_next_packet。只需要解码已取得的 JSON bytes 时,可以单独调用 decode_dictionary(b'{"Message":"hello"}');这一步不访问 Host,也不获得包观察权限。
LevelChunk、SubChunk、StructureTemplateDataResponse 使用旧框架的定制二进制表示;其他已支持包使用 JSON 字典。这些 bytes 不是网络原始帧。每包数据最多 1 MiB;严格 JSON 解码拒绝重复字段、非有限数字、过深结构和非字典根值。SubChunkRequest 是请求包,不作为此入站来源支持项。
出站包转换
tooldelta_packet_send.packet_identifier(packet_id) 接受整数或固定框架的 PacketIDS 枚举,返回整数。packet_dictionary(packet_id, packet) 返回原字典,或把固定类型的 SubChunkRequest 转换为字典;不调用任意对象的序列化方法。
SubChunkRequest 的转换限制为:维度 0..255,子区块 X/Z 为有符号 32 位、Y 为有符号 16 位,offset 最多 4,096 项,每项为三个 -128..127 整数。
Launcher.sendPacket(pkID, pk) 使用上述转换,随后通过 JSONPackets 提交;返回 None,仅表示提交被接受。bytes、bytearray、memoryview 以及任意其他类型的旧包对象均不支持;启用固定物品桥后,该入口也会拒绝绕过物品桥的提交。原生 SDK 的其他发包能力不会自动成为旧 launcher 的能力。
下面的转换示例不发包,不需要 Host 权限:
python
from neomega_runtime.tooldelta_packet_send import packet_dictionary
payload = {'Message': 'hello'}
assert packet_dictionary(9, payload) is payload
# 真正发送还需要匹配当前协议的完整字段与 packet_send_ids 授权。Blob 读取桥
tooldelta_blobs.ThreadBlobReads(peer, session, *, max_pending=4) 为旧 BlobHashHolder 提供同步读取边界。必须在 IPC 事件循环上构造和 await aclose();同步方法必须从插件线程调用。max_pending 允许 1–16。
| 方法 | 返回和限制 |
|---|---|
load_blob_cache(hash, *, timeout=45) | 返回缓存 bytes,缓存未命中返回 b'';hash 必须是 0..2**64-1 的整数。不会主动请求缺失缓存。 |
soft_call_with_bytes(api, args, *, timeout=30) | 仅支持 api='blob-hash-get-hash-payload',返回 Blob payload bytes;其他 RPC 抛 NotImplementedError。 |
update_blob_cache(hash, payload) | 返回 bool;只核对本桥之前取回的长度/摘要回执,不能写入任意缓存。 |
owned_threads() | 返回所拥有的传输线程集合,供域关闭时识别与等待。 |
await aclose() | 停止接收新请求,等已拥有的取数与传输真正结束后关闭。 |
自定义桥接所有者可以把同步读取送到线程,保持 IPC 循环可用:
python
import asyncio
from neomega_runtime.tooldelta_blobs import ThreadBlobReads
async def read_one_cached_blob(peer, session, hash_value):
reads = ThreadBlobReads(peer, session)
try:
return await asyncio.to_thread(reads.load_blob_cache, hash_value)
finally:
await reads.aclose()peer 和 session 来自当前已激活 Worker,hash_value 来自真实观察;示例不会主动请求未缓存区块。
Host 缓存和 Blob payload 来源均需独立授权;Worker 中的 NEOMEGA_TOOLDELTA_BLOB_CACHE=1 只是接线开关。调用方超时不取消底层取数,也不提前释放其所有权。update_blob_cache 返回 True 不是世界数据已写入的证明。
原生 NBT 与物品制作桥
NBT 动态库
tooldelta_native 只连接管理员提供的可信 Linux C ABI 库:
| 签名 | 返回与行为 |
|---|---|
load_verified_library(path, digest) | 校验绝对路径、普通文件、大小和 SHA-256 后返回 ctypes.CDLL。文件上限 128 MiB。 |
load_nbt_library(path, digest) | 进一步绑定 TranslateChunkNBT、FreeMem 的固定签名,发布到旧框架,再返回库对象。 |
bind_from_env(environ) | 从 NEOMEGA_TOOLDELTA_NBT_LIBRARY 和 NEOMEGA_TOOLDELTA_NBT_SHA256 读取配置;都为空返回 None,只设置一个会失败。 |
仅检查并加载已由管理员设置的 NBT 库,使用现有入口即可:
python
import os
from neomega_runtime.tooldelta_native import bind_from_env
def activate_nbt_binding():
return bind_from_env(os.environ)此调用会加载原生代码,只应在激活阶段使用;不要放入候选验证或普通新插件的模块顶层。
摘要必须是 64 位小写十六进制。加载过程中可信路径必须保持不可变;检查摘要不构成原生代码沙箱,也不限制库自己的内存分配。库由进程拥有,不能在旧插件仍持有函数指针时卸载。仅绑定 NBT 转换,不启动旧 launcher 或建立新游戏连接。
固定物品制作调用链
tooldelta_item_bridge 接通固定的铁砧命名/丢弃物品调用链。它要求上述 NBT 库,以及独立经摘要校验的 shim;对应设置为 NEOMEGA_TOOLDELTA_ITEM_BRIDGE_LIBRARY 和 NEOMEGA_TOOLDELTA_ITEM_BRIDGE_SHA256,二者必须同时提供。
| 签名 | 返回与职责 |
|---|---|
NativeItemBridge(path, digest, original_library, rename) | 创建桥;library 保留真实 ctypes 入口。finalize_declarations() 在旧插件初始化后恢复已检查的 ABI 声明;close() 禁用回调。 |
ManagedItemCalls(peer, session, data, output_factory) | 创建同步命令与物品动作所有者,在 data 下持久化动作意图和恢复状态。 |
command(cmd, identity, wait, timeout) | 等待时返回旧命令输出对象,否则 None;timeout 必须大于 0 且不超过 300 秒。 |
rename(position, runtime_id, slot, name) | 先取得新鲜库存引用,再提交命名/丢弃动作;只在动作成功且服务端确认时正常返回 None。 |
bind_item_from_env(environ, original_library, calls) | 启用并发布桥;未配置时返回 None,否则返回 NativeItemBridge。 |
操作链需要 framework.command.execute、framework.self.inventory.observe、framework.compat.item.rename_drop 中实际使用的权限。旧 RenameItemWithAnvil ABI 的坐标为有符号 32 位、block runtime ID 为无符号 32 位、快捷栏槽位为 0..8、名字为最多 512 字节且不含 NUL 的有效 UTF-8 bytes。返回的 C 字符串被复制后释放一次;它不是插件可以长期保留或再次释放的原生指针。
已经由运行域创建 calls: ManagedItemCalls 后,桥接作者可以调用:
python
def rename_observed_hotbar_item(calls, position, block_runtime_id, slot, name):
# 输入来自已确认的目标与当前观察;该调用会修改并丢弃物品。
calls.rename(position, block_runtime_id, slot, name)这段是有副作用的调用方式说明,不是可直接运行的演示;calls、位置、方块类型与槽位必须由实际授权的工作流提供。普通插件不自行构造此兼容链。
此链必须从原插件线程执行。同一时刻只能有一个动作;跨命令与命名阶段的工作流保留线程所有权。超时、未确认结果或上次未完成记录会保留恢复围栏,后续调用抛 ItemBridgeRecoveryRequired。必须核对原动作后恢复,不能删除状态文件或换一套 ID 自动再做一次。
桥接代码存在不代表任意物品制作插件已经适配成功,也不代表完整 NBT 编辑平台已经开放。新插件使用库存与容器、物品动作中的原生接口,并按各自的结果与证据契约判断成功。
迁移前核对
- 明确旧插件及框架版本、实际调用的 API、所需原生库;与完整制品的锁定版本匹配。
- 逐项检查来源、操作和包号授权;
unsupported代表当前路径未接通,不能靠增加无关权限修复。 - 用最小功能验证加载、命令结果、数据读取和正常退出;将离线检查与目标服务器行为分开记录。
- 保留操作 ID、原始意图与错误结果。
accepted、None、发包完成都不能替代服务端效果确认。 - 对未覆盖的旧 API 选择原生接口重写或明确记录不支持,不补造回调、玩家字段或成功回执。
原生语言路径
Go 宿主模块通过 Frame 接入已有 Host;驻留 Lua在 Host 内消费授权输入并发布 View。这两条路径与本页固定组件桥不同,不能用固定桥的限制概括所有 Go/Lua 能力。公开工具包还提供独立 Go Worker;其 SDK 不依赖私有 Host 源码,制品须匹配目标平台。