yeow-api 0.4.2 → 0.5.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.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Yeow API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -1,27 +1,8 @@
1
- import { post } from './task.js';
2
- import type { TaskOptions } from './task.js';
1
+ // 进度操作已上移到 `player.ts` Player 实例方法(`player.grantAdvancement(...)` 等)
2
+ // ——不再提供顶层 uuid 函数。
3
3
 
4
+ /** 进度完成情况。 */
4
5
  export interface AdvancementProgress {
5
6
  awardedCriteria: string[];
6
7
  remainingCriteria: string[];
7
8
  }
8
-
9
- export function grant(uuid: string, key: string, options?: TaskOptions): Promise<boolean> {
10
- return post<boolean>('advancement.grant', { uuid, key }, options);
11
- }
12
-
13
- export function revoke(uuid: string, key: string, options?: TaskOptions): Promise<boolean> {
14
- return post<boolean>('advancement.revoke', { uuid, key }, options);
15
- }
16
-
17
- export function getProgress(uuid: string, key: string, options?: TaskOptions): Promise<AdvancementProgress | null> {
18
- return post<AdvancementProgress | null>('advancement.getProgress', { uuid, key }, options);
19
- }
20
-
21
- export function awardCriteria(uuid: string, key: string, criteria: string, options?: TaskOptions): Promise<boolean> {
22
- return post<boolean>('advancement.awardCriteria', { uuid, key, criteria }, options);
23
- }
24
-
25
- export function revokeCriteria(uuid: string, key: string, criteria: string, options?: TaskOptions): Promise<boolean> {
26
- return post<boolean>('advancement.revokeCriteria', { uuid, key, criteria }, options);
27
- }
package/src/assets.ts CHANGED
@@ -55,18 +55,19 @@ const readSyncImpl = (path: string, options?: FsEncoding | ReadFileOptions): Uin
55
55
  export const read = readImpl as AssetsReadFn;
56
56
  export const readSync = readSyncImpl as AssetsReadSyncFn;
57
57
 
