@zhin.js/plugin-dice-duel 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/dice/[action:string=].js +16 -0
- package/commands/dice/[action:string=].ts +2 -2
- package/lib/dice-command.d.ts +2 -4
- package/lib/dice-command.js +6 -11
- package/lib/dice-command.js.map +1 -1
- package/lib/game-flow.d.ts +4 -10
- package/lib/game-flow.js +18 -40
- 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 +22 -52
- package/lib/session-service.js.map +1 -1
- package/lib/view.js +7 -2
- package/lib/view.js.map +1 -1
- package/middlewares/dice-alias.js +18 -0
- package/middlewares/dice-alias.ts +6 -3
- package/middlewares/dice-choice.js +56 -0
- package/middlewares/dice-choice.ts +6 -3
- package/package.json +8 -8
- package/plugin.js +36 -0
- package/src/dice-command.ts +11 -19
- package/src/game-flow.ts +24 -49
- package/src/index.ts +0 -1
- package/src/models.ts +0 -2
- package/src/session-service.ts +31 -56
- package/src/view.ts +6 -2
- 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 -63
- 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, normalizeDiceAction } from '@zhin.js/game-kit';
|
|
4
|
+
import { DICE_HELP, runDiceCommand } from "../../lib/dice-command.js";
|
|
5
|
+
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: 'Dice Duel',
|
|
8
|
+
async execute({ params, input, use, owner }) {
|
|
9
|
+
const action = normalizeDiceAction(String(params.action ?? ''));
|
|
10
|
+
if (!action || action === 'help')
|
|
11
|
+
return DICE_HELP;
|
|
12
|
+
const services = resolveGameServices({ use, owner });
|
|
13
|
+
const message = messageFromCommandInput(input);
|
|
14
|
+
return runDiceCommand(services, message, action);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from '@zhin.js/command';
|
|
2
2
|
import { messageFromCommandInput, normalizeDiceAction } from '@zhin.js/game-kit';
|
|
3
|
-
import { DICE_HELP,
|
|
3
|
+
import { DICE_HELP, runDiceCommand } from '../../src/dice-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 DICE_HELP;
|
|
11
11
|
const services = resolveGameServices({ use, owner });
|
|
12
12
|
const message = messageFromCommandInput(input);
|
|
13
|
-
return
|
|
13
|
+
return runDiceCommand(services, message, action);
|
|
14
14
|
},
|
|
15
15
|
});
|
package/lib/dice-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 { SessionService } from './session-service.js';
|
|
3
3
|
export declare const DICE_HELP: string;
|
|
4
|
-
export declare function runDiceCommand(
|
|
5
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
6
|
-
export declare function runDiceCommandText(services: SessionService, message: Message<any>, action: string): Promise<string>;
|
|
4
|
+
export declare function runDiceCommand(services: SessionService, message: GameMessageLike, action: string): Promise<GameReply>;
|
package/lib/dice-command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { channelKey } from '@zhin.js/game-kit';
|
|
2
|
-
import { continueGame, startGame } from './game-flow.js';
|
|
1
|
+
import { channelKey, } from '@zhin.js/game-kit';
|
|
2
|
+
import { continueGame, handleChoice, startGame } from './game-flow.js';
|
|
3
3
|
export const DICE_HELP = [
|
|
4
4
|
'🎲 骰子对决(三局两胜)',
|
|
5
5
|
'骰子 / dice — 帮助',
|
|
@@ -7,7 +7,7 @@ export const DICE_HELP = [
|
|
|
7
7
|
'骰子 继续 — 刷新界面',
|
|
8
8
|
'骰子 放弃 — 结束对局',
|
|
9
9
|
].join('\n');
|
|
10
|
-
export async function runDiceCommand(
|
|
10
|
+
export async function runDiceCommand(services, message, action) {
|
|
11
11
|
const ch = channelKey(message);
|
|
12
12
|
const userId = message.$sender.id;
|
|
13
13
|
if (!action || action === 'help') {
|
|
@@ -22,20 +22,15 @@ export async function runDiceCommand(plugin, services, message, action) {
|
|
|
22
22
|
return lines.join('\n');
|
|
23
23
|
}
|
|
24
24
|
if (action === 'start')
|
|
25
|
-
return startGame(
|
|
25
|
+
return (await startGame(services, message)) ?? '';
|
|
26
26
|
if (action === 'continue')
|
|
27
|
-
return continueGame(
|
|
27
|
+
return continueGame(services, message);
|
|
28
28
|
if (action === 'quit') {
|
|
29
29
|
const row = await services.getActiveForUser(ch, userId);
|
|
30
30
|
if (!row)
|
|
31
31
|
return '你没有进行中的骰子对决。';
|
|
32
|
-
await services
|
|
33
|
-
return '已放弃骰子对局。';
|
|
32
|
+
return (await handleChoice(services, message, row.id, 'quit')) ?? '';
|
|
34
33
|
}
|
|
35
34
|
return `未知子命令:${action}\n\n${DICE_HELP}`;
|
|
36
35
|
}
|
|
37
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
38
|
-
export async function runDiceCommandText(services, message, action) {
|
|
39
|
-
return (await runDiceCommand(null, services, message, action)) ?? '';
|
|
40
|
-
}
|
|
41
36
|
//# sourceMappingURL=dice-command.js.map
|
package/lib/dice-command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dice-command.js","sourceRoot":"","sources":["../src/dice-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dice-command.js","sourceRoot":"","sources":["../src/dice-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAGX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGvE,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,cAAc;IACd,cAAc;CACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAwB,EACxB,OAAwB,EACxB,MAAc;IAEd,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IAElC,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC9B,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,WAAW,MAAM,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,CAAC,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,OAAO,cAAc,CAAC;QAChC,OAAO,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACvE,CAAC;IAED,OAAO,SAAS,MAAM,OAAO,SAAS,EAAE,CAAC;AAC3C,CAAC"}
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { DiceSessionRow } from './models.js';
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
3
2
|
import { DICE_PREFIX } from './engine.js';
|
|
4
3
|
import type { SessionService } from './session-service.js';
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
result: 0 | 1 | 2;
|
|
9
|
-
}): Promise<string | void>;
|
|
10
|
-
export declare function startGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string | undefined>;
|
|
11
|
-
export declare function continueGame(plugin: Plugin | null, services: SessionService, message: Message<any>): Promise<string>;
|
|
12
|
-
export declare function handleChoice(plugin: Plugin | null, services: SessionService, message: Message<any>, sessionId: string, choiceId: string): Promise<string | null>;
|
|
4
|
+
export declare function startGame(services: SessionService, message: GameMessageLike): Promise<GameReply | undefined>;
|
|
5
|
+
export declare function continueGame(services: SessionService, message: GameMessageLike): Promise<GameReply>;
|
|
6
|
+
export declare function handleChoice(services: SessionService, message: GameMessageLike, sessionId: string, choiceId: string): Promise<GameReply | null>;
|
|
13
7
|
export { DICE_PREFIX };
|
package/lib/game-flow.js
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
2
1
|
import { compareRolls, DICE_PREFIX, rollD6, WIN_TARGET } from './engine.js';
|
|
3
2
|
import { buildDiceView } from './view.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
messageId: session.board_message_id,
|
|
12
|
-
context: String(message.$adapter),
|
|
13
|
-
endpoint: message.$endpoint,
|
|
14
|
-
id: message.$channel.id,
|
|
15
|
-
type: message.$channel.type,
|
|
16
|
-
content,
|
|
17
|
-
});
|
|
18
|
-
if (msgId !== session.board_message_id) {
|
|
19
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
20
|
-
}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
const msgId = await message.$reply?.(content);
|
|
24
|
-
if (msgId)
|
|
25
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
3
|
+
/**
|
|
4
|
+
* Plugin Runtime: render the board as text. Interactive in-place board editing
|
|
5
|
+
* (the old Adapter.editMessage path) is not part of the runtime flow; commands
|
|
6
|
+
* and the choice middleware return fresh text each turn.
|
|
7
|
+
*/
|
|
8
|
+
function renderView(session, message, lastRound) {
|
|
9
|
+
return buildDiceView(session, lastRound, message.$channel.type);
|
|
26
10
|
}
|
|
27
|
-
export async function startGame(
|
|
11
|
+
export async function startGame(services, message) {
|
|
28
12
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
29
13
|
const active = await services.getActiveByChannel(ch);
|
|
30
14
|
if (active) {
|
|
@@ -34,19 +18,15 @@ export async function startGame(plugin, services, message) {
|
|
|
34
18
|
return `本频道 ${active.player_name} 正在掷骰对决中。`;
|
|
35
19
|
}
|
|
36
20
|
const session = await services.createSession(message);
|
|
37
|
-
|
|
38
|
-
return typeof text === 'string' ? text : undefined;
|
|
21
|
+
return renderView(session, message);
|
|
39
22
|
}
|
|
40
|
-
export async function continueGame(
|
|
23
|
+
export async function continueGame(services, message) {
|
|
41
24
|
const session = await services.getActiveForUser(`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`, message.$sender.id);
|
|
42
25
|
if (!session)
|
|
43
26
|
return '你没有进行中的骰子对决,发送「骰子 开始」。';
|
|
44
|
-
|
|
45
|
-
if (typeof text === 'string')
|
|
46
|
-
return text;
|
|
47
|
-
return '已刷新骰子界面。';
|
|
27
|
+
return renderView(session, message);
|
|
48
28
|
}
|
|
49
|
-
export async function handleChoice(
|
|
29
|
+
export async function handleChoice(services, message, sessionId, choiceId) {
|
|
50
30
|
const session = await services.getById(sessionId);
|
|
51
31
|
if (!session)
|
|
52
32
|
return '对局不存在。';
|
|
@@ -57,8 +37,11 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
57
37
|
}
|
|
58
38
|
if (choiceId === 'restart') {
|
|
59
39
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
60
|
-
|
|
61
|
-
|
|
40
|
+
return (await startGame(services, message)) ?? null;
|
|
41
|
+
}
|
|
42
|
+
if (choiceId === 'quit') {
|
|
43
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
44
|
+
return renderView((await services.getById(session.id)), message);
|
|
62
45
|
}
|
|
63
46
|
if (choiceId !== 'roll')
|
|
64
47
|
return '无效操作。';
|
|
@@ -85,12 +68,7 @@ export async function handleChoice(plugin, services, message, sessionId, choiceI
|
|
|
85
68
|
status,
|
|
86
69
|
});
|
|
87
70
|
const updated = (await services.getById(session.id));
|
|
88
|
-
|
|
89
|
-
void recordGameOutcome(message, 'dice', 'won', playerWins * 10);
|
|
90
|
-
else if (status === 'lost')
|
|
91
|
-
void recordGameOutcome(message, 'dice', 'lost');
|
|
92
|
-
const text = await sendOrEditView(plugin, services, message, updated, { player, bot, result });
|
|
93
|
-
return typeof text === 'string' ? text : null;
|
|
71
|
+
return renderView(updated, message, { player, bot, result });
|
|
94
72
|
}
|
|
95
73
|
export { DICE_PREFIX };
|
|
96
74
|
//# sourceMappingURL=game-flow.js.map
|
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 { DICE_HELP } from './dice-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryDiceDb, mountDiceMemoryServices } 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 { DICE_HELP } from './dice-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryDiceDb, mountDiceMemoryServices } 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,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,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,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,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
|
last_player_roll: { type: 'integer', default: 0 },
|
|
15
15
|
last_bot_roll: { type: 'integer', default: 0 },
|
|
16
16
|
status: { type: 'text', default: 'active' },
|
|
17
|
-
board_message_id: { type: 'text', default: '' },
|
|
18
17
|
updated_at: { type: 'integer', default: 0 },
|
|
19
18
|
created_at: { type: 'integer', default: 0 },
|
|
20
19
|
});
|
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":"AA8BA,MAAM,UAAU,gBAAgB,CAC9B,EAA2E;IAE3E,EAAE,CAAC,MAAM,CAAC,eAAe,EAAE;QACzB,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,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC5C,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACzC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACtC,gBAAgB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QACjD,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;QAC9C,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;KAC5C,CAAC,CAAC;AACL,CAAC"}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
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 { DiceSessionRow } from './models.js';
|
|
3
4
|
export type DiceDatabase = Database<unknown, Models, string>;
|
|
4
|
-
export declare class SessionService {
|
|
5
|
-
private readonly db;
|
|
5
|
+
export declare class SessionService extends BaseSessionService<DiceSessionRow> {
|
|
6
6
|
constructor(db: DiceDatabase);
|
|
7
|
-
|
|
8
|
-
getActiveForUser(channel: string, userId: string): Promise<DiceSessionRow | null>;
|
|
9
|
-
getById(id: string): Promise<DiceSessionRow | null>;
|
|
10
|
-
getActiveByBoardMessageId(messageId: string): Promise<DiceSessionRow | null>;
|
|
11
|
-
createSession(message: Message<any>): Promise<DiceSessionRow>;
|
|
12
|
-
updateSession(id: string, patch: Partial<DiceSessionRow>): Promise<void>;
|
|
13
|
-
abortStale(idleMs: number): Promise<number>;
|
|
7
|
+
createSession(message: GameMessageLike): Promise<DiceSessionRow>;
|
|
14
8
|
}
|
|
15
9
|
export declare function createServices(db: DiceDatabase): SessionService;
|
package/lib/session-service.js
CHANGED
|
@@ -1,37 +1,25 @@
|
|
|
1
|
-
import { channelKey, generateSessionId,
|
|
2
|
-
|
|
3
|
-
const model = db.models.get('dice_sessions');
|
|
4
|
-
if (!model)
|
|
5
|
-
throw new Error('dice_sessions not registered');
|
|
6
|
-
return model;
|
|
7
|
-
}
|
|
8
|
-
export class SessionService {
|
|
9
|
-
db;
|
|
1
|
+
import { BaseSessionService, channelKey, generateSessionId, } from '@zhin.js/game-kit';
|
|
2
|
+
export class SessionService extends BaseSessionService {
|
|
10
3
|
constructor(db) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (const row of rows) {
|
|
31
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId))
|
|
32
|
-
return row;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
4
|
+
super(db, {
|
|
5
|
+
gameId: 'dice',
|
|
6
|
+
table: 'dice_sessions',
|
|
7
|
+
userFields: ['player_id'],
|
|
8
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
9
|
+
? [{
|
|
10
|
+
userId: session.player_id,
|
|
11
|
+
userName: session.player_name,
|
|
12
|
+
result: 'won',
|
|
13
|
+
score: session.player_wins * 10,
|
|
14
|
+
}]
|
|
15
|
+
: session.status === 'lost'
|
|
16
|
+
? [{
|
|
17
|
+
userId: session.player_id,
|
|
18
|
+
userName: session.player_name,
|
|
19
|
+
result: 'lost',
|
|
20
|
+
}]
|
|
21
|
+
: [],
|
|
22
|
+
});
|
|
35
23
|
}
|
|
36
24
|
async createSession(message) {
|
|
37
25
|
const now = Date.now();
|
|
@@ -50,28 +38,10 @@ export class SessionService {
|
|
|
50
38
|
last_player_roll: 0,
|
|
51
39
|
last_bot_roll: 0,
|
|
52
40
|
status: 'active',
|
|
53
|
-
board_message_id: '',
|
|
54
41
|
updated_at: now,
|
|
55
42
|
created_at: now,
|
|
56
43
|
};
|
|
57
|
-
|
|
58
|
-
return row;
|
|
59
|
-
}
|
|
60
|
-
async updateSession(id, patch) {
|
|
61
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
62
|
-
}
|
|
63
|
-
async abortStale(idleMs) {
|
|
64
|
-
const cutoff = Date.now() - idleMs;
|
|
65
|
-
const model = getModel(this.db);
|
|
66
|
-
const rows = await model.findAll({ status: 'active' });
|
|
67
|
-
let n = 0;
|
|
68
|
-
for (const row of rows) {
|
|
69
|
-
if (row.updated_at < cutoff) {
|
|
70
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
71
|
-
n++;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return n;
|
|
44
|
+
return this.createRow(row);
|
|
75
45
|
}
|
|
76
46
|
}
|
|
77
47
|
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;AAK3B,MAAM,OAAO,cAAe,SAAQ,kBAAkC;IACpE,YAAY,EAAgB;QAC1B,KAAK,CAAC,EAAoD,EAAE;YAC1D,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,eAAe;YACtB,UAAU,EAAE,CAAC,WAAW,CAAC;YACzB,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;gBACpD,CAAC,CAAC,CAAC;wBACC,MAAM,EAAE,OAAO,CAAC,SAAS;wBACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;wBAC7B,MAAM,EAAE,KAAK;wBACb,KAAK,EAAE,OAAO,CAAC,WAAW,GAAG,EAAE;qBAChC,CAAC;gBACJ,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM;oBACzB,CAAC,CAAC,CAAC;4BACC,MAAM,EAAE,OAAO,CAAC,SAAS;4BACzB,QAAQ,EAAE,OAAO,CAAC,WAAW;4BAC7B,MAAM,EAAE,MAAM;yBACf,CAAC;oBACJ,CAAC,CAAC,EAAE;SACT,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,GAAG,GAAmB;YAC1B,EAAE,EAAE,iBAAiB,EAAE;YACvB,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,WAAW,EAAE,CAAC;YACd,QAAQ,EAAE,CAAC;YACX,KAAK,EAAE,CAAC;YACR,gBAAgB,EAAE,CAAC;YACnB,aAAa,EAAE,CAAC;YAChB,MAAM,EAAE,QAAQ;YAChB,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,EAAgB;IAC7C,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
|
package/lib/view.js
CHANGED
|
@@ -22,20 +22,25 @@ export function buildDiceView(session, lastRound, channelType) {
|
|
|
22
22
|
lines.push('🏆 **你赢得比赛!**');
|
|
23
23
|
else if (session.status === 'lost')
|
|
24
24
|
lines.push('💀 **机器人赢得比赛。**');
|
|
25
|
+
else
|
|
26
|
+
lines.push('🏳️ **本局已放弃。**');
|
|
25
27
|
}
|
|
26
28
|
else {
|
|
27
29
|
lines.push('', lastRound ? '点击继续掷骰:' : '点击掷骰开始!');
|
|
28
30
|
}
|
|
29
31
|
const choices = terminal
|
|
30
32
|
? [{ id: 'restart', label: '🔄 再来一局', style: 'primary', keepEnabledWhenTerminal: true }]
|
|
31
|
-
: [
|
|
33
|
+
: [
|
|
34
|
+
{ id: 'roll', label: '🎲 掷骰', style: 'primary' },
|
|
35
|
+
{ id: 'quit', label: '🏳️ 放弃', style: 'danger' },
|
|
36
|
+
];
|
|
32
37
|
return buildChoiceKeyboard({
|
|
33
38
|
gamePrefix: DICE_PREFIX,
|
|
34
39
|
sessionId: session.id,
|
|
35
40
|
narrative: lines.join('\n'),
|
|
36
41
|
choices,
|
|
37
42
|
terminal,
|
|
38
|
-
fallbackHint: '回复 1 掷骰',
|
|
43
|
+
fallbackHint: '回复 1 掷骰 · 2 放弃',
|
|
39
44
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
40
45
|
channelType,
|
|
41
46
|
});
|
package/lib/view.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAc,MAAM,aAAa,CAAC;AAEjE,MAAM,UAAU,aAAa,CAC3B,OAAuB,EACvB,SAA8D,EAC9D,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,aAAa;QACb,EAAE;QACF,YAAY,OAAO,CAAC,WAAW,UAAU,OAAO,CAAC,QAAQ,cAAc;KACxE,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,cAAc,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;QAC3H,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBACtB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,YAAY,CACnB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;aACrD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAc,MAAM,aAAa,CAAC;AAEjE,MAAM,UAAU,aAAa,CAC3B,OAAuB,EACvB,SAA8D,EAC9D,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,aAAa;QACb,EAAE;QACF,YAAY,OAAO,CAAC,WAAW,UAAU,OAAO,CAAC,QAAQ,cAAc;KACxE,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,cAAc,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;QAC3H,KAAK,CAAC,IAAI,CACR,SAAS,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBACtB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,YAAY,CACnB,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;aACrD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;;YAC7D,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ;QACtB,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAkB,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC;QACjG,CAAC,CAAC;YACE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAkB,EAAE;YACzD,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAiB,EAAE;SAC1D,CAAC;IAEN,OAAO,mBAAmB,CAAC;QACzB,UAAU,EAAE,WAAW;QACvB,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,OAAO;QACP,QAAQ;QACR,YAAY,EAAE,gBAAgB;QAC9B,kBAAkB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;QACtD,WAAW;KACZ,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineGameCommandAliasMiddleware, isDiceAction, messageFromCommandInput, normalizeDiceAction, } from '@zhin.js/game-kit';
|
|
3
|
+
import { DICE_HELP, runDiceCommand } from "../lib/dice-command.js";
|
|
4
|
+
import { resolveGameServices } from "../lib/runtime-store.js";
|
|
5
|
+
export default defineGameCommandAliasMiddleware({
|
|
6
|
+
aliases: ['骰子', 'dice'],
|
|
7
|
+
async run(action, input, context) {
|
|
8
|
+
const normalized = normalizeDiceAction(String(action ?? ''));
|
|
9
|
+
if (normalized === 'help')
|
|
10
|
+
return DICE_HELP;
|
|
11
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
12
|
+
if (!isDiceAction(normalized))
|
|
13
|
+
return null;
|
|
14
|
+
const services = resolveGameServices(context);
|
|
15
|
+
const message = messageFromCommandInput(input);
|
|
16
|
+
return runDiceCommand(services, message, normalized);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineGameCommandAliasMiddleware,
|
|
3
|
+
isDiceAction,
|
|
3
4
|
messageFromCommandInput,
|
|
4
5
|
normalizeDiceAction,
|
|
5
6
|
} from '@zhin.js/game-kit';
|
|
6
|
-
import { DICE_HELP,
|
|
7
|
+
import { DICE_HELP, runDiceCommand } from '../src/dice-command.js';
|
|
7
8
|
import { resolveGameServices } from '../src/runtime-store.js';
|
|
8
9
|
|
|
9
10
|
export default defineGameCommandAliasMiddleware({
|
|
10
11
|
aliases: ['骰子', 'dice'],
|
|
11
12
|
async run(action, input, context) {
|
|
12
13
|
const normalized = normalizeDiceAction(String(action ?? ''));
|
|
13
|
-
if (
|
|
14
|
+
if (normalized === 'help') return DICE_HELP;
|
|
15
|
+
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
16
|
+
if (!isDiceAction(normalized)) return null;
|
|
14
17
|
const services = resolveGameServices(context);
|
|
15
18
|
const message = messageFromCommandInput(input);
|
|
16
|
-
return
|
|
19
|
+
return runDiceCommand(services, message, normalized);
|
|
17
20
|
},
|
|
18
21
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { DICE_PREFIX, handleChoice } from "../lib/game-flow.js";
|
|
6
|
+
/**
|
|
7
|
+
* 文本入口:按钮 payload(`dice:session:roll|restart`)与数字 fallback
|
|
8
|
+
* (『回复 1 掷骰』,对应 view 的 fallbackHint)。
|
|
9
|
+
*/
|
|
10
|
+
export default defineMiddleware({
|
|
11
|
+
target: 'inbound',
|
|
12
|
+
async handle(context, next) {
|
|
13
|
+
const raw = context.input.content?.trim() ?? '';
|
|
14
|
+
if (!raw) {
|
|
15
|
+
await next();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const services = resolveGameServices(context);
|
|
19
|
+
const message = messageFromCommandInput(context.input);
|
|
20
|
+
const ch = channelKey(message);
|
|
21
|
+
// 直接 payload(QQ 指令预填 / Sandbox action→text)
|
|
22
|
+
const payloadFromText = resolveGameTextPayload(raw);
|
|
23
|
+
if (payloadFromText?.startsWith(`${DICE_PREFIX}:`)) {
|
|
24
|
+
const parsed = parseChoicePayload(payloadFromText, DICE_PREFIX);
|
|
25
|
+
if (parsed) {
|
|
26
|
+
const session = await services.getById(parsed.sessionId);
|
|
27
|
+
if (session?.channel_key === ch) {
|
|
28
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
29
|
+
if (reply)
|
|
30
|
+
await context.input.$reply(reply);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await next();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const session = await services.getActiveForUser(ch, message.$sender.id);
|
|
38
|
+
if (!session || session.status !== 'active') {
|
|
39
|
+
await next();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const map = buildChoiceFallbackMap(DICE_PREFIX, session.id, [
|
|
43
|
+
{ id: 'roll', label: '掷骰' },
|
|
44
|
+
{ id: 'quit', label: '放弃' },
|
|
45
|
+
]);
|
|
46
|
+
const payload = resolveGameTextPayload(raw, map);
|
|
47
|
+
const parsed = payload ? parseChoicePayload(payload, DICE_PREFIX) : null;
|
|
48
|
+
if (!parsed || parsed.sessionId !== session.id) {
|
|
49
|
+
await next();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const reply = await handleChoice(services, message, session.id, 'roll');
|
|
53
|
+
if (reply)
|
|
54
|
+
await context.input.$reply(reply);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -33,7 +33,7 @@ export default defineMiddleware<Message>({
|
|
|
33
33
|
if (parsed) {
|
|
34
34
|
const session = await services.getById(parsed.sessionId);
|
|
35
35
|
if (session?.channel_key === ch) {
|
|
36
|
-
const reply = await handleChoice(
|
|
36
|
+
const reply = await handleChoice(services, message, parsed.sessionId, parsed.choiceId);
|
|
37
37
|
if (reply) await context.input.$reply(reply);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
@@ -48,7 +48,10 @@ export default defineMiddleware<Message>({
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const map = buildChoiceFallbackMap(DICE_PREFIX, session.id, [
|
|
51
|
+
const map = buildChoiceFallbackMap(DICE_PREFIX, session.id, [
|
|
52
|
+
{ id: 'roll', label: '掷骰' },
|
|
53
|
+
{ id: 'quit', label: '放弃' },
|
|
54
|
+
]);
|
|
52
55
|
const payload = resolveGameTextPayload(raw, map);
|
|
53
56
|
const parsed = payload ? parseChoicePayload(payload, DICE_PREFIX) : null;
|
|
54
57
|
if (!parsed || parsed.sessionId !== session.id) {
|
|
@@ -56,7 +59,7 @@ export default defineMiddleware<Message>({
|
|
|
56
59
|
return;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
const reply = await handleChoice(
|
|
62
|
+
const reply = await handleChoice(services, message, session.id, 'roll');
|
|
60
63
|
if (reply) await context.input.$reply(reply);
|
|
61
64
|
},
|
|
62
65
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-dice-duel",
|
|
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,36 @@
|
|
|
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/quit via in-memory DB).
|
|
9
|
+
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
10
|
+
* - Choice middleware under `middlewares/` handles dice roll payloads (Sandbox action→text).
|
|
11
|
+
*/
|
|
12
|
+
export default defineGamePlugin({
|
|
13
|
+
name: 'dice-duel',
|
|
14
|
+
metadata: {
|
|
15
|
+
displayName: 'Dice Duel',
|
|
16
|
+
},
|
|
17
|
+
game: {
|
|
18
|
+
id: 'dice',
|
|
19
|
+
title: '骰子对决',
|
|
20
|
+
icon: '🎲',
|
|
21
|
+
description: '掷骰比大小,三局两胜',
|
|
22
|
+
commandPrefix: '/骰子',
|
|
23
|
+
quickStart: '开始',
|
|
24
|
+
aliases: ['dice'],
|
|
25
|
+
menus: [
|
|
26
|
+
{ id: 'start', label: '🎮 开始对局' },
|
|
27
|
+
{ id: 'continue', label: '🔄 继续' },
|
|
28
|
+
{ id: 'help', label: '📖 玩法说明' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
tables: ['dice_sessions'],
|
|
32
|
+
servicesToken: gameServicesToken,
|
|
33
|
+
defineHostTables,
|
|
34
|
+
createServices: (database) => createServices(database),
|
|
35
|
+
session: (services) => services,
|
|
36
|
+
});
|
package/src/dice-command.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
channelKey,
|
|
3
|
+
type GameMessageLike,
|
|
4
|
+
type GameReply,
|
|
5
|
+
} from '@zhin.js/game-kit';
|
|
6
|
+
import { continueGame, handleChoice, startGame } from './game-flow.js';
|
|
4
7
|
import type { SessionService } from './session-service.js';
|
|
5
8
|
|
|
6
9
|
export const DICE_HELP = [
|
|
@@ -12,11 +15,10 @@ export const DICE_HELP = [
|
|
|
12
15
|
].join('\n');
|
|
13
16
|
|
|
14
17
|
export async function runDiceCommand(
|
|
15
|
-
plugin: Plugin | null,
|
|
16
18
|
services: SessionService,
|
|
17
|
-
message:
|
|
19
|
+
message: GameMessageLike,
|
|
18
20
|
action: string,
|
|
19
|
-
): Promise<
|
|
21
|
+
): Promise<GameReply> {
|
|
20
22
|
const ch = channelKey(message);
|
|
21
23
|
const userId = message.$sender.id;
|
|
22
24
|
|
|
@@ -31,24 +33,14 @@ export async function runDiceCommand(
|
|
|
31
33
|
return lines.join('\n');
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
if (action === 'start') return startGame(
|
|
35
|
-
if (action === 'continue') return continueGame(
|
|
36
|
+
if (action === 'start') return (await startGame(services, message)) ?? '';
|
|
37
|
+
if (action === 'continue') return continueGame(services, message);
|
|
36
38
|
|
|
37
39
|
if (action === 'quit') {
|
|
38
40
|
const row = await services.getActiveForUser(ch, userId);
|
|
39
41
|
if (!row) return '你没有进行中的骰子对决。';
|
|
40
|
-
await services
|
|
41
|
-
return '已放弃骰子对局。';
|
|
42
|
+
return (await handleChoice(services, message, row.id, 'quit')) ?? '';
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
return `未知子命令:${action}\n\n${DICE_HELP}`;
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
/** Plugin Runtime / smoke: text-only, no Adapter.editMessage. */
|
|
48
|
-
export async function runDiceCommandText(
|
|
49
|
-
services: SessionService,
|
|
50
|
-
message: Message<any>,
|
|
51
|
-
action: string,
|
|
52
|
-
): Promise<string> {
|
|
53
|
-
return (await runDiceCommand(null, services, message, action)) ?? '';
|
|
54
|
-
}
|
package/src/game-flow.ts
CHANGED
|
@@ -1,46 +1,26 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { plainTextFromSendContent, recordGameOutcome } from '@zhin.js/game-kit';
|
|
1
|
+
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
3
2
|
import type { DiceSessionRow } from './models.js';
|
|
4
3
|
import { compareRolls, DICE_PREFIX, rollD6, WIN_TARGET } from './engine.js';
|
|
5
4
|
import type { SessionService } from './session-service.js';
|
|
6
5
|
import { buildDiceView } from './view.js';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Plugin Runtime: render the board as text. Interactive in-place board editing
|
|
9
|
+
* (the old Adapter.editMessage path) is not part of the runtime flow; commands
|
|
10
|
+
* and the choice middleware return fresh text each turn.
|
|
11
|
+
*/
|
|
12
|
+
function renderView(
|
|
12
13
|
session: DiceSessionRow,
|
|
14
|
+
message: GameMessageLike,
|
|
13
15
|
lastRound?: { player: number; bot: number; result: 0 | 1 | 2 },
|
|
14
|
-
):
|
|
15
|
-
|
|
16
|
-
if (!plugin) return plainTextFromSendContent(content);
|
|
17
|
-
|
|
18
|
-
const adapter = plugin.root.inject(message.$adapter) as Adapter;
|
|
19
|
-
|
|
20
|
-
if (session.board_message_id) {
|
|
21
|
-
const msgId = await adapter.editMessage({
|
|
22
|
-
messageId: session.board_message_id,
|
|
23
|
-
context: String(message.$adapter),
|
|
24
|
-
endpoint: message.$endpoint,
|
|
25
|
-
id: message.$channel.id,
|
|
26
|
-
type: message.$channel.type,
|
|
27
|
-
content,
|
|
28
|
-
});
|
|
29
|
-
if (msgId !== session.board_message_id) {
|
|
30
|
-
await services.updateSession(session.id, { board_message_id: msgId });
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const msgId = await message.$reply?.(content);
|
|
36
|
-
if (msgId) await services.updateSession(session.id, { board_message_id: msgId });
|
|
16
|
+
): GameReply {
|
|
17
|
+
return buildDiceView(session, lastRound, message.$channel.type);
|
|
37
18
|
}
|
|
38
19
|
|
|
39
20
|
export async function startGame(
|
|
40
|
-
plugin: Plugin | null,
|
|
41
21
|
services: SessionService,
|
|
42
|
-
message:
|
|
43
|
-
): Promise<
|
|
22
|
+
message: GameMessageLike,
|
|
23
|
+
): Promise<GameReply | undefined> {
|
|
44
24
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
45
25
|
const active = await services.getActiveByChannel(ch);
|
|
46
26
|
if (active) {
|
|
@@ -50,32 +30,27 @@ export async function startGame(
|
|
|
50
30
|
return `本频道 ${active.player_name} 正在掷骰对决中。`;
|
|
51
31
|
}
|
|
52
32
|
const session = await services.createSession(message);
|
|
53
|
-
|
|
54
|
-
return typeof text === 'string' ? text : undefined;
|
|
33
|
+
return renderView(session, message);
|
|
55
34
|
}
|
|
56
35
|
|
|
57
36
|
export async function continueGame(
|
|
58
|
-
plugin: Plugin | null,
|
|
59
37
|
services: SessionService,
|
|
60
|
-
message:
|
|
61
|
-
): Promise<
|
|
38
|
+
message: GameMessageLike,
|
|
39
|
+
): Promise<GameReply> {
|
|
62
40
|
const session = await services.getActiveForUser(
|
|
63
41
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
64
42
|
message.$sender.id,
|
|
65
43
|
);
|
|
66
44
|
if (!session) return '你没有进行中的骰子对决,发送「骰子 开始」。';
|
|
67
|
-
|
|
68
|
-
if (typeof text === 'string') return text;
|
|
69
|
-
return '已刷新骰子界面。';
|
|
45
|
+
return renderView(session, message);
|
|
70
46
|
}
|
|
71
47
|
|
|
72
48
|
export async function handleChoice(
|
|
73
|
-
plugin: Plugin | null,
|
|
74
49
|
services: SessionService,
|
|
75
|
-
message:
|
|
50
|
+
message: GameMessageLike,
|
|
76
51
|
sessionId: string,
|
|
77
52
|
choiceId: string,
|
|
78
|
-
): Promise<
|
|
53
|
+
): Promise<GameReply | null> {
|
|
79
54
|
const session = await services.getById(sessionId);
|
|
80
55
|
if (!session) return '对局不存在。';
|
|
81
56
|
if (session.player_id !== message.$sender.id) return '这是别人的对局。';
|
|
@@ -86,8 +61,11 @@ export async function handleChoice(
|
|
|
86
61
|
|
|
87
62
|
if (choiceId === 'restart') {
|
|
88
63
|
await services.updateSession(session.id, { status: 'aborted' });
|
|
89
|
-
|
|
90
|
-
|
|
64
|
+
return (await startGame(services, message)) ?? null;
|
|
65
|
+
}
|
|
66
|
+
if (choiceId === 'quit') {
|
|
67
|
+
await services.updateSession(session.id, { status: 'aborted' });
|
|
68
|
+
return renderView((await services.getById(session.id))!, message);
|
|
91
69
|
}
|
|
92
70
|
|
|
93
71
|
if (choiceId !== 'roll') return '无效操作。';
|
|
@@ -115,10 +93,7 @@ export async function handleChoice(
|
|
|
115
93
|
});
|
|
116
94
|
|
|
117
95
|
const updated = (await services.getById(session.id))!;
|
|
118
|
-
|
|
119
|
-
else if (status === 'lost') void recordGameOutcome(message, 'dice', 'lost');
|
|
120
|
-
const text = await sendOrEditView(plugin, services, message, updated, { player, bot, result });
|
|
121
|
-
return typeof text === 'string' ? text : null;
|
|
96
|
+
return renderView(updated, message, { player, bot, result });
|
|
122
97
|
}
|
|
123
98
|
|
|
124
99
|
export { DICE_PREFIX };
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { DICE_HELP } from './dice-command.js';
|
|
2
2
|
export { gameServicesToken, resolveGameServices } from './runtime-store.js';
|
|
3
|
-
export { createInMemoryDiceDb, mountDiceMemoryServices } from './memory-db.js';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* 已由 plugin.ts 接线:commands/ 命令、middlewares/ 文本与选项中间件、
|
package/src/models.ts
CHANGED
|
@@ -19,7 +19,6 @@ declare module '@zhin.js/core' {
|
|
|
19
19
|
last_player_roll: number;
|
|
20
20
|
last_bot_roll: number;
|
|
21
21
|
status: DiceSessionStatus;
|
|
22
|
-
board_message_id: string;
|
|
23
22
|
updated_at: number;
|
|
24
23
|
created_at: number;
|
|
25
24
|
};
|
|
@@ -47,7 +46,6 @@ export function defineHostTables(
|
|
|
47
46
|
last_player_roll: { type: 'integer', default: 0 },
|
|
48
47
|
last_bot_roll: { type: 'integer', default: 0 },
|
|
49
48
|
status: { type: 'text', default: 'active' },
|
|
50
|
-
board_message_id: { type: 'text', default: '' },
|
|
51
49
|
updated_at: { type: 'integer', default: 0 },
|
|
52
50
|
created_at: { type: 'integer', default: 0 },
|
|
53
51
|
});
|
package/src/session-service.ts
CHANGED
|
@@ -1,43 +1,39 @@
|
|
|
1
|
-
import type { Database,
|
|
2
|
-
import {
|
|
1
|
+
import type { Database, Models } 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 { DiceSessionRow } from './models.js';
|
|
4
10
|
|
|
5
11
|
export type DiceDatabase = Database<unknown, Models, string>;
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return getModel(this.db).findOne({ id });
|
|
13
|
+
export class SessionService extends BaseSessionService<DiceSessionRow> {
|
|
14
|
+
constructor(db: DiceDatabase) {
|
|
15
|
+
super(db as unknown as GameSessionDatabase<DiceSessionRow>, {
|
|
16
|
+
gameId: 'dice',
|
|
17
|
+
table: 'dice_sessions',
|
|
18
|
+
userFields: ['player_id'],
|
|
19
|
+
projectOutcomes: (session) => session.status === 'won'
|
|
20
|
+
? [{
|
|
21
|
+
userId: session.player_id,
|
|
22
|
+
userName: session.player_name,
|
|
23
|
+
result: 'won',
|
|
24
|
+
score: session.player_wins * 10,
|
|
25
|
+
}]
|
|
26
|
+
: session.status === 'lost'
|
|
27
|
+
? [{
|
|
28
|
+
userId: session.player_id,
|
|
29
|
+
userName: session.player_name,
|
|
30
|
+
result: 'lost',
|
|
31
|
+
}]
|
|
32
|
+
: [],
|
|
33
|
+
});
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
async
|
|
32
|
-
if (!messageId) return null;
|
|
33
|
-
const rows = await getModel(this.db).findAll({});
|
|
34
|
-
for (const row of rows) {
|
|
35
|
-
if (boardMessageMatches(row.board_message_id ?? '', messageId)) return row;
|
|
36
|
-
}
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async createSession(message: Message<any>): Promise<DiceSessionRow> {
|
|
36
|
+
async createSession(message: GameMessageLike): Promise<DiceSessionRow> {
|
|
41
37
|
const now = Date.now();
|
|
42
38
|
const row: DiceSessionRow = {
|
|
43
39
|
id: generateSessionId(),
|
|
@@ -54,34 +50,13 @@ export class SessionService {
|
|
|
54
50
|
last_player_roll: 0,
|
|
55
51
|
last_bot_roll: 0,
|
|
56
52
|
status: 'active',
|
|
57
|
-
board_message_id: '',
|
|
58
53
|
updated_at: now,
|
|
59
54
|
created_at: now,
|
|
60
55
|
};
|
|
61
|
-
|
|
62
|
-
return row;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async updateSession(id: string, patch: Partial<DiceSessionRow>): Promise<void> {
|
|
66
|
-
await getModel(this.db).updateWhere({ id }, { ...patch, updated_at: Date.now() });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async abortStale(idleMs: number): Promise<number> {
|
|
70
|
-
const cutoff = Date.now() - idleMs;
|
|
71
|
-
const model = getModel(this.db);
|
|
72
|
-
const rows = await model.findAll({ status: 'active' });
|
|
73
|
-
let n = 0;
|
|
74
|
-
for (const row of rows) {
|
|
75
|
-
if (row.updated_at < cutoff) {
|
|
76
|
-
await model.updateWhere({ id: row.id }, { status: 'aborted', updated_at: Date.now() });
|
|
77
|
-
n++;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return n;
|
|
56
|
+
return this.createRow(row);
|
|
81
57
|
}
|
|
82
58
|
}
|
|
83
59
|
|
|
84
60
|
export function createServices(db: DiceDatabase): SessionService {
|
|
85
61
|
return new SessionService(db);
|
|
86
62
|
}
|
|
87
|
-
|
package/src/view.ts
CHANGED
|
@@ -31,13 +31,17 @@ export function buildDiceView(
|
|
|
31
31
|
lines.push('');
|
|
32
32
|
if (session.status === 'won') lines.push('🏆 **你赢得比赛!**');
|
|
33
33
|
else if (session.status === 'lost') lines.push('💀 **机器人赢得比赛。**');
|
|
34
|
+
else lines.push('🏳️ **本局已放弃。**');
|
|
34
35
|
} else {
|
|
35
36
|
lines.push('', lastRound ? '点击继续掷骰:' : '点击掷骰开始!');
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const choices = terminal
|
|
39
40
|
? [{ id: 'restart', label: '🔄 再来一局', style: 'primary' as const, keepEnabledWhenTerminal: true }]
|
|
40
|
-
: [
|
|
41
|
+
: [
|
|
42
|
+
{ id: 'roll', label: '🎲 掷骰', style: 'primary' as const },
|
|
43
|
+
{ id: 'quit', label: '🏳️ 放弃', style: 'danger' as const },
|
|
44
|
+
];
|
|
41
45
|
|
|
42
46
|
return buildChoiceKeyboard({
|
|
43
47
|
gamePrefix: DICE_PREFIX,
|
|
@@ -45,7 +49,7 @@ export function buildDiceView(
|
|
|
45
49
|
narrative: lines.join('\n'),
|
|
46
50
|
choices,
|
|
47
51
|
terminal,
|
|
48
|
-
fallbackHint: '回复 1 掷骰',
|
|
52
|
+
fallbackHint: '回复 1 掷骰 · 2 放弃',
|
|
49
53
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
50
54
|
channelType,
|
|
51
55
|
});
|
package/lib/memory-db.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { DatabaseHost } from '@zhin.js/plugin-runtime';
|
|
2
|
-
import { type DiceDatabase, type SessionService } from './session-service.js';
|
|
3
|
-
/** In-memory dice_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
4
|
-
export declare function createInMemoryDiceDb(): DiceDatabase;
|
|
5
|
-
export declare function mountDiceMemoryServices(): SessionService;
|
|
6
|
-
export declare function mountDiceHostServices(host: DatabaseHost): SessionService;
|
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 DICE_TABLES = ['dice_sessions'];
|
|
5
|
-
/** In-memory dice_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
6
|
-
export function createInMemoryDiceDb() {
|
|
7
|
-
return createInMemoryGameDb(DICE_TABLES);
|
|
8
|
-
}
|
|
9
|
-
export function mountDiceMemoryServices() {
|
|
10
|
-
const services = createServices(createInMemoryDiceDb());
|
|
11
|
-
return services;
|
|
12
|
-
}
|
|
13
|
-
export function mountDiceHostServices(host) {
|
|
14
|
-
defineHostTables(host);
|
|
15
|
-
const services = createServices(createHostGameDb(host, DICE_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,EAA0C,MAAM,sBAAsB,CAAC;AAE9F,MAAM,WAAW,GAAG,CAAC,eAAe,CAAU,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB;IAClC,OAAO,oBAAoB,CAAC,WAAW,CAA4B,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,MAAM,QAAQ,GAAG,cAAc,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACxD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAkB;IACtD,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,CAA4B,CAAC,CAAC;IAChG,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/plugin.ts
DELETED
|
@@ -1,63 +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 { mountDiceHostServices, mountDiceMemoryServices } from './src/memory-db.js';
|
|
9
|
-
import { gameServicesToken } from './src/runtime-store.js';
|
|
10
|
-
import type { SessionService } from './src/session-service.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Plugin Runtime:
|
|
14
|
-
* - Commands under `commands/` are authoritative (help + start/continue/quit via in-memory DB).
|
|
15
|
-
* - DB: prefer databaseHostToken; else in-memory SessionService.
|
|
16
|
-
* - Choice middleware under `middlewares/` handles dice roll payloads (Sandbox action→text).
|
|
17
|
-
*/
|
|
18
|
-
export default definePlugin({
|
|
19
|
-
name: 'dice-duel',
|
|
20
|
-
metadata: {
|
|
21
|
-
displayName: 'Dice Duel',
|
|
22
|
-
},
|
|
23
|
-
setup(context) {
|
|
24
|
-
let services: SessionService;
|
|
25
|
-
if (context.resources.has(databaseHostToken)) {
|
|
26
|
-
const host = context.resources.use(databaseHostToken);
|
|
27
|
-
services = mountDiceHostServices(host);
|
|
28
|
-
context.lifecycle.add(initGameRecordHost(host));
|
|
29
|
-
} else {
|
|
30
|
-
services = mountDiceMemoryServices();
|
|
31
|
-
}
|
|
32
|
-
context.resources.provide(gameServicesToken, services);
|
|
33
|
-
const disposeHub = registerRuntimeGame({
|
|
34
|
-
id: 'dice',
|
|
35
|
-
title: '骰子对决',
|
|
36
|
-
icon: '🎲',
|
|
37
|
-
description: '掷骰比大小,三局两胜',
|
|
38
|
-
commandPrefix: '/骰子',
|
|
39
|
-
quickStart: '开始',
|
|
40
|
-
aliases: ['dice'],
|
|
41
|
-
menus: [
|
|
42
|
-
{ id: 'start', label: '🎮 开始对局' },
|
|
43
|
-
{ id: 'continue', label: '🔄 继续' },
|
|
44
|
-
{ id: 'help', label: '📖 玩法说明' },
|
|
45
|
-
],
|
|
46
|
-
});
|
|
47
|
-
context.lifecycle.add(disposeHub);
|
|
48
|
-
|
|
49
|
-
if (context.resources.has(scheduleHostToken)) {
|
|
50
|
-
const schedule = context.resources.use(scheduleHostToken);
|
|
51
|
-
const disposeCron = schedule.register({
|
|
52
|
-
id: 'dice/abort-stale',
|
|
53
|
-
cron: DEFAULT_GAME_STALE_CRON,
|
|
54
|
-
description: 'Abort stale dice-duel sessions',
|
|
55
|
-
async execute() {
|
|
56
|
-
if (!services.abortStale) return;
|
|
57
|
-
await services.abortStale(DEFAULT_GAME_STALE_IDLE_MS);
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
context.lifecycle.add(disposeCron);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
});
|
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 DiceDatabase, type SessionService } from './session-service.js';
|
|
5
|
-
|
|
6
|
-
const DICE_TABLES = ['dice_sessions'] as const;
|
|
7
|
-
|
|
8
|
-
/** In-memory dice_sessions for Plugin Runtime slice-2 (no DatabaseFeature). */
|
|
9
|
-
export function createInMemoryDiceDb(): DiceDatabase {
|
|
10
|
-
return createInMemoryGameDb(DICE_TABLES) as unknown as DiceDatabase;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function mountDiceMemoryServices(): SessionService {
|
|
14
|
-
const services = createServices(createInMemoryDiceDb());
|
|
15
|
-
return services;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function mountDiceHostServices(host: DatabaseHost): SessionService {
|
|
19
|
-
defineHostTables(host);
|
|
20
|
-
const services = createServices(createHostGameDb(host, DICE_TABLES) as unknown as DiceDatabase);
|
|
21
|
-
return services;
|
|
22
|
-
}
|