refactor: 收口 BangDream 玩家卡牌列表规格
This commit is contained in:
parent
68b55764c2
commit
322938ae6f
@ -385,6 +385,7 @@ export interface TsuguHook {
|
|||||||
- 已新增 `render-blocks/event-stage-spec.ts`,收口试炼列表最大列高、歌曲单元格/封面/ID/难度条尺寸、试炼类型顶部文字规格和换列判断;`event-stage.ts` 与 `list-event-stage.ts` 改为消费 spec,活动/歌曲选择和输出结构保持不变。
|
- 已新增 `render-blocks/event-stage-spec.ts`,收口试炼列表最大列高、歌曲单元格/封面/ID/难度条尺寸、试炼类型顶部文字规格和换列判断;`event-stage.ts` 与 `list-event-stage.ts` 改为消费 spec,活动/歌曲选择和输出结构保持不变。
|
||||||
- 线上 smoke 暴露 `/查试炼 310 -m` 将所有列横向拼成一张巨大 canvas 会触发 Pod `OOMKilled exit=137`;已改为按小批次绘制 stage、边分列边输出多张 CQ 图片,不再创建最终横向巨图,普通版和 meta 版均拆成 5 张输出,同时避免完全串行导致冷缓存首条命令耗时过长。
|
- 线上 smoke 暴露 `/查试炼 310 -m` 将所有列横向拼成一张巨大 canvas 会触发 Pod `OOMKilled exit=137`;已改为按小批次绘制 stage、边分列边输出多张 CQ 图片,不再创建最终横向巨图,普通版和 meta 版均拆成 5 张输出,同时避免完全串行导致冷缓存首条命令耗时过长。
|
||||||
- 已新增 `event-stage-spec.spec.ts`,覆盖试炼歌曲行尺寸、类型顶部文字规格、换列判断和按列高拆分算法;本地生成 `event-stage-split-310*.jpg`、`event-stage-split-310-meta*.jpg`,验证 `/查试炼 310` 与 `/查试炼 310 -m` 图片输出正常。
|
- 已新增 `event-stage-spec.spec.ts`,覆盖试炼歌曲行尺寸、类型顶部文字规格、换列判断和按列高拆分算法;本地生成 `event-stage-split-310*.jpg`、`event-stage-split-310-meta*.jpg`,验证 `/查试炼 310` 与 `/查试炼 310 -m` 图片输出正常。
|
||||||
|
- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec,主卡组渲染顺序和输出结构保持不变;`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 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 和时间/档线规则
|
||||||
|
|||||||
@ -3,6 +3,12 @@ import { Player } from '@/qqbot/plugins/bangDream/tsugu/models/player';
|
|||||||
import { Canvas } from 'skia-canvas';
|
import { Canvas } from 'skia-canvas';
|
||||||
import { drawCardIcon } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art';
|
import { drawCardIcon } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/card-art';
|
||||||
import { drawList } from './list-frame';
|
import { drawList } from './list-frame';
|
||||||
|
import {
|
||||||
|
BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC,
|
||||||
|
getPlayerCardIconListSpacing,
|
||||||
|
getPlayerCardIconListTextSize,
|
||||||
|
sortPlayerMainDeckEntries,
|
||||||
|
} from './list-player-card-icon-spec';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在图片布局层中绘制玩家卡牌In列表。
|
* 在图片布局层中绘制玩家卡牌In列表。
|
||||||
@ -17,19 +23,14 @@ export async function drawPlayerCardInList(
|
|||||||
player: Player,
|
player: Player,
|
||||||
key?: string,
|
key?: string,
|
||||||
cardIdVisible = false,
|
cardIdVisible = false,
|
||||||
lineHeight = 184,
|
lineHeight = BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.defaultLineHeight,
|
||||||
): Promise<Canvas> {
|
): Promise<Canvas> {
|
||||||
const textSize = (lineHeight / 200) * 180;
|
const textSize = getPlayerCardIconListTextSize(lineHeight);
|
||||||
const spacing = (lineHeight / 200) * 13;
|
const spacing = getPlayerCardIconListSpacing(lineHeight);
|
||||||
const promiseList: Promise<Canvas>[] = [];
|
const promiseList: Promise<Canvas>[] = [];
|
||||||
const tempCardDataList = player.profile.mainDeckUserSituations.entries;
|
const cardDataList = sortPlayerMainDeckEntries(
|
||||||
//将tempCardDataList调整顺序,为3,1,0,2,4
|
player.profile.mainDeckUserSituations.entries,
|
||||||
const defaultCardSort = [3, 1, 0, 2, 4];
|
);
|
||||||
const cardDataList = [];
|
|
||||||
for (let i = 0; i < defaultCardSort.length; i++) {
|
|
||||||
const tempCardData = tempCardDataList[defaultCardSort[i]];
|
|
||||||
cardDataList.push(tempCardData);
|
|
||||||
}
|
|
||||||
const cardIconList: Array<Canvas> = [];
|
const cardIconList: Array<Canvas> = [];
|
||||||
for (const i in cardDataList) {
|
for (const i in cardDataList) {
|
||||||
const tempCardData = cardDataList[i];
|
const tempCardData = cardDataList[i];
|
||||||
@ -40,8 +41,9 @@ export async function drawPlayerCardInList(
|
|||||||
illustrationTrainingStatus: tempCardData.illust == 'after_training',
|
illustrationTrainingStatus: tempCardData.illust == 'after_training',
|
||||||
limitBreakRank: tempCardData.limitBreakRank,
|
limitBreakRank: tempCardData.limitBreakRank,
|
||||||
cardIdVisible: cardIdVisible,
|
cardIdVisible: cardIdVisible,
|
||||||
skillTypeVisible: true,
|
skillTypeVisible:
|
||||||
cardTypeVisible: false,
|
BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.showSkillType,
|
||||||
|
cardTypeVisible: BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.showCardType,
|
||||||
skillLevel: tempCardData.skillLevel,
|
skillLevel: tempCardData.skillLevel,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
export const BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC = {
|
||||||
|
card: {
|
||||||
|
defaultOrder: [3, 1, 0, 2, 4],
|
||||||
|
showCardType: false,
|
||||||
|
showSkillType: true,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
defaultLineHeight: 184,
|
||||||
|
spacingRatio: 13 / 200,
|
||||||
|
textSizeRatio: 180 / 200,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算玩家卡牌列表文本字号。
|
||||||
|
*
|
||||||
|
* @param lineHeight - 列表行高。
|
||||||
|
*/
|
||||||
|
export function getPlayerCardIconListTextSize(lineHeight: number) {
|
||||||
|
return lineHeight * BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.textSizeRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算玩家卡牌列表卡牌间距。
|
||||||
|
*
|
||||||
|
* @param lineHeight - 列表行高。
|
||||||
|
*/
|
||||||
|
export function getPlayerCardIconListSpacing(lineHeight: number) {
|
||||||
|
return lineHeight * BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.spacingRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按历史展示顺序排列玩家主卡组。
|
||||||
|
*
|
||||||
|
* @param entries - 玩家主卡组条目。
|
||||||
|
*/
|
||||||
|
export function sortPlayerMainDeckEntries<T>(entries: T[]): T[] {
|
||||||
|
return BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.card.defaultOrder
|
||||||
|
.map((index) => entries[index])
|
||||||
|
.filter((entry): entry is T => entry !== undefined);
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC,
|
||||||
|
getPlayerCardIconListSpacing,
|
||||||
|
getPlayerCardIconListTextSize,
|
||||||
|
sortPlayerMainDeckEntries,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-player-card-icon-spec';
|
||||||
|
|
||||||
|
describe('BangDream player card icon list spec', () => {
|
||||||
|
it('keeps player card list text and spacing ratios stable', () => {
|
||||||
|
const lineHeight =
|
||||||
|
BANGDREAM_PLAYER_CARD_ICON_LIST_SPEC.list.defaultLineHeight;
|
||||||
|
|
||||||
|
expect(lineHeight).toBe(184);
|
||||||
|
expect(getPlayerCardIconListTextSize(lineHeight)).toBe(165.6);
|
||||||
|
expect(getPlayerCardIconListSpacing(lineHeight)).toBe(11.96);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps historical player deck display order', () => {
|
||||||
|
expect(sortPlayerMainDeckEntries(['0', '1', '2', '3', '4'])).toEqual([
|
||||||
|
'3',
|
||||||
|
'1',
|
||||||
|
'0',
|
||||||
|
'2',
|
||||||
|
'4',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips missing deck entries without changing the remaining order', () => {
|
||||||
|
expect(sortPlayerMainDeckEntries(['0', '1', '2'])).toEqual([
|
||||||
|
'1',
|
||||||
|
'0',
|
||||||
|
'2',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user