refactor: 收口 BangDream 综合力列表规格

This commit is contained in:
sunlei 2026-06-07 05:40:56 +08:00
parent 6bda3f306f
commit 8bc529b50f
4 changed files with 183 additions and 18 deletions

View File

@ -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 和时间/档线规则

View File

@ -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,
};
}

View File

@ -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<string, { color: string; name: string }> =
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<Canvas> {
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 = [];

View File

@ -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,
});
});
});