@crowdedkingdoms/crowdyjs 8.6.0 → 8.8.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/MIGRATION.md +81 -0
- package/README.md +2 -2
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/kit/combat.d.ts +38 -1
- package/dist/kit/combat.d.ts.map +1 -1
- package/dist/kit/combat.js +45 -1
- package/dist/kit/decks.d.ts +33 -1
- package/dist/kit/decks.d.ts.map +1 -1
- package/dist/kit/decks.js +49 -1
- package/dist/kit/director.d.ts +88 -0
- package/dist/kit/director.d.ts.map +1 -0
- package/dist/kit/director.js +86 -0
- package/dist/kit/economy.d.ts +46 -1
- package/dist/kit/economy.d.ts.map +1 -1
- package/dist/kit/economy.js +63 -1
- package/dist/kit/engine.d.ts +43 -0
- package/dist/kit/engine.d.ts.map +1 -0
- package/dist/kit/engine.js +108 -0
- package/dist/kit/index.d.ts +12 -4
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +10 -2
- package/dist/kit/instances.d.ts +52 -0
- package/dist/kit/instances.d.ts.map +1 -0
- package/dist/kit/instances.js +60 -0
- package/dist/kit/kit.d.ts +31 -1
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +21 -7
- package/dist/kit/leaderboards.d.ts +21 -1
- package/dist/kit/leaderboards.d.ts.map +1 -1
- package/dist/kit/leaderboards.js +40 -1
- package/dist/kit/matches.d.ts +33 -1
- package/dist/kit/matches.d.ts.map +1 -1
- package/dist/kit/matches.js +56 -1
- package/dist/kit/matchmaking.d.ts +57 -0
- package/dist/kit/matchmaking.d.ts.map +1 -0
- package/dist/kit/matchmaking.js +59 -0
- package/dist/kit/minigames.d.ts +30 -0
- package/dist/kit/minigames.d.ts.map +1 -0
- package/dist/kit/minigames.js +38 -0
- package/dist/kit/mobs.d.ts +90 -0
- package/dist/kit/mobs.d.ts.map +1 -0
- package/dist/kit/mobs.js +98 -0
- package/dist/kit/npcs.d.ts +38 -1
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/npcs.js +36 -1
- package/dist/kit/pets.d.ts +85 -0
- package/dist/kit/pets.d.ts.map +1 -0
- package/dist/kit/pets.js +96 -0
- package/dist/kit/quests.d.ts +57 -0
- package/dist/kit/quests.d.ts.map +1 -1
- package/dist/kit/quests.js +72 -0
- package/dist/kit/wire.d.ts +124 -0
- package/dist/kit/wire.d.ts.map +1 -0
- package/dist/kit/wire.js +206 -0
- package/dist/kit/worldsim.d.ts +34 -1
- package/dist/kit/worldsim.d.ts.map +1 -1
- package/dist/kit/worldsim.js +38 -1
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { GameModelAPI } from '../domains/gameModel.js';
|
|
2
|
+
import type { Scalars } from '../generated/graphql.js';
|
|
3
|
+
import type { EngineDetector, EngineInvokeResult } from './engine.js';
|
|
4
|
+
import { type ContactDamageEvent } from './wire.js';
|
|
5
|
+
/** Options for {@link MobsKit}. Must match the deployed mob engine. */
|
|
6
|
+
export interface MobsKitOptions {
|
|
7
|
+
/** The compute module serving `attack_mob`/`status`. Defaults to `'mob-engine'`. */
|
|
8
|
+
moduleName?: string;
|
|
9
|
+
/** The mob-definition container type. Defaults to `'MobDef'`. */
|
|
10
|
+
defTypeName?: string;
|
|
11
|
+
/** The mob slot container type. Defaults to `'Mob'`. */
|
|
12
|
+
slotTypeName?: string;
|
|
13
|
+
}
|
|
14
|
+
/** A parsed mob definition container. */
|
|
15
|
+
export interface KitMobDef {
|
|
16
|
+
containerId: string;
|
|
17
|
+
displayName: string;
|
|
18
|
+
mobId: string;
|
|
19
|
+
maxHealth: number;
|
|
20
|
+
damage: number;
|
|
21
|
+
speed: number;
|
|
22
|
+
hostile: boolean;
|
|
23
|
+
spawnTime: string;
|
|
24
|
+
properties: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/** A parsed mob slot container (durable state; live poses ride the mob lane). */
|
|
27
|
+
export interface KitMobSlot {
|
|
28
|
+
containerId: string;
|
|
29
|
+
displayName: string;
|
|
30
|
+
mobId: string;
|
|
31
|
+
actorUuid: string;
|
|
32
|
+
health: number;
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
z: number;
|
|
36
|
+
/** health > 0 — the slot is live and being simulated. */
|
|
37
|
+
alive: boolean;
|
|
38
|
+
properties: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** The referee's verdict for an accepted/denied attack. */
|
|
41
|
+
export interface KitAttackResult {
|
|
42
|
+
success: boolean;
|
|
43
|
+
/** Remaining health after an accepted hit. */
|
|
44
|
+
health?: number;
|
|
45
|
+
killed?: boolean;
|
|
46
|
+
/** The referee's denial reason ("out of range", "mob not live", ...). */
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Runtime helpers for compute-module mob engines (the Wave 1 `mob-engine`
|
|
51
|
+
* template / BWF's `bwf-mobs`): definition + slot reads off the model, the
|
|
52
|
+
* refereed `attack_mob` invoke, and the type-77 contact-damage event parser.
|
|
53
|
+
*
|
|
54
|
+
* Live mob poses arrive on the actor stream (FLAG_MOB, container-id
|
|
55
|
+
* suffix) — decode them with `kit/wire`'s {@link engineLanes} +
|
|
56
|
+
* `enginePoseCodec` in your world session; this kit reads the durable side.
|
|
57
|
+
*
|
|
58
|
+
* Obtained via `client.kit(appId).mobs`.
|
|
59
|
+
*/
|
|
60
|
+
export declare class MobsKit {
|
|
61
|
+
private readonly appId;
|
|
62
|
+
private readonly gameModel;
|
|
63
|
+
private readonly engines;
|
|
64
|
+
private readonly moduleName;
|
|
65
|
+
private readonly defTypeName;
|
|
66
|
+
private readonly slotTypeName;
|
|
67
|
+
constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, engines: EngineDetector, options?: MobsKitOptions);
|
|
68
|
+
/** Is the mob engine deployed + enabled (cached per session)? */
|
|
69
|
+
engineAvailable(): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Attack a live mob slot through the server referee: presence, range and
|
|
72
|
+
* damage clamps are validated engine-side before health moves. Denials
|
|
73
|
+
* resolve with `success: false` and the referee's `reason`.
|
|
74
|
+
*/
|
|
75
|
+
attack(containerId: string, amount?: number): Promise<KitAttackResult>;
|
|
76
|
+
/** The engine's `status` snapshot (mob/def counts, tick counter). */
|
|
77
|
+
status(): Promise<EngineInvokeResult>;
|
|
78
|
+
/** List mob definitions with parsed stats. */
|
|
79
|
+
defs(): Promise<KitMobDef[]>;
|
|
80
|
+
/** List mob slots (durable positions/health; `alive` = health > 0). */
|
|
81
|
+
slots(): Promise<KitMobSlot[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Parse a server-event payload as engine contact damage (type 77), or
|
|
84
|
+
* null when it is another event type. Feed it your world session's
|
|
85
|
+
* server-event stream and apply the damage to your own player when
|
|
86
|
+
* `targetUuid` matches your actor uuid.
|
|
87
|
+
*/
|
|
88
|
+
parseContactDamage(payload: Uint8Array): ContactDamageEvent | null;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=mobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobs.d.ts","sourceRoot":"","sources":["../../src/kit/mobs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAExE,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,OAAO;IAMhB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAGnB,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,cAAc,EACxC,OAAO,GAAE,cAAmB;IAO9B,iEAAiE;IACjE,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;;;OAIG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAcvE,qEAAqE;IAC/D,MAAM,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI3C,8CAA8C;IACxC,IAAI,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BlC,uEAAuE;IACjE,KAAK,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IA6BpC;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,UAAU,GAAG,kBAAkB,GAAG,IAAI;CAGnE"}
|
package/dist/kit/mobs.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { kitContainerProperties } from './shared.js';
|
|
2
|
+
import { parseContactDamage } from './wire.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runtime helpers for compute-module mob engines (the Wave 1 `mob-engine`
|
|
5
|
+
* template / BWF's `bwf-mobs`): definition + slot reads off the model, the
|
|
6
|
+
* refereed `attack_mob` invoke, and the type-77 contact-damage event parser.
|
|
7
|
+
*
|
|
8
|
+
* Live mob poses arrive on the actor stream (FLAG_MOB, container-id
|
|
9
|
+
* suffix) — decode them with `kit/wire`'s {@link engineLanes} +
|
|
10
|
+
* `enginePoseCodec` in your world session; this kit reads the durable side.
|
|
11
|
+
*
|
|
12
|
+
* Obtained via `client.kit(appId).mobs`.
|
|
13
|
+
*/
|
|
14
|
+
export class MobsKit {
|
|
15
|
+
constructor(appId, gameModel, engines, options = {}) {
|
|
16
|
+
this.appId = appId;
|
|
17
|
+
this.gameModel = gameModel;
|
|
18
|
+
this.engines = engines;
|
|
19
|
+
this.moduleName = options.moduleName ?? 'mob-engine';
|
|
20
|
+
this.defTypeName = options.defTypeName ?? 'MobDef';
|
|
21
|
+
this.slotTypeName = options.slotTypeName ?? 'Mob';
|
|
22
|
+
}
|
|
23
|
+
/** Is the mob engine deployed + enabled (cached per session)? */
|
|
24
|
+
engineAvailable() {
|
|
25
|
+
return this.engines.has(this.moduleName);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Attack a live mob slot through the server referee: presence, range and
|
|
29
|
+
* damage clamps are validated engine-side before health moves. Denials
|
|
30
|
+
* resolve with `success: false` and the referee's `reason`.
|
|
31
|
+
*/
|
|
32
|
+
async attack(containerId, amount = 1) {
|
|
33
|
+
const result = await this.engines.invoke(this.moduleName, 'attack_mob', { containerId, amount });
|
|
34
|
+
return {
|
|
35
|
+
success: result.success,
|
|
36
|
+
health: typeof result.body.health === 'number' ? result.body.health : undefined,
|
|
37
|
+
killed: typeof result.body.killed === 'boolean' ? result.body.killed : undefined,
|
|
38
|
+
reason: result.reason,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The engine's `status` snapshot (mob/def counts, tick counter). */
|
|
42
|
+
async status() {
|
|
43
|
+
return this.engines.invoke(this.moduleName, 'status');
|
|
44
|
+
}
|
|
45
|
+
/** List mob definitions with parsed stats. */
|
|
46
|
+
async defs() {
|
|
47
|
+
const containers = await this.gameModel.containers({
|
|
48
|
+
appId: this.appId,
|
|
49
|
+
typeName: this.defTypeName,
|
|
50
|
+
});
|
|
51
|
+
return Promise.all(containers.map(async (c) => {
|
|
52
|
+
const props = await kitContainerProperties(this.gameModel, String(this.appId), c.containerId);
|
|
53
|
+
return {
|
|
54
|
+
containerId: c.containerId,
|
|
55
|
+
displayName: c.displayName,
|
|
56
|
+
mobId: String(props.mob_id ?? ''),
|
|
57
|
+
maxHealth: Number(props.max_health ?? 0),
|
|
58
|
+
damage: Number(props.damage ?? 0),
|
|
59
|
+
speed: Number(props.speed ?? 0),
|
|
60
|
+
hostile: props.hostile === true || props.hostile === 'true',
|
|
61
|
+
spawnTime: String(props.spawn_time ?? 'any'),
|
|
62
|
+
properties: props,
|
|
63
|
+
};
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/** List mob slots (durable positions/health; `alive` = health > 0). */
|
|
67
|
+
async slots() {
|
|
68
|
+
const containers = await this.gameModel.containers({
|
|
69
|
+
appId: this.appId,
|
|
70
|
+
typeName: this.slotTypeName,
|
|
71
|
+
});
|
|
72
|
+
return Promise.all(containers.map(async (c) => {
|
|
73
|
+
const props = await kitContainerProperties(this.gameModel, String(this.appId), c.containerId);
|
|
74
|
+
const health = Number(props.health ?? 0);
|
|
75
|
+
return {
|
|
76
|
+
containerId: c.containerId,
|
|
77
|
+
displayName: c.displayName,
|
|
78
|
+
mobId: String(props.mob_id ?? ''),
|
|
79
|
+
actorUuid: String(props.actor_uuid ?? ''),
|
|
80
|
+
health,
|
|
81
|
+
x: Number(props.x ?? 0),
|
|
82
|
+
y: Number(props.y ?? 0),
|
|
83
|
+
z: Number(props.z ?? 0),
|
|
84
|
+
alive: health > 0,
|
|
85
|
+
properties: props,
|
|
86
|
+
};
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse a server-event payload as engine contact damage (type 77), or
|
|
91
|
+
* null when it is another event type. Feed it your world session's
|
|
92
|
+
* server-event stream and apply the damage to your own player when
|
|
93
|
+
* `targetUuid` matches your actor uuid.
|
|
94
|
+
*/
|
|
95
|
+
parseContactDamage(payload) {
|
|
96
|
+
return parseContactDamage(payload);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/kit/npcs.d.ts
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import type { GameModelAPI } from '../domains/gameModel.js';
|
|
2
2
|
import type { Scalars, SeedPropertyInput } from '../generated/graphql.js';
|
|
3
|
+
import type { EngineDetector } from './engine.js';
|
|
4
|
+
import type { EnginePose } from './wire.js';
|
|
3
5
|
/** Options for {@link NpcsKit}. Must match the deployed NPC blueprint. */
|
|
4
6
|
export interface NpcsKitOptions {
|
|
5
7
|
/** The `typeName` the NPC blueprint was deployed with. Defaults to `'Npc'`. */
|
|
6
8
|
typeName?: string;
|
|
9
|
+
/**
|
|
10
|
+
* The compute module driving NPC movement when the app runs an engine
|
|
11
|
+
* (smooth FLAG_NPC actor emits instead of property nudges). Defaults to
|
|
12
|
+
* `'npc-engine'`.
|
|
13
|
+
*/
|
|
14
|
+
moduleName?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The minimal shape of a live actor entry {@link overlayLivePoses} reads —
|
|
18
|
+
* matches the world-session `RemoteActor<EnginePose>` without importing it.
|
|
19
|
+
*/
|
|
20
|
+
export interface LiveNpcPose {
|
|
21
|
+
uuid: string;
|
|
22
|
+
state: Pick<EnginePose, 'x' | 'y' | 'z'>;
|
|
23
|
+
receivedAt: number;
|
|
7
24
|
}
|
|
8
25
|
/** A parsed view of one live NPC. */
|
|
9
26
|
export interface KitNpc {
|
|
@@ -32,8 +49,28 @@ export interface KitNpc {
|
|
|
32
49
|
export declare class NpcsKit {
|
|
33
50
|
private readonly appId;
|
|
34
51
|
private readonly gameModel;
|
|
52
|
+
private readonly engines?;
|
|
35
53
|
private readonly typeName;
|
|
36
|
-
|
|
54
|
+
private readonly moduleName;
|
|
55
|
+
constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, options?: NpcsKitOptions, engines?: EngineDetector | undefined);
|
|
56
|
+
/**
|
|
57
|
+
* Is an NPC compute engine deployed + enabled (cached per session)? When
|
|
58
|
+
* true, NPCs stream smooth FLAG_NPC actor poses — overlay them with
|
|
59
|
+
* {@link overlayLivePoses}. When false (model-only deployment), the polled
|
|
60
|
+
* container positions are all there is, exactly as before.
|
|
61
|
+
*/
|
|
62
|
+
engineAvailable(): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Overlay live engine-driven poses onto a polled NPC snapshot (the
|
|
65
|
+
* generalized BWF `NpcService.withLivePoses` pattern): each NPC whose
|
|
66
|
+
* `actor_uuid` has a fresh pose in the npcs actor lane gets its position
|
|
67
|
+
* replaced; the rest keep their durable container position, so NPCs stand
|
|
68
|
+
* at their last synced spot instead of disappearing.
|
|
69
|
+
*
|
|
70
|
+
* @param npcs - The polled snapshot (from {@link list}).
|
|
71
|
+
* @param lane - The live actors, e.g. `session.actors.lane('npcs').list()`.
|
|
72
|
+
*/
|
|
73
|
+
overlayLivePoses(npcs: KitNpc[], lane: LiveNpcPose[]): KitNpc[];
|
|
37
74
|
/** Spawn a live NPC instance (admin — the type is admin-instantiable). */
|
|
38
75
|
spawn(input: {
|
|
39
76
|
displayName: string;
|
package/dist/kit/npcs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"npcs.d.ts","sourceRoot":"","sources":["../../src/kit/npcs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"npcs.d.ts","sourceRoot":"","sources":["../../src/kit/npcs.ts"],"names":[],"mappings":"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"}
|
package/dist/kit/npcs.js
CHANGED
|
@@ -11,10 +11,45 @@ import { kitContainerProperties } from './shared.js';
|
|
|
11
11
|
* Obtained via `client.kit(appId).npcs`.
|
|
12
12
|
*/
|
|
13
13
|
export class NpcsKit {
|
|
14
|
-
constructor(appId, gameModel, options = {}) {
|
|
14
|
+
constructor(appId, gameModel, options = {}, engines) {
|
|
15
15
|
this.appId = appId;
|
|
16
16
|
this.gameModel = gameModel;
|
|
17
|
+
this.engines = engines;
|
|
17
18
|
this.typeName = options.typeName ?? 'Npc';
|
|
19
|
+
this.moduleName = options.moduleName ?? 'npc-engine';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Is an NPC compute engine deployed + enabled (cached per session)? When
|
|
23
|
+
* true, NPCs stream smooth FLAG_NPC actor poses — overlay them with
|
|
24
|
+
* {@link overlayLivePoses}. When false (model-only deployment), the polled
|
|
25
|
+
* container positions are all there is, exactly as before.
|
|
26
|
+
*/
|
|
27
|
+
engineAvailable() {
|
|
28
|
+
if (!this.engines)
|
|
29
|
+
return Promise.resolve(false);
|
|
30
|
+
return this.engines.has(this.moduleName);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Overlay live engine-driven poses onto a polled NPC snapshot (the
|
|
34
|
+
* generalized BWF `NpcService.withLivePoses` pattern): each NPC whose
|
|
35
|
+
* `actor_uuid` has a fresh pose in the npcs actor lane gets its position
|
|
36
|
+
* replaced; the rest keep their durable container position, so NPCs stand
|
|
37
|
+
* at their last synced spot instead of disappearing.
|
|
38
|
+
*
|
|
39
|
+
* @param npcs - The polled snapshot (from {@link list}).
|
|
40
|
+
* @param lane - The live actors, e.g. `session.actors.lane('npcs').list()`.
|
|
41
|
+
*/
|
|
42
|
+
overlayLivePoses(npcs, lane) {
|
|
43
|
+
if (lane.length === 0)
|
|
44
|
+
return npcs;
|
|
45
|
+
const poses = new Map(lane.map((actor) => [actor.uuid, actor]));
|
|
46
|
+
return npcs.map((npc) => {
|
|
47
|
+
const uuid = String(npc.properties.actor_uuid ?? '');
|
|
48
|
+
const live = uuid ? poses.get(uuid) : undefined;
|
|
49
|
+
if (!live)
|
|
50
|
+
return npc;
|
|
51
|
+
return { ...npc, x: live.state.x, y: live.state.y, z: live.state.z };
|
|
52
|
+
});
|
|
18
53
|
}
|
|
19
54
|
/** Spawn a live NPC instance (admin — the type is admin-instantiable). */
|
|
20
55
|
async spawn(input) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { GameModelAPI } from '../domains/gameModel.js';
|
|
2
|
+
import type { Scalars, SeedPropertyInput } from '../generated/graphql.js';
|
|
3
|
+
import type { EngineDetector } from './engine.js';
|
|
4
|
+
/** Options for {@link PetsKit}. Must match the deployed npc engine. */
|
|
5
|
+
export interface PetsKitOptions {
|
|
6
|
+
/** The compute module serving summon/dismiss/rename. Defaults to `'npc-engine'`. */
|
|
7
|
+
moduleName?: string;
|
|
8
|
+
/** The pet container type. Defaults to `'Pet'`. */
|
|
9
|
+
typeName?: string;
|
|
10
|
+
}
|
|
11
|
+
/** A parsed pet container. */
|
|
12
|
+
export interface KitPet {
|
|
13
|
+
containerId: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
species: string;
|
|
16
|
+
name: string;
|
|
17
|
+
ownerUserId: string | null;
|
|
18
|
+
bond: number;
|
|
19
|
+
active: boolean;
|
|
20
|
+
actorUuid: string;
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
z: number;
|
|
24
|
+
properties: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/** An engine verdict for a pet invoke. */
|
|
27
|
+
export interface KitPetResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runtime helpers for engine-driven pets (the Wave 1 npc-engine template):
|
|
33
|
+
* `Pet` containers hold species/name/owner/bond; the engine walks active
|
|
34
|
+
* pets after their owner (kit-ai `follow_owner`) and streams FLAG_NPC actor
|
|
35
|
+
* poses with the pet's container id as the payload suffix — decode the lane
|
|
36
|
+
* with `kit/wire` and match `pose.suffix` to the container id.
|
|
37
|
+
*
|
|
38
|
+
* Obtained via `client.kit(appId).pets`.
|
|
39
|
+
*/
|
|
40
|
+
export declare class PetsKit {
|
|
41
|
+
private readonly appId;
|
|
42
|
+
private readonly gameModel;
|
|
43
|
+
private readonly engines;
|
|
44
|
+
private readonly moduleName;
|
|
45
|
+
private readonly typeName;
|
|
46
|
+
constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, engines: EngineDetector, options?: PetsKitOptions);
|
|
47
|
+
/** Is the pet-driving npc engine deployed + enabled (cached per session)? */
|
|
48
|
+
engineAvailable(): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Adopt a pet: creates the caller-owned Pet container (active). Member
|
|
51
|
+
* instantiation defaults the owner to the caller; admin tokens must pass
|
|
52
|
+
* `ownerUserId` explicitly (the engine validates ownership on every
|
|
53
|
+
* summon/dismiss/rename).
|
|
54
|
+
*/
|
|
55
|
+
adopt(input: {
|
|
56
|
+
species: string;
|
|
57
|
+
name: string;
|
|
58
|
+
ownerUserId?: Scalars['BigInt']['input'];
|
|
59
|
+
position?: {
|
|
60
|
+
x: number;
|
|
61
|
+
y: number;
|
|
62
|
+
z: number;
|
|
63
|
+
};
|
|
64
|
+
properties?: SeedPropertyInput[];
|
|
65
|
+
}): Promise<{
|
|
66
|
+
__typename?: "GmContainer";
|
|
67
|
+
containerId: string;
|
|
68
|
+
appId: string;
|
|
69
|
+
sessionId: string | null;
|
|
70
|
+
typeName: string;
|
|
71
|
+
displayName: string;
|
|
72
|
+
description: string | null;
|
|
73
|
+
ownerUserId: string | null;
|
|
74
|
+
metadataJson: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** List pets (all, or one owner's with `ownerUserId`). */
|
|
77
|
+
list(ownerUserId?: Scalars['BigInt']['input']): Promise<KitPet[]>;
|
|
78
|
+
/** Summon your pet: it starts following you (owner-validated engine-side). */
|
|
79
|
+
summon(containerId: string): Promise<KitPetResult>;
|
|
80
|
+
/** Dismiss your pet: it stops simulating until summoned again. */
|
|
81
|
+
dismiss(containerId: string): Promise<KitPetResult>;
|
|
82
|
+
/** Rename your pet (1-32 chars; owner-validated engine-side). */
|
|
83
|
+
rename(containerId: string, name: string): Promise<KitPetResult>;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=pets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pets.d.ts","sourceRoot":"","sources":["../../src/kit/pets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,qBAAa,OAAO;IAKhB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAGf,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,cAAc,EACxC,OAAO,GAAE,cAAmB;IAM9B,6EAA6E;IAC7E,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;;;;OAKG;IACG,KAAK,CAAC,KAAK,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,QAAQ,CAAC,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;KAClC;;;;;;;;;;;IAuBD,0DAA0D;IACpD,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAoCvE,8EAA8E;IACxE,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAKxD,kEAAkE;IAC5D,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAKzD,iEAAiE;IAC3D,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAOvE"}
|
package/dist/kit/pets.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { kitContainerProperties } from './shared.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime helpers for engine-driven pets (the Wave 1 npc-engine template):
|
|
4
|
+
* `Pet` containers hold species/name/owner/bond; the engine walks active
|
|
5
|
+
* pets after their owner (kit-ai `follow_owner`) and streams FLAG_NPC actor
|
|
6
|
+
* poses with the pet's container id as the payload suffix — decode the lane
|
|
7
|
+
* with `kit/wire` and match `pose.suffix` to the container id.
|
|
8
|
+
*
|
|
9
|
+
* Obtained via `client.kit(appId).pets`.
|
|
10
|
+
*/
|
|
11
|
+
export class PetsKit {
|
|
12
|
+
constructor(appId, gameModel, engines, options = {}) {
|
|
13
|
+
this.appId = appId;
|
|
14
|
+
this.gameModel = gameModel;
|
|
15
|
+
this.engines = engines;
|
|
16
|
+
this.moduleName = options.moduleName ?? 'npc-engine';
|
|
17
|
+
this.typeName = options.typeName ?? 'Pet';
|
|
18
|
+
}
|
|
19
|
+
/** Is the pet-driving npc engine deployed + enabled (cached per session)? */
|
|
20
|
+
engineAvailable() {
|
|
21
|
+
return this.engines.has(this.moduleName);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adopt a pet: creates the caller-owned Pet container (active). Member
|
|
25
|
+
* instantiation defaults the owner to the caller; admin tokens must pass
|
|
26
|
+
* `ownerUserId` explicitly (the engine validates ownership on every
|
|
27
|
+
* summon/dismiss/rename).
|
|
28
|
+
*/
|
|
29
|
+
async adopt(input) {
|
|
30
|
+
return this.gameModel.createContainer({
|
|
31
|
+
appId: this.appId,
|
|
32
|
+
typeName: this.typeName,
|
|
33
|
+
displayName: input.name,
|
|
34
|
+
...(input.ownerUserId !== undefined ? { ownerUserId: input.ownerUserId } : {}),
|
|
35
|
+
properties: [
|
|
36
|
+
{ key: 'species', valueType: 'string', valueJson: JSON.stringify(input.species) },
|
|
37
|
+
{ key: 'name', valueType: 'string', valueJson: JSON.stringify(input.name) },
|
|
38
|
+
{ key: 'bond', valueType: 'int', valueJson: '0' },
|
|
39
|
+
{ key: 'active', valueType: 'string', valueJson: '"true"' },
|
|
40
|
+
...(input.position
|
|
41
|
+
? [
|
|
42
|
+
{ key: 'x', valueType: 'int', valueJson: String(Math.round(input.position.x)) },
|
|
43
|
+
{ key: 'y', valueType: 'int', valueJson: String(Math.round(input.position.y)) },
|
|
44
|
+
{ key: 'z', valueType: 'int', valueJson: String(Math.round(input.position.z)) },
|
|
45
|
+
]
|
|
46
|
+
: []),
|
|
47
|
+
...(input.properties ?? []),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** List pets (all, or one owner's with `ownerUserId`). */
|
|
52
|
+
async list(ownerUserId) {
|
|
53
|
+
const containers = await this.gameModel.containers({
|
|
54
|
+
appId: this.appId,
|
|
55
|
+
typeName: this.typeName,
|
|
56
|
+
});
|
|
57
|
+
const filtered = ownerUserId === undefined
|
|
58
|
+
? containers
|
|
59
|
+
: containers.filter((c) => c.ownerUserId != null && String(c.ownerUserId) === String(ownerUserId));
|
|
60
|
+
return Promise.all(filtered.map(async (c) => {
|
|
61
|
+
const props = await kitContainerProperties(this.gameModel, String(this.appId), c.containerId);
|
|
62
|
+
return {
|
|
63
|
+
containerId: c.containerId,
|
|
64
|
+
displayName: c.displayName,
|
|
65
|
+
species: String(props.species ?? ''),
|
|
66
|
+
name: String(props.name ?? c.displayName),
|
|
67
|
+
ownerUserId: c.ownerUserId != null ? String(c.ownerUserId) : null,
|
|
68
|
+
bond: Number(props.bond ?? 0),
|
|
69
|
+
active: props.active !== 'false' && props.active !== false,
|
|
70
|
+
actorUuid: String(props.actor_uuid ?? ''),
|
|
71
|
+
x: Number(props.x ?? 0),
|
|
72
|
+
y: Number(props.y ?? 0),
|
|
73
|
+
z: Number(props.z ?? 0),
|
|
74
|
+
properties: props,
|
|
75
|
+
};
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
/** Summon your pet: it starts following you (owner-validated engine-side). */
|
|
79
|
+
async summon(containerId) {
|
|
80
|
+
const result = await this.engines.invoke(this.moduleName, 'summon', { containerId });
|
|
81
|
+
return { success: result.success, reason: result.reason };
|
|
82
|
+
}
|
|
83
|
+
/** Dismiss your pet: it stops simulating until summoned again. */
|
|
84
|
+
async dismiss(containerId) {
|
|
85
|
+
const result = await this.engines.invoke(this.moduleName, 'dismiss', { containerId });
|
|
86
|
+
return { success: result.success, reason: result.reason };
|
|
87
|
+
}
|
|
88
|
+
/** Rename your pet (1-32 chars; owner-validated engine-side). */
|
|
89
|
+
async rename(containerId, name) {
|
|
90
|
+
const result = await this.engines.invoke(this.moduleName, 'rename_pet', {
|
|
91
|
+
containerId,
|
|
92
|
+
name,
|
|
93
|
+
});
|
|
94
|
+
return { success: result.success, reason: result.reason };
|
|
95
|
+
}
|
|
96
|
+
}
|
package/dist/kit/quests.d.ts
CHANGED
|
@@ -87,6 +87,63 @@ export declare class QuestsKit {
|
|
|
87
87
|
}>;
|
|
88
88
|
/** List a player's quest progress rows. */
|
|
89
89
|
mine(ownerUserId: Scalars['BigInt']['input']): Promise<KitQuestProgress[]>;
|
|
90
|
+
/**
|
|
91
|
+
* STUDIO (admin) — define an ordered tutorial chain as quest defs. Steps
|
|
92
|
+
* are plain quests whose `questId` encodes the chain + index
|
|
93
|
+
* (`"<chain>:<i>"`), so no new server surface is involved: the sequencing
|
|
94
|
+
* is a read-side convention enforced by {@link tutorial} /
|
|
95
|
+
* {@link acceptNextTutorialStep} (a step is `locked` until every earlier
|
|
96
|
+
* step completes).
|
|
97
|
+
*/
|
|
98
|
+
defineTutorial(input: {
|
|
99
|
+
/** Chain id (one app can ship several tutorials). Defaults to `'ftue'`. */
|
|
100
|
+
chain?: string;
|
|
101
|
+
steps: Array<{
|
|
102
|
+
displayName: string;
|
|
103
|
+
targetCount?: number;
|
|
104
|
+
rewardItemId?: string;
|
|
105
|
+
rewardQty?: number;
|
|
106
|
+
rewardGold?: number;
|
|
107
|
+
}>;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
__typename?: "GmContainer";
|
|
110
|
+
containerId: string;
|
|
111
|
+
appId: string;
|
|
112
|
+
sessionId: string | null;
|
|
113
|
+
typeName: string;
|
|
114
|
+
displayName: string;
|
|
115
|
+
description: string | null;
|
|
116
|
+
ownerUserId: string | null;
|
|
117
|
+
metadataJson: string;
|
|
118
|
+
}[]>;
|
|
119
|
+
/** One tutorial step joined with the player's progress. */
|
|
120
|
+
/**
|
|
121
|
+
* A player's view of a tutorial chain: steps in order, each `locked`
|
|
122
|
+
* (an earlier step is incomplete), `active` (the first incomplete step),
|
|
123
|
+
* or `complete`. The client shows/drives only the `active` step; the
|
|
124
|
+
* trusted advance authority is unchanged (players still cannot complete
|
|
125
|
+
* their own quests).
|
|
126
|
+
*/
|
|
127
|
+
tutorial(ownerUserId: Scalars['BigInt']['input'], chain?: string): Promise<Array<{
|
|
128
|
+
stepIndex: number;
|
|
129
|
+
def: KitQuestDef;
|
|
130
|
+
progress: KitQuestProgress | null;
|
|
131
|
+
status: 'locked' | 'active' | 'complete';
|
|
132
|
+
}>>;
|
|
133
|
+
/**
|
|
134
|
+
* Ensure the player's ACTIVE tutorial step has a progress row (accepting
|
|
135
|
+
* it when missing) and return the step. Returns null when the chain is
|
|
136
|
+
* complete. Calling this for a locked step is impossible by construction —
|
|
137
|
+
* it always targets the first incomplete step.
|
|
138
|
+
*/
|
|
139
|
+
acceptNextTutorialStep(ownerUserId: Scalars['BigInt']['input'], chain?: string, options?: {
|
|
140
|
+
sessionId?: string;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
stepIndex: number;
|
|
143
|
+
def: KitQuestDef;
|
|
144
|
+
progress: KitQuestProgress | null;
|
|
145
|
+
status: "locked" | "active" | "complete";
|
|
146
|
+
} | null>;
|
|
90
147
|
/** Read one progress row. */
|
|
91
148
|
state(progressId: string): Promise<KitQuestProgress>;
|
|
92
149
|
/**
|
package/dist/kit/quests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quests.d.ts","sourceRoot":"","sources":["../../src/kit/quests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,8CAA8C;AAC9C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;gBAGjB,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACxC,OAAO,GAAE,gBAAqB;IAKhC,iEAAiE;IAC3D,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IA2BvC,uEAAuE;IACjE,WAAW,CAAC,KAAK,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;KAClC;;;;;;;;;;;IAkCD;;;OAGG;IACG,MAAM,CACV,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACvC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;;;;;;;;;;;IA8B5D,2CAA2C;IACrC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"quests.d.ts","sourceRoot":"","sources":["../../src/kit/quests.ts"],"names":[],"mappings":"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"}
|
package/dist/kit/quests.js
CHANGED
|
@@ -106,6 +106,78 @@ export class QuestsKit {
|
|
|
106
106
|
const mine = containers.filter((c) => c.ownerUserId != null && String(c.ownerUserId) === String(ownerUserId));
|
|
107
107
|
return Promise.all(mine.map((c) => this.state(c.containerId)));
|
|
108
108
|
}
|
|
109
|
+
// -- FTUE / tutorial step sequencing (Wave 2) -----------------------------
|
|
110
|
+
/**
|
|
111
|
+
* STUDIO (admin) — define an ordered tutorial chain as quest defs. Steps
|
|
112
|
+
* are plain quests whose `questId` encodes the chain + index
|
|
113
|
+
* (`"<chain>:<i>"`), so no new server surface is involved: the sequencing
|
|
114
|
+
* is a read-side convention enforced by {@link tutorial} /
|
|
115
|
+
* {@link acceptNextTutorialStep} (a step is `locked` until every earlier
|
|
116
|
+
* step completes).
|
|
117
|
+
*/
|
|
118
|
+
async defineTutorial(input) {
|
|
119
|
+
const chain = input.chain ?? 'ftue';
|
|
120
|
+
const created = [];
|
|
121
|
+
for (const [index, step] of input.steps.entries()) {
|
|
122
|
+
created.push(await this.defineQuest({
|
|
123
|
+
questId: `${chain}:${index}`,
|
|
124
|
+
displayName: step.displayName,
|
|
125
|
+
targetCount: step.targetCount ?? 1,
|
|
126
|
+
rewardItemId: step.rewardItemId,
|
|
127
|
+
rewardQty: step.rewardQty,
|
|
128
|
+
rewardGold: step.rewardGold,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
return created;
|
|
132
|
+
}
|
|
133
|
+
/** One tutorial step joined with the player's progress. */
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- documented shape
|
|
135
|
+
/**
|
|
136
|
+
* A player's view of a tutorial chain: steps in order, each `locked`
|
|
137
|
+
* (an earlier step is incomplete), `active` (the first incomplete step),
|
|
138
|
+
* or `complete`. The client shows/drives only the `active` step; the
|
|
139
|
+
* trusted advance authority is unchanged (players still cannot complete
|
|
140
|
+
* their own quests).
|
|
141
|
+
*/
|
|
142
|
+
async tutorial(ownerUserId, chain = 'ftue') {
|
|
143
|
+
const prefix = `${chain}:`;
|
|
144
|
+
const defs = (await this.catalog())
|
|
145
|
+
.filter((def) => def.questId.startsWith(prefix))
|
|
146
|
+
.map((def) => ({ def, stepIndex: Number(def.questId.slice(prefix.length)) }))
|
|
147
|
+
.filter(({ stepIndex }) => Number.isFinite(stepIndex))
|
|
148
|
+
.sort((a, b) => a.stepIndex - b.stepIndex);
|
|
149
|
+
const progressRows = await this.mine(ownerUserId);
|
|
150
|
+
let blocked = false;
|
|
151
|
+
return defs.map(({ def, stepIndex }) => {
|
|
152
|
+
const progress = progressRows.find((p) => p.questId === def.questId) ?? null;
|
|
153
|
+
const complete = progress?.completed === true;
|
|
154
|
+
const status = complete
|
|
155
|
+
? 'complete'
|
|
156
|
+
: blocked
|
|
157
|
+
? 'locked'
|
|
158
|
+
: 'active';
|
|
159
|
+
if (!complete)
|
|
160
|
+
blocked = true;
|
|
161
|
+
return { stepIndex, def, progress, status };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Ensure the player's ACTIVE tutorial step has a progress row (accepting
|
|
166
|
+
* it when missing) and return the step. Returns null when the chain is
|
|
167
|
+
* complete. Calling this for a locked step is impossible by construction —
|
|
168
|
+
* it always targets the first incomplete step.
|
|
169
|
+
*/
|
|
170
|
+
async acceptNextTutorialStep(ownerUserId, chain = 'ftue', options = {}) {
|
|
171
|
+
const steps = await this.tutorial(ownerUserId, chain);
|
|
172
|
+
const active = steps.find((step) => step.status === 'active');
|
|
173
|
+
if (!active)
|
|
174
|
+
return null;
|
|
175
|
+
if (active.progress)
|
|
176
|
+
return active;
|
|
177
|
+
await this.accept(ownerUserId, active.def.containerId, options);
|
|
178
|
+
const refreshed = await this.tutorial(ownerUserId, chain);
|
|
179
|
+
return refreshed.find((step) => step.stepIndex === active.stepIndex) ?? null;
|
|
180
|
+
}
|
|
109
181
|
/** Read one progress row. */
|
|
110
182
|
async state(progressId) {
|
|
111
183
|
const container = await this.gameModel.container({
|