diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index ed44aa0..d3eba57 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -392,6 +392,7 @@ export interface TsuguHook { - 已新增 `render-blocks/list-difficulty-detail-spec.ts`,收口玩家难度详情列表的徽章宽度/字号/圆角、正文宽度/行高、项画布高度和通用列表行高/间距;`list-difficulty-detail.ts` 改为消费 spec,玩家详情里的已通关/Full Combo/All Perfect 难度详情绘制顺序和输出结构保持不变;`list-difficulty-detail-spec.spec.ts` 覆盖徽章参数、正文绘制参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-band-detail-spec.ts`,收口玩家详情乐队等级、舞台挑战达成情况和乐队编成等级列表的 Logo 缩放、正文宽度/行高、项画布、通用列表框架和 Rank 等级图片布局;`list-band-detail.ts` 改为消费 spec,玩家详情里的乐队详情绘制顺序、资源 repository 和输出结构保持不变;`list-band-detail-spec.spec.ts` 覆盖 Logo/正文参数、项布局、列表框架、Rank 画布/图片位置和等级图片 Rank ID 封顶,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-character-detail-spec.ts`,收口玩家详情角色等级列表的头像缩放、正文宽度/行高、项画布和通用列表框架;`list-character-detail.ts` 改为消费 spec,玩家详情角色等级绘制顺序、角色数据读取和输出结构保持不变;`list-character-detail-spec.spec.ts` 覆盖头像/正文参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。 +- 已新增 `render-blocks/list-card-sd-character-spec.ts`,收口卡牌详情演出缩略图的 SD 角色 sprite 裁切网格、列表缩放行高、字号和间距;`list-card-sd-character.ts` 改为消费 spec,素材加载、四帧裁切和列表绘制顺序保持不变;`list-card-sd-character-spec.spec.ts` 覆盖四帧裁切坐标、单帧裁切计算和列表规格,本地 `/查卡 472` 图片 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-card-sd-character-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec.ts new file mode 100644 index 0000000..8007f70 --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec.ts @@ -0,0 +1,67 @@ +export interface CardSdCharacterCropRect { + sourceX: number; + sourceY: number; + width: number; + height: number; +} + +export const BANGDREAM_CARD_SD_CHARACTER_SPEC = { + sprite: { + columns: 2, + rows: 2, + cropOffsetY: 84, + cropWidth: 400, + cropHeight: 470, + }, + list: { + targetWidth: 190, + spacing: 0, + }, +} as const; + +/** + * 计算卡牌 SD 角色 sprite 裁切区域。 + * + * @param index - 第几个 SD 动作帧。 + */ +export function getCardSdCharacterCropRect( + index: number, +): CardSdCharacterCropRect { + const { columns, cropHeight, cropOffsetY, cropWidth } = + BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite; + + return { + sourceX: (index % columns) * cropWidth, + sourceY: cropOffsetY + Math.floor(index / columns) * cropHeight, + width: cropWidth, + height: cropHeight, + }; +} + +/** + * 生成卡牌 SD 角色列表需要展示的全部裁切区域。 + */ +export function getCardSdCharacterCropRects(): CardSdCharacterCropRect[] { + const { columns, rows } = BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite; + + return Array.from({ length: columns * rows }, (_, index) => + getCardSdCharacterCropRect(index), + ); +} + +/** + * 计算卡牌 SD 角色列表展示行高。 + */ +export function getCardSdCharacterListLineHeight() { + const { cropHeight, cropWidth } = BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite; + const { targetWidth } = BANGDREAM_CARD_SD_CHARACTER_SPEC.list; + + return (cropHeight / cropWidth) * targetWidth; +} + +/** + * 计算卡牌 SD 角色列表展示字号。 + */ +export function getCardSdCharacterListTextSize() { + return getCardSdCharacterListLineHeight(); +} diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character.ts index fea38d7..8b2ba97 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character.ts @@ -2,6 +2,12 @@ import { Costume } from '@/qqbot/plugins/bangDream/tsugu/models/costume'; import { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card'; import { Canvas } from 'skia-canvas'; import { drawList } from './list-frame'; +import { + BANGDREAM_CARD_SD_CHARACTER_SPEC, + getCardSdCharacterCropRects, + getCardSdCharacterListLineHeight, + getCardSdCharacterListTextSize, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec'; /** * 在图片布局层中绘制SD角色In列表。 @@ -14,22 +20,29 @@ export async function drawSdCharacterInList(card: Card): Promise { const costume = new Costume(costumeId); await costume.initFull(); const sdCharacterImage = await costume.getSdCharacter(); - //从高度84开始,把sdCharaImage切成田字形的四分,大小都为400*470 const sdCharacterImageList: Array = []; - for (let i = 0; i < 4; i++) { - const canvas = new Canvas(400, 470); + for (const cropRect of getCardSdCharacterCropRects()) { + const canvas = new Canvas(cropRect.width, cropRect.height); const context = canvas.getContext('2d'); - const x = i % 2 === 0 ? 0 : 400; - const y = i < 2 ? 84 : 554; - context.drawImage(sdCharacterImage, x, y, 400, 470, 0, 0, 400, 470); + context.drawImage( + sdCharacterImage, + cropRect.sourceX, + cropRect.sourceY, + cropRect.width, + cropRect.height, + 0, + 0, + cropRect.width, + cropRect.height, + ); sdCharacterImageList.push(canvas); } return drawList({ key: '演出缩略图', content: sdCharacterImageList, - lineHeight: (470 / 400) * 190, - textSize: (470 / 400) * 190, - spacing: 0, + lineHeight: getCardSdCharacterListLineHeight(), + textSize: getCardSdCharacterListTextSize(), + spacing: BANGDREAM_CARD_SD_CHARACTER_SPEC.list.spacing, }); } diff --git a/test/qqbot/plugins/bangDream/tsugu/list-card-sd-character-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/list-card-sd-character-spec.spec.ts new file mode 100644 index 0000000..9726bf3 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/list-card-sd-character-spec.spec.ts @@ -0,0 +1,33 @@ +import { + BANGDREAM_CARD_SD_CHARACTER_SPEC, + getCardSdCharacterCropRect, + getCardSdCharacterCropRects, + getCardSdCharacterListLineHeight, + getCardSdCharacterListTextSize, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec'; + +describe('BangDream card SD character list spec', () => { + it('keeps the historical four-frame SD character crop grid', () => { + expect(getCardSdCharacterCropRects()).toEqual([ + { sourceX: 0, sourceY: 84, width: 400, height: 470 }, + { sourceX: 400, sourceY: 84, width: 400, height: 470 }, + { sourceX: 0, sourceY: 554, width: 400, height: 470 }, + { sourceX: 400, sourceY: 554, width: 400, height: 470 }, + ]); + }); + + it('computes individual crop rects from the shared sprite spec', () => { + expect(getCardSdCharacterCropRect(3)).toEqual({ + sourceX: 400, + sourceY: 554, + width: 400, + height: 470, + }); + }); + + it('keeps list line height, text size and spacing stable', () => { + expect(BANGDREAM_CARD_SD_CHARACTER_SPEC.list.spacing).toBe(0); + expect(getCardSdCharacterListLineHeight()).toBe(223.25); + expect(getCardSdCharacterListTextSize()).toBe(223.25); + }); +});