@zhin.js/plugin-dice-duel 1.0.4 → 1.0.6

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,4 +1,4 @@
1
- import { type GameMessageLike } from '@zhin.js/game-kit';
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(services: SessionService, message: GameMessageLike, 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 — 帮助',
@@ -29,8 +29,7 @@ export async function runDiceCommand(services, message, action) {
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dice-command.js","sourceRoot":"","sources":["../src/dice-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,MAAM,mBAAmB,CAAC;AACrE,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,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,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"}
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,7 +1,7 @@
1
- import { type GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import { DICE_PREFIX } from './engine.js';
3
3
  import type { SessionService } from './session-service.js';
4
- export declare function startGame(services: SessionService, message: GameMessageLike): Promise<string | undefined>;
5
- export declare function continueGame(services: SessionService, message: GameMessageLike): Promise<string>;
6
- export declare function handleChoice(services: SessionService, message: GameMessageLike, 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>;
7
7
  export { DICE_PREFIX };
package/lib/game-flow.js CHANGED
@@ -1,4 +1,3 @@
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
3
  /**
@@ -7,7 +6,7 @@ import { buildDiceView } from './view.js';
7
6
  * and the choice middleware return fresh text each turn.
8
7
  */
9
8
  function renderView(session, message, lastRound) {
10
- return plainTextFromSendContent(buildDiceView(session, lastRound, message.$channel.type));
9
+ return buildDiceView(session, lastRound, message.$channel.type);
11
10
  }
12
11
  export async function startGame(services, message) {
13
12
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
@@ -40,6 +39,10 @@ export async function handleChoice(services, message, sessionId, choiceId) {
40
39
  await services.updateSession(session.id, { status: 'aborted' });
41
40
  return (await startGame(services, message)) ?? null;
42
41
  }
42
+ if (choiceId === 'quit') {
43
+ await services.updateSession(session.id, { status: 'aborted' });
44
+ return renderView((await services.getById(session.id)), message);
45
+ }
43
46
  if (choiceId !== 'roll')
44
47
  return '无效操作。';
45
48
  const player = rollD6();
@@ -65,10 +68,6 @@ export async function handleChoice(services, message, sessionId, choiceId) {
65
68
  status,
66
69
  });
67
70
  const updated = (await services.getById(session.id));
68
- if (status === 'won')
69
- void recordGameOutcome(message, 'dice', 'won', playerWins * 10);
70
- else if (status === 'lost')
71
- void recordGameOutcome(message, 'dice', 'lost');
72
71
  return renderView(updated, message, { player, bot, result });
73
72
  }
74
73
  export { DICE_PREFIX };
@@ -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,16 +1,9 @@
1
1
  import type { Database, Models } from '@zhin.js/core';
2
- import { type GameMessageLike } from '@zhin.js/game-kit';
2
+ import { BaseSessionService, type GameMessageLike } from '@zhin.js/game-kit';
3
3
  import type { DiceSessionRow } from './models.js';
4
4
  export type DiceDatabase = Database<unknown, Models, string>;
5
- export declare class SessionService {
6
- private readonly db;
5
+ export declare class SessionService extends BaseSessionService<DiceSessionRow> {
7
6
  constructor(db: DiceDatabase);
8
- getActiveByChannel(channel: string): Promise<DiceSessionRow | null>;
9
- getActiveForUser(channel: string, userId: string): Promise<DiceSessionRow | null>;
10
- getById(id: string): Promise<DiceSessionRow | null>;
11
- getActiveByBoardMessageId(messageId: string): Promise<DiceSessionRow | null>;
12
7
  createSession(message: GameMessageLike): Promise<DiceSessionRow>;
13
- updateSession(id: string, patch: Partial<DiceSessionRow>): Promise<void>;
14
- abortStale(idleMs: number): Promise<number>;
15
8
  }
16
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
+ });
@@ -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
+ });
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-dice-duel",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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,10 +24,10 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@zhin.js/command": "1.0.3",
28
- "@zhin.js/core": "1.4.1",
29
- "@zhin.js/game-kit": "2.0.1",
30
- "@zhin.js/middleware": "1.0.3",
27
+ "@zhin.js/command": "1.0.4",
28
+ "@zhin.js/core": "1.4.3",
29
+ "@zhin.js/game-kit": "2.0.3",
30
+ "@zhin.js/middleware": "1.0.4",
31
31
  "@zhin.js/plugin-runtime": "1.1.1"
32
32
  },
