@zhin.js/plugin-text-adventure 1.0.3 → 1.0.5
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/commands/adv/[action:string=].js +16 -0
- package/commands/adv/[action:string=].ts +2 -2
- package/lib/adv-command.d.ts +2 -4
- package/lib/adv-command.js +4 -8
- package/lib/adv-command.js.map +1 -1
- package/lib/game-flow.d.ts +5 -5
- package/lib/game-flow.js +17 -42
- package/lib/game-flow.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/models.d.ts +0 -1
- package/lib/models.js +0 -1
- package/lib/models.js.map +1 -1
- package/lib/session-service.d.ts +4 -10
- package/lib/session-service.js +7 -48
- package/lib/session-service.js.map +1 -1
- package/middlewares/adv-alias.js +18 -0
- package/middlewares/adv-alias.ts +6 -3
- package/middlewares/adv-choice.js +61 -0
- package/middlewares/adv-choice.ts +2 -2
- package/package.json +8 -8
- package/plugin.js +38 -0
- package/src/adv-command.ts +9 -16
- package/src/game-flow.ts +23 -51
- package/src/index.ts +0 -1
- package/src/models.ts +0 -2
- package/src/session-service.ts +16 -55
- package/lib/memory-db.d.ts +0 -6
- package/lib/memory-db.js +0 -18
- package/lib/memory-db.js.map +0 -1
- package/plugin.ts +0 -65
- package/src/memory-db.ts +0 -22
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { messageFromCommandInput, normalizeAdvAction } from '@zhin.js/game-kit';
|
|
4
|
+
import { ADV_HELP, runAdvCommand } from "../../lib/adv-command.js";
|
|
5
|
+
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'Text Adventure',
|
|
8
|
+
async execute({ params, input, use, owner }) {
|
|
9
|
+
const action = normalizeAdvAction(String(params.action ?? ''));
|
|
10
|
+
if (!action || action === 'help')
|
|
11
|
+
return ADV_HELP;
|
|
12
|
+
const services = resolveGameServices({ use, owner });
|
|
13
|
+
const message = messageFromCommandInput(input);
|
|
14
|
+
return runAdvCommand(services, message, action);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from '@zhin.js/command';
|
|
2
2
|
import { messageFromCommandInput, normalizeAdvAction } from '@zhin.js/game-kit';
|
|
3
|
-
import { ADV_HELP,
|
|
3
|
+
import { ADV_HELP, runAdvCommand } from '../../src/adv-command.js';
|
|
4
4
|
import { resolveGameServices } from '../../src/runtime-store.js';
|
|
5
5
|
|
|
6
6
|
export default defineCommand({
|
|
@@ -10,6 +10,6 @@ export default defineCommand({
|
|
|
10
10
|
if (!action || action === 'help') return ADV_HELP;
|
|
11
11
|
const services = resolveGameServices({ use, owner });
|
|
12
12
|
const message = messageFromCommandInput(input);
|
|
13
|
-
return
|
|
13
|
+
return runAdvCommand(services, message, action);
|
|
14
14
|
},
|
|
15
15
|
});
|
package/lib/adv-command.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
|
|
2
2
|
import type { GameServices } from './session-service.js';
|
|
3
3
|
export declare const ADV_HELP: string;
|
|
4
|
-
export declare function runAdvCommand(
|
|
5
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
6
|
-
export declare function runAdvCommandText(services: GameServices, message: Message<any>, action: string): Promise<string>;
|
|
4
|
+
export declare function runAdvCommand(services: GameServices, message: GameMessageLike, action: string): Promise<GameReply>;
|
package/lib/adv-command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { channelKey } from '@zhin.js/game-kit';
|
|
1
|
+
import { channelKey, } from '@zhin.js/game-kit';
|
|
2
2
|
import { continueAdventure, sessionSummary, startAdventure, } from './game-flow.js';
|
|
3
3
|
import { formatAchievements, formatMapProgress, formatProgressCompact } from './profile-format.js';
|
|
4
4
|
export const ADV_HELP = [
|
|
@@ -12,7 +12,7 @@ export const ADV_HELP = [
|
|
|
12
12
|
'',
|
|
13
13
|
'推进方式:点击选项按钮,或回复数字(1、2…)',
|
|
14
14
|
].join('\n');
|
|
15
|
-
export async function runAdvCommand(
|
|
15
|
+
export async function runAdvCommand(services, message, action) {
|
|
16
16
|
const ch = channelKey(message);
|
|
17
17
|
const userId = message.$sender.id;
|
|
18
18
|
const userName = message.$sender.name?.trim() || userId;
|
|
@@ -30,10 +30,10 @@ export async function runAdvCommand(plugin, services, message, action) {
|
|
|
30
30
|
return lines.join('\n');
|
|
31
31
|
}
|
|
32
32
|
if (action === 'start') {
|
|
33
|
-
return startAdventure(
|
|
33
|
+
return (await startAdventure(services, message)) ?? '';
|
|
34
34
|
}
|
|
35
35
|
if (action === 'continue') {
|
|
36
|
-
return continueAdventure(
|
|
36
|
+
return continueAdventure(services, message);
|
|
37
37
|
}
|
|
38
38
|
if (action === 'map') {
|
|
39
39
|
const profile = await services.profiles.getOrCreate(userId, userName);
|
|
@@ -52,8 +52,4 @@ export async function runAdvCommand(plugin, services, message, action) {
|
|
|
52
52
|
}
|
|
53
53
|
return `未知子命令:${action}\n\n${ADV_HELP}`;
|
|
54
54
|
}
|
|
55
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
56
|
-
export async function runAdvCommandText(services, message, action) {
|
|
57
|
-
return (await runAdvCommand(null, services, message, action)) ?? '';
|
|
58
|
-
}
|
|
59
55
|
//# sourceMappingURL=adv-command.js.map
|
package/lib/adv-command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adv-command.js","sourceRoot":"","sources":["../src/adv-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adv-command.js","sourceRoot":"","sources":["../src/adv-command.ts"],"names":[],"mappings":"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"}
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
2
|
import type { AdvProfileRow, AdvSessionRow } from './models.js';
|
|
3
3
|
import type { GameServices } from './session-service.js';
|
|
4
|
-
export declare function sendOrEditScene(
|
|
5
|
-
export declare function startAdventure(
|
|
6
|
-
export declare function handleChoice(
|
|
7
|
-
export declare function continueAdventure(
|
|
4
|
+
export declare function sendOrEditScene(services: GameServices, message: GameMessageLike, session: AdvSessionRow, extraNarrative?: string, profile?: AdvProfileRow): Promise<GameReply>;
|
|
5
|
+
export declare function startAdventure(services: GameServices, message: GameMessageLike): Promise<GameReply | undefined>;
|
|
6
|
+
export declare function handleChoice(services: GameServices, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply | null>;
|
|
7
|
+
export declare function continueAdventure(services: GameServices, message: GameMessageLike): Promise<GameReply>;
|
|
8
8
|
export declare function sessionSummary(session: AdvSessionRow): string;
|
package/lib/game-flow.js
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
|
-
import { plainTextFromSendContent } from '@zhin.js/game-kit';
|
|
2
1
|
import { formatNewAchievements } from './profile-format.js';
|
|
3
2
|
import { buildSceneInteractive } from './scene-view.js';
|
|
4
|
-
import { applyChoiceResult, getScene, resolveChoice, stateFromSession, } from './story.js';
|
|
5
|
-
export async function sendOrEditScene(
|
|
3
|
+
import { applyChoiceResult, getScene, resolveChoice, stateFromSession, visibleChoices, } from './story.js';
|
|
4
|
+
export async function sendOrEditScene(services, message, session, extraNarrative = '', profile) {
|
|
6
5
|
const prof = profile
|
|
7
6
|
?? await services.profiles.getOrCreate(session.player_id, session.player_name);
|
|
8
7
|
const content = buildSceneInteractive(session, extraNarrative, prof, message.$channel.type);
|
|
9
8
|
if (!content) {
|
|
10
9
|
return '场景数据异常,请 adv quit 后重新开始。';
|
|
11
10
|
}
|
|
12
|
-
|
|
13
|
-
return plainTextFromSendContent(content);
|
|
14
|
-
const adapter = plugin.root.inject(message.$adapter);
|
|
15
|
-
if (session.board_message_id) {
|
|
16
|
-
const msgId = await adapter.editMessage({
|
|
17
|
-
messageId: session.board_message_id,
|
|
18
|
-
context: String(message.$adapter),
|
|
19
|
-
endpoint: message.$endpoint,
|
|
20
|
-
id: message.$channel.id,
|
|
21
|
-
type: message.$channel.type,
|
|
22
|
-
content,
|
|
23
|
-
});
|
|
24
|
-
if (msgId !== session.board_message_id) {
|
|
25
|
-
await services.sessions.updateSession(session.id, { board_message_id: msgId });
|
|
26
|
-
}
|
|
27
|
-
return msgId;
|
|
28
|
-
}
|
|
29
|
-
const msgId = await message.$reply?.(content);
|
|
30
|
-
if (msgId) {
|
|
31
|
-
await services.sessions.updateSession(session.id, { board_message_id: msgId });
|
|
32
|
-
}
|
|
33
|
-
return msgId ?? '';
|
|
11
|
+
return content;
|
|
34
12
|
}
|
|
35
|
-
export async function startAdventure(
|
|
13
|
+
export async function startAdventure(services, message) {
|
|
36
14
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
37
15
|
const active = await services.sessions.getActiveByChannel(ch);
|
|
38
16
|
if (active) {
|
|
@@ -44,12 +22,12 @@ export async function startAdventure(plugin, services, message) {
|
|
|
44
22
|
await services.profiles.onRunStarted(message.$sender.id, message.$sender.name?.trim() || message.$sender.id);
|
|
45
23
|
const session = await services.sessions.createSession(message);
|
|
46
24
|
await services.profiles.onStep(message.$sender.id, session.player_name, 'start', []);
|
|
47
|
-
|
|
48
|
-
return plugin ? undefined : text;
|
|
25
|
+
return sendOrEditScene(services, message, session);
|
|
49
26
|
}
|
|
50
|
-
export async function handleChoice(
|
|
27
|
+
export async function handleChoice(services, message, sessionId, choiceId) {
|
|
51
28
|
const session = await services.sessions.getById(sessionId);
|
|
52
|
-
|
|
29
|
+
// 踏进 terminal 场景时 status 必为 completed,终局需放行「再玩一次」(restart)
|
|
30
|
+
if (!session || (session.status !== 'active' && !(session.status === 'completed' && choiceId === 'restart'))) {
|
|
53
31
|
return '冒险不存在或已结束。';
|
|
54
32
|
}
|
|
55
33
|
if (session.player_id !== message.$sender.id) {
|
|
@@ -71,13 +49,15 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
71
49
|
ending_id: '',
|
|
72
50
|
status: 'active',
|
|
73
51
|
step_count: 0,
|
|
74
|
-
board_message_id: '',
|
|
75
52
|
});
|
|
76
53
|
const updated = (await services.sessions.getById(session.id));
|
|
54
|
+
await services.profiles.onRunStarted(updated.player_id, updated.player_name);
|
|
77
55
|
await services.profiles.onStep(updated.player_id, updated.player_name, 'start', []);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
56
|
+
return sendOrEditScene(services, message, updated);
|
|
57
|
+
}
|
|
58
|
+
// 服务端校验:选项必须当前可见(requires 门槛不能只靠视图层过滤,payload 可绕过)
|
|
59
|
+
if (!visibleChoices(scene, state).some((c) => c.id === choiceId)) {
|
|
60
|
+
return '该选项不可用。';
|
|
81
61
|
}
|
|
82
62
|
const result = resolveChoice(state, choiceId);
|
|
83
63
|
if (!result)
|
|
@@ -108,18 +88,13 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
108
88
|
}
|
|
109
89
|
const updated = (await services.sessions.getById(session.id));
|
|
110
90
|
const extra = formatNewAchievements(newAchievements);
|
|
111
|
-
|
|
112
|
-
// text-only 模式(plugin===null)下 sendOrEditScene 的唯一输出就是返回文本
|
|
113
|
-
return plugin ? null : text;
|
|
91
|
+
return sendOrEditScene(services, message, updated, extra);
|
|
114
92
|
}
|
|
115
|
-
export async function continueAdventure(
|
|
93
|
+
export async function continueAdventure(services, message) {
|
|
116
94
|
const session = await services.sessions.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
|
|
117
95
|
if (!session)
|
|
118
96
|
return '你没有进行中的冒险,发送 adv start 开始。';
|
|
119
|
-
|
|
120
|
-
if (!plugin)
|
|
121
|
-
return text;
|
|
122
|
-
return '已刷新当前场景。';
|
|
97
|
+
return sendOrEditScene(services, message, session);
|
|
123
98
|
}
|
|
124
99
|
export function sessionSummary(session) {
|
|
125
100
|
const scene = getScene(session.scene_id);
|
package/lib/game-flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { ADV_HELP } from './adv-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryAdvDb, mountAdvMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
5
|
* 游戏大厅注册(registerRuntimeGame)、过期会话 cron(scheduleHostToken)、
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { ADV_HELP } from './adv-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryAdvDb, mountAdvMemoryServices } from './memory-db.js';
|
|
4
3
|
/**
|
|
5
4
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
|
6
5
|
* 游戏大厅注册(registerRuntimeGame)、过期会话 cron(scheduleHostToken)、
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;;GAIG"}
|
package/lib/models.d.ts
CHANGED
package/lib/models.js
CHANGED
|
@@ -14,7 +14,6 @@ export function defineHostTables(db) {
|
|
|
14
14
|
flags: { type: 'text', default: '{}' },
|
|
15
15
|
ending_id: { type: 'text', default: '' },
|
|
16
16
|
status: { type: 'text', default: 'active' },
|
|
17
|
-
board_message_id: { type: 'text', default: '' },
|
|
18
17
|
step_count: { type: 'integer', default: 0 },
|
|
19
18
|
updated_at: { type: 'integer', default: 0 },
|
|
20
19
|
created_at: { type: 'integer', default: 0 },
|
package/lib/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AA+CA,MAAM,UAAU,gBAAgB,CAC9B,EAA2E;IAE3E,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE;QACxB,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACnC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC1C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC3C,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC/C,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC7C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;QAC5C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1C,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE;QAC5C,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE;QACrC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC1C,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACtC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QACxC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC5C,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE;QACxB,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC1C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1C,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC/C,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC7C,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC5C,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QAC7C,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC7C,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC/C,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC5C,eAAe,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAChD,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;KAC5C,CAAC,CAAC;AACL,CAAC"}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Database,
|
|
1
|
+
import type { Database, Models } from '@zhin.js/core';
|
|
2
|
+
import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
|
|
2
3
|
import type { AdvSessionRow } from './models.js';
|
|
3
4
|
import { ProfileService } from './profile-service.js';
|
|
4
5
|
export type AdvDatabase = Database<unknown, Models, string>;
|
|
@@ -6,15 +7,8 @@ export interface GameServices {
|
|
|
6
7
|
sessions: SessionService;
|
|
7
8
|
profiles: ProfileService;
|
|
8
9
|
}
|
|
9
|
-
export declare class SessionService {
|
|
10
|
-
private readonly db;
|
|
10
|
+
export declare class SessionService extends BaseSessionService<AdvSessionRow> {
|
|
11
11
|
constructor(db: AdvDatabase);
|
|
12
|
-
|
|
13
|
-
getActiveForUser(channel: string, userId: string): Promise<AdvSessionRow | null>;
|
|
14
|
-
getById(id: string): Promise<AdvSessionRow | null>;
|
|
15
|
-
getActiveByBoardMessageId(messageId: string): Promise<AdvSessionRow | null>;
|
|
16
|
-
createSession(message: Message<any>): Promise<AdvSessionRow>;
|
|
17
|
-
updateSession(id: string, patch: Partial<AdvSessionRow>): Promise<void>;
|
|
18
|
-
abortStale(idleMs: number): Promise<number>;
|
|
12
|
+
createSession(message: GameMessageLike): Promise<AdvSessionRow>;
|
|
19
13
|
}
|
|
20
14
|
export declare function createServices(db: AdvDatabase): GameServices;
|
package/lib/session-service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { channelKey, generateSessionId,
|
|
1
|
+
import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
|
|
2
2
|
import { createProfileService } from './profile-service.js';
|
|
3
3
|
function getModel(db, name) {
|
|
4
4
|
const model = db.models.get(name);
|
|
@@ -6,36 +6,13 @@ function getModel(db, name) {
|
|
|
6
6
|
throw new Error(`Model ${name} is not registered`);
|
|
7
7
|
return model;
|
|
8
8
|
}
|
|
9
|
-
export class SessionService {
|
|
10
|
-
db;
|
|
9
|
+
export class SessionService extends BaseSessionService {
|
|
11
10
|
constructor(db) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
channel_key: channel,
|
|
17
|
-
status: 'active',
|
|
11
|
+
super(db, {
|
|
12
|
+
gameId: 'adv',
|
|
13
|
+
table: 'adv_sessions',
|
|
14
|
+
userFields: ['player_id'],
|
|
18
15
|
});
|
|
19
|
-
return rows[0] ?? null;
|
|
20
|
-
}
|
|
21
|
-
async getActiveForUser(channel, userId) {
|
|
22
|
-
const row = await this.getActiveByChannel(channel);
|
|
23
|
-
if (!row || row.player_id !== userId)
|
|
24
|
-
return null;
|
|
25
|
-
return row;
|
|
26
|
-
}
|
|
27
|
-
async getById(id) {
|
|
28
|
-
return getModel(this.db, 'adv_sessions').findOne({ id });
|
|
29
|
-
}
|
|
30
|
-
async getActiveByBoardMessageId(messageId) {
|
|
31
|
-
if (!messageId)
|
|
32
|
-
return null;
|
|
33
|
-
const rows = await getModel(this.db, 'adv_sessions').findAll({});
|
|
34
|
-
for (const row of rows) {
|
|
35
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId))
|
|
36
|
-
return row;
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
16
|
}
|
|
40
17
|
async createSession(message) {
|
|
41
18
|
const now = Date.now();
|
|
@@ -55,29 +32,11 @@ export class SessionService {
|
|
|
55
32
|
flags: '{}',
|
|
56
33
|
ending_id: '',
|
|
57
34
|
status: 'active',
|
|
58
|
-
board_message_id: '',
|
|
59
35
|
step_count: 0,
|
|
60
36
|
updated_at: now,
|
|
61
37
|
created_at: now,
|
|
62
38
|
};
|
|
63
|
-
|
|
64
|
-
return row;
|
|
65
|
-
}
|
|
66
|
-
async updateSession(id, patch) {
|
|
67
|
-
await getModel(this.db, 'adv_sessions').updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
68
|
-
}
|
|
69
|
-
async abortStale(idleMs) {
|
|
70
|
-
const cutoff = Date.now() - idleMs;
|
|
71
|
-
const sessions = getModel(this.db, 'adv_sessions');
|
|
72
|
-
const rows = await sessions.findAll({ status: 'active' });
|
|
73
|
-
let n = 0;
|
|
74
|
-
for (const row of rows) {
|
|
75
|
-
if (row.updated_at < cutoff) {
|
|
76
|
-
await sessions.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
77
|
-
n++;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return n;
|
|
39
|
+
return this.createRow(row);
|
|
81
40
|
}
|
|
82
41
|
}
|
|
83
42
|
export function createServices(db) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"session-service.js","sourceRoot":"","sources":["../src/session-service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,iBAAiB,GAGlB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,oBAAoB,EAAkB,MAAM,sBAAsB,CAAC;AAK5E,SAAS,QAAQ,CAAyB,EAAe,EAAE,IAAO;IAChE,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,oBAAoB,CAAC,CAAC;IAC/D,OAAO,KAAoB,CAAC;AAC9B,CAAC;AAOD,MAAM,OAAO,cAAe,SAAQ,kBAAiC;IACnE,YAAY,EAAe;QACzB,KAAK,CAAC,EAAmD,EAAE;YACzD,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,cAAc;YACrB,UAAU,EAAE,CAAC,WAAW,CAAC;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAkB;YACzB,EAAE;YACF,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YACjC,QAAQ,EAAE,OAAO,CAAC,SAAS;YAC3B,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI;YACnC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE;YAC/B,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;YAChC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC/D,QAAQ,EAAE,OAAO;YACjB,EAAE,EAAE,GAAG;YACP,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,IAAI;YACX,SAAS,EAAE,EAAE;YACb,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;SAChB,CAAC;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO;QACL,QAAQ,EAAE,IAAI,cAAc,CAAC,EAAE,CAAC;QAChC,QAAQ,EAAE,oBAAoB,CAAC,EAAE,CAAC;KACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineGameCommandAliasMiddleware, isAdvAction, messageFromCommandInput, normalizeAdvAction, } from '@zhin.js/game-kit';
|
|
3
|
+
import { ADV_HELP, runAdvCommand } from "../lib/adv-command.js";
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
export default defineGameCommandAliasMiddleware({
|
|
6
|
+
aliases: ['冒险', 'adv', '秘境'],
|
|
7
|
+
async run(action, input, context) {
|
|
8
|
+
const normalized = normalizeAdvAction(String(action ?? ''));
|
|
9
|
+
if (normalized === 'help')
|
|
10
|
+
return ADV_HELP;
|
|
11
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
12
|
+
if (!isAdvAction(normalized))
|
|
13
|
+
return null;
|
|
14
|
+
const services = resolveGameServices(context);
|
|
15
|
+
const message = messageFromCommandInput(input);
|
|
16
|
+
return runAdvCommand(services, message, normalized);
|
|
17
|
+
},
|
|
18
|
+
});
|
package/middlewares/adv-alias.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineGameCommandAliasMiddleware,
|
|
3
|
+
isAdvAction,
|
|
3
4
|
messageFromCommandInput,
|
|
4
5
|
normalizeAdvAction,
|
|
5
6
|
} from '@zhin.js/game-kit';
|
|
6
|
-
import { ADV_HELP,
|
|
7
|
+
import { ADV_HELP, runAdvCommand } from '../src/adv-command.js';
|
|
7
8
|
import { resolveGameServices } from '../src/runtime-store.js';
|
|
8
9
|
|
|
9
10
|
export default defineGameCommandAliasMiddleware({
|
|
10
11
|
aliases: ['冒险', 'adv', '秘境'],
|
|
11
12
|
async run(action, input, context) {
|
|
12
13
|
const normalized = normalizeAdvAction(String(action ?? ''));
|
|
13
|
-
if (
|
|
14
|
+
if (normalized === 'help') return ADV_HELP;
|
|
15
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
16
|
+
if (!isAdvAction(normalized)) return null;
|
|
14
17
|
const services = resolveGameServices(context);
|
|
15
18
|
const message = messageFromCommandInput(input);
|
|
16
|
-
return
|
|
19
|
+
return runAdvCommand(services, message, normalized);
|
|
17
20
|
},
|
|
18
21
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { buildChoiceFallbackMap, channelKey, messageFromCommandInput, parseChoicePayload, resolveGameTextPayload, } from '@zhin.js/game-kit';
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
import { handleChoice } from "../lib/game-flow.js";
|
|
6
|
+
import { ADV_PREFIX, getScene, stateFromSession, visibleChoices } from "../lib/story.js";
|
|
7
|
+
/**
|
|
8
|
+
* 文本入口:按钮 payload(`adv:session:choiceId`)与数字 fallback
|
|
9
|
+
* (按当前场景可见选项编号,对应视图提示用户回复数字选分支)。
|
|
10
|
+
*/
|
|
11
|
+
export default defineMiddleware({
|
|
12
|
+
target: 'inbound',
|
|
13
|
+
async handle(context, next) {
|
|
14
|
+
const raw = context.input.content?.trim() ?? '';
|
|
15
|
+
if (!raw) {
|
|
16
|
+
await next();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const services = resolveGameServices(context);
|
|
20
|
+
const message = messageFromCommandInput(context.input);
|
|
21
|
+
const ch = channelKey(message);
|
|
22
|
+
// 直接 payload(QQ 指令预填 / Sandbox action→text)
|
|
23
|
+
const payloadFromText = resolveGameTextPayload(raw);
|
|
24
|
+
if (payloadFromText?.startsWith(`${ADV_PREFIX}:`)) {
|
|
25
|
+
const parsed = parseChoicePayload(payloadFromText, ADV_PREFIX);
|
|
26
|
+
if (parsed) {
|
|
27
|
+
const session = await services.sessions.getById(parsed.sessionId);
|
|
28
|
+
if (session?.channel_key === ch) {
|
|
29
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
30
|
+
if (reply)
|
|
31
|
+
await context.input.$reply(reply);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
await next();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const session = await services.sessions.getActiveForUser(ch, message.$sender.id);
|
|
39
|
+
if (!session) {
|
|
40
|
+
await next();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const scene = getScene(session.scene_id);
|
|
44
|
+
if (!scene) {
|
|
45
|
+
await next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const state = stateFromSession(session);
|
|
49
|
+
const choices = visibleChoices(scene, state);
|
|
50
|
+
const map = buildChoiceFallbackMap(ADV_PREFIX, session.id, choices);
|
|
51
|
+
const payload = resolveGameTextPayload(raw, map);
|
|
52
|
+
const parsed = payload ? parseChoicePayload(payload, ADV_PREFIX) : null;
|
|
53
|
+
if (!parsed || parsed.sessionId !== session.id) {
|
|
54
|
+
await next();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const reply = await handleChoice(services, message, session.id, parsed.choiceId);
|
|
58
|
+
if (reply)
|
|
59
|
+
await context.input.$reply(reply);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -34,7 +34,7 @@ export default defineMiddleware<Message>({
|
|
|
34
34
|
if (parsed) {
|
|
35
35
|
const session = await services.sessions.getById(parsed.sessionId);
|
|
36
36
|
if (session?.channel_key === ch) {
|
|
37
|
-
const reply = await handleChoice(
|
|
37
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
38
38
|
if (reply) await context.input.$reply(reply);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -64,7 +64,7 @@ export default defineMiddleware<Message>({
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const reply = await handleChoice(
|
|
67
|
+
const reply = await handleChoice(services, message, session.id, parsed.choiceId);
|
|
68
68
|
if (reply) await context.input.$reply(reply);
|
|
69
69
|
},
|
|
70
70
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-text-adventure",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Zhin.js 交互式文字冒险 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"plugin.
|
|
17
|
+
"plugin.js",
|
|
18
18
|
"schema.json",
|
|
19
19
|
"commands",
|
|
20
20
|
"middlewares",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zhin.js/command": "1.0.
|
|
28
|
-
"@zhin.js/core": "1.4.
|
|
29
|
-
"@zhin.js/game-kit": "2.0.
|
|
30
|
-
"@zhin.js/middleware": "1.0.
|
|
31
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
27
|
+
"@zhin.js/command": "1.0.4",
|
|
28
|
+
"@zhin.js/core": "1.4.2",
|
|
29
|
+
"@zhin.js/game-kit": "2.0.2",
|
|
30
|
+
"@zhin.js/middleware": "1.0.4",
|
|
31
|
+
"@zhin.js/plugin-runtime": "1.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "^6.0.3",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"zhin": {
|
|
50
50
|
"protocol": 1,
|
|
51
51
|
"type": "plugin",
|
|
52
|
-
"entry": "./plugin.
|
|
52
|
+
"entry": "./plugin.js",
|
|
53
53
|
"engine": "^1.0.0",
|
|
54
54
|
"runtime": "trusted",
|
|
55
55
|
"features": [
|
package/plugin.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineGamePlugin } from '@zhin.js/game-kit';
|
|
3
|
+
import { defineHostTables } from "./lib/models.js";
|
|
4
|
+
import { gameServicesToken } from "./lib/runtime-store.js";
|
|
5
|
+
import { createServices, } from "./lib/session-service.js";
|
|
6
|
+
/**
|
|
7
|
+
* Plugin Runtime:
|
|
8
|
+
* - Commands under `commands/` are authoritative (help + start/continue/…).
|
|
9
|
+
* - DB: prefer databaseHostToken; else in-memory GameServices.
|
|
10
|
+
* - Choice middleware under `middlewares/` handles adv choice payloads (Sandbox action→text).
|
|
11
|
+
*/
|
|
12
|
+
export default defineGamePlugin({
|
|
13
|
+
name: 'text-adventure',
|
|
14
|
+
metadata: {
|
|
15
|
+
displayName: 'Text Adventure',
|
|
16
|
+
},
|
|
17
|
+
game: {
|
|
18
|
+
id: 'adv',
|
|
19
|
+
title: '秘境探险',
|
|
20
|
+
icon: '🗺️',
|
|
21
|
+
description: '文字冒险,31 区域 · 15 结局 · 成就收集',
|
|
22
|
+
commandPrefix: '/冒险',
|
|
23
|
+
quickStart: '开始',
|
|
24
|
+
aliases: ['adv', '秘境'],
|
|
25
|
+
menus: [
|
|
26
|
+
{ id: 'start', label: '🚪 开始冒险' },
|
|
27
|
+
{ id: 'continue', label: '🔄 继续冒险' },
|
|
28
|
+
{ id: 'map', label: '🗺️ 探索进度' },
|
|
29
|
+
{ id: 'achievements', label: '🏅 成就' },
|
|
30
|
+
{ id: 'help', label: '📖 玩法说明' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
tables: ['adv_sessions', 'adv_profiles'],
|
|
34
|
+
servicesToken: gameServicesToken,
|
|
35
|
+
defineHostTables,
|
|
36
|
+
createServices: (database) => createServices(database),
|
|
37
|
+
session: (services) => services.sessions,
|
|
38
|
+
});
|
package/src/adv-command.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
channelKey,
|
|
3
|
+
type GameMessageLike,
|
|
4
|
+
type GameReply,
|
|
5
|
+
} from '@zhin.js/game-kit';
|
|
3
6
|
import {
|
|
4
7
|
continueAdventure,
|
|
5
8
|
sessionSummary,
|
|
@@ -21,11 +24,10 @@ export const ADV_HELP = [
|
|
|
21
24
|
].join('\n');
|
|
22
25
|
|
|
23
26
|
export async function runAdvCommand(
|
|
24
|
-
plugin: Plugin | null,
|
|
25
27
|
services: GameServices,
|
|
26
|
-
message:
|
|
28
|
+
message: GameMessageLike,
|
|
27
29
|
action: string,
|
|
28
|
-
): Promise<
|
|
30
|
+
): Promise<GameReply> {
|
|
29
31
|
const ch = channelKey(message);
|
|
30
32
|
const userId = message.$sender.id;
|
|
31
33
|
const userName = message.$sender.name?.trim() || userId;
|
|
@@ -44,11 +46,11 @@ export async function runAdvCommand(
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
if (action === 'start') {
|
|
47
|
-
return startAdventure(
|
|
49
|
+
return (await startAdventure(services, message)) ?? '';
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
if (action === 'continue') {
|
|
51
|
-
return continueAdventure(
|
|
53
|
+
return continueAdventure(services, message);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
if (action === 'map') {
|
|
@@ -70,12 +72,3 @@ export async function runAdvCommand(
|
|
|
70
72
|
|
|
71
73
|
return `未知子命令:${action}\n\n${ADV_HELP}`;
|
|
72
74
|
}
|
|
73
|
-
|
|
74
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
75
|
-
export async function runAdvCommandText(
|
|
76
|
-
services: GameServices,
|
|
77
|
-
message: Message<any>,
|
|
78
|
-
action: string,
|
|
79
|
-
): Promise<string> {
|
|
80
|
-
return (await runAdvCommand(null, services, message, action)) ?? '';
|
|
81
|
-
}
|
package/src/game-flow.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { plainTextFromSendContent } from '@zhin.js/game-kit';
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
3
2
|
import type { AdvProfileRow, AdvSessionRow } from './models.js';
|
|
4
3
|
import { formatNewAchievements } from './profile-format.js';
|
|
5
4
|
import { buildSceneInteractive } from './scene-view.js';
|
|
@@ -8,54 +7,30 @@ import {
|
|
|
8
7
|
getScene,
|
|
9
8
|
resolveChoice,
|
|
10
9
|
stateFromSession,
|
|
10
|
+
visibleChoices,
|
|
11
11
|
} from './story.js';
|
|
12
12
|
import type { GameServices } from './session-service.js';
|
|
13
13
|
|
|
14
14
|
export async function sendOrEditScene(
|
|
15
|
-
plugin: Plugin | null,
|
|
16
15
|
services: GameServices,
|
|
17
|
-
message:
|
|
16
|
+
message: GameMessageLike,
|
|
18
17
|
session: AdvSessionRow,
|
|
19
18
|
extraNarrative = '',
|
|
20
19
|
profile?: AdvProfileRow,
|
|
21
|
-
): Promise<
|
|
20
|
+
): Promise<GameReply> {
|
|
22
21
|
const prof = profile
|
|
23
22
|
?? await services.profiles.getOrCreate(session.player_id, session.player_name);
|
|
24
23
|
const content = buildSceneInteractive(session, extraNarrative, prof, message.$channel.type);
|
|
25
24
|
if (!content) {
|
|
26
25
|
return '场景数据异常,请 adv quit 后重新开始。';
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const adapter = plugin.root.inject(message.$adapter) as Adapter;
|
|
31
|
-
|
|
32
|
-
if (session.board_message_id) {
|
|
33
|
-
const msgId = await adapter.editMessage({
|
|
34
|
-
messageId: session.board_message_id,
|
|
35
|
-
context: String(message.$adapter),
|
|
36
|
-
endpoint: message.$endpoint,
|
|
37
|
-
id: message.$channel.id,
|
|
38
|
-
type: message.$channel.type,
|
|
39
|
-
content,
|
|
40
|
-
});
|
|
41
|
-
if (msgId !== session.board_message_id) {
|
|
42
|
-
await services.sessions.updateSession(session.id, { board_message_id: msgId });
|
|
43
|
-
}
|
|
44
|
-
return msgId;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const msgId = await message.$reply?.(content);
|
|
48
|
-
if (msgId) {
|
|
49
|
-
await services.sessions.updateSession(session.id, { board_message_id: msgId });
|
|
50
|
-
}
|
|
51
|
-
return msgId ?? '';
|
|
27
|
+
return content;
|
|
52
28
|
}
|
|
53
29
|
|
|
54
30
|
export async function startAdventure(
|
|
55
|
-
plugin: Plugin | null,
|
|
56
31
|
services: GameServices,
|
|
57
|
-
message:
|
|
58
|
-
): Promise<
|
|
32
|
+
message: GameMessageLike,
|
|
33
|
+
): Promise<GameReply | undefined> {
|
|
59
34
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
60
35
|
const active = await services.sessions.getActiveByChannel(ch);
|
|
61
36
|
if (active) {
|
|
@@ -73,19 +48,18 @@ export async function startAdventure(
|
|
|
73
48
|
'start',
|
|
74
49
|
[],
|
|
75
50
|
);
|
|
76
|
-
|
|
77
|
-
return plugin ? undefined : text;
|
|
51
|
+
return sendOrEditScene(services, message, session);
|
|
78
52
|
}
|
|
79
53
|
|
|
80
54
|
export async function handleChoice(
|
|
81
|
-
plugin: Plugin | null,
|
|
82
55
|
services: GameServices,
|
|
83
|
-
message:
|
|
56
|
+
message: GameMessageLike,
|
|
84
57
|
sessionId: string,
|
|
85
58
|
choiceId: string,
|
|
86
|
-
): Promise<
|
|
59
|
+
): Promise<GameReply | null> {
|
|
87
60
|
const session = await services.sessions.getById(sessionId);
|
|
88
|
-
|
|
61
|
+
// 踏进 terminal 场景时 status 必为 completed,终局需放行「再玩一次」(restart)
|
|
62
|
+
if (!session || (session.status !== 'active' && !(session.status === 'completed' && choiceId === 'restart'))) {
|
|
89
63
|
return '冒险不存在或已结束。';
|
|
90
64
|
}
|
|
91
65
|
if (session.player_id !== message.$sender.id) {
|
|
@@ -109,13 +83,16 @@ export async function handleChoice(
|
|
|
109
83
|
ending_id: '',
|
|
110
84
|
status: 'active',
|
|
111
85
|
step_count: 0,
|
|
112
|
-
board_message_id: '',
|
|
113
86
|
});
|
|
114
87
|
const updated = (await services.sessions.getById(session.id))!;
|
|
88
|
+
await services.profiles.onRunStarted(updated.player_id, updated.player_name);
|
|
115
89
|
await services.profiles.onStep(updated.player_id, updated.player_name, 'start', []);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
90
|
+
return sendOrEditScene(services, message, updated);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 服务端校验:选项必须当前可见(requires 门槛不能只靠视图层过滤,payload 可绕过)
|
|
94
|
+
if (!visibleChoices(scene, state).some((c) => c.id === choiceId)) {
|
|
95
|
+
return '该选项不可用。';
|
|
119
96
|
}
|
|
120
97
|
|
|
121
98
|
const result = resolveChoice(state, choiceId);
|
|
@@ -163,24 +140,19 @@ export async function handleChoice(
|
|
|
163
140
|
|
|
164
141
|
const updated = (await services.sessions.getById(session.id))!;
|
|
165
142
|
const extra = formatNewAchievements(newAchievements);
|
|
166
|
-
|
|
167
|
-
// text-only 模式(plugin===null)下 sendOrEditScene 的唯一输出就是返回文本
|
|
168
|
-
return plugin ? null : text;
|
|
143
|
+
return sendOrEditScene(services, message, updated, extra);
|
|
169
144
|
}
|
|
170
145
|
|
|
171
146
|
export async function continueAdventure(
|
|
172
|
-
plugin: Plugin | null,
|
|
173
147
|
services: GameServices,
|
|
174
|
-
message:
|
|
175
|
-
): Promise<
|
|
148
|
+
message: GameMessageLike,
|
|
149
|
+
): Promise<GameReply> {
|
|
176
150
|
const session = await services.sessions.getActiveForUser(
|
|
177
151
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
178
152
|
message.$sender.id,
|
|
179
153
|
);
|
|
180
154
|
if (!session) return '你没有进行中的冒险,发送 adv start 开始。';
|
|
181
|
-
|
|
182
|
-
if (!plugin) return text;
|
|
183
|
-
return '已刷新当前场景。';
|
|
155
|
+
return sendOrEditScene(services, message, session);
|
|
184
156
|
}
|
|
185
157
|
|
|
186
158
|
export function sessionSummary(session: AdvSessionRow): string {
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
|
@@ -19,7 +19,6 @@ declare module '@zhin.js/core' {
|
|
|
19
19
|
flags: string;
|
|
20
20
|
ending_id: string;
|
|
21
21
|
status: AdvSessionStatus;
|
|
22
|
-
board_message_id: string;
|
|
23
22
|
step_count: number;
|
|
24
23
|
updated_at: number;
|
|
25
24
|
created_at: number;
|
|
@@ -64,7 +63,6 @@ export function defineHostTables(
|
|
|
64
63
|
flags: { type: 'text', default: '{}' },
|
|
65
64
|
ending_id: { type: 'text', default: '' },
|
|
66
65
|
status: { type: 'text', default: 'active' },
|
|
67
|
-
board_message_id: { type: 'text', default: '' },
|
|
68
66
|
step_count: { type: 'integer', default: 0 },
|
|
69
67
|
updated_at: { type: 'integer', default: 0 },
|
|
70
68
|
created_at: { type: 'integer', default: 0 },
|
package/src/session-service.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type { Database,
|
|
2
|
-
import {
|
|
1
|
+
import type { Database, Models, RelatedModel } from '@zhin.js/core';
|
|
2
|
+
import {
|
|
3
|
+
BaseSessionService,
|
|
4
|
+
channelKey,
|
|
5
|
+
generateSessionId,
|
|
6
|
+
type GameMessageLike,
|
|
7
|
+
type GameSessionDatabase,
|
|
8
|
+
} from '@zhin.js/game-kit';
|
|
3
9
|
import type { AdvModelName, AdvSessionRow } from './models.js';
|
|
4
10
|
import { createProfileService, ProfileService } from './profile-service.js';
|
|
5
11
|
|
|
@@ -17,37 +23,16 @@ export interface GameServices {
|
|
|
17
23
|
profiles: ProfileService;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export class SessionService {
|
|
21
|
-
constructor(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
status: 'active',
|
|
26
|
+
export class SessionService extends BaseSessionService<AdvSessionRow> {
|
|
27
|
+
constructor(db: AdvDatabase) {
|
|
28
|
+
super(db as unknown as GameSessionDatabase<AdvSessionRow>, {
|
|
29
|
+
gameId: 'adv',
|
|
30
|
+
table: 'adv_sessions',
|
|
31
|
+
userFields: ['player_id'],
|
|
27
32
|
});
|
|
28
|
-
return rows[0] ?? null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async getActiveForUser(channel: string, userId: string): Promise<AdvSessionRow | null> {
|
|
32
|
-
const row = await this.getActiveByChannel(channel);
|
|
33
|
-
if (!row || row.player_id !== userId) return null;
|
|
34
|
-
return row;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
async
|
|
38
|
-
return getModel(this.db, 'adv_sessions').findOne({ id });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async getActiveByBoardMessageId(messageId: string): Promise<AdvSessionRow | null> {
|
|
42
|
-
if (!messageId) return null;
|
|
43
|
-
const rows = await getModel(this.db, 'adv_sessions').findAll({});
|
|
44
|
-
for (const row of rows) {
|
|
45
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId)) return row;
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async createSession(message: Message<any>): Promise<AdvSessionRow> {
|
|
35
|
+
async createSession(message: GameMessageLike): Promise<AdvSessionRow> {
|
|
51
36
|
const now = Date.now();
|
|
52
37
|
const id = generateSessionId();
|
|
53
38
|
const row: AdvSessionRow = {
|
|
@@ -65,34 +50,11 @@ export class SessionService {
|
|
|
65
50
|
flags: '{}',
|
|
66
51
|
ending_id: '',
|
|
67
52
|
status: 'active',
|
|
68
|
-
board_message_id: '',
|
|
69
53
|
step_count: 0,
|
|
70
54
|
updated_at: now,
|
|
71
55
|
created_at: now,
|
|
72
56
|
};
|
|
73
|
-
|
|
74
|
-
return row;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async updateSession(id: string, patch: Partial<AdvSessionRow>): Promise<void> {
|
|
78
|
-
await getModel(this.db, 'adv_sessions').updateWhere(
|
|
79
|
-
{ id },
|
|
80
|
-
{ ...patch, updated_at: Date.now() },
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async abortStale(idleMs: number): Promise<number> {
|
|
85
|
-
const cutoff = Date.now() - idleMs;
|
|
86
|
-
const sessions = getModel(this.db, 'adv_sessions');
|
|
87
|
-
const rows = await sessions.findAll({ status: 'active' });
|
|
88
|
-
let n = 0;
|
|
89
|
-
for (const row of rows) {
|
|
90
|
-
if (row.updated_at < cutoff) {
|
|
91
|
-
await sessions.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
92
|
-
n++;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return n;
|
|
57
|
+
return this.createRow(row);
|
|
96
58
|
}
|
|
97
59
|
}
|
|
98
60
|
|
|
@@ -102,4 +64,3 @@ export function createServices(db: AdvDatabase): GameServices {
|
|
|
102
64
|
profiles: createProfileService(db),
|
|
103
65
|
};
|
|
104
66
|
}
|
|
105
|
-
|
package/lib/memory-db.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { type AdvDatabase, type GameServices } from './session-service.js';
|
|
3
|
-
/** In-memory adv tables for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
4
|
-
export declare function createInMemoryAdvDb(): AdvDatabase;
|
|
5
|
-
export declare function mountAdvMemoryServices(): GameServices;
|
|
6
|
-
export declare function mountAdvHostServices(host: DatabaseHost): GameServices;
|
package/lib/memory-db.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { createHostGameDb, createInMemoryGameDb } from '@zhin.js/game-kit';
|
|
2
|
-
import { defineHostTables } from './models.js';
|
|
3
|
-
import { createServices } from './session-service.js';
|
|
4
|
-
const ADV_TABLES = ['adv_sessions', 'adv_profiles'];
|
|
5
|
-
/** In-memory adv tables for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
6
|
-
export function createInMemoryAdvDb() {
|
|
7
|
-
return createInMemoryGameDb(ADV_TABLES);
|
|
8
|
-
}
|
|
9
|
-
export function mountAdvMemoryServices() {
|
|
10
|
-
const services = createServices(createInMemoryAdvDb());
|
|
11
|
-
return services;
|
|
12
|
-
}
|
|
13
|
-
export function mountAdvHostServices(host) {
|
|
14
|
-
defineHostTables(host);
|
|
15
|
-
const services = createServices(createHostGameDb(host, ADV_TABLES));
|
|
16
|
-
return services;
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=memory-db.js.map
|
package/lib/memory-db.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-db.js","sourceRoot":"","sources":["../src/memory-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAuC,MAAM,sBAAsB,CAAC;AAE3F,MAAM,UAAU,GAAG,CAAC,cAAc,EAAE,cAAc,CAAU,CAAC;AAE7D,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB;IACjC,OAAO,oBAAoB,CAAC,UAAU,CAA2B,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,MAAM,QAAQ,GAAG,cAAc,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAkB;IACrD,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAA2B,CAAC,CAAC;IAC9F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/plugin.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { definePlugin, databaseHostToken, scheduleHostToken } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import {
|
|
3
|
-
registerRuntimeGame,
|
|
4
|
-
initGameRecordHost,
|
|
5
|
-
DEFAULT_GAME_STALE_CRON,
|
|
6
|
-
DEFAULT_GAME_STALE_IDLE_MS,
|
|
7
|
-
} from '@zhin.js/game-kit';
|
|
8
|
-
import { mountAdvHostServices, mountAdvMemoryServices } from './src/memory-db.js';
|
|
9
|
-
import { gameServicesToken } from './src/runtime-store.js';
|
|
10
|
-
import type { GameServices } from './src/session-service.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Plugin Runtime:
|
|
14
|
-
* - Commands under `commands/` are authoritative (help + start/continue/…).
|
|
15
|
-
* - DB: prefer databaseHostToken; else in-memory GameServices.
|
|
16
|
-
* - Choice middleware under `middlewares/` handles adv choice payloads (Sandbox action→text).
|
|
17
|
-
*/
|
|
18
|
-
export default definePlugin({
|
|
19
|
-
name: 'text-adventure',
|
|
20
|
-
metadata: {
|
|
21
|
-
displayName: 'Text Adventure',
|
|
22
|
-
},
|
|
23
|
-
setup(context) {
|
|
24
|
-
let services: GameServices;
|
|
25
|
-
if (context.resources.has(databaseHostToken)) {
|
|
26
|
-
const host = context.resources.use(databaseHostToken);
|
|
27
|
-
services = mountAdvHostServices(host);
|
|
28
|
-
context.lifecycle.add(initGameRecordHost(host));
|
|
29
|
-
} else {
|
|
30
|
-
services = mountAdvMemoryServices();
|
|
31
|
-
}
|
|
32
|
-
context.resources.provide(gameServicesToken, services);
|
|
33
|
-
const disposeHub = registerRuntimeGame({
|
|
34
|
-
id: 'adv',
|
|
35
|
-
title: '秘境探险',
|
|
36
|
-
icon: '🗺️',
|
|
37
|
-
description: '文字冒险,31 区域 · 15 结局 · 成就收集',
|
|
38
|
-
commandPrefix: '/冒险',
|
|
39
|
-
quickStart: '开始',
|
|
40
|
-
aliases: ['adv', '秘境'],
|
|
41
|
-
menus: [
|
|
42
|
-
{ id: 'start', label: '🚪 开始冒险' },
|
|
43
|
-
{ id: 'continue', label: '🔄 继续冒险' },
|
|
44
|
-
{ id: 'map', label: '🗺️ 探索进度' },
|
|
45
|
-
{ id: 'achievements', label: '🏅 成就' },
|
|
46
|
-
{ id: 'help', label: '📖 玩法说明' },
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
context.lifecycle.add(disposeHub);
|
|
50
|
-
|
|
51
|
-
if (context.resources.has(scheduleHostToken)) {
|
|
52
|
-
const schedule = context.resources.use(scheduleHostToken);
|
|
53
|
-
const disposeCron = schedule.register({
|
|
54
|
-
id: 'adv/abort-stale',
|
|
55
|
-
cron: DEFAULT_GAME_STALE_CRON,
|
|
56
|
-
description: 'Abort stale text-adventure sessions',
|
|
57
|
-
async execute() {
|
|
58
|
-
if (!services.sessions.abortStale) return;
|
|
59
|
-
await services.sessions.abortStale(DEFAULT_GAME_STALE_IDLE_MS);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
context.lifecycle.add(disposeCron);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
});
|
package/src/memory-db.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { createHostGameDb, createInMemoryGameDb } from '@zhin.js/game-kit';
|
|
2
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
3
|
-
import { defineHostTables } from './models.js';
|
|
4
|
-
import { createServices, type AdvDatabase, type GameServices } from './session-service.js';
|
|
5
|
-
|
|
6
|
-
const ADV_TABLES = ['adv_sessions', 'adv_profiles'] as const;
|
|
7
|
-
|
|
8
|
-
/** In-memory adv tables for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
9
|
-
export function createInMemoryAdvDb(): AdvDatabase {
|
|
10
|
-
return createInMemoryGameDb(ADV_TABLES) as unknown as AdvDatabase;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function mountAdvMemoryServices(): GameServices {
|
|
14
|
-
const services = createServices(createInMemoryAdvDb());
|
|
15
|
-
return services;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function mountAdvHostServices(host: DatabaseHost): GameServices {
|
|
19
|
-
defineHostTables(host);
|
|
20
|
-
const services = createServices(createHostGameDb(host, ADV_TABLES) as unknown as AdvDatabase);
|
|
21
|
-
return services;
|
|
22
|
-
}
|