refactor: 收口 BangDream 详情区块规格
This commit is contained in:
parent
319577cffa
commit
08627646e5
@ -14,7 +14,7 @@
|
|||||||
## 当前事实
|
## 当前事实
|
||||||
|
|
||||||
- Tsugu 源码目录:`src/qqbot/plugins/bangDream/tsugu`
|
- Tsugu 源码目录:`src/qqbot/plugins/bangDream/tsugu`
|
||||||
- TS 文件:初始基线 92;当前 `tsugu` 源码 125
|
- TS 文件:初始基线 92;当前 `tsugu` 源码 126
|
||||||
- 函数节点:481,其中稳定函数 410,匿名/内联回调 71
|
- 函数节点:481,其中稳定函数 410,匿名/内联回调 71
|
||||||
- 源码 JSDoc:稳定函数 410/410 已覆盖
|
- 源码 JSDoc:稳定函数 410/410 已覆盖
|
||||||
- 变量声明:1896
|
- 变量声明:1896
|
||||||
@ -354,6 +354,8 @@ export interface TsuguHook {
|
|||||||
- 已新增 `song-chart-preview-spec.spec.ts`,覆盖 BPM 变速时间、十六分单点、双押、滑条 bar/tick/end、布局列数和音符分类;本地生成 `song-chart-preview-spec-136-expert.jpg`,验证谱面预览重构后图片输出非空且视觉结构正常。
|
- 已新增 `song-chart-preview-spec.spec.ts`,覆盖 BPM 变速时间、十六分单点、双押、滑条 bar/tick/end、布局列数和音符分类;本地生成 `song-chart-preview-spec-136-expert.jpg`,验证谱面预览重构后图片输出非空且视觉结构正常。
|
||||||
- 已新增 `render-blocks/card-art-spec.ts`,收口卡牌图标/插画边框 URL 生成、icon/illustration 画布尺寸、属性/乐队/星级/突破/技能等级坐标;`card-art.ts` 改为消费 spec 和 URL factory,下载与绘制顺序保持不变。
|
- 已新增 `render-blocks/card-art-spec.ts`,收口卡牌图标/插画边框 URL 生成、icon/illustration 画布尺寸、属性/乐队/星级/突破/技能等级坐标;`card-art.ts` 改为消费 spec 和 URL factory,下载与绘制顺序保持不变。
|
||||||
- 已新增 `card-art-spec.spec.ts`,覆盖 rarity=1 属性边框、其他稀有度边框和关键尺寸;本地生成 `card-art-spec-card-472.jpg`,验证卡牌详情图在规格收口后仍能正常输出。
|
- 已新增 `card-art-spec.spec.ts`,覆盖 rarity=1 属性边框、其他稀有度边框和关键尺寸;本地生成 `card-art-spec-card-472.jpg`,验证卡牌详情图在规格收口后仍能正常输出。
|
||||||
|
- 已新增 `render-blocks/detail-block-spec.ts`,收口歌曲详情、歌曲 meta、角色半身块和玩家详情头图的尺寸、间距、字号与相对分数舍入规则;`detail-blocks.ts` 改为消费 spec,数据查询和绘制顺序保持不变。
|
||||||
|
- 已新增 `detail-block-spec.spec.ts`,覆盖歌曲详情尺寸、角色/玩家详情尺寸和 meta 相对百分比舍入;本地生成 `detail-spec-song-136.jpg`、`detail-spec-event-50.jpg`、`detail-spec-character-1.jpg`,验证详情区块规格收口后查曲/查活动/查角色图片输出正常。
|
||||||
|
|
||||||
### Phase 6:策略 policy 和时间/档线规则
|
### Phase 6:策略 policy 和时间/档线规则
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
export const BANGDREAM_DETAIL_BLOCK_SPEC = {
|
||||||
|
characterHalf: {
|
||||||
|
footerHeight: 100,
|
||||||
|
height: 800,
|
||||||
|
idTextSize: 30,
|
||||||
|
illustrationInsetY: 2,
|
||||||
|
illustrationPaddingHeight: 20,
|
||||||
|
nameTextSize: 40,
|
||||||
|
opaqueOpacity: 1,
|
||||||
|
overlayOpacity: 0.25,
|
||||||
|
radius: 20,
|
||||||
|
strokeWidth: 4,
|
||||||
|
width: 250,
|
||||||
|
},
|
||||||
|
metaList: {
|
||||||
|
maxRows: 50,
|
||||||
|
percentRoundScale: 100,
|
||||||
|
percentScale: 100,
|
||||||
|
},
|
||||||
|
playerDetail: {
|
||||||
|
dataBlockOpacity: 1,
|
||||||
|
dataBlockY: 900,
|
||||||
|
degreeGapWidth: 20,
|
||||||
|
illustrationHeight: 1000,
|
||||||
|
illustrationWidth: 1000,
|
||||||
|
infoTextSize: 35,
|
||||||
|
nameTextSize: 75,
|
||||||
|
spacerHeight: 25,
|
||||||
|
},
|
||||||
|
songDetail: {
|
||||||
|
detailSeparator: {
|
||||||
|
endX: 360,
|
||||||
|
height: 20,
|
||||||
|
width: 365,
|
||||||
|
},
|
||||||
|
detailTextSize: 30,
|
||||||
|
difficultyGap: 10,
|
||||||
|
difficultyHeight: 60,
|
||||||
|
difficultyX: 435,
|
||||||
|
horizontalGapWidth: 35,
|
||||||
|
jacketMaxWidth: 400,
|
||||||
|
metaSeparatorHeight: 10,
|
||||||
|
rightBottomGapHeight: 60,
|
||||||
|
textMaxWidth: 365,
|
||||||
|
titleTextSize: 40,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算歌曲 meta 相对百分比,保持历史两位小数舍入策略。
|
||||||
|
*
|
||||||
|
* @param meta - 当前歌曲 meta 值。
|
||||||
|
* @param maxMeta - 当前榜单最大 meta 值。
|
||||||
|
*/
|
||||||
|
export function getRelativeMetaPercent(meta: number, maxMeta: number): number {
|
||||||
|
const { percentRoundScale, percentScale } =
|
||||||
|
BANGDREAM_DETAIL_BLOCK_SPEC.metaList;
|
||||||
|
return (
|
||||||
|
Math.round((meta / maxMeta) * percentScale * percentRoundScale) /
|
||||||
|
percentRoundScale
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -49,18 +49,22 @@ import { Canvas, Image } from 'skia-canvas';
|
|||||||
import { Band } from '@/qqbot/plugins/bangDream/tsugu/models/band';
|
import { Band } from '@/qqbot/plugins/bangDream/tsugu/models/band';
|
||||||
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||||
import { createHorizontalSeparatorSpec } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
import { createHorizontalSeparatorSpec } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
||||||
|
import {
|
||||||
|
BANGDREAM_DETAIL_BLOCK_SPEC,
|
||||||
|
getRelativeMetaPercent,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/detail-block-spec';
|
||||||
|
|
||||||
const songDetailSeparator = drawDottedLine(
|
const songDetailSeparator = drawDottedLine(
|
||||||
createHorizontalSeparatorSpec({
|
createHorizontalSeparatorSpec({
|
||||||
width: 365,
|
width: BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.detailSeparator.width,
|
||||||
height: 20,
|
height: BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.detailSeparator.height,
|
||||||
endX: 360,
|
endX: BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.detailSeparator.endX,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const songMetaSeparator = drawDottedLine(
|
const songMetaSeparator = drawDottedLine(
|
||||||
createHorizontalSeparatorSpec({
|
createHorizontalSeparatorSpec({
|
||||||
height: 10,
|
height: BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.metaSeparatorHeight,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -174,18 +178,19 @@ export async function drawSongDataBlock(
|
|||||||
text?: string,
|
text?: string,
|
||||||
displayedServerList: Server[] = globalDefaultServer,
|
displayedServerList: Server[] = globalDefaultServer,
|
||||||
) {
|
) {
|
||||||
|
const spec = BANGDREAM_DETAIL_BLOCK_SPEC.songDetail;
|
||||||
const server = getServerByPriority(song.publishedAt, displayedServerList);
|
const server = getServerByPriority(song.publishedAt, displayedServerList);
|
||||||
const songJacketCanvas = resizeImage({
|
const songJacketCanvas = resizeImage({
|
||||||
image: await song.getSongJacketImage(),
|
image: await song.getSongJacketImage(),
|
||||||
widthMax: 400,
|
widthMax: spec.jacketMaxWidth,
|
||||||
});
|
});
|
||||||
const songName = song.musicTitle[server];
|
const songName = song.musicTitle[server];
|
||||||
const bandName = new Band(song.bandId).bandName[server];
|
const bandName = new Band(song.bandId).bandName[server];
|
||||||
const songTipsName = song.getTagName();
|
const songTipsName = song.getTagName();
|
||||||
const songNameImage = drawText({
|
const songNameImage = drawText({
|
||||||
text: songName,
|
text: songName,
|
||||||
textSize: 40,
|
textSize: spec.titleTextSize,
|
||||||
maxWidth: 365,
|
maxWidth: spec.textMaxWidth,
|
||||||
});
|
});
|
||||||
let songDetail = `${bandName}\n${songTipsName}\nID:${song.songId}`;
|
let songDetail = `${bandName}\n${songTipsName}\nID:${song.songId}`;
|
||||||
if (text != undefined) {
|
if (text != undefined) {
|
||||||
@ -193,23 +198,31 @@ export async function drawSongDataBlock(
|
|||||||
}
|
}
|
||||||
const songDetailImage = drawText({
|
const songDetailImage = drawText({
|
||||||
text: songDetail,
|
text: songDetail,
|
||||||
textSize: 30,
|
textSize: spec.detailTextSize,
|
||||||
maxWidth: 365,
|
maxWidth: spec.textMaxWidth,
|
||||||
});
|
});
|
||||||
const difficultyImage = drawDifficultyList(song, 60, 10);
|
const difficultyImage = drawDifficultyList(
|
||||||
|
song,
|
||||||
|
spec.difficultyHeight,
|
||||||
|
spec.difficultyGap,
|
||||||
|
);
|
||||||
const rightCanvas = stackImage([
|
const rightCanvas = stackImage([
|
||||||
songNameImage,
|
songNameImage,
|
||||||
songDetailSeparator,
|
songDetailSeparator,
|
||||||
songDetailImage,
|
songDetailImage,
|
||||||
new Canvas(1, 60),
|
new Canvas(1, spec.rightBottomGapHeight),
|
||||||
]);
|
]);
|
||||||
const canvas = stackImageHorizontal([
|
const canvas = stackImageHorizontal([
|
||||||
songJacketCanvas,
|
songJacketCanvas,
|
||||||
new Canvas(35, 1),
|
new Canvas(spec.horizontalGapWidth, 1),
|
||||||
rightCanvas,
|
rightCanvas,
|
||||||
]);
|
]);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(difficultyImage, 435, canvas.height - difficultyImage.height);
|
ctx.drawImage(
|
||||||
|
difficultyImage,
|
||||||
|
spec.difficultyX,
|
||||||
|
canvas.height - difficultyImage.height,
|
||||||
|
);
|
||||||
return drawDataBlock({ list: [canvas] });
|
return drawDataBlock({ list: [canvas] });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,9 +263,10 @@ export async function drawSongMetaListDataBlock(
|
|||||||
const tempSongMetaRanking = songMetaRanking[server].data;
|
const tempSongMetaRanking = songMetaRanking[server].data;
|
||||||
for (let j = 0; j < tempSongMetaRanking.length; j++) {
|
for (let j = 0; j < tempSongMetaRanking.length; j++) {
|
||||||
if (tempSongMetaRanking[j].difficulty == difficultyId) {
|
if (tempSongMetaRanking[j].difficulty == difficultyId) {
|
||||||
let percent =
|
const percent = getRelativeMetaPercent(
|
||||||
(tempSongMetaRanking[j].meta / metaRanking[server].maxMeta) * 100;
|
tempSongMetaRanking[j].meta,
|
||||||
percent = Math.round(percent * 100) / 100;
|
metaRanking[server].maxMeta,
|
||||||
|
);
|
||||||
text += `${serverNameFullList[server]}: ${percent}% #${tempSongMetaRanking[j].rank + 1} `;
|
text += `${serverNameFullList[server]}: ${percent}% #${tempSongMetaRanking[j].rank + 1} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,14 +294,14 @@ export async function drawMetaListDataBlock(
|
|||||||
const metaRanking = getMetaRanking(withFever, server);
|
const metaRanking = getMetaRanking(withFever, server);
|
||||||
const maxMeta = metaRanking[0].meta;
|
const maxMeta = metaRanking[0].meta;
|
||||||
const list: Array<Image | Canvas> = [];
|
const list: Array<Image | Canvas> = [];
|
||||||
const max = 50;
|
const maxRows = BANGDREAM_DETAIL_BLOCK_SPEC.metaList.maxRows;
|
||||||
for (let i = 0; i < max; i++) {
|
for (let i = 0; i < maxRows; i++) {
|
||||||
if (i >= metaRanking.length) {
|
if (i >= metaRanking.length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const song = new Song(metaRanking[i].songId);
|
const song = new Song(metaRanking[i].songId);
|
||||||
const difficultyId = metaRanking[i].difficulty;
|
const difficultyId = metaRanking[i].difficulty;
|
||||||
const percent = (metaRanking[i].meta / maxMeta) * 100;
|
const percent = getRelativeMetaPercent(metaRanking[i].meta, maxMeta);
|
||||||
list.push(
|
list.push(
|
||||||
await drawSongInList(
|
await drawSongInList(
|
||||||
song,
|
song,
|
||||||
@ -312,8 +326,9 @@ export async function drawCharacterHalfBlock(
|
|||||||
character: Character,
|
character: Character,
|
||||||
displayedServerList: Server[] = globalDefaultServer,
|
displayedServerList: Server[] = globalDefaultServer,
|
||||||
): Promise<Canvas> {
|
): Promise<Canvas> {
|
||||||
const width = 250;
|
const spec = BANGDREAM_DETAIL_BLOCK_SPEC.characterHalf;
|
||||||
const height = 800;
|
const width = spec.width;
|
||||||
|
const height = spec.height;
|
||||||
const canvas = new Canvas(width, height);
|
const canvas = new Canvas(width, height);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
await character.initFull(false);
|
await character.initFull(false);
|
||||||
@ -324,32 +339,32 @@ export async function drawCharacterHalfBlock(
|
|||||||
drawRoundedRect({
|
drawRoundedRect({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
radius: 20,
|
radius: spec.radius,
|
||||||
color,
|
color,
|
||||||
opacity: 0.25,
|
opacity: spec.overlayOpacity,
|
||||||
}),
|
}),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const characterIllustration = resizeImage({
|
const characterIllustration = resizeImage({
|
||||||
image: await character.getIllustration(),
|
image: await character.getIllustration(),
|
||||||
heightMax: height - 20,
|
heightMax: height - spec.illustrationPaddingHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
characterIllustration,
|
characterIllustration,
|
||||||
width / 2 - characterIllustration.width / 2,
|
width / 2 - characterIllustration.width / 2,
|
||||||
2,
|
spec.illustrationInsetY,
|
||||||
);
|
);
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
drawRoundedRect({
|
drawRoundedRect({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
radius: 20,
|
radius: spec.radius,
|
||||||
opacity: 1,
|
opacity: spec.opaqueOpacity,
|
||||||
color: color + '00',
|
color: color + '00',
|
||||||
strokeColor: color,
|
strokeColor: color,
|
||||||
strokeWidth: 4,
|
strokeWidth: spec.strokeWidth,
|
||||||
}),
|
}),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -357,13 +372,13 @@ export async function drawCharacterHalfBlock(
|
|||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
drawRoundedRect({
|
drawRoundedRect({
|
||||||
width,
|
width,
|
||||||
height: 100,
|
height: spec.footerHeight,
|
||||||
radius: 20,
|
radius: spec.radius,
|
||||||
opacity: 1,
|
opacity: spec.opaqueOpacity,
|
||||||
color,
|
color,
|
||||||
}),
|
}),
|
||||||
0,
|
0,
|
||||||
height - 100,
|
height - spec.footerHeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
const list: Canvas[] = [];
|
const list: Canvas[] = [];
|
||||||
@ -373,19 +388,19 @@ export async function drawCharacterHalfBlock(
|
|||||||
);
|
);
|
||||||
const nameTextImage = drawText({
|
const nameTextImage = drawText({
|
||||||
text: character.characterName[server],
|
text: character.characterName[server],
|
||||||
textSize: 40,
|
textSize: spec.nameTextSize,
|
||||||
color: BANGDREAM_RENDER_THEME.color.surface,
|
color: BANGDREAM_RENDER_THEME.color.surface,
|
||||||
maxWidth: width,
|
maxWidth: width,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([nameTextImage], width));
|
list.push(drawImageListCenter([nameTextImage], width));
|
||||||
const idTextImage = drawText({
|
const idTextImage = drawText({
|
||||||
text: `ID: ${character.characterId}`,
|
text: `ID: ${character.characterId}`,
|
||||||
textSize: 30,
|
textSize: spec.idTextSize,
|
||||||
color: BANGDREAM_RENDER_THEME.color.surface,
|
color: BANGDREAM_RENDER_THEME.color.surface,
|
||||||
maxWidth: width,
|
maxWidth: width,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([idTextImage], width));
|
list.push(drawImageListCenter([idTextImage], width));
|
||||||
ctx.drawImage(stackImage(list), 0, height - 100);
|
ctx.drawImage(stackImage(list), 0, height - spec.footerHeight);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,20 +413,21 @@ export async function drawCharacterHalfBlock(
|
|||||||
export async function drawPlayerDetailBlockWithIllustration(
|
export async function drawPlayerDetailBlockWithIllustration(
|
||||||
player: Player,
|
player: Player,
|
||||||
): Promise<Canvas> {
|
): Promise<Canvas> {
|
||||||
|
const spec = BANGDREAM_DETAIL_BLOCK_SPEC.playerDetail;
|
||||||
const list: Array<Canvas | Image> = [];
|
const list: Array<Canvas | Image> = [];
|
||||||
const playerText = drawText({
|
const playerText = drawText({
|
||||||
text: player.profile.userName,
|
text: player.profile.userName,
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
textSize: 75,
|
textSize: spec.nameTextSize,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([playerText]));
|
list.push(drawImageListCenter([playerText]));
|
||||||
const levelText = drawText({
|
const levelText = drawText({
|
||||||
text: `等级 ${player.profile.rank}`,
|
text: `等级 ${player.profile.rank}`,
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
textSize: 35,
|
textSize: spec.infoTextSize,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([levelText]));
|
list.push(drawImageListCenter([levelText]));
|
||||||
list.push(new Canvas(1, 25));
|
list.push(new Canvas(1, spec.spacerHeight));
|
||||||
|
|
||||||
const degreeImageList: Array<Canvas | Image> = [];
|
const degreeImageList: Array<Canvas | Image> = [];
|
||||||
const userProfileDegreeMap = player.profile.userProfileDegreeMap.entries;
|
const userProfileDegreeMap = player.profile.userProfileDegreeMap.entries;
|
||||||
@ -420,19 +436,19 @@ export async function drawPlayerDetailBlockWithIllustration(
|
|||||||
degreeImageList.push(
|
degreeImageList.push(
|
||||||
await drawDegree(new Degree(tempDegree.degreeId), player.server),
|
await drawDegree(new Degree(tempDegree.degreeId), player.server),
|
||||||
);
|
);
|
||||||
degreeImageList.push(new Canvas(20, 1));
|
degreeImageList.push(new Canvas(spec.degreeGapWidth, 1));
|
||||||
}
|
}
|
||||||
degreeImageList.pop();
|
degreeImageList.pop();
|
||||||
list.push(drawImageListCenter(degreeImageList));
|
list.push(drawImageListCenter(degreeImageList));
|
||||||
list.push(new Canvas(1, 25));
|
list.push(new Canvas(1, spec.spacerHeight));
|
||||||
|
|
||||||
const introductionText = drawText({
|
const introductionText = drawText({
|
||||||
text: player.profile.introduction,
|
text: player.profile.introduction,
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
textSize: 35,
|
textSize: spec.infoTextSize,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([introductionText]));
|
list.push(drawImageListCenter([introductionText]));
|
||||||
list.push(new Canvas(1, 25));
|
list.push(new Canvas(1, spec.spacerHeight));
|
||||||
|
|
||||||
const userId = player.profile.publishUserIdFlg
|
const userId = player.profile.publishUserIdFlg
|
||||||
? player.profile.userId.toString()
|
? player.profile.userId.toString()
|
||||||
@ -440,10 +456,10 @@ export async function drawPlayerDetailBlockWithIllustration(
|
|||||||
const idText = drawTextWithImages({
|
const idText = drawTextWithImages({
|
||||||
content: [await getIcon(player.server), userId],
|
content: [await getIcon(player.server), userId],
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
textSize: 35,
|
textSize: spec.infoTextSize,
|
||||||
});
|
});
|
||||||
list.push(drawImageListCenter([idText]));
|
list.push(drawImageListCenter([idText]));
|
||||||
const dataBlock = drawDataBlock({ list, opacity: 1 });
|
const dataBlock = drawDataBlock({ list, opacity: spec.dataBlockOpacity });
|
||||||
|
|
||||||
const userIllustrationData = player.profile.userIllustration;
|
const userIllustrationData = player.profile.userIllustration;
|
||||||
const illustrationCard = new Card(userIllustrationData.cardId);
|
const illustrationCard = new Card(userIllustrationData.cardId);
|
||||||
@ -451,10 +467,19 @@ export async function drawPlayerDetailBlockWithIllustration(
|
|||||||
userIllustrationData.trainingStatus,
|
userIllustrationData.trainingStatus,
|
||||||
);
|
);
|
||||||
const titleImage = drawTitle('查询', '玩家信息');
|
const titleImage = drawTitle('查询', '玩家信息');
|
||||||
const canvas = new Canvas(1000, 900 + dataBlock.height);
|
const canvas = new Canvas(
|
||||||
|
spec.illustrationWidth,
|
||||||
|
spec.dataBlockY + dataBlock.height,
|
||||||
|
);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(illustrationImage, 0, 0, 1000, 1000);
|
ctx.drawImage(
|
||||||
|
illustrationImage,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
spec.illustrationWidth,
|
||||||
|
spec.illustrationHeight,
|
||||||
|
);
|
||||||
ctx.drawImage(titleImage, 0, 0);
|
ctx.drawImage(titleImage, 0, 0);
|
||||||
ctx.drawImage(dataBlock, 0, 900);
|
ctx.drawImage(dataBlock, 0, spec.dataBlockY);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|||||||
29
test/qqbot/plugins/bangDream/tsugu/detail-block-spec.spec.ts
Normal file
29
test/qqbot/plugins/bangDream/tsugu/detail-block-spec.spec.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
BANGDREAM_DETAIL_BLOCK_SPEC,
|
||||||
|
getRelativeMetaPercent,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/detail-block-spec';
|
||||||
|
|
||||||
|
describe('BangDream detail block spec', () => {
|
||||||
|
it('keeps song detail dimensions stable', () => {
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.jacketMaxWidth).toBe(400);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.textMaxWidth).toBe(365);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.difficultyX).toBe(435);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.songDetail.detailSeparator.endX).toBe(
|
||||||
|
360,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps character and player detail dimensions stable', () => {
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.characterHalf.width).toBe(250);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.characterHalf.height).toBe(800);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.playerDetail.illustrationWidth).toBe(
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
expect(BANGDREAM_DETAIL_BLOCK_SPEC.playerDetail.dataBlockY).toBe(900);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds relative meta percent with the historical precision', () => {
|
||||||
|
expect(getRelativeMetaPercent(333, 400)).toBe(83.25);
|
||||||
|
expect(getRelativeMetaPercent(1, 3)).toBe(33.33);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user