JS 公共 API

速记约定

  • static 前缀 = 直接以类名调用(Resources.id(...));没有它就需要先拿到实例(通常来自其他函数的返回值)。
  • 类型标注按 Java 书写:str: String 收 Java 字符串(JS 字符串自动转换);返回 String? 表示可能为 null;String... 表示可变个数的字符串参数。
  • 标 已弃用 的项是 NTE 时代的兼容接口,新脚本别再用;标 晏阳 的项是本分支自有扩展。

资源与文件

Identifier(资源路径)

Minecraft 用 命名空间:路径 形式的 Identifier 定位资源。自 JCM v2.3 起,大多数收 Identifier 的地方也能直接给普通字符串:完整形式 "mtr:asd.png",相对当前脚本目录的路径加 ./ 前缀(等价 idr())。

函数说明
static Resources.id(idStr: String): Identifier由字符串创建 Identifier
static Resources.idr(relPath: String): Identifier相对当前脚本所在目录解析;只能在解析阶段用(运行期没有「当前脚本」概念)
static Resources.id(id: Identifier, suffix: String): Identifier在已有 Identifier 后追加后缀——解析期用 idr() 记下基准,运行期再拼路径

Resources(资源包读取)

从磁盘读资源比内存操作慢得多:能在解析阶段读好的内容别放进 render() 每帧重读。

函数说明
static Resources.read(id): DataReader?读取资源包文件,返回 DataReader;文件不存在返回 null
static Resources.exist(id): boolean文件是否存在
static Resources.getSystemFont(name: String): Font取 AWT 字体。内置项:Noto Serif(CJK 衬线)、Noto Sans SemiBold、Noto Sans CJK TC Medium;另收 Serif / SansSerif / Monospaced(依玩家机器已装字体而定)
static Resources.hasSystemFont(name: String): boolean系统里是否存在该字体族(不做内置项特判)
static Resources.getFontRenderContext(): FontRenderContextAWT 文字度量用的上下文
static Resources.ensureStrFonts(text, primaryFont, fallbackFonts...): AttributedString按主字体+后备字体链组装文本,主字体缺字时自动回退——多语言混排防「豆腐块」用
static Resources.getAddonVersion(modId: String): String取模组版本:"mtr" / "jcm" / "mtrscripting"(车辆 / 装饰物 / 电梯脚本查 mtrscripting,PIDS 查 jcm)
已弃用说明
Resources.readString(id) / readBufferedImage(id) / readFont(id)统一改用 read() + DataReader
Resources.manager() / Resources.mgr()MTR 4 跨版本框架不暴露,恒返回 null;需要它的函数也接受 null
Resources.getMTRVersion() 等 NTE 版本函数改用 getAddonVersion();NTE 系列恒返回旧值只为兼容

Files(本地文件)

只能在 .minecraft 内活动:读 + 写 + 删限 .minecraft/data/mtrscripting(脚本数据目录),读放开到整个 .minecraft。路径以多个参数传递,不要带斜杠:Files.read("config", "mtr.json")。

函数说明
static Files.read(path: String...): DataReader?读 .minecraft 下的文件,不存在返回 null
static Files.readData(path: String...): DataReader?同上,但相对 data/mtrscripting
static Files.saveData(content: String | BufferedImage, path: String...): void保存文本或图片;父目录自动创建
static Files.deleteData(path: String...): void删除文件
static Files.hasData(path: String...): boolean文件 / 目录是否存在

数学与变换

Vector3f(三维向量)

坐标 (x, y, z)。JCM 实现底层是 double、对外收放 float,运算方法都返回自身可链式调用:

new Vector3f(x, y, z)(也可由 TSC 的 Vector / Position 构造)· x() y() z() · copy() · normalize() · add(x,y,z) / add(v) · sub(v) · mul(x,y,z) / mul(n) · rotX(rad) / rotY(rad) / rotZ(rad)(弧度)· cross(v) · distance(v) · distanceSq(v) · rawBlockPos()。常量 Vector3f.ZERO / XP / YP / ZP——只读,别改动。

