refactor: 收口 BangDream 通用卡牌列表规格
This commit is contained in:
parent
322938ae6f
commit
e1ace51d6e
@ -386,6 +386,7 @@ export interface TsuguHook {
|
|||||||
- 线上 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 输出非空。
|
- 已新增 `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 输出非空。
|
||||||
|
- 已新增 `render-blocks/list-card-icon-spec.ts`,收口通用卡牌图标列表默认行高、文本字号比例、卡牌间距比例、默认可见性标记和历史排序比较器;`list-card-icon-list.ts` 改为消费 spec,卡牌图标列表排序和输出结构保持不变;`list-card-icon-spec.spec.ts` 覆盖稀有度排序、优先卡牌类型排序、普通卡按 ID 排序和字号/间距计算,本地 `/查卡 472` 图片 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 和时间/档线规则
|
||||||
|
|||||||
@ -2,7 +2,12 @@ import { drawList } from './list-frame';
|
|||||||
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 { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card';
|
import { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card';
|
||||||
import { BANGDREAM_CARD_PRIORITY_TYPES } from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-constants';
|
import {
|
||||||
|
BANGDREAM_CARD_ICON_LIST_SPEC,
|
||||||
|
getCardIconListSpacing,
|
||||||
|
getCardIconListTextSize,
|
||||||
|
sortCardIconListCards,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-icon-spec';
|
||||||
|
|
||||||
interface CardIconInListOptions {
|
interface CardIconInListOptions {
|
||||||
key?: string;
|
key?: string;
|
||||||
@ -22,33 +27,15 @@ interface CardIconInListOptions {
|
|||||||
export async function drawCardListInList({
|
export async function drawCardListInList({
|
||||||
key,
|
key,
|
||||||
cardList,
|
cardList,
|
||||||
cardIdVisible = false,
|
cardIdVisible = BANGDREAM_CARD_ICON_LIST_SPEC.card.showCardId,
|
||||||
skillTypeVisible = true,
|
skillTypeVisible = BANGDREAM_CARD_ICON_LIST_SPEC.card.showSkillType,
|
||||||
cardTypeVisible = true,
|
cardTypeVisible = BANGDREAM_CARD_ICON_LIST_SPEC.card.showCardType,
|
||||||
trainingStatus,
|
trainingStatus,
|
||||||
lineHeight = 200,
|
lineHeight = BANGDREAM_CARD_ICON_LIST_SPEC.list.defaultLineHeight,
|
||||||
}: CardIconInListOptions) {
|
}: CardIconInListOptions) {
|
||||||
//cardList排序,稀有度高的在前面,其中cardId低的在前面
|
sortCardIconListCards(cardList);
|
||||||
const typeList: readonly string[] = BANGDREAM_CARD_PRIORITY_TYPES;
|
const textSize = getCardIconListTextSize(lineHeight);
|
||||||
cardList.sort((a, b) => {
|
const spacing = getCardIconListSpacing(lineHeight);
|
||||||
if (a.rarity == b.rarity) {
|
|
||||||
if (typeList.includes(a.type) && !typeList.includes(b.type)) {
|
|
||||||
return -1;
|
|
||||||
} else if (!typeList.includes(a.type) && typeList.includes(b.type)) {
|
|
||||||
return 1;
|
|
||||||
} else if (
|
|
||||||
typeList.indexOf(a.type) != -1 &&
|
|
||||||
typeList.indexOf(b.type) != -1
|
|
||||||
) {
|
|
||||||
return typeList.indexOf(a.type) - typeList.indexOf(b.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.cardId - b.cardId;
|
|
||||||
}
|
|
||||||
return b.rarity - a.rarity;
|
|
||||||
});
|
|
||||||
const textSize = (lineHeight / 200) * 180;
|
|
||||||
const spacing = (lineHeight / 200) * 13;
|
|
||||||
const list: Array<Canvas> = [];
|
const list: Array<Canvas> = [];
|
||||||
for (let i = 0; i < cardList.length; i++) {
|
for (let i = 0; i < cardList.length; i++) {
|
||||||
const element: Card = cardList[i];
|
const element: Card = cardList[i];
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { BANGDREAM_CARD_PRIORITY_TYPES } from '@/qqbot/plugins/bangDream/tsugu/runtime/runtime-options';
|
||||||
|
|
||||||
|
export interface CardIconListSortTarget {
|
||||||
|
cardId: number;
|
||||||
|
rarity: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BANGDREAM_CARD_ICON_LIST_SPEC = {
|
||||||
|
card: {
|
||||||
|
priorityTypes: BANGDREAM_CARD_PRIORITY_TYPES,
|
||||||
|
showCardId: false,
|
||||||
|
showCardType: true,
|
||||||
|
showSkillType: true,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
defaultLineHeight: 200,
|
||||||
|
spacingRatio: 13 / 200,
|
||||||
|
textSizeRatio: 180 / 200,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算卡牌图标列表文本字号。
|
||||||
|
*
|
||||||
|
* @param lineHeight - 列表行高。
|
||||||
|
*/
|
||||||
|
export function getCardIconListTextSize(lineHeight: number) {
|
||||||
|
return lineHeight * BANGDREAM_CARD_ICON_LIST_SPEC.list.textSizeRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算卡牌图标列表卡牌间距。
|
||||||
|
*
|
||||||
|
* @param lineHeight - 列表行高。
|
||||||
|
*/
|
||||||
|
export function getCardIconListSpacing(lineHeight: number) {
|
||||||
|
return lineHeight * BANGDREAM_CARD_ICON_LIST_SPEC.list.spacingRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按历史列表规则比较卡牌图标展示顺序。
|
||||||
|
*
|
||||||
|
* @param left - 左侧卡牌。
|
||||||
|
* @param right - 右侧卡牌。
|
||||||
|
*/
|
||||||
|
export function compareCardIconListCards(
|
||||||
|
left: CardIconListSortTarget,
|
||||||
|
right: CardIconListSortTarget,
|
||||||
|
) {
|
||||||
|
if (left.rarity !== right.rarity) {
|
||||||
|
return right.rarity - left.rarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priorityTypes = BANGDREAM_CARD_ICON_LIST_SPEC.card
|
||||||
|
.priorityTypes as readonly string[];
|
||||||
|
const leftTypeIndex = priorityTypes.indexOf(left.type);
|
||||||
|
const rightTypeIndex = priorityTypes.indexOf(right.type);
|
||||||
|
const leftHasPriority = leftTypeIndex !== -1;
|
||||||
|
const rightHasPriority = rightTypeIndex !== -1;
|
||||||
|
|
||||||
|
if (leftHasPriority && !rightHasPriority) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!leftHasPriority && rightHasPriority) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (leftHasPriority && rightHasPriority) {
|
||||||
|
return leftTypeIndex - rightTypeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.cardId - right.cardId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按卡牌图标列表展示规则原地排序。
|
||||||
|
*
|
||||||
|
* @param cards - 待排序卡牌。
|
||||||
|
*/
|
||||||
|
export function sortCardIconListCards<T extends CardIconListSortTarget>(
|
||||||
|
cards: T[],
|
||||||
|
): T[] {
|
||||||
|
return cards.sort(compareCardIconListCards);
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import { BangDreamCardType } from '@/qqbot/plugins/bangDream/tsugu/models/bangdream-protocol';
|
||||||
|
import {
|
||||||
|
BANGDREAM_CARD_ICON_LIST_SPEC,
|
||||||
|
compareCardIconListCards,
|
||||||
|
getCardIconListSpacing,
|
||||||
|
getCardIconListTextSize,
|
||||||
|
sortCardIconListCards,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-icon-spec';
|
||||||
|
|
||||||
|
describe('BangDream card icon list spec', () => {
|
||||||
|
it('keeps card icon list text and spacing ratios stable', () => {
|
||||||
|
const lineHeight = BANGDREAM_CARD_ICON_LIST_SPEC.list.defaultLineHeight;
|
||||||
|
|
||||||
|
expect(lineHeight).toBe(200);
|
||||||
|
expect(getCardIconListTextSize(lineHeight)).toBe(180);
|
||||||
|
expect(getCardIconListSpacing(lineHeight)).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts higher rarity cards before lower rarity cards', () => {
|
||||||
|
const cards = [
|
||||||
|
{ cardId: 1, rarity: 3, type: BangDreamCardType.permanent },
|
||||||
|
{ cardId: 2, rarity: 5, type: BangDreamCardType.permanent },
|
||||||
|
{ cardId: 3, rarity: 4, type: BangDreamCardType.permanent },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(sortCardIconListCards(cards).map((card) => card.cardId)).toEqual([
|
||||||
|
2, 3, 1,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps priority card types before normal cards at the same rarity', () => {
|
||||||
|
const cards = [
|
||||||
|
{ cardId: 1, rarity: 5, type: BangDreamCardType.permanent },
|
||||||
|
{ cardId: 2, rarity: 5, type: BangDreamCardType.limited },
|
||||||
|
{ cardId: 3, rarity: 5, type: BangDreamCardType.dreamfes },
|
||||||
|
{ cardId: 4, rarity: 5, type: BangDreamCardType.kirafes },
|
||||||
|
{ cardId: 5, rarity: 5, type: BangDreamCardType.birthday },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(sortCardIconListCards(cards).map((card) => card.cardId)).toEqual([
|
||||||
|
4, 3, 2, 5, 1,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts normal cards by card id when rarity is the same', () => {
|
||||||
|
const cards = [
|
||||||
|
{ cardId: 10, rarity: 4, type: BangDreamCardType.event },
|
||||||
|
{ cardId: 8, rarity: 4, type: BangDreamCardType.permanent },
|
||||||
|
{ cardId: 9, rarity: 4, type: BangDreamCardType.initial },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(sortCardIconListCards(cards).map((card) => card.cardId)).toEqual([
|
||||||
|
8, 9, 10,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exposes the same comparison result used by in-place sorting', () => {
|
||||||
|
const limited = {
|
||||||
|
cardId: 1,
|
||||||
|
rarity: 5,
|
||||||
|
type: BangDreamCardType.limited,
|
||||||
|
};
|
||||||
|
const permanent = {
|
||||||
|
cardId: 2,
|
||||||
|
rarity: 5,
|
||||||
|
type: BangDreamCardType.permanent,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(compareCardIconListCards(limited, permanent)).toBeLessThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user