diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index 15f8fcc..ce4c8b1 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -385,6 +385,7 @@ export interface TsuguHook { - 已新增 `render-blocks/event-stage-spec.ts`,收口试炼列表最大列高、歌曲单元格/封面/ID/难度条尺寸、试炼类型顶部文字规格和换列判断;`event-stage.ts` 与 `list-event-stage.ts` 改为消费 spec,活动/歌曲选择和输出结构保持不变。 - 线上 smoke 暴露 `/查试炼 310 -m` 将所有列横向拼成一张巨大 canvas 会触发 Pod `OOMKilled exit=137`;已改为按小批次绘制 stage、边分列边输出多张 CQ 图片,不再创建最终横向巨图,普通版和 meta 版均拆成 5 张输出,同时避免完全串行导致冷缓存首条命令耗时过长。 - 已新增 `event-stage-spec.spec.ts`,覆盖试炼歌曲行尺寸、类型顶部文字规格、换列判断和按列高拆分算法;本地生成 `event-stage-split-310*.jpg`、`event-stage-split-310-meta*.jpg`,验证 `/查试炼 310` 与 `/查试炼 310 -m` 图片输出正常。 +- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec,主卡组渲染顺序和输出结构保持不变;`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 - smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。 ### Phase 6:策略 policy 和时间/档线规则 diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-list.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-list.ts index 424c499..cce01fb 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-list.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-list.ts @@ -3,6 +3,12 @@ import { Player } from '@/qqbot/plugins/bangDream/tsugu/models/player'; import { Canvas } from 'skia-canvas'; import { drawCardIcon } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art'; import { drawList } from './list-frame'; +import { + BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC, + getPlayerCardIconListSpacing, + getPlayerCardIconListTextSize, + sortPlayerMainDeckEntries, +} from './list-player-card-icon-spec'; /** * 在图片布局层中绘制玩家卡牌In列表。 @@ -17,19 +23,14 @@ export async function drawPlayerCardInList( player: Player, key?: string, cardIdVisible = false, - lineHeight = 184, + lineHeight = BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.defaultLineHeight, ): Promise { - const textSize = (lineHeight / 200) * 180; - const spacing = (lineHeight / 200) * 13; + const textSize = getPlayerCardIconListTextSize(lineHeight); + const spacing = getPlayerCardIconListSpacing(lineHeight); const promiseList: Promise[] = []; - const tempCardDataList = player.profile.mainDeckUserSituations.entries; - //将tempCardDataList调整顺序,为3,1,0,2,4 - const defaultCardSort = [3, 1, 0, 2, 4]; - const cardDataList = []; - for (let i = 0; i < defaultCardSort.length; i++) { - const tempCardData = tempCardDataList[defaultCardSort[i]]; - cardDataList.push(tempCardData); - } + const cardDataList = sortPlayerMainDeckEntries( + player.profile.mainDeckUserSituations.entries, + ); const cardIconList: Array = []; for (const i in cardDataList) { const tempCardData = cardDataList[i]; @@ -40,8 +41,9 @@ export async function drawPlayerCardInList( illustrationTrainingStatus: tempCardData.illust == 'after_training', limitBreakRank: tempCardData.limitBreakRank, cardIdVisible: cardIdVisible, - skillTypeVisible: true, - cardTypeVisible: false, + skillTypeVisible: + BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.showSkillType, + cardTypeVisible: BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.showCardType, skillLevel: tempCardData.skillLevel, }), ); diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-spec.ts new file mode 100644 index 0000000..5589fc9 --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-spec.ts @@ -0,0 +1,41 @@ +export const BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC = { + card: { + defaultOrder: [3, 1, 0, 2, 4], + showCardType: false, + showSkillType: true, + }, + list: { + defaultLineHeight: 184, + spacingRatio: 13 / 200, + textSizeRatio: 180 / 200, + }, +} as const; + +/** + * 计算玩家卡牌列表文本字号。 + * + * @param lineHeight - 列表行高。 + */ +export function getPlayerCardIconListTextSize(lineHeight: number) { + return lineHeight * BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.textSizeRatio; +} + +/** + * 计算玩家卡牌列表卡牌间距。 + * + * @param lineHeight - 列表行高。 + */ +export function getPlayerCardIconListSpacing(lineHeight: number) { + return lineHeight * BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.spacingRatio; +} + +/** + * 按历史展示顺序排列玩家主卡组。 + * + * @param entries - 玩家主卡组条目。 + */ +export function sortPlayerMainDeckEntries(entries: T[]): T[] { + return BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.defaultOrder + .map((index) => entries[index]) + .filter((entry): entry is T => entry !== undefined); +} diff --git a/test/qqbot/plugins/bangDream/tsugu/list-player-card-icon-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/list-player-card-icon-spec.spec.ts new file mode 100644 index 0000000..8032b65 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/list-player-card-icon-spec.spec.ts @@ -0,0 +1,35 @@ +import { + BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC, + getPlayerCardIconListSpacing, + getPlayerCardIconListTextSize, + sortPlayerMainDeckEntries, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-spec'; + +describe('BangDream player card icon list spec', () => { + it('keeps player card list text and spacing ratios stable', () => { + const lineHeight = + BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.defaultLineHeight; + + expect(lineHeight).toBe(184); + expect(getPlayerCardIconListTextSize(lineHeight)).toBe(165.6); + expect(getPlayerCardIconListSpacing(lineHeight)).toBe(11.96); + }); + + it('keeps historical player deck display order', () => { + expect(sortPlayerMainDeckEntries(['0', '1', '2', '3', '4'])).toEqual([ + '3', + '1', + '0', + '2', + '4', + ]); + }); + + it('skips missing deck entries without changing the remaining order', () => { + expect(sortPlayerMainDeckEntries(['0', '1', '2'])).toEqual([ + '1', + '0', + '2', + ]); + }); +});