Matrices(变换栈)

渲染常需要「画完这一段、回到之前的变换」。Matrices 是一个变换栈,push 与 pop 成对使用:

new Matrices()(初始为单位矩阵)· translate(x,y,z) · rotateX/Y/Z(rad)(弧度)· rotateX/Y/ZDegrees(deg)(角度)· pushPose()(保存当前状态)· popPose()(恢复上次保存)· popPushPose()(先恢复再保存)。

渲染

脚本在后台线程跑,渲染在主线程——因此绘制走绘制指令(draw call)模式:你在 render() 里把「怎么画」提交给 RenderManager,JCM 记住这批指令,直到你下一次执行前一直按它画。旧指令在每次脚本执行后被清空,想持续显示就得每轮重新提交;但执行慢导致漏帧也不怕——JCM 会沿用上一批指令直到你交出新的。

RenderManager

由各类型 ctx.getRenderManager() 获得,自带一个「基础变换」(装饰物 = 方块位置 + GUI 位移与旋转;车辆按车厢……各类型见 脚本类型页),一切提交相对它绘制:

函数说明
drawModel(model: Model, matrices?: Matrices)提交模型;可选附加变换
drawModel(model, matrices?, light: int)同上,自定义光照(MinecraftClient.lightAt 取值)
drawModel(holder: DynamicModelHolder, matrices?: Matrices)直接提交动态模型持有器——内部自动处理「下一帧才传完」的空档,避免闪烁
draw(call: DrawCall)提交裸绘制指令(QuadDrawCall 等)

QuadDrawCall(四边形)

画一个四边形面,可指定纹理、UV、染色、光照——常拿来贴动态纹理模拟屏幕:

static QuadDrawCall.create()(可带注释参数)· quad(c1, c2, c3, c4) 或 corner1..4(pos)(四个 Vector3f)· renderType(str) · color(rgb) · texture(id) · uv(u1,v1,u2,v2)(0–1)· matrices(m)。

renderType 取值:EXTERIOR(车外漆面,受位置光照,默认)、EXTERIOR_TRANSLUCENT、INTERIOR(车内,全亮带面着色)、INTERIOR_TRANSLUCENT、LIGHT(全亮无阴影,适合 LCD)、LIGHT_TRANSLUCENT。模型侧另有 ALWAYS_ON_LIGHT。

模型

支持 WaveFront OBJ 与 Metasequoia(.mqo);Blockbench 的 .bbmodel 不支持。Blender / Blockbench 导出的 OBJ 需要翻转 V 轴(flipV = true,默认值),Metasequoia 导出的要显式传 false。

类要点
ModelManagerloadModel(id, flipV=true): RawModel、loadModelParts(id, flipV=true): Map<String, RawModel>(按对象组拆分)、upload(rawModel): Model(上传 GPU;仅解析阶段——运行期用 DynamicModelHolder)
RawModel顶点数据容器:append(mesh|model)、applyTranslation(x,y,z)、applyRotation(dir, angleDeg)、applyScale(x,y,z)、applyMirror(x,y,z)、applyUVMirror(u,v)、setAllRenderType(str)、copy()、replaceTexture(fileName, id)、replaceAllTexture(id)
Model已上传 GPU:顶点不可再改,但仍可 replaceTexture(...) / replaceAllTexture(...) / copyForMaterialChanges() / close()
RawMeshBuilder代码建模型:new RawMeshBuilder(faceSize, renderType, textureId) 后 vertex(pos) → normal(x,y,z) → uv(u,v) → endVertex()(可加 color(r,g,b,a)),getMesh() 或 asRawModel() 收工
DynamicModelHolder运行期上传:uploadLater(rawModel)(下一帧在渲染线程上传)、getUploadedModel()(可能为旧模型或 null,优先直接把持有器交给 drawModel)、close()
提示

