feat: add collapsible member board effect summaries
Publish GitHub Pages output / publish (push) Successful in 46s
Publish GitHub Pages output / publish (push) Successful in 46s
This commit is contained in:
@@ -2,7 +2,7 @@ import { skillTargetBelongsTo } from './skill-condition.ts';
|
||||
import type { CooldownReduction } from './types.ts';
|
||||
import type { UnitStat } from './unit-power.ts';
|
||||
|
||||
type BoardEffect = { id?: string; type: string; value: number | null; targetId?: string | null; trigger?: string | null };
|
||||
type BoardEffect = { id?: string; description?: string; type: string; value: number | null; targetId?: string | null; trigger?: string | null };
|
||||
type BoardNode = { characterIds: string[]; effect: BoardEffect | null };
|
||||
type BoardPosition = { groupId: string; x: number; y: number };
|
||||
type BoardMember = { id: string; positionGroupId: string };
|
||||
@@ -125,6 +125,79 @@ function connectionMultipliers(
|
||||
return result;
|
||||
}
|
||||
|
||||
export type MemberBoardEffectSummary = {
|
||||
received: {
|
||||
flat: Record<UnitStat, number>;
|
||||
flatByArea: Record<'B' | 'G', Record<UnitStat, number>>;
|
||||
rate: Record<UnitStat, number>;
|
||||
activationRateUp: number;
|
||||
} | null;
|
||||
groups: {
|
||||
area: string;
|
||||
rows: { description: string; nodeCount: number; boosted: boolean }[];
|
||||
}[];
|
||||
};
|
||||
|
||||
/** Saved-board overview, without assuming a leader or a song. Conditional effects stay in the detail rows. */
|
||||
export function summarizeMemberBoardEffects(
|
||||
summary: BoardSummarySource,
|
||||
memberBoards: Readonly<Record<string, readonly string[]>>,
|
||||
member: { id: string; name: string },
|
||||
card: BoardEffectCard | null,
|
||||
connections?: BoardConnections | null
|
||||
): MemberBoardEffectSummary {
|
||||
const uniqueBoards = Object.fromEntries(Object.entries(memberBoards).map(([id, nodes]) => [id, [...new Set(nodes)]]));
|
||||
const effects = card ? calculateBoardEffects(summary, uniqueBoards, [card], '', null, connections) : null;
|
||||
const flatForArea = (area: 'B' | 'G'): Record<UnitStat, number> => {
|
||||
if (!card) return emptyStats();
|
||||
// Filter effects, not active nodes: connection sockets and duplicate-card handling must stay unchanged.
|
||||
const areaSummary = { ...summary, nodeGroups: Object.fromEntries(
|
||||
Object.entries(summary.nodeGroups).filter(([groupId]) => groupId.startsWith(area))
|
||||
) };
|
||||
return calculateBoardEffects(areaSummary, uniqueBoards, [card], '', null, connections).flatByCardId[card.id];
|
||||
};
|
||||
const multipliers = connectionMultipliers(summary, uniqueBoards, connections)[member.id] ?? {};
|
||||
const active = new Set(memberBoards[member.id] ?? []);
|
||||
const format = (value: number) => value.toLocaleString('ko-KR', { maximumFractionDigits: 3 });
|
||||
const groups = ['B', 'G', 'R', 'Y'].map((area) => {
|
||||
const rows = new Map<string, { template: string; value: number; nodeCount: number; boosted: boolean }>();
|
||||
for (const groupId of Object.keys(summary.nodeGroups)) {
|
||||
if (groupId[0] !== area || !active.has(groupId)) continue;
|
||||
const effect = nodeForMember(summary, groupId, member.id)?.effect;
|
||||
if (!effect?.description) continue;
|
||||
const template = effect.description.replaceAll('[character]', member.name).replace(/\s+/g, ' ').trim();
|
||||
const numeric = effect.value !== null && /\[value(?:\/10)?\]/.test(template);
|
||||
// Keep targets, conditions and non-additive skill unlocks separate.
|
||||
const key = JSON.stringify([effect.type, effect.targetId, effect.trigger, template, numeric ? null : effect.id ?? groupId]);
|
||||
const multiplier = numeric ? multipliers[groupId] ?? 1 : 1;
|
||||
const rawValue = (effect.value ?? 0) * multiplier;
|
||||
const value = affectedStats(effect.type).length > 0 ? Math.ceil(rawValue) : rawValue;
|
||||
const row = rows.get(key) ?? { template, value: 0, nodeCount: 0, boosted: false };
|
||||
row.value += value;
|
||||
row.nodeCount += 1;
|
||||
row.boosted ||= multiplier > 1;
|
||||
rows.set(key, row);
|
||||
}
|
||||
return {
|
||||
area,
|
||||
rows: [...rows.values()].map((row) => ({
|
||||
description: row.template.replaceAll('[value/10]', format(row.value / 10)).replaceAll('[value]', format(row.value)),
|
||||
nodeCount: row.nodeCount,
|
||||
boosted: row.boosted
|
||||
}))
|
||||
};
|
||||
});
|
||||
return {
|
||||
received: effects && card ? {
|
||||
flat: effects.flatByCardId[card.id],
|
||||
flatByArea: { B: flatForArea('B'), G: flatForArea('G') },
|
||||
rate: effects.rateByCardId[card.id],
|
||||
activationRateUp: effects.activationRateUpByCardId[card.id]
|
||||
} : null,
|
||||
groups
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateBoardEffects(
|
||||
summary: BoardSummarySource,
|
||||
memberBoards: Readonly<Record<string, readonly string[]>>,
|
||||
|
||||
Reference in New Issue
Block a user