diff --git a/Box3JS-NeoForge-1.21.1/README.md b/Box3JS-NeoForge-1.21.1/README.md index 4362d48..eb2aefd 100644 --- a/Box3JS-NeoForge-1.21.1/README.md +++ b/Box3JS-NeoForge-1.21.1/README.md @@ -1,106 +1,160 @@ -# Box3JS(神岛代码)-- Minecraft Mod +# Box3JS — Minecraft 脚本引擎 -> **测试版(Beta)** — 本项目处于早期测试阶段,API 可能变动,可能存在未发现的缺陷。欢迎反馈问题。 +> **Beta** — 本项目处于早期测试阶段,API 可能变动,欢迎反馈。 -[简体中文](README.md) | [English](README_en.md) +[简体中文](Readme.md) | [English](README_en.md) **无需 Java 知识,用 TypeScript 为你的 Minecraft 服务器创造无限玩法。** -Box3JS 是一个内置于模组的服务端脚本引擎(Mozilla Rhino),延续了神奇代码岛的代码风格。告别复杂的 Java 模组开发——写 TypeScript,一键热重载,即时生效。无论是 PvP 竞技场、RPG 副本、派对小游戏,还是世界管理和社交工具,都能用脚本快速实现。 +Box3JS 是一个内置于 NeoForge 模组的服务端脚本引擎(Mozilla Rhino),延续了神奇代码岛的 API 风格。告别复杂的 Java 模组开发——写 TypeScript,一键热重载,即时生效。PvP 竞技场、RPG 副本、派对小游戏、世界管理、社交工具,全能用脚本快速实现。 -## 为什么选择 Box3JS? +## 安装 -- **零门槛** — 会写 TypeScript/JavaScript 就能开发 Minecraft 玩法,无需 Gradle、无需 IDE、无需重启服务器 -- **热重载** — 修改代码后自动编译重载(`--watch`),迭代速度秒杀传统模组开发 -- **沙盒保护** — 一键开启沙盒模式,自动追踪所有世界修改;关闭时完整回滚,服务器不留痕迹 -- **TypeScript 全流程** — esbuild 打包 + Babel 转译(Rhino 1.9.1 目标),内置完整类型声明文件,享受类型检查和智能提示 -- **16 种事件回调** — 玩家加入/离开、聊天、方块交互、实体死亡/受伤、玩家重生、按钮按下、跨脚本消息……覆盖所有玩法需求 -- **丰富的视觉 API** — 13+ 种粒子效果、5 种烟花形状、闪电、爆炸、音效,打造沉浸式体验 -- **完整游戏系统** — 计分板、BossBar 倒计时、队伍系统、世界边界缩圈、跨脚本通信,开箱即用 -- **自定义物品/配方** — JSON 配置即可注册自定义物品(支持食物、稀有度、光效),动态管理合成配方 +1. 将 `box3js-.jar` 放入服务端 `mods/` 目录 +2. 如需使用 SQLite 数据库(`db` API),还需安装 [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) +3. 启动服务器 -## 快速开始 +## 5 分钟快速开始 -在游戏中(需要 OP 权限,等级 ≥ 2): +在游戏中(需要 OP 权限等级 ≥ 2): ``` /box3script create mygame ``` -这会创建一个 TypeScript 脚手架项目: +这会创建 TypeScript 项目: ``` config/box3/script/mygame/ -├── .gitignore ├── package.json ← npm 依赖(esbuild、Babel、TypeScript) ├── tsconfig.json ├── build.mjs ← 构建脚本(esbuild → Babel → Rhino) +├── eslint.config.mjs ├── types/ -│ └── globals.d.ts ← Box3JS 完整类型声明 +│ └── globals.d.ts ← 完整 API 类型声明(IDE 自动补全) └── src/ - └── app.ts ← 入口(含 Hello World 示例) + └── app.ts ← 入口文件,代码写在这里 ``` -然后构建: +构建并启动: ```bash cd config/box3/script/mygame -npm install -npm run build # 输出 dist/app.js +npm install && npm run build ``` -回到游戏启用: - ``` /box3script sandbox mygame # (推荐) 开启沙盒,放心测试 /box3script start mygame # 启动脚本 ``` +打开 `src/app.ts` 写入代码,保存后 `npm run build`,再 `/box3script reload mygame` 即时生效——**不需要重启服务器**。 + +## 为什么选择 Box3JS? + +| 特性 | 说明 | +|------|------| +| **零门槛** | 会 JS/TS 就能写,无需 Gradle、无需 IDE、无需重启 | +| **热重载** | 改代码 → build → reload,秒级生效。开启 `watch` 自动重载 | +| **沙盒保护** | 开启沙盒自动追踪所有脚本修改,关闭时完整回滚,服务器不留痕迹 | +| **TypeScript** | 完整 `.d.ts` 类型声明,esbuild + Babel 编译管线,享受智能提示 | +| **17 种事件** | onTick、onPlayerJoin、onChat、onEntityDeath、onBlockActivate、onButtonPressed... | +| **视觉效果** | 13+ 粒子、烟花、闪电、爆炸、音效 | +| **游戏系统** | 计分板、BossBar、队伍、世界边界、跨脚本通信 | +| **自定义物品** | JSON 配置注册自定义物品(食物、稀有度、附魔光效),动态管理配方 | +| **数据持久化** | JSON 存储 + SQLite 数据库(排行榜、经济、玩家数据) | + ## 命令 | 命令 | 说明 | -|---|---| -| `/box3script` | 列出所有项目及启用/沙盒状态 | -| `/box3script create ` | 创建新脚本项目 (TypeScript 脚手架) | -| `/box3script start ` | 启动指定项目 | -| `/box3script start all` | 一键启动所有项目 | -| `/box3script stop ` | 停止指定项目(沙盒项目保留追踪) | -| `/box3script stop all` | 一键停止所有项目 | -| `/box3script reload ` | 重载指定项目(开发调试用) | -| `/box3script reload` | 重载所有已启用项目 | -| `/box3script watch` | 切换文件监控(`.js` 变化自动热重载) | -| `/box3script sandbox ` | 切换沙盒模式(开启追踪 / 关闭回滚) | - -> 所有 `` 参数均支持 **Tab 自动补全**。完整命令文档见 [commands.md](docs/api/commands.md)。 +|------|------| +| `/box3script` | 查看项目状态总览 | +| `/box3script create ` | 创建新 TypeScript 项目 | +| `/box3script start [project\|all]` | 启用并加载项目 | +| `/box3script stop [project\|all]` | 禁用并卸载项目 | +| `/box3script reload [project]` | 重载脚本(开发用) | +| `/box3script watch` | 切换文件监控(自动热重载) | +| `/box3script sandbox ` | 切换沙盒模式(开=追踪 / 关=回滚) | +| `/box3script compile ` | 编译为独立 JAR 模组(无需 Box3JS) | + +所有 `` 参数支持 **Tab 自动补全**。[完整命令文档 →](docs/api/commands.md) + +## API 速览 + +| 全局对象 | 用途 | +|----------|------| +| `world` | 世界状态、事件回调、粒子、烟花、闪电、音效、计分板、BossBar、队伍、边界、自定义物品 | +| `entity` | 实体属性、AI 寻路、装备、药水效果、标签、导航 | +| `player` | 背包、飞行、游戏模式、传送、消息、经验、音效 | +| `voxels` | 方块读写、区域填充、刷怪笼 | +| `storage` | JSON 数据持久化 | +| `db` | SQLite 数据库 — SQL 查询、排行榜、玩家数据 | +| `console` | 控制台日志输出(`log`/`warn`/`error`/`debug`) | +| `GameVector3` | 三维向量(坐标运算) | +| `GameBounds3` | 包围盒 | +| `GameRGBColor` / `GameRGBAColor` | RGB/RGBA 颜色 | +| `GameQuaternion` | 四元数(旋转运算) | + +[API 总览 →](docs/api/README.md) · [按任务速查 →](docs/api/README.md#功能速查---我想) · [English](docs/api/README_en.md) ## 教程 -从零基础到完整小游戏,每个示例均经过 TypeScript 编译 + ESLint 验证: +从零到完整小游戏,每个示例均经过 TypeScript 编译 + ESLint 验证: + +| # | 教程 | 时长 | 学什么 | +|---|------|------|--------| +| 1 | [从零开始](docs/tutorial/01-basics.md) | 10 min | 创建项目、第一个脚本、聊天命令、定时任务 | +| 2 | [玩家操控与物品](docs/tutorial/02-player-items.md) | 15 min | 传送、飞行、物品、附魔、药水、自定义物品 | +| 3 | [事件系统与实体](docs/tutorial/03-events-entities.md) | 15 min | 事件回调、实体生成、AI、战斗、巡逻 | +| 4 | [高级游戏系统](docs/tutorial/04-advanced-systems.md) | 15 min | 计分板、BossBar、队伍、边界、跨脚本通信 | +| 5 | [实战小游戏](docs/tutorial/05-examples.md) | 20 min | PvP 竞技场、粒子烟花、波次刷怪、特效大全 | + +[教程总览 →](docs/tutorial/README.md) -1. [从零开始](docs/tutorial/01-basics.md) — 创建项目、控制台、聊天命令、定时器、粒子特效 -2. [玩家与物品](docs/tutorial/02-player-items.md) — 传送、飞行、游戏模式、药水、附魔、自定义物品 -3. [事件与实体](docs/tutorial/03-events-entities.md) — 方块交互、实体生成/AI、受伤/死亡、巡逻守卫 -4. [高级游戏系统](docs/tutorial/04-advanced-systems.md) — 计分板、BossBar、队伍、世界边界、跨脚本通信 -5. [可视化与实战](docs/tutorial/05-examples.md) — 粒子、烟花、闪电、音效、PvP 竞技场、领地争夺战 +## 文档结构 + +``` +docs/ +├── api/ ← API 参考 +│ ├── README.md 总览 + 功能速查 +│ ├── world.md 世界 API(事件、粒子、烟花、计分板...) +│ ├── entity.md 实体 API(属性、AI、装备、效果...) +│ ├── player.md 玩家 API(背包、消息、飞行、传送...) +│ ├── voxels.md 方块 API(读写、填充、刷怪笼) +│ ├── storage.md 存储 API(JSON 持久化) +│ ├── database.md 数据库 API(SQLite) +│ ├── math.md 数学 API(Vector3、Color、Quaternion) +│ └── commands.md /box3script 命令参考 +├── tutorial/ ← 入门教程 +│ ├── README.md 教程总览 +│ ├── 01-basics.md 从零开始 +│ ├── 02-player-items.md 玩家与物品 +│ ├── 03-events-entities.md 事件与实体 +│ ├── 04-advanced-systems.md 高级系统 +│ └── 05-examples.md 实战小游戏 +└── BOX3_API_COMPARISON.md ← Box3 平台 vs Box3JS API 对照表 +``` ## 示例项目 -`run/config/box3/script/colorzone/` 包含一个完整的领地争夺战(Territory Rush)游戏和 7 个已验证的功能示例,涵盖从 Hello World 到波次刷怪的全部教学场景。 +`run/config/box3/script/colorzone/` 包含完整的领地争夺战游戏和 7 个功能示例,涵盖 Hello World 到波次刷怪的全部教学场景。 -## 可用 API +## 依赖说明 -| 模块 | 功能 | +| 功能 | 依赖 | |------|------| -| `world` | 世界控制、16 种事件回调、计分板、BossBar、队伍、边界、粒子、烟花、闪电、爆炸、抛射物、射线检测、跨脚本通信、自定义物品/配方 | -| `entity` | 实体属性、AI 寻路、装备、药水效果、标签、导航 | -| `player` | 背包、飞行、游戏模式、传送、消息、经验、成就、音效、标题、BossBar | -| `voxels` | 方块读写、区域填充、刷怪笼 | -| `storage` | JSON 数据持久化 | -| `db` | SQLite 数据库 — SQL 查询、排行榜、玩家数据 | -| `console` | 日志输出、`require()`、`sleep()` | -| `GameVector3` / `GameBounds3` / `GameRGBColor` | 数学与颜色类型 | +| 脚本引擎核心 | 内嵌 Rhino 1.9.1,无需额外安装 | +| `db` API(SQLite) | 需安装 [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) 模组 | +| 其他 API | 无额外依赖 | + +> 未安装 `minecraft-sqlite-jdbc` 时,`db` 以外的所有 API 正常工作。只有调用 `db.sql()` 才会提示需要安装。 + +## 技术栈 -[API 总览 →](docs/api/README.md) ([English](docs/api/README_en.md)) +- **运行时:** Mozilla Rhino 1.9.1(Java 嵌入式 JS 引擎) +- **编译工具:** esbuild 打包 → Babel 转译(Rhino 目标) → 正则清理 +- **语言:** TypeScript 编写,编译为 ES5 兼容 JS +- **平台:** NeoForge 1.21.1,Java 21 ## 许可证 diff --git a/Box3JS-NeoForge-1.21.1/README_en.md b/Box3JS-NeoForge-1.21.1/README_en.md index d11fc98..d318100 100644 --- a/Box3JS-NeoForge-1.21.1/README_en.md +++ b/Box3JS-NeoForge-1.21.1/README_en.md @@ -1,20 +1,20 @@ -# Box3JS -- Minecraft Mod +# Box3JS — Minecraft Scripting Engine -> **Beta** — This project is in early beta. APIs may change, and undiscovered issues may still exist. Feedback is welcome. +> **Beta** — This project is in early beta. APIs may change. Feedback is welcome. -[简体中文](README.md) | [English](README_en.md) +[简体中文](Readme.md) | [English](README_en.md) -`Box3JS` is a Minecraft server-side mod inspired by Box3 coding style. You don’t need to write Java — just use TypeScript to build scripts. +**No Java knowledge required. Build unlimited Minecraft gameplay with TypeScript.** -## Features +Box3JS is a server-side scripting engine (Mozilla Rhino) built into a NeoForge mod. Forget complex Java mod development — write TypeScript, hot-reload instantly, see changes live. PvP arenas, RPG dungeons, party games, world management, social tools — all achievable with scripts. -- **TypeScript Support** — The project template includes TS type declarations with full type checking -- **Box3 API Compatibility** — Implements core Box3 APIs (World / Entity / Player / Voxels / Storage) -- **Minecraft Extensions** — 90+ Minecraft-specific features: scoreboards, bossbars, teams, world border, particles, fireworks, potions, and more -- **Hot Reload** — Reload scripts with `/box3script watch` without restarting -- **Project Management** — Multi-project isolation, independent enable/disable, and auto-run on restart +## Installation -## Quick Start +1. Place `box3js-.jar` into your server's `mods/` directory +2. For SQLite database support (`db` API), also install [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) +3. Start the server + +## 5-Minute Quick Start In-game (requires OP level ≥ 2): @@ -22,51 +22,139 @@ In-game (requires OP level ≥ 2): /box3script create mygame ``` -This creates a TypeScript scaffold project: +This creates a TypeScript project: ``` config/box3/script/mygame/ -├── .gitignore ├── package.json ← npm dependencies (esbuild, Babel, TypeScript) ├── tsconfig.json ├── build.mjs ← build script (esbuild → Babel → Rhino) +├── eslint.config.mjs ├── types/ -│ └── globals.d.ts ← full Box3JS type declarations +│ └── globals.d.ts ← full API type declarations (IDE autocomplete) └── src/ - └── app.ts ← entry point (includes Hello World example) + └── app.ts ← entry point — write your code here ``` -Then build: +Build and start: ```bash cd config/box3/script/mygame -npm install -npm run build # outputs dist/app.js +npm install && npm run build ``` -Back in game and enable it: - ``` -/box3script on mygame +/box3script sandbox mygame # (recommended) enable sandbox for safe testing +/box3script start mygame # start the script ``` -## Available APIs +Edit `src/app.ts`, re-run `npm run build`, then `/box3script reload mygame` — changes take effect **without restarting the server**. + +## Why Box3JS? -[API Overview →](docs/api/README.md) ([English](docs/api/README_en.md)) +| Feature | Description | +|---------|-------------| +| **Zero barrier** | Know JS/TS? You can build. No Gradle, no IDE, no restarts | +| **Hot reload** | Edit → build → reload in seconds. Enable `watch` for auto-reload | +| **Sandbox** | Toggle sandbox to track all script changes; disable to fully roll back | +| **TypeScript** | Full `.d.ts` type declarations, esbuild + Babel pipeline, IDE IntelliSense | +| **17 events** | onTick, onPlayerJoin, onChat, onEntityDeath, onBlockActivate, onButtonPressed... | +| **Visual effects** | 13+ particles, fireworks, lightning, explosions, sounds | +| **Game systems** | Scoreboards, BossBar, teams, world border, cross-script messaging | +| **Custom items** | JSON-configured items (food, rarity, glint), dynamic recipe management | +| **Data persistence** | JSON storage + SQLite database (leaderboards, economy, player data) | + +## Commands + +| Command | Description | +|---------|-------------| +| `/box3script` | Show project status overview | +| `/box3script create ` | Create a new TypeScript project | +| `/box3script start [project\|all]` | Enable and load projects | +| `/box3script stop [project\|all]` | Disable and unload projects | +| `/box3script reload [project]` | Reload scripts (for development) | +| `/box3script watch` | Toggle file watching (auto hot-reload) | +| `/box3script sandbox ` | Toggle sandbox (on=track / off=rollback) | +| `/box3script compile ` | Compile to standalone JAR (no Box3JS needed) | + +All `` arguments support **Tab completion**. [Full command reference →](docs/api/commands_en.md) + +## API Overview + +| Global | Purpose | +|--------|---------| +| `world` | World state, events, particles, fireworks, lightning, sounds, scoreboards, BossBar, teams, border, custom items | +| `entity` | Entity properties, AI pathfinding, equipment, potion effects, tags, navigation | +| `player` | Inventory, flight, game mode, teleport, messaging, XP, sounds | +| `voxels` | Block read/write, region fill, spawner control | +| `storage` | JSON data persistence | +| `db` | SQLite database — SQL queries, leaderboards, player data | +| `console` | Server console logging (`log`/`warn`/`error`/`debug`) | +| `GameVector3` | 3D vector (coordinate math) | +| `GameBounds3` | Bounding box | +| `GameRGBColor` / `GameRGBAColor` | RGB / RGBA color | +| `GameQuaternion` | Quaternion (rotation math) | + +[API Overview →](docs/api/README_en.md) · [Find by Task →](docs/api/README_en.md#find-by-task--i-want-to) ## Tutorials -Step-by-step guides from basics to full mini-games: +From zero to full mini-games. Every example is TypeScript-compiled and ESLint-verified: -1. [Getting Started](docs/tutorial/01-basics.md) — Project setup, console, chat commands, timers -2. [Players & Items](docs/tutorial/02-player-items.md) — Teleport, flight, inventory, custom items -3. [Events & Entities](docs/tutorial/03-events-entities.md) — Event callbacks, entity spawn/AI, scoreboards, teams -4. [Advanced Systems](docs/tutorial/04-advanced-systems.md) — BossBar, particles, fireworks, world border, PvP arena -5. [Example Collection](docs/tutorial/05-examples.md) — Teleport, anti-grief, wave mobs, race checkpoints, hide-and-seek etc. +| # | Tutorial | Time | What you'll learn | +|---|----------|------|-------------------| +| 1 | [Getting Started](docs/tutorial/01-basics.md) | 10 min | Project setup, first script, chat commands, timers | +| 2 | [Players & Items](docs/tutorial/02-player-items.md) | 15 min | Teleport, flight, items, enchantments, potions, custom items | +| 3 | [Events & Entities](docs/tutorial/03-events-entities.md) | 15 min | Event callbacks, entity spawning, AI, combat, patrols | +| 4 | [Advanced Systems](docs/tutorial/04-advanced-systems.md) | 15 min | Scoreboards, BossBar, teams, world border, cross-script messaging | +| 5 | [Mini-Games](docs/tutorial/05-examples.md) | 20 min | PvP arena, particles & fireworks, wave mobs, visual effects | -## Commands +[Tutorial overview →](docs/tutorial/README.md) + +## Documentation Structure + +``` +docs/ +├── api/ ← API Reference +│ ├── README.md Overview + find by task +│ ├── world.md World API (events, particles, fireworks, scoreboards...) +│ ├── entity.md Entity API (properties, AI, equipment, effects...) +│ ├── player.md Player API (inventory, messaging, flight, teleport...) +│ ├── voxels.md Voxels API (read/write, fill, spawner) +│ ├── storage.md Storage API (JSON persistence) +│ ├── database.md Database API (SQLite) +│ ├── math.md Math API (Vector3, Color, Quaternion) +│ └── commands.md /box3script command reference +├── tutorial/ ← Tutorials +│ ├── README.md Learning path overview +│ ├── 01-basics.md Getting started +│ ├── 02-player-items.md Players & items +│ ├── 03-events-entities.md Events & entities +│ ├── 04-advanced-systems.md Advanced systems +│ └── 05-examples.md Mini-games +└── BOX3_API_COMPARISON.md ← Box3 platform vs Box3JS API comparison +``` + +## Example Project + +`run/config/box3/script/colorzone/` contains a complete Territory Rush game and 7 verified feature examples covering every tutorial scenario. + +## Dependencies + +| Feature | Requirement | +|---------|-------------| +| Script engine core | Rhino 1.9.1 bundled — no extra install needed | +| `db` API (SQLite) | Requires [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) mod | +| All other APIs | No additional dependencies | + +> Without `minecraft-sqlite-jdbc`, all APIs except `db` work normally. Only calling `db.sql()` triggers an error asking you to install it. + +## Tech Stack -[Full Command Reference →](docs/api/commands.md) ([English](docs/api/commands_en.md)) +- **Runtime:** Mozilla Rhino 1.9.1 (embedded JS engine for JVM) +- **Build tools:** esbuild bundle → Babel transpile (Rhino target) → regex sanitize +- **Language:** TypeScript, compiled to ES5-compatible JS +- **Platform:** NeoForge 1.21.1, Java 21 ## License diff --git a/Box3JS-NeoForge-1.21.1/build.gradle b/Box3JS-NeoForge-1.21.1/build.gradle index ffd5dee..e366251 100644 --- a/Box3JS-NeoForge-1.21.1/build.gradle +++ b/Box3JS-NeoForge-1.21.1/build.gradle @@ -199,6 +199,14 @@ publishing { } } +// Make the mod JAR executable as a standalone script compiler tool. +// Usage: java -jar box3js.jar --project --output --modId --name +tasks.named('jar', Jar).configure { + manifest { + attributes('Main-Class': 'com.box3lab.box3js.standalone.Box3ScriptCompiler') + } +} + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } diff --git a/Box3JS-NeoForge-1.21.1/docs/BOX3_API_COMPARISON.md b/Box3JS-NeoForge-1.21.1/docs/BOX3_API_COMPARISON.md index 260d5e9..dadef76 100644 --- a/Box3JS-NeoForge-1.21.1/docs/BOX3_API_COMPARISON.md +++ b/Box3JS-NeoForge-1.21.1/docs/BOX3_API_COMPARISON.md @@ -232,6 +232,10 @@ Zone 可以设置局部天气/光照/力场参数,这在 MC 服务端无法实 | `world.spawnParticle(type, pos, count, dx, dy, dz, speed)` | `world.spawnParticle(type, pos, count, dx, dy, dz, speed)` | ✅ | GameVector3 重载 | | — | `world.spawnParticleCircle(x, y, z, radius, type, count)` | ⬆ | MC 扩展。圆形粒子圈 | | — | `world.spawnParticleCircle(pos, radius, type, count)` | ⬆ | GameVector3 重载 | +| — | `world.spawnParticle(x, y, z, color, count, dx, dy, dz, speed)` | ⬆ | MC 扩展。使用 GameRGBColor 生成彩色 dust 粒子 | +| — | `world.spawnParticle(pos, color, count, dx, dy, dz, speed)` | ⬆ | GameVector3 + GameRGBColor 重载 | +| — | `world.launchFirework(x, y, z, colors, shape)` | ⬆ | MC 扩展。colors 为 GameRGBColor[] 数组 | +| — | `world.launchFirework(pos, colors, shape)` | ⬆ | GameVector3 + GameRGBColor[] 重载 | ### 1.16 物品与抛射物 @@ -326,6 +330,45 @@ Box3 无世界边界概念。 | `world.setBorderDamage(damage)` | 边界外伤害值 | | `world.setBorderWarning(blocks)` | 警告距离 | +### 1.26 自定义物品 (全部 MC 扩展) + +使用 `minecraft:paper` 作为载体,通过 DataComponents(CUSTOM_NAME, LORE, CUSTOM_MODEL_DATA 等)实现自定义物品,无需注册表同步。客户端贴图通过资源包的 `custom_model_data` override 加载。 + +| Box3JS API | 说明 | +|------------|------| +| `world.loadCustomItems(packName)` | 加载 `resourcepacks//items.json` 中的物品定义 | +| — | 物品支持: 名称、描述、贴图、堆叠上限、附魔光效、稀有度、食物属性 | + +### 1.27 合成管理 (全部 MC 扩展) + +| Box3JS API | 说明 | +|------------|------| +| `world.listRecipes(filter)` | 搜索匹配 filter 的合成配方 ID 列表 | +| `world.removeRecipe(recipeId)` | 禁用指定配方(加入黑名单) | +| `world.clearRecipes()` | 清除黑名单,恢复所有原始配方 | + +### 1.28 结构与成就 (全部 MC 扩展) + +| Box3JS API | 说明 | +|------------|------| +| `world.placeStructure(x, y, z, structureId)` | 在指定位置放置数据包结构模板 (NBT) | +| `world.placeStructure(pos, structureId)` | GameVector3 重载 | +| `world.grantAdvancement(playerName, advancementId)` | 按玩家名称授予进度 | + +### 1.29 数据库 (db 全局对象) + +Box3 平台无独立数据库 API。Box3JS 通过 `db` 全局对象提供 SQLite 能力。 + +| Box3JS API | 说明 | +|------------|------| +| `db.sql(sql, ...params)` | 执行 SQL 查询/更新,返回 `GameQueryResult` | +| `db.sql\`SELECT * FROM t WHERE id = ${id}\`` | Tagged template 语法,自动参数化 | + +**GameQueryResult**: `rows` (数组), `firstRow`, `columnNames`, `columnCount`, `rowCount`, `affectedRows`, `isQuery` +**GameQueryResult 方法**: `next()`, `reset()`, `then(resolve, reject?)` + +每个脚本项目拥有独立的数据库文件 `config/box3/data/.db`。 + --- ## 2. GameEntity (entity) @@ -1006,9 +1049,9 @@ Box3 的 `resources.ls(type?)` 浏览资源文件。MC 无对应资源管理 API ### 7.9 GameEventHandlerToken -**状态**: ❌ 未实现 +**状态**: ✅ 已实现 -Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` / `.resume()` / `.active()`。Box3JS 没有此机制,回调注册后无法单独取消。 +Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` / `.active()`。Box3JS 的所有 `world.onXxx()` 方法均返回 `GameEventHandlerToken`,支持 `.cancel()` 取消注册和 `.active()` 检查状态。`.resume()` 抛出 UnsupportedOperationException(需重新注册)。 --- @@ -1040,9 +1083,16 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` - `world.onVoxelDestroy` — 方块破坏事件 - `world.onBlockPlace` — 方块放置事件 - `world.onBlockActivate` — 方块右键事件 +- `world.onBlockActivateBegin/onBlockActivateEnd` — 方块右键开始/结束(长按检测) - `world.entitiesInArea/entitiesInRadius` — 空间实体查询 - `world.getBiome` — 生物群系查询 - `world.spawnParticleCircle` — 圆形粒子 +- `world.spawnParticle/spawnFirework` GameRGBColor 重载 — RGB 彩色粒子/烟花 +- `world.loadCustomItems(packName)` — 自定义物品注册 +- `world.listRecipes/removeRecipe/clearRecipes` — 合成管理 +- `world.placeStructure` — 结构放置 +- `world.grantAdvancement` — 成就授予 +- `world.onButtonPressed` — 按钮点击事件(石质/木质按钮) ### 9.2 实体管理 - `entity.nameTag` — 名牌 @@ -1059,6 +1109,8 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` - `entity.setPersistent` — 持久化 - `entity.getAttribute/setAttribute` — 属性修改 - `entity.lookAt` — 视线方向 +- `entity.setGlowColor(color)` — RGB 发光颜色(GameRGBColor → 最近 ChatFormatting) +- `entity.setText/setTextColor/setTextBackgroundColor` — 文本显示实体控制 ### 9.3 玩家管理 - `player.opLevel` — OP 权限 @@ -1070,6 +1122,7 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` - `player.actionBar` — ActionBar 消息 - `player.title` — 标题/副标题 - `player.giveItem/giveEnchantedItem/giveNamedItem` — 物品给予 +- `player.giveCustomItem(id, count)` — 给予通过 `world.loadCustomItems()` 加载的自定义物品 - `player.getHeldItem` — 手持物品 - `player.clearInventory` — 清空背包 - `player.addEffect/clearEffects` — 药水效果 @@ -1078,6 +1131,8 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` - `player.runCommand` — 以玩家身份执行命令 - `player.lookAt` — 视线方向 - `player.setPlayerListName` — TAB 列表名称 +- `player.directMessage(msg, color)` — 发送带颜色的系统消息(GameRGBColor 指定颜色) +- `player.grantAdvancement/revokeAdvancement` — 成就授予/撤销 ### 9.4 系统 - `world.addScoreboard/removeScoreboard/setScore/getScore/showScoreboard/hideScoreboard/listScores` — 记分板 @@ -1090,6 +1145,12 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` ### 9.5 额外事件 - `world.onEntityDamage` — 实体受伤(Pre 阶段) - `world.onMessage` — 跨脚本消息 +- `world.onButtonPressed` — 按钮点击事件(支持石质/木质按钮长按检测) + +### 9.6 数据库 +- `db.sql` — SQLite 数据库操作(支持 tagged template 和参数化查询) +- `GameQueryResult` — 查询结果(rows, firstRow, columnNames, rowCount, affectedRows, isQuery) +- 每个项目独立数据库文件 `config/box3/data/.db` --- @@ -1099,14 +1160,15 @@ Box3 的事件注册方法返回 `GameEventHandlerToken`,可调用 `.cancel()` | 类别 | Box3 API 总数 | 已实现 | 部分实现 | 未实现 | MC 独有扩展 | |------|--------------|--------|---------|--------|-------------| -| GameWorld | ~80 | ~30 | ~6 | ~44 | 27 | -| GameEntity | ~65 | ~21 | ~1 | ~43 | 17 | -| GamePlayerEntity | ~72 | ~27 | ~4 | ~41 | 17 | +| GameWorld | ~80 | ~30 | ~6 | ~44 | 33 | +| GameEntity | ~65 | ~21 | ~1 | ~43 | 21 | +| GamePlayerEntity | ~72 | ~27 | ~4 | ~41 | 22 | | GameVoxels | 14 | 14 | 0 | 0 | 4 | | GameDataStorage | 8 | 8 | 7 (同步化) | 0 | 2 | | Math 类型 | ~100 | ~100 | 0 | 0 | 0 | +| 数据库 (db) | N/A | — | — | — | 1 | | 其他服务端 | ~30 | 0 | 0 | ~30 | 0 | -| **总计** | **~369** | **~200** | **~18** | **~158** | **~67** | +| **总计** | **~369** | **~200** | **~18** | **~158** | **~83** | > **2026-05 更新**: 本阶段实现约 54 个新 Box3 API(属性对齐 + math 补全 + 物理属性 + token + 回调签名 + World API 补全),Math 类型现已完全对齐。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/README.md b/Box3JS-NeoForge-1.21.1/docs/api/README.md index 98fc96c..5e50f64 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/README.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/README.md @@ -1,66 +1,222 @@ # Box3JS API 参考 -Box3JS 是一个 Minecraft 模组,允许用 JavaScript 编写服务端脚本。所有脚本运行在 `config/box3/script/<项目名>` 下。 +Box3JS 是一个 Minecraft 模组,允许用 JavaScript/TypeScript 编写服务端脚本。所有脚本运行在 `config/box3/script/<项目名>` 下。 -## 快速开始 +## 5 分钟快速开始 -```js -// app.js — 最简示例 -world.onTick(() => { - // 每 tick 执行 (20 tick = 1 秒) -}); +```bash +# 1. 游戏内创建项目 +/box3script create mygame -world.onChat((entity, message, tick) => { - var p = entity.player; +# 2. 安装依赖并构建 +cd config/box3/script/mygame +npm install && npm run build + +# 3. 启动脚本 +/box3script start mygame +``` + +打开 `src/app.ts`,写入: + +```js +world.onChat((entity, message) => { if (message === "!hello") { - p.directMessage("Hello, " + p.name + "!"); + entity.player.directMessage("Hello, " + entity.player.name + "!"); + return false; // 阻止消息显示在聊天栏 } + return true; }); console.log("脚本已加载"); ``` -## 全局对象 - -| 对象 | 类型 | 说明 | -| ---------------- | ------- | -------------------------------------------------------------------------------- | -| `world` | ✅ Box3 | 世界控制,见 [world.md](world.md) | -| `entity` | ✅ Box3 | 实体包装(回调参数,或通过 `world.spawnEntity` 创建),见 [entity.md](entity.md) | -| `player` | ✅ Box3 | 玩家包装(通过 `entity.player` 获取),见 [player.md](player.md) | -| `voxels` | ✅ Box3 | 方块操作,见 [voxels.md](voxels.md) | -| `storage` | ✅ Box3 | 数据持久化,见 [storage.md](storage.md) | -| `db` | ✅ Box3 | SQLite 数据库,见 [database.md](database.md) | -| `GameVector3` | ✅ Box3 | 三维向量,见 [math.md](math.md) | -| `GameBounds3` | ✅ Box3 | 包围盒,见 [math.md](math.md) | -| `GameRGBColor` | ✅ Box3 | RGB 颜色,见 [math.md](math.md) | -| `GameRGBAColor` | ✅ Box3 | RGBA 颜色,见 [math.md](math.md) | -| `GameQuaternion` | ✅ Box3 | 四元数,见 [math.md](math.md) | +每次修改后重新 `npm run build`,然后用 `/box3script reload mygame` 热重载。 -## API 标注说明 +## 功能速查 — 我想... + +按你想做的事情查找对应 API,而非按全局对象记。 + +### 消息与聊天 + +| 我想... | 用这个 | +|---------|--------| +| 全服广播 | `world.say("消息")` | +| 私密消息(只一人看到) | `player.directMessage("消息")` | +| 快捷栏上方文字 | `player.actionBar("消息")` | +| 屏幕中央大标题 | `player.title("标题", "副标题")` | +| 拦截/处理聊天 | `world.onChat((entity, msg) => { ... })` | + +### 玩家属性 + +| 我想... | 用这个 | +|---------|--------| +| 获取/设置玩家位置 | `player.position` → `GameVector3` | +| 传送玩家 | `player.teleport(new GameVector3(x, y, z))` | +| 修改生命值 | `player.hp = 20` / `player.maxHp = 40` | +| 修改饱食度 | `player.food = 20` / `player.saturation = 10` | +| 切换游戏模式 | `player.gameMode = "creative"` | +| 切换飞行 | `player.canFly = true` / `player.flying = true` | +| 踢出玩家 | `player.kick("原因")` | +| 以玩家身份执行命令 | `player.runCommand("say hi")` | + +### 物品与装备 + +| 我想... | 用这个 | +|---------|--------| +| 给玩家普通物品 | `player.giveItem("minecraft:diamond", 1)` | +| 给带附魔的物品 | `player.giveEnchantedItem(...)` | +| 给带自定义名称的物品 | `player.giveNamedItem(...)` | +| 给自定义模组物品 | `player.giveCustomItem("my_item", 1)` | +| 获取手持物品 | `player.getHeldItem()` | +| 清空背包 | `player.clearInventory()` | +| 设置实体装备 | `entity.setEquipment("head", "iron_helmet")` | +| 加载自定义物品包 | `world.loadCustomItems("mypack")` | + +### 方块操作 + +| 我想... | 用这个 | +|---------|--------| +| 读取某个位置的方块 | `voxels.getVoxel(x, y, z)` | +| 放置/替换方块 | `voxels.setVoxel(x, y, z, "minecraft:stone")` | +| 填充区域 | `voxels.fillVoxel(x1,y1,z1, x2,y2,z2, "stone")` | +| 监听方块破坏 | `world.onVoxelDestroy((entity, x, y, z, voxel) => { ... })` | +| 监听方块放置 | `world.onBlockPlace((entity, x, y, z, voxel) => { ... })` | + +### 实体操控 + +| 我想... | 用这个 | +|---------|--------| +| 生成实体 | `world.spawnEntity("minecraft:zombie", pos)` | +| 带配置创建实体 | `world.createEntity({ type, position, ... })` | +| 设置实体名称 | `entity.setNameTag("§cBoss")` | +| 开关 AI | `entity.setAI(true)` | +| 实体导航 | `entity.navigateTo(x, y, z, speed)` | +| 设置攻击目标 | `entity.setTarget(otherEntity)` | +| 判断是否是玩家 | `entity.isPlayer()` | +| 获取实体类型 | `entity.entityType` | +| 获取实体标签 | `entity.tags()` / `entity.hasTag("boss")` | +| 查询附近实体 | `world.entitiesInRadius(pos, radius)` | +| 查询所有实体 | `world.querySelectorAll("*")` | + +### 视觉效果 + +| 我想... | 用这个 | +|---------|--------| +| 粒子效果 | `world.spawnParticle("flame", x, y, z, ...)` | +| 圆形粒子圈 | `world.spawnParticleCircle(x, y, z, radius, "heart", 20)` | +| 烟花 | `world.launchFirework(x, y, z, "red", "large_ball")` | +| 闪电 | `world.strikeLightning(x, y, z)` | +| 爆炸 | `world.explode(x, y, z, 4)` | +| 播放音效(全局) | `world.playSound("pling", pos, 1.0, 1.0)` | +| 播放音效(单人) | `player.playSound("pling", 1.0, 1.0)` | + +### 药水效果 + +| 我想... | 用这个 | +|---------|--------| +| 施加药水效果 | `entity.addEffect("minecraft:speed", duration, level, hideParticles)` | +| 清除所有效果 | `entity.clearEffects()` | + +### 事件系统 + +| 我想... | 用这个 | +|---------|--------| +| 每 tick 执行 | `world.onTick((info) => { ... })` | +| 玩家加入时 | `world.onPlayerJoin((entity, tick) => { ... })` | +| 玩家离开时 | `world.onPlayerLeave((entity, tick) => { ... })` | +| 实体死亡时 | `world.onEntityDeath((entity, killer, tick) => { ... })` | +| 实体受伤时 | `world.onEntityDamage((entity, amount, source, attacker, tick) => { ... })` | +| 右键实体时 | `world.onInteract((entity, target, tick) => { ... })` | +| 右键方块时 | `world.onBlockActivate((entity, x, y, z, voxel, tick) => { ... })` | +| 按钮按下时 | `world.onButtonPressed((entity, button, tick) => { ... })` | +| 玩家重生时 | `world.onPlayerRespawn((entity, tick) => { ... })` | +| 定时执行一次 | `world.setTimeout(() => { ... }, ticks)` | +| 定时循环执行 | `world.setInterval(() => { ... }, ticks)` | +| 取消事件监听 | `token.cancel()` | +| 检查事件是否活跃 | `token.active()` | -| 标注 | 含义 | -| --------------- | ------------------------------------------- | -| ✅ **Box3 API** | 源自 Box3 平台,命名和语义与 Box3 保持一致 | -| ⬆ **MC 扩展** | 非 Box3 原有,利用 Minecraft 特性新增的 API | +### 数据持久化 -## 文档索引 +| 我想... | 用这个 | +|---------|--------| +| 读写 JSON 数据 | `storage.getDataStorage("key")` | +| SQL 查询 | `db.sql("SELECT ...")` | +| SQL 写入 | `db.sql("INSERT INTO ...")` | -| 文档 | 内容 | -| -------------------------- | ------------------------------------------------------- | -| [world.md](world.md) | 世界状态、事件、记分板、Bossbar、队伍、边界、粒子、烟花 | -| [entity.md](entity.md) | 实体属性、AI、装备、药水、寻路、标签 | -| [player.md](player.md) | 背包、消息、飞行、游戏模式、传送、命令 | -| [voxels.md](voxels.md) | 方块读写、区域填充、刷怪笼 | -| [storage.md](storage.md) | 数据持久化存储 | -| [database.md](database.md) | SQLite 数据库 | -| [math.md](math.md) | Vector3、Bounds3、Color、Quaternion | -| [commands.md](commands.md) | `/box3script` 命令参考 | +### 游戏系统 -## 文件模块 +| 我想... | 用这个 | +|---------|--------| +| 创建计分板 | `world.addScoreboard("name")` | +| 设置分数 | `world.setScore("玩家", "计分板", 10)` | +| 显示计分板 | `world.showScoreboard("sidebar", "name")` | +| 显示 BossBar | `world.showBossbar("id", "标题", 0.5, "red")` | +| 创建队伍 | `world.createTeam("teamName", "color")` | +| 加入队伍 | `world.joinTeam(entity, "teamName")` | +| 设置世界边界 | `world.borderSize = 500` | +| 缩圈 | `world.shrinkBorder(100, 60)` | +| 修改世界时间 | `world.time = 6000` | +| 设置天气 | `world.rainDensity = 0` / `world.clearWeather()` | +| 修改游戏规则 | `world.setGameRule("keepInventory", true)` | -**TypeScript 构建管线:** +### 数学工具 -`/box3script create` 创建的项目自带完整的 TS 构建环境。写入 `src/*.ts`,构建输出到 `dist/app.js`: +| 我想... | 用这个 | +|---------|--------| +| 三维坐标 | `new GameVector3(x, y, z)` | +| 向量运算 | `v.add(other)`, `v.scale(n)`, `v.length()` | +| 包围盒 | `new GameBounds3(min, max)` | +| 颜色 | `new GameRGBColor(r, g, b)` / `new GameRGBAColor(r, g, b, a)` | + +### 跨脚本通信 + +| 我想... | 用这个 | +|---------|--------| +| 发送消息给其他脚本 | `world.sendMessage("projectName", data)` | +| 接收其他脚本的消息 | `world.onMessage((from, data) => { ... })` | + +--- + +## 全局对象一览 + +| 对象 | 类型 | 说明 | +|------|------|------| +| `world` | ✅ Box3 | 世界控制,见 [world.md](world.md) | +| `entity` | ✅ Box3 | 实体包装(回调参数或 `world.spawnEntity` 创建),见 [entity.md](entity.md) | +| `player` | ✅ Box3 | 玩家包装(通过 `entity.player` 获取),见 [player.md](player.md) | +| `voxels` | ✅ Box3 | 方块操作,见 [voxels.md](voxels.md) | +| `storage` | ✅ Box3 | 数据持久化,见 [storage.md](storage.md) | +| `db` | ✅ Box3 | SQLite 数据库,见 [database.md](database.md) | +| `console` | ✅ Box3 | 控制台日志输出(`log`/`warn`/`error`/`debug`) | +| `GameVector3` | ✅ Box3 | 三维向量,见 [math.md](math.md) | +| `GameBounds3` | ✅ Box3 | 包围盒,见 [math.md](math.md) | +| `GameRGBColor` | ✅ Box3 | RGB 颜色,见 [math.md](math.md) | +| `GameRGBAColor` | ✅ Box3 | RGBA 颜色,见 [math.md](math.md) | +| `GameQuaternion` | ✅ Box3 | 四元数,见 [math.md](math.md) | + +## API 标注说明 + +| 标注 | 含义 | +|------|------| +| ✅ **Box3 API** | 源自 Box3 平台,命名和语义与 Box3 保持一致 | +| ⬆ **MC 扩展** | 非 Box3 原有,利用 Minecraft 特性新增的 API | + +## 详细文档索引 + +| 文档 | 内容 | +|------|------| +| [world.md](world.md) | 世界状态、事件回调、记分板、BossBar、队伍、边界、粒子、烟花、闪电、音效 | +| [entity.md](entity.md) | 实体属性、AI、装备、药水效果、寻路、标签、碰撞 | +| [player.md](player.md) | 背包、消息、飞行、游戏模式、传送、命令、经验值 | +| [voxels.md](voxels.md) | 方块读写、区域填充、刷怪笼 | +| [storage.md](storage.md) | 数据持久化存储 | +| [database.md](database.md) | SQLite 数据库 | +| [math.md](math.md) | GameVector3、GameBounds3、GameRGBColor、GameRGBAColor、GameQuaternion | +| [commands.md](commands.md) | `/box3script` 命令参考 | + +## 文件模块 — TypeScript 构建管线 + +`/box3script create` 创建的项目自带完整的 TS 构建环境: ``` config/box3/script/mygame/ @@ -68,14 +224,48 @@ config/box3/script/mygame/ ├── tsconfig.json ├── build.mjs ← Babel TS→JS → esbuild bundle → dist/ ├── types/ -│ └── globals.d.ts ← 完整 API 类型声明 (IDE 自动补全) +│ └── globals.d.ts ← 完整 API 类型声明(IDE 自动补全) ├── src/ │ ├── app.ts ← 入口,require() 其他模块 │ ├── state.ts ← 共享游戏状态 -│ ├── course.ts ← 赛道数据与建筑 │ └── ... └── dist/ - └── app.js ← 编译产物(模组实际加载此文件) + ├── app.js ← 编译产物(模组实际加载此文件) + └── -.jar ← 独立 JAR(/box3script compile) ``` -`npm run build` 或 `node build.mjs` 执行构建。`/box3script watch` 可开启文件监控自动热重载。 +`npm run build` 执行构建。`/box3script watch` 开启文件监控自动热重载。 + +## 发布部署 + +开发调试完成后,将脚本编译为**独立 JAR 模组**,无需 Box3JS 即可运行在任意 NeoForge 服务器: + +``` +/box3script compile <项目名> +``` + +生成 `<项目名>-<版本号>.jar`(从 `package.json` 读取 name/displayName/version/description/author/license/homepage/logoFile 等元数据),放入 `mods/` 目录启动即可。 + +详见 [完整命令参考 →](commands.md#box3script-compile-project) + +## Tick 换算 + +| 时长 | Ticks | +|------|-------| +| 1 秒 | 20 | +| 5 秒 | 100 | +| 30 秒 | 600 | +| 1 分钟 | 1200 | +| 5 分钟 | 6000 | + +## 教程 + +从零开始学习 Box3JS 脚本开发,请阅读 `docs/tutorial/` 系列教程: + +| 教程 | 内容 | +|------|------| +| [01-basics.md](../tutorial/01-basics.md) | 从零开始:第一个脚本、聊天命令、定时任务 | +| [02-player-items.md](../tutorial/02-player-items.md) | 玩家操控:传送、物品给予、药水效果、游戏模式 | +| [03-events-entities.md](../tutorial/03-events-entities.md) | 事件系统与实体操控:AI、战斗、巡逻 | +| [04-advanced-systems.md](../tutorial/04-advanced-systems.md) | 高级系统:计分板、BossBar、队伍、世界边界 | +| [05-examples.md](../tutorial/05-examples.md) | 实战示例:PvP 竞技场、特效、烟花、波次刷怪 | diff --git a/Box3JS-NeoForge-1.21.1/docs/api/README_en.md b/Box3JS-NeoForge-1.21.1/docs/api/README_en.md index 6dd7b9a..5376ee7 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/README_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/README_en.md @@ -1,66 +1,222 @@ # Box3JS API Reference -Box3JS is a Minecraft mod that lets you write server-side scripts in JavaScript. All scripts run under `config/box3/script/`. +Box3JS is a Minecraft mod that lets you write server-side scripts in JavaScript/TypeScript. All scripts run under `config/box3/script/`. -## Quick Start +## 5-Minute Quick Start -```js -// app.js — minimal example -world.onTick(() => { - // runs every tick (20 ticks = 1 second) -}); +```bash +# 1. Create a project in-game +/box3script create mygame + +# 2. Install dependencies and build +cd config/box3/script/mygame +npm install && npm run build + +# 3. Start the script +/box3script start mygame +``` + +Open `src/app.ts` and write: -world.onChat((entity, message, tick) => { - var p = entity.player; +```js +world.onChat((entity, message) => { if (message === "!hello") { - p.directMessage("Hello, " + p.name + "!"); + entity.player.directMessage("Hello, " + entity.player.name + "!"); + return false; // suppress chat message } + return true; }); console.log("Script loaded"); ``` +After each edit, re-run `npm run build`, then use `/box3script reload mygame` to hot-reload. + +## Find by Task — I want to... + +Find APIs by what you want to do, not by which global object they live on. + +### Messages & Chat + +| I want to... | Use this | +|-------------|----------| +| Broadcast to server | `world.say("message")` | +| Send private message | `player.directMessage("message")` | +| Show action bar text | `player.actionBar("message")` | +| Show screen title | `player.title("Title", "Subtitle")` | +| Handle chat input | `world.onChat((entity, msg) => { ... })` | + +### Player Properties + +| I want to... | Use this | +|-------------|----------| +| Get/set player position | `player.position` → `GameVector3` | +| Teleport player | `player.teleport(new GameVector3(x, y, z))` | +| Change health | `player.hp = 20` / `player.maxHp = 40` | +| Change hunger | `player.food = 20` / `player.saturation = 10` | +| Switch game mode | `player.gameMode = "creative"` | +| Toggle flight | `player.canFly = true` / `player.flying = true` | +| Kick player | `player.kick("reason")` | +| Run command as player | `player.runCommand("say hi")` | + +### Items & Equipment + +| I want to... | Use this | +|-------------|----------| +| Give a basic item | `player.giveItem("minecraft:diamond", 1)` | +| Give enchanted item | `player.giveEnchantedItem(...)` | +| Give named item | `player.giveNamedItem(...)` | +| Give custom mod item | `player.giveCustomItem("my_item", 1)` | +| Get held item | `player.getHeldItem()` | +| Clear inventory | `player.clearInventory()` | +| Set entity equipment | `entity.setEquipment("head", "iron_helmet")` | +| Load custom item pack | `world.loadCustomItems("mypack")` | + +### Block Operations + +| I want to... | Use this | +|-------------|----------| +| Read a block | `voxels.getVoxel(x, y, z)` | +| Place/replace a block | `voxels.setVoxel(x, y, z, "minecraft:stone")` | +| Fill a region | `voxels.fillVoxel(x1,y1,z1, x2,y2,z2, "stone")` | +| Listen for block breaks | `world.onVoxelDestroy((entity, x, y, z, voxel) => { ... })` | +| Listen for block placement | `world.onBlockPlace((entity, x, y, z, voxel) => { ... })` | + +### Entity Manipulation + +| I want to... | Use this | +|-------------|----------| +| Spawn an entity | `world.spawnEntity("minecraft:zombie", pos)` | +| Create with config | `world.createEntity({ type, position, ... })` | +| Set entity name | `entity.setNameTag("§cBoss")` | +| Toggle AI | `entity.setAI(true)` | +| Navigate to position | `entity.navigateTo(x, y, z, speed)` | +| Set attack target | `entity.setTarget(otherEntity)` | +| Check if player | `entity.isPlayer()` | +| Get entity type | `entity.entityType` | +| Get entity tags | `entity.tags()` / `entity.hasTag("boss")` | +| Query nearby entities | `world.entitiesInRadius(pos, radius)` | +| Query all entities | `world.querySelectorAll("*")` | + +### Visual Effects + +| I want to... | Use this | +|-------------|----------| +| Spawn particles | `world.spawnParticle("flame", x, y, z, ...)` | +| Particle circle | `world.spawnParticleCircle(x, y, z, radius, "heart", 20)` | +| Firework | `world.launchFirework(x, y, z, "red", "large_ball")` | +| Lightning | `world.strikeLightning(x, y, z)` | +| Explosion | `world.explode(x, y, z, power)` | +| Play sound (global) | `world.playSound("pling", pos, 1.0, 1.0)` | +| Play sound (per-player) | `player.playSound("pling", 1.0, 1.0)` | + +### Potion Effects + +| I want to... | Use this | +|-------------|----------| +| Apply an effect | `entity.addEffect("minecraft:speed", duration, level, hideParticles)` | +| Clear all effects | `entity.clearEffects()` | + +### Event System + +| I want to... | Use this | +|-------------|----------| +| Run every tick | `world.onTick((info) => { ... })` | +| On player join | `world.onPlayerJoin((entity, tick) => { ... })` | +| On player leave | `world.onPlayerLeave((entity, tick) => { ... })` | +| On entity death | `world.onEntityDeath((entity, killer, tick) => { ... })` | +| On entity damaged | `world.onEntityDamage((entity, amount, source, attacker, tick) => { ... })` | +| On right-click entity | `world.onInteract((entity, target, tick) => { ... })` | +| On right-click block | `world.onBlockActivate((entity, x, y, z, voxel, tick) => { ... })` | +| On button pressed | `world.onButtonPressed((entity, button, tick) => { ... })` | +| On player respawn | `world.onPlayerRespawn((entity, tick) => { ... })` | +| Run once after delay | `world.setTimeout(() => { ... }, ticks)` | +| Run on interval | `world.setInterval(() => { ... }, ticks)` | +| Cancel event listener | `token.cancel()` | +| Check if active | `token.active()` | + +### Data Persistence + +| I want to... | Use this | +|-------------|----------| +| Read/write JSON data | `storage.getDataStorage("key")` | +| SQL query | `db.sql("SELECT ...")` | +| SQL write | `db.sql("INSERT INTO ...")` | + +### Game Systems + +| I want to... | Use this | +|-------------|----------| +| Create scoreboard | `world.addScoreboard("name")` | +| Set score | `world.setScore("player", "board", 10)` | +| Display scoreboard | `world.showScoreboard("sidebar", "name")` | +| Show BossBar | `world.showBossbar("id", "title", 0.5, "red")` | +| Create team | `world.createTeam("teamName", "color")` | +| Join team | `world.joinTeam(entity, "teamName")` | +| Set world border | `world.borderSize = 500` | +| Shrink border | `world.shrinkBorder(100, 60)` | +| Change time | `world.time = 6000` | +| Set weather | `world.rainDensity = 0` / `world.clearWeather()` | +| Change game rule | `world.setGameRule("keepInventory", true)` | + +### Math Tools + +| I want to... | Use this | +|-------------|----------| +| 3D coordinate | `new GameVector3(x, y, z)` | +| Vector math | `v.add(other)`, `v.scale(n)`, `v.length()` | +| Bounding box | `new GameBounds3(min, max)` | +| Color | `new GameRGBColor(r, g, b)` / `new GameRGBAColor(r, g, b, a)` | + +### Cross-Script Messaging + +| I want to... | Use this | +|-------------|----------| +| Send to another script | `world.sendMessage("projectName", data)` | +| Receive from other scripts | `world.onMessage((from, data) => { ... })` | + +--- + ## Global Objects -| Object | Type | Description | -| ---------------- | ------- | ---------------------------------------------------------------------------------- | -| `world` | ✅ Box3 | World control, see [world.md](world.md) | -| `entity` | ✅ Box3 | Entity wrapper (from callbacks or `world.spawnEntity`), see [entity.md](entity.md) | -| `player` | ✅ Box3 | Player wrapper (via `entity.player`), see [player.md](player.md) | -| `voxels` | ✅ Box3 | Block operations, see [voxels.md](voxels.md) | -| `storage` | ✅ Box3 | Data persistence, see [storage.md](storage.md) | -| `db` | ✅ Box3 | SQLite database, see [database_en.md](database_en.md) | -| `GameVector3` | ✅ Box3 | 3D vector, see [math.md](math.md) | -| `GameBounds3` | ✅ Box3 | Bounding box, see [math.md](math.md) | -| `GameRGBColor` | ✅ Box3 | RGB color, see [math.md](math.md) | -| `GameRGBAColor` | ✅ Box3 | RGBA color, see [math.md](math.md) | -| `GameQuaternion` | ✅ Box3 | Quaternion, see [math.md](math.md) | +| Object | Type | Description | +|--------|------|-------------| +| `world` | ✅ Box3 | World control, see [world_en.md](world_en.md) | +| `entity` | ✅ Box3 | Entity wrapper (from callbacks or `world.spawnEntity`), see [entity_en.md](entity_en.md) | +| `player` | ✅ Box3 | Player wrapper (via `entity.player`), see [player_en.md](player_en.md) | +| `voxels` | ✅ Box3 | Block operations, see [voxels_en.md](voxels_en.md) | +| `storage` | ✅ Box3 | Data persistence, see [storage_en.md](storage_en.md) | +| `db` | ✅ Box3 | SQLite database, see [database_en.md](database_en.md) | +| `console` | ✅ Box3 | Console logging (`log`/`warn`/`error`/`debug`) | +| `GameVector3` | ✅ Box3 | 3D vector, see [math_en.md](math_en.md) | +| `GameBounds3` | ✅ Box3 | Bounding box, see [math_en.md](math_en.md) | +| `GameRGBColor` | ✅ Box3 | RGB color, see [math_en.md](math_en.md) | +| `GameRGBAColor` | ✅ Box3 | RGBA color, see [math_en.md](math_en.md) | +| `GameQuaternion` | ✅ Box3 | Quaternion, see [math_en.md](math_en.md) | ## API Legend -| Label | Meaning | -| ------------------ | ------------------------------------------------------------------ | -| ✅ **Box3 API** | Originates from the Box3 platform; naming and semantics match Box3 | -| ⬆ **MC Extension** | Not in original Box3; added using Minecraft-specific features | - -## Document Index +| Label | Meaning | +|-------|---------| +| ✅ **Box3 API** | Originates from the Box3 platform; naming and semantics match Box3 | +| ⬆ **MC Extension** | Not in original Box3; added using Minecraft-specific features | -| Document | Content | -| -------------------------------- | ----------------------------------------------------------------------------- | -| [world.md](world.md) | World state, events, scoreboard, bossbar, teams, border, particles, fireworks | -| [entity.md](entity.md) | Entity properties, AI, equipment, potions, pathfinding, tags | -| [player.md](player.md) | Inventory, messaging, flight, gamemode, teleport, commands | -| [voxels.md](voxels.md) | Block read/write, region fill, spawner control | -| [storage.md](storage.md) | Persistent data storage | -| [database_en.md](database_en.md) | SQLite database API | -| [math.md](math.md) | Vector3, Bounds3, Color, Quaternion | -| [commands.md](commands.md) | `/box3script` command reference | +## Detailed Document Index -## File Modules +| Document | Content | +|----------|---------| +| [world_en.md](world_en.md) | World state, events, scoreboard, bossbar, teams, border, particles, fireworks, lightning, sounds | +| [entity_en.md](entity_en.md) | Entity properties, AI, equipment, potion effects, pathfinding, tags, collisions | +| [player_en.md](player_en.md) | Inventory, messaging, flight, game mode, teleport, commands, XP | +| [voxels_en.md](voxels_en.md) | Block read/write, region fill, spawner control | +| [storage_en.md](storage_en.md) | Persistent data storage | +| [database_en.md](database_en.md) | SQLite database API | +| [math_en.md](math_en.md) | GameVector3, GameBounds3, GameRGBColor, GameRGBAColor, GameQuaternion | +| [commands_en.md](commands_en.md) | `/box3script` command reference | -**TypeScript build pipeline:** +## File Modules — TypeScript Build Pipeline -Projects created with `/box3script create` come with a complete TS build environment. Write in `src/*.ts`, build outputs to `dist/app.js`: +Projects created with `/box3script create` come with a complete TS build environment: ``` config/box3/script/mygame/ @@ -72,10 +228,44 @@ config/box3/script/mygame/ ├── src/ │ ├── app.ts ← Entry point, require() other modules │ ├── state.ts ← Shared game state -│ ├── course.ts ← Course data & building │ └── ... └── dist/ - └── app.js ← Compiled output (what the mod actually loads) + ├── app.js ← Compiled output (what the mod actually loads) + └── -.jar ← Standalone JAR (/box3script compile) ``` -Run `npm run build` or `node build.mjs` to build. Use `/box3script watch` to enable file watching for auto hot-reload. +Run `npm run build` to build. Use `/box3script watch` to enable file watching for auto hot-reload. + +## Deployment + +When ready to distribute, compile your script into a **standalone JAR mod** that runs on any NeoForge server without Box3JS: + +``` +/box3script compile +``` + +Outputs `-.jar` (metadata read from `package.json`: name, displayName, version, description, author, license, homepage, logoFile). Drop it into `mods/` and start the server. + +See [full command reference →](commands_en.md#box3script-compile-project) + +## Tick Conversion + +| Duration | Ticks | +|----------|-------| +| 1 second | 20 | +| 5 seconds | 100 | +| 30 seconds | 600 | +| 1 minute | 1200 | +| 5 minutes | 6000 | + +## Tutorials + +Learn Box3JS from scratch with the tutorial series in `docs/tutorial/`: + +| Tutorial | Content | +|----------|---------| +| [01-basics.md](../tutorial/01-basics.md) | From zero: first script, chat commands, timers | +| [02-player-items.md](../tutorial/02-player-items.md) | Player controls: teleport, items, potion effects, game modes | +| [03-events-entities.md](../tutorial/03-events-entities.md) | Events & entities: AI, combat, patrols | +| [04-advanced-systems.md](../tutorial/04-advanced-systems.md) | Advanced: scoreboard, BossBar, teams, world border | +| [05-examples.md](../tutorial/05-examples.md) | Real-world: PvP arena, effects, fireworks, wave mobs | diff --git a/Box3JS-NeoForge-1.21.1/docs/api/commands.md b/Box3JS-NeoForge-1.21.1/docs/api/commands.md index 00c2746..c99a410 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/commands.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/commands.md @@ -114,6 +114,70 @@ npm install && npm run build > **注意:** 沙盒仅追踪通过脚本 API 修改的方块(`setVoxel`/`setVoxelId`/`fillVoxel`),手动挖掘不受影响。 +### `/box3script compile ` + +将脚本项目编译为**轻量独立 JAR 模组**(~50KB),依赖 Box3JS 模组提供 Rhino 运行时和 API 绑定。 + +``` +/box3script compile mygame +``` + +> **依赖:** 脚本 JAR 不包含 Rhino 或 Box3JS API 类,需将 Box3JS 模组(`box3js`)一同放入 `mods/`。 + +编译时**从 `package.json` 读取以下字段**写入 `neoforge.mods.toml`: + +| package.json | mods.toml 字段 | 说明 | +|-------------|---------------|------| +| `name` | `modId` | 模组 ID | +| `displayName` | `displayName` | 模组显示名称(默认同 `name`) | +| `version` | `version` | 版本号 | +| `description` | `description` | 模组简介 | +| `author` | `credits` | 作者/致谢 | +| `license` | `license` | 许可证(默认 `All Rights Reserved`) | +| `homepage` | `displayURL` | 项目主页链接 | +| `bugs.url` | `issueTrackerURL` | 问题反馈链接 | +| `logoFile` | `logoFile` | 模组图标(项目中的 PNG 路径,打包为 `logo.png`) | + +> **`logoFile` 使用说明:** 填写项目根目录下的 PNG 文件相对路径(如 `"logoFile": "logo.png"`),编译时自动打包为 JAR 根目录的 `logo.png`,无需在 `neoforge.mods.toml` 中手动配置。NeoForge 建议尺寸 128×128 或 256×256,仅支持 PNG 格式。不填则使用默认模组图标。 + +输出文件名格式:`dist/-.jar`。编译在后台线程运行,不阻塞服务器 tick,完成后聊天栏通知输出路径。 + +**前提条件:** + +- 已完成 `npm run build`(`dist/app.js` 存在) +- 服务器运行在 **JDK**(不是 JRE),因为需要调用 `javac` 编译生成的 `@Mod` 入口类 + +**输出 JAR 内容:** + +``` +mygame-1.0.0.jar +├── META-INF/neoforge.mods.toml ← 模组元数据(依赖 box3js) +├── logo.png ← 模组图标(如有指定) +├── box3script/mygame/MygameMod.class ← @Mod 入口(含硬编码元数据) +└── box3script/mygame/app.js ← 打包的脚本源码 +``` + +**部署:** 将脚本 JAR 与 Box3JS 模组一起放入 `mods/`: + +``` +mods/ +├── box3js-1.0.0.jar ← Box3JS 主模组 +└── mygame-1.0.0.jar ← 编译的脚本模组 +``` + +**与解释模式的区别:** + +| | 解释模式 | 编译模式 | +|---|---|---| +| 加载方式 | `/box3script start` | 放入 `mods/` 启动服务器 | +| 命令管理 | `/box3script start/stop/reload` | 不受 `/box3script` 管理 | +| 启用/禁用 | `/box3script start/stop` | 增删 `mods/` 下的 JAR,重启服务器 | +| 需要 Box3JS | 是 | 是 | +| 热重载 | 支持 | 不支持(JAR 重启才生效) | +| 适用场景 | 开发调试 | 分发部署 | + +> **注意:** 编译后的 JAR 是标准 NeoForge mod,由 NeoForge mod loader 管理,**不受** `/box3script start/stop/reload` 控制。多个编译 JAR 可同时放入 `mods/`,各自独立运行,互不干扰。 + ## 配置文件 启用/禁用状态保存在 `config/box3/scripts.json`: @@ -138,6 +202,9 @@ config/box3/ │ ├── tsconfig.json │ ├── types/globals.d.ts │ ├── src/app.ts - │ └── dist/app.js ← 编译产物 + │ └── dist/ +│ ├── app.js ← 编译产物 +│ └── -.jar ← 独立 JAR(compile 命令生成) + ├── data/ ← SQLite 数据库 (db API) └── storage/ ← storage API 持久化 ``` diff --git a/Box3JS-NeoForge-1.21.1/docs/api/commands_en.md b/Box3JS-NeoForge-1.21.1/docs/api/commands_en.md index 1401415..84e0c58 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/commands_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/commands_en.md @@ -112,6 +112,70 @@ Typical workflow: > **Note:** Sandbox only tracks blocks placed through script APIs (`setVoxel`/`setVoxelId`/`fillVoxel`). Manual mining is unaffected. +### `/box3script compile ` + +Compiles a script project into a **lightweight standalone JAR mod** (~50KB) that depends on the Box3JS mod for Rhino runtime and API bindings. + +``` +/box3script compile mygame +``` + +> **Dependency:** Script JARs do not bundle Rhino or Box3JS API classes. Place the Box3JS mod (`box3js`) alongside your script JAR(s) in `mods/`. + +The compiler **reads the following `package.json` fields** and writes them to `neoforge.mods.toml`: + +| package.json | mods.toml field | Description | +|-------------|---------------|-------------| +| `name` | `modId` | Mod identifier | +| `displayName` | `displayName` | Display name (defaults to `name`) | +| `version` | `version` | Mod version | +| `description` | `description` | Mod description | +| `author` | `credits` | Author / credits | +| `license` | `license` | License (defaults to `All Rights Reserved`) | +| `homepage` | `displayURL` | Project homepage link | +| `bugs.url` | `issueTrackerURL` | Issue tracker link | +| `logoFile` | `logoFile` | Mod icon (PNG path in project, bundled as `logo.png`) | + +> **`logoFile` usage:** Set to a relative path of a PNG file in the project root (e.g. `"logoFile": "logo.png"`). The file is automatically bundled as `logo.png` in the JAR root — no manual `neoforge.mods.toml` config needed. NeoForge recommends 128×128 or 256×256, PNG format only. Leave empty for the default mod icon. + +Output filename format: `dist/-.jar`. Compilation runs on a background thread — no server tick blocking. The output path is shown in chat on completion. + +**Prerequisites:** + +- Project must be built (`npm run build`) — `dist/app.js` must exist +- Server must run on **JDK** (not JRE), as `javac` is needed to compile the generated `@Mod` entry class + +**Output JAR contents:** + +``` +mygame-1.0.0.jar +├── META-INF/neoforge.mods.toml ← mod metadata (depends on box3js) +├── logo.png ← mod icon (if specified) +├── box3script/mygame/MygameMod.class ← @Mod entry point (hardcoded metadata) +└── box3script/mygame/app.js ← bundled script source +``` + +**Deployment:** Place the script JAR alongside the Box3JS mod in `mods/`: + +``` +mods/ +├── box3js-1.0.0.jar ← Box3JS main mod +└── mygame-1.0.0.jar ← compiled script mod +``` + +**Interpreted vs Compiled:** + +| | Interpreted | Compiled | +|---|---|---| +| Load via | `/box3script start` | Drop in `mods/`, start server | +| Command control | `/box3script start/stop/reload` | Not managed by `/box3script` | +| Enable/disable | `/box3script start/stop` | Add/remove JAR from `mods/`, restart server | +| Requires Box3JS | Yes | Yes | +| Hot reload | Yes | No (restart server to update) | +| Use case | Development & debugging | Distribution & deployment | + +> **Note:** Compiled JARs are standard NeoForge mods managed by the NeoForge mod loader. They are **not** controlled by `/box3script start/stop/reload`. Multiple compiled JARs can coexist in `mods/` — each runs independently with its own hardcoded metadata. + ## Configuration File Enable/disable state is saved in `config/box3/scripts.json`: @@ -136,6 +200,9 @@ config/box3/ │ ├── tsconfig.json │ ├── types/globals.d.ts │ ├── src/app.ts - │ └── dist/app.js ← compiled output + │ └── dist/ +│ ├── app.js ← compiled output +│ └── -.jar ← standalone JAR (compile command) + ├── data/ ← SQLite database (db API) └── storage/ ← storage API persistence ``` diff --git a/Box3JS-NeoForge-1.21.1/docs/api/database.md b/Box3JS-NeoForge-1.21.1/docs/api/database.md index 2d485ba..9ee6a04 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/database.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/database.md @@ -86,17 +86,18 @@ db.sql`INSERT INTO players (name, score, lastLogin) VALUES (${"Steve"}, ${100}, ### 查询数据 ```js -// 获取所有行 — 用 for 循环,不要用 .map()(Rhino 的 NativeArray 不支持 ES5 数组方法) +// TypeScript 可直接用 .map() / .filter() / .forEach() / for...of / 箭头函数 +// (Babel 编译为 Rhino 兼容的 for 循环) var rows = db.sql("SELECT * FROM players WHERE score > ?", 50).rows; -for (var i = 0; i < rows.length; i++) { - console.log(rows[i].name + ": " + rows[i].score); -} +rows.forEach((row) => { + console.log(`${row.name}: ${row.score}`); +}); -// tagged template 风格 -var rows = db.sql`SELECT * FROM players WHERE score > ${50}`.rows; -for (var i = 0; i < rows.length; i++) { - console.log(rows[i].name + ": " + rows[i].score); -} +// tagged template + .filter() + .map() 链式调用 +var scores = db.sql`SELECT name, score FROM players WHERE score > ${50}`.rows + .filter((r) => r.score > 20) + .map((r) => `${r.name}: ${r.score}`); +scores.forEach((s) => console.log(s)); // 获取第一行 var player = db.sql("SELECT * FROM players WHERE name = ?", "Steve").firstRow; @@ -284,11 +285,22 @@ db.sql( ## Rhino 兼容性注意事项 -Box3JS 使用 Rhino 1.9.1 引擎,不支持部分 ES5 特性: +Box3JS 使用 Rhino 1.9.1 引擎。**TypeScript 项目用 `npm run build` 编译后,以下特性均可直接使用**(Babel 插件自动转为 Rhino 兼容代码): + +| 特性 | 编译方式 | +|------|---------| +| 箭头函数 `(x) => x + 1` | Babel `@babel/preset-env` | +| 模板字面量 `` `Hello ${name}` `` | `rhinoTemplatePlugin` | +| `for...of` (JS 数组 + Java ArrayList) | `rhinoForOfPlugin` → 索引 for 循环 + `.toArray()` | +| `.map()` `.filter()` `.forEach()` `.find()` `.some()` `.every()` | `rhinoArrayMethodsPlugin` → IIFE + for 循环 | +| `const` / `let` | Babel `@babel/preset-env` | +| 解构 `const { x, y } = obj` | Babel `@babel/preset-env` | + +**纯 JS 脚本注意事项:** -- **`result.rows` 返回 `NativeArray`**,不支持 `.map()`、`.filter()`、`.forEach()` 等 ES5 数组方法,请使用 for 循环。 -- **避免正则字面量**(如 `/\s+/`),改用字符串方法(如 `split(" ")` + filter)。 -- **箭头函数、模板字面量、展开运算符** — TypeScript 编译时会自动转成 ES5,但写纯 JS 时注意避开。 +- `result.rows` 返回 `NativeArray`,不支持 ES5 数组方法,请使用 for 循环。 +- 避免正则字面量(如 `/\s+/`),改用字符串方法。 +- 箭头函数、模板字面量、for...of 等需要用 TypeScript 编译后才能使用。 ## 注意事项 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/database_en.md b/Box3JS-NeoForge-1.21.1/docs/api/database_en.md index 3fbe906..a3e8c33 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/database_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/database_en.md @@ -86,18 +86,137 @@ db.sql`INSERT INTO players (name, score, lastLogin) VALUES (${"Steve"}, ${100}, ### Query data ```js -// Iterate with for-loop; Rhino NativeArray does not support ES5 array helpers. +// TypeScript: .map() / .filter() / .forEach() / for...of / arrow functions all work +// (Babel compiles them to Rhino-compatible indexed for loops) var rows = db.sql("SELECT * FROM players WHERE score > ?", 50).rows; -for (var i = 0; i < rows.length; i++) { - console.log(rows[i].name + ": " + rows[i].score); -} +rows.forEach((row) => { + console.log(`${row.name}: ${row.score}`); +}); + +// tagged template + .filter() + .map() chaining +var scores = db.sql`SELECT name, score FROM players WHERE score > ${50}`.rows + .filter((r) => r.score > 20) + .map((r) => `${r.name}: ${r.score}`); +scores.forEach((s) => console.log(s)); var player = db.sql("SELECT * FROM players WHERE name = ?", "Steve").firstRow; if (player) { - console.log("Score: " + player.score); + console.log(`Score: ${player.score}`); +} +``` + +## Complete Example: Leaderboard + +```js +// Initialize table +db.sql( + "CREATE TABLE IF NOT EXISTS leaderboard (player TEXT PRIMARY KEY, score INTEGER, updated INTEGER)", +); + +// Record a score +function recordScore(playerName, score) { + var existing = db.sql( + "SELECT score FROM leaderboard WHERE player = ?", + playerName, + ).firstRow; + if (existing) { + if (score > existing.score) { + db.sql( + "UPDATE leaderboard SET score = ?, updated = ? WHERE player = ?", + score, + Date.now(), + playerName, + ); + } + } else { + db.sql( + "INSERT INTO leaderboard (player, score, updated) VALUES (?, ?, ?)", + playerName, + score, + Date.now(), + ); + } +} + +// Get Top 10 +function getTop10() { + return db.sql( + "SELECT player, score FROM leaderboard ORDER BY score DESC LIMIT 10", + ).rows; +} + +// Get player rank +function getRank(playerName) { + var row = db.sql( + "SELECT COUNT(*) + 1 AS rank FROM leaderboard WHERE score > (SELECT score FROM leaderboard WHERE player = ?)", + playerName, + ).firstRow; + return row ? row.rank : 0; +} + +// Usage +recordScore("Steve", 500); +recordScore("Alex", 800); +recordScore("Steve", 600); // update + +var top = getTop10(); +for (var i = 0; i < top.length; i++) { + console.log(i + 1 + ". " + top[i].player + " - " + top[i].score); } + +console.log("Steve rank: " + getRank("Steve")); +``` + +## Complete Example: Player Data Persistence + +```js +db.sql( + "CREATE TABLE IF NOT EXISTS player_data (uuid TEXT PRIMARY KEY, name TEXT, playtime INTEGER, deaths INTEGER, lastSeen INTEGER)", +); + +world.onPlayerJoin(function (entity) { + var p = entity.player; + var row = db.sql( + "SELECT * FROM player_data WHERE uuid = ?", + p.userId, + ).firstRow; + if (row) { + db.sql( + "UPDATE player_data SET name = ?, lastSeen = ? WHERE uuid = ?", + p.name, + Date.now(), + p.userId, + ); + } else { + db.sql( + "INSERT INTO player_data (uuid, name, playtime, deaths, lastSeen) VALUES (?, ?, 0, 0, ?)", + p.userId, + p.name, + Date.now(), + ); + } +}); + +world.onPlayerLeave(function (entity) { + var p = entity.player; + db.sql( + "UPDATE player_data SET lastSeen = ? WHERE uuid = ?", + Date.now(), + p.userId, + ); +}); ``` +## Comparison with storage + +| | `db` (SQLite) | `storage` (JSON) | +| ---- | ----------------------------- | ------------------------------- | +| Query | SQL WHERE/JOIN/ORDER BY/LIMIT | Read all, filter in JS | +| Write | Single-row atomic | Full overwrite | +| Best for | Leaderboards, economy, logs, relational data | Config, flags, simple key-value | +| File | `data/.db` | `storage//.json` | +| Concurrency | Naturally safe (WAL mode) | Serial per-project is adequate | + ## Notes - Database files are auto-created. @@ -106,6 +225,25 @@ if (player) { - SQLite uses dynamic typing; integers/floats are adapted automatically. - BLOB values are passed as `Uint8Array`/byte-array style data. +## Rhino Compatibility + +Box3JS uses the Rhino 1.9.1 engine. **TypeScript projects compiled with `npm run build` can use all modern syntax** — Babel plugins convert it to Rhino-compatible code: + +| Feature | Compilation | +|---------|------------| +| Arrow functions `(x) => x + 1` | Babel `@babel/preset-env` | +| Template literals `` `Hello ${name}` `` | `rhinoTemplatePlugin` | +| `for...of` (JS arrays + Java ArrayList) | `rhinoForOfPlugin` → indexed for + `.toArray()` | +| `.map()` `.filter()` `.forEach()` `.find()` `.some()` `.every()` | `rhinoArrayMethodsPlugin` → IIFE + for loop | +| `const` / `let` | Babel `@babel/preset-env` | +| Destructuring `const { x, y } = obj` | Babel `@babel/preset-env` | + +**Plain JS notes:** + +- `result.rows` returns a `NativeArray` — use indexed for loops. +- Avoid regex literals (e.g. `/\s+/`) — use string methods. +- Arrow functions, template literals, `for...of` require TypeScript compilation. + ## Tagged Template Safety Only bind **values** with `${...}`. Do not bind SQL identifiers (table/column names). diff --git a/Box3JS-NeoForge-1.21.1/docs/api/entity.md b/Box3JS-NeoForge-1.21.1/docs/api/entity.md index 8ad15a5..230807c 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/entity.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/entity.md @@ -188,6 +188,36 @@ entity.glowing = true; console.log(entity.glowing); ``` +### entity.setGlowColor(color) + +⬆ MC 扩展 | 设置发光轮廓颜色。通过队伍颜色实现,映射 RGB 到最接近的 `ChatFormatting`(16 色)。 + +```js +entity.glowing = true; +entity.setGlowColor(new GameRGBColor(1, 0, 0)); // 红色发光 +entity.setGlowColor(new GameRGBColor(0, 0, 1)); // 蓝色发光 +``` + +### entity.setText(text) + +⬆ MC 扩展 | 设置文字展示实体的文本内容(仅 `minecraft:text_display` 实体有效)。 + +### entity.setTextColor(color) + +⬆ MC 扩展 | 设置文字展示实体的文本颜色。 + +### entity.setTextBackgroundColor(color) + +⬆ MC 扩展 | 设置文字展示实体的背景颜色,`GameRGBAColor` 可用于半透明背景。 + +```js +// 创建文字展示实体 +var textEntity = world.createEntity("minecraft:text_display", pos); +textEntity.setText("Hello, World!"); +textEntity.setTextColor(new GameRGBColor(1, 1, 1)); // 白色文字 +textEntity.setTextBackgroundColor(new GameRGBAColor(0, 0, 0, 0.5)); // 半透明黑色背景 +``` + ### entity.nameTag ⬆ MC 扩展 | 获取/设置实体的自定义名称(头上显示的名字,支持颜色代码)。空字符串 = 无名称。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/entity_en.md b/Box3JS-NeoForge-1.21.1/docs/api/entity_en.md index 6bbe495..3def9f8 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/entity_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/entity_en.md @@ -58,7 +58,7 @@ entity.velocity.set(2, 0, 2); // Horizontal velocity ### entity.onGround -⬆ MC extension | Readonly. Whether the entity is standing on a block. +⬆ MC Extension | Readonly. Whether the entity is standing on a block. ```js if (entity.onGround) { @@ -68,7 +68,7 @@ if (entity.onGround) { ### entity.eyePosition -⬆ MC extension | Readonly `GameVector3`. Eye position (raycast origin). +⬆ MC Extension | Readonly `GameVector3`. Eye position (raycast origin). ```js var eye = entity.eyePosition; @@ -105,7 +105,7 @@ zombie.heal(5); // Heal 5 ### entity.invulnerable -⬆ MC extension | Gets/sets whether the entity is invulnerable (immune to damage). +⬆ MC Extension | Gets/sets whether the entity is invulnerable (immune to damage). ```js entity.invulnerable = true; @@ -181,16 +181,46 @@ console.log(entity.meshInvisible); ### entity.glowing -⬆ MC extension | Gets/sets the glow outline effect (similar to spectral arrow). +⬆ MC Extension | Gets/sets the glow outline effect (similar to spectral arrow). ```js entity.glowing = true; console.log(entity.glowing); ``` +### entity.setGlowColor(color) + +⬆ MC Extension | Sets the glow outline color via team color, mapping RGB to the nearest `ChatFormatting` (16 colors). + +```js +entity.glowing = true; +entity.setGlowColor(new GameRGBColor(1, 0, 0)); // Red glow +entity.setGlowColor(new GameRGBColor(0, 0, 1)); // Blue glow +``` + +### entity.setText(text) + +⬆ MC Extension | Sets the text content of a text display entity (only effective on `minecraft:text_display` entities). + +### entity.setTextColor(color) + +⬆ MC Extension | Sets the text color for text display entities. + +### entity.setTextBackgroundColor(color) + +⬆ MC Extension | Sets the background color for text display entities. `GameRGBAColor` can be used for semi-transparent backgrounds. + +```js +// Create a text display entity +var textEntity = world.createEntity("minecraft:text_display", pos); +textEntity.setText("Hello, World!"); +textEntity.setTextColor(new GameRGBColor(1, 1, 1)); // White text +textEntity.setTextBackgroundColor(new GameRGBAColor(0, 0, 0, 0.5)); // Semi-transparent black background +``` + ### entity.nameTag -⬆ MC extension | Gets/sets the entity's custom display name (supports color codes). Empty string = no name. +⬆ MC Extension | Gets/sets the entity's custom display name (supports color codes). Empty string = no name. ```js entity.nameTag = "§cBoss Mob"; @@ -240,11 +270,11 @@ var bosses = world.querySelectorAll(".boss"); ### entity.setFire(ticks) -⬆ MC extension | Sets the entity on fire for the given number of ticks. 20 ticks = 1 second. +⬆ MC Extension | Sets the entity on fire for the given number of ticks. 20 ticks = 1 second. ### entity.clearFire() -⬆ MC extension | Extinguishes any fire on the entity. +⬆ MC Extension | Extinguishes any fire on the entity. ```js entity.setFire(100); // Ignite for 5 seconds @@ -255,7 +285,7 @@ entity.clearFire(); // Extinguish immediately ### entity.setAI(enabled) -⬆ MC extension | Enables/disables the entity's AI (Mob only). When disabled, the entity won't move or attack. +⬆ MC Extension | Enables/disables the entity's AI (Mob only). When disabled, the entity won't move or attack. ```js entity.setAI(false); // Freeze entity @@ -263,15 +293,15 @@ entity.setAI(false); // Freeze entity ### entity.setTarget(target) -⬆ MC extension | Sets the mob's attack target (Mob only). The mob will pathfind to and attack it. +⬆ MC Extension | Sets the mob's attack target (Mob only). The mob will pathfind to and attack it. ### entity.getTarget() -⬆ MC extension | Returns the current attack target, or `null`. +⬆ MC Extension | Returns the current attack target, or `null`. ### entity.clearTarget() -⬆ MC extension | Clears the attack target, stopping pursuit. +⬆ MC Extension | Clears the attack target, stopping pursuit. ```js var boss = world.spawnEntity("minecraft:skeleton", new GameVector3(0, 100, 0)); @@ -283,7 +313,7 @@ boss.clearTarget(); ### entity.navigateTo(x, y, z, speed) -⬆ MC extension | Orders a pathfinder mob to navigate to the given coordinates. Returns `true` if path calculation succeeded. +⬆ MC Extension | Orders a pathfinder mob to navigate to the given coordinates. Returns `true` if path calculation succeeded. ### entity.navigateTo(pos, speed) @@ -296,7 +326,7 @@ entity.navigateTo(target.position, 1.0); ### entity.lookAt(x, y, z) -⬆ MC extension | Makes the entity look at the given coordinates. +⬆ MC Extension | Makes the entity look at the given coordinates. ### entity.lookAt(pos) @@ -309,7 +339,7 @@ entity.lookAt(target.position); ## Status Effects -All ⬆ MC extension. +All ⬆ MC Extension. ### entity.addEffect(effectId, duration, amplifier) @@ -333,7 +363,7 @@ entity.addEffect("minecraft:glowing", 200, 0); // Glowing 10 se ## Equipment -All ⬆ MC extension. +All ⬆ MC Extension. ### entity.setEquipment(slot, itemId) @@ -366,7 +396,7 @@ entity.setDropChance("all", 0); // Drop nothing ## Attributes -All ⬆ MC extension. +All ⬆ MC Extension. ### entity.getAttribute(attributeId) @@ -405,7 +435,7 @@ entity.setOnDestroy(function(e) { ### entity.setPersistent(v) -⬆ MC extension | When `true`, prevents the mob from despawning naturally (Mob only). Write-only method, no getter. +⬆ MC Extension | When `true`, prevents the mob from despawning naturally (Mob only). Write-only method, no getter. ```js var boss = world.spawnEntity( diff --git a/Box3JS-NeoForge-1.21.1/docs/api/math.md b/Box3JS-NeoForge-1.21.1/docs/api/math.md index 05ac38c..aa200ef 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/math.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/math.md @@ -33,6 +33,8 @@ var v = new GameVector3(x, y, z); // 指定坐标 | `v.subEq(w)` | `GameVector3` | 原地减法:`v -= w` | | `v.mulEq(w)` | `GameVector3` | 原地逐分量乘法:`v.x *= w.x` … | | `v.divEq(w)` | `GameVector3` | 原地逐分量除法,除以 0 跳过该分量 | +| `v.scaleEq(n)` | `GameVector3` | 原地标量乘法:`v.x *= n` … | +| `v.negEq()` | `GameVector3` | 原地取反:`v = -v` | #### 创建新向量 (不修改自身) @@ -50,6 +52,11 @@ var v = new GameVector3(x, y, z); // 指定坐标 | `v.towards(w)` | `GameVector3` | 指向 `w` 的方向向量 (已单位化) | | `v.max(w)` | `GameVector3` | 逐分量取较大值 | | `v.min(w)` | `GameVector3` | 逐分量取较小值 | +| `v.neg()` | `GameVector3` | 取反:`-v` | +| `v.moveTowards(target, maxDelta)` | `GameVector3` | 向目标移动不超过 `maxDelta` 距离 | +| `v.floor()` | `GameVector3` | 逐分量向下取整 | +| `v.ceil()` | `GameVector3` | 逐分量向上取整 | +| `v.clampLength(max)` | `GameVector3` | 限制长度至 `max`,超长则等比缩放 | #### 数值计算 @@ -60,6 +67,7 @@ var v = new GameVector3(x, y, z); // 指定坐标 | `v.sqrMag()` | `number` | 长度平方,比 `mag()` 更快 | | `v.distance(w)` | `number` | 与 `w` 的欧几里得距离 | | `v.angle(w)` | `number` | 与 `w` 的夹角 (弧度, 0–π) | +| `v.sqrDistance(w)` | `number` | 与 `w` 的距离平方,比 `distance()` 更快 | #### 比较 @@ -67,6 +75,7 @@ var v = new GameVector3(x, y, z); // 指定坐标 |------|--------|------| | `v.equals(w)` | `boolean` | 近似相等,容差 1e-6 | | `v.exactEquals(w)` | `boolean` | 精确相等,分量完全一致 | +| `v.isZero()` | `boolean` | 是否为 (接近) 零向量,容差 1e-6 | ```js var pos = new GameVector3(0, 100, 0); @@ -140,6 +149,14 @@ var bounds = new GameBounds3( | `bounds.intersect(other)` | `GameBounds3 \| null` | 计算交集包围盒,不相交返回 `null` | | `bounds.contains(v)` | `boolean` | 点 `v` 是否在包围盒内 (含边界) | | `bounds.containsBounds(b)` | `boolean` | 是否完全包含另一个包围盒 `b` | +| `bounds.center()` | `GameVector3` | 包围盒中心点 | +| `bounds.size()` | `GameVector3` | 包围盒尺寸 (宽, 高, 深) | +| `bounds.expand(delta)` | `GameBounds3` | 各面向外扩展 `delta`,返回新包围盒 | +| `bounds.expandEq(delta)` | `GameBounds3` | 原地各面向外扩展 `delta`,返回自身 | +| `bounds.growToInclude(v)` | `GameBounds3` | 原地扩展以包含点 `v`,返回自身 | +| `bounds.closestPoint(v)` | `GameVector3` | 包围盒上离点 `v` 最近的点 | +| `bounds.move(offset)` | `GameBounds3` | 平移 `offset`,返回新包围盒 | +| `bounds.moveEq(offset)` | `GameBounds3` | 原地平移 `offset`,返回自身 | ### 静态方法 @@ -199,6 +216,7 @@ var gray = new GameRGBColor(0.5, 0.5, 0.5); | `c.subEq(o)` | `GameRGBColor` | 原地减法:`c -= o` | | `c.mulEq(o)` | `GameRGBColor` | 原地逐通道乘法 | | `c.divEq(o)` | `GameRGBColor` | 原地逐通道除法,除以 0 跳过该通道 | +| `c.scaleEq(n)` | `GameRGBColor` | 原地标量乘法:每个通道乘以 `n` | #### 创建新颜色 (不修改自身) @@ -210,6 +228,7 @@ var gray = new GameRGBColor(0.5, 0.5, 0.5); | `c.mul(o)` | `GameRGBColor` | 逐通道乘法 | | `c.div(o)` | `GameRGBColor` | 逐通道除法,除以 0 得 0 | | `c.lerp(o, t)` | `GameRGBColor` | 线性插值:`t=0` 为自身,`t=1` 为 `o` | +| `c.scale(n)` | `GameRGBColor` | 标量乘法:每个通道乘以 `n` | | `c.equals(o)` | `boolean` | 近似相等,容差 1e-6 | | `c.toRGBA()` | `string` | 转为 CSS 格式:`"rgba(r,g,b,1.0)"` | @@ -259,6 +278,7 @@ var opaque = new GameRGBAColor(0, 1, 0, 1.0); | `c.subEq(o)` | `GameRGBAColor` | 原地减法 | | `c.mulEq(o)` | `GameRGBAColor` | 原地逐通道乘法 | | `c.divEq(o)` | `GameRGBAColor` | 原地逐通道除法,除以 0 跳过该通道 | +| `c.scaleEq(n)` | `GameRGBAColor` | 原地标量乘法:每个通道乘以 `n` | #### 创建新颜色 (不修改自身) @@ -270,6 +290,7 @@ var opaque = new GameRGBAColor(0, 1, 0, 1.0); | `c.mul(o)` | `GameRGBAColor` | 逐通道乘法 | | `c.div(o)` | `GameRGBAColor` | 逐通道除法,除以 0 得 0 | | `c.lerp(o, t)` | `GameRGBAColor` | 线性插值 | +| `c.scale(n)` | `GameRGBAColor` | 标量乘法:每个通道乘以 `n` | | `c.equals(o)` | `boolean` | 近似相等,容差 1e-6 | | `c.blendEq(rgb)` | `GameRGBColor` | Alpha 混合到 RGB 背景上,返回最终 RGB | @@ -347,6 +368,8 @@ var q = new GameQuaternion(w, x, y, z); // 指定分量 | `q.rotateX(rad)` | `GameQuaternion` | 绕 X 轴旋转 | | `q.rotateY(rad)` | `GameQuaternion` | 绕 Y 轴旋转 | | `q.rotateZ(rad)` | `GameQuaternion` | 绕 Z 轴旋转 | +| `q.rotateVector(v)` | `GameVector3` | 用此四元数旋转向量 `v` | +| `q.toEuler()` | `GameVector3` | 转为欧拉角 (YZX 顺序),返回 `(x, y, z)` 弧度 | #### 轴角分解 @@ -370,6 +393,12 @@ var q2 = GameQuaternion.fromEuler(x, y, z); // 从向量 a 旋转到向量 b 的最短弧 var q3 = GameQuaternion.rotationBetween(fromVec, toVec); + +// 从观察方向创建四元数 (从 from 看向 to) +var q4 = GameQuaternion.lookAt(from, to, up); +// from: GameVector3 — 观察者位置 +// to: GameVector3 — 目标点 +// up: GameVector3 — 上方向 (默认 (0,1,0)) ``` ### toString diff --git a/Box3JS-NeoForge-1.21.1/docs/api/math_en.md b/Box3JS-NeoForge-1.21.1/docs/api/math_en.md index f3c1431..aada06a 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/math_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/math_en.md @@ -33,6 +33,8 @@ var v = new GameVector3(x, y, z); // Specified coordinates | `v.subEq(w)` | `GameVector3` | In-place subtraction: `v -= w` | | `v.mulEq(w)` | `GameVector3` | In-place component-wise multiplication | | `v.divEq(w)` | `GameVector3` | In-place component-wise division; divide-by-zero skips that component | +| `v.scaleEq(n)` | `GameVector3` | In-place scalar multiplication: `v.x *= n` … | +| `v.negEq()` | `GameVector3` | In-place negation: `v = -v` | #### Creating New Vectors (does not mutate) @@ -50,6 +52,11 @@ var v = new GameVector3(x, y, z); // Specified coordinates | `v.towards(w)` | `GameVector3` | Direction vector pointing toward `w` (normalized) | | `v.max(w)` | `GameVector3` | Component-wise maximum | | `v.min(w)` | `GameVector3` | Component-wise minimum | +| `v.neg()` | `GameVector3` | Negation: `-v` | +| `v.moveTowards(target, maxDelta)` | `GameVector3` | Move toward target by at most `maxDelta` distance | +| `v.floor()` | `GameVector3` | Component-wise floor | +| `v.ceil()` | `GameVector3` | Component-wise ceiling | +| `v.clampLength(max)` | `GameVector3` | Clamp magnitude to `max`, scale down proportionally if exceeded | #### Numeric Computations @@ -60,6 +67,7 @@ var v = new GameVector3(x, y, z); // Specified coordinates | `v.sqrMag()` | `number` | Squared magnitude — faster than `mag()` | | `v.distance(w)` | `number` | Euclidean distance to `w` | | `v.angle(w)` | `number` | Angle between `v` and `w` (radians, 0–π) | +| `v.sqrDistance(w)` | `number` | Squared distance to `w` — faster than `distance()` | #### Comparison @@ -67,6 +75,7 @@ var v = new GameVector3(x, y, z); // Specified coordinates |--------|---------|-------------| | `v.equals(w)` | `boolean` | Approximate equality, tolerance 1e-6 | | `v.exactEquals(w)` | `boolean` | Exact equality — components strictly equal | +| `v.isZero()` | `boolean` | Whether this is (approximately) a zero vector, tolerance 1e-6 | ```js var pos = new GameVector3(0, 100, 0); @@ -140,6 +149,14 @@ var bounds = new GameBounds3( | `bounds.intersect(other)` | `GameBounds3 \| null` | Intersection bounds, or `null` if no overlap | | `bounds.contains(v)` | `boolean` | Whether point `v` is inside (inclusive) | | `bounds.containsBounds(b)` | `boolean` | Whether this fully contains `b` | +| `bounds.center()` | `GameVector3` | Center point of the bounds | +| `bounds.size()` | `GameVector3` | Size of the bounds (width, height, depth) | +| `bounds.expand(delta)` | `GameBounds3` | Expand all faces outward by `delta`, returns new bounds | +| `bounds.expandEq(delta)` | `GameBounds3` | In-place expand all faces outward by `delta`, returns this | +| `bounds.growToInclude(v)` | `GameBounds3` | In-place grow to include point `v`, returns this | +| `bounds.closestPoint(v)` | `GameVector3` | Closest point on the bounds to point `v` | +| `bounds.move(offset)` | `GameBounds3` | Translate by `offset`, returns new bounds | +| `bounds.moveEq(offset)` | `GameBounds3` | In-place translate by `offset`, returns this | ### Static Methods @@ -199,6 +216,7 @@ var gray = new GameRGBColor(0.5, 0.5, 0.5); | `c.subEq(o)` | `GameRGBColor` | In-place subtraction: `c -= o` | | `c.mulEq(o)` | `GameRGBColor` | In-place channel-wise multiplication | | `c.divEq(o)` | `GameRGBColor` | In-place channel-wise division; divide-by-zero skips | +| `c.scaleEq(n)` | `GameRGBColor` | In-place scalar multiplication: each channel × `n` | #### Creating New Colors (does not mutate) @@ -210,6 +228,7 @@ var gray = new GameRGBColor(0.5, 0.5, 0.5); | `c.mul(o)` | `GameRGBColor` | Channel-wise multiplication | | `c.div(o)` | `GameRGBColor` | Channel-wise division; divide-by-zero → 0 | | `c.lerp(o, t)` | `GameRGBColor` | Linear interpolation: `t=0` → this, `t=1` → `o` | +| `c.scale(n)` | `GameRGBColor` | Scalar multiplication: each channel × `n` | | `c.equals(o)` | `boolean` | Approximate equality, tolerance 1e-6 | | `c.toRGBA()` | `string` | CSS format string: `"rgba(r,g,b,1.0)"` | @@ -259,6 +278,7 @@ var opaque = new GameRGBAColor(0, 1, 0, 1.0); | `c.subEq(o)` | `GameRGBAColor` | In-place subtraction | | `c.mulEq(o)` | `GameRGBAColor` | In-place channel-wise multiplication | | `c.divEq(o)` | `GameRGBAColor` | In-place channel-wise division; divide-by-zero skips | +| `c.scaleEq(n)` | `GameRGBAColor` | In-place scalar multiplication: each channel × `n` | #### Creating New Colors (does not mutate) @@ -270,6 +290,7 @@ var opaque = new GameRGBAColor(0, 1, 0, 1.0); | `c.mul(o)` | `GameRGBAColor` | Channel-wise multiplication | | `c.div(o)` | `GameRGBAColor` | Channel-wise division; divide-by-zero → 0 | | `c.lerp(o, t)` | `GameRGBAColor` | Linear interpolation | +| `c.scale(n)` | `GameRGBAColor` | Scalar multiplication: each channel × `n` | | `c.equals(o)` | `boolean` | Approximate equality, tolerance 1e-6 | | `c.blendEq(rgb)` | `GameRGBColor` | Alpha-blend onto an RGB background, returns displayed RGB | @@ -352,6 +373,8 @@ var q = new GameQuaternion(w, x, y, z); // Specified components | `q.rotateX(rad)` | `GameQuaternion` | Rotate around X axis | | `q.rotateY(rad)` | `GameQuaternion` | Rotate around Y axis | | `q.rotateZ(rad)` | `GameQuaternion` | Rotate around Z axis | +| `q.rotateVector(v)` | `GameVector3` | Rotate vector `v` by this quaternion | +| `q.toEuler()` | `GameVector3` | Convert to Euler angles (YZX order), returns `(x, y, z)` in radians | #### Axis-Angle Decomposition @@ -375,6 +398,12 @@ var q2 = GameQuaternion.fromEuler(x, y, z); // Shortest-arc quaternion rotating from vector a to b var q3 = GameQuaternion.rotationBetween(fromVec, toVec); + +// Create quaternion from look-at direction (from → to) +var q4 = GameQuaternion.lookAt(from, to, up); +// from: GameVector3 — observer position +// to: GameVector3 — target point +// up: GameVector3 — up direction (default (0,1,0)) ``` ### toString diff --git a/Box3JS-NeoForge-1.21.1/docs/api/player.md b/Box3JS-NeoForge-1.21.1/docs/api/player.md index 5385365..215a51a 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/player.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/player.md @@ -316,6 +316,15 @@ player.kick("你已被移出游戏"); ✅ Box3 API | 向玩家发送聊天栏消息(仅该玩家可见的系统消息)。 +### player.directMessage(msg, color) + +⬆ MC 扩展 | 发送带颜色的聊天消息。 + +```js +player.directMessage("操作成功!", new GameRGBColor(0, 1, 0)); // 绿色 +player.directMessage("警告!", new GameRGBColor(1, 0.5, 0)); // 橙色 +``` + ### player.actionBar(msg) ✅ Box3 API | 向玩家发送快捷栏上方消息(Action Bar)。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/player_en.md b/Box3JS-NeoForge-1.21.1/docs/api/player_en.md index 92f9c46..5757e00 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/player_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/player_en.md @@ -163,7 +163,7 @@ player.disableFly = true; ### player.collision -⬆ MC extension | Gets/sets team collision. Set to `false` to prevent players from pushing each other. Backed by the player's team `CollisionRule` (ALWAYS / NEVER). +⬆ MC Extension | Gets/sets team collision. Set to `false` to prevent players from pushing each other. Backed by the player's team `CollisionRule` (ALWAYS / NEVER). ```js player.collision = false; // Disable collision @@ -172,7 +172,7 @@ console.log(player.collision); ## Health -⬆ MC extension | Gets/sets player health. `ServerPlayer` is a `LivingEntity`, so health is operated on directly. +⬆ MC Extension | Gets/sets player health. `ServerPlayer` is a `LivingEntity`, so health is operated on directly. ### player.hp @@ -240,7 +240,7 @@ Readonly `GameVector3`. A point 5 blocks ahead of the player's eyes. ### player.lookAt(x, y, z) -⬆ MC extension | Makes the player look at the given coordinates. +⬆ MC Extension | Makes the player look at the given coordinates. ### player.lookAt(pos) @@ -285,7 +285,7 @@ console.log(player.spawnPoint); ### player.dimension -⬆ MC extension | Gets/sets the player's dimension. Setting it performs a cross-dimensional teleport. +⬆ MC Extension | Gets/sets the player's dimension. Setting it performs a cross-dimensional teleport. ```js player.teleport(new GameVector3(0, 100, 0)); @@ -316,6 +316,15 @@ player.kick("You have been removed from the game"); ✅ Box3 API | Sends a chat message visible only to this player (system message). +### player.directMessage(msg, color) + +⬆ MC Extension | Sends a colored chat message. + +```js +player.directMessage("Success!", new GameRGBColor(0, 1, 0)); // Green +player.directMessage("Warning!", new GameRGBColor(1, 0.5, 0)); // Orange +``` + ### player.actionBar(msg) ✅ Box3 API | Sends a message displayed on the action bar (above the hotbar). @@ -326,7 +335,7 @@ player.kick("You have been removed from the game"); ### player.title(title, subtitle, fadeIn, stay, fadeOut) -⬆ MC extension | Title with full animation parameters. `fadeIn`/`stay`/`fadeOut` are all in ticks (20 ticks = 1 second). +⬆ MC Extension | Title with full animation parameters. `fadeIn`/`stay`/`fadeOut` are all in ticks (20 ticks = 1 second). ### player.dialog(config) @@ -367,19 +376,19 @@ player.onChat(function(entity, msg, tick) { ### player.xp -⬆ MC extension | Gets/sets experience level. +⬆ MC Extension | Gets/sets experience level. ### player.addExperienceLevels(levels) -⬆ MC extension | Adds `levels` experience levels. +⬆ MC Extension | Adds `levels` experience levels. ### player.food -⬆ MC extension | Gets/sets food level (0–20). +⬆ MC Extension | Gets/sets food level (0–20). ### player.saturation -⬆ MC extension | Gets/sets saturation level (0–20, floating-point). +⬆ MC Extension | Gets/sets saturation level (0–20, floating-point). ```js player.xp = 10; // Set to level 10 @@ -390,7 +399,7 @@ player.saturation = 10; ## Inventory -All ⬆ MC extension. +All ⬆ MC Extension. ### player.giveItem(itemId, count) @@ -420,7 +429,7 @@ player.giveEnchantedItem("minecraft:bow", 1, { ### player.giveCustomItem(id, count) -⬆ MC extension | Gives a custom item loaded via `world.loadCustomItems()`. Items use `minecraft:paper` as a carrier with DataComponents for name, texture, food, etc. +⬆ MC Extension | Gives a custom item loaded via `world.loadCustomItems()`. Items use `minecraft:paper` as a carrier with DataComponents for name, texture, food, etc. ```js // Load config first @@ -468,15 +477,15 @@ player.clearInventory(); ### player.addEffect(effectId, duration, amplifier) -⬆ MC extension | Applies a status effect. `duration` in ticks, `amplifier` starts at 0. +⬆ MC Extension | Applies a status effect. `duration` in ticks, `amplifier` starts at 0. ### player.addEffect(effectId, duration, amplifier, hideParticles) -⬆ MC extension | Applies an effect, optionally hiding particles. +⬆ MC Extension | Applies an effect, optionally hiding particles. ### player.clearEffects() -⬆ MC extension | Removes all status effects. +⬆ MC Extension | Removes all status effects. ```js player.addEffect("minecraft:speed", 600, 2); @@ -488,11 +497,11 @@ player.clearEffects(); ### player.playSound(path, volume, pitch) -⬆ MC extension | Plays a sound to this player only. `path` is a namespace ID (e.g. `"minecraft:block.note_block.pling"`), `volume` 0–1, `pitch` 0.5–2. +⬆ MC Extension | Plays a sound to this player only. `path` is a namespace ID (e.g. `"minecraft:block.note_block.pling"`), `volume` 0–1, `pitch` 0.5–2. ### player.runCommand(cmd) -⬆ MC extension | Executes a Minecraft command as this player. +⬆ MC Extension | Executes a Minecraft command as this player. ```js player.playSound("minecraft:block.note_block.pling", 0.8, 1.5); @@ -503,11 +512,11 @@ player.runCommand("say hello"); ### player.grantAdvancement(advancementId) -⬆ MC extension | Grants an advancement to this player. +⬆ MC Extension | Grants an advancement to this player. ### player.revokeAdvancement(advancementId) -⬆ MC extension | Revokes an advancement from this player. +⬆ MC Extension | Revokes an advancement from this player. ```js player.grantAdvancement("minecraft:story/mine_stone"); @@ -519,7 +528,7 @@ player.revokeAdvancement("minecraft:story/mine_stone"); ### player.setPlayerListName(name) -⬆ MC extension | Changes the player's display name in the tab list (supports color codes). +⬆ MC Extension | Changes the player's display name in the tab list (supports color codes). ```js player.setPlayerListName("§e[CP3] §f" + player.name); diff --git a/Box3JS-NeoForge-1.21.1/docs/api/world.md b/Box3JS-NeoForge-1.21.1/docs/api/world.md index b4d5ba0..569ce53 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/world.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/world.md @@ -104,11 +104,11 @@ console.log(world.difficulty); // "hard" ### world.spawnPoint -⬆ MC 扩展 | 只读,返回世界出生点 `GameVector3`。 +✅ Box3 API | 只读,返回世界出生点 `GameVector3`。 ### world.setWorldSpawn(pos) -⬆ MC 扩展 | 设置世界出生点。 +✅ Box3 API | 设置世界出生点。 ```js world.setWorldSpawn(new GameVector3(0, 70, 0)); @@ -270,9 +270,31 @@ var token = world.onTick(function (info) { | `world.onEntityDeath(fn)` | ⬆ MC | `(entity, killer, tick)` | 实体死亡;`killer` 可能为 null | | `world.onEntityDamage(fn)` | ⬆ MC | `(entity, amount, source, attacker, tick)` | 实体受伤(Pre 阶段) | | `world.onPlayerRespawn(fn)` | ⬆ MC | `(entity, tick)` | 玩家重生 | -| `world.onButtonPressed(fn)` | ⬆ MC | `(entity, button, tick)` | 玩家按下按钮(见 GameButtonType) | +| `world.onButtonPressed(fn)` | ⬆ MC | `(entity, button, tick)` | 玩家按下按钮 | | `world.onMessage(fn)` | ⬆ MC | `(from, data)` | 收到 `world.sendMessage()` 消息 | +### GameButtonType + +`world.onButtonPressed` 回调的 `button` 参数为以下字符串常量之一: + +| 常量 | 说明 | +| ---------- | ---------- | +| `"WALK"` | 行走(长按) | +| `"RUN"` | 奔跑(长按) | +| `"CROUCH"` | 潜行(长按) | +| `"JUMP"` | 跳跃 | +| `"FLY"` | 飞行(长按) | +| `"ACTION0"` | 屏幕按钮0(轻点) | +| `"ACTION1"` | 屏幕按钮1(轻点) | + +```js +world.onButtonPressed((entity, button, tick) => { + if (button === "JUMP") { + player.directMessage("你按下了跳跃!"); + } +}); +``` + 所有 `onXxx()` 方法返回 `GameEventHandlerToken` — 调用 `.cancel()` 取消监听。 ```js @@ -576,6 +598,18 @@ world.launchFirework(0, 100, 0, "gold", "large_ball"); world.launchFirework(new GameVector3(0, 100, 0), "red", "star"); ``` +### world.launchFirework(x, y, z, colors, shape) + +⬆ MC 扩展 | 使用 `GameRGBColor[]` 数组指定烟花颜色,支持任意 RGB 色彩。 + +### world.launchFirework(pos, colors, shape) + +⬆ GameVector3 + `GameRGBColor[]` 重载。 + +```js +world.launchFirework(0, 100, 0, [new GameRGBColor(1, 0, 0), new GameRGBColor(1, 0.5, 0)], "large_ball"); +``` + ### world.spawnParticle(type, x, y, z, count, dx, dy, dz, speed) 在坐标生成粒子。粒子类型使用命名空间 ID。 @@ -584,6 +618,22 @@ world.launchFirework(new GameVector3(0, 100, 0), "red", "star"); ⬆ GameVector3 重载。 +### world.spawnParticle(x, y, z, color, count, dx, dy, dz, speed) + +⬆ MC 扩展 | 生成彩色粒子(类型为 `dust`),使用 `GameRGBColor` 指定颜色。 + +### world.spawnParticle(pos, color, count, dx, dy, dz, speed) + +⬆ GameVector3 + `GameRGBColor` 重载。 + +```js +// 生成红色粒子 +world.spawnParticle(0, 100, 0, new GameRGBColor(1, 0, 0), 20, 0.5, 0.5, 0.5, 0.1); + +// 生成青色粒子 +world.spawnParticle(entity.position, new GameRGBColor(0, 1, 1), 10, 0.2, 0.2, 0.2, 0); +``` + ### world.spawnParticleCircle(x, y, z, radius, type, count) 在水平圆形上均匀生成粒子。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/world_en.md b/Box3JS-NeoForge-1.21.1/docs/api/world_en.md index bca8bd3..e5800c7 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/world_en.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/world_en.md @@ -106,11 +106,11 @@ console.log(world.difficulty); // "hard" ### world.spawnPoint -⬆ MC Extension | Read-only, returns the world spawn point as `GameVector3`. +✅ Box3 API | Read-only, returns the world spawn point as `GameVector3`. ### world.setWorldSpawn(pos) -⬆ MC Extension | Set the world spawn point. +✅ Box3 API | Set the world spawn point. ```js world.setWorldSpawn(new GameVector3(0, 70, 0)); @@ -272,9 +272,31 @@ var token = world.onTick(function (info) { | `world.onEntityDeath(fn)` | ⬆ MC | `(entity, killer, tick)` | Entity dies; `killer` may be null | | `world.onEntityDamage(fn)` | ⬆ MC | `(entity, amount, source, attacker, tick)` | Entity takes damage (Pre phase) | | `world.onPlayerRespawn(fn)` | ⬆ MC | `(entity, tick)` | Player respawns | -| `world.onButtonPressed(fn)` | ⬆ MC | `(entity, button, tick)` | Player presses a button (see GameButtonType) | +| `world.onButtonPressed(fn)` | ⬆ MC | `(entity, button, tick)` | Player presses a button | | `world.onMessage(fn)` | ⬆ MC | `(from, data)` | Receives `world.sendMessage()` message | +### GameButtonType + +The `button` parameter in `world.onButtonPressed` callbacks is one of the following string constants: + +| Constant | Description | +| ----------- | ------------------ | +| `"WALK"` | Walk (hold) | +| `"RUN"` | Run / sprint (hold) | +| `"CROUCH"` | Crouch / sneak (hold) | +| `"JUMP"` | Jump | +| `"FLY"` | Fly (hold) | +| `"ACTION0"` | Screen button 0 (tap) | +| `"ACTION1"` | Screen button 1 (tap) | + +```js +world.onButtonPressed((entity, button, tick) => { + if (button === "JUMP") { + player.directMessage("You pressed jump!"); + } +}); +``` + All `onXxx()` methods return `GameEventHandlerToken` — call `.cancel()` to unregister. ```js @@ -578,6 +600,18 @@ world.launchFirework(0, 100, 0, "gold", "large_ball"); world.launchFirework(new GameVector3(0, 100, 0), "red", "star"); ``` +### world.launchFirework(x, y, z, colors, shape) + +⬆ MC Extension | Launches a firework with an array of `GameRGBColor` values for arbitrary RGB colors. + +### world.launchFirework(pos, colors, shape) + +⬆ GameVector3 + `GameRGBColor[]` overload. + +```js +world.launchFirework(0, 100, 0, [new GameRGBColor(1, 0, 0), new GameRGBColor(1, 0.5, 0)], "large_ball"); +``` + ### world.spawnParticle(type, x, y, z, count, dx, dy, dz, speed) Spawn particles at coordinates. Particle type uses namespaced ID. @@ -586,6 +620,22 @@ Spawn particles at coordinates. Particle type uses namespaced ID. ⬆ GameVector3 overload. +### world.spawnParticle(x, y, z, color, count, dx, dy, dz, speed) + +⬆ MC Extension | Spawns colored particles (dust type) using `GameRGBColor` to specify the color. + +### world.spawnParticle(pos, color, count, dx, dy, dz, speed) + +⬆ GameVector3 + `GameRGBColor` overload. + +```js +// Spawn red particles +world.spawnParticle(0, 100, 0, new GameRGBColor(1, 0, 0), 20, 0.5, 0.5, 0.5, 0.1); + +// Spawn cyan particles +world.spawnParticle(entity.position, new GameRGBColor(0, 1, 1), 10, 0.2, 0.2, 0.2, 0); +``` + ### world.spawnParticleCircle(x, y, z, radius, type, count) Spawn particles evenly on a horizontal circle. @@ -752,6 +802,61 @@ console.log(biome); // "minecraft:plains" var biome = world.getBiome(entity.position); ``` +## Custom Items + +### world.loadCustomItems(packName) + +⬆ MC Extension | Loads custom item definitions from a resource pack's `items.json`. Reads `resourcepacks//items.json`, parses item definitions using Minecraft's native data component IDs as JSON keys. All items use `minecraft:paper` as the base, with `DataComponents` providing name, lore, texture, food, etc. + +JSON format uses MC component ID prefixes: + +| JSON Key | DataComponent | Description | +| -------------------------------------- | ---------------------------- | ------------------------------------------------------ | +| `minecraft:custom_model_data` | `CUSTOM_MODEL_DATA` | Model predicate value, matched by paper.json overrides | +| `minecraft:custom_name` | `CUSTOM_NAME` | Display name | +| `minecraft:lore` | `LORE` | Lore text array | +| `minecraft:max_stack_size` | `MAX_STACK_SIZE` | Max stack size (1–64), default 64 | +| `minecraft:enchantment_glint_override` | `ENCHANTMENT_GLINT_OVERRIDE` | Enchantment foil effect | +| `minecraft:rarity` | `RARITY` | Rarity: `common`/`uncommon`/`rare`/`epic` | +| `minecraft:food` | `FOOD` | Food properties (see sub-fields below) | + +**`minecraft:food` sub-fields:** + +| Sub-field | Type | Description | +| ---------------- | ----- | -------------------------------- | +| `nutrition` | int | Nutrition value (1–20) | +| `saturation` | float | Saturation modifier | +| `can_always_eat` | bool | Always edible | +| `eat_seconds` | float | Eat time in seconds, ≤0.8 = fast | + +```js +world.loadCustomItems("box3js-items"); +// Loads all items defined in resourcepacks/box3js-items/items.json +// Items can then be given via player.giveCustomItem("arena_trophy", 1) +``` + +**Resource pack structure reference:** + +``` +resourcepacks/box3js-items/ +├── pack.mcmeta +├── items.json # Item definitions +└── assets/ + ├── minecraft/models/item/ + │ └── paper.json # custom_model_data overrides + └── box3js/ + ├── models/item/ # Model JSONs + │ ├── arena_trophy.json + │ ├── arena_stew.json + │ └── arena_medal.json + └── textures/item/ # PNG textures + ├── arena_trophy.png + ├── arena_stew.png + └── arena_medal.png +``` + +**Note:** Textures require the client to load the resource pack. Without it, items still function (name/lore/food), but display the default paper texture. + ## Cross-script Messaging ### world.sendMessage(target, data) @@ -771,7 +876,7 @@ world.runCommand("weather clear"); ### world.placeStructure(x, y, z, structureId) -⬆ MC extension | Places a datapack structure template (NBT) at the given position. +⬆ MC Extension | Places a datapack structure template (NBT) at the given position. ### world.placeStructure(pos, structureId) @@ -789,7 +894,7 @@ world.placeStructure(pos, "box3js:arena"); ### world.grantAdvancement(playerName, advancementId) -⬆ MC extension | Grants an advancement to a player by name. +⬆ MC Extension | Grants an advancement to a player by name. ```js world.grantAdvancement("Steve", "minecraft:story/mine_stone"); @@ -799,7 +904,7 @@ world.grantAdvancement("Steve", "minecraft:story/mine_stone"); ### world.listRecipes(filter) -⬆ MC extension | Searches recipe IDs matching a keyword. +⬆ MC Extension | Searches recipe IDs matching a keyword. ```js var recipes = world.listRecipes("diamond"); @@ -808,7 +913,7 @@ console.log(recipes); // ["minecraft:diamond_sword", "minecraft:diamond_block", ### world.removeRecipe(recipeId) -⬆ MC extension | Blacklists a recipe so it's no longer craftable. Returns whether successful. +⬆ MC Extension | Blacklists a recipe so it's no longer craftable. Returns whether successful. ```js world.removeRecipe("minecraft:iron_pickaxe"); @@ -816,43 +921,8 @@ world.removeRecipe("minecraft:iron_pickaxe"); ### world.clearRecipes() -⬆ MC extension | Clears the recipe blacklist, restoring all original recipes. +⬆ MC Extension | Clears the recipe blacklist, restoring all original recipes. ```js world.clearRecipes(); ``` - -## Custom Items - -### world.loadCustomItems(packName) - -⬆ MC extension | Loads custom item definitions from a resource pack's `items.json`. Reads `resourcepacks//items.json`, parses item definitions using Minecraft's native data component IDs as JSON keys. All items use `minecraft:paper` as the base, with `DataComponents` providing name, lore, texture, food, etc. - -JSON format uses MC component ID prefixes: - -| JSON Key | DataComponent | Description | -| -------------------------------------- | ---------------------------- | ------------------------------------------------------ | -| `minecraft:custom_model_data` | `CUSTOM_MODEL_DATA` | Model predicate value, matched by paper.json overrides | -| `minecraft:custom_name` | `CUSTOM_NAME` | Display name | -| `minecraft:lore` | `LORE` | Lore text array | -| `minecraft:max_stack_size` | `MAX_STACK_SIZE` | Max stack size (1–64), default 64 | -| `minecraft:enchantment_glint_override` | `ENCHANTMENT_GLINT_OVERRIDE` | Enchantment foil effect | -| `minecraft:rarity` | `RARITY` | Rarity: `common`/`uncommon`/`rare`/`epic` | -| `minecraft:food` | `FOOD` | Food properties (see sub-fields below) | - -**`minecraft:food` sub-fields:** - -| Sub-field | Type | Description | -| ---------------- | ----- | -------------------------------- | -| `nutrition` | int | Nutrition value (1–20) | -| `saturation` | float | Saturation modifier | -| `can_always_eat` | bool | Always edible | -| `eat_seconds` | float | Eat time in seconds, ≤0.8 = fast | - -```js -world.loadCustomItems("box3js-items"); -// Loads all items defined in resourcepacks/box3js-items/items.json -// Items can then be given via player.giveCustomItem("arena_trophy", 1) -``` - -**Note:** Textures require the client to load the resource pack. Without it, items still function (name/lore/food), but display the default paper texture. diff --git a/Box3JS-NeoForge-1.21.1/docs/tutorial/01-basics.md b/Box3JS-NeoForge-1.21.1/docs/tutorial/01-basics.md index 84f0f1d..8bafbf6 100644 --- a/Box3JS-NeoForge-1.21.1/docs/tutorial/01-basics.md +++ b/Box3JS-NeoForge-1.21.1/docs/tutorial/01-basics.md @@ -1,117 +1,134 @@ -# 教程一:从零开始 +# 教程一:5 分钟上手 Box3JS -本教程将带你创建第一个 Box3JS 脚本,逐步掌握控制台输出、欢迎特效、聊天命令和定时任务。 +本教程带你从零创建第一个 Box3JS 脚本——不需要任何 Minecraft 模组开发经验,会 JavaScript 就能写。 ## 前置要求 - 服务端已安装 Box3JS 模组 -- 了解基础的 JavaScript/TypeScript 语法 +- 了解基础 JavaScript/TypeScript 语法 -## 1.1 创建项目 +## 第一步:创建项目 -在游戏内执行: +在游戏内执行一条命令: ``` -/box3script create mytutorial +/box3script create hello ``` -这会在 `config/box3/script/mytutorial/` 下创建一个 TypeScript 项目模板。如果你想用纯 JavaScript,直接把 `src/app.ts` 当 JS 写即可——构建工具不会阻止你。 +这会在 `config/box3/script/hello/` 下生成一个完整的 TypeScript 项目。其中 `src/app.ts` 就是你要写代码的地方。 -目录结构: +## 第二步:构建 -``` -config/box3/script/mytutorial/ -├── src/ -│ └── app.ts ← 入口文件,代码写在这里 -├── types/ -│ └── globals.d.ts ← API 类型声明(只读参考) -├── build.mjs ← 构建脚本 -├── package.json -└── tsconfig.json -``` - -每次改完代码后,在 `mytutorial/` 目录下执行: +打开终端,进入项目目录: ```bash +cd config/box3/script/hello npm install && npm run build ``` -构建成功后,在游戏内开启脚本: - -``` -/box3script start mytutorial -``` +`npm install` 只需执行一次。之后每次修改代码只需要 `npm run build`。 -## 1.2 第一个脚本 +## 第三步:写你的第一个脚本 打开 `src/app.ts`,清空内容,写入: ```js -console.log("[MyTutorial] Hello, Box3JS!"); +console.log("Hello, Box3JS!"); -world.onPlayerJoin((entity, tick) => { +world.onPlayerJoin((entity) => { entity.player.directMessage("§a欢迎来到服务器!"); }); ``` -构建并开启脚本后,玩家加入就会看到欢迎消息。 +**这就够了**——不需要 import、不需要初始化,`world` 和 `console` 是模组提供的全局对象。 + +## 第四步:启动 + +回到游戏内: -`console` 对象有 4 个级别: +``` +/box3script start hello +``` + +现在让一个玩家加入服务器,他会收到 "§a欢迎来到服务器!" 的绿色消息。服务端控制台会输出 `[Box3JS] [hello] Hello, Box3JS!`。 + +## 第五步:改代码 + 热重载 + +试着把欢迎消息改成: ```js -console.log("普通日志"); // [Box3JS] [mytutorial] 普通日志 -console.debug("调试信息"); // [Box3JS] [mytutorial] [DEBUG] 调试信息 -console.warn("警告"); // [Box3JS] [mytutorial] [WARN] 警告 -console.error("错误"); // [Box3JS] [mytutorial] [ERROR] 错误 +entity.player.directMessage("§6你好," + entity.player.name + "!"); ``` -输出会显示在服务端控制台,格式为 `[Box3JS] [项目名] message`。 +保存后执行 `npm run build`,然后在游戏内: + +``` +/box3script reload hello +``` + +不需要重启服务器,改动立刻生效。 + +--- + +以上 5 步就是完整的开发循环:**改代码 → build → reload**。下文深入讲解你能用的所有能力。 -## 1.3 带特效的欢迎消息 +## 消息系统 -纯文字欢迎太无聊了,加一点视觉效果: +在写聊天命令之前,得先知道有哪些方式给玩家发消息。 + +### 四种消息类型 ```js -world.onPlayerJoin((entity, _tick) => { - const p = entity.player; +// 1. 全服广播 — 聊天栏,所有人都看到 +world.say("全体玩家注意!"); - // 屏幕标题欢迎 - p.title("§6§l欢迎来到服务器!", "§7输入 §f!help §7查看命令", 5, 70, 10); +// 2. 私密消息 — 聊天栏,只有目标玩家看到 +player.directMessage("这条消息只有你能看到"); - // 动作栏提示 - p.actionBar(`§a欢迎 ${p.name} §a| 在线: §f${world.querySelectorAll("*").length}`); +// 3. 动作栏 — 快捷栏上方的小字 +player.actionBar("快捷栏上方的提示"); - // 出生粒子特效 — 绿色粒子圈 - const pos = p.position; - world.spawnParticleCircle( - pos.x, pos.y, pos.z, 1.5, - "minecraft:happy_villager", 15 - ); - // 音效 - world.playSound("minecraft:block.note_block.pling", pos, 1.0, 1.5); -}); +// 4. 屏幕标题 — 屏幕中央大字 +player.title("§6§l主标题", "§7副标题"); +// 带时间的标题: (主标题, 副标题, 淡入tick, 停留tick, 淡出tick) +player.title("§c§lBOSS", "远古巨龙", 10, 60, 10); ``` -效果:玩家加入时看到标题、听到铃铛声、身边冒出绿色粒子圈。 +| 方法 | 位置 | 可见范围 | +|------|------|---------| +| `world.say()` | 聊天栏 | 全服 | +| `player.directMessage()` | 聊天栏 | 单人 | +| `player.actionBar()` | 快捷栏上方 | 单人 | +| `player.title()` | 屏幕中央 | 单人 | + +### console 日志 + +`console` 输出到服务端控制台,格式为 `[Box3JS] [项目名] message`: + +```js +console.log("普通日志"); // [Box3JS] [hello] 普通日志 +console.debug("调试信息"); // [Box3JS] [hello] [DEBUG] 调试信息 +console.warn("警告"); // [Box3JS] [hello] [WARN] 警告 +console.error("错误"); // [Box3JS] [hello] [ERROR] 错误 +``` -## 1.4 聊天命令系统 +## 聊天命令系统 -用 `world.onChat` 拦截聊天消息实现命令: +用 `world.onChat` 拦截聊天消息,实现自定义命令: ```js -world.onChat((entity, message, _tick) => { +world.onChat((entity, message) => { const p = entity.player; switch (message) { case "!help": - p.directMessage("§6── 服务器命令 ──"); + p.directMessage("§6── 命令帮助 ──"); p.directMessage("§f!hello §7- 打招呼"); - p.directMessage("§f!time §7- 查看游戏时间"); + p.directMessage("§f!time §7- 查看时间"); p.directMessage("§f!pos §7- 查看坐标"); - p.directMessage("§f!online §7- 在线人数"); p.directMessage("§f!day §7- 设为白天"); p.directMessage("§f!clear §7- 清除天气"); - return false; // 阻止原始消息显示在聊天栏 + return false; // ★ 返回 false 阻止消息显示在聊天栏 case "!hello": p.directMessage(`§e你好,${p.name}!`); @@ -124,20 +141,11 @@ world.onChat((entity, message, _tick) => { case "!pos": { const pos = p.position; p.directMessage( - `§e你的位置: §f${ - Math.floor(pos.x)}, ${ - Math.floor(pos.y)}, ${ - Math.floor(pos.z)}` + `§e你的位置: §f${Math.floor(pos.x)}, ${Math.floor(pos.y)}, ${Math.floor(pos.z)}` ); return false; } - case "!online": - p.directMessage( - `§e在线玩家: §f${world.querySelectorAll("*").length} 人` - ); - return false; - case "!day": world.time = 1000; world.say(`§e${p.name} §f将时间设为白天`); @@ -152,39 +160,63 @@ world.onChat((entity, message, _tick) => { }); ``` -**关键点:** 回调返回 `false` 会阻止消息在聊天栏显示,返回 `true` 则正常发送。 +**关键规则:** 回调返回 `false` 阻止该消息在聊天栏显示,返回 `true` 则正常发送。 + +## 带特效的欢迎消息 + +纯文字太无聊,加一点视觉效果: + +```js +world.onPlayerJoin((entity) => { + const p = entity.player; + + // 屏幕标题 + p.title("§6§l欢迎!", "§7输入 §f!help §7查看帮助", 5, 70, 10); + + // 粒子圈 + 音效 + const pos = p.position; + world.spawnParticleCircle(pos.x, pos.y, pos.z, 1.5, "minecraft:happy_villager", 15); + world.playSound("minecraft:block.note_block.pling", pos, 1.0, 1.5); +}); +``` + +效果:玩家加入时屏幕出现标题、听到铃铛声、身边冒出绿色粒子圈。 -## 1.5 定时任务 +## 定时任务 ```js -// 每 5 分钟广播一次 (6000 ticks) +// 每 5 分钟广播一次在线人数 world.setInterval(() => { - const online = world.querySelectorAll("*").length; - if (online > 0) { - world.say(`§7[服务器] 当前在线: §f${online} §7人`); - } -}, 6000); + const count = world.querySelectorAll("*").length; + if (count > 0) world.say(`§7在线: §f${count} §7人`); +}, 6000); // 6000 ticks = 5 分钟 -// 30 秒后执行一次 (600 ticks) +// 30 秒后执行一次 world.setTimeout(() => { - world.say("§6[服务器] §f已运行 30 秒"); -}, 600); + world.say("§6服务器已运行 30 秒"); +}, 600); // 600 ticks = 30 秒 ``` -**Ticks 换算:** 20 ticks = 1 秒。`setInterval(fn, 20)` = 每秒执行一次。 +**Tick 换算:** 20 ticks = 1 秒 -## 1.6 世界属性 +| 时长 | Ticks | +|------|-------| +| 1 秒 | 20 | +| 5 秒 | 100 | +| 30 秒 | 600 | +| 1 分钟 | 1200 | +| 5 分钟 | 6000 | + +## 世界属性 ```js -// 时间控制 +// 时间 world.time = 6000; // 正午 (0=日出, 6000=正午, 12000=日落, 18000=午夜) -world.timeScale = 0; // 暂停时间 -world.timeScale = 1; // 恢复 // 天气 -world.rainDensity = 1.0; -world.thunderDensity = 0.5; -world.clearWeather(); // 晴天 +world.rainDensity = 1.0; // 满强度下雨 +world.thunderDensity = 0.5; // 雷暴 +world.clearWeather(); // 晴天 // 难度 world.difficulty = "hard"; // peaceful / easy / normal / hard @@ -195,32 +227,19 @@ world.setGameRule("doFireTick", false); // 火焰不蔓延 world.setGameRule("doMobSpawning", false); // 禁止刷怪 ``` -## 1.7 消息类型汇总 +## 完整整合示例 -```js -world.say("全体可见"); // 全服广播(聊天栏) - -player.directMessage("仅你可见"); // 私密消息(聊天栏) - -player.actionBar("快捷栏上方"); // 动作栏(快捷栏上方) - -player.title("§6§lBOSS名称", "§7副标题"); // 屏幕标题 -player.title("主标题", "副标题", 10, 60, 10); // fadeIn, stay, fadeOut (ticks) -``` - -## 1.8 完整示例 - -把以上整合起来: +把以上所有内容整合到一个脚本: ```js // ═══════════════════════════════════ -// MyTutorial — 入门示例 +// Hello — Box3JS 入门脚本 // ═══════════════════════════════════ -console.log("[MyTutorial] 脚本已加载"); +console.log("[Hello] 脚本已加载"); -// 欢迎特效 -world.onPlayerJoin((entity, _tick) => { +// ── 欢迎特效 ── +world.onPlayerJoin((entity) => { const p = entity.player; p.title("§6§l欢迎来到服务器!", "§7输入 §f!help §7查看命令", 5, 70, 10); const pos = p.position; @@ -228,14 +247,14 @@ world.onPlayerJoin((entity, _tick) => { world.playSound("minecraft:block.note_block.pling", pos, 1.0, 1.5); }); -// 定时公告 +// ── 定时公告 ── world.setInterval(() => { - const online = world.querySelectorAll("*").length; - if (online > 0) world.say(`§7在线: §f${online} §7人`); + const count = world.querySelectorAll("*").length; + if (count > 0) world.say(`§7在线: §f${count} §7人`); }, 6000); -// 聊天命令 -world.onChat((entity, message, _tick) => { +// ── 聊天命令 ── +world.onChat((entity, message) => { const p = entity.player; switch (message) { case "!help": @@ -268,6 +287,38 @@ world.onChat((entity, message, _tick) => { }); ``` +## 常用技巧 + +### 开发循环 + +``` +改代码 → npm run build → /box3script reload hello → 测试 +``` + +开启文件监控自动热重载(无需手动 reload): + +``` +/box3script watch +``` + +### 沙盒模式(安全测试) + +开启沙盒后,脚本对世界的所有修改都会被追踪,关闭时一键回滚: + +``` +/box3script sandbox hello # 开启 +# ... 测试脚本 ... +/box3script sandbox hello # 关闭 → 回滚所有修改 +``` + +### 调试技巧 + +遇到问题时的排查顺序: +1. 检查服务端控制台是否有报错(`console.log` 输出会出现在这里) +2. 确认脚本已加载:`/box3script` 看项目是否显示为 `◉`(已加载运行中) +3. 确认 build 成功:`npm run build` 应该没有错误 +4. 如果语法没问题但逻辑不生效,检查事件回调是否正确注册 + ## 下一步 -教程二将介绍玩家操控:传送、物品给予、药水效果、游戏模式、生命值和经验。 +[教程二:玩家操控与物品](../tutorial/02-player-items.md) — 传送、物品给予、药水效果、游戏模式、生命值、自定义物品。 diff --git a/Box3JS-NeoForge-1.21.1/docs/tutorial/README.md b/Box3JS-NeoForge-1.21.1/docs/tutorial/README.md new file mode 100644 index 0000000..04a0570 --- /dev/null +++ b/Box3JS-NeoForge-1.21.1/docs/tutorial/README.md @@ -0,0 +1,66 @@ +# Box3JS 教程 + +从零开始学习 Box3JS 脚本开发。每个教程约 10-15 分钟,包含可直接运行的完整代码。 + +## 学习路径 + +``` +教程一 教程二 教程三 教程四 教程五 + │ │ │ │ │ +从零开始 → 玩家操控 → 事件系统 → 高级游戏系统 → 实战小游戏 +第一个脚本 物品给予 实体操控 计分板/BossBar PvP 竞技场 +聊天命令 药水效果 方块交互 队伍/边界 波次刷怪 +定时任务 游戏模式 AI/巡逻 跨脚本通信 特效大全 +``` + +## 教程列表 + +| # | 教程 | 你会学到 | +|---|------|---------| +| 1 | [从零开始](01-basics.md) | 创建项目 → 构建 → 第一个脚本 → 聊天命令 → 定时任务 | +| 2 | [玩家操控与物品](02-player-items.md) | 传送、飞行、物品给予、附魔、药水效果、游戏模式、自定义物品 | +| 3 | [事件系统与实体操控](03-events-entities.md) | 全部事件回调、生成实体、AI 控制、巡逻守卫、碰撞检测 | +| 4 | [高级游戏系统](04-advanced-systems.md) | 计分板排名、BossBar 倒计时、队伍分组、世界边界缩圈、跨脚本通信 | +| 5 | [实战小游戏](05-examples.md) | PvP 竞技场(完整可玩)、粒子特效大全、烟花秀、波次刷怪、家传送 | + +## 你需要知道 + +- **语言:** JavaScript/TypeScript。如果你会 JS,直接用 `.ts` 文件当成 JS 写就行。 +- **环境:** 所有代码运行在服务端,不需要客户端安装任何东西。 +- **热重载:** 改完代码 `npm run build` 后 `/box3script reload`,无需重启服务器。 +- **发布部署:** 开发完成后 `/box3script compile` 编译为独立 JAR,丢进任意服务器 `mods/` 即可运行,无需 Box3JS。 +- **API 速查:** 写代码时遇到"这个功能用什么 API",翻 [API 速查表](../api/README.md) 按任务查找。 + +## 最简示例 + +如果你只想看一眼 Box3JS 长什么样: + +```js +// app.ts — 聊天命令 + 定时广播 +world.onChat((entity, message) => { + if (message === "!hello") { + entity.player.directMessage("你好," + entity.player.name + "!"); + return false; + } + return true; +}); + +world.setInterval(() => { + world.say("当前在线: " + world.querySelectorAll("*").length + " 人"); +}, 6000); +``` + +`npm run build` → `/box3script start <项目名>` 即可运行。 + +## 完整 API 文档 + +| 文档 | 说明 | +|------|------| +| [world](../api/world.md) | 世界状态、事件回调、粒子、烟花、音效 | +| [entity](../api/entity.md) | 实体属性、AI、装备、效果 | +| [player](../api/player.md) | 背包、消息、飞行、传送 | +| [voxels](../api/voxels.md) | 方块读写、区域填充 | +| [storage](../api/storage.md) | JSON 数据持久化 | +| [database](../api/database.md) | SQLite 数据库 | +| [math](../api/math.md) | GameVector3、Color、Quaternion | +| [commands](../api/commands.md) | `/box3script` 命令参考 | diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSDatabase.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSDatabase.java index 17334f8..dbc4b25 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSDatabase.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSDatabase.java @@ -15,9 +15,10 @@ import java.util.List; import java.util.Map; -import org.mozilla.javascript.NativeArray; - import com.box3lab.box3js.Box3JS; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; +import org.mozilla.javascript.NativeArray; /** * Per-project SQLite database exposed to JS as the {@code db} global. @@ -56,6 +57,8 @@ */ public class Box3JSDatabase { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String SQLITE_DRIVER_CLASS = "org.sqlite.JDBC"; private static final String SQLITE_MISSING_HINT = "db API requires SQLite JDBC driver. Install the minecraft-sqlite-jdbc mod, then restart server."; private static final boolean SQLITE_AVAILABLE; @@ -71,7 +74,7 @@ public class Box3JSDatabase { ok = true; } catch (ClassNotFoundException e) { ok = false; - Box3JS.LOGGER.warn("{}", SQLITE_MISSING_HINT); + LOGGER.warn("{}", SQLITE_MISSING_HINT); } SQLITE_AVAILABLE = ok; } @@ -157,7 +160,7 @@ public Box3JSQueryResult sql(Object... args) { return new Box3JSQueryResult(count); } } catch (SQLException e) { - Box3JS.LOGGER.error("SQL error: {}", e.getMessage()); + LOGGER.error("SQL error: {}", e.getMessage()); throw new RuntimeException("SQL error: " + e.getMessage(), e); } } @@ -173,9 +176,9 @@ public void closeProject(String project) { if (!conn.isClosed()) { conn.close(); } - Box3JS.LOGGER.debug("Closed database for project: {}", project); + LOGGER.debug("Closed database for project: {}", project); } catch (SQLException e) { - Box3JS.LOGGER.warn("Error closing database for {}: {}", project, e.getMessage()); + LOGGER.warn("Error closing database for {}: {}", project, e.getMessage()); } } } @@ -207,10 +210,10 @@ private Connection getConnection() { try (Statement stmt = conn.createStatement()) { stmt.execute("PRAGMA journal_mode=WAL"); } - Box3JS.LOGGER.info("Opened database for project {}: {}", p, dbFile); + LOGGER.info("Opened database for project {}: {}", p, dbFile); return conn; } catch (IOException | SQLException e) { - Box3JS.LOGGER.error("Failed to open database for project {}: {}", p, e.getMessage()); + LOGGER.error("Failed to open database for project {}: {}", p, e.getMessage()); throw new RuntimeException("Failed to open database: " + e.getMessage(), e); } }); diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSEntity.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSEntity.java index a87ac25..6fe690c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSEntity.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSEntity.java @@ -1,6 +1,8 @@ package com.box3lab.box3js.script; +import net.minecraft.ChatFormatting; import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffect; @@ -12,6 +14,8 @@ import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; import org.mozilla.javascript.Function; import java.util.Map; @@ -113,6 +117,32 @@ public String[] tags() { public boolean isGlowing() { return entity.isCurrentlyGlowing(); } public void setGlowing(boolean v) { trackIfSandboxed(); entity.setGlowingTag(v); } + public void setGlowColor(GameRGBColor color) { + trackIfSandboxed(); + Scoreboard sb = server.getScoreboard(); + String teamName = "b3js_g_" + entity.getStringUUID().replaceAll("-", ""); + PlayerTeam team = sb.getPlayerTeam(teamName); + if (team == null) team = sb.addPlayerTeam(teamName); + team.setColor(closestChatFormatting(color)); + sb.addPlayerToTeam(entity.getScoreboardName(), team); + entity.setGlowingTag(true); + } + + private static ChatFormatting closestChatFormatting(GameRGBColor c) { + ChatFormatting best = ChatFormatting.WHITE; + double bestDist = Double.MAX_VALUE; + for (ChatFormatting cf : ChatFormatting.values()) { + Integer col = cf.getColor(); + if (col == null) continue; + double dr = ((col >> 16) & 0xFF) / 255.0 - c.r; + double dg = ((col >> 8) & 0xFF) / 255.0 - c.g; + double db = (col & 0xFF) / 255.0 - c.b; + double dist = dr * dr + dg * dg + db * db; + if (dist < bestDist) { bestDist = dist; best = cf; } + } + return best; + } + // ---- Name tag (MC extension) ---- public String getNameTag() { @@ -336,6 +366,55 @@ public void setPersistent(boolean v) { if (entity instanceof Mob mob && v) mob.setPersistenceRequired(); } + // ---- TextDisplay (MC extension) ---- + + private static final java.lang.reflect.Method _tdSetText; + private static final java.lang.reflect.Method _tdGetText; + private static final java.lang.reflect.Method _tdSetBgColor; + static { + try { + Class td = net.minecraft.world.entity.Display.TextDisplay.class; + _tdSetText = td.getDeclaredMethod("setText", Component.class); + _tdSetText.setAccessible(true); + _tdGetText = td.getDeclaredMethod("getText"); + _tdGetText.setAccessible(true); + _tdSetBgColor = td.getDeclaredMethod("setBackgroundColor", int.class); + _tdSetBgColor.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException("Failed to access TextDisplay methods", e); + } + } + + public void setText(String text) { + if (entity instanceof net.minecraft.world.entity.Display.TextDisplay td) { + try { _tdSetText.invoke(td, Component.literal(text)); } catch (Exception ignored) {} + } + } + + public void setTextColor(GameRGBColor color) { + if (entity instanceof net.minecraft.world.entity.Display.TextDisplay td) { + try { + Component current = (Component) _tdGetText.invoke(td); + String text = current != null ? current.getString() : ""; + int r = (int) (Math.max(0, Math.min(1, color.r)) * 255); + int g = (int) (Math.max(0, Math.min(1, color.g)) * 255); + int b = (int) (Math.max(0, Math.min(1, color.b)) * 255); + int rgb = (r << 16) | (g << 8) | b; + _tdSetText.invoke(td, Component.literal(text).withColor(rgb)); + } catch (Exception ignored) {} + } + } + + public void setTextBackgroundColor(GameRGBAColor color) { + if (entity instanceof net.minecraft.world.entity.Display.TextDisplay td) { + int r = (int) (Math.max(0, Math.min(1, color.r)) * 255); + int g = (int) (Math.max(0, Math.min(1, color.g)) * 255); + int b = (int) (Math.max(0, Math.min(1, color.b)) * 255); + int a = (int) (Math.max(0, Math.min(1, color.a)) * 255); + try { _tdSetBgColor.invoke(td, (a << 24) | (r << 16) | (g << 8) | b); } catch (Exception ignored) {} + } + } + // ---- Attributes (MC extension) ---- public double getAttribute(String attributeId) { diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSPlayer.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSPlayer.java index 0fece63..77cb812 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSPlayer.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSPlayer.java @@ -308,6 +308,14 @@ public void directMessage(String msg) { player.sendSystemMessage(Component.literal(msg)); } + public void directMessage(String msg, GameRGBColor color) { + int r = (int) (Math.max(0, Math.min(1, color.r)) * 255); + int g = (int) (Math.max(0, Math.min(1, color.g)) * 255); + int b = (int) (Math.max(0, Math.min(1, color.b)) * 255); + int rgb = (r << 16) | (g << 8) | b; + player.sendSystemMessage(Component.literal(msg).withColor(rgb)); + } + public void actionBar(String message) { player.displayClientMessage(Component.literal(message), true); } diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSWorld.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSWorld.java index e451901..e282dc5 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSWorld.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSWorld.java @@ -5,6 +5,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.component.DataComponents; +import net.minecraft.core.particles.DustParticleOptions; import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; @@ -490,6 +491,34 @@ public void launchFirework(GameVector3 pos, String color, String shape) { launchFirework(pos.x, pos.y, pos.z, color, shape); } + public void launchFirework(double x, double y, double z, GameRGBColor[] colors, String shape) { + var colorInts = new it.unimi.dsi.fastutil.ints.IntArrayList(); + for (GameRGBColor c : colors) { + int r = (int) (Math.max(0, Math.min(1, c.r)) * 255); + int g = (int) (Math.max(0, Math.min(1, c.g)) * 255); + int b = (int) (Math.max(0, Math.min(1, c.b)) * 255); + colorInts.add(0xFF000000 | (r << 16) | (g << 8) | b); + } + if (colorInts.isEmpty()) colorInts.add(0xFFFFFFFF); + + FireworkExplosion.Shape fireworkShape = switch (shape != null ? shape.toLowerCase(java.util.Locale.ROOT) : "ball") { + case "large_ball" -> FireworkExplosion.Shape.LARGE_BALL; + case "star" -> FireworkExplosion.Shape.STAR; + case "creeper" -> FireworkExplosion.Shape.CREEPER; + case "burst" -> FireworkExplosion.Shape.BURST; + default -> FireworkExplosion.Shape.SMALL_BALL; + }; + var explosion = new FireworkExplosion(fireworkShape, colorInts, colorInts, false, true); + var fireworks = new Fireworks(1, java.util.List.of(explosion)); + ItemStack rocket = new ItemStack(Items.FIREWORK_ROCKET); + rocket.set(DataComponents.FIREWORKS, fireworks); + var entity = new net.minecraft.world.entity.projectile.FireworkRocketEntity(server.overworld(), x, y, z, rocket); + server.overworld().addFreshEntity(entity); + } + public void launchFirework(GameVector3 pos, GameRGBColor[] colors, String shape) { + launchFirework(pos.x, pos.y, pos.z, colors, shape); + } + // ---- Particle ---- public void spawnParticle(String type, double x, double y, double z, int count, double dx, double dy, double dz, double speed) { @@ -499,6 +528,12 @@ public void spawnParticle(String type, double x, double y, double z, int count, public void spawnParticle(String type, GameVector3 pos, int count, double dx, double dy, double dz, double speed) { spawnParticle(type, pos.x, pos.y, pos.z, count, dx, dy, dz, speed); } + public void spawnParticle(double x, double y, double z, GameRGBColor color, int count, double dx, double dy, double dz, double speed) { + server.overworld().sendParticles(new DustParticleOptions(new org.joml.Vector3f((float) color.r, (float) color.g, (float) color.b), 1.0f), x, y, z, count, dx, dy, dz, speed); + } + public void spawnParticle(GameVector3 pos, GameRGBColor color, int count, double dx, double dy, double dz, double speed) { + spawnParticle(pos.x, pos.y, pos.z, color, count, dx, dy, dz, speed); + } public void spawnParticleCircle(double x, double y, double z, double radius, String type, int count) { var particle = Box3ScriptUtils.lookupParticle(type); if (particle == null) return; diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptCommand.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptCommand.java index 80d92f0..af37b3e 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptCommand.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptCommand.java @@ -6,6 +6,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import com.box3lab.box3js.standalone.Box3ScriptCompiler; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -34,7 +35,8 @@ public static void register(RegisterCommandsEvent event) { .then(stopCommand()) .then(reloadCommand()) .then(watchCommand()) - .then(sandboxCommand())); + .then(sandboxCommand()) + .then(compileCommand())); } // ═══════════════════════════════════════════════════════════ @@ -340,6 +342,102 @@ private static LiteralArgumentBuilder sandboxCommand() { })); } + // ═══════════════════════════════════════════════════════════ + // /box3script compile — 编译为独立 JAR + // /box3script compile runtime — 构建共享 Rhino 运行时 JAR + // ═══════════════════════════════════════════════════════════ + + private static LiteralArgumentBuilder compileCommand() { + return literal("compile") + .then(argument("project", StringArgumentType.word()) + .suggests(Box3ScriptCommand::suggestProjects) + .executes(ctx -> { + String project = StringArgumentType.getString(ctx, "project"); + var config = Box3ScriptConfig.get(); + config.discover(ctx.getSource().getServer()); + if (!config.listProjects().containsKey(project)) { + ctx.getSource().sendFailure( + Component.literal("§cUnknown project: " + project)); + return 0; + } + + Path projectDir = scriptDir(ctx.getSource().getServer()) + .resolve(project).normalize(); + Path appJs = projectDir.resolve("dist/app.js"); + if (!Files.exists(appJs)) { + ctx.getSource().sendFailure( + Component.literal("§cdist/app.js not found — run 'npm run build' first")); + return 0; + } + + // Read package.json for metadata + String[] info = Box3ScriptCompiler.readPackageInfo(projectDir); + String modId = info[0]; + String displayName = info[1]; + String modVersion = info[2]; + String description = info[3]; + String author = info[4]; + String license = info[5]; + String homepage = info[6]; + String bugsUrl = info[7]; + String logoFile = info[8]; + + Path outputJar = projectDir.resolve( + "dist/" + modId + "-" + modVersion + ".jar"); + ctx.getSource().sendSuccess( + () -> Component.literal( + "§7Compiling §f" + project + + " §7→ §f" + modId + "-" + modVersion + ".jar§7..."), + false); + + // Get the running Box3JS mod version for dependency range + String box3jsVersion = net.neoforged.fml.ModList.get() + .getModContainerById(Box3ScriptCompiler.BOX3JS_MOD_ID) + .map(c -> c.getModInfo().getVersion().toString()) + .orElse("0"); + + String finalModId = modId; + String finalDisplayName = displayName; + String finalModVersion = modVersion; + String finalDescription = description; + String finalAuthor = author; + String finalLicense = license; + String finalHomepage = homepage; + String finalBugsUrl = bugsUrl; + String finalLogoFile = logoFile; + String finalBox3jsVersion = box3jsVersion; + // Run on background thread to avoid blocking server + CompletableFuture.runAsync(() -> { + try { + new Box3ScriptCompiler( + projectDir, outputJar, finalModId, finalDisplayName, + finalModVersion, finalDescription, finalAuthor, finalLicense, + finalHomepage, finalBugsUrl, finalLogoFile, finalBox3jsVersion) + .compile(); + ctx.getSource().getServer().execute(() -> { + String jarPath = outputJar.toAbsolutePath().toString(); + Component msg = Component.literal( + "§aCompiled: §f" + jarPath + "\n") + .append(Component.literal( + "§7Deploy this JAR alongside box3js in mods/.")); + ctx.getSource().sendSuccess(() -> msg, false); + }); + } catch (Exception e) { + String err = e.getMessage(); + if (err == null) + err = e.getClass().getSimpleName(); + String finalErr = err; + ctx.getSource().getServer().execute(() -> { + ctx.getSource().sendFailure( + Component.literal("§cCompile failed: " + finalErr)); + }); + } + }); + + return 1; + })); + } + // ═══════════════════════════════════════════════════════════ // helpers // ═══════════════════════════════════════════════════════════ diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptEngine.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptEngine.java index 9a7f87e..1d72c1a 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptEngine.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptEngine.java @@ -1,6 +1,5 @@ package com.box3lab.box3js.script; -import com.box3lab.box3js.Box3JS; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -19,10 +18,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + public class Box3ScriptEngine { + private static final Logger LOGGER = LogUtils.getLogger(); private static final Box3ScriptEngine INSTANCE = new Box3ScriptEngine(); - private static final int MAX_SCRIPT_SLEEP_MS = 10; private ScriptableObject scope; private Box3JSWorld worldBinding; @@ -56,6 +58,29 @@ public void init(MinecraftServer server) { initialized = true; } + /** + * Creates a standalone engine for a compiled JAR script. + * Each standalone JAR gets its own isolated engine, scope, and bindings. + */ + public static Box3ScriptEngine createStandalone(MinecraftServer server, String projectName, Path storageRoot) { + Box3ScriptEngine engine = new Box3ScriptEngine(); + engine.server = server; + engine.currentProject = projectName; + engine.sandbox = new Box3ScriptSandbox(server.overworld()); + engine.worldBinding = new Box3JSWorld(server, engine); + engine.voxelsBinding = new Box3JSVoxels(server, engine.sandbox); + engine.storageBinding = new Box3JSStorage(storageRoot, engine); + engine.dbBinding = new Box3JSDatabase(storageRoot, engine); + engine.setupScope(); + engine.initialized = true; + return engine; + } + + /** Exposed for standalone JAR bootstrap. */ + public ScriptableObject getScope() { + return scope; + } + /** Execute app.js for enabled projects under config/box3/script/ */ public void autoLoad(MinecraftServer server) { init(server); @@ -79,9 +104,9 @@ public void autoLoad(MinecraftServer server) { try { setCurrentProject(name); eval("require('./app')"); - Box3JS.LOGGER.info("Auto-loaded project: {}", name); + LOGGER.info("Auto-loaded project: {}", name); } catch (Exception e) { - Box3JS.LOGGER.error("Failed to auto-load: {}", appJs, e); + LOGGER.error("Failed to auto-load: {}", appJs, e); } finally { setCurrentProject(null); } @@ -105,7 +130,7 @@ public Object eval(String code) { /** Report error to the current errorReporter (player), or just log if none. */ void reportError(String msg) { - Box3JS.LOGGER.error(msg); + LOGGER.error(msg); if (errorReporter != null) errorReporter.accept(msg); } @@ -315,9 +340,9 @@ public void removeProject(String project) { dbBinding.closeProject(project); var summary = sandbox.restoreProject(project); if (summary.hasAny()) { - Box3JS.LOGGER.info("Sandbox [{}] restored: {}", project, summary.toMessage()); + LOGGER.info("Sandbox [{}] restored: {}", project, summary.toMessage()); } - Box3JS.LOGGER.info("Removed project: {}", project); + LOGGER.info("Removed project: {}", project); } /** Check if a project is currently loaded and running. */ @@ -920,37 +945,6 @@ protected String getCharacterEncoding(java.net.URLConnection c) { return req.requireMain(cx, moduleId); } }); - ScriptableObject.putProperty(scope, "sleep", new BaseFunction() { - @Override - public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if (args.length == 0 || !(args[0] instanceof Number)) { - throw ScriptRuntime.throwError(cx, scope, "sleep(ms) requires a numeric millisecond argument"); - } - - int requestedMs = ((Number) args[0]).intValue(); - if (requestedMs < 0) { - throw ScriptRuntime.throwError(cx, scope, "sleep(ms) cannot be negative"); - } - if (requestedMs == 0) { - return Undefined.instance; - } - - int ms = requestedMs; - if (ms > MAX_SCRIPT_SLEEP_MS) { - String project = currentProject != null ? currentProject : ""; - Box3JS.LOGGER.warn("sleep({}) in project {} exceeds safe limit; clamped to {}ms", - requestedMs, project, MAX_SCRIPT_SLEEP_MS); - ms = MAX_SCRIPT_SLEEP_MS; - } - - try { - Thread.sleep(ms); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - return Undefined.instance; - } - }); ScriptableObject.putProperty(scope, "GameVector3", new NativeJavaClass(scope, GameVector3.class)); ScriptableObject.putProperty(scope, "GameBounds3", new NativeJavaClass(scope, GameBounds3.class)); ScriptableObject.putProperty(scope, "GameRGBColor", new NativeJavaClass(scope, GameRGBColor.class)); diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptSandbox.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptSandbox.java index 2ac3cd8..67b1457 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptSandbox.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3ScriptSandbox.java @@ -19,11 +19,16 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ServerLevelData; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + import java.util.*; import java.util.concurrent.ConcurrentHashMap; class Box3ScriptSandbox { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int MAX_BLOCK_CHANGES = 5_000_000; private static final double WARN_THRESHOLD = 0.9; @@ -57,7 +62,7 @@ void trackBlock(String project, BlockPos pos) { if (changes.size() >= MAX_BLOCK_CHANGES) return; changes.putIfAbsent(pos.immutable(), level.getBlockState(pos)); if (changes.size() >= MAX_BLOCK_CHANGES * WARN_THRESHOLD && blockWarnedProjects.add(project)) { - com.box3lab.box3js.Box3JS.LOGGER.warn("[Sandbox:{}] Block tracking at {}% ({} / {})", + LOGGER.warn("[Sandbox:{}] Block tracking at {}% ({} / {})", project, (int)(WARN_THRESHOLD * 100), changes.size(), MAX_BLOCK_CHANGES); } } diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameBounds3.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameBounds3.java index f6045c5..f95be0b 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameBounds3.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameBounds3.java @@ -50,6 +50,56 @@ public boolean containsBounds(GameBounds3 b) { return contains(b.lo) && contains(b.hi); } + public GameVector3 center() { + return new GameVector3( + (lo.x + hi.x) / 2, (lo.y + hi.y) / 2, (lo.z + hi.z) / 2); + } + + public GameVector3 size() { + return new GameVector3(hi.x - lo.x, hi.y - lo.y, hi.z - lo.z); + } + + public GameBounds3 expand(double delta) { + return new GameBounds3( + new GameVector3(lo.x - delta, lo.y - delta, lo.z - delta), + new GameVector3(hi.x + delta, hi.y + delta, hi.z + delta)); + } + + public GameBounds3 expandEq(double delta) { + lo.x -= delta; lo.y -= delta; lo.z -= delta; + hi.x += delta; hi.y += delta; hi.z += delta; + return this; + } + + public GameBounds3 growToInclude(GameVector3 v) { + if (v.x < lo.x) lo.x = v.x; + if (v.y < lo.y) lo.y = v.y; + if (v.z < lo.z) lo.z = v.z; + if (v.x > hi.x) hi.x = v.x; + if (v.y > hi.y) hi.y = v.y; + if (v.z > hi.z) hi.z = v.z; + return this; + } + + public GameVector3 closestPoint(GameVector3 v) { + return new GameVector3( + Math.max(lo.x, Math.min(hi.x, v.x)), + Math.max(lo.y, Math.min(hi.y, v.y)), + Math.max(lo.z, Math.min(hi.z, v.z))); + } + + public GameBounds3 move(GameVector3 offset) { + return new GameBounds3( + new GameVector3(lo.x + offset.x, lo.y + offset.y, lo.z + offset.z), + new GameVector3(hi.x + offset.x, hi.y + offset.y, hi.z + offset.z)); + } + + public GameBounds3 moveEq(GameVector3 offset) { + lo.x += offset.x; lo.y += offset.y; lo.z += offset.z; + hi.x += offset.x; hi.y += offset.y; hi.z += offset.z; + return this; + } + public static GameBounds3 fromPoints(Object points) { if (!(points instanceof NativeArray arr)) return null; long len = arr.getLength(); diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameQuaternion.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameQuaternion.java index 3dcd713..96d91cb 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameQuaternion.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameQuaternion.java @@ -142,6 +142,89 @@ public GameQuaternion rotateZ(double rad) { return rz.mul(this); } + /** Rotates a vector by this unit quaternion. */ + public GameVector3 rotateVector(GameVector3 v) { + // v' = v*(w²-|r|²) + 2*r*(r·v) + 2*w*(r × v) + GameVector3 r = new GameVector3(x, y, z); + double w2mr2 = w * w - (x * x + y * y + z * z); + double dot2 = 2 * r.dot(v); + GameVector3 term1 = v.scale(w2mr2); + GameVector3 term2 = r.scale(dot2); + GameVector3 term3 = r.cross(v).scale(2 * w); + return new GameVector3( + term1.x + term2.x + term3.x, + term1.y + term2.y + term3.y, + term1.z + term2.z + term3.z + ); + } + + /** Decomposes this quaternion into YZX Euler angles (radians). + * Returns a GameVector3 where x/y/z correspond to rotation angles around the X/Y/Z axes. */ + public GameVector3 toEuler() { + double m10 = 2 * (x * y + w * z); + double m00 = 1 - 2 * (y * y + z * z); + double m20 = 2 * (x * z - w * y); + double m11 = 1 - 2 * (x * x + z * z); + double m12 = 2 * (y * z - w * x); + + double ez = Math.asin(Math.max(-1, Math.min(1, m10))); + double ey, ex; + double cosZ = Math.cos(ez); + if (Math.abs(cosZ) < 1e-6) { + ex = 0; + ey = Math.atan2(-m20, m00); + } else { + ex = Math.atan2(-m12, m11); + ey = Math.atan2(-m20, m00); + } + return new GameVector3(ex, ey, ez); + } + + /** Builds a look-at quaternion rotating the -Z direction toward (to-from). */ + public static GameQuaternion lookAt(GameVector3 from, GameVector3 to, GameVector3 up) { + GameVector3 fwd = to.sub(from).normalize(); + GameVector3 right = fwd.cross(up).normalize(); + if (right.mag() < 0.001) { + right = new GameVector3(1, 0, 0).cross(fwd).normalize(); + if (right.mag() < 0.001) + right = new GameVector3(0, 1, 0).cross(fwd).normalize(); + } + GameVector3 upCorr = right.cross(fwd).normalize(); + + double m00 = right.x, m01 = upCorr.x, m02 = -fwd.x; + double m10 = right.y, m11 = upCorr.y, m12 = -fwd.y; + double m20 = right.z, m21 = upCorr.z, m22 = -fwd.z; + + double trace = m00 + m11 + m22; + double w, x, y, z; + if (trace > 0) { + double s = Math.sqrt(trace + 1) * 2; + w = 0.25 * s; + x = (m21 - m12) / s; + y = (m02 - m20) / s; + z = (m10 - m01) / s; + } else if (m00 > m11 && m00 > m22) { + double s = Math.sqrt(1 + m00 - m11 - m22) * 2; + w = (m21 - m12) / s; + x = 0.25 * s; + y = (m01 + m10) / s; + z = (m02 + m20) / s; + } else if (m11 > m22) { + double s = Math.sqrt(1 + m11 - m00 - m22) * 2; + w = (m02 - m20) / s; + x = (m01 + m10) / s; + y = 0.25 * s; + z = (m12 + m21) / s; + } else { + double s = Math.sqrt(1 + m22 - m00 - m11) * 2; + w = (m10 - m01) / s; + x = (m02 + m20) / s; + y = (m12 + m21) / s; + z = 0.25 * s; + } + return new GameQuaternion(w, x, y, z); + } + // ---- Static constructors ---- public static GameQuaternion fromAxisAngle(GameVector3 axis, double rad) { diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBAColor.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBAColor.java index 3354981..8efd99b 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBAColor.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBAColor.java @@ -61,6 +61,14 @@ public GameRGBAColor divEq(GameRGBAColor rgba) { return this; } + public GameRGBAColor scale(double n) { + return new GameRGBAColor(r * n, g * n, b * n, a * n); + } + + public GameRGBAColor scaleEq(double n) { + r *= n; g *= n; b *= n; a *= n; return this; + } + public GameRGBAColor lerp(GameRGBAColor rgba, double n) { return new GameRGBAColor( r + (rgba.r - r) * n, g + (rgba.g - g) * n, diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBColor.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBColor.java index 29174bd..fbbff5c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBColor.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameRGBColor.java @@ -58,6 +58,14 @@ public GameRGBColor divEq(GameRGBColor o) { return this; } + public GameRGBColor scale(double n) { + return new GameRGBColor(r * n, g * n, b * n); + } + + public GameRGBColor scaleEq(double n) { + r *= n; g *= n; b *= n; return this; + } + public GameRGBColor lerp(GameRGBColor o, double n) { return new GameRGBColor(r + (o.r - r) * n, g + (o.g - g) * n, b + (o.b - b) * n); } diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameVector3.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameVector3.java index cda31f8..c78d4b9 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameVector3.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/GameVector3.java @@ -45,6 +45,18 @@ public GameVector3 scale(double n) { return new GameVector3(x * n, y * n, z * n); } + public GameVector3 scaleEq(double n) { + x *= n; y *= n; z *= n; return this; + } + + public GameVector3 neg() { + return new GameVector3(-x, -y, -z); + } + + public GameVector3 negEq() { + x = -x; y = -y; z = -z; return this; + } + public GameVector3 addEq(GameVector3 v) { x += v.x; y += v.y; z += v.z; return this; } @@ -91,10 +103,24 @@ public double distance(GameVector3 v) { return Math.sqrt(dx * dx + dy * dy + dz * dz); } + public double sqrDistance(GameVector3 v) { + double dx = x - v.x, dy = y - v.y, dz = z - v.z; + return dx * dx + dy * dy + dz * dz; + } + public GameVector3 lerp(GameVector3 v, double n) { return new GameVector3(x + (v.x - x) * n, y + (v.y - y) * n, z + (v.z - z) * n); } + public GameVector3 moveTowards(GameVector3 target, double maxDelta) { + double dx = target.x - x, dy = target.y - y, dz = target.z - z; + double d2 = dx * dx + dy * dy + dz * dz; + if (d2 <= maxDelta * maxDelta || d2 == 0) + return new GameVector3(target.x, target.y, target.z); + double t = maxDelta / Math.sqrt(d2); + return new GameVector3(x + dx * t, y + dy * t, z + dz * t); + } + public GameVector3 towards(GameVector3 v) { return sub(v).normalize(); } @@ -124,6 +150,27 @@ public GameVector3 min(GameVector3 v) { return new GameVector3(Math.min(x, v.x), Math.min(y, v.y), Math.min(z, v.z)); } + public boolean isZero() { + return Math.abs(x) < 1e-6 && Math.abs(y) < 1e-6 && Math.abs(z) < 1e-6; + } + + public GameVector3 floor() { + return new GameVector3(Math.floor(x), Math.floor(y), Math.floor(z)); + } + + public GameVector3 ceil() { + return new GameVector3(Math.ceil(x), Math.ceil(y), Math.ceil(z)); + } + + public GameVector3 clampLength(double max) { + double m2 = x * x + y * y + z * z; + if (m2 > max * max) { + double s = max / Math.sqrt(m2); + return new GameVector3(x * s, y * s, z * s); + } + return clone(); + } + public static GameVector3 fromPolar(double mag, double phi, double theta) { return new GameVector3( mag * Math.cos(phi) * Math.cos(theta), diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3ScriptCompiler.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3ScriptCompiler.java new file mode 100644 index 0000000..20c4483 --- /dev/null +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3ScriptCompiler.java @@ -0,0 +1,424 @@ +package com.box3lab.box3js.standalone; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.*; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.regex.Pattern; + +/** + * Compiles a Box3JS TypeScript project into a lightweight NeoForge mod JAR. + * + *

Script JARs (~50KB) contain only the generated {@code @Mod} entry point + * and bundled JS source. They depend on the Box3JS mod ({@code box3js}) for + * the Rhino runtime and API bindings — no classes are bundled. + * + *

Script JAR structure

+ * + *
{@code
+ *   mygame.jar
+ *   ├── META-INF/
+ *   │   ├── MANIFEST.MF
+ *   │   └── neoforge.mods.toml    ← depends on box3js
+ *   ├── logo.png                   ← mod icon (if specified)
+ *   └── box3script/mygame/
+ *       ├── MygameMod.class        ← generated @Mod entry point
+ *       └── app.js                 ← bundled JS source
+ * }
+ * + *

Deployment

+ * + * Place the script JAR alongside {@code box3js} (the main Box3JS mod) in + * {@code mods/}. No other dependencies required. + */ +public class Box3ScriptCompiler { + + /** ModId of the main Box3JS mod that script JARs depend on. */ + public static final String BOX3JS_MOD_ID = "box3js"; + + private final Path projectDir; + private final Path outputJar; + private final String modId; + private final String modName; + private final String modVersion; + private final String description; + private final String author; + private final String license; + private final String homepage; + private final String bugsUrl; + private final String logoFile; + private final String box3jsVersion; + + public Box3ScriptCompiler(Path projectDir, Path outputJar, + String modId, String modName, String modVersion, + String description, String author, String license, + String homepage, String bugsUrl, String logoFile, + String box3jsVersion) { + this.projectDir = projectDir.toAbsolutePath(); + this.outputJar = outputJar.toAbsolutePath(); + this.modId = modId; + this.modName = modName; + this.modVersion = modVersion; + this.description = description; + this.author = author; + this.license = license; + this.homepage = homepage; + this.bugsUrl = bugsUrl; + this.logoFile = logoFile; + this.box3jsVersion = box3jsVersion; + } + + public void compile() throws Exception { + Path appJs = projectDir.resolve("dist/app.js"); + if (!Files.exists(appJs)) { + throw new FileNotFoundException("dist/app.js not found in " + projectDir + + " — run 'npm run build' first"); + } + + Path workDir = projectDir.resolve("dist/.jar-build"); + deleteRecursive(workDir); + Files.createDirectories(workDir); + + System.out.println("[1/4] Bundling JS source ..."); + bundleJsSource(appJs, workDir); + + System.out.println("[2/4] Bundling logo ..."); + bundleLogo(workDir); + + System.out.println("[3/4] Generating & compiling @Mod entry point ..."); + Path genSrcDir = workDir.resolve("gen-src"); + generateModClass(genSrcDir); + compileJava(genSrcDir, workDir); + + System.out.println("[4/4] Creating metadata + packaging ..."); + createMetadata(workDir); + packageJar(workDir, outputJar); + + deleteRecursive(workDir); + System.out.println("Done: " + outputJar); + } + + // ── Step 1: Bundle JS source ── + + private void bundleJsSource(Path jsFile, Path workDir) throws IOException { + String resourcePath = "box3script/" + modId + "/app.js"; + Path dest = workDir.resolve(resourcePath); + Files.createDirectories(dest.getParent()); + Files.copy(jsFile, dest); + System.out.println(" Bundled " + resourcePath); + } + + // ── Step 2: Bundle logo ── + + private void bundleLogo(Path workDir) throws IOException { + if (logoFile == null || logoFile.isEmpty()) + return; + Path logoSrc = projectDir.resolve(logoFile); + if (!Files.exists(logoSrc)) { + System.out.println(" Logo not found: " + logoFile + " — skipping"); + return; + } + Path dest = workDir.resolve("logo.png"); + Files.copy(logoSrc, dest); + System.out.println(" Bundled logo.png"); + } + + // ── Step 3: Generate @Mod entry point ── + + private void generateModClass(Path genSrcDir) throws IOException { + String pkg = "box3script." + modId; + String className = capitalize(modId) + "Mod"; + String resourcePath = "box3script/" + modId + "/app.js"; + + String src = String.format(""" + package %s; + + import com.box3lab.box3js.standalone.Box3StandaloneBootstrap; + import net.neoforged.bus.api.IEventBus; + import net.neoforged.fml.ModContainer; + import net.neoforged.fml.common.Mod; + + @Mod("%s") + public class %s extends Box3StandaloneBootstrap { + public %s(IEventBus modEventBus, ModContainer modContainer) { + super(modEventBus, modContainer, "%s", "%s"); + } + } + """, pkg, modId, className, className, resourcePath, modId); + + Path out = genSrcDir.resolve(pkg.replace('.', '/')).resolve(className + ".java"); + Files.createDirectories(out.getParent()); + Files.writeString(out, src); + } + + private void compileJava(Path genSrcDir, Path classesDir) throws Exception { + List cpEntries = new ArrayList<>(); + ClassLoader cl = getClass().getClassLoader(); + if (cl instanceof URLClassLoader ucl) { + for (URL url : ucl.getURLs()) { + try { + cpEntries.add(Path.of(url.toURI())); + } catch (Exception ignored) { + } + } + } + if (cpEntries.isEmpty()) { + String cp = System.getProperty("java.class.path"); + for (String entry : cp.split(File.pathSeparator)) { + Path p = Path.of(entry); + if (Files.exists(p)) + cpEntries.add(p); + } + } + + List sourceFiles = new ArrayList<>(); + try (var stream = Files.walk(genSrcDir)) { + stream.filter(p -> p.toString().endsWith(".java")) + .forEach(p -> sourceFiles.add(p.toFile())); + } + + JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); + if (jc == null) { + throw new RuntimeException("javax.tools.JavaCompiler not available — run with JDK (not JRE)"); + } + + try (StandardJavaFileManager fm = jc.getStandardFileManager(null, null, null)) { + Iterable units = fm.getJavaFileObjectsFromFiles(sourceFiles); + + List options = new ArrayList<>(); + options.add("-d"); + options.add(classesDir.toString()); + options.add("-proc:none"); + if (!cpEntries.isEmpty()) { + options.add("-classpath"); + options.add(String.join(File.pathSeparator, + cpEntries.stream().map(Path::toString).toList())); + } + + JavaCompiler.CompilationTask task = jc.getTask( + null, fm, null, options, null, units); + if (!task.call()) { + throw new RuntimeException("Java compilation failed for generated @Mod class"); + } + } + System.out.println(" Compiled " + sourceFiles.size() + " generated source(s)"); + } + + // ── Step 4: Metadata + packaging ── + + private void createMetadata(Path workDir) throws IOException { + String desc = (description != null && !description.isEmpty()) + ? description + : "Box3JS standalone script mod"; + + // neoforge.mods.toml + Path toml = workDir.resolve("META-INF/neoforge.mods.toml"); + Files.createDirectories(toml.getParent()); + + StringBuilder sb = new StringBuilder(); + sb.append(""" + modLoader = "javafml" + loaderVersion = "[1,)" + license = "%s" + + [[mods]] + modId = "%s" + version = "%s" + displayName = "%s" + description = "%s" + """.formatted(license, modId, modVersion, modName, desc)); + + if (author != null && !author.isEmpty()) + sb.append("credits = \"%s\"\n".formatted(author)); + if (homepage != null && !homepage.isEmpty()) + sb.append("displayURL = \"%s\"\n".formatted(homepage)); + if (bugsUrl != null && !bugsUrl.isEmpty()) + sb.append("issueTrackerURL = \"%s\"\n".formatted(bugsUrl)); + if (logoFile != null && !logoFile.isEmpty()) + sb.append("logoFile = \"logo.png\"\n"); + + sb.append(""" + + [[dependencies.%s]] + modId = "neoforge" + type = "required" + versionRange = "[21.1,)" + ordering = "NONE" + side = "BOTH" + + [[dependencies.%s]] + modId = "%s" + type = "required" + versionRange = "[%s,)" + ordering = "NONE" + side = "BOTH" + """.formatted(modId, modId, BOX3JS_MOD_ID, box3jsVersion)); + + Files.writeString(toml, sb.toString()); + } + + private void packageJar(Path workDir, Path outputJar) throws IOException { + Files.createDirectories(outputJar.getParent()); + try (JarOutputStream jos = new JarOutputStream( + new BufferedOutputStream(Files.newOutputStream(outputJar)), + new Manifest())) { + + // META-INF/ first + Path metaDir = workDir.resolve("META-INF"); + if (Files.isDirectory(metaDir)) { + addDirToJar(jos, workDir, metaDir); + } + + // logo.png at JAR root + Path logo = workDir.resolve("logo.png"); + if (Files.exists(logo)) { + jos.putNextEntry(new JarEntry("logo.png")); + Files.copy(logo, jos); + jos.closeEntry(); + } + + // Generated class + JS source + Path box3scriptDir = workDir.resolve("box3script"); + if (Files.isDirectory(box3scriptDir)) { + addDirToJar(jos, workDir, box3scriptDir); + } + } + } + + private void addDirToJar(JarOutputStream jos, Path root, Path dir) throws IOException { + try (var stream = Files.walk(dir)) { + stream.filter(Files::isRegularFile).forEach(file -> { + try { + String entryName = root.relativize(file).toString().replace('\\', '/'); + jos.putNextEntry(new JarEntry(entryName)); + Files.copy(file, jos); + jos.closeEntry(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + // ── Helpers ── + + private static String capitalize(String s) { + if (s.isEmpty()) + return s; + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + private static void deleteRecursive(Path path) throws IOException { + if (!Files.exists(path)) + return; + try (var stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).forEach(p -> { + try { + Files.delete(p); + } catch (IOException ignored) { + } + }); + } + } + + // ── Package.json ── + + private static final Pattern JSON_STRING = Pattern.compile("\"(\\w+)\"\\s*:\\s*\"([^\"]+)\""); + + private static final Pattern BUGS_URL = Pattern.compile("\"bugs\"\\s*:\\s*\\{[^}]*\"url\"\\s*:\\s*\"([^\"]+)\""); + + /** + * Reads name, displayName, version, description, author, license, homepage, + * bugsUrl from package.json. + * Falls back to defaults if the file is absent. + */ + public static String[] readPackageInfo(Path projectDir) { + Path pkgJson = projectDir.resolve("package.json"); + String name = projectDir.getFileName().toString(); + String displayName = name; + String version = "1.0.0"; + String description = ""; + String author = ""; + String license = "All Rights Reserved"; + String homepage = ""; + String bugsUrl = ""; + String logoFile = ""; + if (Files.exists(pkgJson)) { + try { + String raw = Files.readString(pkgJson, StandardCharsets.UTF_8); + var m = JSON_STRING.matcher(raw); + while (m.find()) { + switch (m.group(1)) { + case "name" -> name = m.group(2); + case "displayName" -> displayName = m.group(2); + case "version" -> version = m.group(2); + case "description" -> description = m.group(2); + case "author" -> author = m.group(2); + case "license" -> license = m.group(2); + case "homepage" -> homepage = m.group(2); + case "logoFile" -> logoFile = m.group(2); + } + } + var bm = BUGS_URL.matcher(raw); + if (bm.find()) { + bugsUrl = bm.group(1); + } + } catch (IOException ignored) { + } + } + return new String[] { name, displayName, version, description, author, license, homepage, bugsUrl, logoFile }; + } + + // ── CLI ── + + public static void main(String[] args) throws Exception { + Map opts = parseArgs(args); + Path projectDir = Path.of(opts.getOrDefault("project", ".")).toAbsolutePath(); + + String[] info = readPackageInfo(projectDir); + String name = info[0]; + String displayName = info[1]; + String version = info[2]; + String description = info[3]; + String author = info[4]; + String license = info[5]; + String homepage = info[6]; + String bugsUrl = info[7]; + String logoFile = info[8]; + + String modId = opts.getOrDefault("modId", name); + String modName = opts.getOrDefault("name", displayName); + String modVersion = opts.getOrDefault("version", version); + Path output = Path.of(opts.getOrDefault("output", + projectDir.resolve("dist/" + modId + "-" + modVersion + ".jar").toString())); + + String box3jsVersion = opts.getOrDefault("box3jsVersion", "0"); + new Box3ScriptCompiler(projectDir, output, modId, modName, modVersion, + description, author, license, homepage, bugsUrl, logoFile, box3jsVersion).compile(); + } + + private static Map parseArgs(String[] args) { + Map opts = new HashMap<>(); + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("--")) { + String key = args[i].substring(2); + if (i + 1 < args.length && !args[i + 1].startsWith("--")) { + opts.put(key, args[++i]); + } + } + } + return opts; + } +} diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3StandaloneBootstrap.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3StandaloneBootstrap.java new file mode 100644 index 0000000..a01fb34 --- /dev/null +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/standalone/Box3StandaloneBootstrap.java @@ -0,0 +1,183 @@ +package com.box3lab.box3js.standalone; + +import com.box3lab.box3js.script.Box3ScriptEngine; +import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.MinecraftServer; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.ServerChatEvent; +import net.neoforged.neoforge.event.entity.living.LivingDamageEvent; +import net.neoforged.neoforge.event.entity.living.LivingDeathEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; +import net.neoforged.neoforge.event.level.BlockEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; +import org.mozilla.javascript.Context; +import org.slf4j.Logger; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +/** + * Base class for standalone compiled Box3JS script mods. + * + *

Each standalone JAR includes a generated {@code @Mod} subclass of this + * class with hardcoded {@code scriptResource} and {@code projectName}. + * The JAR bundles: + *

    + *
  • Bundled JS source ({@code box3script//app.js})
  • + *
  • {@code META-INF/neoforge.mods.toml} declaring a dependency on box3js
  • + *
  • Optional {@code logo.png} for the mod icon
  • + *
+ * + *

The Box3JS main mod ({@code box3js}) must be present in {@code mods/} + * to provide the Rhino runtime and API bindings. + */ +public class Box3StandaloneBootstrap { + + private static final Logger LOGGER = LogUtils.getLogger(); + + private final String scriptResource; + private final String projectName; + private Box3ScriptEngine engine; + + /** + * Called by the generated {@code @Mod} subclass with hardcoded metadata. + * + * @param modEventBus the mod's event bus (unused; we use NeoForge.EVENT_BUS) + * @param modContainer the mod container (for display name, etc.) + * @param scriptResource resource path to the bundled JS (e.g. {@code box3script/a/app.js}) + * @param projectName unique project name for scope isolation + */ + protected Box3StandaloneBootstrap(IEventBus modEventBus, ModContainer modContainer, + String scriptResource, String projectName) { + this.scriptResource = scriptResource; + this.projectName = projectName; + LOGGER.info("Loaded standalone script: project={} resource={}", projectName, scriptResource); + + NeoForge.EVENT_BUS.addListener(this::onServerStarted); + NeoForge.EVENT_BUS.addListener(this::onServerTick); + + // ── Player join / leave ── + NeoForge.EVENT_BUS.addListener((PlayerEvent.PlayerLoggedInEvent event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.firePlayerJoin(sp); + }); + NeoForge.EVENT_BUS.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.firePlayerLeave(sp); + }); + + // ── Block break / place ── + NeoForge.EVENT_BUS.addListener((BlockEvent.BreakEvent event) -> { + if (engine != null && event.getPlayer() instanceof ServerPlayer sp) + engine.fireVoxelDestroy(sp, event.getPos()); + }); + NeoForge.EVENT_BUS.addListener((BlockEvent.EntityPlaceEvent event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireBlockPlace(sp, event.getPos(), event.getPlacedBlock()); + }); + + // ── Entity interact ── + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.EntityInteract event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireInteract(sp, event.getTarget()); + }); + + // ── Block activate (right-click block) ── + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickBlock event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) { + engine.fireBlockActivate(sp, event.getPos(), + event.getLevel().getBlockState(event.getPos())); + engine.fireActionButton(sp, "ACTION1"); + } + }); + + // ── Action buttons ── + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.LeftClickBlock event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireActionButton(sp, "ACTION0"); + }); + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.LeftClickEmpty event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireActionButton(sp, "ACTION0"); + }); + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickItem event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireActionButton(sp, "ACTION1"); + }); + NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickEmpty event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.fireActionButton(sp, "ACTION1"); + }); + + // ── Chat ── + NeoForge.EVENT_BUS.addListener((ServerChatEvent event) -> { + if (engine != null && event.getPlayer() instanceof ServerPlayer sp) { + if (engine.fireChat(sp, event.getMessage().getString())) + event.setCanceled(true); + } + }); + + // ── Entity death / respawn / damage ── + NeoForge.EVENT_BUS.addListener((LivingDeathEvent event) -> { + if (engine != null) + engine.fireEntityDeath(event.getEntity(), event.getSource().getEntity()); + }); + NeoForge.EVENT_BUS.addListener((PlayerEvent.PlayerRespawnEvent event) -> { + if (engine != null && event.getEntity() instanceof ServerPlayer sp) + engine.firePlayerRespawn(sp); + }); + NeoForge.EVENT_BUS.addListener((LivingDamageEvent.Pre event) -> { + if (engine != null) + engine.fireEntityDamage(event.getEntity(), event.getNewDamage(), + event.getSource().getMsgId(), event.getSource().getEntity()); + }); + } + + + + private void onServerStarted(ServerStartedEvent event) { + MinecraftServer server = event.getServer(); + Path configDir = server.getServerDirectory().resolve("config"); + + engine = Box3ScriptEngine.createStandalone(server, projectName, configDir); + + // Read bundled JS source from JAR resource + String jsSource; + try (InputStream is = getClass().getClassLoader().getResourceAsStream(scriptResource)) { + if (is == null) { + LOGGER.error("Script resource not found in JAR: {}", scriptResource); + return; + } + jsSource = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } catch (Exception e) { + LOGGER.error("Failed to read script resource: {}", scriptResource, e); + return; + } + + Context cx = Context.enter(); + try { + // esbuild cjs output references module.exports; define the CJS globals + cx.evaluateString(engine.getScope(), + "var module = { exports: {} }; var exports = module.exports;", + "cjs-init", 1, null); + cx.evaluateString(engine.getScope(), jsSource, scriptResource, 1, null); + LOGGER.info("Standalone script '{}' loaded successfully", projectName); + } catch (Exception e) { + LOGGER.error("Failed to execute standalone script: {}", projectName, e); + } finally { + Context.exit(); + } + } + + private void onServerTick(ServerTickEvent.Post event) { + if (engine != null) { + engine.fireTick(); + } + } +} diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/build.mjs b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/build.mjs index c28474d..6a3262c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/build.mjs +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/build.mjs @@ -4,20 +4,25 @@ import { fileURLToPath } from "url"; import { writeFileSync, readFileSync, mkdirSync } from "fs"; import babel from "@babel/core"; -// Get current directory path / 获取当前脚本所在的目录路径 +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore const __dirname = dirname(fileURLToPath(import.meta.url)); const entryFile = resolve(__dirname, "src/app.ts"); const distDir = resolve(__dirname, "dist"); const outFile = resolve(distDir, "app.js"); +// ═══════════════════════════════════════════════════════════════ +// Babel plugins — Rhino 1.9.1 compatibility transforms +// ═══════════════════════════════════════════════════════════════ + /** - * Custom Babel plugin: converts tagged and regular template literals into - * Rhino 1.9.1-compatible code. + * Template literal → .concat() calls. + * + * `hello ${name}!` → "hello ".concat(name, "!") + * db.sql`...` → db.sql(["a", "b"], expr1, expr2) * - * Tagged: db.sql`SELECT * FROM t WHERE id = ${id}` - * → db.sql(["SELECT * FROM t WHERE id = ", ""], id) - * Regular: `hello ${name}!` - * → "hello ".concat(name, "!") + * Babel's default transform emits Object.defineProperties / Object.freeze + * helpers that crash in Rhino 1.9.1. */ function rhinoTemplatePlugin({ types: t }) { return { @@ -27,21 +32,33 @@ function rhinoTemplatePlugin({ types: t }) { const strings = quasi.quasis.map((q) => t.stringLiteral(q.value.cooked ?? q.value.raw), ); - const args = [t.arrayExpression(strings), ...quasi.expressions]; - path.replaceWith(t.callExpression(tag, args)); + path.replaceWith( + t.callExpression(tag, [ + t.arrayExpression(strings), + ...quasi.expressions, + ]), + ); }, TemplateLiteral(path) { const { quasis, expressions } = path.node; if (expressions.length === 0) { - path.replaceWith(t.stringLiteral(quasis[0].value.cooked ?? quasis[0].value.raw)); + path.replaceWith( + t.stringLiteral(quasis[0].value.cooked ?? quasis[0].value.raw), + ); return; } - const base = t.stringLiteral(quasis[0].value.cooked ?? quasis[0].value.raw); + const base = t.stringLiteral( + quasis[0].value.cooked ?? quasis[0].value.raw, + ); const args = []; for (let i = 0; i < expressions.length; i++) { args.push(expressions[i]); - args.push(t.stringLiteral(quasis[i + 1].value.cooked ?? quasis[i + 1].value.raw)); + args.push( + t.stringLiteral( + quasis[i + 1].value.cooked ?? quasis[i + 1].value.raw, + ), + ); } path.replaceWith( t.callExpression( @@ -55,19 +72,300 @@ function rhinoTemplatePlugin({ types: t }) { } /** - * Rhino Compatibility Plugin: Invokes Babel during the esbuild process. - * This avoids the need for manual temporary directory management. - * Rhino 兼容性插件:在 esbuild 构建过程中调用 Babel。 - * 避免了手动创建和管理临时目录的需求。 + * for...of → indexed for loop with Java ArrayList detection. + * + * for (const x of arr) { ... } + * ↓ + * var _coll = arr; + * if (_coll.toArray) _coll = _coll.toArray(); + * for (var _i = 0; _i < _coll.length; _i++) { + * var x = _coll[_i]; + * ... + * } + * + * Babel's _createForEachIteratorHelperLoose calls .call() on the iterable, + * which crashes on Java ArrayList. Java lists expose .toArray() → Object[] + * (wrapped as NativeJavaArray), which supports .length and [i]. + */ +function rhinoForOfPlugin({ types: t }) { + return { + visitor: { + ForOfStatement(path) { + const { left, right, body } = path.node; + + const collId = path.scope.generateUidIdentifier("coll"); + const iId = path.scope.generateUidIdentifier("i"); + + // var _coll = + const collDecl = t.variableDeclaration("var", [ + t.variableDeclarator(collId, right), + ]); + + // if (_coll.toArray) _coll = _coll.toArray(); + const toArrayCheck = t.memberExpression( + collId, + t.identifier("toArray"), + ); + const toArrayCall = t.assignmentExpression( + "=", + collId, + t.callExpression(toArrayCheck, []), + ); + const ifToArray = t.ifStatement( + toArrayCheck, + t.expressionStatement(toArrayCall), + ); + + // _coll[_i] + const elementAccess = t.memberExpression(collId, iId, true); + + // var x = _coll[_i]; (or assignment for non-declaration left) + let elementAssign; + if (t.isVariableDeclaration(left)) { + const decl = left.declarations[0]; + elementAssign = t.variableDeclaration("var", [ + t.variableDeclarator(decl.id, elementAccess), + ]); + } else if (t.isIdentifier(left) || t.isMemberExpression(left)) { + elementAssign = t.expressionStatement( + t.assignmentExpression("=", left, elementAccess), + ); + } else { + elementAssign = t.variableDeclaration("var", [ + t.variableDeclarator(t.identifier("_v"), elementAccess), + ]); + } + + const newBody = t.isBlockStatement(body) + ? t.blockStatement([elementAssign, ...body.body]) + : t.blockStatement([elementAssign, t.expressionStatement(body)]); + + // for (var _i = 0; _i < _coll.length; _i++) { ... } + const forLoop = t.forStatement( + t.variableDeclaration("var", [ + t.variableDeclarator(iId, t.numericLiteral(0)), + ]), + t.binaryExpression( + "<", + iId, + t.memberExpression(collId, t.identifier("length")), + ), + t.updateExpression("++", iId, false), + newBody, + ); + + path.replaceWithMultiple([collDecl, ifToArray, forLoop]); + }, + }, + }; +} + +/** + * ES5 array methods → indexed for loops via IIFE. + * + * arr.map(fn) → (function(){var _a=arr;var _r=[];for(var _i=0;_i<_a.length;_i++)_r.push(fn.call(null,_a[_i],_i,_a));return _r})() + * arr.filter(fn) → (function(){var _a=arr;var _r=[];for(var _i=0;_i<_a.length;_i++){if(fn.call(null,_a[_i],_i,_a))_r.push(_a[_i])}return _r})() + * arr.forEach(fn) → (function(){var _a=arr;for(var _i=0;_i<_a.length;_i++)fn.call(null,_a[_i],_i,_a)})() + * arr.find(fn) → (function(){var _a=arr;for(var _i=0;_i<_a.length;_i++){if(fn.call(null,_a[_i],_i,_a))return _a[_i]}return undefined})() + * arr.some(fn) → (function(){var _a=arr;for(var _i=0;_i<_a.length;_i++){if(fn.call(null,_a[_i],_i,_a))return true}return false})() + * arr.every(fn) → (function(){var _a=arr;for(var _i=0;_i<_a.length;_i++){if(!fn.call(null,_a[_i],_i,_a))return false}return true})() + * + * Rhino's NativeArray (from Java interop, e.g. db.sql().rows) lacks ES5 + * Array.prototype methods. The IIFE form preserves chaining (arr.map(f).filter(g)). + */ +function rhinoArrayMethodsPlugin({ types: t }) { + const METHODS = ["map", "filter", "forEach", "find", "some", "every"]; + + function buildIIFE(bodyNodes, returnExpr) { + const fnBody = returnExpr + ? t.blockStatement([...bodyNodes, t.returnStatement(returnExpr)]) + : t.blockStatement(bodyNodes); + return t.callExpression(t.functionExpression(null, [], fnBody), []); + } + + return { + visitor: { + CallExpression(path) { + const { callee } = path.node; + if (!t.isMemberExpression(callee)) { + return; + } + if (!t.isIdentifier(callee.property)) { + return; + } + const method = callee.property.name; + if (!METHODS.includes(method)) { + return; + } + + const obj = callee.object; + const args = path.node.arguments; + const callback = args[0]; + if (!callback) { + return; + } + const thisArg = args[1] || t.nullLiteral(); + + const arrId = path.scope.generateUidIdentifier("arr"); + const iId = path.scope.generateUidIdentifier("i"); + const rId = path.scope.generateUidIdentifier("r"); + + const arrDecl = t.variableDeclaration("var", [ + t.variableDeclarator(arrId, obj), + ]); + + const element = t.memberExpression(arrId, iId, true); + + // callback.call(thisArg, element, i, arr) + const cbCall = t.callExpression( + t.memberExpression(callback, t.identifier("call")), + [thisArg, element, iId, arrId], + ); + + const forInit = t.variableDeclaration("var", [ + t.variableDeclarator(iId, t.numericLiteral(0)), + ]); + const forTest = t.binaryExpression( + "<", + iId, + t.memberExpression(arrId, t.identifier("length")), + ); + const forUpdate = t.updateExpression("++", iId, false); + + let bodyNodes, returnExpr; + + switch (method) { + case "map": { + const rDecl = t.variableDeclaration("var", [ + t.variableDeclarator(rId, t.arrayExpression([])), + ]); + const pushCall = t.callExpression( + t.memberExpression(rId, t.identifier("push")), + [cbCall], + ); + bodyNodes = [ + arrDecl, + rDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([t.expressionStatement(pushCall)]), + ), + ]; + returnExpr = rId; + break; + } + case "filter": { + const rDecl = t.variableDeclaration("var", [ + t.variableDeclarator(rId, t.arrayExpression([])), + ]); + const pushCall = t.callExpression( + t.memberExpression(rId, t.identifier("push")), + [element], + ); + bodyNodes = [ + arrDecl, + rDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([ + t.ifStatement(cbCall, t.expressionStatement(pushCall)), + ]), + ), + ]; + returnExpr = rId; + break; + } + case "forEach": { + bodyNodes = [ + arrDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([t.expressionStatement(cbCall)]), + ), + ]; + returnExpr = null; + break; + } + case "find": { + bodyNodes = [ + arrDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([ + t.ifStatement(cbCall, t.returnStatement(element)), + ]), + ), + ]; + returnExpr = t.identifier("undefined"); + break; + } + case "some": { + bodyNodes = [ + arrDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([ + t.ifStatement( + cbCall, + t.returnStatement(t.booleanLiteral(true)), + ), + ]), + ), + ]; + returnExpr = t.booleanLiteral(false); + break; + } + case "every": { + bodyNodes = [ + arrDecl, + t.forStatement( + forInit, + forTest, + forUpdate, + t.blockStatement([ + t.ifStatement( + t.unaryExpression("!", cbCall), + t.returnStatement(t.booleanLiteral(false)), + ), + ]), + ), + ]; + returnExpr = t.booleanLiteral(true); + break; + } + default: + return; + } + + path.replaceWith(buildIIFE(bodyNodes, returnExpr)); + }, + }, + }; +} + +// ═══════════════════════════════════════════════════════════════ +// esbuild plugin +// ═══════════════════════════════════════════════════════════════ + +/** + * Routes .ts files through Babel for Rhino downleveling. + * esbuild handles bundling; Babel handles precise syntax transforms. */ const babelRhinoPlugin = { name: "babel-rhino", setup(build) { build.onLoad({ filter: /\.ts$/ }, async (args) => { const source = readFileSync(args.path, "utf8"); - - // Use Babel for precise downleveling targeted at Rhino - // 使用 Babel 进行针对 Rhino 环境的精准降级转译 const result = await babel.transformAsync(source, { filename: args.path, presets: [ @@ -82,7 +380,11 @@ const babelRhinoPlugin = { ], "@babel/preset-typescript", ], - plugins: [rhinoTemplatePlugin], + plugins: [ + rhinoArrayMethodsPlugin, + rhinoForOfPlugin, + rhinoTemplatePlugin, + ], configFile: false, babelrc: false, sourceMaps: false, @@ -94,15 +396,15 @@ const babelRhinoPlugin = { }, }; +// ═══════════════════════════════════════════════════════════════ +// Post-processing +// ═══════════════════════════════════════════════════════════════ + /** - * Post-processing function: Fixes regex literals unsupported by Rhino. - * This is the final defense against specific patterns in Babel's injected helpers. - * 后处理函数:修复 Rhino 不支持的正则字面量。 - * 这是处理 Babel 注入的辅助函数中特定问题的最后一道防线。 + * Fixes regex literals in Babel's injected helpers that Rhino cannot parse. + * Specific target: TypedArray detection via /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/ */ function sanitizeForRhino(code) { - // Regex pattern for TypedArray checks that Rhino cannot parse - // Rhino 无法解析的 TypedArray 检查正则模式 const BAD_REGEX = /\/\^\(\?:Ui\|I\)nt\(\?:8\|16\|32\)\(\?:Clamped\)\?Array\$\/\.test\((\w+)\)/g; return code.replace(BAD_REGEX, (_, varName) => { @@ -110,7 +412,10 @@ function sanitizeForRhino(code) { }); } -// esbuild configuration options / esbuild 构建配置选项 +// ═══════════════════════════════════════════════════════════════ +// Build +// ═══════════════════════════════════════════════════════════════ + const buildOptions = { entryPoints: [entryFile], outfile: outFile, @@ -122,33 +427,24 @@ const buildOptions = { logLevel: "info", }; -/** - * Executes a single build pipeline - * 执行单次构建流程 - */ async function runBuild() { try { mkdirSync(distDir, { recursive: true }); - // 1. Run the bundler / 执行打包 - await esbuild.build({ - ...buildOptions, - metafile: true, - }); + await esbuild.build({ ...buildOptions, metafile: true }); - // 2. Read output and apply Rhino-specific sanitization / 读取产物并进行针对 Rhino 的正则修复 const code = readFileSync(outFile, "utf8"); - const sanitizedCode = sanitizeForRhino(code); - writeFileSync(outFile, sanitizedCode, "utf-8"); + writeFileSync(outFile, sanitizeForRhino(code), "utf-8"); } catch (err) { - console.error("❌ Build failed: / 构建失败:", err); + console.error("Build failed:", err); process.exit(1); } } -// Main logic: Watch mode or Single build / 主逻辑:监听模式或单次构建 +// ── Entry ── + if (process.argv.includes("--watch")) { - console.log("👀 Watch mode enabled... / 监听模式已启用..."); + console.log("Watch mode enabled..."); const ctx = await esbuild.context({ ...buildOptions, @@ -157,7 +453,6 @@ if (process.argv.includes("--watch")) { { name: "post-process-plugin", setup(build) { - // Triggered after every rebuild in watch mode / 在监听模式的每次重建后触发 build.onEnd(() => { const code = readFileSync(outFile, "utf8"); writeFileSync(outFile, sanitizeForRhino(code), "utf-8"); diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/package.json b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/package.json index 399a67e..af0b205 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/package.json +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/package.json @@ -1,6 +1,15 @@ { - "name": "box3js", + "name": "PROJECT_NAME", "version": "1.0.0", + "displayName": "PROJECT_NAME", + "description": "A Box3JS script mod", + "author": "", + "license": "All Rights Reserved", + "homepage": "", + "bugs": { + "url": "" + }, + "logoFile": "", "private": true, "type": "module", "scripts": { @@ -12,7 +21,6 @@ "devDependencies": { "@babel/core": "^7.29.0", "@babel/preset-env": "^7.29.5", - "@babel/preset-typescript": "^7.28.5", "esbuild": "^0.28.0", "eslint": "^10.3.0", diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/globals.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/globals.d.ts index af4e532..5a4b1e6 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/globals.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/globals.d.ts @@ -1,194 +1,184 @@ -// ================================================================ -// §1 Math Types — 数学类型 -// ================================================================ +// ── §1 @zh 数学类型 @en Math Types ── /** - * 三维向量 - * A 3‑dimensional vector with double‑precision components. - * - * @remarks - * 所有坐标使用世界坐标 (方块坐标, 非像素)。 + * @zh 三维向量,所有坐标使用世界坐标(方块坐标,非像素)。 + * @en A 3‑dimensional vector with double‑precision components. * All coordinates are in world space (block coordinates, not pixels). */ declare class GameVector3 { - /** X 分量 — X component (east‑west) */ + /** @zh X 分量(东‑西) @en X component (east‑west) */ x: number; - /** Y 分量 — Y component (up‑down) */ + /** @zh Y 分量(上‑下) @en Y component (up‑down) */ y: number; - /** Z 分量 — Z component (north‑south) */ + /** @zh Z 分量(南‑北) @en Z component (north‑south) */ z: number; - /** - * 创建一个零向量 (0, 0, 0)。 - * Creates a zero vector at origin. - */ + /** @zh 创建一个零向量 (0, 0, 0)。 @en Creates a zero vector at origin. */ constructor(); /** - * 创建一个指定坐标的向量。 - * Creates a vector with the given coordinates. - * @param x - X 坐标 / X coordinate - * @param y - Y 坐标 / Y coordinate - * @param z - Z 坐标 / Z coordinate + * @zh 创建一个指定坐标的向量。 + * @en Creates a vector with the given coordinates. + * @param x - @zh X 坐标 @en X coordinate + * @param y - @zh Y 坐标 @en Y coordinate + * @param z - @zh Z 坐标 @en Z coordinate */ constructor(x: number, y: number, z: number); /** - * 设置向量的 X / Y / Z 分量 (会改变调用者自身)。 - * Sets all three components in‑place (mutates the vector). - * @returns 调用者本身 / this vector + * @zh 设置向量的 X / Y / Z 分量(会改变调用者自身)。 + * @en Sets all three components in‑place (mutates the vector). + * @returns @zh 调用者本身 @en this vector */ set(x: number, y: number, z: number): GameVector3; - /** 原地复制 v 的值。Copies values from v in‑place. */ + /** @zh 原地复制 v 的值。 @en Copies values from v in‑place. */ copy(v: GameVector3): GameVector3; - /** 深拷贝。Returns a new independent copy. */ + /** @zh 深拷贝。 @en Returns a new independent copy. */ clone(): GameVector3; /** - * 向量加法: this + v。 - * Vector addition: this + v. - * @returns 一个新向量 / a new vector + * @zh 向量加法:this + v。 + * @en Vector addition: this + v. + * @returns @zh 一个新向量 @en a new vector */ add(v: GameVector3): GameVector3; /** - * 向量减法: this - v。 - * Vector subtraction: this - v. - * @returns 一个新向量 / a new vector + * @zh 向量减法:this - v。 + * @en Vector subtraction: this - v. + * @returns @zh 一个新向量 @en a new vector */ sub(v: GameVector3): GameVector3; - /** 逐分量乘法 (返回新对象)。Component‑wise multiplication (returns new vector). */ + /** @zh 逐分量乘法(返回新对象)。 @en Component‑wise multiplication (returns new vector). */ mul(v: GameVector3): GameVector3; - /** 逐分量除法 (返回新对象, 除以 0 得 0)。Component‑wise division (divide‑by‑zero → 0). */ + /** @zh 逐分量除法(返回新对象,除以 0 得 0)。 @en Component‑wise division (divide‑by‑zero → 0). */ div(v: GameVector3): GameVector3; /** - * 标量乘法: 每个分量乘以 n。 - * Scalar multiplication: each component multiplied by n. - * @returns 一个新向量 / a new vector + * @zh 标量乘法:每个分量乘以 n。 + * @en Scalar multiplication: each component multiplied by n. + * @returns @zh 一个新向量 @en a new vector */ scale(n: number): GameVector3; - /** 原地加法。Addition in‑place. */ + /** @zh 原地缩放。 @en Scale in‑place. */ + scaleEq(n: number): GameVector3; + + /** @zh 反向向量(返回新对象)。 @en Negation (returns new vector). */ + neg(): GameVector3; + + /** @zh 原地反向。 @en Negation in‑place. */ + negEq(): GameVector3; + + /** @zh 原地加法。 @en Addition in‑place. */ addEq(v: GameVector3): GameVector3; - /** 原地减法。Subtraction in‑place. */ + /** @zh 原地减法。 @en Subtraction in‑place. */ subEq(v: GameVector3): GameVector3; - /** 原地乘法。Multiplication in‑place. */ + /** @zh 原地乘法。 @en Multiplication in‑place. */ mulEq(v: GameVector3): GameVector3; - /** 原地除法 (除以 0 跳过该分量)。Division in‑place (divide‑by‑zero skips that component). */ + /** @zh 原地除法(除以 0 跳过该分量)。 @en Division in‑place (divide‑by‑zero skips that component). */ divEq(v: GameVector3): GameVector3; - /** - * 点积 (内积): this · v。 - * Dot (inner) product: this · v. - */ + /** @zh 点积(内积):this · v。 @en Dot (inner) product: this · v. */ dot(v: GameVector3): number; - /** 叉积: this × v。Cross product. */ + /** @zh 叉积:this × v。 @en Cross product. */ cross(v: GameVector3): GameVector3; - /** - * 向量长度 (模)。 - * Magnitude (length) of this vector. - */ + /** @zh 向量长度(模)。 @en Magnitude (length) of this vector. */ mag(): number; - /** - * 向量长度的平方 (比 mag() 更快)。 - * Squared magnitude — faster than mag() when comparing distances. - */ + /** @zh 向量长度的平方(比 `mag()` 更快)。 @en Squared magnitude — faster than `mag()` when comparing distances. */ sqrMag(): number; /** - * 单位化: 返回方向相同、长度为 1 的新向量。 - * Normalizes this vector; returns a unit vector in the same direction. - * 零向量会返回 (0,0,0)。 + * @zh 单位化:返回方向相同、长度为 1 的新向量。零向量会返回 (0,0,0)。 + * @en Normalizes this vector; returns a unit vector in the same direction. + * @zh Zero vector returns (0,0,0). */ normalize(): GameVector3; - /** - * 计算 this 与 v 之间的欧几里得距离。 - * Euclidean distance between this and v. - */ + /** @zh 计算 this 与 v 之间的欧几里得距离。 @en Euclidean distance between this and v. */ distance(v: GameVector3): number; + /** @zh 到 v 的平方距离(比 distance() 快,适合排序/比较)。 @en Squared distance to v — faster than distance() for comparisons. */ + sqrDistance(v: GameVector3): number; + /** - * 线性插值: 在 this 和 v 之间按比率 n 插值。 - * Linear interpolation between this and v by ratio n. - * @param n - 插值比率 (0=this, 1=v) / interpolation factor + * @zh 线性插值:在 this 和 v 之间按比率 n 插值。 + * @en Linear interpolation between this and v by ratio n. + * @param n - @zh 插值比率(0=this,1=v) @en interpolation factor (0=this, 1=v) */ lerp(v: GameVector3, n: number): GameVector3; - /** - * 指向 v 的方向向量 (已单位化)。 - * Direction vector pointing toward v (normalized). - */ + /** @zh 匀速移向目标点(步长 maxDelta,到目标后返回 target 的拷贝)。 @en Moves toward target by at most maxDelta. Returns copy of target when reached. */ + moveTowards(target: GameVector3, maxDelta: number): GameVector3; + + /** @zh 指向 v 的方向向量(已单位化)。 @en Direction vector pointing toward v (normalized). */ towards(v: GameVector3): GameVector3; - /** - * this 与 v 之间的夹角 (弧度)。 - * Angle between this and v in radians. - */ + /** @zh this 与 v 之间的夹角(弧度)。 @en Angle between this and v in radians. */ angle(v: GameVector3): number; - /** - * 近似相等检查 (容差 1e‑6)。 - * Approximate equality within 1e‑6 tolerance. - */ + /** @zh 近似相等检查(容差 1e‑6)。 @en Approximate equality within 1e‑6 tolerance. */ equals(v: GameVector3): boolean; - /** 精确相等检查 (分量完全相等)。Exact component‑wise equality. */ + /** @zh 精确相等检查(分量完全相等)。 @en Exact component‑wise equality. */ exactEquals(v: GameVector3): boolean; - /** 逐分量取较大值 (返回新对象)。Component‑wise max. */ + /** @zh 逐分量取较大值(返回新对象)。 @en Component‑wise max. */ max(v: GameVector3): GameVector3; - /** 逐分量取较小值 (返回新对象)。Component‑wise min. */ + /** @zh 逐分量取较小值(返回新对象)。 @en Component‑wise min. */ min(v: GameVector3): GameVector3; + /** @zh 是否为零向量(容差 1e‑6)。 @en True if all components are within 1e‑6 of zero. */ + isZero(): boolean; + + /** @zh 逐分量向下取整(常用于方块坐标)。 @en Component‑wise floor (useful for block coordinates). */ + floor(): GameVector3; + + /** @zh 逐分量向上取整。 @en Component‑wise ceil. */ + ceil(): GameVector3; + + /** @zh 限制向量长度不超过 max(超长时返回归一化后缩放到 max 的新向量)。 @en Clamps length to max, returning a new vector. */ + clampLength(max: number): GameVector3; + /** - * 从球坐标创建向量。 - * Creates a vector from spherical coordinates. - * @param mag - 半径 / radius (magnitude) - * @param phi - 方位角 / azimuth angle (radians, horizontal rotation around Y) - * @param theta - 仰角 / elevation angle (radians, from horizontal plane) + * @zh 从球坐标创建向量。 + * @en Creates a vector from spherical coordinates. + * @param mag - @zh 半径 @en radius (magnitude) + * @param phi - @zh 方位角(弧度,绕 Y 轴水平旋转) @en azimuth angle (radians, horizontal rotation around Y) + * @param theta - @zh 仰角(弧度,从水平面起算) @en elevation angle (radians, from horizontal plane) */ static fromPolar(mag: number, phi: number, theta: number): GameVector3; - /** 返回 "(x, y, z)" 格式的字符串表示。 */ + /** @zh 返回 "(x, y, z)" 格式的字符串表示。 @en Returns a string in "(x, y, z)" format. */ toString(): string; } -// ────────────────────────────────────────────── - /** - * 三维轴对齐包围盒 (AABB)。 - * Axis‑aligned 3‑dimensional bounding box. - * - * @remarks - * 由两个对角顶点 lo (最小角) 和 hi (最大角) 定义。 - * Defined by two opposing corners: lo (minimum corner) and hi (maximum corner). + * @zh 三维轴对齐包围盒(AABB),由两个对角顶点 lo(最小角)和 hi(最大角)定义。 + * @en Axis‑aligned 3‑dimensional bounding box, + * defined by two opposing corners: lo (minimum corner) and hi (maximum corner). */ declare class GameBounds3 { - /** 最小角 (三个分量均为最小值)。Lower/minimum corner. */ + /** @zh 最小角(三个分量均为最小值)。 @en Lower/minimum corner. */ lo: GameVector3; - /** 最大角 (三个分量均为最大值)。Upper/maximum corner. */ + /** @zh 最大角(三个分量均为最大值)。 @en Upper/maximum corner. */ hi: GameVector3; - /** - * 用两个对角顶点构造包围盒。 - * Constructs bounds from two opposing corners. - */ + /** @zh 用两个对角顶点构造包围盒。 @en Constructs bounds from two opposing corners. */ constructor(lo: GameVector3, hi: GameVector3); - /** 原地设置所有边界。Sets all boundaries in‑place. */ + /** @zh 原地设置所有边界。 @en Sets all boundaries in‑place. */ set( lox: number, loy: number, @@ -198,31 +188,46 @@ declare class GameBounds3 { hiz: number, ): GameBounds3; - /** 原地复制 b 的值。Copies values from b in‑place. */ + /** @zh 原地复制 b 的值。 @en Copies values from b in‑place. */ copy(b: GameBounds3): GameBounds3; - /** - * 判断当前包围盒是否与 other 相交。 - * Returns true if this bounds intersects with other. - */ + /** @zh 判断当前包围盒是否与 other 相交。 @en Returns true if this bounds intersects with other. */ intersects(other: GameBounds3): boolean; - /** - * 计算交集包围盒 (无交集返回 null)。 - * Returns the intersection bounds, or null if they don't overlap. - */ + /** @zh 计算交集包围盒(无交集返回 null)。 @en Returns the intersection bounds, or null if they don't overlap. */ intersect(other: GameBounds3): GameBounds3 | null; - /** - * 判断点 v 是否位于包围盒内部 (含边界)。 - * Returns true if point v is inside (or on the boundary of) this bounds. - */ + /** @zh 判断点 v 是否位于包围盒内部(含边界)。 @en Returns true if point v is inside (or on the boundary of) this bounds. */ contains(v: GameVector3): boolean; - /** 判断是否完全包含另一个包围盒。Whether this bounds fully contains b. */ + /** @zh 判断是否完全包含另一个包围盒。 @en Whether this bounds fully contains b. */ containsBounds(b: GameBounds3): boolean; - /** 从 GameVector3 数组创建最小包围盒。Creates bounds from an array of GameVector3. */ + /** @zh 返回包围盒中心点。 @en Returns the center point of the bounds. */ + center(): GameVector3; + + /** @zh 返回包围盒尺寸 (hi‑lo)。 @en Returns the size (hi‑lo) as a vector. */ + size(): GameVector3; + + /** @zh 扩展包围盒,各方向扩大 delta(返回新对象)。 @en Returns a new bounds expanded by delta on all sides. */ + expand(delta: number): GameBounds3; + + /** @zh 原地扩展包围盒。 @en Expands the bounds in‑place. */ + expandEq(delta: number): GameBounds3; + + /** @zh 扩展包围盒以包含点 v(原地)。 @en Grows the bounds to include point v in‑place. */ + growToInclude(v: GameVector3): GameBounds3; + + /** @zh 包围盒上距离 v 最近的点(v 在内部时返回投影到表面的点)。 @en Closest point on the bounds surface to v. */ + closestPoint(v: GameVector3): GameVector3; + + /** @zh 平移包围盒(返回新对象)。 @en Translates the bounds by offset (returns new object). */ + move(offset: GameVector3): GameBounds3; + + /** @zh 原地平移包围盒。 @en Translates the bounds in‑place. */ + moveEq(offset: GameVector3): GameBounds3; + + /** @zh 从 GameVector3 数组创建最小包围盒。 @en Creates bounds from an array of GameVector3. */ static fromPoints(points: GameVector3[]): GameBounds3 | null; toString(): string; @@ -231,72 +236,69 @@ declare class GameBounds3 { // ────────────────────────────────────────────── /** - * RGB 颜色 (三个通道, 每通道 0.0‑1.0)。 - * An RGB color with three channels ranging from 0.0 to 1.0. + * @zh RGB 颜色,三个通道,每通道 0.0–1.0。 + * @en An RGB color with three channels ranging from 0.0 to 1.0. */ declare class GameRGBColor { - /** 红色通道 (0.0‑1.0)。Red channel. */ + /** @zh 红色通道(0.0–1.0)。 @en Red channel. */ r: number; - /** 绿色通道 (0.0‑1.0)。Green channel. */ + /** @zh 绿色通道(0.0–1.0)。 @en Green channel. */ g: number; - /** 蓝色通道 (0.0‑1.0)。Blue channel. */ + /** @zh 蓝色通道(0.0–1.0)。 @en Blue channel. */ b: number; - /** - * 用指定的 R / G / B 值创建颜色。 - * Creates a color with the given R/G/B values. - */ + /** @zh 用指定的 R / G / B 值创建颜色。 @en Creates a color with the given R/G/B values. */ constructor(r: number, g: number, b: number); - /** 原地设置所有通道。Sets all three channels in‑place. */ + /** @zh 原地设置所有通道。 @en Sets all three channels in‑place. */ set(r: number, g: number, b: number): GameRGBColor; - /** 原地复制另一个颜色的值。Copies values from another color in‑place. */ + /** @zh 原地复制另一个颜色的值。 @en Copies values from another color in‑place. */ copy(o: GameRGBColor): GameRGBColor; - /** 深拷贝。Returns a new independent copy. */ + /** @zh 深拷贝。 @en Returns a new independent copy. */ clone(): GameRGBColor; - /** 逐通道加法 (返回新对象)。Channel‑wise addition (returns new object). */ + /** @zh 逐通道加法(返回新对象)。 @en Channel‑wise addition (returns new object). */ add(o: GameRGBColor): GameRGBColor; - /** 逐通道减法 (返回新对象)。Channel‑wise subtraction (returns new object). */ + /** @zh 逐通道减法(返回新对象)。 @en Channel‑wise subtraction (returns new object). */ sub(o: GameRGBColor): GameRGBColor; - /** 逐通道乘法 (返回新对象)。Channel‑wise multiplication (returns new object). */ + /** @zh 逐通道乘法(返回新对象)。 @en Channel‑wise multiplication (returns new object). */ mul(o: GameRGBColor): GameRGBColor; - /** 逐通道除法 (返回新对象, 除以 0 得 0)。Channel‑wise division (divide‑by‑zero → 0). */ + /** @zh 逐通道除法(返回新对象,除以 0 得 0)。 @en Channel‑wise division (divide‑by‑zero → 0). */ div(o: GameRGBColor): GameRGBColor; - /** 原地加法。Addition in‑place. */ + /** @zh 原地加法。 @en Addition in‑place. */ addEq(o: GameRGBColor): GameRGBColor; - /** 原地减法。Subtraction in‑place. */ + /** @zh 原地减法。 @en Subtraction in‑place. */ subEq(o: GameRGBColor): GameRGBColor; - /** 原地乘法。Multiplication in‑place. */ + /** @zh 原地乘法。 @en Multiplication in‑place. */ mulEq(o: GameRGBColor): GameRGBColor; - /** 原地除法 (除以 0 跳过该通道)。Division in‑place (divide‑by‑zero skips that channel). */ + /** @zh 原地除法(除以 0 跳过该通道)。 @en Division in‑place (divide‑by‑zero skips that channel). */ divEq(o: GameRGBColor): GameRGBColor; - /** - * 在 this 和 o 之间线性插值。 - * Linear interpolation between this and o by ratio n. - */ + /** @zh 标量乘法(返回新对象,常用于亮度调整)。 @en Scalar multiply — brighten (n>1) or darken (n<1). */ + scale(n: number): GameRGBColor; + + /** @zh 原地标量乘法。 @en Scalar multiply in‑place. */ + scaleEq(n: number): GameRGBColor; + + /** @zh 在 this 和 o 之间线性插值。 @en Linear interpolation between this and o by ratio n. */ lerp(o: GameRGBColor, n: number): GameRGBColor; - /** 近似相等检查 (容差 1e‑6)。Approximate equality within 1e‑6 tolerance. */ + /** @zh 近似相等检查(容差 1e‑6)。 @en Approximate equality within 1e‑6 tolerance. */ equals(o: GameRGBColor): boolean; - /** 转为 "rgba(r,g,b,1.0)" 格式字符串。Converts to an rgba CSS string. */ + /** @zh 转为 "rgba(r,g,b,1.0)" 格式字符串。 @en Converts to an rgba CSS string. */ toRGBA(): string; - /** - * 生成一个随机 RGB 颜色 (每个通道 0‑1)。 - * Generates a random RGB color (each channel 0–1). - */ + /** @zh 生成一个随机 RGB 颜色(每个通道 0–1)。 @en Generates a random RGB color (each channel 0–1). */ static random(): GameRGBColor; toString(): string; @@ -305,63 +307,69 @@ declare class GameRGBColor { // ────────────────────────────────────────────── /** - * RGBA 颜色 (四个通道, 每通道 0.0‑1.0)。 - * An RGBA color; all four channels range from 0.0 to 1.0. + * @zh RGBA 颜色,四个通道,每通道 0.0–1.0。 + * @en An RGBA color; all four channels range from 0.0 to 1.0. */ declare class GameRGBAColor { - /** 红色通道 (0.0‑1.0)。Red channel. */ + /** @zh 红色通道(0.0–1.0)。 @en Red channel. */ r: number; - /** 绿色通道 (0.0‑1.0)。Green channel. */ + /** @zh 绿色通道(0.0–1.0)。 @en Green channel. */ g: number; - /** 蓝色通道 (0.0‑1.0)。Blue channel. */ + /** @zh 蓝色通道(0.0–1.0)。 @en Blue channel. */ b: number; - /** Alpha (不透明度), 范围 0.0–1.0。Alpha (opacity), range 0.0–1.0. */ + /** @zh Alpha(不透明度),范围 0.0–1.0。 @en Alpha (opacity), range 0.0–1.0. */ a: number; constructor(r: number, g: number, b: number, a: number); - /** 原地设置所有四个通道。Sets all four channels in‑place. */ + /** @zh 原地设置所有四个通道。 @en Sets all four channels in‑place. */ set(r: number, g: number, b: number, a: number): GameRGBAColor; - /** 原地复制另一个颜色的值。Copies values from another RGBA color in‑place. */ + /** @zh 原地复制另一个颜色的值。 @en Copies values from another RGBA color in‑place. */ copy(c: GameRGBAColor): GameRGBAColor; - /** 深拷贝。Returns a new independent copy. */ + /** @zh 深拷贝。 @en Returns a new independent copy. */ clone(): GameRGBAColor; - /** 逐通道加法 (返回新对象)。Channel‑wise addition (returns new object). */ + /** @zh 逐通道加法(返回新对象)。 @en Channel‑wise addition (returns new object). */ add(rgba: GameRGBAColor): GameRGBAColor; - /** 逐通道减法 (返回新对象)。Channel‑wise subtraction (returns new object). */ + /** @zh 逐通道减法(返回新对象)。 @en Channel‑wise subtraction (returns new object). */ sub(rgba: GameRGBAColor): GameRGBAColor; - /** 逐通道乘法 (返回新对象)。Channel‑wise multiplication (returns new object). */ + /** @zh 逐通道乘法(返回新对象)。 @en Channel‑wise multiplication (returns new object). */ mul(rgba: GameRGBAColor): GameRGBAColor; - /** 逐通道除法 (返回新对象, 除以 0 得 0)。Channel‑wise division (returns new object; divide‑by‑zero → 0). */ + /** @zh 逐通道除法(返回新对象,除以 0 得 0)。 @en Channel‑wise division (returns new object; divide‑by‑zero → 0). */ div(rgba: GameRGBAColor): GameRGBAColor; - /** 原地加法。Addition in‑place. */ + /** @zh 原地加法。 @en Addition in‑place. */ addEq(rgba: GameRGBAColor): GameRGBAColor; - /** 原地减法。Subtraction in‑place. */ + /** @zh 原地减法。 @en Subtraction in‑place. */ subEq(rgba: GameRGBAColor): GameRGBAColor; - /** 原地乘法。Multiplication in‑place. */ + /** @zh 原地乘法。 @en Multiplication in‑place. */ mulEq(rgba: GameRGBAColor): GameRGBAColor; - /** 原地除法 (除以 0 跳过该通道)。Division in‑place (divide‑by‑zero skips that channel). */ + /** @zh 原地除法(除以 0 跳过该通道)。 @en Division in‑place (divide‑by‑zero skips that channel). */ divEq(rgba: GameRGBAColor): GameRGBAColor; - /** 线性插值。Linear interpolation between this and rgba by ratio n. */ + /** @zh 标量乘法(返回新对象,包括 alpha 通道)。 @en Scalar multiply — all channels including alpha. */ + scale(n: number): GameRGBAColor; + + /** @zh 原地标量乘法。 @en Scalar multiply in‑place. */ + scaleEq(n: number): GameRGBAColor; + + /** @zh 线性插值。 @en Linear interpolation between this and rgba by ratio n. */ lerp(rgba: GameRGBAColor, n: number): GameRGBAColor; - /** 近似相等检查 (容差 1e‑6)。Approximate equality within 1e‑6 tolerance. */ + /** @zh 近似相等检查(容差 1e‑6)。 @en Approximate equality within 1e‑6 tolerance. */ equals(rgba: GameRGBAColor): boolean; /** - * Alpha 混合: 将自身 RGBA 颜色混合到 RGB 背景上。 - * Blends this RGBA color onto an RGB background, returning the displayed RGB. + * @zh Alpha 混合:将自身 RGBA 颜色混合到 RGB 背景上。 + * @en Blends this RGBA color onto an RGB background, returning the displayed RGB. */ blendEq(rgb: GameRGBColor): GameRGBColor; @@ -371,196 +379,178 @@ declare class GameRGBAColor { // ────────────────────────────────────────────── /** - * 四元数, 用于三维旋转。 - * A quaternion used for 3‑dimensional rotation. - * - * @remarks - * 单位四元数 (w²+x²+y²+z²=1) 表示纯旋转。 + * @zh 四元数,用于三维旋转。单位四元数(w²+x²+y²+z²=1)表示纯旋转。 + * @en A quaternion used for 3‑dimensional rotation. * Unit quaternions represent pure rotations. */ declare class GameQuaternion { - /** 实部 (标量分量)。Real (scalar) component. */ + /** @zh 实部(标量分量)。 @en Real (scalar) component. */ w: number; - /** 虚部 X 分量。Imaginary X component. */ + /** @zh 虚部 X 分量。 @en Imaginary X component. */ x: number; - /** 虚部 Y 分量。Imaginary Y component. */ + /** @zh 虚部 Y 分量。 @en Imaginary Y component. */ y: number; - /** 虚部 Z 分量。Imaginary Z component. */ + /** @zh 虚部 Z 分量。 @en Imaginary Z component. */ z: number; - /** 创建单位四元数 (1, 0, 0, 0)。Creates an identity quaternion. */ + /** @zh 创建单位四元数 (1, 0, 0, 0)。 @en Creates an identity quaternion. */ constructor(); - /** 用指定的 w/x/y/z 分量创建四元数。 */ + /** @zh 用指定的 w/x/y/z 分量创建四元数。 @en Creates a quaternion with the given w/x/y/z components. */ constructor(w: number, x: number, y: number, z: number); - /** 原地设置所有分量。Sets all components in‑place. */ + /** @zh 原地设置所有分量。 @en Sets all components in‑place. */ set(w: number, x: number, y: number, z: number): GameQuaternion; - /** 原地复制。Copies values from another quaternion in‑place. */ + /** @zh 原地复制。 @en Copies values from another quaternion in‑place. */ copy(v: GameQuaternion): GameQuaternion; - /** 深拷贝。Returns a new independent copy. */ + /** @zh 深拷贝。 @en Returns a new independent copy. */ clone(): GameQuaternion; - /** 逐分量加法。Component‑wise addition. */ + /** @zh 逐分量加法。 @en Component‑wise addition. */ add(v: GameQuaternion): GameQuaternion; - /** 逐分量减法。Component‑wise subtraction. */ + /** @zh 逐分量减法。 @en Component‑wise subtraction. */ sub(v: GameQuaternion): GameQuaternion; /** - * 四元数乘法 (汉密尔顿积): this × q。 - * Hamilton product: this × q. - * @remarks 注意乘法不满足交换律。Multiplication is NOT commutative. + * @zh 四元数乘法(汉密尔顿积):this × q。注意乘法不满足交换律。 + * @en Hamilton product: this × q. Multiplication is NOT commutative. */ mul(q: GameQuaternion): GameQuaternion; - /** - * 共轭四元数 (对单位四元数等价于逆)。 - * Conjugate of this quaternion (equals inverse for unit quaternions). - */ + /** @zh 共轭四元数(对单位四元数等价于逆)。 @en Conjugate of this quaternion (equals inverse for unit quaternions). */ inv(): GameQuaternion; - /** 除法: this × q⁻¹。Division: this × q⁻¹. */ + /** @zh 除法:this × q⁻¹。 @en Division: this × q⁻¹. */ div(q: GameQuaternion): GameQuaternion; - /** 点积: this · q。Dot product. */ + /** @zh 点积:this · q。 @en Dot product. */ dot(q: GameQuaternion): number; - /** 模长 (范数)。Magnitude (norm). */ + /** @zh 模长(范数)。 @en Magnitude (norm). */ mag(): number; - /** 模长平方。Squared magnitude. */ + /** @zh 模长平方。 @en Squared magnitude. */ sqrMag(): number; - /** - * 单位化: 返回模长为 1 的新四元数。 - * Normalizes this quaternion; returns a unit quaternion. - */ + /** @zh 单位化:返回模长为 1 的新四元数。 @en Normalizes this quaternion; returns a unit quaternion. */ normalize(): GameQuaternion; /** - * 球面线性插值 (Slerp): 在 this 和 q 之间平滑旋转。 - * Spherical linear interpolation — smooth rotation between this and q. - * @param t - 插值比率 (0=this, 1=q) / interpolation factor + * @zh 球面线性插值(Slerp):在 this 和 q 之间平滑旋转。 + * @en Spherical linear interpolation — smooth rotation between this and q. + * @param t - @zh 插值比率(0=this,1=q) @en interpolation factor (0=this, 1=q) */ slerp(q: GameQuaternion, t: number): GameQuaternion; - /** - * 返回 this 和 q 之间的角度 (弧度)。 - * Angular difference between this and q (in radians). - */ + /** @zh 返回 this 和 q 之间的角度(弧度)。 @en Angular difference between this and q (in radians). */ angle(q: GameQuaternion): number; /** - * 返回四元数对应的轴‑角表示。 - * Decomposes this quaternion into axis‑angle representation. - * @returns 包含 `angle` 和 `axis` 字段的对象 / object with `angle` and `axis` fields + * @zh 返回四元数对应的轴‑角表示。 + * @en Decomposes this quaternion into axis‑angle representation. + * @returns @zh 包含 `angle` 和 `axis` 字段的对象 @en object with `angle` and `axis` fields */ getAxisAngle(): AxisAngle; - // ── 旋转操作 / Rotation operations ── + // ── @zh 旋转操作 @en Rotation operations ── - /** 绕 X 轴旋转 (在左侧乘以旋转四元数)。Rotate around X axis. */ + /** @zh 绕 X 轴旋转(在左侧乘以旋转四元数)。 @en Rotate around X axis. */ rotateX(rad: number): GameQuaternion; - /** 绕 Y 轴旋转。Rotate around Y axis. */ + /** @zh 绕 Y 轴旋转。 @en Rotate around Y axis. */ rotateY(rad: number): GameQuaternion; - /** 绕 Z 轴旋转。Rotate around Z axis. */ + /** @zh 绕 Z 轴旋转。 @en Rotate around Z axis. */ rotateZ(rad: number): GameQuaternion; - // ── 静态构造器 / Static constructors ── + /** @zh 用此单位四元数旋转向量 v。 @en Rotates vector v by this unit quaternion. */ + rotateVector(v: GameVector3): GameVector3; - /** 从轴‑角表示创建四元数。Create from axis‑angle representation. */ + /** @zh 转为 YZX 欧拉角(弧度),x/y/z 分别对应绕 X/Y/Z 轴的旋转角。 @en Decomposes into YZX Euler angles (radians). */ + toEuler(): GameVector3; + + // ── @zh 静态构造器 @en Static constructors ── + + /** @zh 从轴‑角表示创建四元数。 @en Create from axis‑angle representation. */ static fromAxisAngle(axis: GameVector3, rad: number): GameQuaternion; - /** - * 从欧拉角创建四元数 (YZX 旋转顺序)。 - * Create from Euler angles (YZX rotation order: Y → Z → X). - */ + /** @zh 从欧拉角创建四元数(YZX 旋转顺序:Y → Z → X)。 @en Create from Euler angles (YZX rotation order: Y → Z → X). */ static fromEuler(x: number, y: number, z: number): GameQuaternion; - /** - * 计算从向量 a 旋转到向量 b 的最短弧四元数。 - * Shortest‑arc quaternion rotating from vector a to vector b. - */ + /** @zh 计算从向量 a 旋转到向量 b 的最短弧四元数。 @en Shortest‑arc quaternion rotating from vector a to vector b. */ static rotationBetween(a: GameVector3, b: GameVector3): GameQuaternion; - /** 近似相等检查 (容差 1e‑6)。 */ + /** @zh 构造从 from 看向 to 的朝向四元数(up 为上方向)。 @en Builds a look‑at quaternion orienting -Z from `from` toward `to`. */ + static lookAt(from: GameVector3, to: GameVector3, up: GameVector3): GameQuaternion; + + /** @zh 近似相等检查(容差 1e‑6)。 @en Approximate equality check within 1e‑6 tolerance. */ equals(v: GameQuaternion): boolean; toString(): string; } /** - * 轴‑角表示的返回类型 (由 getAxisAngle() 返回)。 - * Return type for quaternion.getAxisAngle(). + * @zh 轴‑角表示,由 `getAxisAngle()` 返回。 + * @en Axis‑angle representation returned by `getAxisAngle()`. */ interface AxisAngle { - /** 旋转角度 (弧度) / rotation angle in radians */ + /** @zh 旋转角度 (弧度) @en rotation angle in radians */ angle: number; - /** 旋转轴 (单位向量) / rotation axis (unit vector) */ + /** @zh 旋转轴 (单位向量) @en rotation axis (unit vector) */ axis: GameVector3; } /** - * 事件处理器令牌 — 由 world.onXxx() 返回。 - * Event handler token — returned by world.onXxx(). - * - * @remarks - * 调用 cancel() 取消监听后不可恢复, 需重新注册。 - * Once cancelled via cancel(), it cannot be resumed — re-register instead. + * @zh 事件处理器令牌,由 `world.onXxx()` 返回。 + * 调用 `cancel()` 取消监听后不可恢复,需重新注册。 + * @en Event handler token returned by `world.onXxx()`. + * Once cancelled via `cancel()`, it cannot be resumed — re-register instead. */ declare class GameEventHandlerToken { - /** 取消事件监听 (不可逆)。Cancels the event listener (irreversible). */ + /** @zh 取消事件监听 (不可逆) @en Cancels the event listener (irreversible). */ cancel(): void; /** - * 尝试恢复已取消的监听 (会抛出 UnsupportedOperationException)。 - * Attempts to resume a cancelled listener — always throws UnsupportedOperationException. + * @zh 尝试恢复已取消的监听 (会抛出 UnsupportedOperationException)。 + * @en Attempts to resume a cancelled listener — always throws UnsupportedOperationException. * @throws UnsupportedOperationException 始终抛出 / always thrown */ resume(): void; - /** 返回 true 表示监听仍处于活跃状态。Returns true if the listener is still active. */ + /** @zh 返回 true 表示监听仍处于活跃状态 @en Returns true if the listener is still active. */ active(): boolean; } /** - * onTick 回调的参数类型。 - * The info object passed to onTick handlers. + * @zh `onTick` 回调的参数类型。 + * @en The info object passed to `onTick` handlers. */ interface TickInfo { - /** 当前 tick 数。Current tick count. */ + /** @zh 当前 tick 数 @en Current tick count. */ tick: number; - /** 上一 tick 数。Previous tick count. */ + /** @zh 上一 tick 数 @en Previous tick count. */ prevTick: number; - /** 自启动以来的毫秒数。Milliseconds elapsed since server start. */ + /** @zh 自启动以来的毫秒数 @en Milliseconds elapsed since server start. */ elapsedTimeMS: number; - /** 跳过的 tick 数 (MC 下始终为 0)。Number of skipped ticks (always 0 in MC). */ + /** @zh 跳过的 tick 数 (MC 下始终为 0) @en Number of skipped ticks (always 0 in MC). */ skip: number; } -// ================================================================ -// §2 Storage Types — 持久化存储 -// ================================================================ +// ── §2 @zh 持久化存储 @en Persistent Storage ── /** - * JSON 可序列化的值类型。 - * Represents any JSON‑serializable value. - * - * @remarks - * 用作 `GameDataStorage` 的默认类型参数。 + * @zh JSON 可序列化的值类型,用作 `GameDataStorage` 的默认类型参数。 + * @en Represents any JSON‑serializable value. * Used as the default type parameter for `GameDataStorage`. */ type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }; /** - * 数据存储空间 (键值持久化)。 - * A data‑storage namespace — persistent key‑value store backed by JSON files. + * @zh 数据存储空间(键值持久化),通过 `storage.getDataStorage("name")` 获取。 + * T 指定后所有读写操作自动获得类型检查。 * - * @remarks - * 通过 `storage.getDataStorage("name")` 获取, T 指定后所有读写操作自动获得类型检查。 + * @en A data‑storage namespace — persistent key‑value store backed by JSON files. * Obtain via `storage.getDataStorage("name")`; once T is set, all read/write operations are type‑checked. * * @example @@ -572,37 +562,37 @@ type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string */ interface GameDataStorage { /** - * 获取存储空间名称 (只读)。 + * @zh 获取存储空间名称 (只读)。 * @en Returns the read‑only namespace name. */ readonly key: string; /** - * 存入一个键值对。值必须是可 JSON 序列化的类型。 - * Stores a key‑value pair. Value must be JSON‑serializable. - * @param key - 键 / key - * @param value - 值 / value (typed to T) + * @zh 存入一个键值对。值必须是可 JSON 序列化的类型。 + * @en Stores a key‑value pair. Value must be JSON‑serializable. + * @param key - @zh 键 @en key + * @param value - @zh 值 @en value (typed to T) */ set(key: string, value: T): void; /** - * 读取键对应的值, 不存在则返回 null。 - * Retrieves the value for a key, or null if it does not exist. - * @returns 存储的值, 或 null + * @zh 读取键对应的值, 不存在则返回 null。 + * @en Retrieves the value for a key, or null if it does not exist. + * @returns @zh 存储的值, 或 null @en The stored value, or null */ get(key: string): T | null; /** - * 获取当前存储空间中的所有键。 - * Lists all keys in this storage namespace. + * @zh 获取当前存储空间中的所有键。 + * @en Lists all keys in this storage namespace. */ keys(): string[]; /** - * 原子更新: 取出当前值, 用 handler(currentValue) 的结果覆盖。 - * Atomically updates a value using a callback. - * @param key - 键 / key - * @param handler - (prevValue) => newValue / callback receiving the old value, returning the new one + * @zh 原子更新: 取出当前值, 用 handler(currentValue) 的结果覆盖。 + * @en Atomically updates a value using a callback. + * @param key - @zh 键 @en key + * @param handler - @zh (prevValue) => newValue @en callback receiving the old value, returning the new one * @remarks 如果键不存在, 不会创建新条目 (遵循 Box3 规范)。 * If the key does not exist, nothing happens (per Box3 spec). * @@ -613,34 +603,34 @@ interface GameDataStorage { update(key: string, handler: (prevValue: T) => T): void; /** - * 删除键, 返回旧值 (不存在则返回 null)。 - * Removes a key and returns its previous value, or null. - * @returns 被删除的旧值 / the previous value, or null + * @zh 删除键, 返回旧值 (不存在则返回 null)。 + * @en Removes a key and returns its previous value, or null. + * @returns @zh 被删除的旧值, 或 null @en The previous value, or null */ remove(key: string): T | null; /** - * 原子递增 (delta 默认为 1)。 - * Atomically increments a numeric value by delta (default 1). - * @param key - 键 / key - * @param delta - 增量 (可选, 默认 1) / increment amount (optional, default 1) - * @returns 递增后的新值 / the new value after incrementing + * @zh 原子递增 (delta 默认为 1)。 + * @en Atomically increments a numeric value by delta (default 1). + * @param key - @zh 键 @en key + * @param delta - @zh 增量(可选,默认 1) @en increment amount (optional, default 1) + * @returns @zh 递增后的新值 @en The new value after incrementing * @remarks 键不存在时从 0 + delta 开始。 * If the key doesn't exist, starts from 0 + delta. */ increment(key: string, delta?: number): number; /** - * 分页查询存储条目。 - * Paginated query of stored entries. - * @param options - 查询选项 / query options - * @param options.cursor - 起始游标 (页码) / starting cursor (page number * pageSize) - * @param options.pageSize - 每页条目数 (1‑100, 默认 100) / items per page (1–100, default 100) - * @param options.ascending - 是否升序排列 / sort ascending if true - * @param options.max - 值的上限过滤 / maximum value filter - * @param options.min - 值的下限过滤 / minimum value filter - * @param options.constraintTarget - 排序/过滤的目标路径 (如 "a.b.c") / nested path for sorting/filtering - * @returns 分页结果对象 / paginated query result + * @zh 分页查询存储条目。 + * @en Paginated query of stored entries. + * @param options - @zh 查询选项 @en query options + * @param options.cursor - @zh 起始游标(页码) @en starting cursor (page number * pageSize) + * @param options.pageSize - @zh 每页条目数(1‑100,默认 100) @en items per page (1–100, default 100) + * @param options.ascending - @zh 是否升序排列 @en sort ascending if true + * @param options.max - @zh 值的上限过滤 @en maximum value filter + * @param options.min - @zh 值的下限过滤 @en minimum value filter + * @param options.constraintTarget - @zh 排序/过滤的目标路径(如 "a.b.c") @en nested path for sorting/filtering + * @returns @zh 分页结果对象 @en paginated query result */ list(options?: { cursor?: number; @@ -652,68 +642,67 @@ interface GameDataStorage { }): QueryList; /** - * 销毁该存储空间 (删除对应 JSON 文件)。 - * Destroys this storage namespace (deletes the backing JSON file). + * @zh 销毁该存储空间 (删除对应 JSON 文件)。 + * @en Destroys this storage namespace (deletes the backing JSON file). */ destroy(): void; } /** - * 分页查询结果 (由 GameDataStorage.list() 返回)。 - * Paginated query result returned by GameDataStorage.list(). + * @zh 分页查询结果,由 `GameDataStorage.list()` 返回。 + * @en Paginated query result returned by `GameDataStorage.list()`. */ interface QueryList { - /** 是否已到达最后一页。Whether the last page has been reached. */ + /** @zh 是否已到达最后一页 @en Whether the last page has been reached. */ isLastPage: boolean; /** - * 获取当前页的条目数组。 - * Returns the entries for the current page. + * @zh 获取当前页的条目数组。 + * @en Returns the entries for the current page. */ getCurrentPage(): ReturnValue[]; /** - * 移动到下一页。 - * Advances the cursor to the next page. + * @zh 移动到下一页。 + * @en Advances the cursor to the next page. */ nextPage(): void; } /** - * 单个存储条目 (包含元数据)。 - * A single stored entry with metadata. + * @zh 单个存储条目,包含元数据。 + * @en A single stored entry with metadata. */ interface ReturnValue { - /** 键名 / key name */ + /** @zh 键名 @en key name */ key: string; - /** 值 / stored value */ + /** @zh 值 @en stored value */ value: T; - /** 更新时间 (Unix 毫秒) / last‑modified timestamp (Unix ms) */ + /** @zh 更新时间 (Unix 毫秒) @en last‑modified timestamp (Unix ms) */ updateTime: number; - /** 创建时间 (Unix 毫秒) / creation timestamp (Unix ms) */ + /** @zh 创建时间 (Unix 毫秒) @en creation timestamp (Unix ms) */ createTime: number; - /** 版本标识符 (可用于乐观锁) / version identifier (usable for optimistic locking) */ + /** @zh 版本标识符 (可用于乐观锁) @en version identifier (usable for optimistic locking) */ version: string; } /** - * 全局存储入口 — 脚本中通过 `storage` 访问。 - * Global storage entry point — accessed via `storage` in scripts. + * @zh 全局存储入口 — 脚本中通过 `storage` 访问。 + * 项目间数据隔离:每个项目自动使用项目名作为存储文件前缀。 + * 跨项目共享:`getGroupStorage` 使用 `__shared__/` 命名空间,所有项目访问同一数据。 * - * @remarks - * 项目间数据隔离: 每个项目自动使用项目名作为存储文件前缀。 + * @en Global storage entry point — accessed via `storage` in scripts. * Per‑project isolation: each project's storage is automatically prefixed with the project name. - * 跨项目共享: `getGroupStorage` 使用 `__shared__/` 命名空间, 所有项目访问同一数据。 * Cross‑project sharing: `getGroupStorage` uses a `__shared__/` namespace visible to all projects. */ interface GameStorage { - /** 始终返回空字符串 (MC 本地存储无 key, 只读)。Always returns "" for MC local storage, readonly. */ + /** @zh 始终返回空字符串 (MC 本地存储无 key, 只读) @en Always returns "" for MC local storage, readonly. */ readonly key: string; /** - * 打开或创建指定名称的数据存储空间 (项目隔离)。 - * Opens or creates a named data‑storage namespace (per‑project isolated). - * @param name - 命名空间 (可含 "/" 作为目录分隔) / namespace (may contain "/" as directory separator) + * @zh 打开或创建指定名称的数据存储空间 (项目隔离)。 + * @en Opens or creates a named data‑storage namespace (per‑project isolated). + * @param name - @zh 命名空间(可含 "/" 作为目录分隔) @en namespace (may contain "/" as directory separator) * @remarks 不同项目使用同一 name 会访问不同文件。 * Different projects using the same name access different files. * @@ -724,9 +713,9 @@ interface GameStorage { getDataStorage(name: string): GameDataStorage; /** - * 获取跨项目共享存储 — 所有项目通过同一 name 读写同一份数据。 - * Shared cross‑project storage — all projects read/write the same data by name. - * @param name - 命名空间 / namespace + * @zh 获取跨项目共享存储 — 所有项目通过同一 name 读写同一份数据。 + * @en Shared cross‑project storage — all projects read/write the same data by name. + * @param name - @zh 命名空间 @en namespace * @remarks 底层使用 `__shared__/` 前缀, 适合全服排行榜、全局配置等场景。 * Uses `__shared__/` prefix internally; suitable for global leaderboards, shared config, etc. */ @@ -734,13 +723,13 @@ interface GameStorage { } /** - * SQL 查询结果,支持迭代和 thenable 模式。 - * SQL query result — supports iteration and thenable pattern. - * - * @remarks + * @zh SQL 查询结果,支持迭代和 thenable 模式。 * SELECT 查询返回行数据,INSERT/UPDATE/DELETE 返回受影响行数。 + * + * @en SQL query result — supports iteration and thenable pattern. * SELECT queries return row data; INSERT/UPDATE/DELETE return affected row count. * + * @example * ```ts * // 获取所有行 / Get all rows * const rows = db.sql("SELECT * FROM players").rows; @@ -757,60 +746,60 @@ interface GameStorage { * ``` */ interface GameQueryResult> { - /** 所有行 / All rows */ + /** @zh 所有行 @en All rows */ readonly rows: T[]; - /** 第一行,无结果时返回 null / First row, or null if empty */ + /** @zh 第一行,无结果时返回 null @en First row, or null if empty */ readonly firstRow: T | null; - /** 列名数组 / Column name array */ + /** @zh 列名数组 @en Column name array */ readonly columnNames: string[]; - /** 列数 / Column count */ + /** @zh 列数 @en Column count */ readonly columnCount: number; - /** 行数 (SELECT) / Row count (for SELECT queries) */ + /** @zh 行数 (SELECT) @en Row count (for SELECT queries) */ readonly rowCount: number; /** - * 受影响行数 (INSERT/UPDATE/DELETE),SELECT 查询返回 -1。 - * Affected row count for INSERT/UPDATE/DELETE; -1 for SELECT queries. + * @zh 受影响行数 (INSERT/UPDATE/DELETE),SELECT 查询返回 -1。 + * @en Affected row count for INSERT/UPDATE/DELETE; -1 for SELECT queries. */ readonly affectedRows: number; - /** 是否为查询 (SELECT) / Whether this is a query (SELECT) */ + /** @zh 是否为查询 (SELECT) @en Whether this is a query (SELECT) */ readonly isQuery: boolean; /** - * 返回下一行: `{done: boolean, value: T}`。 - * Returns the next row as `{done: boolean, value: T}`. + * @zh 返回下一行: `{done: boolean, value: T}`。 + * @en Returns the next row as `{done: boolean, value: T}`. */ next(): { done: boolean; value: T }; - /** 重置内部游标到第一行 / Resets internal cursor to first row */ + /** @zh 重置内部游标到第一行 @en Resets internal cursor to first row */ reset(): void; /** - * Thenable 支持 — resolve 接收所有行数组。 - * Thenable support — resolve receives the full row array. + * @zh Thenable 支持 — resolve 接收所有行数组。 + * @en Thenable support — resolve receives the full row array. */ then(resolve: (rows: T[]) => void, reject?: (err: string) => void): void; } /** - * SQLite 数据库 (自动按项目隔离到 `config/box3/data/.db`)。 - * SQLite database — auto-isolated per project at `config/box3/data/.db`. + * @zh SQLite 数据库,自动按项目隔离到 `config/box3/data/.db`。 + * 通过全局 `db` 访问,支持 `?` 占位符和 tagged template 两种调用约定。 * - * @remarks - * 通过全局 `db` 访问,支持两种调用约定: - * Access via global `db`, supports two calling conventions: + * @en SQLite database — auto-isolated per project at `config/box3/data/.db`. + * Access via global `db`, supports both `?` placeholder and tagged template calling conventions. * + * @example * ```ts * // 常规查询 / Regular query * db.sql("SELECT * FROM players WHERE score > ?", 100); * - * // 模板字面量 (TS 编译后) / Tagged template (after TS compilation) - * db.sql(["SELECT * FROM players WHERE id = ", ""], playerId); + * // Tagged template (TS 编译后) / Tagged template (after TS compilation) + * db.sql`SELECT * FROM players WHERE id = ${playerId}`; * * // INSERT / UPDATE / DELETE * db.sql("INSERT INTO log (name, msg) VALUES (?, ?)", "Steve", "hello"); @@ -819,14 +808,12 @@ interface GameQueryResult> { */ interface GameDatabase { /** - * 执行 SQL 查询或更新。 - * Executes a SQL query or update. + * @zh 执行 SQL 查询或更新。 + * @en Executes a SQL query or update. * - * @param sql - SQL 字符串 (含 ? 占位符) 或字符串数组 (模板字面量)。 - * SQL string (with ? placeholders) or string array (template literal). - * @param params - 参数值 (number | string | boolean | null | Uint8Array) - * Parameter values to bind. - * @returns 查询结果 / query result + * @param sql - @zh SQL 字符串(含 ? 占位符)或字符串数组(模板字面量) @en SQL string (with ? placeholders) or string array (template literal) + * @param params - @zh 参数值 @en Parameter values to bind (number | string | boolean | null | Uint8Array) + * @returns @zh 查询结果 @en query result * * @example * // 指定行类型获得完整类型检查 / Specify row type for full type-checking @@ -843,198 +830,216 @@ interface GameDatabase { ): GameQueryResult; } -// ================================================================ -// §3 Entity — 实体 -// ================================================================ +// ── §3 @zh 实体 @en Entity ── /** - * 实体包装, 可用于玩家或生物。 - * Entity wrapper — represents a player or mob in the world. + * @zh 实体包装,可用于玩家或生物。 + * 通过 `world.querySelector()`、`world.querySelectorAll()` 或事件回调获取。 * - * @remarks - * 通过 `world.querySelector()`, `world.querySelectorAll()` 或事件回调获取。 + * @en Entity wrapper — represents a player or mob in the world. * Obtained via `world.querySelector()`, `world.querySelectorAll()`, or event callbacks. */ interface GameEntity { - // ── 身份 / Identity ── + // ── @zh 身份 @en Identity ── /** - * 实体 UUID (字符串格式, 只读)。 - * Entity UUID as a string (e.g. "550e8400-e29b-41d4-a716-446655440000"), readonly. + * @zh 实体 UUID (字符串格式, 只读)。 + * @en Entity UUID as a string (e.g. "550e8400-e29b-41d4-a716-446655440000"), readonly. */ readonly id: string; /** - * 是否为玩家实体。返回 true 后 player 属性自动收窄为非 null。 - * True if this entity is a player. After a truthy check, `player` is narrowed to non-null. + * @zh 是否为玩家实体。返回 true 后 player 属性自动收窄为非 null。 + * @en True if this entity is a player. After a truthy check, `player` is narrowed to non-null. */ isPlayer(): this is GamePlayerEntity; /** - * 实体类型标识符 (如 "minecraft:zombie", 只读)。 - * Entity type identifier (e.g. "minecraft:zombie"), readonly. + * @zh 实体类型标识符 (如 "minecraft:zombie", 只读)。 + * @en Entity type identifier (e.g. "minecraft:zombie"), readonly. */ readonly entityType: string; - // ── 位置 & 运动 / Position & Movement ── + // ── @zh 位置 & 运动 @en Position & Movement ── /** - * 当前坐标 (世界坐标, 只读, 可通过 .set() 修改)。 - * Current world‑space position. Readonly ref — mutate via .set(), cannot reassign. + * @zh 当前坐标 (世界坐标, 只读, 可通过 .set() 修改)。 + * @en Current world‑space position. Readonly ref — mutate via .set(), cannot reassign. */ readonly position: GameVector3; /** - * 当前速度 (运动向量, 只读, 可通过 .set() 修改)。 - * Current velocity (motion vector). Readonly ref — mutate via .set(), cannot reassign. + * @zh 当前速度 (运动向量, 只读, 可通过 .set() 修改)。 + * @en Current velocity (motion vector). Readonly ref — mutate via .set(), cannot reassign. */ readonly velocity: GameVector3; /** - * 包围盒半尺寸 (x=宽/2, y=高/2, z=宽/2, 只读)。 - * Bounding‑box half‑extents (x=width/2, y=height/2, z=width/2), readonly. + * @zh 包围盒半尺寸 (x=宽/2, y=高/2, z=宽/2, 只读)。 + * @en Bounding‑box half‑extents (x=width/2, y=height/2, z=width/2), readonly. */ readonly bounds: GameVector3; /** - * 是否在地面上 (只读)。 - * True if the entity is standing on a block, readonly. + * @zh 是否在地面上 (只读)。 + * @en True if the entity is standing on a block, readonly. */ readonly onGround: boolean; /** - * 视线起始点 (眼部位置, 只读)。 - * Eye position (raycast origin for the entity's view), readonly. + * @zh 视线起始点 (眼部位置, 只读)。 + * @en Eye position (raycast origin for the entity's view), readonly. */ readonly eyePosition: GameVector3; - // ── 生命状态 / Lifecycle ── + // ── @zh 生命状态 @en Lifecycle ── /** - * 当前生命值。 - * Current health (HP). + * @zh 当前生命值。 + * @en Current health (HP). */ hp: number; /** - * 最大生命值。 - * Maximum health. + * @zh 最大生命值。 + * @en Maximum health. */ maxHp: number; /** - * 实体是否已被移除/销毁 (true = 已移除, 只读)。 - * Whether the entity has been removed / destroyed (true = removed), readonly. + * @zh 实体是否已被移除/销毁 (true = 已移除, 只读)。 + * @en Whether the entity has been removed / destroyed (true = removed), readonly. */ readonly destroyed: boolean; /** - * 设置实体着火 tick 数 (0 = 灭火)。 - * Sets the remaining fire ticks (0 = extinguish). + * @zh 设置实体着火 tick 数 (0 = 灭火)。 + * @en Sets the remaining fire ticks (0 = extinguish). */ setFire(ticks: number): void; - /** 灭火。Extinguishes any fire on the entity. */ + /** @zh 灭火 @en Extinguishes any fire on the entity. */ clearFire(): void; - // ── 伤害 & 恢复 / Damage & Healing ── + // ── @zh 伤害 & 恢复 @en Damage & Healing ── /** - * 对实体造成伤害。 - * Deals generic damage to the entity. - * @param amount - 伤害值 (半心) / damage amount in half‑hearts + * @zh 对实体造成伤害。 + * @en Deals generic damage to the entity. + * @param amount - @zh 伤害值(半心) @en damage amount in half‑hearts */ hurt(amount: number): void; /** - * 治疗实体。 - * Heals the entity. - * @param amount - 治疗量 (半心) / healing amount in half‑hearts + * @zh 治疗实体。 + * @en Heals the entity. + * @param amount - @zh 治疗量(半心) @en healing amount in half‑hearts */ heal(amount: number): void; - // ── 外观 / Appearance ── + // ── @zh 外观 @en Appearance ── /** - * 是否不可见 (隐身)。 - * True if the entity is invisible. + * @zh 是否不可见 (隐身)。 + * @en True if the entity is invisible. */ meshInvisible: boolean; - /** 是否发光 (轮廓高亮)。Whether glow outline is active. */ + /** @zh 是否发光 (轮廓高亮) @en Whether glow outline is active. */ glowing: boolean; + /** @zh 设置发光颜色 (通过队伍颜色实现, 映射到最接近的 ChatFormatting)。 @en Sets glow outline color (via team color, mapped to nearest ChatFormatting). */ + setGlowColor(color: GameRGBColor): void; + + // ── @zh 文字展示实体 @en TextDisplay ── + + /** @zh 设置文字展示实体的文本 (仅 text_display 实体有效)。 @en Sets text for text display entities. */ + setText(text: string): void; + /** @zh 设置文字展示实体的文本颜色。 @en Sets the text color for text display entities. */ + setTextColor(color: GameRGBColor): void; + /** @zh 设置文字展示实体的背景颜色。 @en Sets the background color for text display entities. */ + setTextBackgroundColor(color: GameRGBAColor): void; + /** - * 名称标签文本 (空字符串 = 无)。 - * Custom name tag text (empty string = none). + * @zh 名称标签文本 (空字符串 = 无)。 + * @en Custom name tag text (empty string = none). */ nameTag: string; setNameTag(name: string): void; - // ── 物理 / Physics ── + // ── @zh 物理 @en Physics ── /** - * 是否参与碰撞 (默认 true)。 - * Whether the entity participates in collisions (default true). + * @zh 是否参与碰撞 (默认 true)。 + * @en Whether the entity participates in collisions (default true). */ collides: boolean; /** - * 是否固定 (默认 false, true 时禁用重力并每 tick 清零速度)。 - * Whether the entity is fixed in place (default false; disables gravity + zeros velocity each tick). + * @zh 是否固定 (默认 false, true 时禁用重力并每 tick 清零速度)。 + * @en Whether the entity is fixed in place (default false; disables gravity + zeros velocity each tick). */ fixed: boolean; /** - * 是否受重力影响 (默认 true)。 - * Whether gravity affects the entity (default true). + * @zh 是否受重力影响 (默认 true)。 + * @en Whether gravity affects the entity (default true). */ gravity: boolean; - /** 摩擦系数 (默认 0.0)。Friction coefficient. */ + /** @zh 摩擦系数 (默认 0.0) @en Friction coefficient. */ friction: number; - /** 质量 (默认 1.0)。Mass. */ + /** @zh 质量 (默认 1.0) @en Mass. */ mass: number; - /** 弹性系数 (默认 0.0)。Restitution (bounciness). */ + /** @zh 弹性系数 (默认 0.0) @en Restitution (bounciness). */ restitution: number; - // ── 无敌 & 持久化 / Invulnerability & Persistence ── + // ── @zh 无敌 & 持久化 @en Invulnerability & Persistence ── - /** 是否无敌。Whether the entity is invulnerable to damage. */ + /** @zh 是否无敌 @en Whether the entity is invulnerable to damage. */ invulnerable: boolean; /** - * 设置为持久化实体 (防止被自然清除)。 - * Marks the entity as persistent (prevents it from being despawned naturally). + * @zh 设置为持久化实体 (防止被自然清除)。 + * @en Marks the entity as persistent (prevents it from being despawned naturally). * @remarks 仅写方法, 无 getter。Write‑only method, no getter available. */ setPersistent(v: boolean): void; - // ── 标签 / Tags ── + // ── @zh 标签 @en Tags ── - /** 添加一个标签。Adds a scoreboard tag. */ + /** @zh 添加一个标签 @en Adds a scoreboard tag. */ addTag(tag: string): void; - /** 移除一个标签。Removes a scoreboard tag. */ + /** @zh 移除一个标签 @en Removes a scoreboard tag. */ removeTag(tag: string): void; - /** 检查是否拥有指定标签。Checks whether the entity has the given tag. */ + /** @zh 检查是否拥有指定标签 @en Checks whether the entity has the given tag. */ hasTag(tag: string): boolean; - /** 获取所有标签。Returns all tags as a string array. */ + /** @zh 获取所有标签 @en Returns all tags as a string array. */ tags(): string[]; - // ── 效果 / Effects ── + // ── @zh 效果 @en Effects ── /** - * 添加状态效果。 - * Applies a status effect to the entity. - * @param effectId - 效果 ID (如 "minecraft:speed") - * @param duration - 持续时间 (tick) - * @param amplifier - 等级 (0 = 一级) - * @param hideParticles - 是否隐藏粒子 (可选, 默认 false) + * @zh 添加状态效果。 + * @en Applies a status effect to the entity. + * + * @example + * @zh ```ts + * @en // 给予实体 30 秒速度 II 效果,隐藏粒子 + * entity.addEffect("minecraft:speed", 600, 1, true); + * // 给予实体 10 秒发光效果 + * entity.addEffect("minecraft:glowing", 200, 0); + * ``` + * + * @param effectId - @zh 效果 ID(如 "minecraft:speed") @en Effect ID (e.g. "minecraft:speed") + * @param duration - @zh 持续时间(tick) @en Duration in ticks + * @param amplifier - @zh 等级(0 = 一级) @en Amplifier (0 = level I) + * @param hideParticles - @zh 是否隐藏粒子(可选,默认 false) @en Whether to hide particles (optional, default false) */ addEffect( effectId: string, @@ -1043,31 +1048,31 @@ interface GameEntity { hideParticles?: boolean, ): void; - // ── 属性 / Attributes ── + // ── @zh 属性 @en Attributes ── /** - * 读取实体属性值。 - * Reads a registered entity attribute value. - * @param attributeId - 属性 ID (如 "minecraft:generic.max_health") - * @returns 当前属性值, 不支持的实体返回 0 + * @zh 读取实体属性值。 + * @en Reads a registered entity attribute value. + * @param attributeId - @zh 属性 ID @en attribute ID (e.g. "minecraft:generic.max_health") + * @returns @zh 当前属性值,不支持的实体返回 0 @en Current attribute value, 0 for unsupported entities */ getAttribute(attributeId: string): number; /** - * 设置实体属性基础值。 - * Sets the base value of a registered entity attribute. - * @param attributeId - 属性 ID (如 "minecraft:generic.movement_speed") - * @param value - 新基础值 / new base value + * @zh 设置实体属性基础值。 + * @en Sets the base value of a registered entity attribute. + * @param attributeId - @zh 属性 ID @en attribute ID (e.g. "minecraft:generic.movement_speed") + * @param value - @zh 新基础值 @en new base value * @remarks 仅对 LivingEntity 有效。Only works on living entities. */ setAttribute(attributeId: string, value: number): void; - // ── 装备 / Equipment ── + // ── @zh 装备 @en Equipment ── /** - * 给生物设置装备。 - * Equips an item onto a mob's equipment slot. - * @param slot - 槽位名称 / slot name: + * @zh 给生物设置装备。 + * @en Equips an item onto a mob's equipment slot. + * @param slot - @zh 槽位名称 @en slot name: * "mainhand", "offhand", "head"/"helmet"/"helm", * "chest"/"chestplate", "legs"/"leggings", "feet"/"boots" * @param itemId - 物品 ID (如 "minecraft:diamond_sword") @@ -1075,332 +1080,333 @@ interface GameEntity { setEquipment(slot: string, itemId: string): void; /** - * 设置装备掉落概率。 - * Sets the drop chance for an equipment slot. - * @param slot - 槽位名称 或 "all" / slot name or "all" for every slot - * @param chance - 掉落概率 (0‑1) / drop chance (0–1) + * @zh 设置装备掉落概率。 + * @en Sets the drop chance for an equipment slot. + * @param slot - @zh 槽位名称 或 "all"(所有槽位) @en slot name or "all" for every slot + * @param chance - @zh 掉落概率(0‑1) @en drop chance (0–1) */ setDropChance(slot: string, chance: number): void; - // ── 导航 & AI / Navigation & AI ── + // ── @zh 导航 & AI @en Navigation & AI ── /** - * 让生物导航到指定坐标。 - * Orders a pathfinder mob to navigate to the given coordinates. - * @param x, y, z - 目标坐标 - * @param speed - 移动速度倍率 - * @returns 路径计算成功返回 true, 非 PathfinderMob 返回 false + * @zh 让生物导航到指定坐标。 + * @en Orders a pathfinder mob to navigate to the given coordinates. + * @param x, y, z - @zh 目标坐标 @en target coordinates + * @param speed - @zh 移动速度倍率 @en movement speed multiplier + * @returns @zh 路径计算成功返回 true,非 PathfinderMob 返回 false @en true if pathfinding succeeded, false for non-PathfinderMob entities */ navigateTo(x: number, y: number, z: number, speed: number): boolean; - /** GameVector3 重载。GameVector3 overload. */ + /** @zh GameVector3 重载 @en GameVector3 overload. */ navigateTo(pos: GameVector3, speed: number): boolean; /** - * 设置生物的当前攻击目标。 - * Sets the mob's attack target (the mob will pathfind to and attack it). + * @zh 设置生物的当前攻击目标。 + * @en Sets the mob's attack target (the mob will pathfind to and attack it). */ setTarget(target: GameEntity): void; - /** 清除攻击目标, 停止追击。Clears the attack target, stopping pursuit. */ + /** @zh 清除攻击目标, 停止追击 @en Clears the attack target, stopping pursuit. */ clearTarget(): void; /** - * 获取当前攻击目标 (可能为 null)。 - * Returns the mob's current attack target, or null. + * @zh 获取当前攻击目标 (可能为 null)。 + * @en Returns the mob's current attack target, or null. */ getTarget(): GameEntity | null; /** - * 启用或禁用生物 AI (寻路/目标等)。 - * Enables or disables the mob's AI (pathfinding, goals, etc.). + * @zh 启用或禁用生物 AI (寻路/目标等)。 + * @en Enables or disables the mob's AI (pathfinding, goals, etc.). */ setAI(enabled: boolean): void; - // ── 朝向 / Look direction ── + // ── @zh 朝向 @en Look direction ── /** - * 让实体看向指定坐标。 - * Makes the entity look at a point in space. + * @zh 让实体看向指定坐标。 + * @en Makes the entity look at a point in space. */ lookAt(x: number, y: number, z: number): void; lookAt(pos: GameVector3): void; - // ── 生命周期 / Lifecycle ── + // ── @zh 生命周期 @en Lifecycle ── /** - * 销毁实体 (触发 onDestroy 回调)。 - * Destroys the entity (triggers any registered onDestroy callback). + * @zh 销毁实体 (触发 onDestroy 回调)。 + * @en Destroys the entity (triggers any registered onDestroy callback). */ destroy(): void; setOnDestroy(handler: (entity: GameEntity) => void): void; - // ── 玩家代理 / Player proxy ── + // ── @zh 玩家代理 @en Player proxy ── /** - * 玩家接口 (仅当 isPlayer 为 true 时非 null)。 - * The player interface — non‑null only when isPlayer is true. + * @zh 玩家接口 (仅当 isPlayer 为 true 时非 null)。 + * @en The player interface — non‑null only when isPlayer is true. */ player: GamePlayer | null; } /** - * 玩家实体 — GameEntity 的子类型, 保证 player 属性非 null。 - * A player entity — subtype of GameEntity with a guaranteed non‑null `player`. + * @zh 玩家实体 — `GameEntity` 的子类型,保证 `player` 属性非 null。 + * @en A player entity — subtype of `GameEntity` with a guaranteed non‑null `player`. */ type GamePlayerEntity = GameEntity & { player: GamePlayer }; -// ================================================================ -// §4 Player — 玩家 -// ================================================================ +// ── §4 @zh 玩家 @en Player ── /** - * 玩家扩展接口 (通过 entity.player 访问)。 - * Player‑specific interface — accessed via `entity.player`. + * @zh 玩家扩展接口,通过 `entity.player` 访问。 + * @en Player‑specific interface — accessed via `entity.player`. */ interface GamePlayer { - // ── 身份 / Identity ── + // ── @zh 身份 @en Identity ── - /** 玩家名 (只读)。Player display name, readonly. */ + /** @zh 玩家名 (只读) @en Player display name, readonly. */ readonly name: string; - /** 玩家 UUID (与 entity.id 相同, 只读)。Player UUID (same as entity.id), readonly. */ + /** @zh 玩家 UUID (与 entity.id 相同, 只读) @en Player UUID (same as entity.id), readonly. */ readonly userId: string; - // ── 位置 & 运动 / Position & Movement ── + // ── @zh 位置 & 运动 @en Position & Movement ── /** - * 当前坐标 (世界坐标, 只读, 可通过 .set() 修改)。 - * Current world‑space position. Readonly ref — mutate via .set(), cannot reassign. + * @zh 当前坐标 (世界坐标, 只读, 可通过 .set() 修改)。 + * @en Current world‑space position. Readonly ref — mutate via .set(), cannot reassign. */ readonly position: GameVector3; /** - * 当前速度 (运动向量, 只读, 可通过 .set() 修改)。 - * Current velocity (motion vector). Readonly ref — mutate via .set(), cannot reassign. + * @zh 当前速度 (运动向量, 只读, 可通过 .set() 修改)。 + * @en Current velocity (motion vector). Readonly ref — mutate via .set(), cannot reassign. */ readonly velocity: GameVector3; /** - * 包围盒半尺寸 (只读)。 - * Bounding‑box half‑extents, readonly. + * @zh 包围盒半尺寸 (只读)。 + * @en Bounding‑box half‑extents, readonly. */ readonly bounds: GameVector3; /** - * 是否在地面上 (只读)。 - * True if the player is standing on a block, readonly. + * @zh 是否在地面上 (只读)。 + * @en True if the player is standing on a block, readonly. */ readonly onGround: boolean; - // ── 外观 / Appearance ── + // ── @zh 外观 @en Appearance ── /** - * 是否隐身。 - * Whether the player is invisible. + * @zh 是否隐身。 + * @en Whether the player is invisible. */ invisible: boolean; /** - * 模型缩放比例 (MC 原生, 非 Box3 scale)。 - * Player model scale (Minecraft native, not Box3 scale). + * @zh 模型缩放比例 (MC 原生, 非 Box3 scale)。 + * @en Player model scale (Minecraft native, not Box3 scale). */ readonly scale: number; - // ── 移动 / Movement ── + // ── @zh 移动 @en Movement ── - /** 行走速度 (基础值)。Walk speed (base attribute value). */ + /** @zh 行走速度 (基础值) @en Walk speed (base attribute value). */ walkSpeed: number; /** - * 疾跑速度 (≈ walkSpeed × 1.3)。 - * Run/sprint speed (≈ walkSpeed × 1.3). + * @zh 疾跑速度 (≈ walkSpeed × 1.3)。 + * @en Run/sprint speed (≈ walkSpeed × 1.3). */ runSpeed: number; /** - * 跳跃力度。 - * Jump power (jump strength attribute). + * @zh 跳跃力度。 + * @en Jump power (jump strength attribute). */ jumpPower: number; /** - * 当前移动状态。 - * Current movement state. + * @zh 当前移动状态。 + * @en Current movement state. * @returns "FLYING" | "GROUND" | "SWIM" | "FALL" | "JUMP" */ readonly moveState: string; /** - * 当前行走状态。 - * Current walk state. + * @zh 当前行走状态。 + * @en Current walk state. * @returns "NONE" | "CROUCH" | "WALK" | "RUN" */ readonly walkState: string; - // ── 跳跃 / 潜行 / 游泳 / Jump / Sneak / Swim ── + // ── @zh 跳跃 / 潜行 / 游泳 @en Jump / Sneak / Swim ── /** - * 是否允许跳跃 (默认 true, false 时清除跳跃力)。 - * Whether jumping is enabled (default true; when false, jump strength is zeroed). + * @zh 是否允许跳跃 (默认 true, false 时清除跳跃力)。 + * @en Whether jumping is enabled (default true; when false, jump strength is zeroed). */ enableJump: boolean; - /** 潜行速度 (默认 0.0, MC 下无独立潜行速度)。Crouch speed (stored as custom prop). */ + /** @zh 潜行速度 (默认 0.0, MC 下无独立潜行速度) @en Crouch speed (stored as custom prop). */ crouchSpeed: number; - /** 游泳速度 (映射到 WATER_MOVEMENT_EFFICIENCY 属性)。Swim speed (maps to WATER_MOVEMENT_EFFICIENCY attribute). */ + /** @zh 游泳速度 (映射到 WATER_MOVEMENT_EFFICIENCY 属性) @en Swim speed (maps to WATER_MOVEMENT_EFFICIENCY attribute). */ swimSpeed: number; - // ── 飞行 & 碰撞 / Flying & Collision ── + // ── @zh 飞行 & 碰撞 @en Flying & Collision ── - /** 是否允许飞行。Whether flight is enabled. */ + /** @zh 是否允许飞行 @en Whether flight is enabled. */ canFly: boolean; - /** 是否正在飞行。Whether the player is currently flying. */ + /** @zh 是否正在飞行 @en Whether the player is currently flying. */ flying: boolean; - /** 飞行速度。Flying speed. */ + /** @zh 飞行速度 @en Flying speed. */ flySpeed: number; /** - * 碰撞开关 (通过队伍碰撞规则实现)。 - * Collision toggle (implemented via team collision rules). + * @zh 碰撞开关 (通过队伍碰撞规则实现)。 + * @en Collision toggle (implemented via team collision rules). */ collision: boolean; - /** 是否为观察者模式。Whether the player is in spectator mode. */ + /** @zh 是否为观察者模式 @en Whether the player is in spectator mode. */ readonly spectator: boolean; - /** 是否禁用飞行 (不允许且自动关闭飞行)。Whether flying is disabled entirely. */ + /** @zh 是否禁用飞行 (不允许且自动关闭飞行) @en Whether flying is disabled entirely. */ disableFly: boolean; - // ── 游戏模式 / Game Mode ── + // ── @zh 游戏模式 @en Game Mode ── /** - * 游戏模式字符串 (如 "survival", "creative", "adventure", "spectator")。 - * Game mode as a string (e.g. "survival", "creative", "adventure", "spectator"). + * @zh 游戏模式字符串 (如 "survival", "creative", "adventure", "spectator")。 + * @en Game mode as a string (e.g. "survival", "creative", "adventure", "spectator"). * 也可以接受数字 (0=survival, 1=creative, 2=adventure, 3=spectator)。 */ gameMode: string | number; /** - * 当前维度 ID (如 "minecraft:overworld")。 - * Current dimension identifier. + * @zh 当前维度 ID (如 "minecraft:overworld")。 + * @en Current dimension identifier. */ dimension: string; - // ── 相机 / Camera ── + // ── @zh 相机 @en Camera ── /** - * 相机模式。 - * Camera mode. + * @zh 相机模式。 + * @en Camera mode. * @default "FPS" */ cameraMode: string; /** - * 相机跟随的实体 (在 FOLLOW 模式下)。 - * The entity the camera follows (when in FOLLOW mode). + * @zh 相机跟随的实体 (在 FOLLOW 模式下)。 + * @en The entity the camera follows (when in FOLLOW mode). */ cameraEntity: GameEntity | null; - /** 相机俯仰角。Camera pitch (vertical rotation). */ + /** @zh 相机俯仰角 @en Camera pitch (vertical rotation). */ cameraPitch: number; - /** 相机偏航角。Camera yaw (horizontal rotation). */ + /** @zh 相机偏航角 @en Camera yaw (horizontal rotation). */ cameraYaw: number; /** - * 玩家面朝方向 (单位向量)。 - * Direction the player is facing (unit vector). + * @zh 玩家面朝方向 (单位向量)。 + * @en Direction the player is facing (unit vector). */ readonly facingDirection: GameVector3; /** - * 玩家视线前方 5 格处的目标点。 - * A point 5 blocks ahead of the player's eyes (look‑at target). + * @zh 玩家视线前方 5 格处的目标点。 + * @en A point 5 blocks ahead of the player's eyes (look‑at target). */ readonly cameraTarget: GameVector3; - // ── 生命 / Vital stats ── + // ── @zh 生命 @en Vital stats ── - /** 饥饿值 (0‑20)。Food level (0–20). */ + /** @zh 饥饿值 (0‑20) @en Food level (0–20). */ food: number; - /** 饱和度 (0‑20)。Saturation level (0–20). */ + /** @zh 饱和度 (0‑20) @en Saturation level (0–20). */ saturation: number; - /** 当前生命值。Current health. */ + /** @zh 当前生命值 @en Current health. */ hp: number; - /** 最大生命值。Maximum health. */ + /** @zh 最大生命值 @en Maximum health. */ maxHp: number; - // ── 经验 / Experience ── + // ── @zh 经验 @en Experience ── - /** 经验等级 (与 /xp 命令相同)。Experience level (same as /xp command). */ + /** @zh 经验等级 (与 /xp 命令相同) @en Experience level (same as /xp command). */ xp: number; - /** 增加经验等级。Adds experience levels to the player. */ + /** @zh 增加经验等级 @en Adds experience levels to the player. */ addExperienceLevels(levels: number): void; - // ── 传送 / Teleport ── + // ── @zh 传送 @en Teleport ── /** - * 将玩家传送到指定坐标。 - * Teleports the player to the given coordinates. + * @zh 将玩家传送到指定坐标。 + * @en Teleports the player to the given coordinates. */ teleport(pos: GameVector3): void; - // ── 重生 / Respawn ── + // ── @zh 重生 @en Respawn ── /** - * 是否已死亡。 - * Whether the player is dead or dying. + * @zh 是否已死亡。 + * @en Whether the player is dead or dying. */ readonly dead: boolean; /** - * 重生点坐标 (可读写)。 - * Spawn point coordinates (readable & writable). + * @zh 重生点坐标 (可读写)。 + * @en Spawn point coordinates (readable & writable). */ spawnPoint: GameVector3; /** - * 设置重生点。 - * Sets the player's respawn point. + * @zh 设置重生点。 + * @en Sets the player's respawn point. */ setRespawnPoint(pos: GameVector3): void; /** - * 强制重生 (仅在死亡状态下有效)。 - * Forces a respawn (only works when dead). + * @zh 强制重生 (仅在死亡状态下有效)。 + * @en Forces a respawn (only works when dead). */ respawn(): void; - // ── 踢出 / Kick ── + // ── @zh 踢出 @en Kick ── - /** 踢出玩家 (默认理由 "Kicked")。Kicks the player with default reason. */ + /** @zh 踢出玩家 (默认理由 "Kicked") @en Kicks the player with default reason. */ kick(): void; - /** 踢出玩家 (自定义理由)。Kicks the player with a custom reason. */ + /** @zh 踢出玩家 (自定义理由) @en Kicks the player with a custom reason. */ kick(reason: string): void; - // ── 消息 / Messaging ── + // ── @zh 消息 @en Messaging ── /** - * 发送仅该玩家可见的聊天消息。 - * Sends a chat message visible only to this player. + * @zh 发送仅该玩家可见的聊天消息。 + * @en Sends a chat message visible only to this player. */ directMessage(msg: string): void; + /** @zh 发送带颜色的聊天消息。 @en Sends a colored chat message. */ + directMessage(msg: string, color: GameRGBColor): void; + /** - * 在动作栏 (快捷栏上方) 显示文字。 - * Displays text in the action bar (above the hotbar). + * @zh 在动作栏 (快捷栏上方) 显示文字。 + * @en Displays text in the action bar (above the hotbar). */ actionBar(message: string): void; /** - * 显示屏幕标题。 - * Displays a screen title. + * @zh 显示屏幕标题。 + * @en Displays a screen title. * @param title - 主标题 * @param subtitle - 副标题 * @param fadeIn - 淡入 tick (可选, 默认 10) @@ -1416,63 +1422,70 @@ interface GamePlayer { ): void; /** - * 弹出对话面板 (简化版, MC 目前仅发送文本)。 - * Shows a dialog panel — simplified; currently just sends text in MC. + * @zh 弹出对话面板 (简化版, MC 目前仅发送文本)。 + * @en Shows a dialog panel — simplified; currently just sends text in MC. * @param config.content - 对话内容 * @param config.options - 选项数组 - * @returns 用户选择结果 { index, value } + * @returns @zh 用户选择结果 { index, value } @en User selection result { index, value } */ dialog(config: { content?: string; options?: string[] }): { index: number; value: string; }; - // ── 链接 / Link ── + // ── @zh 链接 @en Link ── /** - * 向玩家发送可点击的 URL 链接。 - * Sends a clickable URL link to the player. + * @zh 向玩家发送可点击的 URL 链接。 + * @en Sends a clickable URL link to the player. */ link(href: string): void; - // ── 计分板名称 / Tab list name ── + // ── @zh 计分板名称 @en Tab list name ── /** - * 设置玩家在 TAB 列表中的显示名称 (支持颜色代码)。 - * Sets the player's display name in the tab list (supports color codes). + * @zh 设置玩家在 TAB 列表中的显示名称 (支持颜色代码)。 + * @en Sets the player's display name in the tab list (supports color codes). */ setPlayerListName(name: string): void; - // ── 朝向 / Look direction ── + // ── @zh 朝向 @en Look direction ── /** - * 让玩家看向指定坐标。 - * Makes the player look at a point in space. + * @zh 让玩家看向指定坐标。 + * @en Makes the player look at a point in space. */ lookAt(x: number, y: number, z: number): void; lookAt(pos: GameVector3): void; - // ── 执行命令 / Command ── + // ── @zh 执行命令 @en Command ── /** - * 以玩家身份执行 Minecraft 命令。 - * Executes a Minecraft command as this player. + * @zh 以玩家身份执行 Minecraft 命令。 + * @en Executes a Minecraft command as this player. */ runCommand(cmd: string): void; - // ── 物品栏 / Inventory ── + // ── @zh 物品栏 @en Inventory ── /** - * 给予玩家物品。 - * Gives an item to the player. - * @param itemId - 物品 ID (如 "minecraft:diamond") - * @param count - 数量 (1‑64) + * @zh 给予玩家物品。 + * @en Gives an item to the player. + * + * @example + * @zh ```ts + * @en player.giveItem("minecraft:diamond", 10); + * player.giveItem("minecraft:diamond_sword", 1); + * ``` + * + * @param itemId - @zh 物品 ID(如 "minecraft:diamond") @en Item ID (e.g. "minecraft:diamond") + * @param count - @zh 数量 (1–64) @en Count (1–64) */ giveItem(itemId: string, count: number): void; /** - * 给予玩家自定义物品 (基于 resourcepacks/box3js-items/items.json 配置)。 - * Gives a custom item defined in the resource pack's items.json. + * @zh 给予玩家自定义物品 (基于 resourcepacks/box3js-items/items.json 配置)。 + * @en Gives a custom item defined in the resource pack's items.json. * Items are vanilla paper with custom_model_data + name/lore/food components. * @param id - 自定义物品 ID (如 "arena_trophy") * @param count - 数量 (1‑64) @@ -1480,8 +1493,8 @@ interface GamePlayer { giveCustomItem(id: string, count: number): void; /** - * 给予玩家附魔物品。 - * Gives an enchanted item to the player. + * @zh 给予玩家附魔物品。 + * @en Gives an enchanted item to the player. * @param itemId - 物品 ID * @param count - 数量 * @param enchants - 附魔对象 (如 { "minecraft:sharpness": 5 }) @@ -1493,8 +1506,8 @@ interface GamePlayer { ): void; /** - * 给予玩家带自定义名称和描述的命名物品。 - * Gives an item with a custom name and lore. + * @zh 给予玩家带自定义名称和描述的命名物品。 + * @en Gives an item with a custom name and lore. * @param itemId - 物品 ID * @param count - 数量 * @param customName - 自定义名称 @@ -1508,23 +1521,23 @@ interface GamePlayer { ): void; /** - * 获取手持物品信息。 - * Returns info about the currently held item. + * @zh 获取手持物品信息。 + * @en Returns info about the currently held item. * @returns { id: string, count: number } */ getHeldItem(): { id: string; count: number }; - /** 清空背包。Clears the player's inventory. */ + /** @zh 清空背包 @en Clears the player's inventory. */ clearInventory(): void; - /** 管理员权限等级 (0-4)。0=普通玩家, 4=最高权限。Server operator permission level (0–4). */ + /** @zh 管理员权限等级 (0-4)。0=普通玩家, 4=最高权限 @en Server operator permission level (0–4). */ opLevel: number; - // ── 效果 / Effects ── + // ── @zh 效果 @en Effects ── /** - * 添加状态效果。 - * Applies a status effect. + * @zh 添加状态效果。 + * @en Applies a status effect. * @param effectId - 效果 ID (如 "minecraft:speed") * @param duration - 持续时间 (tick) * @param amplifier - 等级 (0 = 一级) @@ -1537,25 +1550,25 @@ interface GamePlayer { hideParticles?: boolean, ): void; - /** 清除所有状态效果。Removes all status effects. */ + /** @zh 清除所有状态效果 @en Removes all status effects. */ clearEffects(): void; - // ── 声音 / Sound ── + // ── @zh 声音 @en Sound ── /** - * 向该玩家播放声音。 - * Plays a sound for this player only. + * @zh 向该玩家播放声音。 + * @en Plays a sound for this player only. * @param path - 声音 ID (如 "minecraft:block.note_block.pling") * @param volume - 音量 (0‑1) * @param pitch - 音高 (0.5‑2) */ playSound(path: string, volume: number, pitch: number): void; - // ── 聊天 / Chat ── + // ── @zh 聊天 @en Chat ── /** - * 为该玩家注册聊天处理器 (覆盖全局 onChat)。 - * Registers a per‑player chat handler (overrides global onChat for this player). + * @zh 为该玩家注册聊天处理器 (覆盖全局 onChat)。 + * @en Registers a per‑player chat handler (overrides global onChat for this player). * @returns GameEventHandlerToken */ onChat( @@ -1566,149 +1579,161 @@ interface GamePlayer { ) => boolean | void, ): GameEventHandlerToken; - // ── 成就 / Advancements ── + // ── @zh 成就 @en Advancements ── /** - * 授予该玩家一个成就/进度。 - * Grants an advancement to this player by resource location (e.g. "minecraft:story/mine_stone"). + * @zh 授予该玩家一个成就/进度。 + * @en Grants an advancement to this player by resource location (e.g. "minecraft:story/mine_stone"). */ grantAdvancement(advancementId: string): void; /** - * 撤销该玩家的一个成就/进度。 - * Revokes an advancement from this player. + * @zh 撤销该玩家的一个成就/进度。 + * @en Revokes an advancement from this player. */ revokeAdvancement(advancementId: string): void; } -// ================================================================ -// §5 World — 世界 API -// ================================================================ +// ── §5 @zh 世界 API @en World ── /** - * 世界控制与事件 — 脚本中通过 `world` 访问。 - * World control & events — accessed via `world` in scripts. + * @zh 世界控制与事件 — 脚本中通过 `world` 访问。 + * @en World control & events — accessed via `world` in scripts. */ interface GameWorld { - // ── 世界属性 / World properties ── + // ── @zh 世界属性 @en World properties ── - /** 服务器 MOTD。Server MOTD string. */ + /** @zh 服务器 MOTD @en Server MOTD string. */ projectName(): string; - /** 服务器 MOTD (可读写, 同 projectName)。Server MOTD (read/write, alias of projectName). */ + /** @zh 服务器 MOTD (可读写, 同 projectName) @en Server MOTD (read/write, alias of projectName). */ serverId: string; - /** 当前服务端 tick 计数。Current server tick count. */ + /** @zh 当前服务端 tick 计数 @en Current server tick count. */ currentTick(): number; /** - * 降雨强度 (0‑1)。 - * Rain density (0–1). + * @zh 降雨强度 (0‑1)。 + * @en Rain density (0–1). */ rainDensity: number; /** - * 雷暴强度 (0‑1)。 - * Thunder density (0–1). + * @zh 雷暴强度 (0‑1)。 + * @en Thunder density (0–1). */ thunderDensity: number; - /** 清除天气 (晴天)。Clears weather to clear skies. */ + /** @zh 清除天气 (晴天) @en Clears weather to clear skies. */ clearWeather(): void; - // ── 时间 / Time ── + // ── @zh 时间 @en Time ── /** - * 当前游戏内时间 (tick, 0‑24000)。 - * Current in‑game time in ticks (0–24000). + * @zh 当前游戏内时间 (tick, 0‑24000)。 + * @en Current in‑game time in ticks (0–24000). */ time: number; /** - * 时间流速 (1=正常, 0=停止)。 - * Time scale (1 = normal, 0 = frozen). + * @zh 时间流速 (1=正常, 0=停止)。 + * @en Time scale (1 = normal, 0 = frozen). */ timeScale: number; /** - * 设置游戏内时间 (tick, 0‑24000)。 - * Sets the in-game time in ticks. + * @zh 设置游戏内时间 (tick, 0‑24000)。 + * @en Sets the in-game time in ticks. * @param time - 0=黎明, 6000=正午, 12000=黄昏, 18000=午夜 */ setTime(time: number): void; - // ── 难度 / Difficulty ── + // ── @zh 难度 @en Difficulty ── /** - * 当前难度。 - * Current difficulty ("peaceful" | "easy" | "normal" | "hard"). + * @zh 当前难度。 + * @en Current difficulty ("peaceful" | "easy" | "normal" | "hard"). */ difficulty: string; - // ── 出生点 / Spawn ── + // ── @zh 出生点 @en Spawn ── /** - * 世界出生点坐标。 - * World spawn point coordinates. + * @zh 世界出生点坐标。 + * @en World spawn point coordinates. */ readonly spawnPoint: GameVector3; /** - * 设置世界出生点。 - * Sets the world spawn point. + * @zh 设置世界出生点。 + * @en Sets the world spawn point. */ setWorldSpawn(pos: GameVector3): void; - // ── 游戏规则 (MC 扩展) / Game Rules (MC extension) ── + // ── @zh 游戏规则 (MC 扩展) @en Game Rules (MC extension) ── /** - * 读取游戏规则。 - * Reads a game‑rule value. - * @param name - 规则名 / rule name (see setGameRule for the list) + * @zh 读取游戏规则。 + * @en Reads a game‑rule value. + * @param name - @zh 规则名 @en rule name (see setGameRule for the list) */ getGameRule(name: string): boolean | null; /** - * 设置游戏规则。 - * Sets a game rule. + * @zh 设置游戏规则。 + * @en Sets a game rule. * @param name - supported: doDaylightCycle | doWeatherCycle | keepInventory | * doMobSpawning | doFireTick | mobGriefing | doImmediateRespawn * @param value - boolean or string "true"/"false" */ setGameRule(name: string, value: boolean | string): void; - // ── 音效属性 / Sound Properties ── + // ── @zh 音效属性 @en Sound Properties ── - /** 环境音效路径 (每 200 tick 在世界出生点自动播放, 0.3 音量)。Ambient sound — auto-plays at world spawn every 200 ticks at 0.3 volume. */ + /** @zh 环境音效路径 (每 200 tick 在世界出生点自动播放, 0.3 音量) @en Ambient sound — auto-plays at world spawn every 200 ticks at 0.3 volume. */ ambientSound: string; - /** 玩家加入音效路径 (玩家加入时自动播放)。Player join sound — auto-plays when a player joins. */ + /** @zh 玩家加入音效路径 (玩家加入时自动播放) @en Player join sound — auto-plays when a player joins. */ playerJoinSound: string; - /** 玩家离开音效路径 (玩家离开时自动播放)。Player leave sound — auto-plays when a player leaves. */ + /** @zh 玩家离开音效路径 (玩家离开时自动播放) @en Player leave sound — auto-plays when a player leaves. */ playerLeaveSound: string; - /** 方块放置音效路径 (放置方块时自动播放)。Block place sound — auto-plays when a block is placed. */ + /** @zh 方块放置音效路径 (放置方块时自动播放) @en Block place sound — auto-plays when a block is placed. */ placeVoxelSound: string; - /** 方块破坏音效路径 (破坏方块时自动播放)。Block break sound — auto-plays when a block is broken. */ + /** @zh 方块破坏音效路径 (破坏方块时自动播放) @en Block break sound — auto-plays when a block is broken. */ breakVoxelSound: string; - // ── 实体生成 / Entity Spawning ── + // ── @zh 实体生成 @en Entity Spawning ── /** - * 在指定位置生成实体。 - * Spawns an entity at the given position. + * @zh 在指定位置生成实体。 + * @en Spawns an entity at the given position. * @param type - 实体类型 ID (如 "minecraft:zombie") * @param pos - 生成坐标 - * @returns 生成的实体包装, 失败返回 null + * @returns @zh 生成的实体包装,失败返回 null @en The spawned entity wrapper, or null on failure */ spawnEntity(type: string, pos: GameVector3): GameEntity | null; /** - * 使用完整配置对象生成实体。 - * Spawns an entity with a full configuration object. - * @param config - { type, position, velocity, fixed, gravity, friction, mass, restitution, collides, meshInvisible, hp, maxHp, tags } + * @zh 使用完整配置对象生成实体。 + * @en Spawns an entity with a full configuration object. + * + * @example + * @zh ```ts + * @en // 生成一个固定在空中的发光僵尸 + * const entity = world.createEntity({ + * type: "minecraft:zombie", + * position: new GameVector3(100, 70, 100), + * fixed: true, + * hp: 40, + * maxHp: 40, + * tags: ["boss"], + * }); + * ``` + * + * @param config - @zh 实体配置对象 @en entity configuration object */ createEntity(config: { type?: string; @@ -1726,19 +1751,19 @@ interface GameWorld { tags?: string[]; }): GameEntity | null; - // ── 消息 & 声音 / Broadcasting ── + // ── @zh 消息 & 声音 @en Broadcasting ── /** - * 向全服广播消息。 - * Sends a chat message to all players. + * @zh 向全服广播消息。 + * @en Sends a chat message to all players. */ say(message: string): void; - // ── 自定义物品 / Custom Items ── + // ── @zh 自定义物品 @en Custom Items ── /** - * 从资源包加载自定义物品配置 (基于数据组件, 无需 DeferredRegister, 无注册表同步问题)。 - * Loads custom item definitions from a resource pack's items.json. + * @zh 从资源包加载自定义物品配置 (基于数据组件, 无需 DeferredRegister, 无注册表同步问题)。 + * @en Loads custom item definitions from a resource pack's items.json. * Items use minecraft:paper as base with custom_model_data for model switching. * Models & textures must be provided via the resource pack (resourcepacks//). * @@ -1751,46 +1776,46 @@ interface GameWorld { */ loadCustomItems(packName: string): void; - // ── 结构 & 成就 / Structure & Advancement ── + // ── @zh 结构 & 成就 @en Structure & Advancement ── /** - * 在指定位置放置数据包中的 .nbt 结构。 - * Places an .nbt structure from current datapacks at the given position. + * @zh 在指定位置放置数据包中的 .nbt 结构。 + * @en Places an .nbt structure from current datapacks at the given position. * Structure must exist under data//structure/.nbt */ placeStructure(x: number, y: number, z: number, structureId: string): void; placeStructure(pos: GameVector3, structureId: string): void; /** - * 为指定玩家授予成就/进度。 - * Grants a datapack advancement to a player by name. + * @zh 为指定玩家授予成就/进度。 + * @en Grants a datapack advancement to a player by name. */ grantAdvancement(playerName: string, advancementId: string): void; /** - * 按物品名搜索配方 ID 列表。 - * Searches recipe IDs matching a filter string. + * @zh 按物品名搜索配方 ID 列表。 + * @en Searches recipe IDs matching a filter string. * @param filter - 搜索关键词 (匹配配方 ID) */ listRecipes(filter: string): string[]; /** - * 移除指定 ID 的配方 (黑名单机制, 服务器重载后需重新移除)。 - * Removes a recipe by ID (blacklisted; re‑apply after server reload). + * @zh 移除指定 ID 的配方 (黑名单机制, 服务器重载后需重新移除)。 + * @en Removes a recipe by ID (blacklisted; re‑apply after server reload). * @param recipeId - 配方 ID, 例如 "minecraft:iron_pickaxe" - * @returns 是否成功加入黑名单 + * @returns @zh 是否成功加入黑名单 @en Whether the recipe was successfully blacklisted */ removeRecipe(recipeId: string): boolean; /** - * 清除所有配方黑名单, 恢复全部原始配方。 - * Clears the recipe blacklist and restores all original recipes. + * @zh 清除所有配方黑名单, 恢复全部原始配方。 + * @en Clears the recipe blacklist and restores all original recipes. */ clearRecipes(): void; /** - * 在指定位置向全服播放声音。 - * Plays a sound for all players at a location. + * @zh 在指定位置向全服播放声音。 + * @en Plays a sound for all players at a location. * @param path - 声音 ID * @param x, y, z - 声源坐标 * @param volume - 音量 (0‑1) @@ -1811,38 +1836,38 @@ interface GameWorld { pitch: number, ): void; - // ── 命令 / Command ── + // ── @zh 命令 @en Command ── /** - * 以服务端身份执行命令。 - * Executes a Minecraft command as the server. + * @zh 以服务端身份执行命令。 + * @en Executes a Minecraft command as the server. */ runCommand(cmd: string): void; - // ── 实体查询 / Entity Queries ── + // ── @zh 实体查询 @en Entity Queries ── /** - * 查询所有匹配选择器的实体 (目前仅限玩家)。 - * Selects all entities matching a selector (currently only players). + * @zh 查询所有匹配选择器的实体 (目前仅限玩家)。 + * @en Selects all entities matching a selector (currently only players). * @param selector - "*" (所有玩家) | "#uuid" | ".tag" */ querySelectorAll(selector: string): GameEntity[]; /** - * 查询第一个匹配的实体 (或 null)。 - * Selects the first matching entity, or null. + * @zh 查询第一个匹配的实体 (或 null)。 + * @en Selects the first matching entity, or null. */ querySelector(selector: string): GameEntity | null; /** - * 查询指定区域内的所有实体。 - * Returns all entities inside an AABB defined by two corners. + * @zh 查询指定区域内的所有实体。 + * @en Returns all entities inside an AABB defined by two corners. */ entitiesInArea(pos1: GameVector3, pos2: GameVector3): GameEntity[]; /** - * 查询指定半径内的所有实体。 - * Returns all entities within a radius around a point. + * @zh 查询指定半径内的所有实体。 + * @en Returns all entities within a radius around a point. */ entitiesInRadius( x: number, @@ -1852,11 +1877,11 @@ interface GameWorld { ): GameEntity[]; entitiesInRadius(pos: GameVector3, radius: number): GameEntity[]; - // ── 搜索与音效 / Search & Sound ── + // ── @zh 搜索与音效 @en Search & Sound ── /** - * 播放音效 (简写或完整配置)。 - * Plays a sound (string shorthand or full config object). + * @zh 播放音效 (简写或完整配置)。 + * @en Plays a sound (string shorthand or full config object). * @param config - 音效路径字符串 或 { path, position, volume, pitch } */ sound( @@ -1871,20 +1896,34 @@ interface GameWorld { ): void; /** - * 查询包围盒内的所有实体。 - * Returns all entities inside a GameBounds3. + * @zh 查询包围盒内的所有实体。 + * @en Returns all entities inside a GameBounds3. */ searchBox(bounds: GameBounds3): GameEntity[]; - // ── 射线检测 / Raycast ── + // ── @zh 射线检测 @en Raycast ── /** - * 从起点向指定方向发射射线, 返回碰撞结果。 - * Casts a ray and returns hit information. - * @param origin - 起点 - * @param direction - 方向向量 (自动归一化) - * @param maxDistance - 最大距离 (可选, 默认 5) - * @returns { hit, x, y, z, normalX, normalY, normalZ, distance, entity?, voxel? } + * @zh 从起点向指定方向发射射线,返回碰撞结果。 + * @en Casts a ray and returns hit information. + * + * @example + * @zh ```ts + * @en // 检测玩家视线前方 10 格内是否有方块或实体 + * const hit = world.raycast(player.eyePosition, player.facingDirection, 10); + * if (hit.hit) { + * if (hit.entity) { + * world.say(`命中实体: ${hit.entity.entityType}`); + * } else if (hit.voxel !== undefined) { + * world.say(`命中方块: ${voxels.name(hit.voxel)}`); + * } + * } + * ``` + * + * @param origin - @zh 起点 @en ray origin + * @param direction - @zh 方向向量(自动归一化) @en direction vector (auto-normalized) + * @param maxDistance - @zh 最大距离(可选,默认 5) @en max distance (optional, default 5) + * @returns @zh 碰撞结果 @en hit result */ raycast( origin: GameVector3, @@ -1892,20 +1931,20 @@ interface GameWorld { maxDistance?: number, ): RaycastResult; - // ── 生物群系 / Biome ── + // ── @zh 生物群系 @en Biome ── /** - * 获取指定位置的生物群系 ID。 - * Returns the biome identifier at the given position. + * @zh 获取指定位置的生物群系 ID。 + * @en Returns the biome identifier at the given position. */ getBiome(x: number, y: number, z: number): string; getBiome(pos: GameVector3): string; - // ── 爆炸 / Explosion ── + // ── @zh 爆炸 @en Explosion ── /** - * 在指定位置制造爆炸。 - * Creates an explosion at the given position. + * @zh 在指定位置制造爆炸。 + * @en Creates an explosion at the given position. * @param x, y, z - 爆炸中心 * @param power - 爆炸强度 * @param fire - 是否产生火焰 (可选, 默认 false) @@ -1913,18 +1952,30 @@ interface GameWorld { explode(x: number, y: number, z: number, power: number, fire?: boolean): void; explode(pos: GameVector3, power: number, fire?: boolean): void; - // ── 粒子 / Particles ── + // ── @zh 粒子 @en Particles ── /** - * 在指定位置生成粒子。 - * Spawns particles at a given location. - * @param type - 粒子 ID (如 "minecraft:flame") - * @param x, y, z - 位置 - * @param count - 数量 - * @param dx - X 扩散范围 - * @param dy - Y 扩散范围 - * @param dz - Z 扩散范围 - * @param speed - 粒子速度 + * @zh 在指定位置生成粒子。 + * @en Spawns particles at a given location. + * + * @example + * @zh ```ts + * @en // 在玩家位置生成火焰粒子 + * world.spawnParticle("minecraft:flame", player.position, 10, 0.5, 0.5, 0.5, 0); + * + * // 在指定坐标生成末影粒子 + * world.spawnParticle("minecraft:portal", 100, 64, 100, 20, 1, 1, 1, 0.1); + * ``` + * + * @param type - @zh 粒子 ID(如 "minecraft:flame") @en Particle ID (e.g. "minecraft:flame") + * @param x - @zh X 坐标 @en X coordinate + * @param y - @zh Y 坐标 @en Y coordinate + * @param z - @zh Z 坐标 @en Z coordinate + * @param count - @zh 数量 @en Count + * @param dx - @zh X 扩散范围 @en X spread + * @param dy - @zh Y 扩散范围 @en Y spread + * @param dz - @zh Z 扩散范围 @en Z spread + * @param speed - @zh 粒子速度 @en Particle speed */ spawnParticle( type: string, @@ -1937,6 +1988,7 @@ interface GameWorld { dz: number, speed: number, ): void; + /** @zh GameVector3 重载。 @en GameVector3 overload. */ spawnParticle( type: string, pos: GameVector3, @@ -1947,9 +1999,26 @@ interface GameWorld { speed: number, ): void; + /** @zh 彩色粒子 (DustParticleOptions)。 @en Colored dust particle. */ + spawnParticle( + x: number, y: number, z: number, + color: GameRGBColor, + count: number, + dx: number, dy: number, dz: number, + speed: number, + ): void; + /** @zh 彩色粒子,GameVector3 重载。 @en Colored dust particle, GameVector3 overload. */ + spawnParticle( + pos: GameVector3, + color: GameRGBColor, + count: number, + dx: number, dy: number, dz: number, + speed: number, + ): void; + /** - * 在指定圆环上生成粒子。 - * Spawns particles in a circle. + * @zh 在指定圆环上生成粒子。 + * @en Spawns particles in a circle. * @param x, y, z - 圆心 * @param radius - 半径 * @param type - 粒子 ID @@ -1970,11 +2039,11 @@ interface GameWorld { count: number, ): void; - // ── 烟花 / Fireworks ── + // ── @zh 烟花 @en Fireworks ── /** - * 在指定位置发射烟花。 - * Launches a firework rocket. + * @zh 在指定位置发射烟花。 + * @en Launches a firework rocket. * @param x, y, z - 发射位置 * @param color - 颜色名称: "red" | "blue" | "green" | "yellow" | "gold" | "white" | "aqua" | "pink" | "purple" * @param shape - 形状: "ball" | "large_ball" | "star" | "creeper" | "burst" @@ -1988,23 +2057,36 @@ interface GameWorld { ): void; launchFirework(pos: GameVector3, color: string, shape: string): void; - // ── 闪电 / Lightning ── + /** @zh 彩色烟花,GameRGBColor 数组。 @en Colored firework with GameRGBColor array. */ + launchFirework( + x: number, y: number, z: number, + colors: GameRGBColor[], + shape: string, + ): void; + /** @zh 彩色烟花,GameVector3 + GameRGBColor[] 重载。 @en Colored firework, GameVector3 overload. */ + launchFirework( + pos: GameVector3, + colors: GameRGBColor[], + shape: string, + ): void; + + // ── @zh 闪电 @en Lightning ── /** - * 在指定位置召唤闪电。 - * Summons a lightning bolt at the given position. + * @zh 在指定位置召唤闪电。 + * @en Summons a lightning bolt at the given position. * @param x, y, z - 位置 * @param damage - 伤害值 (可选, 仅对实体造成) - * @returns 是否成功 + * @returns @zh 是否成功 @en Whether the lightning was successfully summoned */ strikeLightning(x: number, y: number, z: number, damage?: number): boolean; strikeLightning(pos: GameVector3, damage?: number): boolean; - // ── 掉落物 / Drop Item ── + // ── @zh 掉落物 @en Drop Item ── /** - * 在指定位置生成掉落物。 - * Drops an item stack at the given position. + * @zh 在指定位置生成掉落物。 + * @en Drops an item stack at the given position. * @param x, y, z - 位置 * @param itemId - 物品 ID * @param count - 数量 @@ -2018,16 +2100,16 @@ interface GameWorld { ): void; dropItem(pos: GameVector3, itemId: string, count: number): void; - // ── 弹射物 / Projectile ── + // ── @zh 弹射物 @en Projectile ── /** - * 从起点向目标发射弹射物。 - * Launches a projectile from origin toward a target. + * @zh 从起点向目标发射弹射物。 + * @en Launches a projectile from origin toward a target. * @param type - 弹射物类型 (如 "minecraft:arrow") * @param x, y, z - 发射位置 * @param tx, ty, tz - 目标位置 * @param speed - 速度 - * @returns 弹射物实体, 失败返回 null + * @returns @zh 弹射物实体,失败返回 null @en The projectile entity, or null on failure */ launchProjectile( type: string, @@ -2046,26 +2128,26 @@ interface GameWorld { speed: number, ): GameEntity | null; - // ── 计分板 / Scoreboard ── + // ── @zh 计分板 @en Scoreboard ── /** - * 添加计分板目标 (默认 dummy 标准)。 - * Adds a scoreboard objective (default dummy criteria). + * @zh 添加计分板目标 (默认 dummy 标准)。 + * @en Adds a scoreboard objective (default dummy criteria). */ addScoreboard(name: string): void; /** - * 添加计分板目标 (自定义标准)。 - * Adds a scoreboard objective with a custom criteria. + * @zh 添加计分板目标 (自定义标准)。 + * @en Adds a scoreboard objective with a custom criteria. */ addScoreboard(name: string, criteria: string): void; - /** 移除计分板目标。Removes a scoreboard objective. */ + /** @zh 移除计分板目标 @en Removes a scoreboard objective. */ removeScoreboard(name: string): void; /** - * 设置实体/名称的分数。 - * Sets the score of an entity or name for a given objective. + * @zh 设置实体/名称的分数。 + * @en Sets the score of an entity or name for a given objective. */ setScore( entityOrName: string | GameEntity, @@ -2074,36 +2156,36 @@ interface GameWorld { ): void; /** - * 获取分数。 - * Gets the score of an entity or name for a given objective. + * @zh 获取分数。 + * @en Gets the score of an entity or name for a given objective. */ getScore(entityOrName: string | GameEntity, objectiveName: string): number; /** - * 在指定显示位置展示计分板。 - * Displays a scoreboard objective in a display slot. + * @zh 在指定显示位置展示计分板。 + * @en Displays a scoreboard objective in a display slot. * @param slot - "sidebar" | "list" | "belowname" */ showScoreboard(slot: string, objectiveName: string): void; /** - * 从显示位置隐藏计分板。 - * Hides a scoreboard from a display slot. + * @zh 从显示位置隐藏计分板。 + * @en Hides a scoreboard from a display slot. */ hideScoreboard(slot: string): void; /** - * 列出计分板上所有玩家的分数。 - * Lists all player scores for a given objective. + * @zh 列出计分板上所有玩家的分数。 + * @en Lists all player scores for a given objective. * @returns Array<{ name: string, value: number }> */ listScores(objectiveName: string): Array<{ name: string; value: number }>; - // ── Boss 血条 / Boss Bar ── + // ── @zh Boss 血条 @en Boss Bar ── /** - * 显示或更新 Boss 血条。 - * Shows or updates a boss bar. + * @zh 显示或更新 Boss 血条。 + * @en Shows or updates a boss bar. * @param name - 血条 ID * @param text - 显示文字 * @param progress - 进度 (0‑1) @@ -2116,145 +2198,142 @@ interface GameWorld { color: string, ): void; - /** 移除 Boss 血条。Removes a boss bar by ID. */ + /** @zh 移除 Boss 血条 @en Removes a boss bar by ID. */ removeBossbar(name: string): void; - // ── 队伍 / Teams ── + // ── @zh 队伍 @en Teams ── /** - * 创建一个队伍。 - * Creates a scoreboard team. + * @zh 创建一个队伍。 + * @en Creates a scoreboard team. * @param name - 队伍名 * @param color - 颜色 (如 "aqua", "red", "blue" 等) */ createTeam(name: string, color: string): void; - /** 删除队伍。Removes a team. */ + /** @zh 删除队伍 @en Removes a team. */ removeTeam(name: string): void; /** - * 将实体/名称加入队伍。 - * Adds an entity or name to a team. + * @zh 将实体/名称加入队伍。 + * @en Adds an entity or name to a team. */ joinTeam(entityOrName: string | GameEntity, teamName: string): void; /** - * 将实体/名称移出队伍。 - * Removes an entity or name from its current team. + * @zh 将实体/名称移出队伍。 + * @en Removes an entity or name from its current team. */ leaveTeam(entityOrName: string | GameEntity): void; /** - * 获取实体/名称所在的队伍名 (不在任何队伍返回 null)。 - * Returns the team name of an entity or name, or null. + * @zh 获取实体/名称所在的队伍名 (不在任何队伍返回 null)。 + * @en Returns the team name of an entity or name, or null. */ getTeamOf(entityOrName: string | GameEntity): string | null; - // ── 世界边界 / World Border ── + // ── @zh 世界边界 @en World Border ── - /** 当前边界大小。Current world border size. */ + /** @zh 当前边界大小 @en Current world border size. */ borderSize: number; /** - * 设置边界中心。 - * Sets the world border center. + * @zh 设置边界中心。 + * @en Sets the world border center. */ setBorderCenter(x: number, z: number): void; /** - * 缩放边界到目标大小 (带动画)。 - * Shrinks/grows the world border to a target size over time. + * @zh 缩放边界到目标大小 (带动画)。 + * @en Shrinks/grows the world border to a target size over time. * @param targetSize - 目标大小 * @param seconds - 动画秒数 */ shrinkBorder(targetSize: number, seconds: number): void; /** - * 边界伤害 (每秒造成的伤害值)。 - * World border damage per block per second. + * @zh 边界伤害 (每秒造成的伤害值)。 + * @en World border damage per block per second. */ setBorderDamage(damage: number): void; /** - * 边界警告距离 (方块数)。 - * World border warning distance in blocks. + * @zh 边界警告距离 (方块数)。 + * @en World border warning distance in blocks. */ setBorderWarning(blocks: number): void; - // ── 定时器 / Timers ── + // ── @zh 定时器 @en Timers ── /** - * 设置一次性延时回调。 - * Schedules a one‑shot delayed callback. + * @zh 设置一次性延时回调。 + * @en Schedules a one‑shot delayed callback. * @param handler - 回调函数 * @param ticks - 延迟 tick 数 - * @returns 定时器 ID (可用于 clearTimeout) + * @returns @zh 定时器 ID(可用于 clearTimeout) @en Timer ID (can be used with clearTimeout) */ setTimeout(handler: () => void, ticks: number): number; /** - * 设置循环定时回调。 - * Schedules a recurring interval callback. + * @zh 设置循环定时回调。 + * @en Schedules a recurring interval callback. * @param handler - 回调函数 * @param ticks - 间隔 tick 数 - * @returns 定时器 ID (可用于 clearInterval) + * @returns @zh 定时器 ID(可用于 clearInterval) @en Timer ID (can be used with clearInterval) */ setInterval(handler: () => void, ticks: number): number; - /** 取消 setTimeout。Clears a timeout by ID. */ + /** @zh 取消 setTimeout @en Clears a timeout by ID. */ clearTimeout(id: number): void; - /** 取消 setInterval。Clears an interval by ID. */ + /** @zh 取消 setInterval @en Clears an interval by ID. */ clearInterval(id: number): void; - // ── 项目间消息 / Cross‑project Messaging ── + // ── @zh 项目间消息 @en Cross‑project Messaging ── /** - * 向另一个项目发送消息。 - * Sends a message to another script project. + * @zh 向另一个项目发送消息。 + * @en Sends a message to another script project. * @param target - 目标项目名 (不含路径) * @param data - 数据 (任意 JSON 可序列化的值) */ sendMessage(target: string, data: unknown): void; - // ═══════════════════════════════════════════════════ - // 事件注册 / Event Registration - // 所有 onXxx() 返回 GameEventHandlerToken, 调用 .cancel() 取消监听。 - // All onXxx() return GameEventHandlerToken; call .cancel() to unregister. - // ═══════════════════════════════════════════════════ + // ── @zh 事件注册 @en Event Registration ── + // @zh 所有 onXxx() 返回 GameEventHandlerToken, 调用 .cancel() 取消监听。 @en All onXxx() return GameEventHandlerToken; call .cancel() to unregister. /** - * 注册每 tick 回调 (每秒 20 次)。 - * Registers a callback invoked every tick (20 times/sec). - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册每 tick 回调 (每秒 20 次)。 + * @en Registers a callback invoked every tick (20 times/sec). + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onTick(handler: (info: TickInfo) => void): GameEventHandlerToken; /** - * 注册玩家加入回调。 - * Registers a callback invoked when a player joins the server. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册玩家加入回调。 + * @en Registers a callback invoked when a player joins the server. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onPlayerJoin( handler: (entity: GamePlayerEntity, tick: number) => void, ): GameEventHandlerToken; /** - * 注册玩家离开回调。 - * Registers a callback invoked when a player leaves the server. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册玩家离开回调。 + * @en Registers a callback invoked when a player leaves the server. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onPlayerLeave( handler: (entity: GamePlayerEntity, tick: number) => void, ): GameEventHandlerToken; /** - * 注册聊天消息回调 (包括 /me 消息)。 - * Registers a callback for chat messages (including /me). + * @zh 注册聊天消息回调 (包括 /me 消息)。 + * @en Registers a callback for chat messages (including /me). * @param handler - (entity, message, tick) => boolean|void * 返回 false 可取消聊天消息发送。 * Return false to cancel sending this chat message. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onChat( handler: ( @@ -2265,18 +2344,18 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册玩家重生回调。 - * Registers a callback invoked when a player respawns. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册玩家重生回调。 + * @en Registers a callback invoked when a player respawns. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onPlayerRespawn( handler: (entity: GamePlayerEntity, tick: number) => void, ): GameEventHandlerToken; /** - * 注册方块右键激活回调。 - * Registers a callback invoked when a player right‑clicks a block. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册方块右键激活回调。 + * @en Registers a callback invoked when a player right‑clicks a block. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onBlockActivate( handler: ( @@ -2290,9 +2369,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册方块破坏回调。 - * Registers a callback invoked when a player breaks a block. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册方块破坏回调。 + * @en Registers a callback invoked when a player breaks a block. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onVoxelDestroy( handler: ( @@ -2306,9 +2385,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册方块放置回调。 - * Registers a callback invoked when a player places a block. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册方块放置回调。 + * @en Registers a callback invoked when a player places a block. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onBlockPlace( handler: ( @@ -2323,9 +2402,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册方块接触回调 (玩家移动到新方块时触发)。 - * Registers a callback invoked when a player's block position changes. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册方块接触回调 (玩家移动到新方块时触发)。 + * @en Registers a callback invoked when a player's block position changes. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onVoxelContact( handler: ( @@ -2341,9 +2420,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册实体交互回调 (玩家右键实体)。 - * Registers a callback invoked when a player right‑clicks an entity. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册实体交互回调 (玩家右键实体)。 + * @en Registers a callback invoked when a player right‑clicks an entity. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onInteract( handler: ( @@ -2354,9 +2433,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册实体死亡回调。 - * Registers a callback invoked when an entity dies. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册实体死亡回调。 + * @en Registers a callback invoked when an entity dies. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onEntityDeath( handler: ( @@ -2367,9 +2446,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册实体受伤回调。 - * Registers a callback invoked when an entity takes damage. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册实体受伤回调。 + * @en Registers a callback invoked when an entity takes damage. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onEntityDamage( handler: ( @@ -2382,9 +2461,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册流体进入回调 (玩家进入水/熔岩)。 - * Registers a callback invoked when a player enters a fluid. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册流体进入回调 (玩家进入水/熔岩)。 + * @en Registers a callback invoked when a player enters a fluid. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onFluidEnter( handler: ( @@ -2398,9 +2477,9 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册流体离开回调 (玩家离开水/熔岩)。 - * Registers a callback invoked when a player leaves a fluid. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册流体离开回调 (玩家离开水/熔岩)。 + * @en Registers a callback invoked when a player leaves a fluid. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onFluidLeave( handler: ( @@ -2414,40 +2493,40 @@ interface GameWorld { ): GameEventHandlerToken; /** - * 注册实体接触回调 (两个实体碰撞)。 - * Registers a callback invoked when two entities come into contact. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册实体接触回调 (两个实体碰撞)。 + * @en Registers a callback invoked when two entities come into contact. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onEntityContact( handler: (entityA: GameEntity, entityB: GameEntity, tick: number) => void, ): GameEventHandlerToken; /** - * 注册实体分离回调 (两个实体不再碰撞)。 - * Registers a callback invoked when two entities separate after contact. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册实体分离回调 (两个实体不再碰撞)。 + * @en Registers a callback invoked when two entities separate after contact. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onEntitySeparate( handler: (entityA: GameEntity, entityB: GameEntity, tick: number) => void, ): GameEventHandlerToken; /** - * 注册按钮按下回调 — 当玩家按下指定按钮时触发。 - * Registers a callback for button presses from any player. + * @zh 注册按钮按下回调 — 当玩家按下指定按钮时触发。 + * @en Registers a callback for button presses from any player. * @param handler — `(entity, button, tick) => void` * * `button` 参数值是 {@link GameButtonType} 中的字符串常量之一: * WALK / RUN / CROUCH / JUMP / FLY / ACTION0 / ACTION1 - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onButtonPressed( handler: (entity: GamePlayerEntity, button: string, tick: number) => void, ): GameEventHandlerToken; /** - * 注册跨项目消息回调。 - * Registers a callback for messages from other script projects. - * @returns GameEventHandlerToken — 调用 .cancel() 取消 + * @zh 注册跨项目消息回调。 + * @en Registers a callback for messages from other script projects. + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe */ onMessage( handler: (sender: string, data: unknown) => void, @@ -2455,123 +2534,118 @@ interface GameWorld { } /** - * raycast() 返回结果。 - * Return type of world.raycast(). + * @zh `world.raycast()` 返回结果。 + * @en Return type of `world.raycast()`. */ interface RaycastResult { - /** 是否命中。True if something was hit. */ + /** @zh 是否命中 @en True if something was hit. */ hit: boolean; - /** 命中点 X 坐标。Hit point X coordinate. */ + /** @zh 命中点 X 坐标 @en Hit point X coordinate. */ x: number; - /** 命中点 Y 坐标。Hit point Y coordinate. */ + /** @zh 命中点 Y 坐标 @en Hit point Y coordinate. */ y: number; - /** 命中点 Z 坐标。Hit point Z coordinate. */ + /** @zh 命中点 Z 坐标 @en Hit point Z coordinate. */ z: number; - /** 表面法线 X 分量。Surface normal X component. */ + /** @zh 表面法线 X 分量 @en Surface normal X component. */ normalX: number; - /** 表面法线 Y 分量。Surface normal Y component. */ + /** @zh 表面法线 Y 分量 @en Surface normal Y component. */ normalY: number; - /** 表面法线 Z 分量。Surface normal Z component. */ + /** @zh 表面法线 Z 分量 @en Surface normal Z component. */ normalZ: number; - /** 命中距离。Distance from origin to hit point. */ + /** @zh 命中距离 @en Distance from origin to hit point. */ distance: number; - /** 命中的方块 ID (命中方块时为数字)。Hit block ID (number when a block was hit). */ + /** @zh 命中的方块 ID (命中方块时为数字) @en Hit block ID (number when a block was hit). */ voxel?: number; - /** 命中的实体 (命中实体时)。The entity that was hit (when an entity was hit). */ + /** @zh 命中的实体 (命中实体时) @en The entity that was hit (when an entity was hit). */ entity?: GameEntity; } -// ================================================================ -// §6 Voxels — 方块操作 -// ================================================================ +// ── §6 @zh 方块操作 @en Voxels ── /** - * 方块读写操作 — 脚本中通过 `voxels` 访问。 - * Voxel (block) read/write — accessed via `voxels` in scripts. - * - * @remarks - * 所有坐标使用世界方块坐标 (整数)。 + * @zh 方块读写操作 — 脚本中通过 `voxels` 访问。所有坐标使用世界方块坐标(整数)。 + * @en Voxel (block) read/write — accessed via `voxels` in scripts. * All coordinates are in world block space (integers). */ interface GameVoxels { - // ── 世界尺寸 / World dimensions ── + // ── @zh 世界尺寸 @en World dimensions ── /** - * 世界最大尺寸 (x, y, z 均为世界高度)。 - * Maximum world dimensions (x/y/z all equal world height). + * @zh 世界最大尺寸 (x, y, z 均为世界高度)。 + * @en Maximum world dimensions (x/y/z all equal world height). */ readonly shape: GameVector3; /** - * 所有可用的方块类型名称数组。 - * Array of all registered block type resource‑location strings. + * @zh 所有可用的方块类型名称数组。 + * @en Array of all registered block type resource‑location strings. */ readonly VoxelTypes: string[]; - // ── 名称 ↔ ID 映射 / Name–ID mapping ── + // ── @zh 名称 ↔ ID 映射 @en Name–ID mapping ── /** - * 将方块名称转为数字 ID。 - * Resolves a block name (e.g. "stone" or "minecraft:stone") to its numeric ID. - * @returns 数字 ID, 未知方块的返回 0 (air) + * @zh 将方块名称转为数字 ID。 + * @en Resolves a block name (e.g. "stone" or "minecraft:stone") to its numeric ID. + * @returns @zh 数字 ID,未知方块的返回 0(air) @en Numeric ID, 0 for unknown blocks (air) */ id(name: string): number; /** - * 将数字 ID 转为方块名称。 - * Resolves a numeric ID back to a block name string. - * @returns ResourceLocation 字符串, 未知 ID 返回 "air" + * @zh 将数字 ID 转为方块名称。 + * @en Resolves a numeric ID back to a block name string. + * @returns @zh ResourceLocation 字符串,未知 ID 返回 "air" @en ResourceLocation string, "air" for unknown IDs */ name(id: number): string; - // ── 读取 / Read ── + // ── @zh 读取 @en Read ── /** - * 获取方块数字 ID (不含旋转信息的基础 ID)。 - * Returns the base numeric block ID at the given position (without rotation encoding). - * @returns 基础方块 ID, 空气返回 0 / base block ID, 0 for air + * @zh 获取方块数字 ID (不含旋转信息的基础 ID)。 + * @en Returns the base numeric block ID at the given position (without rotation encoding). + * @returns @zh 基础方块 ID,空气返回 0 @en base block ID, 0 for air */ getVoxel(x: number, y: number, z: number): number; getVoxel(pos: GameVector3): number; /** - * 获取方块数字 ID (不含旋转信息的基础 ID)。 - * Returns the base numeric block ID (without rotation encoding). + * @zh 获取方块数字 ID (不含旋转信息的基础 ID)。 + * @en Returns the base numeric block ID (without rotation encoding). */ getVoxelId(x: number, y: number, z: number): number; getVoxelId(pos: GameVector3): number; /** - * 获取方块名称 (如 "minecraft:stone")。 - * Returns the block name at the given position (e.g. "minecraft:stone"). + * @zh 获取方块名称 (如 "minecraft:stone")。 + * @en Returns the block name at the given position (e.g. "minecraft:stone"). */ getVoxelName(x: number, y: number, z: number): string; getVoxelName(pos: GameVector3): string; /** - * 获取方块旋转值 (0‑3, 对应南/西/北/东)。 - * Returns the block rotation: 0=South, 1=West, 2=North, 3=East. + * @zh 获取方块旋转值 (0‑3, 对应南/西/北/东)。 + * @en Returns the block rotation: 0=South, 1=West, 2=North, 3=East. */ getVoxelRotation(x: number, y: number, z: number): number; getVoxelRotation(pos: GameVector3): number; - // ── 写入 / Write ── + // ── @zh 写入 @en Write ── /** - * 放置方块 (名称或 ID)。返回含旋转编码的完整 ID。 - * Places a block by name or ID. Returns the full encoded ID (baseId + rotation * 16384). + * @zh 放置方块 (名称或 ID)。返回含旋转编码的完整 ID。 + * @en Places a block by name or ID. Returns the full encoded ID (baseId + rotation * 16384). * @param voxel - 方块名称 (如 "minecraft:diamond_block") 或数字 ID - * @returns 含旋转编码的完整方块 ID, 删除/空气返回 0 + * @returns @zh 含旋转编码的完整方块 ID,删除/空气返回 0 @en Full encoded block ID (base + rotation * 16384), 0 for remove/air */ setVoxel(x: number, y: number, z: number, voxel: string | number): number; setVoxel(pos: GameVector3, voxel: string | number): number; /** - * 放置方块并指定旋转。返回含旋转编码的完整 ID。 - * Places a block with explicit rotation. + * @zh 放置方块并指定旋转。返回含旋转编码的完整 ID。 + * @en Places a block with explicit rotation. * @param voxel - 方块名称或数字 ID * @param rotation - 旋转值 0‑3 (或字符串 "0"‑"3") - * @returns 含旋转编码的完整 ID + * @returns @zh 含旋转编码的完整 ID @en Full encoded block ID (base + rotation * 16384) */ setVoxel( x: number, @@ -2587,18 +2661,18 @@ interface GameVoxels { ): number; /** - * 放置已含旋转编码的完整 ID 方块。 - * Places a block using a rotation‑encoded full ID (from getVoxelId). + * @zh 放置已含旋转编码的完整 ID 方块。 + * @en Places a block using a rotation‑encoded full ID (from getVoxelId). * @param voxel - 完整编码 ID (baseId + rotation * 16384) */ setVoxelId(x: number, y: number, z: number, voxel: number): number; setVoxelId(pos: GameVector3, voxel: number): number; - // ── 区域操作 / Region operations ── + // ── @zh 区域操作 @en Region operations ── /** - * 在两个对角顶点定义的区域内填充方块。 - * Fills a cuboid region with a block. + * @zh 在两个对角顶点定义的区域内填充方块。 + * @en Fills a cuboid region with a block. * @param x1, y1, z1 - 顶点 1 * @param x2, y2, z2 - 顶点 2 * @param voxel - 方块名称或 ID @@ -2615,8 +2689,8 @@ interface GameVoxels { fillVoxel(pos1: GameVector3, pos2: GameVector3, voxel: string | number): void; /** - * 统计区域内指定方块的数量。 - * Counts matching blocks within a cuboid region. + * @zh 统计区域内指定方块的数量。 + * @en Counts matching blocks within a cuboid region. */ countVoxel( x1: number, @@ -2633,68 +2707,63 @@ interface GameVoxels { voxel: string | number, ): number; - // ── 刷怪笼 / Spawner ── + // ── @zh 刷怪笼 @en Spawner ── /** - * 设置刷怪笼的生成实体类型。 - * Sets the spawner entity type at the given position. - * @param x, y, z - 刷怪笼坐标 / spawner coordinates + * @zh 设置刷怪笼的生成实体类型。 + * @en Sets the spawner entity type at the given position. + * @param x, y, z - @zh 刷怪笼坐标 @en spawner coordinates * @param entityType - 实体类型 ID (如 "minecraft:zombie") */ setSpawner(x: number, y: number, z: number, entityType: string): void; setSpawner(pos: GameVector3, entityType: string): void; } -// ================================================================ -// §7 Console — 控制台 -// ================================================================ +// ── §7 @zh 控制台 @en Console ── /** - * 服务端控制台输出 — 脚本中通过 `console` 访问。 - * Server console output — accessed via `console` in scripts. + * @zh 服务端控制台输出 — 脚本中通过 `console` 访问。 + * 输出格式:`[Box3JS] [projectName] `,通过 System.out / System.err 输出到服务端控制台。 * - * @remarks - * 输出格式: [Box3JS] [projectName] - * 会通过 System.out / System.err 输出到服务端控制台。 + * @en Server console output — accessed via `console` in scripts. + * Output format: `[Box3JS] [projectName] `, written via System.out / System.err. */ interface GameConsole { - /** 普通日志。Info‑level log. */ + /** @zh 普通日志 @en Info‑level log. */ log(...args: unknown[]): void; - /** 调试日志 (前缀 [DEBUG])。Debug‑level log (prefixed with [DEBUG]). */ + /** @zh 调试日志 (前缀 [DEBUG]) @en Debug‑level log (prefixed with [DEBUG]). */ debug(...args: unknown[]): void; - /** 警告日志 (前缀 [WARN])。Warning‑level log (prefixed with [WARN]). */ + /** @zh 警告日志 (前缀 [WARN]) @en Warning‑level log (prefixed with [WARN]). */ warn(...args: unknown[]): void; /** - * 错误日志 (输出到 stderr, 前缀 [ERROR])。 - * Error‑level log (written to stderr, prefixed with [ERROR]). + * @zh 错误日志 (输出到 stderr, 前缀 [ERROR])。 + * @en Error‑level log (written to stderr, prefixed with [ERROR]). */ error(...args: unknown[]): void; /** - * 清除控制台 (发送 ANSI 清屏序列)。 - * Clears the console output (sends ANSI clear‑screen sequence). + * @zh 清除控制台 (发送 ANSI 清屏序列)。 + * @en Clears the console output (sends ANSI clear‑screen sequence). */ clear(): void; /** - * 断言: 条件为 false 时输出错误。 - * Asserts a condition; logs an error message if the condition is false. - * @param condition - 要测试的条件 / the condition to test - * @param args - 失败时输出的额外参数 / additional values to log on failure + * @zh 断言: 条件为 false 时输出错误。 + * @en Asserts a condition; logs an error message if the condition is false. + * @param condition - @zh 要测试的条件 @en the condition to test + * @param args - @zh 失败时输出的额外参数 @en additional values to log on failure */ assert(condition: boolean, ...args: unknown[]): void; } -// ================================================================ -// §8 Enum Constants — 运行时枚举常量 -// ================================================================ +// ── §8 @zh 运行时枚举常量 @en Runtime Enum Constants ── /** - * 按钮类型 — 用于 world.onButtonPressed() 的 button 参数。 - * Button type constants for the button parameter of world.onButtonPressed(). + * @zh 按钮类型常量 — 用于 `world.onButtonPressed()` 的 `button` 参数。 + * @en Button type constants for the `button` parameter of `world.onButtonPressed()`. */ declare const GameButtonType: { readonly WALK: "WALK"; @@ -2707,8 +2776,8 @@ declare const GameButtonType: { }; /** - * 相机模式 — player.cameraMode 的取值。 - * Camera mode constants for the player.cameraMode property. + * @zh 相机模式常量 — `player.cameraMode` 的取值。 + * @en Camera mode constants for the `player.cameraMode` property. */ declare const GameCameraMode: { readonly FOLLOW: "FOLLOW"; @@ -2716,8 +2785,8 @@ declare const GameCameraMode: { }; /** - * 玩家移动状态 — player.moveState 的可能返回值。 - * Player movement state constants — possible return values of player.moveState. + * @zh 玩家移动状态常量 — `player.moveState` 的可能返回值。 + * @en Player movement state constants — possible return values of `player.moveState`. */ declare const GamePlayerMoveState: { readonly FLYING: "FLYING"; @@ -2728,8 +2797,8 @@ declare const GamePlayerMoveState: { }; /** - * 玩家行走状态 — player.walkState 的可能返回值。 - * Player walk state constants — possible return values of player.walkState. + * @zh 玩家行走状态常量 — `player.walkState` 的可能返回值。 + * @en Player walk state constants — possible return values of `player.walkState`. */ declare const GamePlayerWalkState: { readonly NONE: "NONE"; @@ -2738,31 +2807,31 @@ declare const GamePlayerWalkState: { readonly RUN: "RUN"; }; -// ================================================================ -// §9 Global Declarations — 全局声明 -// ================================================================ +// ── §9 @zh 全局声明 @en Global Declarations ── -/** 世界控制与事件 API / World control & events */ +/** @zh 世界控制与事件 API @en World control & events */ declare const world: GameWorld; -/** 方块读写 API / Block read & write */ +/** @zh 方块读写 API @en Block read & write */ declare const voxels: GameVoxels; -/** 持久化存储 API / Persistent key‑value storage */ +/** @zh 持久化存储 API @en Persistent key‑value storage */ declare const storage: GameStorage; -/** SQLite 数据库 API / SQLite database */ -declare const db: GameDatabase; - -/** 服务端控制台输出 / Server console output */ -declare const console: GameConsole; - /** - * 阻塞当前执行线程 (毫秒级)。 - * Blocks the current execution thread for the specified duration. + * @zh SQLite 数据库 API。 + * + * **前置条件:** 需要安装 `minecraft-sqlite-jdbc` 模组来提供 JDBC 驱动。 + * 未安装时,调用 `db.sql()` 会抛出带明确提示的错误,不影响其它 API(`world`、`storage`、`voxels` 等)。 + * + * @en SQLite database API. * - * @warning 会导致服务端卡顿, 谨慎使用。当前实现会将 sleep 上限钳制到 10ms。 - * Will lag the server — use sparingly. Current runtime clamps sleep to at most 10ms. - * @param ms - 阻塞毫秒数 / sleep duration in milliseconds + * **Prerequisite:** Install the `minecraft-sqlite-jdbc` mod to provide the JDBC driver. + * If missing, `db.sql()` throws a clear error without affecting other APIs. + * + * @see https://modrinth.com/plugin/minecraft-sqlite-jdbc */ -declare function sleep(ms: number): void; +declare const db: GameDatabase; + +/** @zh 服务端控制台输出 @en Server console output */ +declare const console: GameConsole;