运行期的简单变换(平移 / 旋转 / 缩放)用 Matrices 传给 drawModel,别每帧改顶点再重上传——后者性能差几个数量级。

动态纹理

GraphicsTexture

固定分辨率的虚拟纹理 + Java AWT 画布,画完上传给游戏显示——车内外屏幕、LED 显示牌都靠它:

成员说明
new GraphicsTexture(width, height)创建。逐块屏 / 逐车的内容请在 create 里建并存进 state
.identifier该纹理的 Identifier——交给 QuadDrawCall.texture() 或替换模型贴图
.bufferedImage / .graphicsAWT BufferedImage 画布与其 Graphics2D(默认只开文字抗锯齿)
.fullAA() / .noAA()全抗锯齿 / 全关(点阵屏常用 noAA)
.upload()把整张图上传显存。开销不小——配合 RateLimit 降频(如 10 Hz)、远处不更新
.upload(x, y, width, height)只上传子区域,旧内容保留。参数形式 2:upload(dstX, dstY, srcX, srcY, w, h);还可直接传外部 BufferedImage:upload(img, dstX, dstY, srcX, srcY, w, h)
.close()释放显存。在 create 里建就必须在 dispose 里关,否则泄漏
晏阳 .uploadRawABGR(bytes, dstX, dstY, w, h, lut)把 BGRA 顺序的字节行阵直写进纹理再只上传该矩形——供逐像素高频更新省带宽,详见 晏阳扩展

AWT 侧常用:Color.decode / new Color、graphics.setColor / setFont / setStroke、drawRect / fillRect / drawRoundRect / fillRoundRect、drawImage / drawString、Font.deriveFont、GradientPaint、AffineTransform、setClip、AlphaComposite;用 Rhino 的 importPackage(java.awt) 引入。教程看 Graphics2D 的 JavaDoc。

注意

新建的纹理内容不保证干净(可能复用旧内存显示花屏)——创建后先 upload() 一次刷成自己的内容。

车辆多屏需求可改用内置的 DisplayHelper(include(Resources.id("mtrsteamloco:scripts/display_helper.js"))):一张大纹理切槽位、模型随配置自动生成,见 脚本类型页。

声音

SoundManager

各类型 ctx.getSoundManager() 获得,基准位置随类型而定:

playLocalSound(id, volume, pitch, category="MASTER")(无定位,报站类)· playSound(id, pos, volume, pitch, category="MASTER")(世界定位,相对基准位置)· play(soundInstance) / stop(soundInstance)(可控制实例)。

TickableSoundInstance

一次性音效播完就没法再管;需要中途调音量 / 打断的场景用可控制实例(车载复杂报站、电机音、循环音乐):

static create(id, category) · setSoundVolume(v) / setSoundPitch(v)(1.0 为基准)· setRepeatable(b) · setLoopDelay(tick) · setRelative(b)(跟随玩家恒定音量)· setPos(v)(循环声音必须自己逐帧更新位置)· isPlaying()。播放由 play() / play(delayTick) / play(ctx) 驱动,abort() 可中途切断(上游 v2.3.0-prerelease.3 重构)。循环实例务必在 dispose 里 stop。

已弃用:setLoopable(b)——v2.3.0-prerelease.3 重构后由 setRepeatable(b) 取代;既有脚本照常运行。

声音类别:MASTER BLOCKS MUSIC RECORDS WEATHER HOSTILE NEUTRAL PLAYERS AMBIENT VOICE。

网络与后台

Networking

static Networking.fetch(url, requestOption?): NetworkResponse<DataReader>。RequestOption 是普通 JS 对象:method(默认 GET)、connectTimeout / readTimeout(毫秒)、headers(对象)、body(字符串,自动带 Content-Length)。

NetworkResponse:getResponseCode() · success()(拿到响应即真——判业务成功用 ok(),即 200–299)· getData()(失败为 null)· exception() · getHeaders()。

