yeow-api 0.4.0 → 0.4.2

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.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Yeow API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/block.ts CHANGED
@@ -58,8 +58,6 @@ export class Block {
58
58
 
59
59
  isSolid(options?: TaskOptions): Promise<boolean> { return Material.isSolid(this.type, options); }
60
60
  isSolidSync(options?: TaskOptions): boolean { return Material.isSolidSync(this.type, options); }
61
- isLiquid(options?: TaskOptions): Promise<boolean> { return Material.isLiquid(this.type, options); }
62
- isLiquidSync(options?: TaskOptions): boolean { return Material.isLiquidSync(this.type, options); }
63
61
  isAir(options?: TaskOptions): Promise<boolean> { return Material.isAir(this.type, options); }
64
62
  isAirSync(options?: TaskOptions): boolean { return Material.isAirSync(this.type, options); }
65
63
 
package/src/core.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference path="global.d.ts" />
2
2
  /// <reference path="yeow-dev.d.ts" />
3
3
 
4
- export { call, callBatch, postBatch } from './task.js';
4
+ export { call, post, callBatch, postBatch } from './task.js';
5
5
  export type { BatchTask } from './task.js';
6
6
  export { Location } from './location.js';
7
7
  export { Player } from './player.js';
package/src/env.ts CHANGED
@@ -12,6 +12,8 @@ export interface EnvInfo {
12
12
  yeow: { platform: string; version: string };
13
13
  /** epoch 微秒时间戳。 */
14
14
  now: number;
15
+ /** 插件数据目录路径(如 `plugins/my-plugin`;Worker 中为主插件目录)。 */
16
+ pluginDir: string;
15
17
  }
16
18
 
17
19
  /** 获取运行时环境信息(同步;含微秒时间戳)。 */
package/src/event.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { call } from './task.js';
2
2
  import { Player } from './player.js';
3
3
  import { Location } from './location.js';
4
+ import type { ItemStack } from './item.js';
4
5
  import type { Message } from './message.js';
5
6
 
6
7
  // ── Event Data Interfaces ──────────────────────────────────────────
@@ -31,7 +32,7 @@ export interface PlayerInteractEvent {
31
32
  player: Player;
32
33
  action: string;
33
34
  material: string | null;
34
- block: { x: number; y: number; z: number; type: string } | null;
35
+ block: { location: Location; type: string } | null;
35
36
  cancelled?: boolean;
36
37
  }
37
38
  export interface PlayerCommandEvent {
@@ -53,14 +54,12 @@ export interface PlayerRespawnEvent {
53
54
  }
54
55
  export interface PlayerDropItemEvent {
55
56
  player: Player;
56
- itemType: string;
57
- amount: number;
57
+ item: ItemStack;
58
58
  cancelled?: boolean;
59
59
  }
60
60
  export interface PlayerPickupItemEvent {
61
61
  player: Player;
62
- itemType: string;
63
- amount: number;
62
+ item: ItemStack;
64
63
  cancelled?: boolean;
65
64
  }
66
65
  export interface PlayerBucketFillEvent {
@@ -111,10 +110,7 @@ export interface EntityDeathEvent {
111
110
  export interface EntitySpawnEvent {
112
111
  entity: string;
113
112
  entityType: string;
114
- x: number;
115
- y: number;
116
- z: number;
117
- world: string;
113
+ location: Location;
118
114
  cancelled?: boolean;
119
115
  }
120
116
  export interface ProjectileLaunchEvent {
@@ -126,18 +122,14 @@ export interface ProjectileLaunchEvent {
126
122
  export interface BlockBreakEvent {
127
123
  player: Player;
128
124
  block: string;
129
- x: number;
130
- y: number;
131
- z: number;
125
+ location: Location;
132
126
  cancelled?: boolean;
133
127
  }
134
128
  export interface BlockPlaceEvent {
135
129
  player: Player;
136
130
  block: string;
137
131
  blockAgainst: string;
138
- x: number;
139
- y: number;
140
- z: number;
132
+ location: Location;
141
133
  cancelled?: boolean;
142
134
  }
143
135
  export interface InventoryOpenEvent {
@@ -177,7 +169,7 @@ export interface PlayerTeleportEvent {
177
169
  }
178
170
  export interface PlayerItemConsumeEvent {
179
171
  player: Player;
180
- itemType: string;
172
+ item: ItemStack;
181
173
  cancelled?: boolean;
182
174
  }
183
175
  export interface PlayerAdvancementDoneEvent {
@@ -199,9 +191,7 @@ export interface PlayerToggleFlightEvent {
199
191
  export interface EntityExplodeEvent {
200
192
  entity: string;
201
193
  entityType: string;
202
- x: number;
203
- y: number;
204
- z: number;
194
+ location: Location;
205
195
  blockCount: number;
206
196
  cancelled?: boolean;
207
197
  }
@@ -220,35 +210,27 @@ export interface ProjectileHitEvent {
220
210
  entity: string;
221
211
  projectileType: string;
222
212
  hitEntity: string | null;
223
- hitBlock: { x: number; y: number; z: number; type: string } | null;
213
+ hitBlock: { location: Location; type: string } | null;
224
214
  cancelled?: boolean;
225
215
  }
226
216
  export interface BlockFadeEvent {
227
217
  block: string;
228
- x: number;
229
- y: number;
230
- z: number;
218
+ location: Location;
231
219
  cancelled?: boolean;
232
220
  }
233
221
  export interface BlockGrowEvent {
234
222
  block: string;
235
- x: number;
236
- y: number;
237
- z: number;
223
+ location: Location;
238
224
  cancelled?: boolean;
239
225
  }
240
226
  export interface BlockSpreadEvent {
241
227
  block: string;
242
- x: number;
243
- y: number;
244
- z: number;
228
+ location: Location;
245
229
  cancelled?: boolean;
246
230
  }
247
231
  export interface BlockExplodeEvent {
248
232
  block: string;
249
- x: number;
250
- y: number;
251
- z: number;
233
+ location: Location;
252
234
  cancelled?: boolean;
253
235
  }
254
236
  export interface ServerCommandEvent {
@@ -286,16 +268,12 @@ export interface InventoryClickEvent {
286
268
  isLeftClick: boolean;
287
269
  isRightClick: boolean;
288
270
  isShiftClick: boolean;
289
- clickedItem: ItemData | null;
290
- cursorItem: ItemData | null;
271
+ clickedItem: ItemStack | null;
272
+ cursorItem: ItemStack | null;
291
273
  /** 若点击发生在 Yeow 自定义 Inventory(Inventory.create 创建):该 Inventory 的句柄 id(inventory.toString());否则缺省。 */
292
274
  inventoryId?: string;
293
275
  cancelled?: boolean;
294
276
  }
295
- interface ItemData {
296
- type: string;
297
- amount: number;
298
- }
299
277
  export interface PlayerResourcePackStatusEvent {
300
278
  player: Player;
301
279
  status: string;
@@ -382,6 +360,28 @@ function adaptEvent<K extends keyof EventMap>(type: K, data: RawEvent): { event:
382
360
  if (data.to) initial.to = loc(data.to as Record<string, unknown>);
383
361
  if (data.respawnLocation) initial.respawnLocation = loc(data.respawnLocation as Record<string, unknown>);
384
362
 
363
+ // 平铺坐标事件(entitySpawn / blockBreak 等)→ 统一 `location: Location`(与 from/to 同形;
364
+ // 原始 x/y/z/world 字段保留在事件对象上,兼容旧代码读取)。
365
+ if (typeof data.x === 'number' && typeof data.y === 'number' && typeof data.z === 'number') {
366
+ initial.location = loc(data);
367
+ }
368
+ // 嵌套方块坐标 → `{ location, type }`
369
+ if (type === 'playerInteract' && data.block && typeof data.block === 'object') {
370
+ const b = data.block as Record<string, unknown>;
371
+ initial.block = { type: b.type as string, location: loc(b) };
372
+ }
373
+ if (type === 'projectileHit' && data.hitBlock && typeof data.hitBlock === 'object') {
374
+ const b = data.hitBlock as Record<string, unknown>;
375
+ initial.hitBlock = { type: b.type as string, location: loc(b) };
376
+ }
377
+ // 物品事件字段统一为 ItemStack({type, amount} 数据快照;原始 itemType/amount 保留)
378
+ if ((type === 'playerDropItem' || type === 'playerPickupItem') && data.itemType) {
379
+ initial.item = { type: data.itemType as string, amount: (data.amount as number) ?? 1 };
380
+ }
381
+ if (type === 'playerItemConsume' && data.itemType) {
382
+ initial.item = { type: data.itemType as string };
383
+ }
384
+
385
385
  // 修改收集:所有字段经 getter/setter——handler 直接赋值(e.xxx = ...)即记录为回写 mods。
386
386
  // cancelled 单独处理(仅可取消事件暴露;读取语义保持原状:未设置时返回 false)。
387
387
  const mods: Record<string, unknown> = {};
@@ -436,26 +436,42 @@ export function eventOn<K extends keyof EventMap>(
436
436
  const { event: wrapped, mods: mutatedMods } = adaptEvent(eventType, data);
437
437
  const eventId = data?._eventId;
438
438
 
439
- if (manualRelease) {
440
- const complete = (result?: Record<string, unknown>) => {
439
+ // 释放事件(event.complete)。处理器抛错时也必须释放——否则事件桥等待 complete
440
+ // 直到 5s 超时(EventBridge.timeoutMs),每次抛错都会卡住该事件并产生 event.timeout 噪音。
441
+ const release = (mods?: Record<string, unknown>) => {
442
+ try {
441
443
  $send('task', {
442
444
  type: 'event.complete',
443
- params: { eventId, mods: result },
445
+ params: { eventId, mods },
444
446
  cb: '',
445
447
  });
446
- };
447
- (handler as ManualHandler<typeof eventType>)(wrapped, complete);
448
+ } catch { /* 桥故障:保留原始错误(若有),事件由 Java 侧超时兜底 */ }
449
+ };
450
+
451
+ if (manualRelease) {
452
+ const complete = (result?: Record<string, unknown>) => release(result);
453
+ try {
454
+ (handler as ManualHandler<typeof eventType>)(wrapped, complete);
455
+ } catch (e) {
456
+ release(); // 处理器同步抛错:立即释放(complete 幂等,重复调用无副作用)
457
+ throw e;
458
+ }
448
459
  return;
449
460
  }
450
461
 
451
- const result = (handler as EventHandler<typeof eventType>)(wrapped);
452
- // 回写合并:返回值(mods)优先合并,事件参数直接赋值(e.xxx = ...)覆盖之——
453
- // 返回 Promise 时视为无修改(Promise 不展开),事件立即释放
454
- const mods: Record<string, unknown> = {
455
- ...(result && typeof result === 'object' ? result : {}),
456
- ...mutatedMods,
457
- };
458
- $send('task', { type: 'event.complete', params: { eventId, mods }, cb: '' });
462
+ let result: unknown;
463
+ try {
464
+ result = (handler as EventHandler<typeof eventType>)(wrapped);
465
+ } finally {
466
+ // 回写合并:返回值(mods)优先合并,事件参数直接赋值(e.xxx = ...)覆盖之——
467
+ // 返回 Promise 时视为无修改(Promise 不展开),事件立即释放。
468
+ // finally 保证处理器抛错时也释放事件。
469
+ const mods: Record<string, unknown> = {
470
+ ...(result && typeof result === 'object' ? result : {}),
471
+ ...mutatedMods,
472
+ };
473
+ release(mods);
474
+ }
459
475
  }, { persistent: true });
460
476
 
461
477
  const gh = globalThis as any;
package/src/http.ts CHANGED
@@ -11,10 +11,13 @@ interface HttpResult {
11
11
  export interface RespondOptions {
12
12
  /** HTTP 状态码(默认 200)。 */
13
13
  status?: number;
14
- /** 文本响应体(UTF-8)。 */
15
- body?: string;
16
- /** base64 编码的**二进制**响应体(与 body 互斥,优先)——如从 assets 读取的资源包等。 */
17
- bodyBase64?: string;
14
+ /**
15
+ * 响应体:`Uint8Array` 直接作为二进制写出;字符串按 `encoding` 解释
16
+ * (默认 UTF-8 文本;`'base64'` 时视为 base64 编码的二进制数据)。
17
+ */
18
+ body?: string | Uint8Array;
19
+ /** body 为字符串时的编码:`'utf8'`(默认)或 `'base64'`(base64 编码的二进制);Uint8Array 时忽略。 */
20
+ encoding?: 'utf8' | 'base64';
18
21
  /** 响应头。 */
19
22
  headers?: Record<string, string>;
20
23
  }
@@ -44,7 +47,17 @@ export function listen(callback: (req: Record<string, unknown>) => void, port?:
44
47
  }
45
48
 
46
49
  export function respond(serverId: string, connId: string, opts: RespondOptions = {}): void {
47
- _sendHttp({ t: 'respond', p: { serverId, connId, ...opts } });
50
+ const p: Record<string, unknown> = { serverId, connId };
51
+ if (opts.body instanceof Uint8Array) {
52
+ p.body = opts.body.toBase64(); // Uint8Array → 二进制(base64 承载)
53
+ p.encoding = 'base64';
54
+ } else {
55
+ p.body = opts.body;
56
+ if (opts.encoding === 'base64') p.encoding = 'base64';
57
+ }
58
+ if (opts.status !== undefined) p.status = opts.status;
59
+ if (opts.headers !== undefined) p.headers = opts.headers;
60
+ _sendHttp({ t: 'respond', p });
48
61
  }
49
62
 
50
63
  export function close(serverId: string): void {
@@ -2,11 +2,16 @@ let _seq = 0;
2
2
  // 每个插件 QuickJS 上下文独立(模块级变量彼此隔离);随机种子保证
3
3
  // 跨插件/跨 Worker 生成的 id 全局唯一——id 是不透明句柄,不携带任何业务信息。
4
4
  const _seed = Math.random().toString(36).slice(2, 12);
5
- const _gcQueue: string[] = [];
5
+ // GC 队列必须复用运行时(init.js)创建的全局数组(读已有 / 否则创建):
6
+ // 1) init.js 的 _flushGC 在每次消息分发后冲刷该数组并上报 gc-collect;
7
+ // 若此处新建数组覆盖全局,FinalizationRegistry 回调推入的 id 永远不会被冲刷。
8
+ // 2) 多 yeow-api 副本共存时(peer 范围不重叠、npm 安装独立副本),共享同一
9
+ // 数组可保证所有副本的句柄回收都进入同一条上报通道。
10
+ const _g = globalThis as any;
11
+ const _gcQueue: string[] = _g.__yeowGcQueue || (_g.__yeowGcQueue = []);
6
12
  const _gcReg = typeof FinalizationRegistry !== 'undefined'
7
13
  ? new FinalizationRegistry<string>((raw: string) => { _gcQueue.push(raw); })
8
14
  : null;
9
- (globalThis as any).__yeowGcQueue = _gcQueue;
10
15
 
11
16
  export class InstanceId {
12
17
  readonly _raw: string;
package/src/lifecycle.ts CHANGED
@@ -1,11 +1,30 @@
1
- const _initCbs: (() => void)[] = [];
2
- const _loadCbs: (() => void)[] = [];
3
- const _unloadCbs: (() => void)[] = [];
1
+ // 生命周期钩子注册表(共享全局数组)。
2
+ //
3
+ // yeow-api 副本共存(npm 语义化版本规则:peer 范围不重叠时依赖包自带
4
+ // 独立副本)时,每个副本的模块级数组必须指向**同一个**全局数组——
5
+ // 若各副本直接 `globalThis.__yeowInitCbs = <自身数组>`,后加载的副本会
6
+ // 覆盖先加载副本注册的回调(表现为 onLoad 不执行)。读已有 / 否则创建:
7
+ // 首个加载的副本创建数组,后续副本复用,运行时(init.js)读取同一数组。
8
+ const _global = globalThis as any;
9
+ const _initCbs: (() => void)[] = _global.__yeowInitCbs || (_global.__yeowInitCbs = []);
10
+ const _loadCbs: (() => void)[] = _global.__yeowLoadCbs || (_global.__yeowLoadCbs = []);
11
+ const _unloadCbs: (() => void)[] = _global.__yeowUnloadCbs || (_global.__yeowUnloadCbs = []);
4
12
 
5
- export function onInit(cb: () => void): void { _initCbs.push(cb); }
6
- export function onLoad(cb: () => void): void { _loadCbs.push(cb); }
7
- export function onUnload(cb: () => void): void { _unloadCbs.push(cb); }
13
+ // dev 模式栈追踪:在注册点捕获调用栈挂到回调函数上(__yeowNode)。
14
+ // 运行时(init.js)分发钩子时优先使用它作为该钩子的栈上下文——比 init.js
15
+ // 分发点的内部帧更能还原用户调用链(外层回调经 _getCurrentCbStack 连接)。
16
+ // 仅 $dev 时捕获(QuickJS 的 Error.stack 为懒构建 getter,无访问即无成本)。
17
+ function _attachNode(cb: () => void): void {
18
+ try {
19
+ if (!_global.$dev) return;
20
+ const getCb = _global._getCurrentCbStack;
21
+ (cb as any).__yeowNode = {
22
+ stack: new Error().stack,
23
+ outer: typeof getCb === 'function' ? getCb() : null,
24
+ };
25
+ } catch { /* 注册点捕获失败不影响钩子注册 */ }
26
+ }
8
27
 
9
- (globalThis as any).__yeowInitCbs = _initCbs;
10
- (globalThis as any).__yeowLoadCbs = _loadCbs;
11
- (globalThis as any).__yeowUnloadCbs = _unloadCbs;
28
+ export function onInit(cb: () => void): void { _attachNode(cb); _initCbs.push(cb); }
29
+ export function onLoad(cb: () => void): void { _attachNode(cb); _loadCbs.push(cb); }
30
+ export function onUnload(cb: () => void): void { _attachNode(cb); _unloadCbs.push(cb); }
package/src/material.ts CHANGED
@@ -37,7 +37,7 @@ export async function getItems(options?: TaskOptions): Promise<string[]> {
37
37
 
38
38
  /**
39
39
  * Material —— 材料级静态判断对象(不依赖坐标/状态)。
40
- * 基于方块类型(material)判断其固有属性:固体/液体/空气。
40
+ * 基于方块类型(material)判断其固有属性:固体/空气。
41
41
  */
42
42
  export const Material = {
43
43
  /** 是否为固体方块(基于类型,状态不影响)。 */
@@ -48,14 +48,6 @@ export const Material = {
48
48
  return call<boolean>('material.isSolid', { type }, options);
49
49
  },
50
50
 
51
- /** 是否为液体(水 / 熔岩)。 */
52
- isLiquid(type: string, options?: TaskOptions): Promise<boolean> {
53
- return post<boolean>('material.isLiquid', { type }, options);
54
- },
55
- isLiquidSync(type: string, options?: TaskOptions): boolean {
56
- return call<boolean>('material.isLiquid', { type }, options);
57
- },
58
-
59
51
  /** 是否为空气(空方块)。 */
60
52
  isAir(type: string, options?: TaskOptions): Promise<boolean> {
61
53
  return post<boolean>('material.isAir', { type }, options);
package/src/player.ts CHANGED
@@ -2,6 +2,7 @@ import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
  import { Location, LocationData } from './location.js';
4
4
  import type { ItemStack } from './item.js';
5
+ import { Block } from './block.js';
5
6
  import type { Message } from './message.js';
6
7
  import type { Permission } from './permission.js';
7
8
  import { Inventory } from './inventory.js';
@@ -79,7 +80,7 @@ export class Player {
79
80
  isOpAsync(options?: TaskOptions): Promise<boolean> { return post<boolean>('player.isOp', { uuid: this.uuid }, options); }
80
81
 
81
82
  get online(): boolean { return call<boolean>('player.isOnline', { uuid: this.uuid }); }
82
- getOnline(options?: TaskOptions): Promise<boolean> { return post<boolean>('player.isOnline', { uuid: this.uuid }, options); }
83
+ isOnlineAsync(options?: TaskOptions): Promise<boolean> { return post<boolean>('player.isOnline', { uuid: this.uuid }, options); }
83
84
 
84
85
  get isFlying(): boolean { return call<boolean>('player.isFlying', { uuid: this.uuid }); }
85
86
  set isFlying(v: boolean) { call('player.setFlying', { uuid: this.uuid, value: v }); }
@@ -170,6 +171,27 @@ export class Player {
170
171
  }
171
172
  teleport(loc: Location, options?: TaskOptions): Promise<void> { return post('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
172
173
  teleportSync(loc: Location, options?: TaskOptions): void { call('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
174
+ /** 向玩家发送假方块变化(仅客户端视觉,不改变真实世界)。block 为 Block 对象或字符串(同 world.setBlock,字符串无状态)。 */
175
+ sendBlockChange(location: Location, block: Block | string, options?: TaskOptions): Promise<void> {
176
+ const p: Record<string, unknown> = { uuid: this.uuid, ...location.toObject() };
177
+ if (typeof block === 'string') {
178
+ p.blockType = block;
179
+ } else {
180
+ p.blockType = block.type;
181
+ if (block.state && Object.keys(block.state).length > 0) p.state = block.state;
182
+ }
183
+ return post('player.sendBlockChange', p, options);
184
+ }
185
+ sendBlockChangeSync(location: Location, block: Block | string, options?: TaskOptions): void {
186
+ const p: Record<string, unknown> = { uuid: this.uuid, ...location.toObject() };
187
+ if (typeof block === 'string') {
188
+ p.blockType = block;
189
+ } else {
190
+ p.blockType = block.type;
191
+ if (block.state && Object.keys(block.state).length > 0) p.state = block.state;
192
+ }
193
+ call('player.sendBlockChange', p, options);
194
+ }
173
195
  sendActionBar(message: string | Message, options?: TaskOptions): Promise<void> { return post('player.sendActionBar', { uuid: this.uuid, message }, options); }
174
196
  sendActionBarSync(message: string | Message, options?: TaskOptions): void { call('player.sendActionBar', { uuid: this.uuid, message }, options); }
175
197
  sendResourcePack(url: string, hash?: string, prompt?: string | Message, force?: boolean, options?: TaskOptions): Promise<void> {
package/src/task.ts CHANGED
@@ -73,12 +73,23 @@ export interface BatchTask {
73
73
  priority?: 'high' | 'normal' | 'low';
74
74
  }
75
75
 
76
- /** 同步批量:阻塞直到全部任务完成,返回结果数组(顺序对应 tasks;单个任务失败时对应项为 `{err}` 对象)。 */
76
+ /** 同步批量:阻塞直到全部任务完成,返回结果数组(顺序对应 tasks;单个任务失败时对应项为 `{err}` 对象——与单任务 call/post 抛 Error 不同,批量保留部分结果)。 */
77
77
  export function callBatch(tasks: BatchTask[]): unknown[] {
78
78
  if (tasks.length === 0) return [];
79
79
  const r = $send('task', { tasks });
80
80
  if (r == null) return [];
81
- if ((r as any)?.err) throw new Error((r as any).err);
81
+ if ((r as any)?.err) {
82
+ // 与 call/post 对齐的错误上下文(type/task/Java 堆栈)
83
+ const errObj = r as any;
84
+ const msg = errObj.type ? `[${errObj.type}] ${errObj.err}` : errObj.err;
85
+ const e = new Error(msg);
86
+ if (errObj.stack) {
87
+ e.stack += '\n --- runtime executer error(for reference) ---\n' + errObj.stack;
88
+ }
89
+ (e as any).javaType = errObj.type || null;
90
+ (e as any).taskType = errObj.task || null;
91
+ throw e;
92
+ }
82
93
  return r as unknown[];
83
94
  }
84
95
 
package/src/world.ts CHANGED
@@ -3,6 +3,7 @@ import type { TaskOptions } from './task.js';
3
3
  import { Location, LocationData } from './location.js';
4
4
  import { Block } from './block.js';
5
5
  import type { BlockState } from './block.js';
6
+ import type { ItemStack } from './item.js';
6
7
  import { Chunk, ChunkData } from './chunk.js';
7
8
 
8
9
  interface WorldData {
@@ -103,14 +104,23 @@ export class World {
103
104
  setBorderDamage(amount?: number, buffer?: number, options?: TaskOptions): Promise<boolean> {
104
105
  return post<boolean>('world.setBorderDamage', { world: this.name, amount, buffer }, options);
105
106
  }
107
+ setBorderDamageSync(amount?: number, buffer?: number, options?: TaskOptions): boolean {
108
+ return call<boolean>('world.setBorderDamage', { world: this.name, amount, buffer }, options);
109
+ }
106
110
  /** 边界警告(distance 方块距离;time 秒)。 */
107
111
  setBorderWarning(distance?: number, time?: number, options?: TaskOptions): Promise<boolean> {
108
112
  return post<boolean>('world.setBorderWarning', { world: this.name, distance, time }, options);
109
113
  }
114
+ setBorderWarningSync(distance?: number, time?: number, options?: TaskOptions): boolean {
115
+ return call<boolean>('world.setBorderWarning', { world: this.name, distance, time }, options);
116
+ }
110
117
  /** 边界平滑移动(from → to,seconds 秒)。 */
111
118
  setBorderMoving(from: number, to: number, seconds: number, options?: TaskOptions): Promise<boolean> {
112
119
  return post<boolean>('world.setBorderMoving', { world: this.name, from, to, seconds }, options);
113
120
  }
121
+ setBorderMovingSync(from: number, to: number, seconds: number, options?: TaskOptions): boolean {
122
+ return call<boolean>('world.setBorderMoving', { world: this.name, from, to, seconds }, options);
123
+ }
114
124
  getChunkAt(x: number, z: number, options?: TaskOptions): Promise<Chunk> {
115
125
  return post<ChunkData>('world.getChunkAt', { world: this.name, x, z }, options).then((d) => Chunk.from(d));
116
126
  }
@@ -211,11 +221,16 @@ export class World {
211
221
  getNearbyEntitiesSync(x: number, y: number, z: number, radius: number, options?: TaskOptions): string[] {
212
222
  return call<string[]>('world.getNearbyEntities', { world: this.name, x, y, z, radius }, options);
213
223
  }
214
- dropItem(x: number, y: number, z: number, itemType: string, amount?: number, options?: TaskOptions): Promise<void> {
215
- return post('world.dropItem', { world: this.name, x, y, z, itemType, amount }, options);
224
+ /** 在指定位置掉落物品(`item` ItemStack 数据快照或材质名字符串;字符串时可用 `amount` 指定数量——旧式参数兼容)。 */
225
+ dropItem(x: number, y: number, z: number, item: ItemStack | string, amount?: number, options?: TaskOptions): Promise<void> {
226
+ const it: Record<string, unknown> = typeof item === 'string' ? { type: item } : { ...item };
227
+ if (typeof item === 'string' && amount !== undefined) it.amount = amount;
228
+ return post('world.dropItem', { world: this.name, x, y, z, item: it }, options);
216
229
  }
217
- dropItemSync(x: number, y: number, z: number, itemType: string, amount?: number, options?: TaskOptions): void {
218
- call('world.dropItem', { world: this.name, x, y, z, itemType, amount }, options);
230
+ dropItemSync(x: number, y: number, z: number, item: ItemStack | string, amount?: number, options?: TaskOptions): void {
231
+ const it: Record<string, unknown> = typeof item === 'string' ? { type: item } : { ...item };
232
+ if (typeof item === 'string' && amount !== undefined) it.amount = amount;
233
+ call('world.dropItem', { world: this.name, x, y, z, item: it }, options);
219
234
  }
220
235
  strikeLightning(x: number, y: number, z: number, options?: TaskOptions): Promise<void> {
221
236
  return post('world.strikeLightning', { world: this.name, x, y, z }, options);