yeow-api 0.2.116 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeow-api",
3
- "version": "0.2.116",
3
+ "version": "0.3.0",
4
4
  "description": "Yeow API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/block.ts CHANGED
@@ -3,6 +3,11 @@ import type { TaskOptions } from './task.js';
3
3
  import { Location } from './location.js';
4
4
  import type { ItemStack } from './item.js';
5
5
  import { Material } from './material.js';
6
+ import { Inventory } from './inventory.js';
7
+ import {
8
+ getBlock as pdcGet, setBlock as pdcSet, hasBlock as pdcHas, removeBlock as pdcRemove,
9
+ keysBlock as pdcKeys, getAllBlock as pdcGetAll,
10
+ } from './pdc.js';
6
11
 
7
12
  /** 方块状态(Minecraft 原版键值对枚举,值统一为字符串)。 */
8
13
  export interface BlockState {
@@ -81,4 +86,54 @@ export class Block {
81
86
  if (tool) p.item = tool;
82
87
  return call<boolean>('block.breakNaturally', p, options);
83
88
  }
89
+
90
+ // ── PDC(方块持久数据;需要 location) ──
91
+
92
+ private requirePos(): { world: string; x: number; y: number; z: number } {
93
+ const pos = this.pos;
94
+ if (!pos) throw new Error('block has no location (create with world.getBlock)');
95
+ return pos;
96
+ }
97
+
98
+ /** 容器方块的内容物(Chest / Furnace / Hopper / Barrel 等 Container;需要 location;非容器方块抛错)。 */
99
+ getInventory(): Inventory {
100
+ const { world, x, y, z } = this.requirePos();
101
+ return Inventory.ofBlock(world, x, y, z);
102
+ }
103
+
104
+ /** 读取并 JSON 反序列化(无值返回 null;旧数据非 JSON 时原样返回字符串)。 */
105
+ getPdc<T = unknown>(key: string, options?: TaskOptions): Promise<T | null> {
106
+ const { world, x, y, z } = this.requirePos();
107
+ return pdcGet(world, x, y, z, key, options);
108
+ }
109
+
110
+ /** 任意可 JSON 序列化的值自动序列化后写入。 */
111
+ setPdc(key: string, value: unknown, options?: TaskOptions): Promise<boolean> {
112
+ const { world, x, y, z } = this.requirePos();
113
+ return pdcSet(world, x, y, z, key, value, options);
114
+ }
115
+
116
+ /** 键是否存在。 */
117
+ hasPdc(key: string, options?: TaskOptions): Promise<boolean> {
118
+ const { world, x, y, z } = this.requirePos();
119
+ return pdcHas(world, x, y, z, key, options);
120
+ }
121
+
122
+ /** 移除键。 */
123
+ removePdc(key: string, options?: TaskOptions): Promise<boolean> {
124
+ const { world, x, y, z } = this.requirePos();
125
+ return pdcRemove(world, x, y, z, key, options);
126
+ }
127
+
128
+ /** 全部键(完整 key 格式,含命名空间)。 */
129
+ keysPdc(options?: TaskOptions): Promise<string[]> {
130
+ const { world, x, y, z } = this.requirePos();
131
+ return pdcKeys(world, x, y, z, options);
132
+ }
133
+
134
+ /** 全量读取本插件命名空间的键值(每个值 JSON 反序列化)。 */
135
+ getAllPdc(options?: TaskOptions): Promise<Record<string, unknown>> {
136
+ const { world, x, y, z } = this.requirePos();
137
+ return pdcGetAll(world, x, y, z, options);
138
+ }
84
139
  }