请求默认带 User-Agent: Joban Client Mod (https://jcm.joban.org),正常途径改不掉(除非玩家关闭脚本限制)。网络请求放后台线程做(见下)。

BackgroundWorker

static BackgroundWorker.submit(task) 把 lambda 任务交给单线程后台执行器——别过度依赖,其他脚本也要用它;任务内部的异常要自己 try/catch。资源包重载时队列清空、在跑任务被中断,卡死也能自愈。

BackgroundWorker.submit(() => {
    let resp = Networking.fetch("https://example.com/data.json");
    if (resp.ok()) {
        let str = resp.getData().asString();
        // 处理后写进 state,render 里读
    }
});

控制台

print(...) 与 console.log(...) 等价(NTE 遗产,随便用)。Console API 对齐 Web 惯例但不含表格 / profile 类方法:

console.log / warn / error / debug(...)(debug 仅调试模式输出)· console.count(label="default") / countReset(label)(计数器按标签独立)· console.time(label) / timeLog(label) / timeEnd(label)(计时器按标签独立)。占位符插值不支持,模板字符串够用。

MTR 数据

MTR.ClientConfig(玩家偏好)

isChatAnnouncementEnabled() · isTtsAnnouncementEnabled() · shouldHideTranslucentParts() · getLanguageDisplay()(NORMAL / CJK_ONLY / NON_CJK_ONLY)· getDynamicTextureResolution()(0–8)· getVehicleOscillationMultiplier()(×10 得百分比)· getDefaultRail3D() · isCustomFontEnabled()。

MTR.Data(客户端已知数据)

MTR 4 只同步附近的数据——查不到远处不奇怪,很多函数会返回 null:

分组函数
车站 / 车厂findStation(pos) · getStation(id) · getKnownStations();findDepot(pos) · getDepot(id) · getKnownDepots()
线路getRoute(id) · getKnownRoutes() · getKnownRoutesPassingPlatform(platformId)
载具getVehicle(vehicleId) · getPlayerMountedVehicle() · getKnownVehicles() · isPlayerMounted(id) · getVehicleMountDetail(): MountDetail(ridingCarIndex(): int、相对车厢中心的 pos(): Vector3f,上游 v2.3.0-prerelease.3)
电梯getLift(id) · getPlayerMountedLift() · getKnownLifts() · getLiftFloors(tscLift): List<Floor>(TSC 电梯的楼层表,上游 v2.3.0-prerelease.3)
轨道getRailFromPath(pathData) · getKnownRails() · isRailBlocked(hexId) · getKnownBlockedRails() · getBlockedSignalColors(hexId) · getConnectedRails(pathData, front, back, excludeSelf = false) / getConnectedRails(pos)(与路径端点 / 轨道节点位置相连的轨道,上游 v2.3.0-prerelease.3)
站台 / 侧线getPlatform(id) · getKnownPlatforms() · findNearbyPlatforms(pos, radius);getSiding(id) · getKnownSidings() · findNearbySidings(pos, radius)
到站getArrivals(platformId | platformIds[]): ArrivalEntries——与 PIDS 脚本里 pids.arrivals() 同物

MTRClientData 是 MTR 内部数据类的直通引用,仅作最后手段——它没有稳定性承诺,跨版本可能变。

Minecraft 环境

MinecraftClient

worldIsRaining() · worldIsThundering() · worldIsRainingAt(pos) · worldDayTime() · blockLightAt(pos) · skyLightAt(pos) · lightAt(pos)(渲染用打包值)· lightLevelAt(pos) · renderDistance() · localPlayer() · gamePaused()(TSC 模拟不随暂停停!)· narrate(msg) · displayMessage(msg, actionBar)(也收 VanillaText)· spawnParticleInWorld(id, pos, velocity, alwaysSpawn=false) · getScoreboardScore(objective, player) · getWorldPlayers() · getCurrentWorldId() · getCameraMode() · getRedstoneLevel(pos) · getWorldBlockState(pos, stateName)。

VanillaText(原版富文本)

构造聊天 / 覆盖层用的原版文本组件:literal(str) · translatable(key, ...) · append(other) · withBold() · withItalic() · withFont(id) · withColor(rgb | name) · clickToCopyText(str) · clickToOpenURL(url) · withHoverText(str | VanillaText) · getString()(拿本地化结果)。

VoxelShape(方块轮廓)

装饰物自定义碰撞 / 轮廓用:empty() · fullCube() · create(minX, minY, minZ, maxX, maxY, maxZ, facing?)(单位 16 = 一格)· combine(other);配合 ctx.setOutlineShape(...) / ctx.setCollisionShape(...)。

ItemStack / PlayerEntity

ItemStack:itemId() · translationId() · empty() · count()。PlayerEntity(通常来自 MinecraftClient.localPlayer() 或事件):uuid() · pos() / blockPos() / smoothPos() / velocity() · pitch() / yaw() / bodyYaw() · playerName() / displayName() · hasPermissionLevel(level)(客户端信息,别当真)· mainHandItem() / offHandItem() / activeItem() / isHoldingItem(id) · isSneaking() / isSprinting() / isSwimming() / isCreative() / isSpectator()。

工具类

DataReader(统一解码)

Resources.read / Files.read / Networking.fetch 都返回它——字节序列的解码器:

asString(): String?(UTF-8)· asBufferedImage(): BufferedImage? · asFont(): Font?(TTF/OTF;实现会在临时目录落文件,磁盘紧张可清 +~JF 开头的残留)· asByteArray(): byte[] · openInputStream(callback)(自动关流)· asInputStream()(要自己关)。解码失败返回 null 并在控制台报错。

其余工具

类要点
Timingelapsed()(游戏运行秒数,暂停也走)· delta()(距上次 render 的秒差——按帧率缩放动画全靠它)· currentTimeMillis() · nanoTime()
TextUtil拆 MTR 的 中文|English||附加 命名格式:getCjkParts / getNonCjkParts / getExtraParts / getNonExtraParts / getNonCjkAndExtraParts / isCjk / cycleString(src[, tick])
StateTracker状态变迁跟踪(避免「满足条件就每帧播一次」):setState(value): boolean(变化才真)· stateNow() / stateLast() / stateNowDuration() / stateNowFirst() · changedTo(v) / changedFromTo(old, v);逐对象一份,存 state
CycleTracker按时间循环轮播:new CycleTracker(["route", 5, "next", 5]) + tick() · stateNow() / stateLast() / stateNowDuration() / stateNowFirst()
RateLimit限频:new RateLimit(intervalSec) + if (rl.shouldUpdate()) { ... };resetCoolDown() 立即到点
JavaJava 互操作帮手:Java.newArray(class, length | dims)、Java.toArray(class, list)——AWT 某些方法要真 Java 数组

脚本特性

include(脚本互相包含)

include(Resources.id("mtrsteamloco:scripts/display_helper.js"));  // 绝对路径
include("./util/common.js");                                     // 相对当前脚本

在解析阶段加载并执行另一个 JS 文件(可收字符串相对路径或 Identifier)。

脚本条目互引

同包内没有内容引用的脚本条目默认拒绝加载(防漏写 scriptId);给脚本条目加 "forceLoad": true 可强制加载——跨包共享脚本条目、MTR 3 条目借 MTR 4 共享声明时用,注意资源包加载顺序(声明方先加载)。

ANTE 兼容类

SoundHelper(ANTE 风格放音)、TickableSound、GlobalRegister、ComponentUtil(原版文本组件工具)与全局 asJavaArray(list, class) 自 v2.3 起可用,签名与 ANTE 一致,方便 ANTE 内容迁移。


接下来:JS 脚本类型 —— 车辆 / 装饰物 / 电梯 / PIDS 的专属 API;晏阳扩展 API —— 本分支在 JS 之上增加的能力。