Wander Python Loader API Guide

面向 AI 和脚本作者的单文件参考。目标环境:Wander 注入的 Minecraft Bedrock 网易客户端 Python 2 运行时。

先读这份契约

脚本由 Wander 加载器提交到游戏线程,在本地玩家的 Actor::normalTick 上执行。脚本不是普通桌面 Python 程序,也不是完整网易 ModSDK 包。

Python 2 语法UTF-8单文件脚本接口随游戏版本变化
给 AI 的生成规则:默认使用 Python 2 兼容语法;导入 mod.client.extraClientApi as clientApi;通过 GetClientSystemCls() 创建系统;在构造函数注册事件;需要周期任务时使用 Game.AddTimerAddRepeatedTimer;退出时实现 Destroy() 并取消计时器。
不要臆造接口:CreateChat 在当前实测构建中不存在。聊天/提示优先使用 CreateTextNotifyClient(...).SetLeftCornerNotifyGame.SetTipMessage。只有本文标记为“实测”的接口才应直接生成。

最小可运行脚本

选择脚本后,加载器会在游戏线程执行一次。下面脚本注册聊天事件,输入 /wander 时显示提示。

# -*- coding: utf-8 -*-
from __future__ import print_function
import traceback
import mod.client.extraClientApi as clientApi

ClientSystem = clientApi.GetClientSystemCls()

class ExampleSystem(ClientSystem):
    def __init__(self, namespace, system_name):
        super(ExampleSystem, self).__init__(namespace, system_name)
        self.level_id = clientApi.GetLevelId()
        self.factory = clientApi.GetEngineCompFactory()
        self.game = self.factory.CreateGame(self.level_id)
        self.ListenForEvent(
            clientApi.GetEngineNamespace(),
            clientApi.GetEngineSystemName(),
            "ClickChatSendClientEvent", self, self.on_chat)
        self.notify(u"§aWander 示例脚本已加载")

    def notify(self, message):
        try:
            self.factory.CreateTextNotifyClient(self.level_id).SetLeftCornerNotify(message)
        except Exception:
            self.game.SetTipMessage(message)

    def on_chat(self, args):
        if args.get("message", "").strip() == "/wander":
            args["cancel"] = True
            args["message"] = ""
            self.notify(u"§b脚本正在运行")

    def Destroy(self):
        try:
            self.UnListenAllEvents()
        except Exception:
            pass
        try:
            super(ExampleSystem, self).Destroy()
        except Exception:
            pass

try:
    old = globals().get("_wander_example_instance")
    if old is not None:
        old.Destroy()
    _wander_example_instance = ExampleSystem(
        clientApi.GetEngineNamespace(), "WanderExampleSystem")
except Exception:
    print("[WanderExample] load failed:")
    print(traceback.format_exc())

脚本生命周期

加载源代码入队后,等待本地玩家 tick;不是选择文件的瞬间执行。
重载同一全局对象名先调用 Destroy(),再创建新实例。
停止全部加载器会调用全局对象的 DestroydestroyUnListenAllEvents
输出print 和异常会进入 Wander 注入日志;单次输出有长度上限。
def Destroy(self):
    self.stop_timer()
    try:
        self.UnListenAllEvents()
    except Exception:
        pass
    try:
        super(MySystem, self).Destroy()
    except Exception:
        pass

clientApi 模块

clientApi.GetClientSystemCls() - 实测

返回客户端系统基类。脚本通常定义 class MySystem(ClientSystem)

clientApi.GetLevelId() - 实测

返回当前客户端关卡 ID。传给 CreateGame(level_id)CreateTextNotifyClient(level_id) 等组件。

clientApi.GetLocalPlayerId() - 实测

返回本地玩家实体 ID。用于 CreatePosCreateRot、实体查询等。

clientApi.GetEngineCompFactory() - 实测

返回组件工厂。不要缓存不存在的组件名;建议使用 getattr(factory, "CreateX", None) 探测可选接口。

clientApi.GetEngineNamespace()

返回事件命名空间,和 GetEngineSystemName() 一起用于注册引擎事件。该调用在现有脚本中使用,具体返回值不应写死。

clientApi.GetEngineSystemName()

返回事件系统名。与上一个 API 成对使用。

clientApi.GetMinecraftEnum() - 实测于杀戮光环脚本

返回枚举容器。已使用 .EntityType.AttrType.HEALTH;枚举成员因游戏版本可能不同,使用 getattr

事件系统

客户端系统继承自 ModSDK 系统基类,因此事件注册方法来自 self,不是 clientApi

self.ListenForEvent(namespace, system_name, event_name, owner, callback)

注册事件。当前脚本实测事件:

事件名用途常见 args
ClickChatSendClientEvent客户端发送聊天/命令前拦截message、可写 cancelmessage
OnScriptTickClient客户端脚本 tick版本相关,先打印 args
OnCommandOutputClientEvent监听命令输出版本相关,先打印 args
self.UnListenAllEvents()

清理当前系统注册的事件。放在 Destroy 中。

def on_chat(self, args):
    message = args.get("message", "").strip()
    if message != "/demo":
        return
    args["cancel"] = True
    args["message"] = ""
    self.notify(u"命令已被脚本处理")

组件工厂与已确认组件

调用形式统一为 factory.CreateX(entity_or_level_id)。以下名称来自现有脚本和实际日志,不代表所有客户端版本都支持。

