yeow-api 0.2.115 → 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 +1 -1
- package/src/block.ts +55 -0
- package/src/command.ts +17 -10
- package/src/core.ts +11 -7
- package/src/entity.ts +43 -0
- package/src/event.ts +6 -0
- package/src/global.d.ts +3 -6
- package/src/instance-id.ts +7 -5
- package/src/inventory.ts +158 -26
- package/src/item.ts +59 -9
- package/src/pdc.ts +93 -4
- package/src/player.ts +83 -2
- package/src/scoreboard.ts +9 -0
- package/src/task.ts +48 -1
- package/src/world.ts +65 -0
- package/src/yeow-dev.d.ts +7 -0
- package/src/gui.ts +0 -34
package/package.json
CHANGED
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
|
@@ -8,11 +8,6 @@ import { permissionPayload } from './permission.js';
|
|
|
8
8
|
/** 命令发送者:玩家为真正的 `Player` 对象(异步 `sendMessage` 等全部方法);控制台为字符串 `'CONSOLE'`。 */
|
|
9
9
|
export type CommandSender = Player | 'CONSOLE';
|
|
10
10
|
|
|
11
|
-
/** 判断发送者是否为玩家(非 'CONSOLE' 即玩家)。 */
|
|
12
|
-
export function isPlayer(sender: CommandSender): sender is Player {
|
|
13
|
-
return sender !== 'CONSOLE';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
export interface CommandPayload {
|
|
17
12
|
readonly sender: CommandSender;
|
|
18
13
|
readonly args: string[];
|
|
@@ -84,11 +79,23 @@ export function registerCommand(name: string, options: CommandOptions, taskOptio
|
|
|
84
79
|
} else {
|
|
85
80
|
const result = completerFn(sender, data.args);
|
|
86
81
|
if (result && typeof result.then === 'function') {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
);
|
|
92
99
|
} else {
|
|
93
100
|
$send('task', {
|
|
94
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,
|
|
58
|
-
export
|
|
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
|
-
|
|
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 包内类型为准。
|
package/src/instance-id.ts
CHANGED
|
@@ -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(
|
|
13
|
-
this._raw =
|
|
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
|
|
29
|
-
export class
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
66
|
+
/** 持有者类型:'PLAYER' | 'CUSTOM' | 方块实体类型名(如 'CHEST')。 */
|
|
67
|
+
getType(options?: TaskOptions): Promise<string> {
|
|
68
|
+
return post<string>('inventory.getType', this.address(), options);
|
|
14
69
|
}
|
|
15
|
-
|
|
16
|
-
|
|
70
|
+
|
|
71
|
+
/** 容器槽位数。 */
|
|
72
|
+
getSize(options?: TaskOptions): Promise<number> {
|
|
73
|
+
return post<number>('inventory.getSize', this.address(), options);
|
|
17
74
|
}
|
|
18
|
-
|
|
19
|
-
|
|
75
|
+
|
|
76
|
+
/** 全槽位快照数组(空槽为 null,长度 = 容器槽位数)。 */
|
|
77
|
+
getContents(options?: TaskOptions): Promise<(ItemStack | null)[]> {
|
|
78
|
+
return post<(ItemStack | null)[]>('inventory.getContents', this.address(), options);
|
|
20
79
|
}
|
|
21
|
-
|
|
22
|
-
call('inventory.
|
|
80
|
+
getContentsSync(options?: TaskOptions): (ItemStack | null)[] {
|
|
81
|
+
return call<(ItemStack | null)[]>('inventory.getContents', this.address(), options);
|
|
23
82
|
}
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
call('inventory.
|
|
88
|
+
setContentsSync(items: (ItemStack | null)[], options?: TaskOptions): boolean {
|
|
89
|
+
return call<boolean>('inventory.setContents', { ...this.address(), items }, options);
|
|
29
90
|
}
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
call('inventory.
|
|
96
|
+
getItemSync(slot: number, options?: TaskOptions): ItemStack | null {
|
|
97
|
+
return call<ItemStack | null>('inventory.getItem', { ...this.address(), slot }, options);
|
|
35
98
|
}
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
}
|