From 8bc529b50fc21dd46ae62db9e3e49753aff6fccf Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 05:40:56 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20BangDream=20?= =?UTF-8?q?=E7=BB=BC=E5=90=88=E5=8A=9B=E5=88=97=E8=A1=A8=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ot-bangdream-tsugu-global-refactor-plan.md | 1 + .../tsugu/render-blocks/list-stat-spec.ts | 80 +++++++++++++++++++ .../tsugu/render-blocks/list-stat.ts | 55 ++++++++----- .../bangDream/tsugu/list-stat-spec.spec.ts | 65 +++++++++++++++ 4 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat-spec.ts create mode 100644 test/qqbot/plugins/bangDream/tsugu/list-stat-spec.spec.ts diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index 16442d8..aabc184 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -394,6 +394,7 @@ export interface TsuguHook { - 已新增 `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 输出非空。 - 已新增 `render-blocks/list-card-prefix-spec.ts`,收口卡牌详情顶部标题块的画布、背景、乐队 Logo 等比缩放、卡牌标题和角色名文字布局;`list-card-prefix.ts` 改为消费 spec,乐队/角色/服务器优先级读取和绘制顺序保持不变;`list-card-prefix-spec.spec.ts` 覆盖标题块尺寸、背景、文字坐标和 Logo 缩放,本地 `/查卡 472` 图片 smoke 输出非空。 +- 已新增 `render-blocks/list-stat-spec.ts`,收口卡牌/玩家综合力列表的间隔、数值行画布、文字规格、进度条布局和条宽算法;`list-stat.ts` 改为消费 spec,综合力计算、突破加成和绘制顺序保持不变;`list-stat-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-stat-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat-spec.ts new file mode 100644 index 0000000..17f694b --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat-spec.ts @@ -0,0 +1,80 @@ +export interface StatLineTextParams { + label: string; + limitBreakValue?: number; + value: number; +} + +export const BANGDREAM_STAT_LIST_SPEC = { + line: { + bar: { + height: 30, + radius: 15, + strokeWidth: 0, + widthScale: 2, + x: 20, + y: 35, + }, + canvas: { + height: 70, + width: 800, + }, + text: { + lineHeight: 30, + maxWidth: 800, + textSize: 30, + x: 20, + y: 0, + }, + }, + spacer: { + height: 5, + width: 1, + }, +} as const; + +/** + * 创建综合力数值行展示文本。 + * + * @param params - 数值行文本参数。 + */ +export function createStatLineText(params: StatLineTextParams): string { + const baseText = `${params.label}: ${Math.floor(params.value)}`; + + if (params.limitBreakValue == null) { + return baseText; + } + + return `${baseText} + (${params.limitBreakValue * 4})`; +} + +/** + * 计算综合力数值条宽度。 + * + * @param value - 当前数值。 + * @param total - 综合力总数值。 + */ +export function getStatLineBarWidth(value: number, total: number): number { + return ( + ((BANGDREAM_STAT_LIST_SPEC.line.canvas.width * value) / total) * + BANGDREAM_STAT_LIST_SPEC.line.bar.widthScale + ); +} + +/** + * 生成综合力数值条绘制布局。 + * + * @param value - 当前数值。 + * @param total - 综合力总数值。 + */ +export function getStatLineBarLayout(value: number, total: number) { + const barSpec = BANGDREAM_STAT_LIST_SPEC.line.bar; + + return { + height: barSpec.height, + radius: barSpec.radius, + strokeWidth: barSpec.strokeWidth, + width: getStatLineBarWidth(value, total), + x: barSpec.x, + y: barSpec.y, + }; +} diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat.ts index f55ab64..658ce82 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat.ts @@ -9,6 +9,11 @@ import { limitBreakRankStat, } from '@/qqbot/plugins/bangDream/tsugu/models/card'; import { BANGDREAM_STAT_CONFIG } from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants'; +import { + BANGDREAM_STAT_LIST_SPEC, + createStatLineText, + getStatLineBarLayout, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat-spec'; export const statConfig: Record = BANGDREAM_STAT_CONFIG; @@ -34,7 +39,12 @@ export async function drawCardStatInList(card: Card) { content: [`综合力: ${statTotal} + (${limitBreakstatTotal * 4})`], }), ); - list.push(new Canvas(1, 5)); + list.push( + new Canvas( + BANGDREAM_STAT_LIST_SPEC.spacer.width, + BANGDREAM_STAT_LIST_SPEC.spacer.height, + ), + ); list.push(statImage); return stackImage(list); } @@ -54,7 +64,12 @@ export async function drawStatInList(stat: Stat) { content: [`综合力: ${statTotal}`], }), ); - list.push(new Canvas(1, 5)); + list.push( + new Canvas( + BANGDREAM_STAT_LIST_SPEC.spacer.width, + BANGDREAM_STAT_LIST_SPEC.spacer.height, + ), + ); list.push(statImage); return stackImage(list); } @@ -72,8 +87,6 @@ async function drawCardStatDivided( statTotal: number, limitBreakstat?: Stat, ): Promise { - const widthMax = 800; - /** * 在图片布局层中绘制数值线条。 * @@ -83,27 +96,33 @@ async function drawCardStatDivided( * @returns 渲染或资源结果。 */ function drawStatLine(key: string, value: number, total: number): Canvas { - const canvas = new Canvas(800, 70); + const canvas = new Canvas( + BANGDREAM_STAT_LIST_SPEC.line.canvas.width, + BANGDREAM_STAT_LIST_SPEC.line.canvas.height, + ); const ctx = canvas.getContext('2d'); - let text = `${statConfig[key].name}: ${Math.floor(value)}`; - if (limitBreakstat) { - text += ` + (${limitBreakstat[key] * 4})`; - } + const text = createStatLineText({ + label: statConfig[key].name, + limitBreakValue: limitBreakstat?.[key], + value, + }); + const textSpec = BANGDREAM_STAT_LIST_SPEC.line.text; const textImage = drawText({ text, - maxWidth: widthMax, - textSize: 30, - lineHeight: 30, + maxWidth: textSpec.maxWidth, + textSize: textSpec.textSize, + lineHeight: textSpec.lineHeight, }); + const barLayout = getStatLineBarLayout(value, total); const roundedRect = drawRoundedRect({ - width: ((widthMax * value) / total) * 2, - height: 30, - radius: 15, + width: barLayout.width, + height: barLayout.height, + radius: barLayout.radius, color: statConfig[key].color, - strokeWidth: 0, + strokeWidth: barLayout.strokeWidth, }); - ctx.drawImage(textImage, 20, 0); - ctx.drawImage(roundedRect, 20, 35); + ctx.drawImage(textImage, textSpec.x, textSpec.y); + ctx.drawImage(roundedRect, barLayout.x, barLayout.y); return canvas; } const list = []; diff --git a/test/qqbot/plugins/bangDream/tsugu/list-stat-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/list-stat-spec.spec.ts new file mode 100644 index 0000000..ed5010d --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/list-stat-spec.spec.ts @@ -0,0 +1,65 @@ +import { + BANGDREAM_STAT_LIST_SPEC, + createStatLineText, + getStatLineBarLayout, + getStatLineBarWidth, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-stat-spec'; + +describe('BangDream stat list spec', () => { + it('keeps the historical stat list spacer stable', () => { + expect(BANGDREAM_STAT_LIST_SPEC.spacer).toEqual({ + height: 5, + width: 1, + }); + }); + + it('keeps the historical stat line canvas, text and bar layout stable', () => { + expect(BANGDREAM_STAT_LIST_SPEC.line.canvas).toEqual({ + height: 70, + width: 800, + }); + expect(BANGDREAM_STAT_LIST_SPEC.line.text).toEqual({ + lineHeight: 30, + maxWidth: 800, + textSize: 30, + x: 20, + y: 0, + }); + expect(BANGDREAM_STAT_LIST_SPEC.line.bar).toEqual({ + height: 30, + radius: 15, + strokeWidth: 0, + widthScale: 2, + x: 20, + y: 35, + }); + }); + + it('formats stat line text with optional limit break bonus', () => { + expect( + createStatLineText({ + label: '演出', + value: 10654.9, + }), + ).toBe('演出: 10654'); + expect( + createStatLineText({ + label: '演出', + limitBreakValue: 800, + value: 10654.9, + }), + ).toBe('演出: 10654 + (3200)'); + }); + + it('computes the doubled historical stat bar width and layout', () => { + expect(getStatLineBarWidth(100, 400)).toBe(400); + expect(getStatLineBarLayout(100, 400)).toEqual({ + height: 30, + radius: 15, + strokeWidth: 0, + width: 400, + x: 20, + y: 35, + }); + }); +});