58
- export async function extract(path: string, dest?: string): Promise<string> {
59
- const p: Record<string, unknown> = { path };
60
- if (dest) p.dest = dest;
61
- const r = await _sendAssetsAsync({ t: 'extract', p }) as { path: string };
58
+ /**
59
+ * 解压单文件资产到磁盘。**`dest` 必填**,基于插件数据目录(`plugins/<插件名>/`)计算,
60
+ * 最终目标必须位于插件目录内。返回解压后的相对服务器根目录路径。
61
+ */
62
+ export async function extract(path: string, dest: string): Promise<string> {
63
+ const r = await _sendAssetsAsync({ t: 'extract', p: { path, dest } }) as { path: string };
62
64
  return r.path;
63
65
  }
64
- export function extractSync(path: string, dest?: string): string {
65
- const p: Record<string, unknown> = { path };
66
- if (dest) p.dest = dest;
67
- return (_sendAssets({ t: 'extract', p }) as { path: string }).path;
66
+ export function extractSync(path: string, dest: string): string {
67
+ return (_sendAssets({ t: 'extract', p: { path, dest } }) as { path: string }).path;
68
68
  }
69
69
 
70
+ /** 解压目录树到磁盘。`dest` 可选(默认 `assets/<path>`),基于插件数据目录计算并限定其内。 */
70
71
  export async function extractDir(path: string, dest?: string): Promise<string> {
71
72
  const p: Record<string, unknown> = { path };
72
73
  if (dest) p.dest = dest;
package/src/block.ts CHANGED
@@ -9,15 +9,16 @@ import {
9
9
  keysBlock as pdcKeys, getAllBlock as pdcGetAll,
10
10
  } from './pdc.js';
11
11
 
12
- /** 方块状态(Minecraft 原版键值对枚举,值统一为字符串)。 */
12
+ /** 方块状态(Minecraft 原版键值对枚举;值为字符串 / 数字 / 布尔——按原版语义保留类型)。 */
13
13
  export interface BlockState {
14
- [key: string]: string;
14
+ [key: string]: string | number | boolean;
15
15
  }
16
16
 
17
17
  /**
18
18
  * Block —— 方块数据描述符 + 可选的世界位置(location)。
19
19
  * 对应 Minecraft 原版的方块概念:类型 + 方块状态(键值对枚举,如
20
- * `facing`、`waterlogged`、`level` 等,值统一为字符串)。
20
+ * `facing`、`waterlogged`、`level` 等;值按原版语义保留类型——布尔 `waterlogged: false`、
21
+ * 数字 `level: 8`、枚举串 `facing: "north"`)。
21
22
  *
22
23
  * **静态数据语义**:`type` / `state` / `location` 均为**获取时刻的快照**,
23
24
  * 之后世界变化不会自动更新;需要最新状态请重新调用 `world.getBlock`。
package/src/bossbar.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { post } from './task.js';
1
+ import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
  import { BossBarHandle } from './instance-id.js';
4
+ import { resolveUuid } from './target.js';
5
+ import type { PlayerTarget } from './target.js';
4
6
 
5
7
  export interface BossBarOptions {
6
8
  color?: string;
@@ -9,52 +11,102 @@ export interface BossBarOptions {
9
11
  visible?: boolean;
10
12
  }
11
13
 
12
- export async function createBossBar(title: string, options?: BossBarOptions, taskOptions?: TaskOptions): Promise<BossBarHandle> {
13
- const h = new BossBarHandle();
14
- await post('bossbar.create', { id: h.toString(), title, ...options }, taskOptions);
15
- return h;
16
- }
14
+ /**
15
+ * BossBar —— 面向玩家的血条对象(OOP)。
16
+ *
17
+ * ```js
18
+ * const bar = await BossBar.create('<red>Loading...</red>', { color: 'RED', progress: 0.0 });
19
+ * await bar.addPlayer(player); // 接受 Player 对象或 uuid
20
+ * await bar.setProgress(0.5);
21
+ * await bar.destroy();
22
+ * ```
23
+ *
24
+ * 事件比对:`bar.toString()` 返回底层句柄 id(与 `inventoryClick` 等比对)。
25
+ * BossBar 颜色/样式/Flag 值域见 [值域附录 · 直接维护的枚举清单]。
26
+ */
27
+ export class BossBar {
28
+ private constructor(readonly id: string) {}
17
29
 
18
- export function destroy(id: BossBarHandle, options?: TaskOptions): Promise<void> {
19
- return post('bossbar.destroy', { id: id.toString() }, options);
20
- }
30
+ /** 创建血条(title 支持 MiniMessage)。 */
31
+ static async create(title: string, options?: BossBarOptions, taskOptions?: TaskOptions): Promise<BossBar> {
32
+ const id = new BossBarHandle().toString();
33
+ await post('bossbar.create', { id, title, ...options }, taskOptions);
34
+ return new BossBar(id);
35
+ }
21
36
 
22
- export function setTitle(id: BossBarHandle, title: string, options?: TaskOptions): Promise<void> {
23
- return post('bossbar.setTitle', { id: id.toString(), title }, options);
24
- }
37
+ /** 底层句柄 id(用于事件比对)。 */
38
+ get handle(): string { return this.id; }
39
+ toString(): string { return this.id; }
25
40
 
26
- export function setProgress(id: BossBarHandle, progress: number, options?: TaskOptions): Promise<void> {
27
- return post('bossbar.setProgress', { id: id.toString(), progress }, options);
28
- }
41
+ // ── 标题 / 进度 / 颜色 / 样式 / 可见性 ──
29
42
 
30
- export function setColor(id: BossBarHandle, color: string, options?: TaskOptions): Promise<void> {
31
- return post('bossbar.setColor', { id: id.toString(), color }, options);
32
- }
43
+ set title(v: string) { call('bossbar.setTitle', { id: this.id, title: v }); }
44
+ setTitle(title: string, options?: TaskOptions): Promise<void> { return post('bossbar.setTitle', { id: this.id, title }, options); }
45
+ setTitleSync(title: string, options?: TaskOptions): void { call('bossbar.setTitle', { id: this.id, title }, options); }
33
46
 
34
- export function setStyle(id: BossBarHandle, style: string, options?: TaskOptions): Promise<void> {
35
- return post('bossbar.setStyle', { id: id.toString(), style }, options);
36
- }
47
+ set progress(v: number) { call('bossbar.setProgress', { id: this.id, progress: v }); }
48
+ setProgress(progress: number, options?: TaskOptions): Promise<void> { return post('bossbar.setProgress', { id: this.id, progress }, options); }
49
+ setProgressSync(progress: number, options?: TaskOptions): void { call('bossbar.setProgress', { id: this.id, progress }, options); }
37
50
 
38
- export function setVisible(id: BossBarHandle, visible: boolean, options?: TaskOptions): Promise<void> {
39
- return post('bossbar.setVisible', { id: id.toString(), visible }, options);
40
- }
51
+ set color(v: string) { call('bossbar.setColor', { id: this.id, color: v }); }
52
+ setColor(color: string, options?: TaskOptions): Promise<void> { return post('bossbar.setColor', { id: this.id, color }, options); }
53
+ setColorSync(color: string, options?: TaskOptions): void { call('bossbar.setColor', { id: this.id, color }, options); }
41
54
 
42
- export function addPlayer(id: BossBarHandle, uuid: string, options?: TaskOptions): Promise<void> {
43
- return post('bossbar.addPlayer', { id: id.toString(), uuid }, options);
44
- }
55
+ set style(v: string) { call('bossbar.setStyle', { id: this.id, style: v }); }
56
+ setStyle(style: string, options?: TaskOptions): Promise<void> { return post('bossbar.setStyle', { id: this.id, style }, options); }
57
+ setStyleSync(style: string, options?: TaskOptions): void { call('bossbar.setStyle', { id: this.id, style }, options); }
45
58
 
46
- export function removePlayer(id: BossBarHandle, uuid: string, options?: TaskOptions): Promise<void> {
47
- return post('bossbar.removePlayer', { id: id.toString(), uuid }, options);
48
- }
59
+ set visible(v: boolean) { call('bossbar.setVisible', { id: this.id, visible: v }); }
60
+ setVisible(visible: boolean, options?: TaskOptions): Promise<void> { return post('bossbar.setVisible', { id: this.id, visible }, options); }
61
+ setVisibleSync(visible: boolean, options?: TaskOptions): void { call('bossbar.setVisible', { id: this.id, visible }, options); }
49
62
 
50
- export function removeAll(id: BossBarHandle, options?: TaskOptions): Promise<void> {
51
- return post('bossbar.removeAll', { id: id.toString() }, options);
52
- }
63
+ // ── 玩家绑定 ──
53
64
 
54
- export function addFlag(id: BossBarHandle, flag: string, options?: TaskOptions): Promise<void> {
55
- return post('bossbar.addFlag', { id: id.toString(), flag }, options);
56
- }
65
+ /** 绑定玩家(接受 `Player` 对象或 uuid)。 */
66
+ addPlayer(player: PlayerTarget, options?: TaskOptions): Promise<void> {
67
+ return post('bossbar.addPlayer', { id: this.id, uuid: resolveUuid(player) }, options);
68
+ }
69
+ addPlayerSync(player: PlayerTarget, options?: TaskOptions): void {
70
+ call('bossbar.addPlayer', { id: this.id, uuid: resolveUuid(player) }, options);
71
+ }
72
+
73
+ /** 移除玩家(接受 `Player` 对象或 uuid)。 */
74
+ removePlayer(player: PlayerTarget, options?: TaskOptions): Promise<void> {
75
+ return post('bossbar.removePlayer', { id: this.id, uuid: resolveUuid(player) }, options);
76
+ }
77
+ removePlayerSync(player: PlayerTarget, options?: TaskOptions): void {
78
+ call('bossbar.removePlayer', { id: this.id, uuid: resolveUuid(player) }, options);
79
+ }
80
+
81
+ /** 移除全部绑定的玩家。 */
82
+ removeAllPlayers(options?: TaskOptions): Promise<void> {
83
+ return post('bossbar.removeAll', { id: this.id }, options);
84
+ }
85
+ removeAllPlayersSync(options?: TaskOptions): void {
86
+ call('bossbar.removeAll', { id: this.id }, options);
87
+ }
88
+
89
+ // ── Flags ──
90
+
91
+ addFlag(flag: string, options?: TaskOptions): Promise<void> {
92
+ return post('bossbar.addFlag', { id: this.id, flag }, options);
93
+ }
94
+ addFlagSync(flag: string, options?: TaskOptions): void {
95
+ call('bossbar.addFlag', { id: this.id, flag }, options);
96
+ }
97
+
98
+ removeFlag(flag: string, options?: TaskOptions): Promise<void> {
99
+ return post('bossbar.removeFlag', { id: this.id, flag }, options);
100
+ }
101
+ removeFlagSync(flag: string, options?: TaskOptions): void {
102
+ call('bossbar.removeFlag', { id: this.id, flag }, options);
103
+ }
57
104
 
58
- export function removeFlag(id: BossBarHandle, flag: string, options?: TaskOptions): Promise<void> {
59
- return post('bossbar.removeFlag', { id: id.toString(), flag }, options);
105
+ /** 销毁血条(此后句柄失效)。 */
106
+ destroy(options?: TaskOptions): Promise<void> {
107
+ return post('bossbar.destroy', { id: this.id }, options);
108
+ }
109
+ destroySync(options?: TaskOptions): void {
110
+ call('bossbar.destroy', { id: this.id }, options);
111
+ }
60
112
  }
package/src/core.ts CHANGED
@@ -57,43 +57,27 @@ export { assets, read as assetsRead, readSync as assetsReadSync,
57
57
  export { path } from './path.js';
58
58
  export { createReadStream, createWriteStream } from './fs.js';
59
59
  export type { ReadStream, WriteStream, ReadStreamOptions, WriteStreamOptions } from './fs.js';
60
- export { listen, respond, close, request, requestSync } from './http.js';
61
- export type { RespondOptions } from './http.js';
60
+ export { listen, respond, close, request } from './http.js';
61
+ export type { RespondOptions, RequestOptions, HttpResponse } from './http.js';
62
62
  export { logError } from './log-error.js';
63
63
  export { InstanceId, BossBarHandle, InventoryHandle } from './instance-id.js';
64
64
  export { ItemStack } from './item.js';
65
65
  export type { ItemMeta, PotionEffectData, AttributeModifierData } from './item.js';
66
66
  export type { PotionEffect } from './potion.js';
67
- export { addPotionEffect, removePotionEffect, clearPotionEffects, getActivePotionEffects } from './potion.js';
68
- export { playSound, stopSound, stopAllSounds } from './sound.js';
67
+ export { playSound } from './sound.js';
69
68
  export type { ParticleOptions } from './particle.js';
70
69
  export { spawnParticle } from './particle.js';
70
+ export type { PlayerTarget, LivingTarget, EntityTarget } from './target.js';
71
+ export type { AdvancementProgress } from './advancement.js';
72
+ export { BossBar } from './bossbar.js';
73
+ export type { BossBarOptions } from './bossbar.js';
74
+ export { Scoreboard, Objective, Team } from './scoreboard.js';
75
+ export type { ObjectiveInfo, TeamInfo } from './scoreboard.js';
71
76
  export { get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove, keys as pdcKeys, getAll as pdcGetAll,
72
77
  getRaw as pdcGetRaw, setRaw as pdcSetRaw, getAllRaw as pdcGetAllRaw,
73
78
  getBlock as pdcGetBlock, setBlock as pdcSetBlock, hasBlock as pdcHasBlock, removeBlock as pdcRemoveBlock,
74
79
  keysBlock as pdcKeysBlock, getAllBlock as pdcGetAllBlock, getBlockRaw as pdcGetBlockRaw, setBlockRaw as pdcSetBlockRaw, getAllBlockRaw as pdcGetAllBlockRaw } from './pdc.js';
75
- export { createBossBar, destroy as destroyBossBar,
76
- setTitle as setBossBarTitle, setProgress as setBossBarProgress,
77
- setColor as setBossBarColor, setStyle as setBossBarStyle,
78
- setVisible as setBossBarVisible, addPlayer as addBossBarPlayer,
79
- removePlayer as removeBossBarPlayer, removeAll as removeAllBossBarPlayers,
80
- addFlag as addBossBarFlag, removeFlag as removeBossBarFlag } from './bossbar.js';
81
- export type { BossBarOptions } from './bossbar.js';
82
- export type { AdvancementProgress } from './advancement.js';
83
- export { grant as grantAdvancement, revoke as revokeAdvancement,
84
- getProgress as getAdvancementProgress, awardCriteria, revokeCriteria } from './advancement.js';
85
80
  export { add as addRecipe, remove as removeRecipe, getForItem as getRecipesForItem } from './recipe.js';
86
- export type {
87
- ObjectiveInfo, TeamInfo,
88
- } from './scoreboard.js';
89
- export { createBoard as createScoreboard, deleteBoard as deleteScoreboard,
90
- createObjective, deleteObjective, getObjectives,
91
- setObjectiveDisplay, getScore, setScore, resetScore,
92
- createTeam, deleteTeam, getTeam, getTeams,
93
- setTeamDisplayName, setTeamPrefix, setTeamSuffix, setTeamColor,
94
- setTeamFriendlyFire, setTeamSeeInvisible, setTeamOption,
95
- teamAddEntry, teamRemoveEntry, teamGetEntries,
96
- setPlayerBoard } from './scoreboard.js';
97
81
  export { Material, getMaterials, getBlocks, getItems } from './material.js';
98
82
  export type { MaterialInfo } from './material.js';
99
83
  export { registerService, registerNativeService, request as serviceRequest, subscribe as serviceSubscribe, publish as servicePublish } from './service.js';
@@ -107,8 +91,8 @@ export type { Permission, PermissionOptions, PermissionDefault } from './permiss
107
91
  export { createWorker, Worker, onMessage, postMessage } from './worker.js';
108
92
  export type { WorkerOptions } from './worker.js';
109
93
  export {
110
- stringToBytes, stringToBytesAsync,
111
- bytesToString, bytesToStringAsync,
94
+ stringToBytes, stringToBytesSync,
95
+ bytesToString, bytesToStringSync,
112
96
  Gzip,
113
97
  } from './util.js';
114
98
  export type { GzipCompressor, GzipDecompressor, GzipCompressOptions, GzipDecompressOptions } from './util.js';
package/src/entity.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
  import { Location, LocationData } from './location.js';
4
+ import type { PotionEffect } from './potion.js';
4
5
 
5
6
  export interface BoundingBox {
6
7
  minX: number;
@@ -113,6 +114,15 @@ export class Entity {
113
114
  }
114
115
 
115
116
  export class LivingEntity extends Entity {
117
+ /** 按 uuid 查找活体实体(返回 LivingEntity;目标非活体/不存在返回 null)。 */
118
+ static get(uuid: string, options?: TaskOptions): Promise<LivingEntity | null> {
119
+ return post<{ uuid: string }>('entity.get', { uuid }, options).then((d) => (d ? new LivingEntity(d.uuid) : null));
120
+ }
121
+ static getSync(uuid: string, options?: TaskOptions): LivingEntity | null {
122
+ const d = call<{ uuid: string }>('entity.get', { uuid }, options);
123
+ return d ? new LivingEntity(d.uuid) : null;
124
+ }
125
+
116
126
  get health(): number { return call<number>('entity.getHealth', { uuid: this.uuid }); }
117
127
  set health(v: number) { call('entity.setHealth', { uuid: this.uuid, value: v }); }
118
128
  getHealth(options?: TaskOptions): Promise<number> { return post<number>('entity.getHealth', { uuid: this.uuid }, options); }
@@ -142,4 +152,38 @@ export class LivingEntity extends Entity {
142
152
  setTargetSync(target: { targetUuid: string } | { world: string; x: number; y: number; z: number; speed?: number }, options?: TaskOptions): boolean {
143
153
  return call<boolean>('entity.setTarget', { uuid: this.uuid, ...target }, options);
144
154
  }
155
+
156
+ // ── 药水效果(实体级;Player 经继承自动获得) ──
157
+
158
+ /** 添加药水效果。 */
159
+ addPotionEffect(effect: PotionEffect, options?: TaskOptions): Promise<void> {
160
+ return post('entity.addPotionEffect', { uuid: this.uuid, ...effect }, options);
161
+ }
162
+ addPotionEffectSync(effect: PotionEffect, options?: TaskOptions): void {
163
+ call('entity.addPotionEffect', { uuid: this.uuid, ...effect }, options);
164
+ }
165
+
166
+ /** 移除指定类型的药水效果。 */
167
+ removePotionEffect(type: string, options?: TaskOptions): Promise<void> {
168
+ return post('entity.removePotionEffect', { uuid: this.uuid, type }, options);
169
+ }
170
+ removePotionEffectSync(type: string, options?: TaskOptions): void {
171
+ call('entity.removePotionEffect', { uuid: this.uuid, type }, options);
172
+ }
173
+
174
+ /** 清除全部药水效果。 */
175
+ clearPotionEffects(options?: TaskOptions): Promise<void> {
176
+ return post('entity.clearPotionEffects', { uuid: this.uuid }, options);
177
+ }
178
+ clearPotionEffectsSync(options?: TaskOptions): void {
179
+ call('entity.clearPotionEffects', { uuid: this.uuid }, options);
180
+ }
181
+
182
+ /** 当前生效的药水效果列表(与输入同结构)。 */
183
+ getActivePotionEffects(options?: TaskOptions): Promise<PotionEffect[]> {
184
+ return post<PotionEffect[]>('entity.getActivePotionEffects', { uuid: this.uuid }, options);
185
+ }
186
+ getActivePotionEffectsSync(options?: TaskOptions): PotionEffect[] {
187
+ return call<PotionEffect[]>('entity.getActivePotionEffects', { uuid: this.uuid }, options);
188
+ }
145
189
  }
package/src/fs.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  type FsLevel = 'plugin' | 'server' | 'outer';
2
2
 
3
- import { stringToBytes, bytesToString } from './util.js';
3
+ import { stringToBytesSync, bytesToStringSync } from './util.js';
4
4
 
5
5
  /** 文件编码:utf8 = 文本;base64 = 将字符串视为 Base64 编码的二进制数据。 */
6
6
  export type FsEncoding = 'utf8' | 'base64';
@@ -53,7 +53,7 @@ function _makeUtf8StreamDecoder() {
53
53
  if ((b & 0xE0) === 0xC0) return 2;
54
54
  if ((b & 0xF0) === 0xE0) return 3;
55
55
  if ((b & 0xF8) === 0xF0) return 4;
56
- return 1; // 非法起始字节:交给 bytesToString 以替换字符处理
56
+ return 1; // 非法起始字节:交给 bytesToStringSync 以替换字符处理
57
57
  };
58
58
 
59
59
  return {
@@ -84,12 +84,12 @@ function _makeUtf8StreamDecoder() {
84
84
 
85
85
  pending = cut === buf.length ? new Uint8Array(0) : buf.slice(cut);
86
86
  const complete = cut === buf.length ? buf : buf.subarray(0, cut);
87
- return complete.length ? bytesToString(complete) : null;
87
+ return complete.length ? bytesToStringSync(complete) : null;
88
88
  },
89
89
  /** EOF:把剩余的不完整序列交给 Java 解码(通常产出 U+FFFD)。 */
90
90
  flush(): string | null {
91
91
  if (!pending.length) return null;
92
- const s = bytesToString(pending);
92
+ const s = bytesToStringSync(pending);
93
93
  pending = new Uint8Array(0);
94
94
  return s;
95
95
  },
@@ -394,7 +394,7 @@ function _makeWriteStream(
394
394
  const check = () => { if (closed) throw new Error('write stream closed'); };
395
395
  const toBytes = (chunk: Uint8Array | string): Uint8Array => {
396
396
  if (typeof chunk !== 'string') return chunk; // Uint8Array 始终按原始字节写入
397
- return encoding === 'base64' ? Uint8Array.fromBase64(chunk) : stringToBytes(chunk);
397
+ return encoding === 'base64' ? Uint8Array.fromBase64(chunk) : stringToBytesSync(chunk);
398
398
  };
399
399
  return {
400
400
  encoding,
package/src/global.d.ts CHANGED
@@ -29,12 +29,34 @@ declare global {
29
29
  headers: { get(name: string): string | undefined };
30
30
  text(): Promise<string>;
31
31
  json(): Promise<any>;
32
+ base64(): Promise<string>;
33
+ bytes(): Promise<Uint8Array>;
34
+ arrayBuffer(): Promise<ArrayBuffer>;
32
35
  }
33
36
  function fetch(
34
37
  url: string,
35
- init?: { method?: string; headers?: Record<string, string>; body?: string | null }
38
+ init?: { method?: string; headers?: Record<string, string>; body?: string | Uint8Array | null; encoding?: 'utf8' | 'base64'; timeout?: number }
36
39
  ): Promise<YeowResponse>;
37
40
 
41
+ // ── TextEncoder / TextDecoder(utf-8,polyfill 注入;同步 API)──
42
+ interface TextEncoder {
43
+ readonly encoding: string;
44
+ encode(input?: string): Uint8Array;
45
+ }
46
+ const TextEncoder: {
47
+ new (encoding?: string): TextEncoder;
48
+ prototype: TextEncoder;
49
+ };
50
+
51
+ interface TextDecoder {
52
+ readonly encoding: string;
53
+ decode(input?: Uint8Array): string;
54
+ }
55
+ const TextDecoder: {
56
+ new (encoding?: string): TextDecoder;
57
+ prototype: TextDecoder;
58
+ };
59
+
38
60
  function setTimeout(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
39
61
  function clearTimeout(id: string): void;
40
62
  function setInterval(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
package/src/http.ts CHANGED
@@ -22,6 +22,42 @@ export interface RespondOptions {
22
22
  headers?: Record<string, string>;
23
23
  }
24
24
 
25
+ /** HTTP 请求选项(request)。 */
26
+ export interface RequestOptions {
27
+ /** HTTP 方法(默认 `"GET"`)。 */
28
+ method?: string;
29
+ /** 请求头。 */
30
+ headers?: Record<string, string>;
31
+ /**
32
+ * 请求体(与 `fs.writeFile` 同语义):`Uint8Array` 直接作为二进制;
33
+ * 字符串按 `encoding` 解释——缺省 UTF-8 文本,`'base64'` 时视为 base64 编码的二进制。
34
+ */
35
+ body?: string | Uint8Array;
36
+ /**
37
+ * 请求体(body 为字符串时)解释:`'utf8'`(默认)文本 / `'base64'` base64 二进制
38
+ * (与 `fs.writeFile` 同语义)。
39
+ */
40
+ encoding?: 'utf8' | 'base64';
41
+ /**
42
+ * 响应体形态:缺省 `Uint8Array`(原始字节,可在收到后自行解码,
43
+ * 如 `bytesToStringSync(body)` 或 `new TextDecoder().decode(body)`);
44
+ * `'utf8'` → UTF-8 字符串;`'base64'` → base64 字符串(原样返回,不解码)。
45
+ */
46
+ responseEncoding?: 'utf8' | 'base64';
47
+ /** 超时(毫秒);缺省运行时默认(连接 5s / 读取 10s)。 */
48
+ timeout?: number;
49
+ }
50
+
51
+ /** HTTP 响应(request)。 */
52
+ export interface HttpResponse {
53
+ /** HTTP 状态码。 */
54
+ status: number;
55
+ /** 响应头(键小写)。 */
56
+ headers: Record<string, string>;
57
+ /** 响应体:缺省 `Uint8Array`;`encoding: 'utf8' | 'base64'` 时为字符串。 */
58
+ body: string | Uint8Array;
59
+ }
60
+
25
61
  function _sendHttp(payload: Record<string, unknown>): HttpResult {
26
62
  const r = $send('http', payload);
27
63
  if (!r) return {};
@@ -75,25 +111,49 @@ _registerCallback(() => {
75
111
 
76
112
  /**
77
113
  * 异步 HTTP 请求(不阻塞 JS 线程)——底层走 `http:requestAsync` 通道。
78
- * 推荐用于事件处理器与高频场景。需要同步阻塞返回的用 `requestSync`。
114
+ *
115
+ * 请求体与 `fs.writeFile` 同语义(`body: string | Uint8Array`,字符串按
116
+ * `encoding` 解释);响应体默认 `Uint8Array`(原始字节),可用
117
+ * `responseEncoding` 直接得到字符串(`'utf8'` / `'base64'`),也可在收到后
118
+ * 用 `bytesToStringSync` 自行解码。`timeout` 指定超时毫秒数(连接与读取,缺省
119
+ * 运行时默认)。
79
120
  */
80
- export function request(url: string, opts: Record<string, unknown> = {}): Promise<HttpResult> {
121
+ export function request(url: string, opts: RequestOptions = {}): Promise<HttpResponse> {
81
122
  return new Promise((resolve, reject) => {
82
123
  const cbId = _registerCallback((result: unknown) => {
83
- if ((result as any)?.err) reject(new Error((result as any).err));
84
- else resolve(result as HttpResult);
124
+ const r = result as HttpResult;
125
+ if (r?.err || (r as any)?.error) {
126
+ reject(new Error(r?.err || (r as any).error));
127
+ return;
128
+ }
129
+ const body = r.body ?? '';
130
+ resolve({
131
+ status: r.status ?? 0,
132
+ headers: r.headers ?? {},
133
+ // 底层始终以 base64 承载原始字节;'utf8' 时 Java 侧已解码为文本,其余形态本地转换
134
+ body: opts.responseEncoding === 'utf8' ? body
135
+ : opts.responseEncoding === 'base64' ? body
136
+ : Uint8Array.fromBase64(body),
137
+ });
85
138
  });
86
139
  try {
87
- _sendHttp({ t: 'requestAsync', p: { url, ...opts, cb: String(cbId) } });
140
+ const p: Record<string, unknown> = {
141
+ url,
142
+ method: opts.method || 'GET',
143
+ headers: opts.headers || {},
144
+ };
145
+ // 请求体与 fs.writeFile 同语义:Uint8Array 直接二进制(base64 承载);
146
+ // 字符串按 encoding——缺省 UTF-8 文本,'base64' 视为 base64 二进制
147
+ if (opts.body instanceof Uint8Array) {
148
+ p.body = opts.body.toBase64();
149
+ p.encoding = 'base64';
150
+ } else {
151
+ p.body = opts.body ?? null;
152
+ if (opts.encoding === 'base64') p.encoding = 'base64';
153
+ }
154
+ p.responseType = opts.responseEncoding === 'utf8' ? 'text' : 'base64';
155
+ if (opts.timeout !== undefined) p.timeout = opts.timeout;
156
+ _sendHttp({ t: 'requestAsync', p: { ...p, cb: String(cbId) } });
88
157
  } catch (e) { reject(e); }
89
158
  });
90
159
  }
91
-
92
- /**
93
- * 同步 HTTP 请求(**阻塞 JS 线程**直到响应返回)——底层走 `http:request` 通道。
94
- * 阻塞期间 JS 线程无法处理事件/命令/回调(可能触发 event.timeout 告警);
95
- * 仅适合低频、非事件上下文。事件处理器或高频场景请用 `request`(异步)或全局 `fetch`。
96
- */
97
- export function requestSync(url: string, opts: Record<string, unknown> = {}): HttpResult {
98
- return _sendHttp({ t: 'request', p: { url, ...opts } });
99
- }
package/src/inventory.ts CHANGED
@@ -2,6 +2,8 @@ import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
  import type { ItemStack } from './item.js';
4
4
  import { InstanceId } from './instance-id.js';
5
+ import { resolveUuid } from './target.js';
6
+ import type { PlayerTarget } from './target.js';
5
7
 
6
8
  /**
7
9
  * Inventory —— 统一容器抽象,三种持有者:
@@ -141,9 +143,9 @@ export class Inventory {
141
143
 
142
144
  // ── 自定义 Inventory 专属 ─────────────────────────────────────────
143
145
 
144
- /** 为玩家打开(自定义 Inventory)。 */
145
- open(uuid: string, options?: TaskOptions): Promise<boolean> {
146
- return post<boolean>('inventory.open', { ...this.address(), uuid }, options);
146
+ /** 为玩家打开(自定义 Inventory;接受 `Player` 对象或 uuid)。 */
147
+ open(player: PlayerTarget, options?: TaskOptions): Promise<boolean> {
148
+ return post<boolean>('inventory.open', { ...this.address(), uuid: resolveUuid(player) }, options);
147
149
  }
148
150
 
149
151
  /** 关闭所有查看者(自定义 Inventory)。 */
@@ -151,9 +153,9 @@ export class Inventory {
151
153
  return post<boolean>('inventory.close', this.address(), options);
152
154
  }
153
155
 
154
- /** 仅关闭指定玩家(自定义 Inventory)。 */
155
- closePlayer(uuid: string, options?: TaskOptions): Promise<boolean> {
156
- return post<boolean>('inventory.closePlayer', { ...this.address(), uuid }, options);
156
+ /** 仅关闭指定玩家(自定义 Inventory;接受 `Player` 对象或 uuid)。 */
157
+ closePlayer(player: PlayerTarget, options?: TaskOptions): Promise<boolean> {
158
+ return post<boolean>('inventory.closePlayer', { ...this.address(), uuid: resolveUuid(player) }, options);
157
159
  }
158
160
 
159
161
  /** 当前查看者 uuid 列表(自定义 Inventory)。 */
package/src/material.ts CHANGED
@@ -55,4 +55,12 @@ export const Material = {
55
55
  isAirSync(type: string, options?: TaskOptions): boolean {
56
56
  return call<boolean>('material.isAir', { type }, options);
57
57
  },
58
+
59
+ /** 最大耐久(非耐用品返回 0;未知类型抛错)。 */
60
+ getMaxDurability(type: string, options?: TaskOptions): Promise<number> {
61
+ return post<number>('material.getMaxDurability', { type }, options);
62
+ },
63
+ getMaxDurabilitySync(type: string, options?: TaskOptions): number {
64
+ return call<number>('material.getMaxDurability', { type }, options);
65
+ },
58
66
  };
package/src/player.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
+ import { LivingEntity } from './entity.js';
3
4
  import { Location, LocationData } from './location.js';
4
5
  import type { ItemStack } from './item.js';
5
6
  import { Block } from './block.js';
6
7
  import type { Message } from './message.js';
7
8
  import type { Permission } from './permission.js';
9
+ import type { AdvancementProgress } from './advancement.js';
8
10
  import { Inventory } from './inventory.js';
9
11
  import {
10
12
  get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove,
@@ -16,7 +18,7 @@ interface PlayerData {
16
18
  name: string;
17
19
  }
18
20
 
19
- export class Player {
21
+ export class Player extends LivingEntity {
20
22
  static get(identifier: string, options?: TaskOptions): Promise<Player | null> {
21
23
  return post<PlayerData>('player.get', { identifier }, options).then((d) => (d ? new Player(d.uuid, d.name) : null));
22
24
  }
@@ -31,7 +33,10 @@ export class Player {
31
33
  return call<PlayerData[]>('player.getAll', {}, options).map((r) => new Player(r.uuid, r.name));
32
34
  }
33
35
 
34
- constructor(public readonly uuid: string, private _name?: string) {}
36
+ // uuid Entity 基类持有(readonly);本类扩展玩家特有字段。
37
+ constructor(uuid: string, private _name?: string) {
38
+ super(uuid);
39
+ }
35
40
 
36
41
  /** 玩家名。未提供时首次访问惰性同步获取并缓存(仅一次往返)。 */
37
42
  get name(): string {
@@ -153,6 +158,20 @@ export class Player {
153
158
  playSoundSync(sound: string, volume?: number, pitch?: number, options?: TaskOptions): void {
154
159
  call('player.playSound', { uuid: this.uuid, sound, volume, pitch }, options);
155
160
  }
161
+ /** 停止播放指定音效。 */
162
+ stopSound(sound: string, options?: TaskOptions): Promise<void> {
163
+ return post('player.stopSound', { uuid: this.uuid, sound }, options);
164
+ }
165
+ stopSoundSync(sound: string, options?: TaskOptions): void {
166
+ call('player.stopSound', { uuid: this.uuid, sound }, options);
167
+ }
168
+ /** 停止该玩家所有音效。 */
169
+ stopAllSounds(options?: TaskOptions): Promise<void> {
170
+ return post('player.stopAllSounds', { uuid: this.uuid }, options);
171
+ }
172
+ stopAllSoundsSync(options?: TaskOptions): void {
173
+ call('player.stopAllSounds', { uuid: this.uuid }, options);
174
+ }
156
175
  giveExp(amount: number, options?: TaskOptions): Promise<void> { return post('player.giveExp', { uuid: this.uuid, amount }, options); }
157
176
  giveExpSync(amount: number, options?: TaskOptions): void { call('player.giveExp', { uuid: this.uuid, amount }, options); }
158
177
  /** 检查权限(经 Yeow 权限检查:`permissionCheck` 事件优先,无处理时回退 Bukkit)。node 可为权限节点对象。 */
@@ -169,6 +188,41 @@ export class Player {
169
188
  performCommandSync(cmd: string, options?: TaskOptions): boolean {
170
189
  return call<boolean>('player.performCommand', { uuid: this.uuid, command: cmd.replace(/^\//, '') }, options);
171
190
  }
191
+ /** 授予进度全部条件。 */
192
+ grantAdvancement(key: string, options?: TaskOptions): Promise<boolean> {
193
+ return post<boolean>('advancement.grant', { uuid: this.uuid, key }, options);
194
+ }
195
+ grantAdvancementSync(key: string, options?: TaskOptions): boolean {
196
+ return call<boolean>('advancement.grant', { uuid: this.uuid, key }, options);
197
+ }
198
+ /** 撤销进度全部条件。 */
199
+ revokeAdvancement(key: string, options?: TaskOptions): Promise<boolean> {
200
+ return post<boolean>('advancement.revoke', { uuid: this.uuid, key }, options);
201
+ }
202
+ revokeAdvancementSync(key: string, options?: TaskOptions): boolean {
203
+ return call<boolean>('advancement.revoke', { uuid: this.uuid, key }, options);
204
+ }
205
+ /** 查询进度完成情况。 */
206
+ getAdvancementProgress(key: string, options?: TaskOptions): Promise<AdvancementProgress | null> {
207
+ return post<AdvancementProgress | null>('advancement.getProgress', { uuid: this.uuid, key }, options);
208
+ }
209
+ getAdvancementProgressSync(key: string, options?: TaskOptions): AdvancementProgress | null {
210
+ return call<AdvancementProgress | null>('advancement.getProgress', { uuid: this.uuid, key }, options);
211
+ }
212
+ /** 授予进度单个条件。 */
213
+ awardCriteria(key: string, criteria: string, options?: TaskOptions): Promise<boolean> {
214
+ return post<boolean>('advancement.awardCriteria', { uuid: this.uuid, key, criteria }, options);
215
+ }
216
+ awardCriteriaSync(key: string, criteria: string, options?: TaskOptions): boolean {
217
+ return call<boolean>('advancement.awardCriteria', { uuid: this.uuid, key, criteria }, options);
218
+ }
219
+ /** 撤销进度单个条件。 */
220
+ revokeCriteria(key: string, criteria: string, options?: TaskOptions): Promise<boolean> {
221
+ return post<boolean>('advancement.revokeCriteria', { uuid: this.uuid, key, criteria }, options);
222
+ }
223
+ revokeCriteriaSync(key: string, criteria: string, options?: TaskOptions): boolean {
224
+ return call<boolean>('advancement.revokeCriteria', { uuid: this.uuid, key, criteria }, options);
225
+ }
172
226
  teleport(loc: Location, options?: TaskOptions): Promise<void> { return post('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
173
227
  teleportSync(loc: Location, options?: TaskOptions): void { call('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
174
228
  /** 向玩家发送假方块变化(仅客户端视觉,不改变真实世界)。block 为 Block 对象或字符串(同 world.setBlock,字符串无状态)。 */
@@ -236,14 +290,6 @@ export class Player {
236
290
  return call<boolean>('player.setPlayerListName', { uuid: this.uuid, name }, options);
237
291
  }
238
292
 
239
- /** 设置客户端世界边界(传 null 重置为服务端边界;centerX/centerZ 可选,指定边界中心)。 */
240
- setBorder(size: number | null, centerX?: number, centerZ?: number, options?: TaskOptions): Promise<boolean> {
241
- return post<boolean>('player.setBorder', { uuid: this.uuid, size, centerX, centerZ }, options);
242
- }
243
- setBorderSync(size: number | null, centerX?: number, centerZ?: number, options?: TaskOptions): boolean {
244
- return call<boolean>('player.setBorder', { uuid: this.uuid, size, centerX, centerZ }, options);
245
- }
246
-
247
293
  // ── PDC(玩家持久数据) ──
248
294
 
249
295
  /** 读取并 JSON 反序列化(无值返回 null;旧数据非 JSON 时原样返回字符串)。 */
package/src/potion.ts CHANGED
@@ -1,24 +1,18 @@
1
- import { post } from './task.js';
2
- import type { TaskOptions } from './task.js';
1
+ // 药水效果类型定义。操作已上移到 `entity.ts` LivingEntity 实例方法
2
+ // (`entity.addPotionEffect(...)` 等)——不再提供顶层 uuid 函数。
3
3
 
4
+ /** 药水效果(`entity.addPotionEffect` 任务载荷;与 `getActivePotionEffects` 输出同结构)。 */
4
5
  export interface PotionEffect {
6
+ /** 药水效果类型(值域 R1 注册键,如 `minecraft:speed`;兼容旧枚举名,大小写不敏感)。 */
5
7
  type: string;
8
+ /** 持续时间(tick)。 */
6
9
  duration: number;
10
+ /** 等级(0 = 一级)。 */
7
11
  amplifier: number;
12
+ /** 是否环境指示剂(默认 true)。 */
8
13
  ambient?: boolean;
14
+ /** 是否显示粒子(默认 true)。 */
9
15
  particles?: boolean;
16
+ /** 是否显示图标(默认 true)。 */
10
17
  icon?: boolean;
11
18
  }
12
-
13
- export function addPotionEffect(uuid: string, effect: PotionEffect, options?: TaskOptions): Promise<void> {
14
- return post('entity.addPotionEffect', { uuid, ...effect }, options);
15
- }
16
- export function removePotionEffect(uuid: string, type: string, options?: TaskOptions): Promise<void> {
17
- return post('entity.removePotionEffect', { uuid, type }, options);
18
- }
19
- export function clearPotionEffects(uuid: string, options?: TaskOptions): Promise<void> {
20
- return post('entity.clearPotionEffects', { uuid }, options);
21
- }
22
- export function getActivePotionEffects(uuid: string, options?: TaskOptions): Promise<PotionEffect[]> {
23
- return post<PotionEffect[]>('entity.getActivePotionEffects', { uuid }, options);
24
- }
package/src/scoreboard.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { post } from './task.js';
1
+ import { call, post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
+ import { resolveUuid } from './target.js';
4
+ import type { PlayerTarget } from './target.js';
3
5
 
4
6
  export interface ObjectiveInfo {
5
7
  name: string;
@@ -23,117 +25,280 @@ export interface TeamInfo {
23
25
  };
24
26
  }
25
27
 
26
- function boardParam(board?: string): Record<string, unknown> {
27
- return board ? { board } : {};
28
+ /** 线缆 board 参数:自定义计分板注入 `{ board: <id> }`,主计分板为空。 */
29
+ function wireBoard(boardId: string | null): Record<string, unknown> {
30
+ return boardId != null ? { board: boardId } : {};
28
31
  }
29
32
 
30
- // ── Board ──
31
-
32
- export function createBoard(id: string, options?: TaskOptions): Promise<string> {
33
- return post<string>('scoreboard.createBoard', { id }, options);
34
- }
35
-
36
- export function deleteBoard(id: string, options?: TaskOptions): Promise<void> {
37
- return post('scoreboard.deleteBoard', { id }, options);
33
+ /** 计分板 entry 解析:Player 对象取其玩家名(Bukkit 计分板按玩家名登记),字符串原样。 */
34
+ function resolveEntry(t: PlayerTarget): string {
35
+ return typeof t === 'string' ? t : t.name;
38
36
  }
39
37
 
40
- // ── Objectives ──
41
-
42
38
  /**
43
- * 创建计分项。⚠️ **Folia 平台限制**:Folia 不支持注册新 objective(registerNewObjective
44
- * UnsupportedOperationException)——在 Folia 上本调用会 reject(错误消息含
45
- * "Folia does not support creating new objectives");已存在的 objective 会更新 displayName 后返回。
39
+ * Scoreboard —— 计分板对象(OOP)。主计分板用 `Scoreboard.main()`,自定义用
40
+ * `Scoreboard.create(id)`;Objective / Team / Score 操作都在对应对象上调用,
41
+ * 不再反复传 `board`/裸名。
42
+ *
43
+ * ```js
44
+ * const board = await Scoreboard.create('myBoard');
45
+ * const obj = await board.createObjective('kills', 'dummy', '<red>Kills</red>');
46
+ * await obj.setDisplay('SIDEBAR');
47
+ * await obj.setScore(player, 42); // 接受 Player 对象或 entry 字符串
48
+ * const team = await board.createTeam('red');
49
+ * await team.add(player);
50
+ * await board.attach(player); // 为该玩家设置个人计分板
51
+ * await board.destroy();
52
+ * ```
53
+ *
54
+ * > **Folia 限制**:Folia 不支持注册新 objective/team(`createObjective` / `createTeam`
55
+ * > 会 reject);读取与修改已存在对象(`setScore` / `setTeamPrefix` 等)可用。
46
56
  */
47
- export function createObjective(name: string, criteria: string, displayName: string, board?: string, options?: TaskOptions): Promise<ObjectiveInfo> {
48
- return post<ObjectiveInfo>('scoreboard.createObjective', { name, criteria, displayName, ...boardParam(board) }, options);
49
- }
57
+ export class Scoreboard {
58
+ private constructor(readonly id: string | null) {}
50
59
 
51
- export function deleteObjective(name: string, board?: string, options?: TaskOptions): Promise<void> {
52
- return post('scoreboard.deleteObjective', { name, ...boardParam(board) }, options);
53
- }
60
+ /** 主计分板(服务器默认)。 */
61
+ static main(): Scoreboard {
62
+ return new Scoreboard(null);
63
+ }
54
64
 
55
- export function getObjectives(board?: string, options?: TaskOptions): Promise<ObjectiveInfo[]> {
56
- return post<ObjectiveInfo[]>('scoreboard.getObjectives', { ...boardParam(board) }, options);
57
- }
65
+ /** 创建自定义计分板(返回自身句柄)。 */
66
+ static async create(id: string, options?: TaskOptions): Promise<Scoreboard> {
67
+ await post('scoreboard.createBoard', { id }, options);
68
+ return new Scoreboard(id);
69
+ }
58
70
 
59
- export function setObjectiveDisplay(name: string, slot: string | null, board?: string, options?: TaskOptions): Promise<boolean> {
60
- return post<boolean>('scoreboard.setObjectiveDisplay', { name, slot, ...boardParam(board) }, options);
61
- }
71
+ static createSync(id: string, options?: TaskOptions): Scoreboard {
72
+ call('scoreboard.createBoard', { id }, options);
73
+ return new Scoreboard(id);
74
+ }
62
75
 
63
- export function getScore(objective: string, entry: string, board?: string, options?: TaskOptions): Promise<number | null> {
64
- return post<number | null>('scoreboard.getScore', { objective, entry, ...boardParam(board) }, options);
65
- }
76
+ /** 销毁自定义计分板(主计分板不可销毁,抛错)。 */
77
+ destroy(options?: TaskOptions): Promise<void> {
78
+ if (this.id == null) return Promise.reject(new Error('cannot destroy main scoreboard'));
79
+ return post('scoreboard.deleteBoard', { id: this.id }, options);
80
+ }
81
+ destroySync(options?: TaskOptions): void {
82
+ if (this.id == null) throw new Error('cannot destroy main scoreboard');
83
+ call('scoreboard.deleteBoard', { id: this.id }, options);
84
+ }
66
85
 
67
- export function setScore(objective: string, entry: string, value: number, board?: string, options?: TaskOptions): Promise<void> {
68
- return post('scoreboard.setScore', { objective, entry, value, ...boardParam(board) }, options);
69
- }
86
+ /** 为该玩家设置其个人计分板为本计分板(接受 `Player` 对象或 uuid;重置用 `Scoreboard.main().attach(player)`)。 */
87
+ attach(player: PlayerTarget, options?: TaskOptions): Promise<void> {
88
+ return post('scoreboard.setPlayerBoard', { uuid: resolveUuid(player), ...wireBoard(this.id) }, options);
89
+ }
90
+ attachSync(player: PlayerTarget, options?: TaskOptions): void {
91
+ call('scoreboard.setPlayerBoard', { uuid: resolveUuid(player), ...wireBoard(this.id) }, options);
92
+ }
70
93
 
71
- export function resetScore(objective: string, entry: string, board?: string, options?: TaskOptions): Promise<void> {
72
- return post('scoreboard.resetScore', { objective, entry, ...boardParam(board) }, options);
73
- }
94
+ // ── Objective ──
74
95
 
75
- // ── Teams ──
96
+ createObjective(name: string, criteria: string, displayName: string, options?: TaskOptions): Promise<Objective> {
97
+ return post<{ name: string; criteria: string }>('scoreboard.createObjective', { name, criteria, displayName, ...wireBoard(this.id) }, options)
98
+ .then((o) => new Objective(this.id, o.name, o.criteria, null));
99
+ }
100
+ createObjectiveSync(name: string, criteria: string, displayName: string, options?: TaskOptions): Objective {
101
+ const o = call<{ name: string; criteria: string }>('scoreboard.createObjective', { name, criteria, displayName, ...wireBoard(this.id) }, options);
102
+ return new Objective(this.id, o.name, o.criteria, null);
103
+ }
76
104
 
77
- /**
78
- * 创建队伍。⚠️ **Folia 平台限制**:Folia 不支持注册新 team(registerNewTeam 抛
79
- * UnsupportedOperationException)——在 Folia 上本调用会 reject;已存在的 team 返回其信息。
80
- */
81
- export function createTeam(name: string, board?: string, options?: TaskOptions): Promise<void> {
82
- return post('scoreboard.createTeam', { name, ...boardParam(board) }, options);
83
- }
105
+ getObjectives(options?: TaskOptions): Promise<Objective[]> {
106
+ return post<ObjectiveInfo[]>('scoreboard.getObjectives', { ...wireBoard(this.id) }, options)
107
+ .then((list) => list.map((o) => new Objective(this.id, o.name, o.criteria, o.displaySlot)));
108
+ }
109
+ getObjectivesSync(options?: TaskOptions): Objective[] {
110
+ return call<ObjectiveInfo[]>('scoreboard.getObjectives', { ...wireBoard(this.id) }, options)
111
+ .map((o) => new Objective(this.id, o.name, o.criteria, o.displaySlot));
112
+ }
84
113
 
85
- export function deleteTeam(name: string, board?: string, options?: TaskOptions): Promise<void> {
86
- return post('scoreboard.deleteTeam', { name, ...boardParam(board) }, options);
87
- }
114
+ // ── Team ──
88
115
 
89
- export function getTeam(name: string, board?: string, options?: TaskOptions): Promise<TeamInfo | null> {
90
- return post<TeamInfo | null>('scoreboard.getTeam', { name, ...boardParam(board) }, options);
91
- }
116
+ createTeam(name: string, options?: TaskOptions): Promise<Team> {
117
+ return post('scoreboard.createTeam', { name, ...wireBoard(this.id) }, options)
118
+ .then(() => this.getTeam(name))
119
+ .then((t) => {
120
+ if (!t) throw new Error('team created but not found: ' + name);
121
+ return t;
122
+ });
123
+ }
124
+ createTeamSync(name: string, options?: TaskOptions): Team {
125
+ call('scoreboard.createTeam', { name, ...wireBoard(this.id) }, options);
126
+ const t = this.getTeamSync(name);
127
+ if (!t) throw new Error('team created but not found: ' + name);
128
+ return t;
129
+ }
92
130
 
93
- export function getTeams(board?: string, options?: TaskOptions): Promise<TeamInfo[]> {
94
- return post<TeamInfo[]>('scoreboard.getTeams', { ...boardParam(board) }, options);
95
- }
131
+ getTeam(name: string, options?: TaskOptions): Promise<Team | null> {
132
+ return post<TeamInfo | null>('scoreboard.getTeam', { name, ...wireBoard(this.id) }, options)
133
+ .then((info) => (info ? new Team(this.id, info) : null));
134
+ }
135
+ getTeamSync(name: string, options?: TaskOptions): Team | null {
136
+ const info = call<TeamInfo | null>('scoreboard.getTeam', { name, ...wireBoard(this.id) }, options);
137
+ return info ? new Team(this.id, info) : null;
138
+ }
96
139
 
97
- export function setTeamDisplayName(name: string, displayName: string, board?: string, options?: TaskOptions): Promise<void> {
98
- return post('scoreboard.setTeamDisplayName', { name, displayName, ...boardParam(board) }, options);
140
+ getTeams(options?: TaskOptions): Promise<Team[]> {
141
+ return post<TeamInfo[]>('scoreboard.getTeams', { ...wireBoard(this.id) }, options)
142
+ .then((list) => list.map((info) => new Team(this.id, info)));
143
+ }
144
+ getTeamsSync(options?: TaskOptions): Team[] {
145
+ return call<TeamInfo[]>('scoreboard.getTeams', { ...wireBoard(this.id) }, options)
146
+ .map((info) => new Team(this.id, info));
147
+ }
99
148
  }
100
149
 
101
- export function setTeamPrefix(name: string, prefix: string, board?: string, options?: TaskOptions): Promise<void> {
102
- return post('scoreboard.setTeamPrefix', { name, prefix, ...boardParam(board) }, options);
103
- }
150
+ /** 计分项(objective)句柄。 */
151
+ export class Objective {
152
+ constructor(
153
+ private readonly boardId: string | null,
154
+ public readonly name: string,
155
+ public readonly criteria?: string,
156
+ public readonly displaySlot: string | null = null,
157
+ ) {}
104
158
 
105
- export function setTeamSuffix(name: string, suffix: string, board?: string, options?: TaskOptions): Promise<void> {
106
- return post('scoreboard.setTeamSuffix', { name, suffix, ...boardParam(board) }, options);
107
- }
159
+ /** 设置显示位置(slot BELOW_NAME/PLAYER_LIST/SIDEBAR;null 取消显示)。 */
160
+ setDisplay(slot: string | null, options?: TaskOptions): Promise<boolean> {
161
+ return post<boolean>('scoreboard.setObjectiveDisplay', { name: this.name, slot, ...wireBoard(this.boardId) }, options);
162
+ }
163
+ setDisplaySync(slot: string | null, options?: TaskOptions): boolean {
164
+ return call<boolean>('scoreboard.setObjectiveDisplay', { name: this.name, slot, ...wireBoard(this.boardId) }, options);
165
+ }
108
166
 
109
- export function setTeamColor(name: string, color: string, board?: string, options?: TaskOptions): Promise<void> {
110
- return post('scoreboard.setTeamColor', { name, color, ...boardParam(board) }, options);
111
- }
167
+ /** 设置分数(target `Player` 对象或 entry 字符串)。 */
168
+ setScore(target: PlayerTarget, value: number, options?: TaskOptions): Promise<void> {
169
+ return post('scoreboard.setScore', { objective: this.name, entry: resolveEntry(target), value, ...wireBoard(this.boardId) }, options);
170
+ }
171
+ setScoreSync(target: PlayerTarget, value: number, options?: TaskOptions): void {
172
+ call('scoreboard.setScore', { objective: this.name, entry: resolveEntry(target), value, ...wireBoard(this.boardId) }, options);
173
+ }
112
174
 
113
- export function setTeamFriendlyFire(name: string, allow: boolean, board?: string, options?: TaskOptions): Promise<void> {
114
- return post('scoreboard.setTeamFriendlyFire', { name, allow, ...boardParam(board) }, options);
115
- }
175
+ /** 查询分数(无记录返回 null)。 */
176
+ getScore(target: PlayerTarget, options?: TaskOptions): Promise<number | null> {
177
+ return post<number | null>('scoreboard.getScore', { objective: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
178
+ }
179
+ getScoreSync(target: PlayerTarget, options?: TaskOptions): number | null {
180
+ return call<number | null>('scoreboard.getScore', { objective: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
181
+ }
116
182
 
117
- export function setTeamSeeInvisible(name: string, canSee: boolean, board?: string, options?: TaskOptions): Promise<void> {
118
- return post('scoreboard.setTeamSeeInvisible', { name, canSee, ...boardParam(board) }, options);
119
- }
183
+ /** 重置(清除)分数。 */
184
+ resetScore(target: PlayerTarget, options?: TaskOptions): Promise<void> {
185
+ return post('scoreboard.resetScore', { objective: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
186
+ }
187
+ resetScoreSync(target: PlayerTarget, options?: TaskOptions): void {
188
+ call('scoreboard.resetScore', { objective: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
189
+ }
120
190
 
121
- export function setTeamOption(name: string, option: string, value: string, board?: string, options?: TaskOptions): Promise<void> {
122
- return post('scoreboard.setTeamOption', { name, option, value, ...boardParam(board) }, options);
191
+ /** 删除此计分项。 */
192
+ delete(options?: TaskOptions): Promise<void> {
193
+ return post('scoreboard.deleteObjective', { name: this.name, ...wireBoard(this.boardId) }, options);
194
+ }
195
+ deleteSync(options?: TaskOptions): void {
196
+ call('scoreboard.deleteObjective', { name: this.name, ...wireBoard(this.boardId) }, options);
197
+ }
123
198
  }
124
199
 
125
- export function teamAddEntry(name: string, entry: string, board?: string, options?: TaskOptions): Promise<void> {
126
- return post('scoreboard.teamAddEntry', { name, entry, ...boardParam(board) }, options);
127
- }
200
+ /** 队伍(team)句柄;携带读取时刻的快照字段。 */
201
+ export class Team {
202
+ readonly name: string;
203
+ readonly displayName: string;
204
+ readonly prefix: string;
205
+ readonly suffix: string;
206
+ readonly color: string;
207
+ readonly allowFriendlyFire: boolean;
208
+ readonly canSeeFriendlyInvisibles: boolean;
209
+ readonly entries: string[];
210
+ readonly options: TeamInfo['options'];
128
211
 
129
- export function teamRemoveEntry(name: string, entry: string, board?: string, options?: TaskOptions): Promise<void> {
130
- return post('scoreboard.teamRemoveEntry', { name, entry, ...boardParam(board) }, options);
131
- }
212
+ constructor(private readonly boardId: string | null, info: TeamInfo) {
213
+ this.name = info.name;
214
+ this.displayName = info.displayName;
215
+ this.prefix = info.prefix;
216
+ this.suffix = info.suffix;
217
+ this.color = info.color;
218
+ this.allowFriendlyFire = info.allowFriendlyFire;
219
+ this.canSeeFriendlyInvisibles = info.canSeeFriendlyInvisibles;
220
+ this.entries = info.entries;
221
+ this.options = info.options;
222
+ }
132
223
 
133
- export function teamGetEntries(name: string, board?: string, options?: TaskOptions): Promise<string[]> {
134
- return post<string[]>('scoreboard.teamGetEntries', { name, ...boardParam(board) }, options);
135
- }
224
+ setDisplayName(v: string, options?: TaskOptions): Promise<void> {
225
+ return post('scoreboard.setTeamDisplayName', { name: this.name, displayName: v, ...wireBoard(this.boardId) }, options);
226
+ }
227
+ setDisplayNameSync(v: string, options?: TaskOptions): void {
228
+ call('scoreboard.setTeamDisplayName', { name: this.name, displayName: v, ...wireBoard(this.boardId) }, options);
229
+ }
230
+
231
+ setPrefix(v: string, options?: TaskOptions): Promise<void> {
232
+ return post('scoreboard.setTeamPrefix', { name: this.name, prefix: v, ...wireBoard(this.boardId) }, options);
233
+ }
234
+ setPrefixSync(v: string, options?: TaskOptions): void {
235
+ call('scoreboard.setTeamPrefix', { name: this.name, prefix: v, ...wireBoard(this.boardId) }, options);
236
+ }
237
+
238
+ setSuffix(v: string, options?: TaskOptions): Promise<void> {
239
+ return post('scoreboard.setTeamSuffix', { name: this.name, suffix: v, ...wireBoard(this.boardId) }, options);
240
+ }
241
+ setSuffixSync(v: string, options?: TaskOptions): void {
242
+ call('scoreboard.setTeamSuffix', { name: this.name, suffix: v, ...wireBoard(this.boardId) }, options);
243
+ }
244
+
245
+ setColor(v: string, options?: TaskOptions): Promise<void> {
246
+ return post('scoreboard.setTeamColor', { name: this.name, color: v, ...wireBoard(this.boardId) }, options);
247
+ }
248
+ setColorSync(v: string, options?: TaskOptions): void {
249
+ call('scoreboard.setTeamColor', { name: this.name, color: v, ...wireBoard(this.boardId) }, options);
250
+ }
251
+
252
+ setFriendlyFire(allow: boolean, options?: TaskOptions): Promise<void> {
253
+ return post('scoreboard.setTeamFriendlyFire', { name: this.name, allow, ...wireBoard(this.boardId) }, options);
254
+ }
255
+ setFriendlyFireSync(allow: boolean, options?: TaskOptions): void {
256
+ call('scoreboard.setTeamFriendlyFire', { name: this.name, allow, ...wireBoard(this.boardId) }, options);
257
+ }
258
+
259
+ setSeeInvisible(canSee: boolean, options?: TaskOptions): Promise<void> {
260
+ return post('scoreboard.setTeamSeeInvisible', { name: this.name, canSee, ...wireBoard(this.boardId) }, options);
261
+ }
262
+ setSeeInvisibleSync(canSee: boolean, options?: TaskOptions): void {
263
+ call('scoreboard.setTeamSeeInvisible', { name: this.name, canSee, ...wireBoard(this.boardId) }, options);
264
+ }
265
+
266
+ setOption(option: string, value: string, options?: TaskOptions): Promise<void> {
267
+ return post('scoreboard.setTeamOption', { name: this.name, option, value, ...wireBoard(this.boardId) }, options);
268
+ }
269
+ setOptionSync(option: string, value: string, options?: TaskOptions): void {
270
+ call('scoreboard.setTeamOption', { name: this.name, option, value, ...wireBoard(this.boardId) }, options);
271
+ }
272
+
273
+ /** 添加成员(`Player` 对象取其名,或 entry 字符串)。 */
274
+ add(target: PlayerTarget, options?: TaskOptions): Promise<void> {
275
+ return post('scoreboard.teamAddEntry', { name: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
276
+ }
277
+ addSync(target: PlayerTarget, options?: TaskOptions): void {
278
+ call('scoreboard.teamAddEntry', { name: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
279
+ }
280
+
281
+ /** 移除成员。 */
282
+ remove(target: PlayerTarget, options?: TaskOptions): Promise<void> {
283
+ return post('scoreboard.teamRemoveEntry', { name: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
284
+ }
285
+ removeSync(target: PlayerTarget, options?: TaskOptions): void {
286
+ call('scoreboard.teamRemoveEntry', { name: this.name, entry: resolveEntry(target), ...wireBoard(this.boardId) }, options);
287
+ }
288
+
289
+ /** 成员列表(快照)。 */
290
+ getEntries(options?: TaskOptions): Promise<string[]> {
291
+ return post<string[]>('scoreboard.teamGetEntries', { name: this.name, ...wireBoard(this.boardId) }, options);
292
+ }
293
+ getEntriesSync(options?: TaskOptions): string[] {
294
+ return call<string[]>('scoreboard.teamGetEntries', { name: this.name, ...wireBoard(this.boardId) }, options);
295
+ }
136
296
 
137
- export function setPlayerBoard(uuid: string, board?: string, options?: TaskOptions): Promise<void> {
138
- return post('scoreboard.setPlayerBoard', { uuid, ...boardParam(board) }, options);
297
+ /** 删除队伍。 */
298
+ delete(options?: TaskOptions): Promise<void> {
299
+ return post('scoreboard.deleteTeam', { name: this.name, ...wireBoard(this.boardId) }, options);
300
+ }
301
+ deleteSync(options?: TaskOptions): void {
302
+ call('scoreboard.deleteTeam', { name: this.name, ...wireBoard(this.boardId) }, options);
303
+ }
139
304
  }
package/src/sound.ts CHANGED
@@ -1,14 +1,10 @@
1
1
  import { post } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
3
 
4
+ // 玩家侧 stopSound/stopAllSounds 已上移到 `player.ts` 的 Player 实例方法
5
+ // (`player.stopSound(sound)` / `player.stopAllSounds()`)——不再提供顶层 uuid 函数。
6
+
7
+ /** 世界级音效(在指定世界坐标播放,对范围内玩家生效)。 */
4
8
  export function playSound(world: string, sound: string, x: number, y: number, z: number, volume?: number, pitch?: number, options?: TaskOptions): Promise<void> {
5
9
  return post('world.playSound', { world, sound, x, y, z, volume, pitch }, options);
6
10
  }
7
-
8
- export function stopSound(uuid: string, sound: string, options?: TaskOptions): Promise<void> {
9
- return post('player.stopSound', { uuid, sound }, options);
10
- }
11
-
12
- export function stopAllSounds(uuid: string, options?: TaskOptions): Promise<void> {
13
- return post('player.stopAllSounds', { uuid }, options);
14
- }
package/src/target.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { Player } from './player.js';
2
+ import type { Entity, LivingEntity } from './entity.js';
3
+
4
+ /**
5
+ * 以玩家/实体为目标的参数统一类型:**对象或字符串**。
6
+ *
7
+ * 传对象(`Player` / `LivingEntity` / `Entity`)时取其 `uuid`;传字符串时按原样
8
+ * 当作 uuid(或按具体 API 上下文为玩家名——如计分板 entry)。彻底消除
9
+ * 「必须手动传 uuid」的摩擦:有对象直接传对象,只有原始 id 时才传字符串。
10
+ */
11
+ export type PlayerTarget = Player | string;
12
+
13
+ export type LivingTarget = LivingEntity | string;
14
+
15
+ export type EntityTarget = Entity | string;
16
+
17
+ /** 解析目标 → uuid:对象取其 `uuid`,字符串原样返回。 */
18
+ export function resolveUuid(t: PlayerTarget | LivingTarget | EntityTarget): string {
19
+ return typeof t === 'string' ? t : t.uuid;
20
+ }
package/src/util.ts CHANGED
@@ -31,25 +31,25 @@ function toB64(data: Uint8Array | string): string {
31
31
  : data.toBase64();
32
32
  }
33
33
 
34
- // ── 字符串 ↔ 字节(UTF-8)────────────────────────────────────────
34
+ // ── 字符串 ↔ 字节(UTF-8;默认异步,同步加 Sync 后缀)──────────────
35
35
 
36
36
  /** UTF-8 字符串 → 字节(同步)。 */
37
- export function stringToBytes(text: string): Uint8Array {
37
+ export function stringToBytesSync(text: string): Uint8Array {
38
38
  return Uint8Array.fromBase64(send<{ data: string }>('encode.utf8', { data: text }).data);
39
39
  }
40
40
 
41
41
  /** 字节 → UTF-8 字符串(同步;非法序列替换为 U+FFFD,不抛错)。 */
42
- export function bytesToString(bytes: Uint8Array): string {
42
+ export function bytesToStringSync(bytes: Uint8Array): string {
43
43
  return send<{ data: string }>('decode.utf8', { data: bytes.toBase64() }).data;
44
44
  }
45
45
 
46
- /** UTF-8 字符串 → 字节(异步,ioExecutor 执行)。 */
47
- export function stringToBytesAsync(text: string): Promise<Uint8Array> {
46
+ /** UTF-8 字符串 → 字节(异步,ioExecutor 执行;默认)。 */
47
+ export function stringToBytes(text: string): Promise<Uint8Array> {
48
48
  return sendAsync<{ data: string }>('encode.utf8', { data: text }).then((r) => Uint8Array.fromBase64(r.data));
49
49
  }
50
50
 
51
- /** 字节 → UTF-8 字符串(异步)。 */
52
- export function bytesToStringAsync(bytes: Uint8Array): Promise<string> {
51
+ /** 字节 → UTF-8 字符串(异步;非法序列替换为 U+FFFD)。 */
52
+ export function bytesToString(bytes: Uint8Array): Promise<string> {
53
53
  return sendAsync<{ data: string }>('decode.utf8', { data: bytes.toBase64() }).then((r) => r.data);
54
54
  }
55
55
 
package/src/world.ts CHANGED
@@ -133,6 +133,12 @@ export class World {
133
133
  isChunkLoadedSync(x: number, z: number, options?: TaskOptions): boolean {
134
134
  return call<boolean>('world.isChunkLoaded', { world: this.name, x, z }, options);
135
135
  }
136
+ isChunkGenerated(x: number, z: number, options?: TaskOptions): Promise<boolean> {
137
+ return post<boolean>('world.isChunkGenerated', { world: this.name, x, z }, options);
138
+ }
139
+ isChunkGeneratedSync(x: number, z: number, options?: TaskOptions): boolean {
140
+ return call<boolean>('world.isChunkGenerated', { world: this.name, x, z }, options);
141
+ }
136
142
  loadChunk(x: number, z: number, options?: TaskOptions): Promise<boolean> {
137
143
  return post<boolean>('world.loadChunk', { world: this.name, x, z }, options);
138
144
  }