From 5b028984610f94075d56fefb77b9b59e840127be Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Fri, 22 May 2026 14:44:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(api):=20=E6=96=B0=E5=A2=9E=20API=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=B9=B6=E6=9B=B4=E6=96=B0=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为客户端 API 添加相机控制方法,包括 setCameraTarget 和 resetCamera,用于旁观实体及返回玩家视角。 通过 client.getBlockState 增强方块查询能力,可从渲染缓存中获取方块状态。 通过 client.onRender 引入渲染回调,支持带部分帧插值的每帧自定义渲染。 扩展实体 API,添加支持伤害来源的 hurt 方法、基于位置的 teleportTo 传送方法、带附魔装备的 setEquipmentWithEnchants 方法,以及不触发回调直接移除实体的 remove 方法。 添加鼠标增量输入方法 getMouseDeltaX 和 getMouseDeltaY,用于跟踪鼠标相对移动。 扩展 GameBounds3 数学运算,新增 volume、isEmpty、equals、union、inflate 和 deflate 方法。 增强玩家背包管理,新增 removeItem、setHeldSlot、inventoryFreeSlots、hasItem、getItemCount 和 givePotion 方法。 实现按玩家独立的 Bossbar 系统,包含 showBossbar 和 removeBossbar 方法。 添加 UI 绘制能力,包含 drawItem、removeDrawItem、drawRect 和 removeDrawRect 方法,用于自定义叠加层显示。 更新世界事件回调,支持通过返回 false 取消操作,阻止方块破坏/放置、实体交互和伤害事件等行为。 为体素 API 添加缺失的重载方法,包括支持 GameBounds3 参数的 fillVoxel、replace、clone、setVoxelState 和 countVoxel 方法。 更新中文文档,同步所有新增 API 及变更内容。 --- Box3JS-NeoForge-1.21.1/docs/api/client.md | 46 + Box3JS-NeoForge-1.21.1/docs/api/entity.md | 39 + Box3JS-NeoForge-1.21.1/docs/api/input.md | 16 + Box3JS-NeoForge-1.21.1/docs/api/math.md | 6 + Box3JS-NeoForge-1.21.1/docs/api/player.md | 83 ++ Box3JS-NeoForge-1.21.1/docs/api/ui.md | 56 + Box3JS-NeoForge-1.21.1/docs/api/voxels.md | 79 ++ Box3JS-NeoForge-1.21.1/docs/api/world.md | 14 +- Box3JS-NeoForge-1.21.1/docs/en/api/client.md | 46 + Box3JS-NeoForge-1.21.1/docs/en/api/entity.md | 39 + Box3JS-NeoForge-1.21.1/docs/en/api/input.md | 16 + Box3JS-NeoForge-1.21.1/docs/en/api/math.md | 6 + Box3JS-NeoForge-1.21.1/docs/en/api/player.md | 82 ++ Box3JS-NeoForge-1.21.1/docs/en/api/ui.md | 56 + Box3JS-NeoForge-1.21.1/docs/en/api/voxels.md | 79 ++ Box3JS-NeoForge-1.21.1/docs/en/api/world.md | 4 + .../main/java/com/box3lab/box3js/Box3JS.java | 22 +- .../box3js/client/Box3JSClientEngine.java | 239 ++++- .../box3js/script/Box3JSCallbacks.java | 10 +- .../box3lab/box3js/script/Box3JSEntity.java | 53 + .../box3lab/box3js/script/Box3JSPlayer.java | 115 ++ .../box3lab/box3js/script/Box3JSVoxels.java | 112 ++ .../box3lab/box3js/script/Box3JSWorld.java | 1 + .../box3js/script/Box3ScriptEngine.java | 111 +- .../box3lab/box3js/script/GameBounds3.java | 42 + .../box3js/template/types/client/client.d.ts | 34 + .../box3js/template/types/client/input.d.ts | 14 + .../box3js/template/types/client/ui.d.ts | 56 + .../box3js/template/types/server/entity.d.ts | 36 + .../box3js/template/types/server/player.d.ts | 71 ++ .../box3js/template/types/server/voxels.d.ts | 76 ++ .../box3js/template/types/server/world.d.ts | 996 +++++------------- .../assets/box3js/template/types/shared.d.ts | 18 + .../tools/box3js-api-manifest.json | 2 +- 34 files changed, 1883 insertions(+), 792 deletions(-) diff --git a/Box3JS-NeoForge-1.21.1/docs/api/client.md b/Box3JS-NeoForge-1.21.1/docs/api/client.md index 7287168..30e2e38 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/client.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/client.md @@ -157,6 +157,52 @@ client.setFogEndDistance(50); client.resetFog(); ``` +## 相机控制 (Camera Control) + +### client.setCameraTarget(entityUuid) + +将相机切换到目标实体的视角(旁观模式效果)。 + +```js +// 切换到指定实体的视角 +client.setCameraTarget("550e8400-e29b-41d4-a716-446655440000"); +``` + +### client.resetCamera() + +重置相机到本地玩家视角。 + +```js +client.resetCamera(); +``` + +## 方块查询 (Block Query) + +### client.getBlockState(x, y, z) + +在客户端查询指定坐标的方块状态(从渲染区块缓存读取)。空气或未加载区块返回 `null`。 + +```js +var block = client.getBlockState(0, 100, 0); +if (block) { + console.log("方块 ID: " + block.id); +} +``` + +## 渲染回调 (Render Callback) + +### client.onRender(callback) + +注册每 Render 帧回调(仅在 HUD 渲染阶段触发)。回调接收 `partialTick`(0–1,帧间插值因子)。 + +返回 `GameEventHandlerToken`,调用 `.cancel()` 取消。 + +```js +var token = client.onRender(function (partialTick) { + // 使用 partialTick 进行平滑自定义渲染 +}); +``` + ## 客户端完整示例 ```js diff --git a/Box3JS-NeoForge-1.21.1/docs/api/entity.md b/Box3JS-NeoForge-1.21.1/docs/api/entity.md index 9e43314..dad2049 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/entity.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/entity.md @@ -95,6 +95,10 @@ zombie.hp = 100; 对实体造成 `amount` 点伤害(通用伤害类型,触发伤害事件)。 +### entity.hurt(amount, source) + +对实体造成伤害并指定伤害来源实体(用于击杀追踪)。`source` 为另一个 `GameEntity`。 + ### entity.heal(amount) 治疗实体 `amount` 点生命值(不超过 maxHp)。 @@ -325,6 +329,19 @@ entity.navigateTo(10, 100, 10, 1.0); entity.navigateTo(target.position, 1.0); ``` +### entity.teleportTo(x, y, z) + +将实体传送到指定坐标。 + +### entity.teleportTo(pos) + +⬆ GameVector3 重载。 + +```js +entity.teleportTo(0, 100, 0); +entity.teleportTo(new GameVector3(10, 100, 20)); +``` + ### entity.lookAt(x, y, z) 实体面朝目标坐标。 @@ -386,6 +403,20 @@ entity.setEquipment("chest", "minecraft:iron_chestplate"); entity.setEquipment("feet", "minecraft:leather_boots"); ``` +### entity.setEquipmentWithEnchants(slot, itemId, enchants) + +给生物穿戴带附魔的装备。`enchants` 是 `{附魔ID: 等级}` 对象。 + +```js +entity.setEquipmentWithEnchants("mainhand", "minecraft:diamond_sword", { + "minecraft:sharpness": 5, + "minecraft:fire_aspect": 2, +}); +entity.setEquipmentWithEnchants("head", "minecraft:diamond_helmet", { + "minecraft:protection": 4, +}); +``` + ### entity.setDropChance(slot, chance) 设置装备槽物品的掉落概率,范围 0.0–1.0。`slot` 设为 `"all"` 可一次性设置所有槽位(包括主副手和四个护甲槽)。 @@ -426,6 +457,14 @@ entity.setAttribute("minecraft:generic.armor", 10); 销毁实体。如果通过 `setOnDestroy()` 设置了回调,会触发它。 +### entity.remove() + +立即从世界中移除实体,不触发回调。与 `destroy()` 不同,`remove()` 不会调用 `setOnDestroy` 注册的回调。 + +```js +entity.remove(); // 简单移除,无回调 +``` + ### entity.setOnDestroy(handler) 设置销毁回调。`handler` 接收一个参数 `(entity)`。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/input.md b/Box3JS-NeoForge-1.21.1/docs/api/input.md index fc31504..6fd0650 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/input.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/input.md @@ -45,6 +45,22 @@ var mx = input.getMouseX(); var my = input.getMouseY(); ``` +## input.getMouseDeltaX() + +获取当前帧鼠标 X 轴增量(自上一帧的移动量)。 + +```js +var dx = input.getMouseDeltaX(); +``` + +## input.getMouseDeltaY() + +获取当前帧鼠标 Y 轴增量(自上一帧的移动量)。 + +```js +var dy = input.getMouseDeltaY(); +``` + ## input.onMouseClick(callback) 注册鼠标按键回调。返回 `GameEventHandlerToken`,调用 `.cancel()` 取消。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/math.md b/Box3JS-NeoForge-1.21.1/docs/api/math.md index 5344dce..41b29e6 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/math.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/math.md @@ -155,6 +155,12 @@ var bounds = new GameBounds3( | `bounds.closestPoint(v)` | `GameVector3` | 包围盒上离点 `v` 最近的点 | | `bounds.move(offset)` | `GameBounds3` | 平移 `offset`,返回新包围盒 | | `bounds.moveEq(offset)` | `GameBounds3` | 原地平移 `offset`,返回自身 | +| `bounds.volume()` | `number` | 包围盒体积 (宽×高×深) | +| `bounds.isEmpty()` | `boolean` | 包围盒是否为空 (任一维度 ≤ 0) | +| `bounds.equals(b)` | `boolean` | 判断两个包围盒是否完全相等 | +| `bounds.union(b)` | `GameBounds3` | 返回同时包围两者自身和 b 的最小盒 | +| `bounds.inflate(amount)` | `GameBounds3` | 每面向外扩展 amount(返回新对象) | +| `bounds.deflate(amount)` | `GameBounds3` | 每面向内收缩 amount(最小为 0) | ### 静态方法 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/player.md b/Box3JS-NeoForge-1.21.1/docs/api/player.md index fdf11f6..8195a47 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/player.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/player.md @@ -472,6 +472,64 @@ console.log(held.id, held.count); // "minecraft:diamond_sword" 1 player.clearInventory(); ``` +### player.removeItem(itemId, count) + +从背包中移除指定数量的物品。返回实际移除的数量。 + +```js +// 移除 3 个铁锭 +const removed = player.removeItem("minecraft:iron_ingot", 3); +console.log("实际移除: " + removed); +``` + +### player.setHeldSlot(slot) + +设置玩家手持快捷栏槽位 (0–8)。 + +```js +player.setHeldSlot(0); // 切换到第 1 格 +player.setHeldSlot(4); // 切换到第 5 格 +``` + +### player.inventoryFreeSlots + +只读 `number`。返回背包空余槽位数(0–36)。 + +```js +if (player.inventoryFreeSlots < 2) { + player.directMessage("背包空间不足!"); +} +``` + +### player.hasItem(itemId) + +检查背包中是否拥有某物品。 + +```js +if (player.hasItem("minecraft:diamond")) { + player.directMessage("你有一颗钻石!"); +} +``` + +### player.getItemCount(itemId) + +统计背包中某物品的总数量。 + +```js +var count = player.getItemCount("minecraft:iron_ingot"); +console.log("背包里有 " + count + " 个铁锭"); +``` + +### player.givePotion(itemId, potionType, count) + +给予玩家指定类型的药水(带有 `PotionContents` 组件,适用于 1.21.1+)。 + +```js +player.givePotion("minecraft:potion", "minecraft:healing", 3); +player.givePotion("minecraft:splash_potion", "minecraft:poison", 1); +player.givePotion("minecraft:lingering_potion", "minecraft:regeneration", 2); +``` + ## 自定义容器 GUI 为玩家打开脚本控制的容器 GUI(类似箱子界面),可自定义格子内容、点击行为和关闭回调。 @@ -579,6 +637,31 @@ player.grantAdvancement("minecraft:adventure/kill_a_mob"); player.revokeAdvancement("minecraft:story/mine_stone"); ``` +## Bossbar + +### player.showBossbar(name, text, progress, color) + +向该玩家显示一个独立的 Bossbar。 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `name` | string | Bossbar 唯一标识名,用于后续移除 | +| `text` | string | 显示文字 | +| `progress` | number | 进度条 (0–1) | +| `color` | string | 颜色:`"pink"` / `"blue"` / `"red"` / `"green"` / `"yellow"` / `"purple"` / `"white"` | + +```js +player.showBossbar("boss_health", "§c§lBoss HP", 0.75, "red"); +``` + +### player.removeBossbar(name) + +移除指定名称的 Bossbar。 + +```js +player.removeBossbar("boss_health"); +``` + ## Tab 列表 ### player.setPlayerListName(name) diff --git a/Box3JS-NeoForge-1.21.1/docs/api/ui.md b/Box3JS-NeoForge-1.21.1/docs/api/ui.md index b6b3ea1..6ff772b 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/ui.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/ui.md @@ -80,3 +80,59 @@ ui.removeDrawText(1); ```js ui.clearDrawTexts(); ``` + +## ui.drawItem(id, x, y, itemId, scale?) + +在屏幕上绘制物品图标(每帧持续绘制,直到调用 `removeDrawItem` 移除)。 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `id` | number | (必需) | 图标 ID,用于后续移除或更新 | +| `x` | number | (必需) | X 坐标(GUI 缩放坐标系) | +| `y` | number | (必需) | Y 坐标(GUI 缩放坐标系) | +| `itemId` | string | (必需) | 物品 ID(如 `"minecraft:diamond"`) | +| `scale` | number | `16` | 图标尺寸(像素) | + +返回图标 ID(与传入的 `id` 相同)。 + +```js +ui.drawItem(1, 10, 10, "minecraft:diamond"); +ui.drawItem(2, 30, 10, "minecraft:golden_apple", 24); +``` + +## ui.removeDrawItem(id) + +移除指定 ID 的绘制图标。 + +```js +ui.removeDrawItem(1); +``` + +## ui.drawRect(id, x, y, w, h, color, alpha?) + +在屏幕上绘制矩形(每帧持续绘制,直到调用 `removeDrawRect` 移除)。 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `id` | number | (必需) | 矩形 ID,用于后续移除或更新 | +| `x` | number | (必需) | 左上角 X 坐标 | +| `y` | number | (必需) | 左上角 Y 坐标 | +| `w` | number | (必需) | 宽度(像素) | +| `h` | number | (必需) | 高度(像素) | +| `color` | GameRGBColor | (必需) | 填充颜色(RGB) | +| `alpha` | number | `255` | 透明度 0–255 | + +返回矩形 ID(与传入的 `id` 相同)。 + +```js +var red = new GameRGBColor(1, 0, 0); +ui.drawRect(1, 50, 50, 100, 60, red, 128); // 半透明红色矩形 +``` + +## ui.removeDrawRect(id) + +移除指定 ID 的绘制矩形。 + +```js +ui.removeDrawRect(1); +``` diff --git a/Box3JS-NeoForge-1.21.1/docs/api/voxels.md b/Box3JS-NeoForge-1.21.1/docs/api/voxels.md index 0310e84..3c25077 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/voxels.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/voxels.md @@ -76,6 +76,10 @@ voxels.setVoxel(new GameVector3(0, 100, 0), "minecraft:oak_stairs", 2); ⬆ GameVector3 重载。 +### voxels.fillVoxel(bounds, voxel) + +⬆ GameBounds3 重载。 + ```js // 填充一个 5×1×5 的平台 voxels.fillVoxel(-2, 100, -2, 2, 100, 2, "minecraft:white_concrete"); @@ -93,6 +97,77 @@ voxels.fillVoxel( ); ``` +### voxels.replace(x1, y1, z1, x2, y2, z2, fromBlock, toBlock) + +在矩形区域内,将所有 `fromBlock` 替换为 `toBlock`。 + +### voxels.replace(pos1, pos2, fromBlock, toBlock) + +⬆ GameVector3 重载。 + +### voxels.replace(bounds, fromBlock, toBlock) + +⬆ GameBounds3 重载。 + +```js +// 清除所有羊毛方块 +voxels.replace(-10, 60, -10, 10, 80, 10, "minecraft:white_wool", "minecraft:air"); +voxels.replace( + new GameVector3(-10, 60, -10), + new GameVector3(10, 80, 10), + "minecraft:red_wool", + "minecraft:blue_wool", +); +``` + +### voxels.clone(x1, y1, z1, x2, y2, z2, destX, destY, destZ) + +将源区域的所有方块复制到目标位置(含方块状态和旋转)。 + +### voxels.clone(pos1, pos2, destPos) + +⬆ GameVector3 重载。 + +### voxels.clone(bounds, destX, destY, destZ) + +⬆ GameBounds3 重载,目标坐标为原始坐标。 + +### voxels.clone(bounds, destPos) + +⬆ GameBounds3 重载,目标坐标为 GameVector3。 + +```js +// 复制一个 5×5×5 的结构到新位置 +voxels.clone(0, 100, 0, 5, 105, 5, 10, 100, 10); +voxels.clone( + new GameVector3(0, 100, 0), + new GameVector3(5, 105, 5), + new GameVector3(10, 100, 10), +); +``` + +### voxels.setVoxelState(x, y, z, voxel, state) + +放置方块并指定 BlockState 属性。`state` 是一个键值对对象,key 为属性名,value 为属性值。 + +```js +// 放置朝北的橡木楼梯 +voxels.setVoxelState(0, 100, 0, "minecraft:oak_stairs", { + facing: "north", + half: "top", +}); + +// 放置点燃的蜡烛 +voxels.setVoxelState(0, 100, 0, "minecraft:candle", { + candles: "3", + lit: "true", +}); +``` + +### voxels.setVoxelState(pos, voxel, state) + +⬆ GameVector3 重载。 + ## 读取方块 ### voxels.getVoxel(x, y, z) @@ -146,6 +221,10 @@ var name = voxels.getVoxelName(new GameVector3(0, 100, 0)); ⬆ GameVector3 重载。 +### voxels.countVoxel(bounds, voxel) + +⬆ GameBounds3 重载。 + ```js // 统计区域内有多少个钻石块 var count = voxels.countVoxel( diff --git a/Box3JS-NeoForge-1.21.1/docs/api/world.md b/Box3JS-NeoForge-1.21.1/docs/api/world.md index 935d548..8973ef6 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/world.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/world.md @@ -258,17 +258,17 @@ var token = world.onTick(function (info) { | `world.onPlayerJoin(fn)` | | `(entity, tick)` | 玩家登录 | | `world.onPlayerLeave(fn)` | | `(entity, tick)` | 玩家退出 | | `world.onChat(fn)` | | `(entity, message, tick) => boolean \| void` | 玩家发送聊天消息;返回 `false` 可取消 | -| `world.onVoxelDestroy(fn)` | | `(entity, x, y, z, voxel, tick)` | 玩家破坏方块 | -| `world.onBlockPlace(fn)` | ⬆ MC | `(entity, x, y, z, voxel, voxelId, tick)` | 玩家放置方块 | -| `world.onBlockActivate(fn)` | ⬆ MC | `(entity, x, y, z, voxel, tick)` | 玩家右键方块 | -| `world.onInteract(fn)` | | `(entity, target, tick)` | 玩家右键实体 | +| `world.onVoxelDestroy(fn)` | | `(entity, x, y, z, voxel, tick) => boolean \| void` | 玩家破坏方块;返回 `false` 可取消 | +| `world.onBlockPlace(fn)` | ⬆ MC | `(entity, x, y, z, voxel, voxelId, tick) => boolean \| void` | 玩家放置方块;返回 `false` 可取消 | +| `world.onBlockActivate(fn)` | ⬆ MC | `(entity, x, y, z, voxel, tick) => boolean \| void` | 玩家右键方块;返回 `false` 可取消交互 | +| `world.onInteract(fn)` | | `(entity, target, tick) => boolean \| void` | 玩家右键实体;返回 `false` 可取消交互 | | `world.onVoxelContact(fn)` | | `(entity, voxelId, x, y, z, contactType, force, tick)` | 实体接触方块 | | `world.onEntityContact(fn)` | | `(entity, other, tick)` | 两个实体接触 | | `world.onEntitySeparate(fn)` | | `(entity, other, tick)` | 两个实体分离 | | `world.onFluidEnter(fn)` | | `(entity, fluid, x, y, z, tick)` | 实体进入液体 | | `world.onFluidLeave(fn)` | | `(entity, fluid, x, y, z, tick)` | 实体离开液体 | | `world.onEntityDeath(fn)` | ⬆ MC | `(entity, killer, tick)` | 实体死亡;`killer` 可能为 null | -| `world.onEntityDamage(fn)` | ⬆ MC | `(entity, amount, source, attacker, tick)` | 实体受伤(Pre 阶段) | +| `world.onEntityDamage(fn)` | ⬆ MC | `(entity, amount, source, attacker, tick)` => `boolean\|void` | 实体受伤(Pre 阶段);返回 `false` 可取消伤害 | | `world.onPlayerRespawn(fn)` | ⬆ MC | `(entity, tick)` | 玩家重生 | | `world.onButtonPressed(fn)` | ⬆ MC | `(entity, button, tick)` | 玩家按下按钮 | | `world.onMessage(fn)` | ⬆ MC | `(from, data)` | 收到 `world.sendMessage()` 消息 | @@ -787,6 +787,10 @@ if (result.hit) { 返回 AABB 包围盒内所有实体。 +### world.entitiesInArea(bounds) + +⬆ GameBounds3 重载。 + ### world.entitiesInRadius(x, y, z, radius) 返回球体范围内所有实体。`entitiesInArea` 的便捷封装。 diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/client.md b/Box3JS-NeoForge-1.21.1/docs/en/api/client.md index 8cee98a..9e70b13 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/client.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/client.md @@ -157,6 +157,52 @@ Resets fog to Minecraft's default behaviour. client.resetFog(); ``` +## Camera Control + +### client.setCameraTarget(entityUuid) + +Switches the camera to spectate the given entity (spectator-style view). + +```js +// Switch to spectate a specific entity +client.setCameraTarget("550e8400-e29b-41d4-a716-446655440000"); +``` + +### client.resetCamera() + +Resets the camera back to the local player. + +```js +client.resetCamera(); +``` + +## Block Query + +### client.getBlockState(x, y, z) + +Queries the block state at a position from the client's render chunk cache. Returns `{ id: string }` or `null` for air/unloaded chunks. + +```js +var block = client.getBlockState(0, 100, 0); +if (block) { + console.log("Block ID: " + block.id); +} +``` + +## Render Callback + +### client.onRender(callback) + +Registers a callback invoked every render frame (during HUD render phase). The callback receives `partialTick` (0–1), the frame interpolation factor. + +Returns a `GameEventHandlerToken`; call `.cancel()` to unsubscribe. + +```js +var token = client.onRender(function (partialTick) { + // Use partialTick for smooth custom rendering +}); +``` + ## Complete Client Example ```js diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md b/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md index 2cf07a6..55a7599 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md @@ -98,6 +98,10 @@ zombie.hp = 100; Deals `amount` generic damage to the entity (triggers damage events). +### entity.hurt(amount, source) + +Deals damage to the entity with a source entity for kill tracking. `source` is another `GameEntity`. + ### entity.heal(amount) Heals the entity by `amount` (capped at maxHp). @@ -328,6 +332,19 @@ entity.navigateTo(10, 100, 10, 1.0); entity.navigateTo(target.position, 1.0); ``` +### entity.teleportTo(x, y, z) + +Teleports the entity to the given coordinates. + +### entity.teleportTo(pos) + +⬆ GameVector3 overload. + +```js +entity.teleportTo(0, 100, 0); +entity.teleportTo(new GameVector3(10, 100, 20)); +``` + ### entity.lookAt(x, y, z) Makes the entity look at the given coordinates. @@ -389,6 +406,20 @@ entity.setEquipment("chest", "minecraft:iron_chestplate"); entity.setEquipment("feet", "minecraft:leather_boots"); ``` +### entity.setEquipmentWithEnchants(slot, itemId, enchants) + +Equips an enchanted item onto a mob's equipment slot. `enchants` is an `{enchantmentId: level}` object. + +```js +entity.setEquipmentWithEnchants("mainhand", "minecraft:diamond_sword", { + "minecraft:sharpness": 5, + "minecraft:fire_aspect": 2, +}); +entity.setEquipmentWithEnchants("head", "minecraft:diamond_helmet", { + "minecraft:protection": 4, +}); +``` + ### entity.setDropChance(slot, chance) Sets the drop chance for an equipment slot, range 0.0–1.0. Use `"all"` for `slot` to set all slots at once (both hands + four armor slots). @@ -429,6 +460,14 @@ entity.setAttribute("minecraft:generic.armor", 10); Destroys the entity. If a callback was registered via `setOnDestroy()`, it will be invoked. +### entity.remove() + +Immediately removes the entity from the world without triggering the destroy callback. Unlike `destroy()`, `remove()` does not invoke the `setOnDestroy` handler. + +```js +entity.remove(); // Simple removal, no callback +``` + ### entity.setOnDestroy(handler) Registers a callback called when the entity is destroyed. `handler` receives one argument `(entity)`. diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/input.md b/Box3JS-NeoForge-1.21.1/docs/en/api/input.md index bd37a8f..781a40a 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/input.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/input.md @@ -45,6 +45,22 @@ Gets the current mouse Y position in screen pixels. var my = input.getMouseY(); ``` +## input.getMouseDeltaX() + +Gets the mouse X delta since the last frame. + +```js +var dx = input.getMouseDeltaX(); +``` + +## input.getMouseDeltaY() + +Gets the mouse Y delta since the last frame. + +```js +var dy = input.getMouseDeltaY(); +``` + ## input.onMouseClick(callback) Registers a mouse button callback. Returns `GameEventHandlerToken`; call `.cancel()` to unregister. diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/math.md b/Box3JS-NeoForge-1.21.1/docs/en/api/math.md index 1915335..5f7dd56 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/math.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/math.md @@ -158,6 +158,12 @@ var bounds = new GameBounds3( | `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 | +| `bounds.volume()` | `number` | Volume of the bounds (width × height × depth) | +| `bounds.isEmpty()` | `boolean` | Whether the bounds is empty (any dimension ≤ 0) | +| `bounds.equals(b)` | `boolean` | Whether this bounds exactly equals another | +| `bounds.union(b)` | `GameBounds3` | Returns the smallest bounds containing both this and b | +| `bounds.inflate(amount)` | `GameBounds3` | Expand outward by amount on all six faces (returns new object) | +| `bounds.deflate(amount)` | `GameBounds3` | Shrink inward by amount on all six faces (clamped to zero) | ### Static Methods diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/player.md b/Box3JS-NeoForge-1.21.1/docs/en/api/player.md index 92ac144..ba11700 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/player.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/player.md @@ -475,6 +475,63 @@ Clears the entire inventory (including armor slots and offhand). player.clearInventory(); ``` +### player.removeItem(itemId, count) + +Removes a specified count of the given item from the player's inventory. Returns the actual number of items removed. + +```js +var removed = player.removeItem("minecraft:iron_ingot", 3); +console.log("Removed: " + removed); +``` + +### player.setHeldSlot(slot) + +Sets the player's selected hotbar slot (0–8). + +```js +player.setHeldSlot(0); // Switch to first slot +player.setHeldSlot(4); // Switch to fifth slot +``` + +### player.inventoryFreeSlots + +Readonly `number`. Number of empty slots in the player's main inventory (0–36). + +```js +if (player.inventoryFreeSlots < 2) { + player.directMessage("Not enough inventory space!"); +} +``` + +### player.hasItem(itemId) + +Checks whether the player has at least one of the given item in inventory. + +```js +if (player.hasItem("minecraft:diamond")) { + player.directMessage("You have a diamond!"); +} +``` + +### player.getItemCount(itemId) + +Counts the total number of the given item in the player's inventory. + +```js +var count = player.getItemCount("minecraft:iron_ingot"); +console.log("You have " + count + " iron ingots"); +``` + +### player.givePotion(itemId, potionType, count) + +Gives the player a potion with a `PotionContents` component (1.21.1+). + +```js +player.givePotion("minecraft:potion", "minecraft:healing", 3); +player.givePotion("minecraft:splash_potion", "minecraft:poison", 1); +player.givePotion("minecraft:lingering_potion", "minecraft:regeneration", 2); +``` + ## Custom Container GUI Opens a script-controlled container GUI (chest-like screen) for the player, with custom slot contents, click behavior, and close callbacks. @@ -577,6 +634,31 @@ player.grantAdvancement("minecraft:adventure/kill_a_mob"); player.revokeAdvancement("minecraft:story/mine_stone"); ``` +## Bossbar + +### player.showBossbar(name, text, progress, color) + +Shows a per-player bossbar (adds the player to a `ServerBossEvent`). + +| Parameter | Type | Description | +|------------|--------|-------------| +| `name` | string | Unique bossbar identifier for later removal | +| `text` | string | Display text | +| `progress` | number | Progress bar fill (0–1) | +| `color` | string | Color: `"pink"` / `"blue"` / `"red"` / `"green"` / `"yellow"` / `"purple"` / `"white"` | + +```js +player.showBossbar("boss_health", "§c§lBoss HP", 0.75, "red"); +``` + +### player.removeBossbar(name) + +Removes a per-player bossbar by name. + +```js +player.removeBossbar("boss_health"); +``` + ## Tab List ### player.setPlayerListName(name) diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/ui.md b/Box3JS-NeoForge-1.21.1/docs/en/api/ui.md index fe20fac..3d2f350 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/ui.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/ui.md @@ -80,3 +80,59 @@ Clears all texts drawn via `drawText()`. ```js ui.clearDrawTexts(); ``` + +## ui.drawItem(id, x, y, itemId, scale?) + +Draws an item icon on screen (persists every frame until removed via `removeDrawItem`). + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `id` | number | (required) | Icon ID for later removal or update | +| `x` | number | (required) | X position (GUI-scaled coordinates) | +| `y` | number | (required) | Y position (GUI-scaled coordinates) | +| `itemId` | string | (required) | Item ID (e.g. `"minecraft:diamond"`) | +| `scale` | number | `16` | Icon size in pixels | + +Returns the icon ID (same as the passed `id`). + +```js +ui.drawItem(1, 10, 10, "minecraft:diamond"); +ui.drawItem(2, 30, 10, "minecraft:golden_apple", 24); +``` + +## ui.removeDrawItem(id) + +Removes the drawn item icon with the given ID. + +```js +ui.removeDrawItem(1); +``` + +## ui.drawRect(id, x, y, w, h, color, alpha?) + +Draws a filled rectangle on screen (persists every frame until removed via `removeDrawRect`). + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `id` | number | (required) | Rectangle ID for later removal or update | +| `x` | number | (required) | Top-left X position | +| `y` | number | (required) | Top-left Y position | +| `w` | number | (required) | Width in pixels | +| `h` | number | (required) | Height in pixels | +| `color` | GameRGBColor | (required) | Fill colour (RGB) | +| `alpha` | number | `255` | Alpha / opacity (0–255) | + +Returns the rectangle ID (same as the passed `id`). + +```js +var red = new GameRGBColor(1, 0, 0); +ui.drawRect(1, 50, 50, 100, 60, red, 128); // Semi-transparent red rectangle +``` + +## ui.removeDrawRect(id) + +Removes the drawn rectangle with the given ID. + +```js +ui.removeDrawRect(1); +``` diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/voxels.md b/Box3JS-NeoForge-1.21.1/docs/en/api/voxels.md index ef9c650..3cfb4ac 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/voxels.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/voxels.md @@ -79,6 +79,10 @@ Fill a rectangular region with a block. Corner coordinates are auto-sorted (no n ⬆ GameVector3 overload. +### voxels.fillVoxel(bounds, voxel) + +⬆ GameBounds3 overload. + ```js // Fill a 5×1×5 platform voxels.fillVoxel(-2, 100, -2, 2, 100, 2, "minecraft:white_concrete"); @@ -96,6 +100,77 @@ voxels.fillVoxel( ); ``` +### voxels.replace(x1, y1, z1, x2, y2, z2, fromBlock, toBlock) + +Replaces all occurrences of `fromBlock` with `toBlock` within the rectangular region. + +### voxels.replace(pos1, pos2, fromBlock, toBlock) + +⬆ GameVector3 overload. + +### voxels.replace(bounds, fromBlock, toBlock) + +⬆ GameBounds3 overload. + +```js +// Remove all wool blocks +voxels.replace(-10, 60, -10, 10, 80, 10, "minecraft:white_wool", "minecraft:air"); +voxels.replace( + new GameVector3(-10, 60, -10), + new GameVector3(10, 80, 10), + "minecraft:red_wool", + "minecraft:blue_wool", +); +``` + +### voxels.clone(x1, y1, z1, x2, y2, z2, destX, destY, destZ) + +Copies all blocks from the source region to the destination (preserving block state and rotation). + +### voxels.clone(pos1, pos2, destPos) + +⬆ GameVector3 overload. + +### voxels.clone(bounds, destX, destY, destZ) + +⬆ GameBounds3 overload with raw destination coordinates. + +### voxels.clone(bounds, destPos) + +⬆ GameBounds3 overload with GameVector3 destination. + +```js +// Copy a 5×5×5 structure to a new location +voxels.clone(0, 100, 0, 5, 105, 5, 10, 100, 10); +voxels.clone( + new GameVector3(0, 100, 0), + new GameVector3(5, 105, 5), + new GameVector3(10, 100, 10), +); +``` + +### voxels.setVoxelState(x, y, z, voxel, state) + +Place a block with specific BlockState properties. `state` is a key-value object mapping property names to values. + +```js +// Place an oak stair facing north, top half +voxels.setVoxelState(0, 100, 0, "minecraft:oak_stairs", { + facing: "north", + half: "top", +}); + +// Place three lit candles +voxels.setVoxelState(0, 100, 0, "minecraft:candle", { + candles: "3", + lit: "true", +}); +``` + +### voxels.setVoxelState(pos, voxel, state) + +⬆ GameVector3 overload. + ## Reading Blocks ### voxels.getVoxel(x, y, z) @@ -149,6 +224,10 @@ Count matching blocks in a region. `voxel` can be a string or numeric ID. ⬆ GameVector3 overload. +### voxels.countVoxel(bounds, voxel) + +⬆ GameBounds3 overload. + ```js // Count how many diamond blocks are in the region var count = voxels.countVoxel( diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/world.md b/Box3JS-NeoForge-1.21.1/docs/en/api/world.md index 9dd191f..4959dca 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/world.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/world.md @@ -792,6 +792,10 @@ if (result.hit) { Returns all entities within the AABB defined by two corner positions. +### world.entitiesInArea(bounds) + +⬆ GameBounds3 overload. + ### world.entitiesInRadius(x, y, z, radius) Returns all entities within a spherical radius. Convenience wrapper around `entitiesInArea`. diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java index 50216e1..ec097df 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java @@ -129,28 +129,36 @@ public Box3JS(IEventBus modEventBus, ModContainer modContainer) { // Block break NeoForge.EVENT_BUS.addListener((BlockEvent.BreakEvent event) -> { if (event.getPlayer() instanceof ServerPlayer sp) { - Box3ScriptEngine.get().fireVoxelDestroy(sp, event.getPos()); + if (Box3ScriptEngine.get().fireVoxelDestroy(sp, event.getPos())) { + event.setCanceled(true); + } } }); // Block place NeoForge.EVENT_BUS.addListener((BlockEvent.EntityPlaceEvent event) -> { if (event.getEntity() instanceof ServerPlayer sp) { - Box3ScriptEngine.get().fireBlockPlace(sp, event.getPos(), event.getPlacedBlock()); + if (Box3ScriptEngine.get().fireBlockPlace(sp, event.getPos(), event.getPlacedBlock())) { + event.setCanceled(true); + } } }); // Interact (entity) NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.EntityInteract event) -> { if (event.getEntity() instanceof ServerPlayer sp) { - Box3ScriptEngine.get().fireInteract(sp, event.getTarget()); + if (Box3ScriptEngine.get().fireInteract(sp, event.getTarget())) { + event.setCanceled(true); + } } }); // Right-click block NeoForge.EVENT_BUS.addListener((PlayerInteractEvent.RightClickBlock event) -> { if (event.getEntity() instanceof ServerPlayer sp) { - Box3ScriptEngine.get().fireBlockActivate(sp, event.getPos(), event.getLevel().getBlockState(event.getPos())); + if (Box3ScriptEngine.get().fireBlockActivate(sp, event.getPos(), event.getLevel().getBlockState(event.getPos()))) { + event.setCanceled(true); + } Box3ScriptEngine.get().fireActionButton(sp, "ACTION1"); } }); @@ -202,10 +210,12 @@ public Box3JS(IEventBus modEventBus, ModContainer modContainer) { // Entity damage NeoForge.EVENT_BUS.addListener((LivingDamageEvent.Pre event) -> { - Box3ScriptEngine.get().fireEntityDamage(event.getEntity(), + if (Box3ScriptEngine.get().fireEntityDamage(event.getEntity(), event.getNewDamage(), event.getSource().getMsgId(), - event.getSource().getEntity()); + event.getSource().getEntity())) { + event.setNewDamage(0); + } }); // Auto-load server scripts from config/box3/script//dist/server.js on server start diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java index 56f9fbd..2edd07b 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java @@ -35,6 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; @@ -76,7 +78,10 @@ public class Box3JSClientEngine { private volatile boolean renderRegistered; private volatile boolean mouseRegistered; private final Map drawTexts = new ConcurrentHashMap<>(); - private final AtomicInteger drawTextIdCounter = new AtomicInteger(0); + private final Map drawItems = new ConcurrentHashMap<>(); + private final Map drawRects = new ConcurrentHashMap<>(); + private final AtomicInteger drawIdCounter = new AtomicInteger(0); + private final List renderCallbacks = new CopyOnWriteArrayList<>(); private final List mouseClickHandlers = new CopyOnWriteArrayList<>(); private String currentProject = ""; private Box3JSClientStorage storage; @@ -94,6 +99,7 @@ public class Box3JSClientEngine { private volatile float fogStartDist = -1f; private volatile float fogEndDist = -1f; private volatile boolean fogRegistered; + private double prevMouseX, prevMouseY, mouseDeltaX, mouseDeltaY; // ── Timers ── private final List timers = new CopyOnWriteArrayList<>(); @@ -379,6 +385,75 @@ public Object call(Context cx, Scriptable scope, } }); + // client.setCameraTarget(entityUuid) — spectate an entity + ScriptableObject.putProperty(clientObj, "setCameraTarget", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 1) return Undefined.instance; + String uuid = args[0].toString(); + Minecraft.getInstance().execute(() -> { + var level = Minecraft.getInstance().level; + if (level == null) return; + for (var entity : level.entitiesForRendering()) { + if (entity.getStringUUID().equals(uuid)) { + Minecraft.getInstance().setCameraEntity(entity); + return; + } + } + }); + return Undefined.instance; + } + }); + + // client.resetCamera() — back to local player + ScriptableObject.putProperty(clientObj, "resetCamera", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + Minecraft.getInstance().execute(() -> { + Minecraft.getInstance().setCameraEntity( + Minecraft.getInstance().player); + }); + return Undefined.instance; + } + }); + + // client.getBlockState(x, y, z) -> { id: string } | null + ScriptableObject.putProperty(clientObj, "getBlockState", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 3) return null; + int x = ((Number) args[0]).intValue(); + int y = ((Number) args[1]).intValue(); + int z = ((Number) args[2]).intValue(); + var level = Minecraft.getInstance().level; + if (level == null) return null; + var state = level.getBlockState(new BlockPos(x, y, z)); + if (state == null || state.isAir()) return null; + Scriptable obj = cx.newObject(scope); + ScriptableObject.putProperty(obj, "id", + net.minecraft.core.registries.BuiltInRegistries.BLOCK.getKey( + state.getBlock()).toString()); + return obj; + } + }); + + // client.onRender(callback) — fires every render frame + ScriptableObject.putProperty(clientObj, "onRender", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length > 0 && args[0] instanceof Function fn) { + renderCallbacks.add(fn); + registerRenderListener(); + return new GameEventHandlerToken(() -> renderCallbacks.remove(fn)); + } + return Undefined.instance; + } + }); + ScriptableObject.putProperty(scope, "client", clientObj); // -- audio global (sound playback) ------------------------------ @@ -524,6 +599,24 @@ public Object call(Context cx, Scriptable scope, } }); + // input.getMouseDeltaX() + ScriptableObject.putProperty(inputObj, "getMouseDeltaX", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + return mouseDeltaX; + } + }); + + // input.getMouseDeltaY() + ScriptableObject.putProperty(inputObj, "getMouseDeltaY", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + return mouseDeltaY; + } + }); + // input.onMouseClick(callback) — returns GameEventHandlerToken ScriptableObject.putProperty(inputObj, "onMouseClick", new BaseFunction() { @Override @@ -627,7 +720,7 @@ public Object call(Context cx, Scriptable scope, public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (args.length < 4) return -1; - int id = args[0] instanceof Number n ? n.intValue() : drawTextIdCounter.incrementAndGet(); + int id = args[0] instanceof Number n ? n.intValue() : drawIdCounter.incrementAndGet(); int x = ((Number) args[1]).intValue(); int y = ((Number) args[2]).intValue(); String text = args[3].toString(); @@ -666,6 +759,67 @@ public Object call(Context cx, Scriptable scope, } }); + // ui.drawItem(id, x, y, itemId, scale?) + ScriptableObject.putProperty(uiObj, "drawItem", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 4) return -1; + int id = args[0] instanceof Number n ? n.intValue() : drawIdCounter.incrementAndGet(); + int x = ((Number) args[1]).intValue(); + int y = ((Number) args[2]).intValue(); + String itemId = args[3].toString(); + int scale = args.length > 4 && args[4] instanceof Number n ? n.intValue() : 16; + drawItems.put(id, new DrawItemEntry(x, y, itemId, scale)); + registerRenderListener(); + return id; + } + }); + + // ui.removeDrawItem(id) + ScriptableObject.putProperty(uiObj, "removeDrawItem", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 1) return Undefined.instance; + int id = ((Number) args[0]).intValue(); + drawItems.remove(id); + return Undefined.instance; + } + }); + + // ui.drawRect(id, x, y, w, h, color, alpha?) + ScriptableObject.putProperty(uiObj, "drawRect", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 6) return -1; + int id = args[0] instanceof Number n ? n.intValue() : drawIdCounter.incrementAndGet(); + int x = ((Number) args[1]).intValue(); + int y = ((Number) args[2]).intValue(); + int w = ((Number) args[3]).intValue(); + int h = ((Number) args[4]).intValue(); + int color = argbToInt(args[5]); + int alpha = args.length > 6 && args[6] instanceof Number n + ? Math.max(0, Math.min(255, n.intValue())) : 255; + drawRects.put(id, new DrawRectEntry(x, y, w, h, color, alpha)); + registerRenderListener(); + return id; + } + }); + + // ui.removeDrawRect(id) + ScriptableObject.putProperty(uiObj, "removeDrawRect", new BaseFunction() { + @Override + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + if (args.length < 1) return Undefined.instance; + int id = ((Number) args[0]).intValue(); + drawRects.remove(id); + return Undefined.instance; + } + }); + ScriptableObject.putProperty(scope, "ui", uiObj); // -- chat global (messaging) ------------------------------------ @@ -921,6 +1075,14 @@ public void loadScript(String projectName, String scriptSource) { // ── Tick dispatch ── private void fireTick() { + // Track mouse delta + double curX = Minecraft.getInstance().mouseHandler.xpos(); + double curY = Minecraft.getInstance().mouseHandler.ypos(); + mouseDeltaX = curX - prevMouseX; + mouseDeltaY = curY - prevMouseY; + prevMouseX = curX; + prevMouseY = curY; + for (Runnable cb : tickCallbacks) { try { cb.run(); @@ -1106,11 +1268,63 @@ private void registerRenderListener() { Minecraft.getInstance().execute(() -> { if (renderRegistered) return; NeoForge.EVENT_BUS.addListener(RenderGuiEvent.Post.class, event -> { - if (drawTexts.isEmpty()) return; GuiGraphics gfx = event.getGuiGraphics(); - var font = Minecraft.getInstance().font; - for (DrawTextEntry entry : drawTexts.values()) { - gfx.drawString(font, entry.text(), entry.x(), entry.y(), entry.color()); + float partialTick = event.getPartialTick().getGameTimeDeltaTicks(); + + // Fire render callbacks + for (Function fn : renderCallbacks) { + Context cx = Context.enter(); + try { + fn.call(cx, scope, scope, new Object[]{partialTick}); + } catch (Exception e) { + LOGGER.error("Render callback error", e); + } finally { + Context.exit(); + } + } + + // Draw texts + if (!drawTexts.isEmpty()) { + var font = Minecraft.getInstance().font; + for (DrawTextEntry entry : drawTexts.values()) { + gfx.drawString(font, entry.text(), entry.x(), entry.y(), entry.color()); + } + } + + // Draw rects + if (!drawRects.isEmpty()) { + for (DrawRectEntry entry : drawRects.values()) { + int a = entry.alpha(); + int c = entry.color(); + int argb = (a << 24) | (c & 0x00FFFFFF); + gfx.fill(entry.x(), entry.y(), + entry.x() + entry.w(), entry.y() + entry.h(), + argb); + } + } + + // Draw items + if (!drawItems.isEmpty()) { + for (DrawItemEntry entry : drawItems.values()) { + var rl = net.minecraft.resources.ResourceLocation.tryParse(entry.itemId()); + if (rl == null) continue; + var holder = net.minecraft.core.registries.BuiltInRegistries.ITEM.getHolder(rl); + if (holder.isEmpty()) continue; + var stack = new ItemStack(holder.get()); + int s = entry.scale(); + if (s != 16) { + gfx.pose().pushPose(); + float scale = s / 16.0f; + gfx.pose().translate(entry.x(), entry.y(), 0); + gfx.pose().scale(scale, scale, 1); + gfx.renderItem(stack, 0, 0); + gfx.renderItemDecorations(Minecraft.getInstance().font, stack, 0, 0); + gfx.pose().popPose(); + } else { + gfx.renderItem(stack, entry.x(), entry.y()); + gfx.renderItemDecorations(Minecraft.getInstance().font, stack, entry.x(), entry.y()); + } + } } }); renderRegistered = true; @@ -1178,6 +1392,19 @@ private void registerFogListener() { // ── Draw text entry ── private record DrawTextEntry(int x, int y, String text, int color) {} + private record DrawItemEntry(int x, int y, String itemId, int scale) {} + private record DrawRectEntry(int x, int y, int w, int h, int color, int alpha) {} + + /** Parse a NativeObject { r, g, b } or { r, g, b, a } to ARGB int. */ + private static int argbToInt(Object raw) { + if (raw instanceof NativeObject c) { + int r = c.containsKey("r") ? ((Number) c.get("r", c)).intValue() & 0xFF : 255; + int g = c.containsKey("g") ? ((Number) c.get("g", c)).intValue() & 0xFF : 255; + int b = c.containsKey("b") ? ((Number) c.get("b", c)).intValue() & 0xFF : 255; + return (r << 16) | (g << 8) | b; + } + return 0xFFFFFF; + } // ── Console backend ── diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSCallbacks.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSCallbacks.java index 5ca2932..2ed4b72 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSCallbacks.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSCallbacks.java @@ -12,7 +12,7 @@ interface PlayerLeaveCallback { @FunctionalInterface interface VoxelDestroyCallback { - void onDestroy(Box3JSEntity entity, int x, int y, int z, String voxel, long tick); + Object onDestroy(Box3JSEntity entity, int x, int y, int z, String voxel, long tick); } @FunctionalInterface @@ -22,7 +22,7 @@ interface VoxelContactCallback { @FunctionalInterface interface InteractCallback { - void onInteract(Box3JSEntity entity, Box3JSEntity target, long tick); + Object onInteract(Box3JSEntity entity, Box3JSEntity target, long tick); } @FunctionalInterface @@ -52,7 +52,7 @@ interface EntitySeparateCallback { @FunctionalInterface interface BlockPlaceCallback { - void onPlace(Box3JSEntity entity, int x, int y, int z, String voxel, int voxelId, long tick); + Object onPlace(Box3JSEntity entity, int x, int y, int z, String voxel, int voxelId, long tick); } @FunctionalInterface @@ -67,12 +67,12 @@ interface PlayerRespawnCallback { @FunctionalInterface interface BlockActivateCallback { - void onActivate(Box3JSEntity entity, int x, int y, int z, String voxel, long tick); + Object onActivate(Box3JSEntity entity, int x, int y, int z, String voxel, long tick); } @FunctionalInterface interface EntityDamageCallback { - void onDamage(Box3JSEntity entity, double amount, String source, Box3JSEntity attacker, long tick); + Object onDamage(Box3JSEntity entity, double amount, String source, Box3JSEntity attacker, long tick); } @FunctionalInterface 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 5aba6f3..596fdc1 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 @@ -3,7 +3,9 @@ import net.minecraft.ChatFormatting; import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffect; @@ -19,6 +21,7 @@ import net.minecraft.world.scores.Scoreboard; import com.mojang.logging.LogUtils; import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import java.util.Map; @@ -210,6 +213,16 @@ public void hurt(double amount) { if (le != null) le.hurt(le.damageSources().generic(), (float) amount); } + /** hurt with a source entity (for kill tracking) */ + public void hurt(double amount, Box3JSEntity source) { + LivingEntity le = asLiving(); + if (le != null && source != null && source.getEntity() instanceof LivingEntity sle) { + le.hurt(le.damageSources().mobAttack(sle), (float) amount); + } else { + hurt(amount); + } + } + public void heal(double amount) { LivingEntity le = asLiving(); if (le != null) le.heal((float) amount); @@ -271,6 +284,16 @@ public void clearFire() { entity.setRemainingFireTicks(0); } + // ---- Teleport (MC extension) ---- + + public void teleportTo(double x, double y, double z) { + trackIfSandboxed(); + entity.teleportTo(x, y, z); + } + public void teleportTo(GameVector3 pos) { + teleportTo(pos.x, pos.y, pos.z); + } + // ---- Look at (MC extension) ---- public void lookAt(double x, double y, double z) { Box3ScriptUtils.lookAt(entity, x, y, z); } @@ -348,6 +371,31 @@ public void setEquipment(String slot, String itemId) { mob.setItemSlot(equipmentSlot, new ItemStack(item)); } + /** setEquipment with enchantments. enchants is a NativeObject like { "minecraft:sharpness": 5 } */ + public void setEquipmentWithEnchants(String slot, String itemId, NativeObject enchants) { + trackIfSandboxed(); + if (!(entity instanceof Mob mob)) return; + EquipmentSlot equipmentSlot = parseEquipmentSlot(slot); + if (equipmentSlot == null) return; + Item item = Box3ScriptUtils.lookupItem(itemId); + if (item == null) return; + ItemStack stack = new ItemStack(item); + if (enchants != null) { + var enchRegistry = entity.level().registryAccess().registryOrThrow(Registries.ENCHANTMENT); + for (Object key : enchants.keySet()) { + String enchId = key.toString(); + int level = ((Number) enchants.get(key)).intValue(); + ResourceLocation enchRl = ResourceLocation.tryParse(enchId); + if (enchRl == null) continue; + var holder = enchRegistry.getHolder(enchRl); + if (holder.isPresent()) { + stack.enchant(holder.get(), level); + } + } + } + mob.setItemSlot(equipmentSlot, stack); + } + // ---- Drop chances (MC extension) ---- public void setDropChance(String slot, double chance) { @@ -451,6 +499,11 @@ public void setAttribute(String attributeId, double value) { } } + /** Remove the entity from the world immediately. */ + public void remove() { + entity.discard(); + } + // ---- Lifecycle ---- public void destroy() { 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 a43de4b..9ba14c1 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 @@ -15,7 +15,12 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.server.level.ServerBossEvent; +import net.minecraft.world.BossEvent.BossBarColor; +import net.minecraft.world.BossEvent.BossBarOverlay; +import net.minecraft.world.item.alchemy.PotionContents; import net.minecraft.world.level.GameType; import com.mojang.logging.LogUtils; import org.mozilla.javascript.Function; @@ -495,6 +500,24 @@ public void clearInventory() { player.getInventory().clearContent(); } + /** Remove up to `count` items of the given type from the player's inventory. + * Returns the number of items actually removed. */ + public int removeItem(String itemId, int count) { + Item item = Box3ScriptUtils.lookupItem(itemId); + if (item == null) return 0; + var inv = player.getInventory(); + int remaining = count; + for (int i = 0; i < inv.getContainerSize() && remaining > 0; i++) { + var stack = inv.getItem(i); + if (stack.is(item)) { + int take = Math.min(remaining, stack.getCount()); + stack.shrink(take); + remaining -= take; + } + } + return count - remaining; + } + // ---- Advancements ---- public void grantAdvancement(String advancementId) { @@ -547,6 +570,98 @@ public void playSound(String path, double volume, double pitch) { } } + // ---- Bossbar (per-player) ---- + + private static final java.util.Map> playerBossbars = new java.util.HashMap<>(); + + public void showBossbar(String name, String text, double progress, String color) { + var bars = playerBossbars.computeIfAbsent(player.getUUID(), k -> new java.util.HashMap<>()); + ServerBossEvent bar = bars.get(name); + if (bar == null) { + bar = new ServerBossEvent(Component.literal(text), resolveBossBarColor(color), BossBarOverlay.PROGRESS); + bar.addPlayer(player); + bars.put(name, bar); + } else { + bar.setName(Component.literal(text)); + if (color != null) bar.setColor(resolveBossBarColor(color)); + } + bar.setProgress((float) Math.max(0, Math.min(1, progress))); + } + + public void removeBossbar(String name) { + var bars = playerBossbars.get(player.getUUID()); + if (bars == null) return; + ServerBossEvent bar = bars.remove(name); + if (bar != null) bar.removeAllPlayers(); + } + + private static BossBarColor resolveBossBarColor(String colorName) { + if (colorName == null) return BossBarColor.WHITE; + return switch (colorName.toLowerCase()) { + case "red" -> BossBarColor.RED; + case "blue" -> BossBarColor.BLUE; + case "green" -> BossBarColor.GREEN; + case "yellow" -> BossBarColor.YELLOW; + case "purple" -> BossBarColor.PURPLE; + case "pink" -> BossBarColor.PINK; + default -> BossBarColor.WHITE; + }; + } + + // ---- Hotbar ---- + + public void setHeldSlot(int slot) { + if (slot >= 0 && slot <= 8) { + player.getInventory().selected = slot; + } + } + + // ---- Potion ---- + + public void givePotion(String itemId, String potionType, int count) { + var item = Box3ScriptUtils.lookupItem(itemId); + if (item == null) return; + var stack = new ItemStack(item, Math.max(1, Math.min(count, 64))); + var potionRl = ResourceLocation.tryParse(potionType); + if (potionRl != null) { + var potionRegistry = player.server.registryAccess().registryOrThrow(Registries.POTION); + var holder = potionRegistry.getHolder(ResourceKey.create(Registries.POTION, potionRl)); + if (holder.isPresent()) { + stack.set(net.minecraft.core.component.DataComponents.POTION_CONTENTS, + new PotionContents(holder.get())); + } + } + player.getInventory().add(stack); + } + + // ---- Inventory Query ---- + + /** Number of empty slots in the player's main inventory (0–36). */ + public int getInventoryFreeSlots() { + int free = 0; + var inv = player.getInventory(); + for (int i = 0; i < 36; i++) { + if (inv.getItem(i).isEmpty()) free++; + } + return free; + } + + public boolean hasItem(String itemId) { + return getItemCount(itemId) > 0; + } + + public int getItemCount(String itemId) { + var item = Box3ScriptUtils.lookupItem(itemId); + if (item == null) return 0; + int count = 0; + var inv = player.getInventory(); + for (int i = 0; i < inv.getContainerSize(); i++) { + var stack = inv.getItem(i); + if (stack.is(item)) count += stack.getCount(); + } + return count; + } + // ---- Custom properties ---- private void trackIfSandboxed() { diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSVoxels.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSVoxels.java index 39ed0a7..ceaab92 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSVoxels.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSVoxels.java @@ -12,6 +12,8 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.DirectionProperty; +import org.mozilla.javascript.NativeObject; + import java.util.*; public class Box3JSVoxels { @@ -143,6 +145,47 @@ public int setVoxel(GameVector3 pos, Object voxel, Object rotation) { return setVoxel((int) pos.x, (int) pos.y, (int) pos.z, voxel, rotation); } + /** setVoxelState(x, y, z, blockId, state): number — place block with state properties */ + public int setVoxelState(int x, int y, int z, Object voxel, NativeObject state) { + ServerLevel level = server.overworld(); + BlockPos pos = new BlockPos(x, y, z); + + if (sandbox != null) sandbox.trackBlock(engine.getCurrentProject(), pos); + + if (isAir(voxel)) { + level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); + return 0; + } + + Block block = resolveBlock(voxel); + if (block == null) return 0; + + BlockState blockState = block.defaultBlockState(); + if (state != null) { + for (Object key : state.keySet()) { + String propName = key.toString(); + String propValue = state.get(key).toString(); + for (var prop : blockState.getProperties()) { + if (prop.getName().equals(propName)) { + @SuppressWarnings({"unchecked", "rawtypes"}) + var result = ((BlockState) blockState).setValue((net.minecraft.world.level.block.state.properties.Property) prop, + (Comparable) prop.getValue(propValue).orElse(null)); + if (result != null) blockState = result; + break; + } + } + } + } + + level.setBlock(pos, blockState, 3); + Integer baseId = blockToId.get(block); + return baseId != null ? baseId : 0; + } + /** setVoxelState(pos, blockId, state): number */ + public int setVoxelState(GameVector3 pos, Object voxel, NativeObject state) { + return setVoxelState((int) pos.x, (int) pos.y, (int) pos.z, voxel, state); + } + /** fillVoxel(x1, y1, z1, x2, y2, z2, voxel): void — fill a region */ public void fillVoxel(int x1, int y1, int z1, int x2, int y2, int z2, Object voxel) { int minX = Math.min(x1, x2), maxX = Math.max(x1, x2); @@ -160,6 +203,71 @@ public void fillVoxel(int x1, int y1, int z1, int x2, int y2, int z2, Object vox public void fillVoxel(GameVector3 pos1, GameVector3 pos2, Object voxel) { fillVoxel((int) pos1.x, (int) pos1.y, (int) pos1.z, (int) pos2.x, (int) pos2.y, (int) pos2.z, voxel); } + /** fillVoxel(bounds, voxel): void */ + public void fillVoxel(GameBounds3 bounds, Object voxel) { + fillVoxel(bounds.lo, bounds.hi, voxel); + } + + /** replace(x1, y1, z1, x2, y2, z2, fromBlock, toBlock): void — replace blocks in a region */ + public void replace(int x1, int y1, int z1, int x2, int y2, int z2, Object fromBlock, Object toBlock) { + Block from = resolveBlock(fromBlock); + Block to = resolveBlock(toBlock); + if (from == null || to == null) return; + int minX = Math.min(x1, x2), maxX = Math.max(x1, x2); + int minY = Math.min(y1, y2), maxY = Math.max(y1, y2); + int minZ = Math.min(z1, z2), maxZ = Math.max(z1, z2); + var level = server.overworld(); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + BlockPos pos = new BlockPos(x, y, z); + if (level.getBlockState(pos).getBlock() == from) { + setVoxel(x, y, z, toBlock); + } + } + } + } + } + /** replace(pos1, pos2, fromBlock, toBlock): void */ + public void replace(GameVector3 pos1, GameVector3 pos2, Object fromBlock, Object toBlock) { + replace((int) pos1.x, (int) pos1.y, (int) pos1.z, (int) pos2.x, (int) pos2.y, (int) pos2.z, fromBlock, toBlock); + } + /** replace(bounds, fromBlock, toBlock): void */ + public void replace(GameBounds3 bounds, Object fromBlock, Object toBlock) { + replace(bounds.lo, bounds.hi, fromBlock, toBlock); + } + + /** clone(x1, y1, z1, x2, y2, z2, destX, destY, destZ): void — copy a block region */ + public void clone(int x1, int y1, int z1, int x2, int y2, int z2, int destX, int destY, int destZ) { + int minX = Math.min(x1, x2), maxX = Math.max(x1, x2); + int minY = Math.min(y1, y2), maxY = Math.max(y1, y2); + int minZ = Math.min(z1, z2), maxZ = Math.max(z1, z2); + var level = server.overworld(); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + BlockState state = level.getBlockState(new BlockPos(x, y, z)); + int dx = destX + (x - minX); + int dy = destY + (y - minY); + int dz = destZ + (z - minZ); + if (sandbox != null) sandbox.trackBlock(engine.getCurrentProject(), new BlockPos(dx, dy, dz)); + level.setBlock(new BlockPos(dx, dy, dz), state, 3); + } + } + } + } + /** clone(pos1, pos2, destPos): void */ + public void clone(GameVector3 pos1, GameVector3 pos2, GameVector3 destPos) { + clone((int) pos1.x, (int) pos1.y, (int) pos1.z, (int) pos2.x, (int) pos2.y, (int) pos2.z, (int) destPos.x, (int) destPos.y, (int) destPos.z); + } + /** clone(bounds, destX, destY, destZ): void */ + public void clone(GameBounds3 bounds, int destX, int destY, int destZ) { + clone((int) bounds.lo.x, (int) bounds.lo.y, (int) bounds.lo.z, (int) bounds.hi.x, (int) bounds.hi.y, (int) bounds.hi.z, destX, destY, destZ); + } + /** clone(bounds, destPos): void */ + public void clone(GameBounds3 bounds, GameVector3 destPos) { + clone(bounds.lo, bounds.hi, destPos); + } /** countVoxel(x1, y1, z1, x2, y2, z2, voxel): number — count matching blocks in region */ public int countVoxel(int x1, int y1, int z1, int x2, int y2, int z2, Object voxel) { @@ -186,6 +294,10 @@ public int countVoxel(int x1, int y1, int z1, int x2, int y2, int z2, Object vox public int countVoxel(GameVector3 pos1, GameVector3 pos2, Object voxel) { return countVoxel((int) pos1.x, (int) pos1.y, (int) pos1.z, (int) pos2.x, (int) pos2.y, (int) pos2.z, voxel); } + /** countVoxel(bounds, voxel): number */ + public int countVoxel(GameBounds3 bounds, Object voxel) { + return countVoxel(bounds.lo, bounds.hi, voxel); + } /** setVoxelId(x, y, z, voxel: number): number — rotation already encoded in the ID */ public int setVoxelId(int x, int y, int z, int voxel) { 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 0de8a8e..5318613 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 @@ -556,6 +556,7 @@ public void dropItem(GameVector3 pos, String itemId, int count) { public Object raycast(GameVector3 origin, GameVector3 direction) { return query.raycast(origin, direction); } public Object raycast(GameVector3 origin, GameVector3 direction, double maxDistance) { return query.raycast(origin, direction, maxDistance); } public List entitiesInArea(GameVector3 pos1, GameVector3 pos2) { return query.entitiesInArea(pos1, pos2); } + public List entitiesInArea(GameBounds3 bounds) { return query.entitiesInArea(bounds.lo, bounds.hi); } public List entitiesInRadius(double x, double y, double z, double radius) { return query.entitiesInRadius(x, y, z, radius); } public List entitiesInRadius(GameVector3 pos, double radius) { return query.entitiesInRadius(pos, radius); } public String getBiome(int x, int y, int z) { return query.getBiome(x, y, z); } 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 96d15f1..2e52427 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 @@ -234,8 +234,11 @@ public Runnable addLeaveCallback(PlayerLeaveCallback cb) { public Runnable addVoxelDestroyCallback(VoxelDestroyCallback cb) { String project = currentProject; - VoxelDestroyCallback wrapped = (e, x, y, z, v, t) -> runInContext(project, - () -> cb.onDestroy(e, x, y, z, v, t)); + VoxelDestroyCallback wrapped = (e, x, y, z, v, t) -> { + java.util.concurrent.atomic.AtomicReference result = new java.util.concurrent.atomic.AtomicReference<>(); + runInContext(project, () -> result.set(cb.onDestroy(e, x, y, z, v, t))); + return result.get(); + }; bus.addVoxelDestroy(project, wrapped); return () -> bus.removeVoxelDestroy(project, wrapped); } @@ -250,7 +253,11 @@ public Runnable addVoxelContactCallback(VoxelContactCallback cb) { public Runnable addInteractCallback(InteractCallback cb) { String project = currentProject; - InteractCallback wrapped = (e, tgt, tick) -> runInContext(project, () -> cb.onInteract(e, tgt, tick)); + InteractCallback wrapped = (e, tgt, tick) -> { + java.util.concurrent.atomic.AtomicReference result = new java.util.concurrent.atomic.AtomicReference<>(); + runInContext(project, () -> result.set(cb.onInteract(e, tgt, tick))); + return result.get(); + }; bus.addInteract(project, wrapped); return () -> bus.removeInteract(project, wrapped); } @@ -296,8 +303,11 @@ public Runnable addEntitySeparateCallback(EntitySeparateCallback cb) { public Runnable addBlockPlaceCallback(BlockPlaceCallback cb) { String project = currentProject; - BlockPlaceCallback wrapped = (e, x, y, z, v, vid, t) -> runInContext(project, - () -> cb.onPlace(e, x, y, z, v, vid, t)); + BlockPlaceCallback wrapped = (e, x, y, z, v, vid, t) -> { + java.util.concurrent.atomic.AtomicReference result = new java.util.concurrent.atomic.AtomicReference<>(); + runInContext(project, () -> result.set(cb.onPlace(e, x, y, z, v, vid, t))); + return result.get(); + }; bus.addBlockPlace(project, wrapped); return () -> bus.removeBlockPlace(project, wrapped); } @@ -318,15 +328,22 @@ public Runnable addRespawnCallback(PlayerRespawnCallback cb) { public Runnable addBlockActivateCallback(BlockActivateCallback cb) { String project = currentProject; - BlockActivateCallback wrapped = (e, x, y, z, v, t) -> runInContext(project, - () -> cb.onActivate(e, x, y, z, v, t)); + BlockActivateCallback wrapped = (e, x, y, z, v, t) -> { + java.util.concurrent.atomic.AtomicReference result = new java.util.concurrent.atomic.AtomicReference<>(); + runInContext(project, () -> result.set(cb.onActivate(e, x, y, z, v, t))); + return result.get(); + }; bus.addBlockActivate(project, wrapped); return () -> bus.removeBlockActivate(project, wrapped); } public Runnable addEntityDamageCallback(EntityDamageCallback cb) { String project = currentProject; - EntityDamageCallback wrapped = (e, a, s, at, t) -> runInContext(project, () -> cb.onDamage(e, a, s, at, t)); + EntityDamageCallback wrapped = (e, a, s, at, t) -> { + java.util.concurrent.atomic.AtomicReference result = new java.util.concurrent.atomic.AtomicReference<>(); + runInContext(project, () -> result.set(cb.onDamage(e, a, s, at, t))); + return result.get(); + }; bus.addEntityDamage(project, wrapped); return () -> bus.removeEntityDamage(project, wrapped); } @@ -676,7 +693,9 @@ private String getBlockIdString(BlockPos pos) { return key != null ? key.toString() : "minecraft:air"; } - public void fireVoxelDestroy(ServerPlayer player, BlockPos pos) { + /** @return true if any destroy callback returned false to cancel */ + public boolean fireVoxelDestroy(ServerPlayer player, BlockPos pos) { + java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); String voxel = null; long tick = -1; Box3JSEntity entity = null; @@ -692,16 +711,24 @@ public void fireVoxelDestroy(ServerPlayer player, BlockPos pos) { long t = tick; String v = voxel; runInContext(entry.getKey(), () -> { - for (var cb : entry.getValue()) - cb.onDestroy(e, pos.getX(), pos.getY(), pos.getZ(), v, t); + for (var cb : entry.getValue()) { + Object result = cb.onDestroy(e, pos.getX(), pos.getY(), pos.getZ(), v, t); + if (result instanceof Boolean && !((Boolean) result)) { + cancelled.set(true); + } + } }); } - String s = worldBinding.getBreakVoxelSound(); - if (s != null && !s.isEmpty()) - worldBinding.playSound(s, pos.getX(), pos.getY(), pos.getZ(), 1.0, 1.0); + if (!cancelled.get()) { + String s = worldBinding.getBreakVoxelSound(); + if (s != null && !s.isEmpty()) + worldBinding.playSound(s, pos.getX(), pos.getY(), pos.getZ(), 1.0, 1.0); + } + return cancelled.get(); } - public void fireInteract(ServerPlayer player, net.minecraft.world.entity.Entity target) { + public boolean fireInteract(ServerPlayer player, net.minecraft.world.entity.Entity target) { + java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); Box3JSEntity entity = null; Box3JSEntity targetEntity = null; long tick = -1; @@ -717,10 +744,15 @@ public void fireInteract(ServerPlayer player, net.minecraft.world.entity.Entity Box3JSEntity te = targetEntity; long t = tick; runInContext(entry.getKey(), () -> { - for (var cb : entry.getValue()) - cb.onInteract(e, te, t); + for (var cb : entry.getValue()) { + Object result = cb.onInteract(e, te, t); + if (result instanceof Boolean && !((Boolean) result)) { + cancelled.set(true); + } + } }); } + return cancelled.get(); } /** @return true if any chat callback returned false to cancel */ @@ -759,7 +791,9 @@ public boolean fireChat(ServerPlayer player, String message) { return cancelled.get(); } - public void fireBlockPlace(ServerPlayer player, BlockPos pos, BlockState state) { + /** @return true if any place callback returned false to cancel */ + public boolean fireBlockPlace(ServerPlayer player, BlockPos pos, BlockState state) { + java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); Box3JSEntity entity = null; long tick = -1; int voxelId = -1; @@ -779,13 +813,20 @@ public void fireBlockPlace(ServerPlayer player, BlockPos pos, BlockState state) int vid = voxelId; String v = voxel; runInContext(entry.getKey(), () -> { - for (var cb : entry.getValue()) - cb.onPlace(e, pos.getX(), pos.getY(), pos.getZ(), v, vid, t); + for (var cb : entry.getValue()) { + Object result = cb.onPlace(e, pos.getX(), pos.getY(), pos.getZ(), v, vid, t); + if (result instanceof Boolean && !((Boolean) result)) { + cancelled.set(true); + } + } }); } - String s = worldBinding.getPlaceVoxelSound(); - if (s != null && !s.isEmpty()) - worldBinding.playSound(s, pos.getX(), pos.getY(), pos.getZ(), 1.0, 1.0); + if (!cancelled.get()) { + String s = worldBinding.getPlaceVoxelSound(); + if (s != null && !s.isEmpty()) + worldBinding.playSound(s, pos.getX(), pos.getY(), pos.getZ(), 1.0, 1.0); + } + return cancelled.get(); } public void fireEntityDeath(net.minecraft.world.entity.Entity deadEntity, @@ -830,7 +871,8 @@ public void firePlayerRespawn(ServerPlayer player) { } } - public void fireBlockActivate(ServerPlayer player, BlockPos pos, BlockState state) { + public boolean fireBlockActivate(ServerPlayer player, BlockPos pos, BlockState state) { + java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); Box3JSEntity entity = null; long tick = -1; String voxel = null; @@ -847,14 +889,20 @@ public void fireBlockActivate(ServerPlayer player, BlockPos pos, BlockState stat long t = tick; String v = voxel; runInContext(entry.getKey(), () -> { - for (var cb : entry.getValue()) - cb.onActivate(e, pos.getX(), pos.getY(), pos.getZ(), v, t); + for (var cb : entry.getValue()) { + Object result = cb.onActivate(e, pos.getX(), pos.getY(), pos.getZ(), v, t); + if (result instanceof Boolean && !((Boolean) result)) { + cancelled.set(true); + } + } }); } + return cancelled.get(); } - public void fireEntityDamage(net.minecraft.world.entity.Entity damagedEntity, double amount, String source, + public boolean fireEntityDamage(net.minecraft.world.entity.Entity damagedEntity, double amount, String source, net.minecraft.world.entity.Entity attacker) { + java.util.concurrent.atomic.AtomicBoolean cancelled = new java.util.concurrent.atomic.AtomicBoolean(false); Box3JSEntity entity = null; Box3JSEntity attackerEntity = null; long tick = -1; @@ -870,10 +918,15 @@ public void fireEntityDamage(net.minecraft.world.entity.Entity damagedEntity, do Box3JSEntity ae = attackerEntity; long t = tick; runInContext(entry.getKey(), () -> { - for (var cb : entry.getValue()) - cb.onDamage(e, amount, source, ae, t); + for (var cb : entry.getValue()) { + Object result = cb.onDamage(e, amount, source, ae, t); + if (result instanceof Boolean && !((Boolean) result)) { + cancelled.set(true); + } + } }); } + return cancelled.get(); } public void firePlayerJoin(ServerPlayer player) { 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 f95be0b..6608283 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 @@ -100,6 +100,48 @@ public GameBounds3 moveEq(GameVector3 offset) { return this; } + public double volume() { + return (hi.x - lo.x) * (hi.y - lo.y) * (hi.z - lo.z); + } + + public boolean isEmpty() { + return hi.x <= lo.x || hi.y <= lo.y || hi.z <= lo.z; + } + + public boolean equals(GameBounds3 b) { + if (b == null) return false; + return lo.x == b.lo.x && lo.y == b.lo.y && lo.z == b.lo.z && + hi.x == b.hi.x && hi.y == b.hi.y && hi.z == b.hi.z; + } + + public GameBounds3 union(GameBounds3 b) { + if (b == null) return new GameBounds3(new GameVector3(lo.x, lo.y, lo.z), new GameVector3(hi.x, hi.y, hi.z)); + return new GameBounds3( + new GameVector3(Math.min(lo.x, b.lo.x), Math.min(lo.y, b.lo.y), Math.min(lo.z, b.lo.z)), + new GameVector3(Math.max(hi.x, b.hi.x), Math.max(hi.y, b.hi.y), Math.max(hi.z, b.hi.z))); + } + + public GameBounds3 inflate(double amount) { + return new GameBounds3( + new GameVector3(lo.x - amount, lo.y - amount, lo.z - amount), + new GameVector3(hi.x + amount, hi.y + amount, hi.z + amount)); + } + + public GameBounds3 deflate(double amount) { + double hw = (hi.x - lo.x) / 2; + double hh = (hi.y - lo.y) / 2; + double hd = (hi.z - lo.z) / 2; + double cx = (lo.x + hi.x) / 2; + double cy = (lo.y + hi.y) / 2; + double cz = (lo.z + hi.z) / 2; + double nw = Math.max(0, hw - amount); + double nh = Math.max(0, hh - amount); + double nd = Math.max(0, hd - amount); + return new GameBounds3( + new GameVector3(cx - nw, cy - nh, cz - nd), + new GameVector3(cx + nw, cy + nh, cz + nd)); + } + 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/resources/assets/box3js/template/types/client/client.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/client.d.ts index 200de8d..6c689e6 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/client.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/client.d.ts @@ -132,6 +132,40 @@ interface GameClient { * @en Resets fog to Minecraft's default behaviour. */ resetFog(): void; + + // ── Camera control ── + + /** + * @zh 将相机切换到目标实体的视角(旁观模式效果)。 + * @en Switches the camera to spectate the given entity (spectator-style view). + * @param entityUuid - @zh 目标实体的 UUID 字符串 @en the target entity's UUID string + */ + setCameraTarget(entityUuid: string): void; + + /** + * @zh 重置相机到本地玩家视角。 + * @en Resets the camera back to the local player. + */ + resetCamera(): void; + + // ── Block query ── + + /** + * @zh 在客户端查询指定坐标的方块状态(从渲染区块缓存读取)。 + * @en Queries the block state at a position from the client's render chunk cache. + * @returns @zh `{ id: string }` 或空气/null 时返回 null @en `{ id: string }` or null for air/unloaded + */ + getBlockState(x: number, y: number, z: number): { id: string } | null; + + // ── Render callback ── + + /** + * @zh 注册每 Render 帧回调(仅在 HUD 渲染阶段触发)。 + * @en Registers a callback invoked every render frame (during HUD render phase). + * @param callback - @zh 接收 partialTick(0‑1)的回调 @en callback receiving partialTick (0–1) + * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe + */ + onRender(callback: (partialTick: number) => void): GameEventHandlerToken; } // ── §7 @zh 全局声明(客户端) @en Global Declarations (client) ── diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/input.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/input.d.ts index 105733d..57b56ab 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/input.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/input.d.ts @@ -68,4 +68,18 @@ interface GameInput { y: number, ) => void, ): GameEventHandlerToken; + + /** + * @zh 获取当前帧的鼠标 X 轴增量 (自上一帧的移动量)。 + * @en Gets the mouse X delta since the last frame. + * @returns @zh 鼠标 X 位移量 @en mouse X displacement + */ + getMouseDeltaX(): number; + + /** + * @zh 获取当前帧的鼠标 Y 轴增量 (自上一帧的移动量)。 + * @en Gets the mouse Y delta since the last frame. + * @returns @zh 鼠标 Y 位移量 @en mouse Y displacement + */ + getMouseDeltaY(): number; } diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/ui.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/ui.d.ts index 8d54cc9..467d670 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/ui.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/ui.d.ts @@ -77,4 +77,60 @@ interface GameUI { * @en Clears all texts drawn via `drawText()`. */ clearDrawTexts(): void; + + // ── Item icon drawing ── + + /** + * @zh 在屏幕上绘制物品图标 (每帧绘制, 直到被移除)。 + * @en Draws an item icon on screen (drawn every frame until removed). + * @param id - @zh 图标 ID (用于后续移除), 不传则自动生成 @en icon ID for later removal; auto-generated if omitted + * @param x - @zh X 坐标 (GUI 缩放坐标) @en X position (GUI-scaled coordinates) + * @param y - @zh Y 坐标 (GUI 缩放坐标) @en Y position (GUI-scaled coordinates) + * @param itemId - @zh 物品 ID (如 "minecraft:diamond") @en item ID (e.g. "minecraft:diamond") + * @param scale - @zh 图标尺寸 (像素, 默认 16) @en icon size in pixels (default 16) + * @returns @zh 图标 ID @en the icon ID + */ + drawItem( + id: number, + x: number, + y: number, + itemId: string, + scale?: number, + ): number; + + /** + * @zh 移除指定 ID 的绘制图标。 + * @en Removes the drawn item icon with the given ID. + */ + removeDrawItem(id: number): void; + + // ── Rectangle drawing ── + + /** + * @zh 在屏幕上绘制矩形 (每帧绘制, 直到被移除)。 + * @en Draws a filled rectangle on screen (drawn every frame until removed). + * @param id - @zh 矩形 ID (用于后续移除) @en rect ID for later removal + * @param x - @zh 左上角 X 坐标 @en top-left X + * @param y - @zh 左上角 Y 坐标 @en top-left Y + * @param w - @zh 宽度 (像素) @en width in pixels + * @param h - @zh 高度 (像素) @en height in pixels + * @param color - @zh 颜色 (GameRGBColor) @en colour (GameRGBColor) + * @param alpha - @zh 透明度 0‑255 (可选, 默认 255) @en alpha 0–255 (optional, default 255) + * @returns @zh 矩形 ID @en the rect ID + */ + drawRect( + id: number, + x: number, + y: number, + w: number, + h: number, + color: GameRGBColor, + alpha?: number, + ): number; + + /** + * @zh 移除指定 ID 的绘制矩形。 + * @en Removes the drawn rectangle with the given ID. + */ + removeDrawRect(id: number): void; } diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts index 0ae1bf6..f494cc2 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts @@ -100,6 +100,14 @@ interface GameEntity { */ hurt(amount: number): void; + /** + * @zh 对实体造成伤害 (带伤害来源, 用于击杀追踪)。 + * @en Deals damage to the entity with a source entity (for kill tracking). + * @param amount - @zh 伤害值(半心) @en damage amount in half‑hearts + * @param source - @zh 伤害来源实体 @en the entity that dealt the damage + */ + hurt(amount: number, source: GameEntity): void; + /** * @zh 治疗实体。 * @en Heals the entity. @@ -251,6 +259,19 @@ interface GameEntity { */ setEquipment(slot: string, itemId: string): void; + /** + * @zh 给生物设置带附魔的装备。 + * @en Equips an enchanted item onto a mob's equipment slot. + * @param slot - @zh 槽位名称 (如 "mainhand", "head") @en slot name (e.g. "mainhand", "head") + * @param itemId - @zh 物品 ID @en item ID (e.g. "minecraft:diamond_sword") + * @param enchants - @zh 附魔对象 @en enchantment map (e.g. { "minecraft:sharpness": 5 }) + */ + setEquipmentWithEnchants( + slot: string, + itemId: string, + enchants: Record, + ): void; + /** * @zh 设置装备掉落概率。 * @en Sets the drop chance for an equipment slot. @@ -293,6 +314,21 @@ interface GameEntity { */ setAI(enabled: boolean): void; + /** + * @zh 立即从世界中移除实体 (不触发 destroy 回调)。 + * @en Immediately removes the entity from the world (does not trigger the destroy callback). + */ + remove(): void; + + // ── @zh 传送 @en Teleport ── + + /** + * @zh 将实体传送到指定坐标。 + * @en Teleports the entity to the given coordinates. + */ + teleportTo(x: number, y: number, z: number): void; + teleportTo(pos: GameVector3): void; + // ── @zh 朝向 @en Look direction ── /** diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/player.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/player.d.ts index a3f8176..b2204d9 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/player.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/player.d.ts @@ -367,6 +367,15 @@ interface GamePlayer { /** @zh 清空背包 @en Clears the player's inventory. */ clearInventory(): void; + /** + * @zh 从背包中移除指定数量的物品。 + * @en Removes a specified count of the given item from the player's inventory. + * @param itemId - @zh 物品 ID @en item ID (e.g. "minecraft:diamond") + * @param count - @zh 要移除的数量 @en count to remove + * @returns @zh 实际移除的数量 @en actual number of items removed + */ + removeItem(itemId: string, count: number): number; + /** @zh 管理员权限等级 (0-4)。0=普通玩家, 4=最高权限 @en Server operator permission level (0–4). */ opLevel: number; @@ -416,6 +425,68 @@ interface GamePlayer { ) => boolean | void, ): GameEventHandlerToken; + // ── @zh 快捷栏 @en Hotbar ── + + /** + * @zh 设置玩家手持快捷栏槽位 (0‑8)。 + * @en Sets the player's selected hotbar slot (0–8). + */ + setHeldSlot(slot: number): void; + + // ── @zh 物品栏查询 @en Inventory Query ── + + /** + * @zh 背包空余槽位数 (0‑36, 只读)。 + * @en Number of empty slots in the player's main inventory (0–36), readonly. + */ + readonly inventoryFreeSlots: number; + + /** + * @zh 检查背包中是否拥有某物品。 + * @en Checks whether the player has at least one of the given item in inventory. + */ + hasItem(itemId: string): boolean; + + /** + * @zh 统计背包中某物品的数量。 + * @en Counts the total number of the given item in the player's inventory. + */ + getItemCount(itemId: string): number; + + // ── @zh 药水 @en Potions ── + + /** + * @zh 给予玩家指定类型的药水 (带有 PotionContents 组件, 适用于 1.21.1+)。 + * @en Gives the player a potion with a PotionContents component (1.21.1+). + * @param itemId - @zh 药水物品 ID (如 "minecraft:potion") @en potion item ID (e.g. "minecraft:potion") + * @param potionType - @zh 药水效果 ID (如 "minecraft:healing") @en potion effect ID (e.g. "minecraft:healing") + * @param count - @zh 数量 @en count + */ + givePotion(itemId: string, potionType: string, count: number): void; + + // ── @zh Bossbar @en Bossbar ── + + /** + * @zh 向该玩家显示一个独立的 Bossbar。 + * @en Shows a per‑player bossbar (adds the player to a ServerBossEvent). + * @param name - @zh Bossbar 唯一标识名 @en unique bossbar identifier + * @param text - @zh 显示文字 @en display text + * @param progress - @zh 进度 (0‑1) @en progress (0–1) + * @param color - @zh 颜色 ("pink"/"blue"/"red"/"green"/"yellow"/"purple"/"white") @en bar color + */ + showBossbar( + name: string, + text: string, + progress: number, + color: string, + ): void; + + /** + * @zh 移除该玩家的一个 Bossbar。 + * @en Removes a per‑player bossbar by name. + */ + removeBossbar(name: string): void; + // ── @zh 成就 @en Advancements ── /** diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/voxels.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/voxels.d.ts index fa87ed1..324cfac 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/voxels.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/voxels.d.ts @@ -127,6 +127,59 @@ interface GameVoxels { voxel: string | number, ): void; fillVoxel(pos1: GameVector3, pos2: GameVector3, voxel: string | number): void; + fillVoxel(bounds: GameBounds3, voxel: string | number): void; + + /** + * @zh 将区域内所有 fromBlock 替换为 toBlock。 + * @en Replaces all occurrences of fromBlock with toBlock within the region. + * @param fromBlock - @zh 被替换的方块名称或 ID @en the block to replace + * @param toBlock - @zh 替换后的方块名称或 ID @en the replacement block + */ + replace( + x1: number, + y1: number, + z1: number, + x2: number, + y2: number, + z2: number, + fromBlock: string | number, + toBlock: string | number, + ): void; + replace( + pos1: GameVector3, + pos2: GameVector3, + fromBlock: string | number, + toBlock: string | number, + ): void; + replace( + bounds: GameBounds3, + fromBlock: string | number, + toBlock: string | number, + ): void; + + /** + * @zh 将源区域的所有方块复制到目标位置 (包含方块状态和旋转)。 + * @en Copies all blocks from the source region to the destination (preserving block state and rotation). + * @param destX, destY, destZ - @zh 目标区域最小角坐标 @en minimum corner of the destination region + */ + clone( + x1: number, + y1: number, + z1: number, + x2: number, + y2: number, + z2: number, + destX: number, + destY: number, + destZ: number, + ): void; + clone( + pos1: GameVector3, + pos2: GameVector3, + destPos: GameVector3, + ): void; + clone(bounds: GameBounds3, destX: number, destY: number, destZ: number): void; + clone(bounds: GameBounds3, destPos: GameVector3): void; /** * @zh 统计区域内指定方块的数量。 @@ -146,6 +199,29 @@ interface GameVoxels { pos2: GameVector3, voxel: string | number, ): number; + countVoxel(bounds: GameBounds3, voxel: string | number): number; + + // ── @zh 带状态的方块放置 @en Stateful block placement ── + + /** + * @zh 放置方块并指定方块状态属性 (如 facing, half, waterlogged 等)。 + * @en Places a block with specific block state properties (e.g. facing, half, waterlogged). + * @param voxel - @zh 方块名称或数字 ID @en block name or numeric ID + * @param state - @zh 状态属性对象 (如 { "facing": "north", "half": "top" }) @en state properties map (e.g. { "facing": "north" }) + * @returns @zh 基础方块 ID @en base block ID (without rotation encoding) + */ + setVoxelState( + x: number, + y: number, + z: number, + voxel: string | number, + state: Record, + ): number; + setVoxelState( + pos: GameVector3, + voxel: string | number, + state: Record, + ): number; // ── @zh 刷怪笼 @en Spawner ── diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts index fa8a6d7..1e5467a 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts @@ -1,146 +1,96 @@ /// /// -// ── §6 @zh 世界 API @en World ── +// ── §2 @zh 世界 @en World ── /** - * @zh 世界控制与事件 — 脚本中通过 `world` 访问。 - * @en World control & events — accessed via `world` in scripts. + * @zh 世界接口,通过 `world` 访问。 + * @en World interface — accessed via `world`. */ interface GameWorld { - // ── @zh 世界属性 @en World properties ── + // ── @zh 基本信息 @en Identity ── - /** @zh 当前脚本项目名称 (只读方法) @en Current script project name, readonly method. */ + /** @zh 项目名称 (只读方法) @en Project name, call as method. */ projectName(): string; - /** @zh 服务器 MOTD/标识符 (可读写) @en Server MOTD/identifier, read/write. */ + /** @zh 当前 tick 数 (只读方法) @en Current server tick, call as method. */ + currentTick(): number; + + /** @zh 服务器 MOTD / 标识 @en Server MOTD / identifier. */ serverId: string; - /** @zh 当前服务端 tick 计数 (只读方法) @en Current server tick count, readonly method. */ - currentTick(): number; + // ── @zh 天气 @en Weather ── - /** - * @zh 降雨强度 (0‑1)。 - * @en Rain density (0–1). - */ + /** @zh 降雨密度 (0‑1) @en Rain density (0–1). */ rainDensity: number; - /** - * @zh 雷暴强度 (0‑1)。 - * @en Thunder density (0–1). - */ + /** @zh 雷暴密度 (0‑1) @en Thunder density (0–1). */ thunderDensity: number; - /** @zh 清除天气 (晴天) @en Clears weather to clear skies. */ + /** @zh 清除所有天气 @en Clears rain and thunder. */ clearWeather(): void; // ── @zh 时间 @en Time ── - /** - * @zh 当前游戏内时间 (tick, 0‑24000)。 - * @en Current in‑game time in ticks (0–24000). - */ + /** @zh 世界时间 (tick) @en World day time in ticks. */ time: number; /** - * @zh 时间流速 (1=正常, 0=停止)。 - * @en Time scale (1 = normal, 0 = frozen). + * @zh 设置世界时间。 + * @en Sets the world day time. */ - timeScale: number; + setTime(tick: number): void; - /** - * @zh 设置游戏内时间 (tick, 0‑24000)。 - * @en Sets the in-game time in ticks. - * @param time - 0=黎明, 6000=正午, 12000=黄昏, 18000=午夜 - */ - setTime(time: number): void; + /** @zh 时间流速 (1.0 = 正常, 0 = 冻结) @en Time scale (1.0 = normal, 0 = frozen). */ + timeScale: number; // ── @zh 难度 @en Difficulty ── - /** - * @zh 当前难度。 - * @en Current difficulty ("peaceful" | "easy" | "normal" | "hard"). - */ + /** @zh 游戏难度 ("peaceful" / "easy" / "normal" / "hard") @en Game difficulty string. */ difficulty: string; - // ── @zh 出生点 @en Spawn ── + // ── @zh 游戏规则 @en Game Rules ── /** - * @zh 世界出生点坐标。 - * @en World spawn point coordinates. + * @zh 获取游戏规则值。 + * @en Gets a game rule value. + * @param name - 规则名 (如 "doDaylightCycle") + * @returns 布尔值或 null */ - readonly spawnPoint: GameVector3; + getGameRule(name: string): boolean | null; /** - * @zh 设置世界出生点。 - * @en Sets the world spawn point. + * @zh 设置游戏规则值。 + * @en Sets a game rule value. + * @param name - 规则名 + * @param value - 新值 (布尔) */ - setWorldSpawn(pos: GameVector3): void; + setGameRule(name: string, value: boolean): void; - // ── @zh 游戏规则 (MC 扩展) @en Game Rules (MC extension) ── + // ── @zh 出生点 @en Spawn ── - /** - * @zh 读取游戏规则。 - * @en Reads a game‑rule value. - * @param name - @zh 规则名 @en rule name (see setGameRule for the list) - */ - getGameRule(name: string): boolean | null; + /** @zh 世界出生点 (只读) @en World spawn point, readonly. */ + readonly spawnPoint: GameVector3; /** - * @zh 设置游戏规则。 - * @en Sets a game rule. - * @param name - supported: doDaylightCycle | doWeatherCycle | keepInventory | - * doMobSpawning | doFireTick | mobGriefing | doImmediateRespawn - * @param value - boolean or string "true"/"false" + * @zh 设置世界出生点。 + * @en Sets the world spawn point. */ - setGameRule(name: string, value: boolean | string): void; - - // ── @zh 音效属性 @en Sound Properties ── - - /** @zh 环境音效路径 (每 200 tick 在世界出生点自动播放, 0.3 音量) @en Ambient sound — auto-plays at world spawn every 200 ticks at 0.3 volume. */ - ambientSound: string; - - /** @zh 玩家加入音效路径 (玩家加入时自动播放) @en Player join sound — auto-plays when a player joins. */ - playerJoinSound: string; - - /** @zh 玩家离开音效路径 (玩家离开时自动播放) @en Player leave sound — auto-plays when a player leaves. */ - playerLeaveSound: string; - - /** @zh 方块放置音效路径 (放置方块时自动播放) @en Block place sound — auto-plays when a block is placed. */ - placeVoxelSound: string; - - /** @zh 方块破坏音效路径 (破坏方块时自动播放) @en Block break sound — auto-plays when a block is broken. */ - breakVoxelSound: string; + setWorldSpawn(pos: GameVector3): void; // ── @zh 实体生成 @en Entity Spawning ── /** - * @zh 在指定位置生成实体。 + * @zh 在指定坐标生成实体。 * @en Spawns an entity at the given position. - * @param type - 实体类型 ID (如 "minecraft:zombie") - * @param pos - 生成坐标 - * @returns @zh 生成的实体包装,失败返回 null @en The spawned entity wrapper, or null on failure + * @returns GameEntity 或 null */ spawnEntity(type: string, pos: GameVector3): GameEntity | null; /** - * @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 + * @zh 按配置对象创建实体。 + * @en Creates an entity from a config object. + * @param config - { type, position, velocity?, fixed?, gravity?, friction?, mass?, restitution?, collides?, meshInvisible?, hp?, maxHp?, tags? } */ createEntity(config: { type?: string; @@ -158,772 +108,338 @@ interface GameWorld { tags?: string[]; }): GameEntity | null; - // ── @zh 消息 & 声音 @en Broadcasting ── + // ── @zh 实体查询 @en Entity Query ── /** - * @zh 向全服广播消息。 - * @en Sends a chat message to all players. + * @zh 查询所有匹配选择器的实体。 + * @en Queries all entities matching the selector. */ - say(message: string): void; - - // ── @zh 结构 & 成就 @en Structure & Advancement ── + querySelectorAll(selector: string): GameEntity[]; /** - * @zh 在指定位置放置数据包中的 .nbt 结构。 - * @en Places an .nbt structure from current datapacks at the given position. - * Structure must exist under data//structure/.nbt + * @zh 查询第一个匹配选择器的实体。 + * @en Queries the first entity matching the selector. */ - placeStructure(x: number, y: number, z: number, structureId: string): void; - placeStructure(pos: GameVector3, structureId: string): void; + querySelector(selector: string): GameEntity | null; /** - * @zh 为指定玩家授予成就/进度。 - * @en Grants a datapack advancement to a player by name. + * @zh 在矩形区域内搜索实体。 + * @en Searches entities within a bounding box. */ - grantAdvancement(playerName: string, advancementId: string): void; + searchBox(bounds: GameBounds3): GameEntity[]; /** - * @zh 按物品名搜索配方 ID 列表。 - * @en Searches recipe IDs matching a filter string. - * @param filter - 搜索关键词 (匹配配方 ID) + * @zh 获取区域内的所有实体。 + * @en Returns all entities in a rectangular area. */ - listRecipes(filter: string): string[]; + entitiesInArea(pos1: GameVector3, pos2: GameVector3): GameEntity[]; + entitiesInArea(bounds: GameBounds3): GameEntity[]; /** - * @zh 移除指定 ID 的配方 (黑名单机制, 服务器重载后需重新移除)。 - * @en Removes a recipe by ID (blacklisted; re‑apply after server reload). - * @param recipeId - 配方 ID, 例如 "minecraft:iron_pickaxe" - * @returns @zh 是否成功加入黑名单 @en Whether the recipe was successfully blacklisted + * @zh 获取半径内的所有实体。 + * @en Returns all entities within a radius. */ - removeRecipe(recipeId: string): boolean; + entitiesInRadius(x: number, y: number, z: number, radius: number): GameEntity[]; + entitiesInRadius(pos: GameVector3, radius: number): GameEntity[]; /** - * @zh 清除所有配方黑名单, 恢复全部原始配方。 - * @en Clears the recipe blacklist and restores all original recipes. + * @zh 射线检测,返回命中的方块或实体。 + * @en Casts a ray and returns the hit block or entity. */ - clearRecipes(): void; + raycast(origin: GameVector3, direction: GameVector3): object | null; + raycast(origin: GameVector3, direction: GameVector3, maxDistance: number): object | null; /** - * @zh 在指定位置向全服播放声音。 - * @en Plays a sound for all players at a location. - * @param path - 声音 ID - * @param x, y, z - 声源坐标 - * @param volume - 音量 (0‑1) - * @param pitch - 音高 (0.5‑2) + * @zh 获取指定坐标的生物群系。 + * @en Gets the biome at the given coordinates. */ - playSound( - path: string, - x: number, - y: number, - z: number, - volume: number, - pitch: number, - ): void; - playSound( - path: string, - pos: GameVector3, - volume: number, - pitch: number, - ): void; + getBiome(x: number, y: number, z: number): string; + getBiome(pos: GameVector3): string; - // ── @zh 命令 @en Command ── + // ── @zh 聊天 & 命令 @en Chat & Command ── - /** - * @zh 以服务端身份执行命令。 - * @en Executes a Minecraft command as the server. - */ + /** @zh 广播系统消息。 @en Broadcasts a system message to all players. */ + say(message: string): void; + + /** @zh 以控制台身份执行命令。 @en Executes a command as console. */ runCommand(cmd: string): void; - // ── @zh 实体查询 @en Entity Queries ── + // ── @zh 音效属性 @en Sound Properties ── - /** - * @zh 查询所有匹配选择器的实体 (目前仅限玩家)。 - * @en Selects all entities matching a selector (currently only players). - * @param selector - "*" (所有玩家) | "#uuid" | ".tag" - */ - querySelectorAll(selector: string): GameEntity[]; + /** @zh 环境音效路径 @en Ambient sound resource path. */ + ambientSound: string; - /** - * @zh 查询第一个匹配的实体 (或 null)。 - * @en Selects the first matching entity, or null. - */ - querySelector(selector: string): GameEntity | null; + /** @zh 玩家加入音效 @en Player join sound. */ + playerJoinSound: string; - /** - * @zh 查询指定区域内的所有实体。 - * @en Returns all entities inside an AABB defined by two corners. - */ - entitiesInArea(pos1: GameVector3, pos2: GameVector3): GameEntity[]; + /** @zh 玩家离开音效 @en Player leave sound. */ + playerLeaveSound: string; - /** - * @zh 查询指定半径内的所有实体。 - * @en Returns all entities within a radius around a point. - */ - entitiesInRadius( - x: number, - y: number, - z: number, - radius: number, - ): GameEntity[]; - entitiesInRadius(pos: GameVector3, radius: number): GameEntity[]; + /** @zh 放置方块音效 @en Block place sound. */ + placeVoxelSound: string; - // ── @zh 搜索与音效 @en Search & Sound ── + /** @zh 破坏方块音效 @en Block break sound. */ + breakVoxelSound: string; /** - * @zh 播放音效 (简写或完整配置)。 - * @en Plays a sound (string shorthand or full config object). - * @param config - 音效路径字符串 或 { path, position, volume, pitch } + * @zh 播放环境音效 (支持字符串路径或配置对象)。 + * @en Plays a sound (accepts a path string or a config object). */ - sound( - config: - | string - | { - path: string; - position?: GameVector3; - volume?: number; - pitch?: number; - }, - ): void; + sound(cfg: string | { + path?: string; + position?: GameVector3; + volume?: number; + pitch?: number; + }): void; /** - * @zh 查询包围盒内的所有实体。 - * @en Returns all entities inside a GameBounds3. + * @zh 在指定坐标播放音效。 + * @en Plays a sound at the given position for all players. */ - searchBox(bounds: GameBounds3): GameEntity[]; + playSound(path: string, x: number, y: number, z: number, volume: number, pitch: number): void; + playSound(path: string, pos: GameVector3, volume: number, pitch: number): void; - // ── @zh 射线检测 @en Raycast ── + // ── @zh 计分板 @en Scoreboard ── - /** - * @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, - direction: GameVector3, - maxDistance?: number, - ): RaycastResult; + /** @zh 创建计分板 (默认 criteria "dummy") @en Creates a scoreboard (default criteria "dummy"). */ + addScoreboard(name: string): void; + addScoreboard(name: string, criteria: string): void; - // ── @zh 生物群系 @en Biome ── + /** @zh 移除计分板 @en Removes a scoreboard. */ + removeScoreboard(name: string): void; /** - * @zh 获取指定位置的生物群系 ID。 - * @en Returns the biome identifier at the given position. + * @zh 设置计分。entityOrName 可以是 GameEntity 或玩家名字符串。 + * @en Sets a score. entityOrName can be a GameEntity or player name string. */ - getBiome(x: number, y: number, z: number): string; - getBiome(pos: GameVector3): string; - - // ── @zh 爆炸 @en Explosion ── + setScore(entityOrName: GameEntity | string, objectiveName: string, value: number): void; /** - * @zh 在指定位置制造爆炸。 - * @en Creates an explosion at the given position. - * @param x, y, z - 爆炸中心 - * @param power - 爆炸强度 - * @param fire - 是否产生火焰 (可选, 默认 false) + * @zh 获取计分。 + * @en Gets a score value. */ - explode(x: number, y: number, z: number, power: number, fire?: boolean): void; - explode(pos: GameVector3, power: number, fire?: boolean): void; + getScore(entityOrName: GameEntity | string, objectiveName: string): number; - // ── @zh 粒子 @en Particles ── + /** @zh 在指定 slot 显示计分板 @en Shows a scoreboard in the given display slot. */ + showScoreboard(slot: string, objectiveName: string): void; - /** - * @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, - x: number, - y: number, - z: number, - count: number, - dx: number, - dy: number, - dz: number, - speed: number, - ): void; - /** @zh GameVector3 重载。 @en GameVector3 overload. */ - spawnParticle( - type: string, - pos: GameVector3, - count: number, - dx: number, - dy: number, - dz: number, - speed: number, - ): void; + /** @zh 隐藏指定 slot 的计分板 @en Hides the scoreboard in the given slot. */ + hideScoreboard(slot: string): 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; + /** @zh 列出计分板所有条目 @en Lists all entries of a scoreboard. */ + listScores(objectiveName: string): Array<{ name: string; score: number }>; + + // ── @zh Boss 条 @en Boss Bar ── /** - * @zh 在指定圆环上生成粒子。 - * @en Spawns particles in a circle. - * @param x, y, z - 圆心 - * @param radius - 半径 - * @param type - 粒子 ID - * @param count - 数量 + * @zh 显示全局 Bossbar。 + * @en Shows a global bossbar. */ - spawnParticleCircle( - x: number, - y: number, - z: number, - radius: number, - type: string, - count: number, - ): void; - spawnParticleCircle( - pos: GameVector3, - radius: number, - type: string, - count: number, - ): void; - - // ── @zh 烟花 @en Fireworks ── + showBossbar(name: string, text: string, progress: number, colorName: string): void; /** - * @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" + * @zh 移除全局 Bossbar。 + * @en Removes a global bossbar. */ - launchFirework( - x: number, - y: number, - z: number, - color: string, - shape: string, - ): void; - launchFirework(pos: GameVector3, color: string, shape: string): void; + removeBossbar(name: string): void; - /** @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 Team ── - // ── @zh 闪电 @en Lightning ── + /** @zh 创建队伍 @en Creates a team. */ + createTeam(name: string, colorName: string): void; - /** - * @zh 在指定位置召唤闪电。 - * @en Summons a lightning bolt at the given position. - * @param x, y, z - 位置 - * @param damage - 伤害值 (可选, 仅对实体造成) - * @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; + /** @zh 移除队伍 @en Removes a team. */ + removeTeam(name: string): void; - // ── @zh 掉落物 @en Drop Item ── + /** @zh 将实体/玩家加入队伍 @en Adds an entity/player to a team. */ + joinTeam(entityOrName: GameEntity | string, teamName: string): void; - /** - * @zh 在指定位置生成掉落物。 - * @en Drops an item stack at the given position. - * @param x, y, z - 位置 - * @param itemId - 物品 ID - * @param count - 数量 - */ - dropItem( - x: number, - y: number, - z: number, - itemId: string, - count: number, - ): void; - dropItem(pos: GameVector3, itemId: string, count: number): void; + /** @zh 将实体/玩家移出队伍 @en Removes an entity/player from their team. */ + leaveTeam(entityOrName: GameEntity | string): void; - // ── @zh 弹射物 @en Projectile ── + /** @zh 获取实体/玩家所在队伍名 @en Gets the team name of an entity/player. */ + getTeamOf(entityOrName: GameEntity | string): string; - /** - * @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 @zh 弹射物实体,失败返回 null @en The projectile entity, or null on failure - */ - launchProjectile( - type: string, - x: number, - y: number, - z: number, - tx: number, - ty: number, - tz: number, - speed: number, - ): GameEntity | null; - launchProjectile( - type: string, - pos: GameVector3, - target: GameVector3, - speed: number, - ): GameEntity | null; + // ── @zh 世界边界 @en World Border ── - // ── @zh 计分板 @en Scoreboard ── + /** @zh 当前边界尺寸 @en Current border size. */ + borderSize: number; - /** - * @zh 添加计分板目标 (默认 dummy 标准)。 - * @en Adds a scoreboard objective (default dummy criteria). - */ - addScoreboard(name: string): void; + /** @zh 设置边界中心 @en Sets the border center. */ + setBorderCenter(x: number, z: number): void; /** - * @zh 添加计分板目标 (自定义标准)。 - * @en Adds a scoreboard objective with a custom criteria. + * @zh 收缩边界到目标大小。 + * @en Shrinks the border to the target size over time. */ - addScoreboard(name: string, criteria: string): void; + shrinkBorder(targetSize: number, seconds: number): void; - /** @zh 移除计分板目标 @en Removes a scoreboard objective. */ - removeScoreboard(name: string): void; + /** @zh 设置边界伤害 (每方块) @en Sets border damage per block. */ + setBorderDamage(damage: number): void; - /** - * @zh 设置实体/名称的分数。 - * @en Sets the score of an entity or name for a given objective. - */ - setScore( - entityOrName: string | GameEntity, - objectiveName: string, - value: number, - ): void; + /** @zh 设置边界警告距离 (方块) @en Sets the border warning distance in blocks. */ + setBorderWarning(blocks: number): void; - /** - * @zh 获取分数。 - * @en Gets the score of an entity or name for a given objective. - */ - getScore(entityOrName: string | GameEntity, objectiveName: string): number; + // ── @zh 闪电 @en Lightning ── - /** - * @zh 在指定显示位置展示计分板。 - * @en Displays a scoreboard objective in a display slot. - * @param slot - "sidebar" | "list" | "belowname" - */ - showScoreboard(slot: string, objectiveName: string): void; + /** @zh 在指定坐标生成闪电。 @en Strikes lightning at the given coordinates. */ + strikeLightning(x: number, y: number, z: number): boolean; + strikeLightning(pos: GameVector3): boolean; + strikeLightning(x: number, y: number, z: number, damage: number): boolean; + strikeLightning(pos: GameVector3, damage: number): boolean; - /** - * @zh 从显示位置隐藏计分板。 - * @en Hides a scoreboard from a display slot. - */ - hideScoreboard(slot: string): void; + // ── @zh 弹射物 @en Projectile ── /** - * @zh 列出计分板上所有玩家的分数。 - * @en Lists all player scores for a given objective. - * @returns Array<{ name: string, value: number }> + * @zh 发射弹射物。 + * @en Launches a projectile from start to target. */ - listScores(objectiveName: string): Array<{ name: string; value: number }>; + launchProjectile( + type: string, + x: number, y: number, z: number, + tx: number, ty: number, tz: number, + speed: number, + ): GameEntity | null; + launchProjectile(type: string, pos: GameVector3, target: GameVector3, speed: number): GameEntity | null; - // ── @zh Boss 血条 @en Boss Bar ── + // ── @zh 烟花 @en Firework ── - /** - * @zh 显示或更新 Boss 血条。 - * @en Shows or updates a boss bar. - * @param name - 血条 ID - * @param text - 显示文字 - * @param progress - 进度 (0‑1) - * @param color - 颜色: "red" | "blue" | "green" | "yellow" | "purple" | "pink" | "white" - */ - showBossbar( - name: string, - text: string, - progress: number, - color: string, - ): void; + /** @zh 发射烟花 (颜色名/字符串) @en Launches a firework with a color name. */ + launchFirework(x: number, y: number, z: number, color: string, shape: string): void; + launchFirework(pos: GameVector3, color: string, shape: string): void; + /** @zh 发射烟花 (GameRGBColor 数组) @en Launches a firework with RGB colors. */ + launchFirework(x: number, y: number, z: number, colors: GameRGBColor[], shape: string): void; + launchFirework(pos: GameVector3, colors: GameRGBColor[], shape: string): void; - /** @zh 移除 Boss 血条 @en Removes a boss bar by ID. */ - removeBossbar(name: string): void; + // ── @zh 粒子 @en Particle ── - // ── @zh 队伍 @en Teams ── + /** @zh 生成粒子效果 @en Spawns particles. */ + spawnParticle( + type: string, + x: number, y: number, z: number, + count: number, + dx: number, dy: number, dz: number, + speed: number, + ): void; + spawnParticle(type: string, pos: GameVector3, count: number, dx: number, dy: number, dz: number, speed: number): void; + /** @zh 生成彩色粉尘粒子 @en Spawns colored dust particles. */ + spawnParticle(x: number, y: number, z: number, color: GameRGBColor, count: number, dx: number, dy: number, dz: number, speed: number): void; + spawnParticle(pos: GameVector3, color: GameRGBColor, count: number, dx: number, dy: number, dz: number, speed: number): void; /** - * @zh 创建一个队伍。 - * @en Creates a scoreboard team. - * @param name - 队伍名 - * @param color - 颜色 (如 "aqua", "red", "blue" 等) + * @zh 在水平圆环上生成粒子。 + * @en Spawns particles in a horizontal circle. */ - createTeam(name: string, color: string): void; - - /** @zh 删除队伍 @en Removes a team. */ - removeTeam(name: string): void; + spawnParticleCircle(x: number, y: number, z: number, radius: number, type: string, count: number): void; + spawnParticleCircle(pos: GameVector3, radius: number, type: string, count: number): void; - /** - * @zh 将实体/名称加入队伍。 - * @en Adds an entity or name to a team. - */ - joinTeam(entityOrName: string | GameEntity, teamName: string): void; + // ── @zh 掉落物 @en Drop Item ── - /** - * @zh 将实体/名称移出队伍。 - * @en Removes an entity or name from its current team. - */ - leaveTeam(entityOrName: string | GameEntity): void; + /** @zh 在指定坐标掉落物品。 @en Drops an item stack at the given position. */ + dropItem(x: number, y: number, z: number, itemId: string, count: number): void; + dropItem(pos: GameVector3, itemId: string, count: number): void; - /** - * @zh 获取实体/名称所在的队伍名 (不在任何队伍返回 null)。 - * @en Returns the team name of an entity or name, or null. - */ - getTeamOf(entityOrName: string | GameEntity): string | null; + // ── @zh 爆炸 @en Explosion ── - // ── @zh 世界边界 @en World Border ── + /** @zh 在指定坐标产生爆炸。 @en Creates an explosion at the given position. */ + explode(x: number, y: number, z: number, power: number): void; + explode(pos: GameVector3, power: number): void; + explode(x: number, y: number, z: number, power: number, fire: boolean): void; + explode(pos: GameVector3, power: number, fire: boolean): void; - /** @zh 当前边界大小 @en Current world border size. */ - borderSize: number; + // ── @zh 结构 @en Structure ── /** - * @zh 设置边界中心。 - * @en Sets the world border center. + * @zh 在指定坐标放置结构。 + * @en Places a structure at the given position. */ - setBorderCenter(x: number, z: number): void; + placeStructure(x: number, y: number, z: number, structureId: string): void; + placeStructure(pos: GameVector3, structureId: string): void; - /** - * @zh 缩放边界到目标大小 (带动画)。 - * @en Shrinks/grows the world border to a target size over time. - * @param targetSize - 目标大小 - * @param seconds - 动画秒数 - */ - shrinkBorder(targetSize: number, seconds: number): void; + // ── @zh 成就 @en Advancement ── /** - * @zh 边界伤害 (每秒造成的伤害值)。 - * @en World border damage per block per second. + * @zh 为指定玩家授予成就。 + * @en Grants an advancement to the given player. */ - setBorderDamage(damage: number): void; + grantAdvancement(playerName: string, advancementId: string): void; - /** - * @zh 边界警告距离 (方块数)。 - * @en World border warning distance in blocks. - */ - setBorderWarning(blocks: number): void; + // ── @zh 配方 @en Recipe ── - // ── @zh 项目间消息 @en Cross‑project Messaging ── + /** @zh 列出匹配过滤器的配方 ID @en Lists recipe IDs matching a filter. */ + listRecipes(filter: string): string[]; - /** - * @zh 向另一个项目发送消息。 - * @en Sends a message to another script project. - * @param target - 目标项目名 (不含路径) - * @param data - 数据 (任意 JSON 可序列化的值) - */ - sendMessage(target: string, data: unknown): void; + /** @zh 移除配方 @en Removes a recipe by ID. */ + removeRecipe(recipeId: string): boolean; - // ── @zh 事件注册 @en Event Registration ── - // @zh 所有 onXxx() 返回 GameEventHandlerToken, 调用 .cancel() 取消监听。 @en All onXxx() return GameEventHandlerToken; call .cancel() to unregister. + /** @zh 清除所有配方 @en Clears all recipes. */ + clearRecipes(): void; - /** - * @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; + // ── @zh 跨脚本消息 @en Cross-project Message ── /** - * @zh 注册玩家加入回调。 - * @en Registers a callback invoked when a player joins the server. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe + * @zh 向其他项目发送消息。 + * @en Sends a message to another project. */ - onPlayerJoin( - handler: (entity: GamePlayerEntity, tick: number) => void, - ): GameEventHandlerToken; + sendMessage(target: string, data: any): void; - /** - * @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; + // ── @zh 事件 @en Events ── /** - * @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 @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe + * @zh 每 tick 触发 (20/秒)。 + * @en Fired every server tick (20/sec). + * @param handler - (info: { tick: number; prevTick: number; elapsedTimeMS: number; skip: number }) => void */ - onChat( - handler: ( - entity: GamePlayerEntity, - message: string, - tick: number, - ) => boolean | void, - ): GameEventHandlerToken; + onTick(handler: (info: { tick: number; prevTick: number; elapsedTimeMS: number; skip: number }) => void): GameEventHandlerToken; - /** - * @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; + /** @zh 玩家加入时触发 @en Fired when a player joins. */ + onPlayerJoin(handler: (entity: GamePlayerEntity, tick: number) => void): GameEventHandlerToken; - /** - * @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: ( - entity: GamePlayerEntity, - x: number, - y: number, - z: number, - voxel: string, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 玩家离开时触发 @en Fired when a player leaves. */ + onPlayerLeave(handler: (entity: GamePlayerEntity, tick: number) => void): GameEventHandlerToken; - /** - * @zh 注册方块破坏回调。 - * @en Registers a callback invoked when a player breaks a block. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onVoxelDestroy( - handler: ( - entity: GamePlayerEntity, - x: number, - y: number, - z: number, - voxel: string, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 方块被破坏时触发 @en Fired when a block is destroyed. */ + onVoxelDestroy(handler: (entity: GameEntity, x: number, y: number, z: number, voxel: string, tick: number) => void): GameEventHandlerToken; - /** - * @zh 注册方块放置回调。 - * @en Registers a callback invoked when a player places a block. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onBlockPlace( - handler: ( - entity: GamePlayerEntity, - x: number, - y: number, - z: number, - voxel: string, - voxelId: number, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体接触方块时触发 @en Fired when an entity touches a block. */ + onVoxelContact(handler: (entity: GameEntity, voxel: number, x: number, y: number, z: number, axis: number, force: number, tick: number) => void): GameEventHandlerToken; - /** - * @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: ( - entity: GamePlayerEntity, - voxelId: number, - x: number, - y: number, - z: number, - contactType: number, - force: number, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体交互时触发 @en Fired when entities interact. */ + onInteract(handler: (entity: GameEntity, target: any, tick: number) => void): GameEventHandlerToken; - /** - * @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: ( - entity: GamePlayerEntity, - target: GameEntity, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 聊天消息时触发 (返回 false 取消广播) @en Fired on chat message (return false to cancel broadcast). */ + onChat(handler: (entity: GamePlayerEntity, message: string, tick: number) => boolean | void): GameEventHandlerToken; - /** - * @zh 注册实体死亡回调。 - * @en Registers a callback invoked when an entity dies. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onEntityDeath( - handler: ( - entity: GameEntity, - killer: GameEntity | null, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体进入流体时触发 @en Fired when an entity enters a fluid. */ + onFluidEnter(handler: (entity: GameEntity, fluid: string, x: number, y: number, z: number, tick: number) => void): GameEventHandlerToken; - /** - * @zh 注册实体受伤回调。 - * @en Registers a callback invoked when an entity takes damage. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onEntityDamage( - handler: ( - entity: GameEntity, - amount: number, - source: string, - attacker: GameEntity | null, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体离开流体时触发 @en Fired when an entity leaves a fluid. */ + onFluidLeave(handler: (entity: GameEntity, fluid: string, x: number, y: number, z: number, tick: number) => void): GameEventHandlerToken; - /** - * @zh 注册流体进入回调 (玩家进入水/熔岩)。 - * @en Registers a callback invoked when a player enters a fluid. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onFluidEnter( - handler: ( - entity: GamePlayerEntity, - fluid: string, - x: number, - y: number, - z: number, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体接触实体时触发 @en Fired when two entities begin touching. */ + onEntityContact(handler: (entity: GameEntity, other: GameEntity, tick: number) => void): GameEventHandlerToken; - /** - * @zh 注册流体离开回调 (玩家离开水/熔岩)。 - * @en Registers a callback invoked when a player leaves a fluid. - * @returns @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onFluidLeave( - handler: ( - entity: GamePlayerEntity, - fluid: string, - x: number, - y: number, - z: number, - tick: number, - ) => void, - ): GameEventHandlerToken; + /** @zh 实体分开时触发 @en Fired when two entities separate. */ + onEntitySeparate(handler: (entity: GameEntity, other: GameEntity, tick: number) => void): GameEventHandlerToken; - /** - * @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; + /** @zh 方块被放置时触发 @en Fired when a block is placed. */ + onBlockPlace(handler: (entity: GameEntity, x: number, y: number, z: number, voxel: string, voxelId: number, tick: number) => void): GameEventHandlerToken; - /** - * @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; + /** @zh 实体死亡时触发 @en Fired when an entity dies. */ + onEntityDeath(handler: (entity: GameEntity, killer: GameEntity | null, tick: number) => void): GameEventHandlerToken; - /** - * @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 @zh GameEventHandlerToken — 调用 .cancel() 取消 @en GameEventHandlerToken — call .cancel() to unsubscribe - */ - onButtonPressed( - handler: (entity: GamePlayerEntity, button: string, tick: number) => void, - ): GameEventHandlerToken; + /** @zh 玩家重生时触发 @en Fired when a player respawns. */ + onPlayerRespawn(handler: (entity: GamePlayerEntity, tick: number) => void): GameEventHandlerToken; - /** - * @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, - ): GameEventHandlerToken; -} + /** @zh 方块被激活时触发 (按钮/拉杆等) @en Fired when a block is activated (button/lever etc.). */ + onBlockActivate(handler: (entity: GameEntity, x: number, y: number, z: number, voxel: string, tick: number) => void): GameEventHandlerToken; -/** - * @zh `world.raycast()` 返回结果。 - * @en Return type of `world.raycast()`. - */ -interface RaycastResult { - /** @zh 是否命中 @en True if something was hit. */ - hit: boolean; - /** @zh 命中点 X 坐标 @en Hit point X coordinate. */ - x: number; - /** @zh 命中点 Y 坐标 @en Hit point Y coordinate. */ - y: number; - /** @zh 命中点 Z 坐标 @en Hit point Z coordinate. */ - z: number; - /** @zh 表面法线 X 分量 @en Surface normal X component. */ - normalX: number; - /** @zh 表面法线 Y 分量 @en Surface normal Y component. */ - normalY: number; - /** @zh 表面法线 Z 分量 @en Surface normal Z component. */ - normalZ: number; - /** @zh 命中距离 @en Distance from origin to hit point. */ - distance: number; - /** @zh 命中的方块 ID (命中方块时为数字) @en Hit block ID (number when a block was hit). */ - voxel?: number; - /** @zh 命中的实体 (命中实体时) @en The entity that was hit (when an entity was hit). */ - entity?: GameEntity; + /** @zh 实体受伤时触发 @en Fired when an entity takes damage. */ + onEntityDamage(handler: (entity: GameEntity, amount: number, source: string, attacker: GameEntity | null, tick: number) => void): GameEventHandlerToken; + + /** @zh 玩家按下按钮时触发 (客户端事件) @en Fired when a player presses a button (client event). */ + onButtonPressed(handler: (entity: GamePlayerEntity, button: string, tick: number) => void): GameEventHandlerToken; + + /** @zh 跨脚本消息接收 @en Receives cross-project messages. */ + onMessage(handler: (from: string, data: any) => void): GameEventHandlerToken; } diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/shared.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/shared.d.ts index f51df92..b74965a 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/shared.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/shared.d.ts @@ -230,6 +230,24 @@ declare class GameBounds3 { /** @zh 原地平移包围盒。 @en Translates the bounds in‑place. */ moveEq(offset: GameVector3): GameBounds3; + /** @zh 包围盒体积 (宽×高×深)。 @en Volume of the bounds (width × height × depth). */ + volume(): number; + + /** @zh 包围盒是否为空 (任一维度 ≤ 0)。 @en Whether the bounds is empty (any dimension ≤ 0). */ + isEmpty(): boolean; + + /** @zh 判断两个包围盒是否完全相等。 @en Whether this bounds exactly equals another. */ + equals(b: GameBounds3): boolean; + + /** @zh 返回同时包围自身和 b 的最小包围盒。 @en Returns the smallest bounds containing both this and b. */ + union(b: GameBounds3): GameBounds3; + + /** @zh 每面向外扩展 amount(返回新对象,等效 expand)。 @en Inflates the bounds outward by amount on all six faces (returns new object; equivalent to expand). */ + inflate(amount: number): GameBounds3; + + /** @zh 每面向内收缩 amount(返回新对象,最小收缩到零体积)。 @en Deflates the bounds inward by amount on all six faces (clamped to zero volume). */ + deflate(amount: number): GameBounds3; + /** @zh 从 GameVector3 数组创建最小包围盒。 @en Creates bounds from an array of GameVector3. */ static fromPoints(points: GameVector3[]): GameBounds3 | null; diff --git a/Box3JS-NeoForge-1.21.1/tools/box3js-api-manifest.json b/Box3JS-NeoForge-1.21.1/tools/box3js-api-manifest.json index 4b55146..1ee1afc 100644 --- a/Box3JS-NeoForge-1.21.1/tools/box3js-api-manifest.json +++ b/Box3JS-NeoForge-1.21.1/tools/box3js-api-manifest.json @@ -94,7 +94,7 @@ "dts": "src/main/resources/assets/box3js/template/types/server/player.d.ts", "iface": "GamePlayer", "ignoreJava": ["getPlayer"], - "accessorProperties": ["position", "velocity", "bounds", "onGround", "name", "userId", "opLevel", "invisible", "scale", "walkSpeed", "runSpeed", "jumpPower", "moveState", "walkState", "enableJump", "crouchSpeed", "swimSpeed", "canFly", "flying", "collision", "spectator", "flySpeed", "gameMode", "dimension", "disableFly", "cameraMode", "cameraEntity", "cameraPitch", "cameraYaw", "facingDirection", "cameraTarget", "dead", "spawnPoint", "hp", "maxHp", "xp", "food", "saturation"], + "accessorProperties": ["position", "velocity", "bounds", "onGround", "name", "userId", "opLevel", "invisible", "scale", "walkSpeed", "runSpeed", "jumpPower", "moveState", "walkState", "enableJump", "crouchSpeed", "swimSpeed", "canFly", "flying", "collision", "spectator", "flySpeed", "gameMode", "dimension", "disableFly", "cameraMode", "cameraEntity", "cameraPitch", "cameraYaw", "facingDirection", "cameraTarget", "dead", "spawnPoint", "hp", "maxHp", "xp", "food", "saturation", "inventoryFreeSlots"], "docs": ["docs/api/player.md", "docs/en/api/player.md"] }, { From 68c6cc0035e517f3e1e2f0f3a12bd1baf8bd8357 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Fri, 22 May 2026 17:23:24 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(readme):=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8C=E5=AE=8C=E5=96=84=20API=20=E6=A6=82=E8=A7=88?= =?UTF-8?q?=E4=B8=8E=E4=BD=BF=E7=94=A8=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将项目状态从 Beta 更新为 v0.1.0 正式发布 - 增加 Box3JS 与传统 Java 模组开发的全面功能对比 - 扩展安装说明,提供清晰的分步安装流程 - 增强快速入门章节,补充完整的工作流示例 - 新增所有可用 box3script 命令的命令参考表 - 优化 API 概览,完善全局对象描述 - 补充涵盖客户端脚本编写的教程内容 - 新增示例项目展示,详细说明功能亮点 - 重组依赖信息,提升结构清晰度 - 整体优化文档结构与可读性 --- Box3JS-NeoForge-1.21.1/README.md | 310 ++++++++++-------- Box3JS-NeoForge-1.21.1/README_en.md | 299 +++++++++-------- .../docs/.vitepress/config.mjs | 10 +- Box3JS-NeoForge-1.21.1/docs/api/entity.md | 13 + Box3JS-NeoForge-1.21.1/docs/api/world.md | 17 + Box3JS-NeoForge-1.21.1/docs/en/api/entity.md | 13 + Box3JS-NeoForge-1.21.1/docs/en/api/world.md | 17 + .../docs/en/guide/about-box3js.md | 11 +- Box3JS-NeoForge-1.21.1/docs/en/index.md | 2 +- Box3JS-NeoForge-1.21.1/docs/en/overview.md | 70 ++++ .../main/java/com/box3lab/box3js/Box3JS.java | 2 +- .../com/box3lab/box3js/Box3JSNetwork.java | 8 +- .../box3js/client/Box3JSClientEngine.java | 2 +- .../box3lab/box3js/client/Box3JSGuiProxy.java | 36 +- .../box3lab/box3js/script/Box3JSEntity.java | 12 + .../box3js/script/Box3JSGuiController.java | 33 +- .../box3js/script/Box3JSGuiServerHandler.java | 4 +- .../box3lab/box3js/script/Box3JSWorld.java | 17 + .../box3js/script/Box3ScriptEngine.java | 12 +- .../box3js/template/types/client/gui.d.ts | 8 + .../box3js/template/types/server/entity.d.ts | 14 + .../box3js/template/types/server/world.d.ts | 7 + 22 files changed, 624 insertions(+), 293 deletions(-) create mode 100644 Box3JS-NeoForge-1.21.1/docs/en/overview.md diff --git a/Box3JS-NeoForge-1.21.1/README.md b/Box3JS-NeoForge-1.21.1/README.md index c296748..b8a12cd 100644 --- a/Box3JS-NeoForge-1.21.1/README.md +++ b/Box3JS-NeoForge-1.21.1/README.md @@ -1,185 +1,219 @@ # Box3JS — Minecraft 脚本引擎 -> **Beta** — 本项目处于早期测试阶段,API 可能变动,欢迎反馈。 +> **v0.1.0** — 首个公开测试版。API 可能变动,欢迎反馈。 [简体中文](Readme.md) | [English](README_en.md) -**无需 Java 知识,用 TypeScript 为你的 Minecraft 服务器创造无限玩法。** +**无需 Java 知识。用 TypeScript 为 Minecraft 创造无限玩法。** + +Box3JS 在 Minecraft 嵌入 Mozilla Rhino JavaScript 引擎,API 设计延续[神奇代码岛](https://dao3.fun)简洁高效的风格——告别 Gradle、告别重启、告别复杂的环境配置。PvP 竞技场、塔防、RPG 副本、派对小游戏,写 TypeScript,一键加载,即时生效。 + +> Box3JS 与神奇代码岛的关系?→ [关于 Box3JS](docs/guide/about-box3js.md) + +```ts +// 玩家加入时:弹窗欢迎 + 头顶粒子 + 升级音效 +world.onPlayerJoin((player) => { + player.player.dialog({ content: "欢迎来到 Box3JS 服务器!" }); + const pos = new GameVector3(player.position.x, player.position.y + 1.8, player.position.z); + world.spawnParticle("minecraft:happy_villager", pos, 10, 0.5, 0.5, 0.5, 0.1); + world.playSound("minecraft:entity.player.levelup", pos, 1.0, 1.0); +}); + +// 聊天命令:!heal 回血、!firework 放烟花 +world.onChat((player, message) => { + if (message === "!heal") { + player.hp = player.maxHp; + player.player.actionBar("生命已恢复!"); + return false; // 不广播此消息 + } + if (message === "!firework") { + const pos = new GameVector3(player.position.x, player.position.y + 1, player.position.z); + world.launchFirework(pos, "green", "large_ball"); + return false; + } +}); +``` + +
+对比:用传统 Java Mod 实现同样功能 + +```java +// Java — 需要 ~60 行、3 个类才能实现同等功能 +@Mod.EventBusSubscriber(modid = "mymod") +public class PlayerJoinListener { + @SubscribeEvent + public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer player = (ServerPlayer) event.getEntity(); + // 发送对话框需要在客户端处理,服务端无法直接弹窗 + player.sendSystemMessage( + Component.literal("欢迎来到服务器!").withStyle(ChatFormatting.GREEN) + ); + // 粒子需要获取 ServerLevel、ParticleOptions + ServerLevel level = player.serverLevel(); + Vec3 pos = player.position().add(0, 1.8, 0); + level.sendParticles( + ParticleTypes.HAPPY_VILLAGER, + pos.x, pos.y, pos.z, + 10, 0.5, 0.5, 0.5, 0.1 + ); + // 音效同理 + level.playSound( + null, player.blockPosition(), + SoundEvents.PLAYER_LEVELUP, SoundSource.PLAYERS, + 1.0f, 1.0f + ); + } +} + +@Mod.EventBusSubscriber(modid = "mymod") +public class ChatListener { + @SubscribeEvent + public static void onChat(ServerChatEvent event) { + ServerPlayer player = event.getPlayer(); + String message = event.getMessage().getString(); + if ("!heal".equals(message)) { + player.setHealth(player.getMaxHealth()); + player.displayClientMessage( + Component.literal("生命已恢复!"), true + ); + event.setCanceled(true); + } + if ("!firework".equals(message)) { + // 烟花需要手动构建 FireworkRocketEntity + FireworkRocketEntity firework = new FireworkRocketEntity( + player.level(), + player.getX(), player.getY() + 1, player.getZ(), + createFireworkItem("green", FireworkRocketEntity.Shape.LARGE_BALL) + ); + player.level().addFreshEntity(firework); + event.setCanceled(true); + } + } + // 还需要 ~20 行构建烟花物品的辅助方法... +} +``` +
+ +## 为什么选择 Box3JS? + +**零门槛上手** — 会 JS/TS 就能写模组。不需要 Gradle、不需要 IDE、不需要重启服务端。`/box3script create` 一键生成完整 TypeScript 项目。 + +**秒级热重载** — 改代码 → 构建 → 重载,改动即时生效。开启 `watch` 模式连构建步骤都省了,保存即更新。 + +**写起来像 Box3,跑在 Minecraft 里** — API 命名和设计延续神奇代码岛的开发体验,学过 Box3 的创作者可以无缝过渡。同时充分利用 Minecraft 原生能力:原版生物 AI、粒子效果、结构、进度、配方等。 + +**沙盒保护,放心测试** — 开启沙盒后所有脚本修改自动追踪,关闭沙盒完整回滚。测试时不用担心破坏地图,开发环境即生产环境。 + +**TypeScript 原生支持** — 完整 `.d.ts` 类型声明,中英双语 JSDoc 注释。构建管线:esbuild 打包 → Babel 转译 ES5 → 正则清理,全程自动。 -Box3JS 是一个社区驱动的 Minecraft 模组,在服务端嵌入 Mozilla Rhino JavaScript 引擎。它的 API 设计延续了[神奇代码岛](https://box3.fun)(深圳奇梦岛科技有限公司)的风格——把 Box3 平台简洁高效的开发体验带进 Minecraft。告别复杂的 Java 模组开发:写 TypeScript,一键热重载,即时生效。PvP 竞技场、RPG 副本、派对小游戏、世界管理、社交工具,全能用脚本快速实现。 +**双端脚本** — 服务端处理游戏逻辑、实体 AI、方块操作;客户端处理键盘输入、屏幕 UI、聊天、音效。通过 `remoteChannel` 双向实时通信。 -> 了解 Box3JS 与神奇代码岛的关系?→ [Box3JS 与神奇代码岛](docs/guide/about-box3js.md) +**数据持久化** — 内置 JSON 文件存储和 SQLite 数据库,排行榜、经济系统、玩家存档都能轻松实现。 + +**独立分发** — `/box3script compile` 将脚本编译为独立 JAR 模组,像普通模组一样放入 `mods/` 即可运行,无需 Box3JS。 ## 安装 1. 将 `box3js-.jar` 放入服务端 `mods/` 目录 -2. 如需使用 SQLite 数据库(`db` API),还需安装 [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) +2. 如需 SQLite 数据库(`db` API),同时安装 [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) 3. 启动服务器 -## 5 分钟快速开始 +## 快速开始 -在游戏中(需要 OP 权限等级 ≥ 2): +在聊天框输入(需 OP 权限 ≥ 2): ``` -/box3script create mygame +/box3script create mygame # 创建 TypeScript 项目 ``` -这会创建 TypeScript 项目: - -```` -config/box3/script/mygame/ -├── package.json ← npm 依赖(esbuild、Babel、TypeScript) -├── tsconfig.base.json ← 公共 TS 编译选项 -├── tsconfig.server.json ← 服务端 TS 配置 -├── tsconfig.client.json ← 客户端 TS 配置 -├── build.mjs ← 构建脚本(esbuild → Babel → Rhino) -├── eslint.config.mjs -├── types/ -│ ├── shared.d.ts ← 服务端&客户端共享类型 -│ ├── server/ ← 服务端专属类型(server/entity/player/world/voxels) -│ └── client/ ← 客户端专属类型(client/audio/input/ui/chat) -└── src/ - ├── server/ - │ └── app.ts ← 服务端入口(游戏逻辑) - └── client/ - └── app.ts ← 客户端入口(UI/按键/网络) - -构建并启动: +然后构建并启动: ```bash cd config/box3/script/mygame -npm install && npm run build -```` +npm install && npm run build # 安装依赖 + 构建 +``` ``` -/box3script sandbox mygame # (推荐) 开启沙盒,放心测试 -/box3script start mygame # 启动脚本 +/box3script sandbox mygame # 开启沙盒(推荐,测试无忧) +/box3script start mygame # 启动脚本 ``` -打开 `src/app.ts` 写入代码,保存后 `npm run build`,再 `/box3script reload mygame` 即时生效——**不需要重启服务器**。 +打开 `src/server/app.ts` 写游戏逻辑,保存后 `npm run build`,再 `/box3script reload mygame` — **不需要重启服务器**。开启 `/box3script watch` 后,保存即自动重载。 -## 为什么选择 Box3JS? +> [完整入门指南 →](docs/guide/getting-started.md) + +## 命令速查 + +| 命令 | 说明 | +|------|------| +| `/box3script` | 查看所有项目运行状态 | +| `/box3script create ` | 创建新 TypeScript 项目 | +| `/box3script start [project\|all]` | 启用并加载项目 | +| `/box3script stop [project\|all]` | 禁用并卸载项目 | +| `/box3script reload [project]` | 重载脚本(开发用) | +| `/box3script watch` | 切换文件监控(自动热重载) | +| `/box3script sandbox ` | 切换沙盒(开=追踪修改 / 关=完整回滚) | +| `/box3script compile ` | 编译为独立 JAR 模组 | -| 特性 | 说明 | -| ---------------- | ---------------------------------------------------------------------------------- | -| **零门槛** | 会 JS/TS 就能写,无需 Gradle、无需 IDE、无需重启 | -| **热重载** | 改代码 → build → reload,秒级生效。开启 `watch` 自动重载 | -| **沙盒保护** | 开启沙盒自动追踪所有脚本修改,关闭时完整回滚,服务器不留痕迹 | -| **TypeScript** | 完整 `.d.ts` 类型声明,esbuild + Babel 编译管线,享受智能提示 | -| **20+ 种事件** | onTick、onPlayerJoin、onChat、onEntityDeath、onBlockActivate、onButtonPressed... | -| **视觉效果** | 13+ 粒子、烟花、闪电、爆炸、音效 | -| **客户端 API** | 键盘输入、屏幕 UI、聊天拦截、音效/音乐控制、客户端存储、SQLite、HTTP、双向事件通道 | -| **游戏系统** | 计分板、BossBar、队伍、世界边界、跨脚本通信 | -| **自定义注册表** | JSON 配置注册方块、物品(食物/工具/盔甲)、音效与创造标签页,编译为独立 JAR | -| **数据持久化** | JSON 存储 + SQLite 数据库(排行榜、经济、玩家数据) | -| **独立打包** | `/box3script compile` 将脚本编译为独立 JAR 模组,便于分发部署 | - -## 命令 - -| 命令 | 说明 | -| ---------------------------------- | --------------------------------- | -| `/box3script` | 查看项目状态总览 | -| `/box3script create ` | 创建新 TypeScript 项目 | -| `/box3script start [project\|all]` | 启用并加载项目 | -| `/box3script stop [project\|all]` | 禁用并卸载项目 | -| `/box3script reload [project]` | 重载脚本(开发用) | -| `/box3script watch` | 切换文件监控(自动热重载) | -| `/box3script sandbox ` | 切换沙盒模式(开=追踪 / 关=回滚) | -| `/box3script compile ` | 预检 + 编译为独立 JAR 模组 | - -所有 `` 参数支持 **Tab 自动补全**。[完整命令文档 →](docs/api/commands.md) - -> 运行策略:若同名脚本 JAR 已被 NeoForge 加载,则 `/box3script start/reload/watch` 会跳过文件模式(jar 优先),避免重复执行。 +所有 `` 参数支持 **Tab 自动补全**。[完整命令参考 →](docs/api/commands.md) ## API 速览 -| 全局对象 | 用途 | -| -------------------------------------------- | --------------------------------------------------------------------------------- | -| `world` | 世界状态、事件回调、粒子、烟花、闪电、音效、计分板、BossBar、队伍、边界 | -| `entity` | 实体属性、AI 寻路、装备、药水效果、标签、导航 | -| `player` | 背包、飞行、游戏模式、传送、消息、经验、音效 | -| `voxels` | 方块读写、区域填充、刷怪笼 | -| `http` | HTTP 网络请求(同步 + 异步,GET/POST/JSON) | -| `remoteChannel` | 服务端 ↔ 客户端双向事件通讯 | -| `registries` | 自定义方块/物品/音效(编译 JAR 模式),见 [registries.md](docs/api/registries.md) | -| `client` · `input` · `ui` · `chat` · `audio` | 客户端脚本:生命周期、键盘、屏幕文字、聊天、音频控制 | -| `storage` | JSON 数据持久化(服务端 & 客户端) | -| `db` | SQLite 数据库(服务端 & 客户端) | -| `console` | 控制台日志输出(`log`/`warn`/`error`/`debug`/`assert`/`clear`) | -| `GameVector3` | 三维向量(坐标运算) | -| `GameBounds3` | 包围盒 | -| `GameRGBColor` / `GameRGBAColor` | RGB/RGBA 颜色 | -| `GameQuaternion` | 四元数(旋转运算) | - -[文档首页 →](docs/README.md) · [API 总览 →](docs/api/README.md) · [按任务速查 →](docs/api/README.md#功能速查---我想) +| 全局对象 | 用途 | +|----------|------| +| `world` | 世界事件、实体查询、粒子、烟花、闪电、音效、计分板、BossBar、队伍、边界 | +| `entity` | 实体属性、AI 寻路/攻击、装备、药水效果、属性修改 | +| `player` | 背包、飞行、游戏模式、传送、消息、经验 | +| `voxels` | 方块读写、区域填充、替换、刷怪笼 | +| `remoteChannel` | 服务端 ↔ 客户端 双向事件通道 | +| `client` · `input` · `ui` · `chat` · `audio` · `gui` | 客户端 API:生命周期、键盘、屏幕文字、聊天、音频、自定义容器 | +| `http` | HTTP 请求(同步 + 异步) | +| `storage` · `db` | JSON 持久化 / SQLite 数据库(双端) | +| `registries` | 自定义方块/物品/音效(编译 JAR 模式) | +| `GameVector3` · `GameBounds3` · `GameRGBColor` · `GameQuaternion` | 数学类型:向量、包围盒、颜色、四元数 | + +[API 总览 →](docs/api/README.md) · [按功能速查 →](docs/api/README.md#功能速查---我想) · [完整文档 →](docs/README.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 竞技场、粒子烟花、波次刷怪、特效大全 | +| # | 教程 | 时长 | 学什么 | +|---|------|------|--------| +| 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 竞技场、粒子与烟花、波次刷怪 | +| 6 | [客户端脚本](docs/tutorial/06-client-scripting.md) | 15 min | 键盘输入、屏幕 UI、音效音乐、本地存储、remoteChannel | [教程总览 →](docs/tutorial/README.md) -## 文档结构 - -``` -docs/ -├── guide/ ← 入门指南 -│ ├── README.md 指南总览 -│ ├── about-box3js.md Box3JS 与神奇代码岛(起源、关系、优势) -│ ├── getting-started.md 从零开始(环境、第一个脚本、调试、发布) -│ ├── architecture.md 运行原理(Rhino 引擎、作用域、构建管线) -│ └── js-vs-java.md JS vs Java 模组开发对比 -├── 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) -│ ├── http.md HTTP 请求 API -│ ├── client.md 客户端 API(UI、输入、聊天、通讯) -│ ├── registries.md 自定义方块/物品/音效 -│ ├── 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/` 包含完整的双向通讯游戏和 7 个功能示例,涵盖服务端逻辑、客户端 UI、键盘输入、HTTP 请求、数据库等全部教学场景。 +`run/config/box3/script/` 下包含多个完整游戏项目,可直接运行或作为参考: + +| 项目 | 类型 | 亮点 | +|------|------|------| +| `patdef` | 塔防 | 四类塔(箭/冰/火/雷)、波次刷怪、GUI 商店、攻击光束特效 | +| `bedwar` | 团队竞技 | 双队对抗、资源生成、装备升级、陷阱、床破坏机制 | +| `coredf` | 核心防守 | 中文消息、多阶段波次、经济系统 | +| `az` | 多阶段游戏 | 复杂状态机、阶段切换、多玩法融合 | +| `colorzone` | 跑酷 + 教程 | 双向客户端通信、UI 示例、7 个功能演示 | +| `mygame` | API 测试 | 覆盖所有 Box3JS API 的功能测试用例 | -## 依赖说明 +每个项目都有独立的客户端 `dist/client.js` 和服务端 `dist/server.js`,开箱即用。 -| 功能 | 依赖 | -| ------------------ | ------------------------------------------------------------------------------------- | -| 脚本引擎核心 | 内嵌 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()` 才会提示需要安装。 +| 功能 | 要求 | +|------|------| +| 脚本引擎核心 | 内嵌 Rhino 1.9.1,无需额外安装 | +| `db` API(SQLite) | 需 [`minecraft-sqlite-jdbc`](https://modrinth.com/mod/minecraft-sqlite-jdbc) | +| 其他所有 API | 无额外依赖 | -## 技术栈 +> 即使不装 SQLite 模组,除 `db` 外所有功能正常工作。 -- **运行时:** 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 67a8664..63d17b2 100644 --- a/Box3JS-NeoForge-1.21.1/README_en.md +++ b/Box3JS-NeoForge-1.21.1/README_en.md @@ -1,14 +1,116 @@ # Box3JS — Minecraft Scripting Engine -> **Beta** — This project is in early beta. APIs may change. Feedback is welcome. +> **v0.1.0** — First public beta release. APIs may change. Feedback is welcome. [简体中文](Readme.md) | [English](README_en.md) **No Java knowledge required. Build unlimited Minecraft gameplay with TypeScript.** -Box3JS is a community-driven Minecraft mod (NeoForge 1.21.1) that embeds the Mozilla Rhino JavaScript engine in the server. Its API design is inspired by [Box3](https://box3.fun) (Shenzhen Qimengdao Technology Co., Ltd.) — bringing Box3's clean, efficient developer experience into Minecraft. 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. +Box3JS embeds the Mozilla Rhino JavaScript engine in a Minecraft mod. Its API design is inspired by [Box3](https://dao3.fun) — bringing Box3's clean, efficient developer experience into Minecraft. No Gradle, no server restarts, no complex toolchains. PvP arenas, tower defense, RPG dungeons, party games — write TypeScript, load instantly, see changes live. + +> Box3JS & the Box3 platform? → [About Box3JS](docs/en/guide/about-box3js.md) + +```ts +// On player join: welcome dialog + particles + sound +world.onPlayerJoin((player) => { + player.player.dialog({ content: "Welcome to the Box3JS server!" }); + const pos = new GameVector3(player.position.x, player.position.y + 1.8, player.position.z); + world.spawnParticle("minecraft:happy_villager", pos, 10, 0.5, 0.5, 0.5, 0.1); + world.playSound("minecraft:entity.player.levelup", pos, 1.0, 1.0); +}); + +// Chat commands: !heal to restore HP, !firework to launch a firework +world.onChat((player, message) => { + if (message === "!heal") { + player.hp = player.maxHp; + player.player.actionBar("Healed!"); + return false; // cancel broadcast + } + if (message === "!firework") { + const pos = new GameVector3(player.position.x, player.position.y + 1, player.position.z); + world.launchFirework(pos, "green", "large_ball"); + return false; + } +}); +``` + +
+Versus: the same feature in a traditional Java mod + +```java +// Java — ~60 lines, 3 classes for the same result +@Mod.EventBusSubscriber(modid = "mymod") +public class PlayerJoinListener { + @SubscribeEvent + public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer player = (ServerPlayer) event.getEntity(); + // Dialogs require client-side handling; server-side can only send chat + player.sendSystemMessage( + Component.literal("Welcome to the server!").withStyle(ChatFormatting.GREEN) + ); + // Particles need ServerLevel and ParticleOptions + ServerLevel level = player.serverLevel(); + Vec3 pos = player.position().add(0, 1.8, 0); + level.sendParticles( + ParticleTypes.HAPPY_VILLAGER, + pos.x, pos.y, pos.z, + 10, 0.5, 0.5, 0.5, 0.1 + ); + // Same for sound + level.playSound( + null, player.blockPosition(), + SoundEvents.PLAYER_LEVELUP, SoundSource.PLAYERS, + 1.0f, 1.0f + ); + } +} + +@Mod.EventBusSubscriber(modid = "mymod") +public class ChatListener { + @SubscribeEvent + public static void onChat(ServerChatEvent event) { + ServerPlayer player = event.getPlayer(); + String message = event.getMessage().getString(); + if ("!heal".equals(message)) { + player.setHealth(player.getMaxHealth()); + player.displayClientMessage( + Component.literal("Healed!"), true + ); + event.setCanceled(true); + } + if ("!firework".equals(message)) { + // Fireworks need manual entity construction + FireworkRocketEntity firework = new FireworkRocketEntity( + player.level(), + player.getX(), player.getY() + 1, player.getZ(), + createFireworkItem("green", FireworkRocketEntity.Shape.LARGE_BALL) + ); + player.level().addFreshEntity(firework); + event.setCanceled(true); + } + } + // + ~20 lines for the firework item builder... +} +``` +
+ +## Why Box3JS? + +**Zero barrier to entry** — If you know JS/TS, you can build mods. No Gradle, no IDE, no server restarts. `/box3script create` scaffolds a complete TypeScript project in one command. + +**Instant hot reload** — Edit → build → reload takes seconds. Enable `watch` mode and skip the build step too — save and it reloads automatically. + +**Box3-like, Minecraft-native** — The API design mirrors Box3's developer experience. Creators familiar with Box3 can transition seamlessly. Plus full access to Minecraft's native capabilities: mob AI, particles, structures, advancements, recipes. + +**Sandbox protection** — Enable sandbox to automatically track all script changes. Disable it to fully roll back. Test fearlessly — your map is always safe. + +**First-class TypeScript** — Complete `.d.ts` type declarations with bilingual JSDoc. Build pipeline: esbuild bundle → Babel transpile to ES5 → regex sanitize. Full IDE IntelliSense. -> Curious about Box3JS's relationship with the Box3 platform? → [Box3JS & Box3](docs/guide/about-box3js_en.md) +**Dual-side scripting** — Server handles game logic, entity AI, block manipulation. Client handles keyboard input, screen UI, chat, audio. Bidirectional real-time communication via `remoteChannel`. + +**Built-in persistence** — JSON file storage and SQLite database for leaderboards, economies, player saves — all accessible from both server and client scripts. + +**Standalone distribution** — `/box3script compile` packages your scripts into a standalone JAR mod. Drop it into `mods/` like any other mod — no Box3JS required. ## Installation @@ -16,170 +118,101 @@ Box3JS is a community-driven Minecraft mod (NeoForge 1.21.1) that embeds the Moz 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 +## Quick Start -In-game (requires OP level ≥ 2): +In chat (requires OP level ≥ 2): ``` -/box3script create mygame +/box3script create mygame # scaffold a TypeScript project ``` -This creates a TypeScript project: - -```` -config/box3/script/mygame/ -├── package.json ← npm dependencies (esbuild, Babel, TypeScript) -├── tsconfig.base.json ← Shared TS compiler options -├── tsconfig.server.json ← Server-side TS config -├── tsconfig.client.json ← Client-side TS config -├── build.mjs ← build script (esbuild → Babel → Rhino) -├── eslint.config.mjs -├── types/ -│ ├── shared.d.ts ← types shared by server & client -│ ├── server/ ← server-only types (server/entity/player/world/voxels) -│ └── client/ ← client-only types (client/audio/input/ui/chat) -└── src/ - ├── server/ - │ └── app.ts ← server entry (game logic) - └── client/ - └── app.ts ← client entry (UI/input/network) - -Build and start: +Then build and start: ```bash cd config/box3/script/mygame -npm install && npm run build -```` +npm install && npm run build # install deps + build +``` ``` -/box3script sandbox mygame # (recommended) enable sandbox for safe testing -/box3script start mygame # start the script +/box3script sandbox mygame # enable sandbox (recommended) +/box3script start mygame # start the script ``` -Edit `src/app.ts`, re-run `npm run build`, then `/box3script reload mygame` — changes take effect **without restarting the server**. - -## Why Box3JS? +Open `src/server/app.ts`, write game logic, `npm run build`, then `/box3script reload mygame` — **no server restart needed**. Enable `/box3script watch` and it auto-reloads on every save. -| 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 | -| **20+ events** | onTick, onPlayerJoin, onChat, onEntityDeath, onBlockActivate, onButtonPressed... | -| **Visual effects** | 13+ particles, fireworks, lightning, explosions, sounds | -| **Client API** | Keyboard input, screen UI, chat interception, sound/music control, client storage, SQLite, HTTP, bidirectional events | -| **Game systems** | Scoreboards, BossBar, teams, world border, cross-script messaging | -| **Custom registries** | JSON-configured blocks, items (food/tools/armor), sounds & creative tabs, compiled to standalone JAR | -| **Data persistence** | JSON storage + SQLite database (leaderboards, economy, player data) | -| **Standalone JAR** | `/box3script compile` packages scripts into a standalone JAR mod for distribution | +> [Full getting-started guide →](docs/en/guide/getting-started.md) ## 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 ` | Preflight + compile to standalone JAR | +| Command | Description | +|---------|-------------| +| `/box3script` | Show all project statuses | +| `/box3script create ` | Scaffold 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 (development) | +| `/box3script watch` | Toggle file watching (auto hot-reload) | +| `/box3script sandbox ` | Toggle sandbox (on=track / off=rollback) | +| `/box3script compile ` | Compile to standalone JAR mod | -All `` arguments support **Tab completion**. [Full command reference →](docs/api/commands_en.md) - -> Runtime policy: if a matching script JAR is already loaded by NeoForge, `/box3script start/reload/watch` skips filesystem mode (jar-first) to avoid duplicate execution. +All `` arguments support **Tab completion**. [Full command reference →](docs/en/api/commands.md) ## API Overview -| Global | Purpose | -| -------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `world` | World state, events, particles, fireworks, lightning, sounds, scoreboards, BossBar, teams, border | -| `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 | -| `http` | HTTP requests (sync + async, GET/POST/JSON) | -| `remoteChannel` | Server ↔ client bidirectional event channel | -| `registries` | Custom blocks, items & sounds (compiled JAR mode), see [registries_en.md](docs/api/registries_en.md) | -| `client` · `input` · `ui` · `chat` · `audio` | Client scripts: lifecycle, keyboard, screen text, chat, audio control | -| `storage` | JSON data persistence (server & client) | -| `db` | SQLite database (server & client) | -| `console` | Console logging (`log`/`warn`/`error`/`debug`/`assert`/`clear`) | -| `GameVector3` | 3D vector (coordinate math) | -| `GameBounds3` | Bounding box | -| `GameRGBColor` / `GameRGBAColor` | RGB / RGBA color | -| `GameQuaternion` | Quaternion (rotation math) | - -[Docs Home →](docs/README_en.md) · [API Overview →](docs/api/README_en.md) · [Find by Task →](docs/api/README_en.md#find-by-task--i-want-to) +| Global | Purpose | +|--------|---------| +| `world` | World events, entity queries, particles, fireworks, lightning, sounds, scoreboards, BossBar, teams, border | +| `entity` | Entity properties, AI pathfinding/attack, equipment, potion effects, attribute modification | +| `player` | Inventory, flight, game mode, teleport, messaging, XP | +| `voxels` | Block read/write, region fill, replace, spawner control | +| `remoteChannel` | Server ↔ client bidirectional event channel | +| `client` · `input` · `ui` · `chat` · `audio` · `gui` | Client APIs: lifecycle, keyboard, screen text, chat, audio, custom containers | +| `http` | HTTP requests (sync + async) | +| `storage` · `db` | JSON persistence / SQLite database (server & client) | +| `registries` | Custom blocks, items & sounds (compiled JAR mode) | +| `GameVector3` · `GameBounds3` · `GameRGBColor` · `GameQuaternion` | Math types: vectors, bounding boxes, colors, quaternions | + +[API Overview →](docs/en/api/README.md) · [Find by Task →](docs/en/api/README.md#find-by-task--i-want-to) · [Full Docs →](docs/en/README.md) ## Tutorials From zero to full mini-games. Every example is TypeScript-compiled and ESLint-verified: -| # | 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 | -| 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 | +| # | Tutorial | Time | What you'll learn | +|---|----------|------|-------------------| +| 1 | [Getting Started](docs/en/tutorial/01-basics.md) | 10 min | Project setup, first script, chat commands, timers | +| 2 | [Players & Items](docs/en/tutorial/02-player-items.md) | 15 min | Teleport, flight, items, enchantments, potion effects | +| 3 | [Events & Entities](docs/en/tutorial/03-events-entities.md) | 15 min | All event callbacks, entity spawning, AI control, patrol guards | +| 4 | [Advanced Systems](docs/en/tutorial/04-advanced-systems.md) | 15 min | Scoreboards, BossBar, teams, world border, cross-script messaging | +| 5 | [Mini-Games](docs/en/tutorial/05-examples.md) | 20 min | PvP arena, particles & fireworks, wave mob spawning | +| 6 | [Client Scripting](docs/en/tutorial/06-client-scripting.md) | 15 min | Keyboard input, screen UI, audio/music, local storage, remoteChannel | -[Tutorial overview →](docs/tutorial/README.md) +[Tutorial Overview →](docs/en/tutorial/README.md) -## Documentation Structure +## Example Projects -``` -docs/ -├── guide/ ← Getting Started -│ ├── README.md Guide overview -│ ├── about-box3js.md Box3JS & Box3 (origin, relationship, advantages) -│ ├── getting-started.md From zero (setup, first script, debug, deploy) -│ ├── architecture.md Internals (Rhino engine, scopes, build pipeline) -│ └── js-vs-java.md JS vs Java modding comparison -├── 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) -│ ├── http.md HTTP request API -│ ├── client.md Client API (UI, input, chat, events) -│ ├── registries.md Custom blocks, items & sounds -│ ├── 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 -``` +`run/config/box3/script/` contains multiple full game projects ready to run or study: -## Example Project +| Project | Genre | Highlights | +|---------|-------|------------| +| `patdef` | Tower Defense | 4 tower types (arrow/ice/fire/lightning), wave spawning, GUI shop, attack beam VFX | +| `bedwar` | Team PvP | Two-team combat, resource generators, gear upgrades, traps, bed destruction | +| `coredf` | Core Defense | Chinese-localized, multi-phase waves, economy system | +| `az` | Multi-Phase | Complex state machine, phase transitions, mixed gameplay | +| `colorzone` | Parkour + Demo | Bidirectional client-server communication, UI examples, 7 feature demos | +| `mygame` | API Tests | Functional test cases covering every Box3JS API | -`run/config/box3/script/colorzone/` contains a complete bidirectional communication game and 7 verified feature examples covering every tutorial scenario — from server logic to client UI. +Each project has its own client `dist/client.js` and server `dist/server.js` — drop in and play. ## 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 +| 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) | +| All other APIs | No additional dependencies | -- **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 +> Without the SQLite mod, everything except `db` works normally. Only calling `db.sql()` triggers a reminder. ## License diff --git a/Box3JS-NeoForge-1.21.1/docs/.vitepress/config.mjs b/Box3JS-NeoForge-1.21.1/docs/.vitepress/config.mjs index 36f3c78..c10f27e 100644 --- a/Box3JS-NeoForge-1.21.1/docs/.vitepress/config.mjs +++ b/Box3JS-NeoForge-1.21.1/docs/.vitepress/config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from "vitepress"; const cnNav = [ { text: "首页", link: "/" }, + { text: "文档", link: "/overview" }, { text: "指南", link: "/guide/README" }, { text: "教程", link: "/tutorial/README" }, { text: "API", link: "/api/README" }, @@ -9,6 +10,7 @@ const cnNav = [ const enNav = [ { text: "Home", link: "/en/" }, + { text: "Docs", link: "/en/overview" }, { text: "Guide", link: "/en/guide/README" }, { text: "Tutorials", link: "/en/tutorial/README" }, { text: "API", link: "/en/api/README" }, @@ -96,6 +98,7 @@ const enSidebar = [ text: "Get Started", collapsed: false, items: [ + { text: "Documentation Index", link: "/en/overview" }, { text: "Overview", link: "/en/guide/README" }, { text: "Getting Started", link: "/en/guide/getting-started" }, { text: "Recipes", link: "/en/guide/recipes" }, @@ -181,9 +184,8 @@ export default defineConfig({ description: "Minecraft 的 JavaScript/TypeScript 脚本引擎", lastUpdated: true, cleanUrls: true, - ignoreDeadLinks: true, + ignoreDeadLinks: false, base: "/box3js-mc/", - head: [["link", { rel: "icon", href: "/favicon.ico" }]], locales: { root: { label: "简体中文", @@ -206,7 +208,7 @@ export default defineConfig({ next: "下一页", }, footer: { - message: "基于 MIT 许可证发布", + message: "基于 Apache License 2.0 发布", }, }, }, @@ -231,7 +233,7 @@ export default defineConfig({ next: "Next page", }, footer: { - message: "Released under the MIT License.", + message: "Released under the Apache License 2.0.", }, }, }, diff --git a/Box3JS-NeoForge-1.21.1/docs/api/entity.md b/Box3JS-NeoForge-1.21.1/docs/api/entity.md index dad2049..2ef0876 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/entity.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/entity.md @@ -355,6 +355,19 @@ entity.lookAt(0, 100, -10); entity.lookAt(target.position); ``` +## 粒子 + +⬆ MC 扩展。 + +### entity.spawnParticle(type, count, dx, dy, dz, speed) + +在实体中心位置生成粒子。 + +```js +entity.spawnParticle("minecraft:flame", 10, 0.5, 0.5, 0.5, 0.1); +entity.spawnParticle("minecraft:cloud", 5, 0.2, 0, 0.2, 0); +``` + ## 药水效果 全部 ⬆ MC 扩展。 diff --git a/Box3JS-NeoForge-1.21.1/docs/api/world.md b/Box3JS-NeoForge-1.21.1/docs/api/world.md index 8973ef6..776dd0d 100644 --- a/Box3JS-NeoForge-1.21.1/docs/api/world.md +++ b/Box3JS-NeoForge-1.21.1/docs/api/world.md @@ -660,6 +660,14 @@ world.spawnParticle( ⬆ GameVector3 重载。 +### world.spawnParticleLine(x1, y1, z1, x2, y2, z2, type, count) + +在两点之间均匀生成粒子线。⬆ MC 扩展。 + +### world.spawnParticleLine(from, to, type, count) + +⬆ GameVector3 重载。 + ```js // 单点粒子 world.spawnParticle("minecraft:flame", 0, 100, 0, 10, 0.5, 0.5, 0.5, 0.1); @@ -674,6 +682,15 @@ world.spawnParticleCircle( 20, ); +// 粒子线(两点之间) +world.spawnParticleLine(0, 100, 0, 10, 100, 10, "minecraft:flame", 20); +world.spawnParticleLine( + new GameVector3(0, 100, 0), + new GameVector3(10, 100, 10), + "minecraft:flame", + 20, +); + // 常用粒子: // minecraft:flame, minecraft:cloud, minecraft:happy_villager // minecraft:witch, minecraft:portal, minecraft:end_rod diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md b/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md index 55a7599..12361ca 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/entity.md @@ -358,6 +358,19 @@ entity.lookAt(0, 100, -10); entity.lookAt(target.position); ``` +## Particles + +⬆ MC Extension. + +### entity.spawnParticle(type, count, dx, dy, dz, speed) + +Spawns particles at the entity's center position. + +```js +entity.spawnParticle("minecraft:flame", 10, 0.5, 0.5, 0.5, 0.1); +entity.spawnParticle("minecraft:cloud", 5, 0.2, 0, 0.2, 0); +``` + ## Status Effects All ⬆ MC Extension. diff --git a/Box3JS-NeoForge-1.21.1/docs/en/api/world.md b/Box3JS-NeoForge-1.21.1/docs/en/api/world.md index 4959dca..d75df14 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/api/world.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/api/world.md @@ -665,6 +665,14 @@ Spawn particles evenly on a horizontal circle. ⬆ GameVector3 overload. +### world.spawnParticleLine(x1, y1, z1, x2, y2, z2, type, count) + +Spawn particles evenly along a line between two points. ⬆ MC Extension. + +### world.spawnParticleLine(from, to, type, count) + +⬆ GameVector3 overload. + ```js // Point particles world.spawnParticle("minecraft:flame", 0, 100, 0, 10, 0.5, 0.5, 0.5, 0.1); @@ -679,6 +687,15 @@ world.spawnParticleCircle( 20, ); +// Particle line (between two points) +world.spawnParticleLine(0, 100, 0, 10, 100, 10, "minecraft:flame", 20); +world.spawnParticleLine( + new GameVector3(0, 100, 0), + new GameVector3(10, 100, 10), + "minecraft:flame", + 20, +); + // Common particles: // minecraft:flame, minecraft:cloud, minecraft:happy_villager // minecraft:witch, minecraft:portal, minecraft:end_rod diff --git a/Box3JS-NeoForge-1.21.1/docs/en/guide/about-box3js.md b/Box3JS-NeoForge-1.21.1/docs/en/guide/about-box3js.md index b4fcceb..a9fa2ac 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/guide/about-box3js.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/guide/about-box3js.md @@ -1,11 +1,8 @@ ---- ---- - -# Box3JS and Box3 (Shenqi Code Island) +# Box3JS and Box3 ## What is Box3 (神奇代码岛) -[Box3](https://box3.fun) (also known as 神奇代码岛 or "Code Island 3.0") is a **multiplayer 3D game creation platform** developed by Shenzhen Qimengdao Technology Co., Ltd. (深圳奇梦岛科技有限公司), a brand under Codemao (编程猫). Users create racing games, PvP arenas, RPGs, FPS shooters, and even MOBAs — all in a browser, using nothing but JavaScript. +[Box3](https://box3.fun) (also known as 神奇代码岛 or "Box3") is a **multiplayer 3D game creation platform**. Users create racing games, PvP arenas, RPGs, FPS shooters, and even MOBAs — all in a browser, using nothing but JavaScript. Key features: @@ -67,7 +64,7 @@ world.onChat((entity, message) => { }); ``` -For a detailed API comparison, see [Box3 API vs Box3JS](../BOX3_API_COMPARISON.md). +For a detailed API comparison, see [Box3 API vs Box3JS](../../BOX3_API_COMPARISON.md). ### 2. Real Minecraft World @@ -129,7 +126,7 @@ Box3JS is not a 1:1 copy of Box3's API. Differences stem from the fundamental di | Database | Built-in KV storage | JSON storage + SQLite (requires sqlite-jdbc mod) | | Networking | Platform-managed | `remoteChannel` custom payloads | -**Design principle:** Keep API naming and semantics consistent where possible, but don't force-fit MC-incompatible features. For a detailed comparison, see [Box3 API vs Box3JS](../BOX3_API_COMPARISON.md). +**Design principle:** Keep API naming and semantics consistent where possible, but don't force-fit MC-incompatible features. For a detailed comparison, see [Box3 API vs Box3JS](../../BOX3_API_COMPARISON.md). ## Next Steps diff --git a/Box3JS-NeoForge-1.21.1/docs/en/index.md b/Box3JS-NeoForge-1.21.1/docs/en/index.md index 9f5cfba..0dd46e7 100644 --- a/Box3JS-NeoForge-1.21.1/docs/en/index.md +++ b/Box3JS-NeoForge-1.21.1/docs/en/index.md @@ -64,7 +64,7 @@ world.onChat((entity, message) => { }); ``` -[Read full docs →](/en/guide/getting-started_en) +[Read full docs →](/en/guide/getting-started) ## Version Info diff --git a/Box3JS-NeoForge-1.21.1/docs/en/overview.md b/Box3JS-NeoForge-1.21.1/docs/en/overview.md new file mode 100644 index 0000000..c4491d0 --- /dev/null +++ b/Box3JS-NeoForge-1.21.1/docs/en/overview.md @@ -0,0 +1,70 @@ +# Box3JS Documentation + +Box3JS is a JavaScript/TypeScript scripting engine mod for Minecraft NeoForge 1.21.1. Write server-side gameplay scripts and client-side UI scripts in JS/TS — no JDK, no Java compilation required. + +## Documentation Navigation + +### Getting Started + +Start from zero: what Box3JS is, how to use it, and how it works under the hood. + +| Doc | Content | +|-----|---------| +| [Getting Started](guide/getting-started.md) | Setup → first script → dev loop → debugging → deployment | +| [Common Recipes](guide/recipes.md) | Templates: economy, teleport, shop, daily rewards, leaderboard, webhook | +| [Architecture](guide/architecture.md) | Rhino engine, scope management, build pipeline, network communication | +| [JS vs Java](guide/js-vs-java.md) | Advantages and trade-offs of Box3JS scripting vs native Java modding | +| [FAQ](guide/faq.md) | Loading, build, runtime, database, HTTP, client-side, deployment | + +### Tutorials + +6 progressive tutorials, 10-15 minutes each, with complete runnable code. + +| # | Tutorial | What you'll learn | +|---|----------|-------------------| +| 1 | [Basics](tutorial/01-basics.md) | Create a project → build → first script → chat commands → timers | +| 2 | [Players & Items](tutorial/02-player-items.md) | Teleport, flight, items, enchantments, potion effects, game mode | +| 3 | [Events & Entities](tutorial/03-events-entities.md) | All event callbacks, entity spawning, AI control, patrol guards, collision | +| 4 | [Advanced Systems](tutorial/04-advanced-systems.md) | Scoreboard rankings, BossBar countdown, teams, world border, cross-script messaging | +| 5 | [Mini-Games](tutorial/05-examples.md) | PvP arena (fully playable), particle effects, fireworks, wave-based mob spawning | +| 6 | [Client Scripting](tutorial/06-client-scripting.md) | Keyboard input, screen UI, audio/music, local storage, SQLite, HTTP, remoteChannel | +| 📋 | [Tutorial Overview](tutorial/README.md) | Learning path, prerequisites, development tips | + +### API Reference + +Complete API documentation organized by function. One doc per global object/namespace. + +| Category | Doc | Global Object | +|----------|-----|---------------| +| **Server Overview** | [server](api/server.md) | Server runtime boundaries, events, players/entities, blocks, data, cross-side communication | +| **World** | [world](api/world.md) | `world` — events, particles, fireworks, sounds, scoreboards | +| **Entity** | [entity](api/entity.md) | `entity` — properties, AI, equipment, effects | +| **Player** | [player](api/player.md) | `player` — inventory, messaging, flight, teleport | +| **Voxels** | [voxels](api/voxels.md) | `voxels` — read/write blocks, region fill | +| **Storage** | [storage](api/storage.md) | `storage` — JSON persistence | +| **Database** | [database](api/database.md) | `db` — SQLite database | +| **HTTP** | [http](api/http.md) | `http` — HTTP requests | +| **Client** | [client](api/client.md) | `audio` `client` `input` `ui` `chat` `gui` `remoteChannel` | +| **Registries** | [registries](api/registries.md) | `registries` — custom blocks/items/sounds | +| **Math** | [math](api/math.md) | `GameVector3` `GameBounds3` `GameRGBColor` `GameRGBAColor` `GameQuaternion` | +| **Commands** | [commands](api/commands.md) | `/box3script` CLI reference | +| **Quick Search** | [Find by Task](api/README.md) | "I want to do X" → find the right API | +| **Comparison** | [Box3 API Comparison](../BOX3_API_COMPARISON.md) | Side-by-side comparison of Box3 platform APIs vs Box3JS (Chinese) | + +### Version & Compatibility + +| Component | Version | +|-----------|---------| +| Minecraft | 1.21.1 | +| Mod Loader | NeoForge | +| Java | 21 | +| JS Engine | Mozilla Rhino 1.9.1 (ES5 compatible) | +| TypeScript | Compiled to ES5 via Babel | + +## Quick Links + +- **5-minute quickstart**: [Getting Started →](guide/getting-started.md) +- **"I want to do X, what API?"**: [API Quick Search →](api/README.md) +- **Why Box3JS over Java mods?**: [JS vs Java →](guide/js-vs-java.md) +- **How Box3JS works internally**: [Architecture →](guide/architecture.md) +- **Learn Box3JS from scratch**: [Tutorial 1 →](tutorial/01-basics.md) diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java index ec097df..44b136c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JS.java @@ -81,7 +81,7 @@ public Box3JS(IEventBus modEventBus, ModContainer modContainer) { case 0 -> Box3JSGuiServerHandler.handleOpen( sp, payload.title(), payload.rows(), payload.slotsJson()); case 1 -> Box3JSGuiServerHandler.handleSetItem( - sp, payload.slot(), payload.itemId(), payload.count()); + sp, payload.slot(), payload.itemId(), payload.count(), payload.loreJson(), payload.enchanted()); case 2 -> Box3JSGuiServerHandler.handleRegisterCallbacks( sp, payload.hasSlotClick(), payload.hasClose()); case 3 -> Box3JSGuiServerHandler.handleClose(sp); diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JSNetwork.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JSNetwork.java index 8d6ff3c..54d6fb7 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JSNetwork.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/Box3JSNetwork.java @@ -105,7 +105,9 @@ public record GUIServerboundPayload( String itemId, // for SET_ITEM int count, // for SET_ITEM boolean hasSlotClick, // for REGISTER_CALLBACKS - boolean hasClose // for REGISTER_CALLBACKS + boolean hasClose, // for REGISTER_CALLBACKS + String loreJson, // for SET_ITEM (JSON array of lore strings) + boolean enchanted // for SET_ITEM (enchantment glint override) ) implements CustomPacketPayload { public static final Type TYPE = @@ -123,6 +125,8 @@ public record GUIServerboundPayload( buf.writeVarInt(p.count); buf.writeBoolean(p.hasSlotClick); buf.writeBoolean(p.hasClose); + buf.writeUtf(p.loreJson); + buf.writeBoolean(p.enchanted); }, buf -> new GUIServerboundPayload( buf.readVarInt(), @@ -133,6 +137,8 @@ public record GUIServerboundPayload( buf.readUtf(), buf.readVarInt(), buf.readBoolean(), + buf.readBoolean(), + buf.readUtf(), buf.readBoolean() ) ); diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java index 2edd07b..7805d14 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSClientEngine.java @@ -1010,7 +1010,7 @@ public Object call(Context cx, Scriptable scope, PacketDistributor.sendToServer( new Box3JSNetwork.GUIServerboundPayload(0, title, rows, slotsJson, - 0, "", 0, false, false)); + 0, "", 0, false, false, "", false)); return Context.javaToJS(proxy, scope); } diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSGuiProxy.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSGuiProxy.java index 1fb4c5c..3b0cf1c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSGuiProxy.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/client/Box3JSGuiProxy.java @@ -1,6 +1,7 @@ package com.box3lab.box3js.client; import com.box3lab.box3js.Box3JSNetwork; +import com.box3lab.box3js.script.Box3ScriptUtils; import com.box3lab.box3js.script.GameEventHandlerToken; import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; @@ -8,6 +9,7 @@ import net.neoforged.neoforge.network.PacketDistributor; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; @@ -35,7 +37,35 @@ public void setItem(int slot, String itemId) { public void setItem(int slot, String itemId, int count) { PacketDistributor.sendToServer( - new Box3JSNetwork.GUIServerboundPayload(1, "", 0, "", slot, itemId, Math.max(1, Math.min(count, 64)), false, false)); + new Box3JSNetwork.GUIServerboundPayload(1, "", 0, "", slot, itemId, Math.max(1, Math.min(count, 64)), false, false, "", false)); + } + + /** Overload with options: { count?, lore?: string[], enchanted?: boolean } */ + public void setItem(int slot, String itemId, NativeObject options) { + int count = 1; + String loreJson = ""; + boolean enchanted = false; + if (options.containsKey("count")) { + count = Math.max(1, Math.min(((Number) options.get("count")).intValue(), 64)); + } + if (options.containsKey("lore")) { + Object lore = options.get("lore"); + if (lore instanceof NativeArray arr) { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < arr.getLength(); i++) { + if (i > 0) sb.append(","); + String line = arr.get(i).toString(); + sb.append("\"").append(line.replace("\\", "\\\\").replace("\"", "\\\"")).append("\""); + } + sb.append("]"); + loreJson = sb.toString(); + } + } + if (options.containsKey("enchanted")) { + enchanted = Box3ScriptUtils.coerceBool(options.get("enchanted")); + } + PacketDistributor.sendToServer( + new Box3JSNetwork.GUIServerboundPayload(1, "", 0, "", slot, itemId, count, false, false, loreJson, enchanted)); } public NativeObject getItem(int slot) { @@ -86,7 +116,7 @@ private void ensureCallbacksRegistered(boolean wantsSlotClick, boolean wantsClos closeRegistered = closeRegistered || sendClose; PacketDistributor.sendToServer( new Box3JSNetwork.GUIServerboundPayload(2, "", 0, "", 0, "", 0, - sendSlotClick, sendClose)); + sendSlotClick, sendClose, "", false)); } // ---- Close ---- @@ -98,7 +128,7 @@ public void close() { } }); PacketDistributor.sendToServer( - new Box3JSNetwork.GUIServerboundPayload(3, "", 0, "", 0, "", 0, false, false)); + new Box3JSNetwork.GUIServerboundPayload(3, "", 0, "", 0, "", 0, false, false, "", false)); } // ---- Internal: called from GUIClientboundPayload handler (on netty thread → render thread) ---- 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 596fdc1..a3499aa 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 @@ -7,6 +7,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; @@ -359,6 +360,17 @@ public void addEffect(String effectId, int duration, int amplifier, boolean hide le.addEffect(new MobEffectInstance(effect, duration, amplifier, false, !hideParticles, true)); } + // ---- Particles (MC extension) ---- + + /** Spawn particles at the entity's center position. */ + public void spawnParticle(String type, int count, double dx, double dy, double dz, double speed) { + var particle = Box3ScriptUtils.lookupParticle(type); + if (particle == null) return; + if (entity.level() instanceof ServerLevel sl) { + sl.sendParticles(particle, entity.getX(), entity.getY() + entity.getBbHeight() / 2.0, entity.getZ(), count, dx, dy, dz, speed); + } + } + // ---- Equipment (MC extension) ---- public void setEquipment(String slot, String itemId) { diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiController.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiController.java index f2e6b0d..c943249 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiController.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiController.java @@ -1,9 +1,14 @@ package com.box3lab.box3js.script; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.ItemLore; import org.mozilla.javascript.NativeObject; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -29,11 +34,37 @@ void setMenu(Box3JSScriptContainerMenu menu) { } public void setItem(int slot, String itemId, int count) { + setItem(slot, itemId, count, "", false); + } + + public void setItem(int slot, String itemId, int count, String loreJson, boolean enchanted) { if (menu == null) return; var item = Box3ScriptUtils.lookupItem(itemId); if (item == null) return; if (slot < 0 || slot >= menu.getContainer().getContainerSize()) return; - menu.getContainer().setItem(slot, new ItemStack(item, Math.max(1, Math.min(count, 64)))); + ItemStack stack = new ItemStack(item, Math.max(1, Math.min(count, 64))); + if (loreJson != null && !loreJson.isEmpty()) { + List lines = new ArrayList<>(); + String inner = loreJson.trim(); + if (inner.startsWith("[") && inner.endsWith("]")) { + inner = inner.substring(1, inner.length() - 1); + for (String part : inner.split(",")) { + part = part.trim(); + if (part.startsWith("\"") && part.endsWith("\"")) { + String line = part.substring(1, part.length() - 1) + .replace("\\\\", "\\").replace("\\\"", "\""); + lines.add(Component.literal(line)); + } + } + } + if (!lines.isEmpty()) { + stack.set(DataComponents.LORE, new ItemLore(lines, lines)); + } + } + if (enchanted) { + stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true); + } + menu.getContainer().setItem(slot, stack); } public NativeObject getItem(int slot) { diff --git a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiServerHandler.java b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiServerHandler.java index 31d4aea..61993d7 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiServerHandler.java +++ b/Box3JS-NeoForge-1.21.1/src/main/java/com/box3lab/box3js/script/Box3JSGuiServerHandler.java @@ -120,10 +120,10 @@ public static void handleOpen(ServerPlayer player, String title, int rows, Strin } } - public static void handleSetItem(ServerPlayer player, int slot, String itemId, int count) { + public static void handleSetItem(ServerPlayer player, int slot, String itemId, int count, String loreJson, boolean enchanted) { ActiveGui gui = activeGuis.get(player.getUUID()); if (gui != null) { - gui.controller.setItem(slot, itemId, count); + gui.controller.setItem(slot, itemId, count, loreJson, enchanted); } } 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 5318613..86b3dfd 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 @@ -539,6 +539,23 @@ public void spawnParticleCircle(GameVector3 pos, double radius, String type, int spawnParticleCircle(pos.x, pos.y, pos.z, radius, type, count); } + // ---- Particle Line ---- + + public void spawnParticleLine(double x1, double y1, double z1, double x2, double y2, double z2, String type, int count) { + var particle = Box3ScriptUtils.lookupParticle(type); + if (particle == null) return; + for (int i = 0; i < count; i++) { + double t = count > 1 ? (double) i / (count - 1) : 0; + double x = x1 + (x2 - x1) * t; + double y = y1 + (y2 - y1) * t; + double z = z1 + (z2 - z1) * t; + server.overworld().sendParticles(particle, x, y, z, 1, 0, 0, 0, 0); + } + } + public void spawnParticleLine(GameVector3 from, GameVector3 to, String type, int count) { + spawnParticleLine(from.x, from.y, from.z, to.x, to.y, to.z, type, count); + } + // ---- Drop Item ---- public void dropItem(double x, double y, double z, String itemId, int count) { 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 2e52427..7417a6a 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 @@ -18,8 +18,11 @@ import java.util.*; import java.util.function.Consumer; +import com.box3lab.box3js.Box3JS; import com.box3lab.box3js.standalone.Box3ScriptCompiler; import com.mojang.logging.LogUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; import net.neoforged.fml.ModList; import org.slf4j.Logger; @@ -189,11 +192,18 @@ public Object eval(String code) { } } - /** Report error to the current errorReporter (player), or just log if none. */ + /** Report error to the current errorReporter (player), broadcast to Box3JS clients, and log. */ void reportError(String msg) { LOGGER.error(msg); if (errorReporter != null) errorReporter.accept(msg); + if (server != null) { + for (ServerPlayer sp : server.getPlayerList().getPlayers()) { + if (Box3JS.clientsWithBox3JS.contains(sp.getUUID())) { + sp.sendSystemMessage(Component.literal("[Box3JS] " + msg).withStyle(ChatFormatting.RED)); + } + } + } } /** diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/gui.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/gui.d.ts index 320b7fa..f76f16c 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/gui.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/client/gui.d.ts @@ -43,6 +43,14 @@ interface GuiController { * @param count - @zh 数量(可选,默认 1) @en Count (optional, default 1) */ setItem(slot: number, itemId: string, count?: number): void; + /** + * @zh 设置指定槽位的物品(带选项)。 + * @en Sets the item in the given slot with options. + * @param slot - @zh 槽位索引 @en Slot index + * @param itemId - @zh 物品 ID @en Item ID + * @param options - @zh 选项 @en Options + */ + setItem(slot: number, itemId: string, options: { count?: number; lore?: string[]; enchanted?: boolean }): void; /** * @zh 获取指定槽位的物品。 diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts index f494cc2..d20ca88 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/entity.d.ts @@ -320,6 +320,20 @@ interface GameEntity { */ remove(): void; + // ── @zh 粒子 @en Particles ── + + /** + * @zh 在实体中心位置生成粒子。 + * @en Spawns particles at the entity's center position. + * @param type - @zh 粒子类型 ID (如 "minecraft:flame") @en Particle type ID (e.g. "minecraft:flame") + * @param count - @zh 数量 @en Number of particles + * @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, count: number, dx: number, dy: number, dz: number, speed: number): void; + // ── @zh 传送 @en Teleport ── /** diff --git a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts index 1e5467a..2e2eb52 100644 --- a/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts +++ b/Box3JS-NeoForge-1.21.1/src/main/resources/assets/box3js/template/types/server/world.d.ts @@ -333,6 +333,13 @@ interface GameWorld { spawnParticleCircle(x: number, y: number, z: number, radius: number, type: string, count: number): void; spawnParticleCircle(pos: GameVector3, radius: number, type: string, count: number): void; + /** + * @zh 在两点之间生成粒子线。 + * @en Spawns a line of particles evenly spaced between two points. + */ + spawnParticleLine(x1: number, y1: number, z1: number, x2: number, y2: number, z2: number, type: string, count: number): void; + spawnParticleLine(from: GameVector3, to: GameVector3, type: string, count: number): void; + // ── @zh 掉落物 @en Drop Item ── /** @zh 在指定坐标掉落物品。 @en Drops an item stack at the given position. */