package/src/command.ts CHANGED
@@ -79,11 +79,23 @@ export function registerCommand(name: string, options: CommandOptions, taskOptio
79
79
  } else {
80
80
  const result = completerFn(sender, data.args);
81
81
  if (result && typeof result.then === 'function') {
82
- $send('task', {
83
- type: 'command.tabComplete',
84
- params: { callbackId: compCbId, completions: [] },
85
- cb: '',
86
- });
82
+ // 异步 completer:await 结果后再回传(原实现立即发空补全、Promise 结果被丢弃)
83
+ (result as Promise<string[]>).then(
84
+ (list) => {
85
+ $send('task', {
86
+ type: 'command.tabComplete',
87
+ params: { callbackId: compCbId, completions: list || [] },
88
+ cb: '',
89
+ });
90
+ },
91
+ () => {
92
+ $send('task', {
93
+ type: 'command.tabComplete',
94
+ params: { callbackId: compCbId, completions: [] },
95
+ cb: '',
96
+ });
97
+ },
98
+ );
87
99
  } else {
88
100
  $send('task', {
89
101
  type: 'command.tabComplete',
package/src/core.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  /// <reference path="global.d.ts" />
2
+ /// <reference path="yeow-dev.d.ts" />
2
3
 
3
- export { call } from './task.js';
4
+ export { call, callBatch, postBatch } from './task.js';
5
+ export type { BatchTask } from './task.js';
4
6
  export { Location } from './location.js';
5
7
  export { Player } from './player.js';
6
8
  export { World } from './world.js';
7
9
  export { Chunk, ChunkSnapshot, ChunkTopSnapshot } from './chunk.js';
8
10
  export type { ChunkData } from './chunk.js';
11
+ export type { WorldBorderInfo } from './world.js';
9
12
  export { Entity, LivingEntity } from './entity.js';
10
13
  export type { BoundingBox } from './entity.js';
11
14
  export { Block } from './block.js';
@@ -54,15 +57,18 @@ export { path } from './path.js';
54
57
  export { listen, respond, close, request, requestSync } from './http.js';
55
58
  export type { RespondOptions } from './http.js';
56
59
  export { logError } from './log-error.js';
57
- export { InstanceId, GUIHandle, BossBarHandle, InventoryHandle } from './instance-id.js';
58
- export type { ItemStack } from './item.js';
60
+ export { InstanceId, BossBarHandle, InventoryHandle } from './instance-id.js';
61
+ export { ItemStack } from './item.js';
62
+ export type { ItemMeta, PotionEffectData, AttributeModifierData } from './item.js';
59
63
  export type { PotionEffect } from './potion.js';
60
64
  export { addPotionEffect, removePotionEffect, clearPotionEffects, getActivePotionEffects } from './potion.js';
61
65
  export { playSound, stopSound, stopAllSounds } from './sound.js';
62
66
  export type { ParticleOptions } from './particle.js';
63
67
  export { spawnParticle } from './particle.js';
64
- export { get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove, keys as pdcKeys,
65
- getBlock as pdcGetBlock, setBlock as pdcSetBlock, hasBlock as pdcHasBlock, removeBlock as pdcRemoveBlock } from './pdc.js';
68
+ export { get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove, keys as pdcKeys, getAll as pdcGetAll,
69
+ getRaw as pdcGetRaw, setRaw as pdcSetRaw, getAllRaw as pdcGetAllRaw,
70
+ getBlock as pdcGetBlock, setBlock as pdcSetBlock, hasBlock as pdcHasBlock, removeBlock as pdcRemoveBlock,
71
+ keysBlock as pdcKeysBlock, getAllBlock as pdcGetAllBlock, getBlockRaw as pdcGetBlockRaw, setBlockRaw as pdcSetBlockRaw, getAllBlockRaw as pdcGetAllBlockRaw } from './pdc.js';
66
72
  export { createBossBar, destroy as destroyBossBar,
67
73
  setTitle as setBossBarTitle, setProgress as setBossBarProgress,
68
74
  setColor as setBossBarColor, setStyle as setBossBarStyle,
@@ -70,8 +76,6 @@ export { createBossBar, destroy as destroyBossBar,
70
76
  removePlayer as removeBossBarPlayer, removeAll as removeAllBossBarPlayers,
71
77
  addFlag as addBossBarFlag, removeFlag as removeBossBarFlag } from './bossbar.js';
72
78
  export type { BossBarOptions } from './bossbar.js';
73
- export { createGUI, destroy as destroyGUI, open as openGUI,
74
- close as closeGUI, setItem as setGUIItem, fill as fillGUI, clear as clearGUI } from './gui.js';
75
79
  export type { AdvancementProgress } from './advancement.js';
76
80
  export { grant as grantAdvancement, revoke as revokeAdvancement,
77
81
  getProgress as getAdvancementProgress, awardCriteria, revokeCriteria } from './advancement.js';
package/src/entity.ts CHANGED
@@ -86,6 +86,30 @@ export class Entity {
86
86
  removeSync(options?: TaskOptions): void { call('entity.remove', { uuid: this.uuid }, options); }
87
87
  teleport(loc: Location, options?: TaskOptions): Promise<void> { return post('entity.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
88
88
  teleportSync(loc: Location, options?: TaskOptions): void { call('entity.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
89
+
90
+ // ── 基础补齐(2026-08-13) ──
91
+
92
+ /** 速度向量 { x, y, z }(方块/秒)。 */
93
+ get velocity(): { x: number; y: number; z: number } { return call<{ x: number; y: number; z: number }>('entity.getVelocity', { uuid: this.uuid }); }
94
+ set velocity(v: { x: number; y: number; z: number }) { call('entity.setVelocity', { uuid: this.uuid, x: v.x, y: v.y, z: v.z }); }
95
+ getVelocity(options?: TaskOptions): Promise<{ x: number; y: number; z: number }> { return post('entity.getVelocity', { uuid: this.uuid }, options); }
96
+ setVelocity(v: { x: number; y: number; z: number }, options?: TaskOptions): Promise<void> { return post('entity.setVelocity', { uuid: this.uuid, x: v.x, y: v.y, z: v.z }, options); }
97
+
98
+ /** 着火刻数(0 = 未着火)。 */
99
+ get fireTicks(): number { return call<number>('entity.getFireTicks', { uuid: this.uuid }); }
100
+ set fireTicks(v: number) { call('entity.setFireTicks', { uuid: this.uuid, value: v }); }
101
+ getFireTicks(options?: TaskOptions): Promise<number> { return post<number>('entity.getFireTicks', { uuid: this.uuid }, options); }
102
+ setFireTicks(v: number, options?: TaskOptions): Promise<void> { return post('entity.setFireTicks', { uuid: this.uuid, value: v }, options); }
103
+
104
+ /** 已存活刻数。 */
105
+ get ticksLived(): number { return call<number>('entity.getTicksLived', { uuid: this.uuid }); }
106
+ set ticksLived(v: number) { call('entity.setTicksLived', { uuid: this.uuid, value: v }); }
107
+ getTicksLived(options?: TaskOptions): Promise<number> { return post<number>('entity.getTicksLived', { uuid: this.uuid }, options); }
108
+ setTicksLived(v: number, options?: TaskOptions): Promise<void> { return post('entity.setTicksLived', { uuid: this.uuid, value: v }, options); }
109
+
110
+ /** 是否在地面上。 */
111
+ get isOnGround(): boolean { return call<boolean>('entity.isOnGround', { uuid: this.uuid }); }
112
+ isOnGroundAsync(options?: TaskOptions): Promise<boolean> { return post<boolean>('entity.isOnGround', { uuid: this.uuid }, options); }
89
113
  }
90
114
 
91
115
  export class LivingEntity extends Entity {
@@ -99,4 +123,23 @@ export class LivingEntity extends Entity {
99
123
 
100
124
  get isDead(): boolean { return call<boolean>('entity.isDead', { uuid: this.uuid }); }
101
125
  isDeadAsync(options?: TaskOptions): Promise<boolean> { return post<boolean>('entity.isDead', { uuid: this.uuid }, options); }
126
+
127
+ /** 施加伤害(可带伤害来源实体 uuid)。 */
128
+ damage(amount: number, damager?: string, options?: TaskOptions): Promise<void> {
129
+ return post('entity.damage', { uuid: this.uuid, amount, damager }, options);
130
+ }
131
+ damageSync(amount: number, damager?: string, options?: TaskOptions): void {
132
+ call('entity.damage', { uuid: this.uuid, amount, damager }, options);
133
+ }
134
+
135
+ /**
136
+ * 设置 AI 目标(**不保证必然生效**——取决于实体类型/寻路能力)。
137
+ * 目标为实体(targetUuid)或位置(world+x+y+z,可带 speed)。
138
+ */
139
+ setTarget(target: { targetUuid: string } | { world: string; x: number; y: number; z: number; speed?: number }, options?: TaskOptions): Promise<boolean> {
140
+ return post<boolean>('entity.setTarget', { uuid: this.uuid, ...target }, options);
141
+ }
142
+ setTargetSync(target: { targetUuid: string } | { world: string; x: number; y: number; z: number; speed?: number }, options?: TaskOptions): boolean {
143
+ return call<boolean>('entity.setTarget', { uuid: this.uuid, ...target }, options);
144
+ }
102
145
  }
package/src/event.ts CHANGED
@@ -149,6 +149,8 @@ export interface InventoryOpenEvent {
149
149
  export interface InventoryCloseEvent {
150
150
  player: Player;
151
151
  inventoryType: string;
152
+ /** 若关闭的是 Yeow 自定义 Inventory(Inventory.create 创建):该 Inventory 的句柄 id(inventory.toString());否则缺省。 */
153
+ inventoryId?: string;
152
154
  cancelled?: boolean;
153
155
  }
154
156
  export interface ServerPingEvent {
@@ -286,6 +288,8 @@ export interface InventoryClickEvent {
286
288
  isShiftClick: boolean;
287
289
  clickedItem: ItemData | null;
288
290
  cursorItem: ItemData | null;
291
+ /** 若点击发生在 Yeow 自定义 Inventory(Inventory.create 创建):该 Inventory 的句柄 id(inventory.toString());否则缺省。 */
292
+ inventoryId?: string;
289
293
  cancelled?: boolean;
290
294
  }
291
295
  interface ItemData {
@@ -368,6 +372,8 @@ function adaptEvent<K extends keyof EventMap>(type: K, data: RawEvent): EventMap
368
372
  'playerBucketEmpty', 'playerExpChange', 'playerLevelChange',
369
373
  'playerGameModeChange', 'foodLevelChange', 'blockBreak',
370
374
  'blockPlace', 'inventoryOpen', 'inventoryClose',
375
+ 'playerAdvancementDone', 'playerToggleSneak', 'playerToggleFlight',
376
+ 'inventoryClick', 'playerResourcePackStatus',
371
377
  ] as K[];
372
378
  if (hasPlayer.includes(type) && data.player) {
373
379
  wrap.player = Player.getSync(data.player as string);
package/src/global.d.ts CHANGED
@@ -59,10 +59,7 @@ declare global {
59
59
  | undefined;
60
60
  }
61
61
 
62
- // yeow-dev:构建期虚拟模块(由 Yeow 构建器按 importer 所属依赖项注入命名空间)。
63
- // 插件未安装 yeow-dev 时此声明生效;类型与实际构建行为一致。
64
- declare module 'yeow-dev' {
65
- export function getAssetsPath(path: string): string;
66
- }
67
-
68
62
  export {};
63
+
64
+ // yeow-dev 备选声明见同目录 yeow-dev.d.ts(ambient,非模块文件)——
65
+ // 插件未安装 yeow-dev(构建期虚拟模块)时生效;已安装时以 yeow-dev 包内类型为准。
@@ -1,4 +1,7 @@
1
1
  let _seq = 0;
2
+ // 每个插件 QuickJS 上下文独立(模块级变量彼此隔离);随机种子保证
3
+ // 跨插件/跨 Worker 生成的 id 全局唯一——id 是不透明句柄,不携带任何业务信息。
4
+ const _seed = Math.random().toString(36).slice(2, 12);
2
5
  const _gcQueue: string[] = [];
3
6
  const _gcReg = typeof FinalizationRegistry !== 'undefined'
4
7
  ? new FinalizationRegistry<string>((raw: string) => { _gcQueue.push(raw); })
@@ -9,8 +12,8 @@ export class InstanceId {
9
12
  readonly _raw: string;
10
13
  readonly _managed: boolean;
11
14
 
12
- constructor(prefix: string) {
13
- this._raw = prefix + '_' + (++_seq);
15
+ constructor() {
16
+ this._raw = _seed + '_' + (++_seq);
14
17
  this._managed = true;
15
18
  _gcReg?.register(this, this._raw);
16
19
  }
@@ -25,6 +28,5 @@ export class InstanceId {
25
28
  toString(): string { return this._raw; }
26
29
  }
27
30
 
28
- export class GUIHandle extends InstanceId { constructor() { super('gui'); } }
29
- export class BossBarHandle extends InstanceId { constructor() { super('boss'); } }
30
- export class InventoryHandle extends InstanceId { constructor() { super('inv'); } }
31
+ export class BossBarHandle extends InstanceId {}
32
+ export class InventoryHandle extends InstanceId {}
package/src/inventory.ts CHANGED
@@ -1,42 +1,174 @@
1
1
  import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
+ import type { ItemStack } from './item.js';
4
+ import { InstanceId } from './instance-id.js';
3
5
 
4
- interface ItemData {
5
- type: string;
6
- amount: number;
7
- }
8
-
6
+ /**
7
+ * Inventory —— 统一容器抽象,三种持有者:
8
+ * - **玩家物品栏**:`player.inventory`
9
+ * - **容器方块**(Chest / Furnace / Hopper / Barrel / Dispenser / Dropper / BrewingStand 等):`block.getInventory()`
10
+ * - **自定义 Inventory**(自定义箱子界面,原 GUI):`Inventory.create(size, title)`
11
+ *
12
+ * ```js
13
+ * // 玩家
14
+ * await player.inventory.setItem(0, ItemStack.create('minecraft:diamond'));
15
+ * // 容器方块(需方块有 location)
16
+ * const chest = await world.getBlock(x, y, z);
17
+ * await chest.getInventory().getItem(0);
18
+ * // 自定义 Inventory
19
+ * const inv = await Inventory.create(27, '<gold>Shop</gold>');
20
+ * await inv.open(player.uuid);
21
+ * eventOn('inventoryClick', (e) => {
22
+ * if (e.inventoryId === inv.toString()) { handleClick(e); }
23
+ * });
24
+ * await inv.destroy();
25
+ * ```
26
+ */
9
27
  export class Inventory {
10
- constructor(private readonly uuid: string) {}
28
+ /** 寻址方式:'player' | 'block' | 'custom'。 */
29
+ private readonly kind: 'player' | 'block' | 'custom';
30
+ /** 玩家寻址:uuid。 */
31
+ private readonly uuid?: string;
32
+ /** 方块寻址:世界坐标。 */
33
+ private readonly block?: { world: string; x: number; y: number; z: number };
34
+ /** 自定义寻址:句柄 id。 */
35
+ private readonly id?: string;
36
+
37
+ private constructor(kind: 'player' | 'block' | 'custom', uuid?: string, block?: { world: string; x: number; y: number; z: number }, id?: string) {
38
+ this.kind = kind;
39
+ this.uuid = uuid;
40
+ this.block = block;
41
+ this.id = id;
42
+ }
43
+
44
+ /** 玩家物品栏(等价于 `new Inventory('player', uuid)`,通常经 `player.inventory` 获取)。 */
45
+ static ofPlayer(uuid: string): Inventory {
46
+ return new Inventory('player', uuid);
47
+ }
48
+
49
+ /** 容器方块(需方块有 location;通常经 `block.getInventory()` 获取)。 */
50
+ static ofBlock(world: string, x: number, y: number, z: number): Inventory {
51
+ return new Inventory('block', undefined, { world, x, y, z });
52
+ }
53
+
54
+ /** 创建自定义 Inventory(size 为槽位数,须为 9 的倍数:9/18/27/36/45/54;title 支持 MiniMessage)。 */
55
+ static async create(size: number, title: string, options?: TaskOptions): Promise<Inventory> {
56
+ const h = new InstanceId();
57
+ await post('inventory.create', { id: h.toString(), size, title }, options);
58
+ return new Inventory('custom', undefined, undefined, h.toString());
59
+ }
60
+
61
+ /** 自定义 Inventory 的句柄 id(与 inventoryClick/Close 事件的 `inventoryId` 字段一致)。 */
62
+ toString(): string {
63
+ return this.id ?? '';
64
+ }
11
65
 
12
- getItem(slot: number, options?: TaskOptions): Promise<ItemData | null> {
13
- return post<ItemData | null>('inventory.getItem', { uuid: this.uuid, slot }, options);
66
+ /** 持有者类型:'PLAYER' | 'CUSTOM' | 方块实体类型名(如 'CHEST')。 */
67
+ getType(options?: TaskOptions): Promise<string> {
68
+ return post<string>('inventory.getType', this.address(), options);
14
69
  }
15
- getItemSync(slot: number, options?: TaskOptions): ItemData | null {
16
- return call<ItemData | null>('inventory.getItem', { uuid: this.uuid, slot }, options);
70
+
71
+ /** 容器槽位数。 */
72
+ getSize(options?: TaskOptions): Promise<number> {
73
+ return post<number>('inventory.getSize', this.address(), options);
17
74
  }
18
- setItem(slot: number, itemType: string, amount?: number, options?: TaskOptions): Promise<void> {
19
- return post('inventory.setItem', { uuid: this.uuid, slot, itemType, amount }, options);
75
+
76
+ /** 全槽位快照数组(空槽为 null,长度 = 容器槽位数)。 */
77
+ getContents(options?: TaskOptions): Promise<(ItemStack | null)[]> {
78
+ return post<(ItemStack | null)[]>('inventory.getContents', this.address(), options);
20
79
  }
21
- setItemSync(slot: number, itemType: string, amount?: number, options?: TaskOptions): void {
22
- call('inventory.setItem', { uuid: this.uuid, slot, itemType, amount }, options);
80
+ getContentsSync(options?: TaskOptions): (ItemStack | null)[] {
81
+ return call<(ItemStack | null)[]>('inventory.getContents', this.address(), options);
23
82
  }
24
- addItem(itemType: string, amount?: number, options?: TaskOptions): Promise<void> {
25
- return post('inventory.addItem', { uuid: this.uuid, itemType, amount }, options);
83
+
84
+ /** 整容器写入(items 长度可与容器不匹配:短数组只写前段,长数组忽略超出;null 清空对应槽位)。 */
85
+ setContents(items: (ItemStack | null)[], options?: TaskOptions): Promise<boolean> {
86
+ return post<boolean>('inventory.setContents', { ...this.address(), items }, options);
26
87
  }
27
- addItemSync(itemType: string, amount?: number, options?: TaskOptions): void {
28
- call('inventory.addItem', { uuid: this.uuid, itemType, amount }, options);
88
+ setContentsSync(items: (ItemStack | null)[], options?: TaskOptions): boolean {
89
+ return call<boolean>('inventory.setContents', { ...this.address(), items }, options);
29
90
  }
30
- removeItem(itemType: string, amount?: number, options?: TaskOptions): Promise<void> {
31
- return post('inventory.removeItem', { uuid: this.uuid, itemType, amount }, options);
91
+
92
+ /** 读取槽位物品快照(含 meta;空槽返回 null)。 */
93
+ getItem(slot: number, options?: TaskOptions): Promise<ItemStack | null> {
94
+ return post<ItemStack | null>('inventory.getItem', { ...this.address(), slot }, options);
32
95
  }
33
- removeItemSync(itemType: string, amount?: number, options?: TaskOptions): void {
34
- call('inventory.removeItem', { uuid: this.uuid, itemType, amount }, options);
96
+ getItemSync(slot: number, options?: TaskOptions): ItemStack | null {
97
+ return call<ItemStack | null>('inventory.getItem', { ...this.address(), slot }, options);
35
98
  }
36
- clear(slot?: number, options?: TaskOptions): Promise<void> {
37
- return post('inventory.clear', { uuid: this.uuid, slot }, options);
99
+
100
+ /** 设置槽位(完整 ItemStack meta;传 null 清空槽位)。 */
101
+ setItem(slot: number, item: ItemStack | null, options?: TaskOptions): Promise<boolean> {
102
+ return post<boolean>('inventory.setItem', { ...this.address(), slot, item }, options);
103
+ }
104
+ setItemSync(slot: number, item: ItemStack | null, options?: TaskOptions): boolean {
105
+ return call<boolean>('inventory.setItem', { ...this.address(), slot, item }, options);
106
+ }
107
+
108
+ /** 批量设置多个槽位(分页/布局用);item 传 null 清空对应槽位。 */
109
+ setItems(slots: number[], item: ItemStack | null, options?: TaskOptions): Promise<boolean> {
110
+ return post<boolean>('inventory.setItems', { ...this.address(), slots, item }, options);
111
+ }
112
+
113
+ /** 用同一物品填充全部槽位。 */
114
+ fill(item: ItemStack, options?: TaskOptions): Promise<boolean> {
115
+ return post<boolean>('inventory.fill', { ...this.address(), item }, options);
38
116
  }
39
- clearSync(slot?: number, options?: TaskOptions): void {
40
- call('inventory.clear', { uuid: this.uuid, slot }, options);
117
+
118
+ /** 添加物品到空位:返回**未放入数量**(0 = 全部放入;玩家物品栏溢出部分掉落在地上,同样返回 0)。 */
119
+ addItem(item: ItemStack, options?: TaskOptions): Promise<number> {
120
+ return post<number>('inventory.addItem', { ...this.address(), item }, options);
121
+ }
122
+ addItemSync(item: ItemStack, options?: TaskOptions): number {
123
+ return call<number>('inventory.addItem', { ...this.address(), item }, options);
124
+ }
125
+
126
+ /** 移除指定物品(按类型 + meta 匹配,amount 默认 1)。返回**未移除数量**(0 = 全部移除成功)。 */
127
+ removeItem(item: ItemStack, options?: TaskOptions): Promise<number> {
128
+ return post<number>('inventory.removeItem', { ...this.address(), item }, options);
129
+ }
130
+ removeItemSync(item: ItemStack, options?: TaskOptions): number {
131
+ return call<number>('inventory.removeItem', { ...this.address(), item }, options);
132
+ }
133
+
134
+ /** 清空(slot 可选,不传清空全部)。 */
135
+ clear(slot?: number, options?: TaskOptions): Promise<boolean> {
136
+ return post<boolean>('inventory.clear', { ...this.address(), slot }, options);
137
+ }
138
+ clearSync(slot?: number, options?: TaskOptions): boolean {
139
+ return call<boolean>('inventory.clear', { ...this.address(), slot }, options);
140
+ }
141
+
142
+ // ── 自定义 Inventory 专属 ─────────────────────────────────────────
143
+
144
+ /** 为玩家打开(自定义 Inventory)。 */
145
+ open(uuid: string, options?: TaskOptions): Promise<boolean> {
146
+ return post<boolean>('inventory.open', { ...this.address(), uuid }, options);
147
+ }
148
+
149
+ /** 关闭所有查看者(自定义 Inventory)。 */
150
+ close(options?: TaskOptions): Promise<boolean> {
151
+ return post<boolean>('inventory.close', this.address(), options);
152
+ }
153
+
154
+ /** 仅关闭指定玩家(自定义 Inventory)。 */
155
+ closePlayer(uuid: string, options?: TaskOptions): Promise<boolean> {
156
+ return post<boolean>('inventory.closePlayer', { ...this.address(), uuid }, options);
157
+ }
158
+
159
+ /** 当前查看者 uuid 列表(自定义 Inventory)。 */
160
+ getViewers(options?: TaskOptions): Promise<string[]> {
161
+ return post<string[]>('inventory.getViewers', this.address(), options);
162
+ }
163
+
164
+ /** 销毁并关闭所有查看者(自定义 Inventory)。 */
165
+ destroy(options?: TaskOptions): Promise<boolean> {
166
+ return post<boolean>('inventory.destroy', this.address(), options);
167
+ }
168
+
169
+ private address(): Record<string, unknown> {
170
+ if (this.kind === 'player') return { uuid: this.uuid };
171
+ if (this.kind === 'block') return { ...this.block! };
172
+ return { id: this.id };
41
173
  }
42
174
  }
package/src/item.ts CHANGED
@@ -1,13 +1,63 @@
1
+ /**
2
+ * ItemStack:物品纯数据描述符(值语义,快照——不绑定真实物品)。
3
+ *
4
+ * meta 字段设计(2026-08-13 扩展):
5
+ * - 遵循 Paper / Mojang 现行行为;字段名避开已废弃的 Java 方法名(如不叫 setDamage 语义)
6
+ * - 全部字段可选;运行时不支持的字段静默忽略(跨版本兼容)
7
+ * - 文本字段(displayName/lore)支持 MiniMessage
8
+ */
9
+
10
+ /** 自定义药水效果(PotionMeta)。 */
11
+ export interface PotionEffectData {
12
+ type: string; // 药水效果名(如 'speed' / 'SPEED',不区分大小写)
13
+ duration?: number; // 刻(默认 200)
14
+ amplifier?: number; // 等级(默认 0)
15
+ ambient?: boolean; // 是否环境粒子(信标样式,默认 false)
16
+ particles?: boolean; // 是否显示粒子(默认 true)
17
+ }
18
+
19
+ /** 属性修饰符(AttributeModifier)。 */
20
+ export interface AttributeModifierData {
21
+ attribute: string; // Bukkit Attribute 枚举名(如 'ATTACK_DAMAGE' / 'MOVEMENT_SPEED')
22
+ amount: number;
23
+ operation: 'ADD_NUMBER' | 'ADD_SCALED_AMOUNT' | 'MULTIPLY_SCALED_1';
24
+ slot?: string; // 适用槽位:mainhand / offhand / feet / legs / chest / head / body / any(默认 any)
25
+ }
26
+
27
+ export interface ItemMeta {
28
+ displayName?: string; // MiniMessage
29
+ lore?: string[]; // 每行 MiniMessage
30
+ customModelData?: number;
31
+ unbreakable?: boolean;
32
+ hideTooltip?: boolean;
33
+ enchantments?: Record<string, number>; // 附魔 key(如 'minecraft:sharpness')→ 等级
34
+ itemFlags?: string[]; // ItemFlag 枚举名(如 'HIDE_ATTRIBUTES')
35
+ damage?: number; // 耐久损伤值
36
+ color?: string | { r: number; g: number; b: number }; // 皮革盔甲染色 / 自定义药水颜色('#RRGGBB' 或 rgb 对象)
37
+ potionEffects?: PotionEffectData[]; // 自定义药水效果(仅药水类物品生效)
38
+ skullOwner?: string; // 玩家头颅:玩家名 / UUID / base64 纹理值
39
+ attributeModifiers?: AttributeModifierData[];
40
+ }
41
+
1
42
  export interface ItemStack {
2
43
  type: string;
3
44
  amount?: number;
4
- meta?: {
5
- displayName?: string;
6
- lore?: string[];
7
- customModelData?: number;
8
- unbreakable?: boolean;
9
- hideTooltip?: boolean;
10
- enchantments?: Record<string, number>;
11
- itemFlags?: string[];
12
- };
45
+ meta?: ItemMeta;
46
+ }
47
+
48
+ export namespace ItemStack {
49
+ /** 构造物品(便利函数,等价于手写 `{ type, amount, meta }`)。 */
50
+ export function create(type: string, amount = 1, meta?: ItemMeta): ItemStack {
51
+ return meta ? { type, amount, meta } : { type, amount };
52
+ }
53
+
54
+ /** 深拷贝(快照语义:修改副本不影响原对象)。 */
55
+ export function clone(item: ItemStack): ItemStack {
56
+ return JSON.parse(JSON.stringify(item)) as ItemStack;
57
+ }
58
+
59
+ /** 深度相等(序列化比较)。 */
60
+ export function equals(a: ItemStack, b: ItemStack): boolean {
61
+ return JSON.stringify(a) === JSON.stringify(b);
62
+ }
13
63
  }
package/src/pdc.ts CHANGED
@@ -1,31 +1,85 @@
1
1
  import { post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
 
4
- export function get(uuid: string, key: string, options?: TaskOptions): Promise<string | null> {
4
+ // ══════════════════════════════════════════════════════════════════
5
+ // PDC:基于字符串的 K-V 存储。set/get 自动 JSON 序列化/反序列化——
6
+ // 开发者无需手写 JSON.stringify / JSON.parse(旧数据非 JSON 时 get 原样返回)。
7
+ // getRaw/setRaw 提供底层字符串读写(跨版本/跨语言数据交换用)。
8
+ // key 规则:无冒号的裸 key 使用**插件命名空间**(跨插件互不冲突);
9
+ // 显式命名空间用 `ns:key`(如 `myplugin:score`)。
10
+ // ══════════════════════════════════════════════════════════════════
11
+
12
+ // ── 底层(raw 字符串) ────────────────────────────────────────────
13
+
14
+ /** 读取原始字符串(无值返回 null)。 */
15
+ export function getRaw(uuid: string, key: string, options?: TaskOptions): Promise<string | null> {
5
16
  return post<string | null>('pdc.get', { uuid, key }, options);
6
17
  }
7
18
 
8
- export function set(uuid: string, key: string, value: string, options?: TaskOptions): Promise<boolean> {
19
+ /** 写入原始字符串。 */
20
+ export function setRaw(uuid: string, key: string, value: string, options?: TaskOptions): Promise<boolean> {
9
21
  return post<boolean>('pdc.set', { uuid, key, value }, options);
10
22
  }
11
23
 
24
+ /** 键是否存在。 */
12
25
  export function has(uuid: string, key: string, options?: TaskOptions): Promise<boolean> {
13
26
  return post<boolean>('pdc.has', { uuid, key }, options);
14
27
  }
15
28
 
29
+ /** 移除键。 */
16
30
  export function remove(uuid: string, key: string, options?: TaskOptions): Promise<boolean> {
17
31
  return post<boolean>('pdc.remove', { uuid, key }, options);
18
32
  }
19
33
 
34
+ /** 全部键(完整 key 格式,含命名空间)。 */
20
35
  export function keys(uuid: string, options?: TaskOptions): Promise<string[]> {
21
36
  return post<string[]>('pdc.keys', { uuid }, options);
22
37
  }
23
38
 
24
- export function getBlock(world: string, x: number, y: number, z: number, key: string, options?: TaskOptions): Promise<string | null> {
39
+ /** 全量读取本插件命名空间的键值(value 为原始字符串)。 */
40
+ export function getAllRaw(uuid: string, options?: TaskOptions): Promise<Record<string, string>> {
41
+ return post<Record<string, string>>('pdc.getAll', { uuid }, options);
42
+ }
43
+
44
+ // ── 推荐:JSON 自动序列化 ─────────────────────────────────────────
45
+
46
+ /** 读取并 JSON 反序列化(无值返回 null;旧数据非 JSON 时原样返回字符串)。 */
47
+ export async function get<T = unknown>(uuid: string, key: string, options?: TaskOptions): Promise<T | null> {
48
+ const raw = await getRaw(uuid, key, options);
49
+ if (raw == null) return null;
50
+ try {
51
+ return JSON.parse(raw) as T;
52
+ } catch {
53
+ return raw as unknown as T;
54
+ }
55
+ }
56
+
57
+ /** 任意可 JSON 序列化的值自动序列化后写入。 */
58
+ export function set(uuid: string, key: string, value: unknown, options?: TaskOptions): Promise<boolean> {
59
+ return setRaw(uuid, key, JSON.stringify(value), options);
60
+ }
61
+
62
+ /** 全量读取本插件命名空间的键值(每个值 JSON 反序列化;非 JSON 值原样保留)。 */
63
+ export async function getAll(uuid: string, options?: TaskOptions): Promise<Record<string, unknown>> {
64
+ const raw = await getAllRaw(uuid, options);
65
+ const out: Record<string, unknown> = {};
66
+ for (const [k, v] of Object.entries(raw)) {
67
+ try {
68
+ out[k] = JSON.parse(v);
69
+ } catch {
70
+ out[k] = v;
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+
76
+ // ── Block 变体(世界坐标) ────────────────────────────────────────
77
+
78
+ export function getBlockRaw(world: string, x: number, y: number, z: number, key: string, options?: TaskOptions): Promise<string | null> {
25
79
  return post<string | null>('pdc.get', { world, x, y, z, key }, options);
26
80
  }
27
81
 
28
- export function setBlock(world: string, x: number, y: number, z: number, key: string, value: string, options?: TaskOptions): Promise<boolean> {
82
+ export function setBlockRaw(world: string, x: number, y: number, z: number, key: string, value: string, options?: TaskOptions): Promise<boolean> {
29
83
  return post<boolean>('pdc.set', { world, x, y, z, key, value }, options);
30
84
  }
31
85
 
@@ -36,3 +90,38 @@ export function hasBlock(world: string, x: number, y: number, z: number, key: st
36
90
  export function removeBlock(world: string, x: number, y: number, z: number, key: string, options?: TaskOptions): Promise<boolean> {
37
91
  return post<boolean>('pdc.remove', { world, x, y, z, key }, options);
38
92
  }
93
+
94
+ export function keysBlock(world: string, x: number, y: number, z: number, options?: TaskOptions): Promise<string[]> {
95
+ return post<string[]>('pdc.keys', { world, x, y, z }, options);
96
+ }
97
+
98
+ export function getAllBlockRaw(world: string, x: number, y: number, z: number, options?: TaskOptions): Promise<Record<string, string>> {
99
+ return post<Record<string, string>>('pdc.getAll', { world, x, y, z }, options);
100
+ }
101
+
102
+ export async function getBlock<T = unknown>(world: string, x: number, y: number, z: number, key: string, options?: TaskOptions): Promise<T | null> {
103
+ const raw = await getBlockRaw(world, x, y, z, key, options);
104
+ if (raw == null) return null;
105
+ try {
106
+ return JSON.parse(raw) as T;
107
+ } catch {
108
+ return raw as unknown as T;
109
+ }
110
+ }
111
+
112
+ export function setBlock(world: string, x: number, y: number, z: number, key: string, value: unknown, options?: TaskOptions): Promise<boolean> {
113
+ return setBlockRaw(world, x, y, z, key, JSON.stringify(value), options);
114
+ }
115
+
116
+ export async function getAllBlock(world: string, x: number, y: number, z: number, options?: TaskOptions): Promise<Record<string, unknown>> {
117
+ const raw = await getAllBlockRaw(world, x, y, z, options);
118
+ const out: Record<string, unknown> = {};
119
+ for (const [k, v] of Object.entries(raw)) {
120
+ try {
121
+ out[k] = JSON.parse(v);
122
+ } catch {
123
+ out[k] = v;
124
+ }
125
+ }
126
+ return out;
127
+ }
package/src/player.ts CHANGED
@@ -4,6 +4,11 @@ import { Location, LocationData } from './location.js';
4
4
  import type { ItemStack } from './item.js';
5
5
  import type { Message } from './message.js';
6
6
  import type { Permission } from './permission.js';
7
+ import { Inventory } from './inventory.js';
8
+ import {
9
+ get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove,
10
+ keys as pdcKeys, getAll as pdcGetAll,
11
+ } from './pdc.js';
7
12
 
8
13
  interface PlayerData {
9
14
  uuid: string;
@@ -29,6 +34,11 @@ export class Player {
29
34
 
30
35
  get name(): string { return this._name ?? ''; }
31
36
 
37
+ /** 玩家物品栏(统一 Inventory 容器抽象)。 */
38
+ get inventory(): Inventory {
39
+ return Inventory.ofPlayer(this.uuid);
40
+ }
41
+
32
42
  get ping(): number { return call<number>('player.getPing', { uuid: this.uuid }); }
33
43
  getPing(options?: TaskOptions): Promise<number> { return post<number>('player.getPing', { uuid: this.uuid }, options); }
34
44
 
@@ -138,10 +148,10 @@ export class Player {
138
148
  giveExpSync(amount: number, options?: TaskOptions): void { call('player.giveExp', { uuid: this.uuid, amount }, options); }
139
149
  /** 检查权限(经 Yeow 权限检查:`permissionCheck` 事件优先,无处理时回退 Bukkit)。node 可为权限节点对象。 */
140
150
  hasPermission(node: string | Permission, options?: TaskOptions): Promise<boolean> {
141
- return post<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? node : { node: node.node } }, options);
151
+ return post<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? { node } : { node: node.node } }, options);
142
152
  }
143
153
  hasPermissionSync(node: string | Permission, options?: TaskOptions): boolean {
144
- return call<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? node : { node: node.node } }, options);
154
+ return call<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? { node } : { node: node.node } }, options);
145
155
  }
146
156
  /** 以玩家身份执行命令(**不含 `/` 前缀**,如 `say hi`;前缀会自动剥离;与服务器 `dispatchCommand`(控制台)相对)。 */
147
157
  performCommand(cmd: string, options?: TaskOptions): Promise<boolean> {
@@ -164,4 +174,75 @@ export class Player {
164
174
  getItemInMainHandSync(options?: TaskOptions): ItemStack | null { return call<ItemStack | null>('player.getItemInMainHand', { uuid: this.uuid }, options); }
165
175
  getItemInOffHand(options?: TaskOptions): Promise<ItemStack | null> { return post<ItemStack | null>('player.getItemInOffHand', { uuid: this.uuid }, options); }
166
176
  getItemInOffHandSync(options?: TaskOptions): ItemStack | null { return call<ItemStack | null>('player.getItemInOffHand', { uuid: this.uuid }, options); }
177
+
178
+ /** 设置主手物品(完整 ItemStack 含 meta;传 null 清空)。 */
179
+ setItemInMainHand(item: ItemStack | null, options?: TaskOptions): Promise<boolean> {
180
+ return post<boolean>('player.setItemInMainHand', { uuid: this.uuid, item }, options);
181
+ }
182
+ setItemInMainHandSync(item: ItemStack | null, options?: TaskOptions): boolean {
183
+ return call<boolean>('player.setItemInMainHand', { uuid: this.uuid, item }, options);
184
+ }
185
+ /** 设置副手物品(完整 ItemStack 含 meta;传 null 清空)。 */
186
+ setItemInOffHand(item: ItemStack | null, options?: TaskOptions): Promise<boolean> {
187
+ return post<boolean>('player.setItemInOffHand', { uuid: this.uuid, item }, options);
188
+ }
189
+ setItemInOffHandSync(item: ItemStack | null, options?: TaskOptions): boolean {
190
+ return call<boolean>('player.setItemInOffHand', { uuid: this.uuid, item }, options);
191
+ }
192
+
193
+ /** 设置 Tab 列表 header/footer(MiniMessage;传 null 清空对应栏)。 */
194
+ sendTabHeader(header: string | null, footer: string | null, options?: TaskOptions): Promise<boolean> {
195
+ return post<boolean>('player.sendTabHeader', { uuid: this.uuid, header, footer }, options);
196
+ }
197
+ sendTabHeaderSync(header: string | null, footer: string | null, options?: TaskOptions): boolean {
198
+ return call<boolean>('player.sendTabHeader', { uuid: this.uuid, header, footer }, options);
199
+ }
200
+
201
+ /** 设置 Tab 列表显示名(传 null 恢复默认)。 */
202
+ setPlayerListName(name: string | null, options?: TaskOptions): Promise<boolean> {
203
+ return post<boolean>('player.setPlayerListName', { uuid: this.uuid, name }, options);
204
+ }
205
+ setPlayerListNameSync(name: string | null, options?: TaskOptions): boolean {
206
+ return call<boolean>('player.setPlayerListName', { uuid: this.uuid, name }, options);
207
+ }
208
+
209
+ /** 设置客户端世界边界(传 null 重置为服务端边界;centerX/centerZ 可选,指定边界中心)。 */
210
+ setBorder(size: number | null, centerX?: number, centerZ?: number, options?: TaskOptions): Promise<boolean> {
211
+ return post<boolean>('player.setBorder', { uuid: this.uuid, size, centerX, centerZ }, options);
212
+ }
213
+ setBorderSync(size: number | null, centerX?: number, centerZ?: number, options?: TaskOptions): boolean {
214
+ return call<boolean>('player.setBorder', { uuid: this.uuid, size, centerX, centerZ }, options);
215
+ }
216
+
217
+ // ── PDC(玩家持久数据) ──
218
+
219
+ /** 读取并 JSON 反序列化(无值返回 null;旧数据非 JSON 时原样返回字符串)。 */
220
+ getPdc<T = unknown>(key: string, options?: TaskOptions): Promise<T | null> {
221
+ return pdcGet(this.uuid, key, options);
222
+ }
223
+
224
+ /** 任意可 JSON 序列化的值自动序列化后写入。 */
225
+ setPdc(key: string, value: unknown, options?: TaskOptions): Promise<boolean> {
226
+ return pdcSet(this.uuid, key, value, options);
227
+ }
228
+
229
+ /** 键是否存在。 */
230
+ hasPdc(key: string, options?: TaskOptions): Promise<boolean> {
231
+ return pdcHas(this.uuid, key, options);
232
+ }
233
+
234
+ /** 移除键。 */
235
+ removePdc(key: string, options?: TaskOptions): Promise<boolean> {
236
+ return pdcRemove(this.uuid, key, options);
237
+ }
238
+
239
+ /** 全部键(完整 key 格式,含命名空间)。 */
240
+ keysPdc(options?: TaskOptions): Promise<string[]> {
241
+ return pdcKeys(this.uuid, options);
242
+ }
243
+
244
+ /** 全量读取本插件命名空间的键值(每个值 JSON 反序列化)。 */
245
+ getAllPdc(options?: TaskOptions): Promise<Record<string, unknown>> {
246
+ return pdcGetAll(this.uuid, options);
247
+ }
167
248
  }
package/src/scoreboard.ts CHANGED
@@ -39,6 +39,11 @@ export function deleteBoard(id: string, options?: TaskOptions): Promise<void> {
39
39
 
40
40
  // ── Objectives ──
41
41
 
42
+ /**
43
+ * 创建计分项。⚠️ **Folia 平台限制**:Folia 不支持注册新 objective(registerNewObjective
44
+ * 抛 UnsupportedOperationException)——在 Folia 上本调用会 reject(错误消息含
45
+ * "Folia does not support creating new objectives");已存在的 objective 会更新 displayName 后返回。
46
+ */
42
47
  export function createObjective(name: string, criteria: string, displayName: string, board?: string, options?: TaskOptions): Promise<ObjectiveInfo> {
43
48
  return post<ObjectiveInfo>('scoreboard.createObjective', { name, criteria, displayName, ...boardParam(board) }, options);
44
49
  }
@@ -69,6 +74,10 @@ export function resetScore(objective: string, entry: string, board?: string, opt
69
74
 
70
75
  // ── Teams ──
71
76
 
77
+ /**
78
+ * 创建队伍。⚠️ **Folia 平台限制**:Folia 不支持注册新 team(registerNewTeam 抛
79
+ * UnsupportedOperationException)——在 Folia 上本调用会 reject;已存在的 team 返回其信息。
80
+ */
72
81
  export function createTeam(name: string, board?: string, options?: TaskOptions): Promise<void> {
73
82
  return post('scoreboard.createTeam', { name, ...boardParam(board) }, options);
74
83
  }
package/src/task.ts CHANGED
@@ -47,6 +47,53 @@ export function call<T = unknown>(
47
47
  applyOptions(pld, options);
48
48
  const r = $send('task', pld);
49
49
  if (r == null) return undefined as T;
50
- if ((r as any)?.err) throw new Error((r as any).err);
50
+ if ((r as any)?.err) {
51
+ // 与 post() 对齐的错误上下文(type/task/Java 堆栈),2026-08-13 审计修复
52
+ const errObj = r as any;
53
+ const msg = errObj.type ? `[${errObj.type}] ${errObj.err}` : errObj.err;
54
+ const e = new Error(msg);
55
+ if (errObj.stack) {
56
+ e.stack += '\n --- runtime executer error(for reference) ---\n' + errObj.stack;
57
+ }
58
+ (e as any).javaType = errObj.type || null;
59
+ (e as any).taskType = errObj.task || null;
60
+ throw e;
61
+ }
51
62
  return r as T;
52
63
  }
64
+
65
+ // ══════════════════════════════════════════════════════════════════
66
+ // 批量任务:一次提交任务数组,结果按原顺序返回(逐个独立执行,无原子性)。
67
+ // 依赖包可基于此构造自己的批量优化(如批量发物品、批量写方块)。
68
+ // ══════════════════════════════════════════════════════════════════
69
+
70
+ export interface BatchTask {
71
+ type: string;
72
+ params?: Record<string, unknown>;
73
+ priority?: 'high' | 'normal' | 'low';
74
+ }
75
+
76
+ /** 同步批量:阻塞直到全部任务完成,返回结果数组(顺序对应 tasks;单个任务失败时对应项为 `{err}` 对象)。 */
77
+ export function callBatch(tasks: BatchTask[]): unknown[] {
78
+ if (tasks.length === 0) return [];
79
+ const r = $send('task', { tasks });
80
+ if (r == null) return [];
81
+ if ((r as any)?.err) throw new Error((r as any).err);
82
+ return r as unknown[];
83
+ }
84
+
85
+ /** 异步批量:全部任务完成后 Promise resolve 结果数组(顺序对应 tasks;单个任务失败时对应项为 `{err}` 对象)。 */
86
+ export function postBatch<T = unknown>(tasks: BatchTask[]): Promise<T[]> {
87
+ return new Promise((resolve, reject) => {
88
+ if (tasks.length === 0) { resolve([] as T[]); return; }
89
+ const cbId = _registerCallback((result: any) => {
90
+ if (result?.err) {
91
+ const msg = result.type ? `[${result.type}] ${result.err}` : result.err;
92
+ reject(new Error(msg));
93
+ } else {
94
+ resolve(result as T[]);
95
+ }
96
+ });
97
+ $send('task', { tasks, cb: cbId });
98
+ });
99
+ }
package/src/world.ts CHANGED
@@ -9,6 +9,17 @@ interface WorldData {
9
9
  name: string;
10
10
  }
11
11
 
12
+ /** 世界边界快照。 */
13
+ export interface WorldBorderInfo {
14
+ centerX: number;
15
+ centerZ: number;
16
+ size: number;
17
+ damageAmount: number;
18
+ damageBuffer: number;
19
+ warningDistance: number;
20
+ warningTime: number;
21
+ }
22
+
12
23
  export class World {
13
24
  static get(name: string, options?: TaskOptions): Promise<World | null> {
14
25
  return post<WorldData>('world.get', { name }, options).then((d) => (d ? new World(d.name) : null));
@@ -46,6 +57,60 @@ export class World {
46
57
  getHighestBlockYSync(x: number, z: number, options?: TaskOptions): number {
47
58
  return call<number>('world.getHighestBlockY', { world: this.name, x, z }, options);
48
59
  }
60
+
61
+ // ── 世界信息(2026-08-13) ──
62
+
63
+ /** 世界种子。 */
64
+ get seed(): number { return call<number>('world.getSeed', { world: this.name }); }
65
+ getSeed(options?: TaskOptions): Promise<number> { return post<number>('world.getSeed', { world: this.name }, options); }
66
+
67
+ /** 环境:NORMAL / NETHER / THE_END。 */
68
+ get environment(): string { return call<string>('world.getEnvironment', { world: this.name }); }
69
+ getEnvironment(options?: TaskOptions): Promise<string> { return post<string>('world.getEnvironment', { world: this.name }, options); }
70
+
71
+ /** 世界类型(可能返回 null——平台不支持时)。 */
72
+ get worldType(): string | null { return call<string | null>('world.getWorldType', { world: this.name }); }
73
+ getWorldType(options?: TaskOptions): Promise<string | null> { return post<string | null>('world.getWorldType', { world: this.name }, options); }
74
+
75
+ /** 全部游戏规则名。 */
76
+ get gameRules(): string[] { return call<string[]>('world.getGameRules', { world: this.name }); }
77
+ getGameRules(options?: TaskOptions): Promise<string[]> { return post<string[]>('world.getGameRules', { world: this.name }, options); }
78
+
79
+ // ── WorldBorder(2026-08-13) ──
80
+
81
+ /** 世界边界快照。 */
82
+ get border(): WorldBorderInfo {
83
+ return call<WorldBorderInfo>('world.getBorder', { world: this.name });
84
+ }
85
+ getBorder(options?: TaskOptions): Promise<WorldBorderInfo> {
86
+ return post<WorldBorderInfo>('world.getBorder', { world: this.name }, options);
87
+ }
88
+ /** 边界中心。 */
89
+ setBorderCenter(x: number, z: number, options?: TaskOptions): Promise<boolean> {
90
+ return post<boolean>('world.setBorderCenter', { world: this.name, x, z }, options);
91
+ }
92
+ setBorderCenterSync(x: number, z: number, options?: TaskOptions): boolean {
93
+ return call<boolean>('world.setBorderCenter', { world: this.name, x, z }, options);
94
+ }
95
+ /** 边界半径(方块)。 */
96
+ setBorderSize(size: number, options?: TaskOptions): Promise<boolean> {
97
+ return post<boolean>('world.setBorderSize', { world: this.name, size }, options);
98
+ }
99
+ setBorderSizeSync(size: number, options?: TaskOptions): boolean {
100
+ return call<boolean>('world.setBorderSize', { world: this.name, size }, options);
101
+ }
102
+ /** 边界伤害(amount 每秒伤害;buffer 无伤缓冲距离)。 */
103
+ setBorderDamage(amount?: number, buffer?: number, options?: TaskOptions): Promise<boolean> {
104
+ return post<boolean>('world.setBorderDamage', { world: this.name, amount, buffer }, options);
105
+ }
106
+ /** 边界警告(distance 方块距离;time 秒)。 */
107
+ setBorderWarning(distance?: number, time?: number, options?: TaskOptions): Promise<boolean> {
108
+ return post<boolean>('world.setBorderWarning', { world: this.name, distance, time }, options);
109
+ }
110
+ /** 边界平滑移动(from → to,seconds 秒)。 */
111
+ setBorderMoving(from: number, to: number, seconds: number, options?: TaskOptions): Promise<boolean> {
112
+ return post<boolean>('world.setBorderMoving', { world: this.name, from, to, seconds }, options);
113
+ }
49
114
  getChunkAt(x: number, z: number, options?: TaskOptions): Promise<Chunk> {
50
115
  return post<ChunkData>('world.getChunkAt', { world: this.name, x, z }, options).then((d) => Chunk.from(d));
51
116
  }
@@ -0,0 +1,7 @@
1
+ // 备选声明:插件**未安装** yeow-dev(构建期虚拟模块)时提供 getAssetsPath 类型。
2
+ // 注意:本文件必须为**非模块**(无 import/export)——ambient `declare module` 在
3
+ // 模块文件中会被视为模块增强(需要先 import 才生效),独立使用报 Cannot find module。
4
+ // 已安装 yeow-dev 时以 node_modules/yeow-dev/index.d.ts 为准(内容一致)。
5
+ declare module 'yeow-dev' {
6
+ export function getAssetsPath(path: string): string;
7
+ }
package/src/gui.ts DELETED
@@ -1,34 +0,0 @@
1
- import { post } from './task.js';
2
- import type { TaskOptions } from './task.js';
3
- import { GUIHandle } from './instance-id.js';
4
- import type { ItemStack } from './item.js';
5
-
6
- export async function createGUI(size: number, title: string, options?: TaskOptions): Promise<GUIHandle> {
7
- const h = new GUIHandle();
8
- await post('gui.create', { id: h.toString(), size, title }, options);
9
- return h;
10
- }
11
-
12
- export function destroy(id: GUIHandle, options?: TaskOptions): Promise<void> {
13
- return post('gui.destroy', { id: id.toString() }, options);
14
- }
15
-
16
- export function open(id: GUIHandle, uuid: string, options?: TaskOptions): Promise<void> {
17
- return post('gui.open', { id: id.toString(), uuid }, options);
18
- }
19
-
20
- export function close(id: GUIHandle, options?: TaskOptions): Promise<void> {
21
- return post('gui.close', { id: id.toString() }, options);
22
- }
23
-
24
- export function setItem(id: GUIHandle, slot: number, item: ItemStack, options?: TaskOptions): Promise<void> {
25
- return post('gui.setItem', { id: id.toString(), slot, item }, options);
26
- }
27
-
28
- export function fill(id: GUIHandle, item: ItemStack, options?: TaskOptions): Promise<void> {
29
- return post('gui.fill', { id: id.toString(), item }, options);
30
- }
31
-
32
- export function clear(id: GUIHandle, options?: TaskOptions): Promise<void> {
33
- return post('gui.clear', { id: id.toString() }, options);
34
- }