refactor: 收口 BangDream 乐队详情列表规格
This commit is contained in:
parent
a826f0c5f1
commit
97d21767ae
@ -390,6 +390,7 @@ export interface TsuguHook {
|
|||||||
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec,歌曲列表绘制顺序和输出结构保持不变;`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
|
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec,歌曲列表绘制顺序和输出结构保持不变;`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
|
||||||
- 已新增 `render-blocks/list-difficulty-spec.ts`,收口难度列表默认高度/间距、徽章兜底色、圆形布局和等级文字比例/居中计算;`list-difficulty.ts` 改为消费 spec,难度列表绘制顺序和输出结构保持不变;`list-difficulty-spec.spec.ts` 覆盖列表宽度、条目偏移、徽章颜色、圆形布局和文字居中,本地 `/查曲 136` 图片 smoke 输出非空。
|
- 已新增 `render-blocks/list-difficulty-spec.ts`,收口难度列表默认高度/间距、徽章兜底色、圆形布局和等级文字比例/居中计算;`list-difficulty.ts` 改为消费 spec,难度列表绘制顺序和输出结构保持不变;`list-difficulty-spec.spec.ts` 覆盖列表宽度、条目偏移、徽章颜色、圆形布局和文字居中,本地 `/查曲 136` 图片 smoke 输出非空。
|
||||||
- 已新增 `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-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 输出非空。
|
||||||
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
||||||
|
|
||||||
### Phase 6:策略 policy 和时间/档线规则
|
### Phase 6:策略 policy 和时间/档线规则
|
||||||
|
|||||||
@ -0,0 +1,131 @@
|
|||||||
|
interface ImageLike {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BANGDREAM_BAND_DETAIL_LIST_SPEC = {
|
||||||
|
deckRank: {
|
||||||
|
height: 100,
|
||||||
|
levelHeight: 50,
|
||||||
|
levelOffsetX: 2,
|
||||||
|
levelY: 45,
|
||||||
|
maxLevelSpriteRankId: 4,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
height: 100,
|
||||||
|
logoWidth: 110,
|
||||||
|
textLineHeight: 40,
|
||||||
|
textOffsetY: 50,
|
||||||
|
width: 152,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
spacing: 0,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队详情 Logo 缩放参数。
|
||||||
|
*/
|
||||||
|
export function createBandDetailLogoSpec() {
|
||||||
|
return {
|
||||||
|
widthMax: BANGDREAM_BAND_DETAIL_LIST_SPEC.item.logoWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队详情正文绘制参数。
|
||||||
|
*/
|
||||||
|
export function createBandDetailTextSpec() {
|
||||||
|
const item = BANGDREAM_BAND_DETAIL_LIST_SPEC.item;
|
||||||
|
return {
|
||||||
|
lineHeight: item.textLineHeight,
|
||||||
|
maxWidth: item.width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队详情项画布和内容位置。
|
||||||
|
*
|
||||||
|
* @param contentImage - 正文图片尺寸。
|
||||||
|
*/
|
||||||
|
export function createBandDetailItemLayout(contentImage: ImageLike) {
|
||||||
|
const item = BANGDREAM_BAND_DETAIL_LIST_SPEC.item;
|
||||||
|
return {
|
||||||
|
canvasHeight: item.height,
|
||||||
|
canvasWidth: item.width,
|
||||||
|
logoX: (item.width - item.logoWidth) / 2,
|
||||||
|
logoY: 0,
|
||||||
|
textX: item.width / 2 - contentImage.width / 2,
|
||||||
|
textY: item.textOffsetY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队详情列表传给通用列表框架的尺寸。
|
||||||
|
*
|
||||||
|
* @param firstItem - 第一个乐队详情项。
|
||||||
|
*/
|
||||||
|
export function createBandDetailListFrameSpec(firstItem?: ImageLike) {
|
||||||
|
return {
|
||||||
|
lineHeight: firstItem?.height,
|
||||||
|
spacing: BANGDREAM_BAND_DETAIL_LIST_SPEC.list.spacing,
|
||||||
|
textSize: firstItem?.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队编成等级画布尺寸。
|
||||||
|
*/
|
||||||
|
export function createDeckRankCanvasSpec() {
|
||||||
|
const deckRank = BANGDREAM_BAND_DETAIL_LIST_SPEC.deckRank;
|
||||||
|
return {
|
||||||
|
height: deckRank.height,
|
||||||
|
width: deckRank.width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队编成等级图片位置。
|
||||||
|
*
|
||||||
|
* @param rankImage - Rank 主图片尺寸。
|
||||||
|
* @param levelImage - Rank 等级图片尺寸。
|
||||||
|
*/
|
||||||
|
export function createDeckRankImageLayout(
|
||||||
|
rankImage: ImageLike,
|
||||||
|
levelImage?: ImageLike,
|
||||||
|
) {
|
||||||
|
const deckRank = BANGDREAM_BAND_DETAIL_LIST_SPEC.deckRank;
|
||||||
|
return {
|
||||||
|
rankX: (deckRank.width - rankImage.width) / 2,
|
||||||
|
rankY: 0,
|
||||||
|
...(levelImage
|
||||||
|
? {
|
||||||
|
levelX:
|
||||||
|
(deckRank.width + rankImage.width) / 2 +
|
||||||
|
deckRank.levelOffsetX -
|
||||||
|
levelImage.width,
|
||||||
|
levelY: deckRank.levelY,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算乐队编成等级的等级图片缩放参数。
|
||||||
|
*/
|
||||||
|
export function createDeckRankLevelImageSpec() {
|
||||||
|
return {
|
||||||
|
heightMax: BANGDREAM_BAND_DETAIL_LIST_SPEC.deckRank.levelHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算可用于等级图片文件名的 Rank ID。
|
||||||
|
*
|
||||||
|
* @param rankId - 原始 Rank ID。
|
||||||
|
*/
|
||||||
|
export function normalizeDeckRankLevelSpriteRankId(rankId: number) {
|
||||||
|
const maxRankId = BANGDREAM_BAND_DETAIL_LIST_SPEC.deckRank.maxLevelSpriteRankId;
|
||||||
|
return rankId > maxRankId ? maxRankId : rankId;
|
||||||
|
}
|
||||||
@ -11,6 +11,16 @@ import {
|
|||||||
BANGDREAM_STAGE_CHALLENGE_BAND_ID,
|
BANGDREAM_STAGE_CHALLENGE_BAND_ID,
|
||||||
} from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants';
|
} from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants';
|
||||||
import { deckRankResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/deck-rank-resource-repository';
|
import { deckRankResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/deck-rank-resource-repository';
|
||||||
|
import {
|
||||||
|
createBandDetailItemLayout,
|
||||||
|
createBandDetailListFrameSpec,
|
||||||
|
createBandDetailLogoSpec,
|
||||||
|
createBandDetailTextSpec,
|
||||||
|
createDeckRankCanvasSpec,
|
||||||
|
createDeckRankImageLayout,
|
||||||
|
createDeckRankLevelImageSpec,
|
||||||
|
normalizeDeckRankLevelSpriteRankId,
|
||||||
|
} from './list-band-detail-spec';
|
||||||
|
|
||||||
interface drawBandDetailsInListOptions {
|
interface drawBandDetailsInListOptions {
|
||||||
[bandId: number]: Array<Canvas | Image | string>;
|
[bandId: number]: Array<Canvas | Image | string>;
|
||||||
@ -30,33 +40,30 @@ async function drawBandDetailsInList(
|
|||||||
for (const i in BandDetailsInListOptions) {
|
for (const i in BandDetailsInListOptions) {
|
||||||
const tempBand = new Band(parseInt(i));
|
const tempBand = new Band(parseInt(i));
|
||||||
const content = BandDetailsInListOptions[i];
|
const content = BandDetailsInListOptions[i];
|
||||||
const maxWidth = 152;
|
|
||||||
const logoWidth = 110;
|
|
||||||
const tempBandIcon = resizeImage({
|
const tempBandIcon = resizeImage({
|
||||||
image: await tempBand.getLogo(),
|
image: await tempBand.getLogo(),
|
||||||
widthMax: logoWidth,
|
...createBandDetailLogoSpec(),
|
||||||
});
|
});
|
||||||
const canvas = new Canvas(maxWidth, 100);
|
const textSpec = createBandDetailTextSpec();
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(tempBandIcon, (maxWidth - logoWidth) / 2, 0);
|
|
||||||
const tempBandRankText = drawTextWithImages({
|
const tempBandRankText = drawTextWithImages({
|
||||||
content,
|
content,
|
||||||
maxWidth: maxWidth,
|
maxWidth: textSpec.maxWidth,
|
||||||
lineHeight: 40,
|
lineHeight: textSpec.lineHeight,
|
||||||
});
|
});
|
||||||
ctx.drawImage(
|
const layout = createBandDetailItemLayout(tempBandRankText);
|
||||||
tempBandRankText,
|
const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight);
|
||||||
maxWidth / 2 - tempBandRankText.width / 2,
|
const ctx = canvas.getContext('2d');
|
||||||
50,
|
ctx.drawImage(tempBandIcon, layout.logoX, layout.logoY);
|
||||||
);
|
ctx.drawImage(tempBandRankText, layout.textX, layout.textY);
|
||||||
bandAndContentList.push(canvas);
|
bandAndContentList.push(canvas);
|
||||||
}
|
}
|
||||||
|
const frameSpec = createBandDetailListFrameSpec(bandAndContentList?.[0]);
|
||||||
const bandAndContentListImage = drawList({
|
const bandAndContentListImage = drawList({
|
||||||
key,
|
key,
|
||||||
content: bandAndContentList,
|
content: bandAndContentList,
|
||||||
spacing: 0,
|
spacing: frameSpec.spacing,
|
||||||
lineHeight: bandAndContentList?.[0].height,
|
lineHeight: frameSpec.lineHeight,
|
||||||
textSize: bandAndContentList?.[0].height,
|
textSize: frameSpec.textSize,
|
||||||
});
|
});
|
||||||
return bandAndContentListImage;
|
return bandAndContentListImage;
|
||||||
}
|
}
|
||||||
@ -145,27 +152,24 @@ export async function drawPlayerDeckTotalRatingInList(
|
|||||||
const rankName = userDeckTotalRatingMap[i].rank;
|
const rankName = userDeckTotalRatingMap[i].rank;
|
||||||
let rankId = BANGDREAM_DECK_TOTAL_RATING_ID[rankName];
|
let rankId = BANGDREAM_DECK_TOTAL_RATING_ID[rankName];
|
||||||
const rankImage = await loadRankImage(`rank_${rankId}`);
|
const rankImage = await loadRankImage(`rank_${rankId}`);
|
||||||
const widthMax = 150,
|
const canvasSpec = createDeckRankCanvasSpec();
|
||||||
heightMax = 100;
|
const canvas = new Canvas(canvasSpec.width, canvasSpec.height);
|
||||||
const canvas = new Canvas(widthMax, heightMax);
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(rankImage, (widthMax - rankImage.width) / 2, 0);
|
const rankLayout = createDeckRankImageLayout(rankImage);
|
||||||
|
ctx.drawImage(rankImage, rankLayout.rankX, rankLayout.rankY);
|
||||||
if (userDeckTotalRatingMap[i].level != 0) {
|
if (userDeckTotalRatingMap[i].level != 0) {
|
||||||
//ss与s字体相同
|
rankId = normalizeDeckRankLevelSpriteRankId(rankId);
|
||||||
if (rankId > 4) {
|
|
||||||
rankId = 4;
|
|
||||||
}
|
|
||||||
const rankLevelImage = resizeImage({
|
const rankLevelImage = resizeImage({
|
||||||
image: await loadRankImage(
|
image: await loadRankImage(
|
||||||
`rank_${rankId}_${userDeckTotalRatingMap[i].level}`,
|
`rank_${rankId}_${userDeckTotalRatingMap[i].level}`,
|
||||||
),
|
),
|
||||||
heightMax: 50,
|
...createDeckRankLevelImageSpec(),
|
||||||
});
|
});
|
||||||
ctx.drawImage(
|
const levelLayout = createDeckRankImageLayout(
|
||||||
|
rankImage,
|
||||||
rankLevelImage,
|
rankLevelImage,
|
||||||
(widthMax + rankImage.width) / 2 + 2 - rankLevelImage.width,
|
|
||||||
45,
|
|
||||||
);
|
);
|
||||||
|
ctx.drawImage(rankLevelImage, levelLayout.levelX, levelLayout.levelY);
|
||||||
}
|
}
|
||||||
BandDetails[i] = [canvas];
|
BandDetails[i] = [canvas];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
BANGDREAM_BAND_DETAIL_LIST_SPEC,
|
||||||
|
createBandDetailItemLayout,
|
||||||
|
createBandDetailListFrameSpec,
|
||||||
|
createBandDetailLogoSpec,
|
||||||
|
createBandDetailTextSpec,
|
||||||
|
createDeckRankCanvasSpec,
|
||||||
|
createDeckRankImageLayout,
|
||||||
|
createDeckRankLevelImageSpec,
|
||||||
|
normalizeDeckRankLevelSpriteRankId,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-band-detail-spec';
|
||||||
|
|
||||||
|
describe('BangDream band detail list spec', () => {
|
||||||
|
it('keeps band detail item constants stable', () => {
|
||||||
|
expect(BANGDREAM_BAND_DETAIL_LIST_SPEC.item).toEqual({
|
||||||
|
height: 100,
|
||||||
|
logoWidth: 110,
|
||||||
|
textLineHeight: 40,
|
||||||
|
textOffsetY: 50,
|
||||||
|
width: 152,
|
||||||
|
});
|
||||||
|
expect(BANGDREAM_BAND_DETAIL_LIST_SPEC.list.spacing).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates logo and text drawing specs', () => {
|
||||||
|
expect(createBandDetailLogoSpec()).toEqual({ widthMax: 110 });
|
||||||
|
expect(createBandDetailTextSpec()).toEqual({
|
||||||
|
lineHeight: 40,
|
||||||
|
maxWidth: 152,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps band detail item layout stable', () => {
|
||||||
|
expect(createBandDetailItemLayout({ height: 36, width: 48 })).toEqual({
|
||||||
|
canvasHeight: 100,
|
||||||
|
canvasWidth: 152,
|
||||||
|
logoX: 21,
|
||||||
|
logoY: 0,
|
||||||
|
textX: 52,
|
||||||
|
textY: 50,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps list frame values derived from the first item', () => {
|
||||||
|
expect(createBandDetailListFrameSpec({ height: 100, width: 152 })).toEqual({
|
||||||
|
lineHeight: 100,
|
||||||
|
spacing: 0,
|
||||||
|
textSize: 100,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps deck rank canvas and image layout stable', () => {
|
||||||
|
expect(createDeckRankCanvasSpec()).toEqual({ height: 100, width: 150 });
|
||||||
|
expect(createDeckRankLevelImageSpec()).toEqual({ heightMax: 50 });
|
||||||
|
expect(createDeckRankImageLayout({ height: 32, width: 84 })).toEqual({
|
||||||
|
rankX: 33,
|
||||||
|
rankY: 0,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
createDeckRankImageLayout(
|
||||||
|
{ height: 32, width: 84 },
|
||||||
|
{ height: 20, width: 30 },
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
levelX: 89,
|
||||||
|
levelY: 45,
|
||||||
|
rankX: 33,
|
||||||
|
rankY: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps deck rank level sprite rank id cap stable', () => {
|
||||||
|
expect(normalizeDeckRankLevelSpriteRankId(3)).toBe(3);
|
||||||
|
expect(normalizeDeckRankLevelSpriteRankId(5)).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user