@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.
@@ -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, runDiceCommandText } from '../../src/dice-command.js';
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 runDiceCommandText(services, message, action);
13
+ return runDiceCommand(services, message, action);
14
14
  },
15
15
  });
@@ -1,6 +1,4 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
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(plugin: Plugin | null, services: SessionService, message: Message<any>, action: string): Promise<string | undefined>;
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>;
@@ -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(plugin, services, message, action) {
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(plugin, services, message);
25
+ return (await startGame(services, message)) ?? '';
26
26
  if (action === 'continue')
27
- return continueGame(plugin, services, message);
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.updateSession(row.id, { status: 'aborted' });
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
@@ -1 +1 @@
1
- {"version":3,"file":"dice-command.js","sourceRoot":"","sources":["../src/dice-command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGzD,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,MAAqB,EACrB,QAAwB,EACxB,OAAqB,EACrB,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,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpE,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE1E,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,MAAM,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,SAAS,MAAM,OAAO,SAAS,EAAE,CAAC;AAC3C,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAwB,EACxB,OAAqB,EACrB,MAAc;IAEd,OAAO,CAAC,MAAM,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AACvE,CAAC"}
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"}
@@ -1,13 +1,7 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
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 sendOrEditView(plugin: Plugin | null, services: SessionService, message: Message<any>, session: DiceSessionRow, lastRound?: {
6
- player: number;
7
- bot: number;
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
- export async function sendOrEditView(plugin, services, message, session, lastRound) {
5
- const content = buildDiceView(session, lastRound, message.$channel.type);
6
- if (!plugin)
7
- return plainTextFromSendContent(content);
8
- const adapter = plugin.root.inject(message.$adapter);
9
- if (session.board_message_id) {
10
- const msgId = await adapter.editMessage({
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(plugin, services, message) {
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
- const text = await sendOrEditView(plugin, services, message, session);
38
- return typeof text === 'string' ? text : undefined;
21
+ return renderView(session, message);
39
22
  }
40
- export async function continueGame(plugin, services, message) {
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
- const text = await sendOrEditView(plugin, services, message, session);
45
- if (typeof text === 'string')
46
- return text;
47
- return '已刷新骰子界面。';
27
+ return renderView(session, message);
48
28
  }
49
- export async function handleChoice(plugin, services, message, sessionId, choiceId) {
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
- // text-only 模式(plugin===null)下 startGame 的唯一输出就是返回文本
61
- return (await startGame(plugin, services, message)) ?? null;
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
- if (status === 'won')
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
@@ -1 +1 @@
1
- {"version":3,"file":"game-flow.js","sourceRoot":"","sources":["../src/game-flow.ts"],"names":[],"mappings":"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"}
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;AAC5E,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE/E;;;;GAIG"}
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
@@ -17,7 +17,6 @@ declare module '@zhin.js/core' {
17
17
  last_player_roll: number;
18
18
  last_bot_roll: number;
19
19
  status: DiceSessionStatus;
20
- board_message_id: string;
21
20
  updated_at: number;
22
21
  created_at: number;
23
22
  };
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":"AA+BA,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,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC/C,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"}
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"}
@@ -1,15 +1,9 @@
1
- import type { Database, Message, Models } from '@zhin.js/core';
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
- getActiveByChannel(channel: string): Promise<DiceSessionRow | null>;
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;
@@ -1,37 +1,25 @@
1
- import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-kit';
2
- function getModel(db) {
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
- this.db = db;
12
- }
13
- async getActiveByChannel(channel) {
14
- const rows = await getModel(this.db).findAll({ channel_key: channel, status: 'active' });
15
- return rows[0] ?? null;
16
- }
17
- async getActiveForUser(channel, userId) {
18
- const row = await this.getActiveByChannel(channel);
19
- if (!row || row.player_id !== userId)
20
- return null;
21
- return row;
22
- }
23
- async getById(id) {
24
- return getModel(this.db).findOne({ id });
25
- }
26
- async getActiveByBoardMessageId(messageId) {
27
- if (!messageId)
28
- return null;
29
- const rows = await getModel(this.db).findAll({});
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
- await getModel(this.db).create(row);
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":"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"}
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
- : [{ id: 'roll', label: '🎲 掷骰', style: 'primary' }];
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;IACpE,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,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAkB,EAAE,CAAC,CAAC;IAEhE,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,SAAS;QACvB,kBAAkB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;QACtD,WAAW;KACZ,CAAC,CAAC;AACL,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, runDiceCommandText } from '../src/dice-command.js';
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 (!normalized || normalized === 'help') return DICE_HELP;
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 runDiceCommandText(services, message, normalized);
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(null, services, message, parsed.sessionId, parsed.choiceId);
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, [{ id: 'roll', label: '掷骰' }]);
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(null, services, message, session.id, 'roll');
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",
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.ts",
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.2",
28
- "@zhin.js/core": "1.4.0",
29
- "@zhin.js/game-kit": "2.0.0",
30
- "@zhin.js/middleware": "1.0.2",
31
- "@zhin.js/plugin-runtime": "1.1.0"
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.ts",
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
+ });
@@ -1,6 +1,9 @@
1
- import type { Message, Plugin } from '@zhin.js/core';
2
- import { channelKey } from '@zhin.js/game-kit';
3
- import { continueGame, startGame } from './game-flow.js';
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: Message<any>,
19
+ message: GameMessageLike,
18
20
  action: string,
19
- ): Promise<string | undefined> {
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(plugin, services, message);
35
- if (action === 'continue') return continueGame(plugin, services, message);
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.updateSession(row.id, { status: 'aborted' });
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 { Adapter, Message, Plugin } from '@zhin.js/core';
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
- export async function sendOrEditView(
9
- plugin: Plugin | null,
10
- services: SessionService,
11
- message: Message<any>,
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
- ): Promise<string | void> {
15
- const content = buildDiceView(session, lastRound, message.$channel.type);
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: Message<any>,
43
- ): Promise<string | undefined> {
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
- const text = await sendOrEditView(plugin, services, message, session);
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: Message<any>,
61
- ): Promise<string> {
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
- const text = await sendOrEditView(plugin, services, message, session);
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: Message<any>,
50
+ message: GameMessageLike,
76
51
  sessionId: string,
77
52
  choiceId: string,
78
- ): Promise<string | null> {
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
- // text-only 模式(plugin===null)下 startGame 的唯一输出就是返回文本
90
- return (await startGame(plugin, services, message)) ?? null;
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
- if (status === 'won') void recordGameOutcome(message, 'dice', 'won', playerWins * 10);
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
  });
@@ -1,43 +1,39 @@
1
- import type { Database, Message, Models, RelatedModel } from '@zhin.js/core';
2
- import { channelKey, generateSessionId, boardMessageMatches } from '@zhin.js/game-kit';
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
- function getModel(db: DiceDatabase) {
8
- const model = db.models.get('dice_sessions');
9
- if (!model) throw new Error('dice_sessions not registered');
10
- return model as RelatedModel<unknown, Models, 'dice_sessions'>;
11
- }
12
-
13
- export class SessionService {
14
- constructor(private readonly db: DiceDatabase) {}
15
-
16
- async getActiveByChannel(channel: string): Promise<DiceSessionRow | null> {
17
- const rows = await getModel(this.db).findAll({ channel_key: channel, status: 'active' });
18
- return rows[0] ?? null;
19
- }
20
-
21
- async getActiveForUser(channel: string, userId: string): Promise<DiceSessionRow | null> {
22
- const row = await this.getActiveByChannel(channel);
23
- if (!row || row.player_id !== userId) return null;
24
- return row;
25
- }
26
-
27
- async getById(id: string): Promise<DiceSessionRow | null> {
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 getActiveByBoardMessageId(messageId: string): Promise<DiceSessionRow | null> {
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
- await getModel(this.db).create(row);
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
- : [{ id: 'roll', label: '🎲 掷骰', style: 'primary' as const }];
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
  });
@@ -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
@@ -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
- }