创建方法常用方法对象状态
CreateGame(level_id)SetTipMessageAddTimerAddRepeatedTimerCancelTimerGetEntitiesAroundByType、可选 SimulateClick游戏/计时器实测
CreateTextNotifyClient(level_id)SetLeftCornerNotify(message)左下角提示实测
CreatePos(entity_id)GetPos()实体坐标实测
CreateRot(entity_id)SetRot((pitch, yaw))实体朝向目标版本相关
CreateName(entity_id)GetName()实体名称实测
CreateEngineType(entity_id)GetEngineTypeStr()实体类型字符串实测
CreateAttr(entity_id)GetAttrValue(enum)属性目标版本相关
CreateAction(entity_id)PlayAnim("attack")动作目标版本相关
CreateBlockInfo(level_id)GetBlock((x,y,z))方块信息建筑脚本实测
明确不存在/不要生成:当前日志中 EngineCompFactoryClient 没有 CreateChat。需要聊天反馈时使用通知或提示组件;服务器命令应通过游戏命令路径,并考虑权限。

Game 组件详解

game.SetTipMessage(message)

显示顶部/居中的短提示。清空可传入空字符串。

timer_id = game.AddTimer(seconds, callback)

一次性计时器。回调不接收参数,具体线程行为由游戏版本决定。

timer_id = game.AddRepeatedTimer(seconds, callback)

重复计时器。保存返回值,停止时调用 CancelTimer(timer_id)

game.CancelTimer(timer_id)

取消一次性或重复计时器。对空值调用前先判断。

game.GetEntitiesAroundByType(entity_id, radius, entity_type)

按枚举类型查询附近实体。返回实体 ID 列表或空列表;需要去重,并排除本地玩家。

game.SimulateClick() - 可选

模拟当前准星点击。不是“攻击指定实体”接口,必须先让准星对准目标;缺失时应优雅降级。

def start(self):
    self.timer_id = self.game.AddRepeatedTimer(0.5, self.tick)

def stop(self):
    if self.timer_id is not None:
        self.game.CancelTimer(self.timer_id)
        self.timer_id = None

玩家与实体

player_id = clientApi.GetLocalPlayerId()
pos_comp = factory.CreatePos(player_id)
pos = pos_comp.GetPos()          # 通常为 (x, y, z)
rot_comp = factory.CreateRot(player_id)
rot_comp.SetRot((pitch, yaw))    # 目标版本必须支持
name = factory.CreateName(entity_id).GetName()
kind = factory.CreateEngineType(entity_id).GetEngineTypeStr()

属性查询示例:

try:
    enums = clientApi.GetMinecraftEnum()
    health_enum = getattr(enums.AttrType, "HEALTH", None)
    if health_enum is not None:
        health = factory.CreateAttr(entity_id).GetAttrValue(health_enum)
except Exception:
    health = None
实体类型枚举不是稳定 ABI。使用 getattr、捕获异常,并给出“接口不可用”的提示,不要因为单个枚举缺失让整个脚本崩溃。

命令与权限

加载器没有独立的“万能命令执行 API”。现有坐骑脚本通过修改 ClickChatSendClientEventargs["message"] 让游戏正常处理命令;建筑脚本还使用游戏内部命令/RPC,这些属于高风险、版本相关能力。

def replace_with_command(self, args, command):
    # 让游戏继续处理重写后的命令;不要伪造不存在的组件。
    args["cancel"] = False
    args["message"] = command

def cancel_command(self, args):
    args["cancel"] = True
    args["message"] = ""
权限:/summon/ride、建筑导入等命令通常需要房主、作弊或服务器权限。脚本只能发送/重写请求,不能保证服务器接受。

限制、排错与兼容性

项目当前行为建议
Python 版本嵌入式 Python 2 风格执行;日志包装使用 Python 2 语法。使用 print_function,避免 f-string、async/await、类型注解。
文件编码脚本按 UTF-8 提交。首行写 # -*- coding: utf-8 -*-
脚本大小加载器接受最多 256 KiB UTF-8 字节。大资源放外部文件,脚本只做逻辑。
执行时机入队后等待本地玩家 normalTick;不是立即执行。脚本入口先打印一行“loaded”,再注册事件。
节流核心调度默认约 1 秒执行一次提交。高频逻辑使用游戏计时器,但控制频率和清理计时器。
日志stdout/stderr 与 traceback 写入 Wander_inject.log先看“selected/queued/completed”,再看 Python traceback。
停止停止按钮只会在游戏线程清理已登记对象。全局保存系统实例,并实现 Destroy

诊断模板

try:
    print("[MyScript] loaded")
    print("level=%r player=%r" % (clientApi.GetLevelId(), clientApi.GetLocalPlayerId()))
    factory = clientApi.GetEngineCompFactory()
    game = factory.CreateGame(clientApi.GetLevelId())
    print("SetTipMessage=%r AddRepeatedTimer=%r" % (
        callable(getattr(game, "SetTipMessage", None)),
        callable(getattr(game, "AddRepeatedTimer", None))))
except Exception:
    import traceback
    print(traceback.format_exc())

安全边界

脚本在游戏进程中执行,具有该运行时能提供的高权限。不要加载来源不明的脚本;脚本可能读写文件、调用游戏命令、注册持续事件或拖慢游戏。
  • 当前加载器只执行用户选择/云端审核后提交的源代码,不提供沙箱。
  • 不要在脚本中打印账号密码、Cookie、访问令牌或设备码。
  • 远程资源脚本应先校验来源、版本、大小和审核状态,再交给加载器。
  • 脚本必须可停止:保存实例、计时器 ID 和事件清理逻辑。