33
33
  "devDependencies": {
@@ -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,5 +1,9 @@
1
- import { channelKey, type GameMessageLike } from '@zhin.js/game-kit';
2
- 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';
3
7
  import type { SessionService } from './session-service.js';
4
8
 
5
9
  export const DICE_HELP = [
@@ -14,7 +18,7 @@ export async function runDiceCommand(
14
18
  services: SessionService,
15
19
  message: GameMessageLike,
16
20
  action: string,
17
- ): Promise<string> {
21
+ ): Promise<GameReply> {
18
22
  const ch = channelKey(message);
19
23
  const userId = message.$sender.id;
20
24
 
@@ -35,8 +39,7 @@ export async function runDiceCommand(
35
39
  if (action === 'quit') {
36
40
  const row = await services.getActiveForUser(ch, userId);
37
41
  if (!row) return '你没有进行中的骰子对决。';
38
- await services.updateSession(row.id, { status: 'aborted' });
39
- return '已放弃骰子对局。';
42
+ return (await handleChoice(services, message, row.id, 'quit')) ?? '';
40
43
  }
41
44
 
42
45
  return `未知子命令:${action}\n\n${DICE_HELP}`;
package/src/game-flow.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { plainTextFromSendContent, recordGameOutcome, type GameMessageLike } from '@zhin.js/game-kit';
1
+ import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
2
2
  import type { DiceSessionRow } from './models.js';
3
3
  import { compareRolls, DICE_PREFIX, rollD6, WIN_TARGET } from './engine.js';
4
4
  import type { SessionService } from './session-service.js';
@@ -13,14 +13,14 @@ function renderView(
13
13
  session: DiceSessionRow,
14
14
  message: GameMessageLike,
15
15
  lastRound?: { player: number; bot: number; result: 0 | 1 | 2 },
16
- ): string {
17
- return plainTextFromSendContent(buildDiceView(session, lastRound, message.$channel.type));
16
+ ): GameReply {
17
+ return buildDiceView(session, lastRound, message.$channel.type);
18
18
  }
19
19
 
20
20
  export async function startGame(
21
21
  services: SessionService,
22
22
  message: GameMessageLike,
23
- ): Promise<string | undefined> {
23
+ ): Promise<GameReply | undefined> {
24
24
  const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
25
25
  const active = await services.getActiveByChannel(ch);
26
26
  if (active) {
@@ -36,7 +36,7 @@ export async function startGame(
36
36
  export async function continueGame(
37
37
  services: SessionService,
38
38
  message: GameMessageLike,
39
- ): Promise<string> {
39
+ ): Promise<GameReply> {
40
40
  const session = await services.getActiveForUser(
41
41
  `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
42
42
  message.$sender.id,
@@ -50,7 +50,7 @@ export async function handleChoice(
50
50
  message: GameMessageLike,
51
51
  sessionId: string,
52
52
  choiceId: string,
53
- ): Promise<string | null> {
53
+ ): Promise<GameReply | null> {
54
54
  const session = await services.getById(sessionId);
55
55
  if (!session) return '对局不存在。';
56
56
  if (session.player_id !== message.$sender.id) return '这是别人的对局。';
@@ -63,6 +63,10 @@ export async function handleChoice(
63
63
  await services.updateSession(session.id, { status: 'aborted' });
64
64
  return (await startGame(services, message)) ?? null;
65
65
  }
66
+ if (choiceId === 'quit') {
67
+ await services.updateSession(session.id, { status: 'aborted' });
68
+ return renderView((await services.getById(session.id))!, message);
69
+ }
66
70
 
67
71
  if (choiceId !== 'roll') return '无效操作。';
68
72
 
@@ -89,8 +93,6 @@ export async function handleChoice(
89
93
  });
90
94
 
91
95
  const updated = (await services.getById(session.id))!;
92
- if (status === 'won') void recordGameOutcome(message, 'dice', 'won', playerWins * 10);
93
- else if (status === 'lost') void recordGameOutcome(message, 'dice', 'lost');
94
96
  return renderView(updated, message, { player, bot, result });
95
97
  }
96
98
 
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,40 +1,36 @@
1
- import type { Database, Models, RelatedModel } from '@zhin.js/core';
2
- import { channelKey, generateSessionId, boardMessageMatches, type GameMessageLike } 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 });
29
- }
30
-
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;
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
+ });
38
34
  }
39
35
 
40
36
  async createSession(message: GameMessageLike): Promise<DiceSessionRow> {
@@ -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
- }