先读这份契约
脚本由 Wander 加载器提交到游戏线程,在本地玩家的 Actor::normalTick 上执行。脚本不是普通桌面 Python 程序,也不是完整网易 ModSDK 包。
mod.client.extraClientApi as clientApi;通过 GetClientSystemCls() 创建系统;在构造函数注册事件;需要周期任务时使用 Game.AddTimer 或 AddRepeatedTimer;退出时实现 Destroy() 并取消计时器。CreateChat 在当前实测构建中不存在。聊天/提示优先使用 CreateTextNotifyClient(...).SetLeftCornerNotify 或 Game.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())脚本生命周期
Destroy(),再创建新实例。Destroy、destroy 或 UnListenAllEvents。print 和异常会进入 Wander 注入日志;单次输出有长度上限。def Destroy(self):
self.stop_timer()
try:
self.UnListenAllEvents()
except Exception:
pass
try:
super(MySystem, self).Destroy()
except Exception:
passclientApi 模块
返回客户端系统基类。脚本通常定义 class MySystem(ClientSystem)。
返回当前客户端关卡 ID。传给 CreateGame(level_id)、CreateTextNotifyClient(level_id) 等组件。
返回本地玩家实体 ID。用于 CreatePos、CreateRot、实体查询等。
返回组件工厂。不要缓存不存在的组件名;建议使用 getattr(factory, "CreateX", None) 探测可选接口。
返回事件命名空间,和 GetEngineSystemName() 一起用于注册引擎事件。该调用在现有脚本中使用,具体返回值不应写死。
返回事件系统名。与上一个 API 成对使用。
返回枚举容器。已使用 .EntityType 和 .AttrType.HEALTH;枚举成员因游戏版本可能不同,使用 getattr。
事件系统
客户端系统继承自 ModSDK 系统基类,因此事件注册方法来自 self,不是 clientApi。
注册事件。当前脚本实测事件:
| 事件名 | 用途 | 常见 args |
|---|---|---|
ClickChatSendClientEvent | 客户端发送聊天/命令前拦截 | message、可写 cancel、message |
OnScriptTickClient | 客户端脚本 tick | 版本相关,先打印 args |
OnCommandOutputClientEvent | 监听命令输出 | 版本相关,先打印 args |
清理当前系统注册的事件。放在 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) | SetTipMessage、AddTimer、AddRepeatedTimer、CancelTimer、GetEntitiesAroundByType、可选 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 组件详解
显示顶部/居中的短提示。清空可传入空字符串。
一次性计时器。回调不接收参数,具体线程行为由游戏版本决定。
重复计时器。保存返回值,停止时调用 CancelTimer(timer_id)。
取消一次性或重复计时器。对空值调用前先判断。
按枚举类型查询附近实体。返回实体 ID 列表或空列表;需要去重,并排除本地玩家。
模拟当前准星点击。不是“攻击指定实体”接口,必须先让准星对准目标;缺失时应优雅降级。
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 = Nonegetattr、捕获异常,并给出“接口不可用”的提示,不要因为单个枚举缺失让整个脚本崩溃。命令与权限
加载器没有独立的“万能命令执行 API”。现有坐骑脚本通过修改 ClickChatSendClientEvent 的 args["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 和事件清理逻辑。