137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
import type { CSSProperties } from 'react';
|
|
|
|
import { type RoleCombatStats } from '../data/attributeCombat';
|
|
import {
|
|
type BuildDamageBreakdown,
|
|
getBuildContributionQuality,
|
|
getBuildContributionQualityRatio,
|
|
} from '../data/buildDamage';
|
|
import { getResourceLabelsForWorld } from '../services/customWorldPresentation';
|
|
import type { Character } from '../types';
|
|
|
|
export function getGenderLabel(gender: Character['gender']) {
|
|
if (gender === 'female') return '女';
|
|
if (gender === 'male') return '男';
|
|
return '未明';
|
|
}
|
|
|
|
export function getCharacterDetailSpriteStyle(character: Character) {
|
|
const groundOffset = character.groundOffsetY ?? 22;
|
|
const translateY = Math.max(10, Math.round((groundOffset - 22) * 0.34));
|
|
|
|
return {
|
|
transform: `translateY(${translateY}px) scale(1.34)`,
|
|
transformOrigin: 'center bottom',
|
|
} satisfies CSSProperties;
|
|
}
|
|
|
|
const SKILL_STYLE_LABELS = {
|
|
burst: '爆发',
|
|
steady: '稳态',
|
|
mobility: '机动',
|
|
finisher: '终结',
|
|
projectile: '投射',
|
|
} satisfies Record<Character['skills'][number]['style'], string>;
|
|
|
|
export function getSkillDeliveryLabel(skill: Character['skills'][number]) {
|
|
return skill.delivery === 'ranged' || skill.style === 'projectile'
|
|
? '远程'
|
|
: '近战';
|
|
}
|
|
|
|
export function getSkillStyleLabel(skill: Character['skills'][number]) {
|
|
return SKILL_STYLE_LABELS[skill.style];
|
|
}
|
|
|
|
export function buildCharacterSkillRenderId(
|
|
skill: Character['skills'][number],
|
|
index: number,
|
|
) {
|
|
const normalizedId = skill.id.trim();
|
|
if (normalizedId) {
|
|
return normalizedId;
|
|
}
|
|
|
|
const fallbackSeed = skill.name.trim() || getSkillStyleLabel(skill) || 'skill';
|
|
return `skill-${fallbackSeed}-${index}`;
|
|
}
|
|
|
|
function getContributionHeatRatio(value: number) {
|
|
return getBuildContributionQualityRatio(value);
|
|
}
|
|
|
|
export function getContributionVisualStyle(value: number): CSSProperties {
|
|
const quality = getBuildContributionQuality(value);
|
|
if (quality.tier === 'epic') {
|
|
return {
|
|
borderColor: 'hsla(286, 68%, 66%, 0.46)',
|
|
background:
|
|
'linear-gradient(135deg, hsla(284, 72%, 44%, 0.34) 0%, hsla(265, 64%, 28%, 0.26) 42%, rgba(12, 16, 24, 0.94) 78%)',
|
|
boxShadow:
|
|
'inset 0 1px 0 rgba(255,255,255,0.05), 0 0 24px hsla(284, 78%, 62%, 0.22)',
|
|
color: 'rgb(247 236 255)',
|
|
};
|
|
}
|
|
|
|
const ratio = getContributionHeatRatio(value);
|
|
const hue = 210 - ratio * 178;
|
|
const saturation = 62 + ratio * 16;
|
|
const lightness = 56 + ratio * 6;
|
|
|
|
return {
|
|
borderColor: `hsla(${hue}, ${saturation + 6}%, ${lightness}%, ${0.22 + ratio * 0.28})`,
|
|
background: `linear-gradient(135deg, hsla(${hue}, ${saturation + 10}%, ${36 + ratio * 12}%, ${0.18 + ratio * 0.22}) 0%, rgba(12, 16, 24, 0.94) 72%)`,
|
|
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.04), 0 0 ${10 + ratio * 20}px hsla(${hue}, ${saturation + 14}%, ${58 + ratio * 8}%, ${0.12 + ratio * 0.18})`,
|
|
color:
|
|
ratio > 0.76
|
|
? 'rgb(255 244 235)'
|
|
: ratio > 0.32
|
|
? 'rgb(236 242 248)'
|
|
: 'rgb(203 213 225)',
|
|
};
|
|
}
|
|
|
|
export function formatAttributeMetricValue(value: number) {
|
|
const rounded = Math.round(value * 10) / 10;
|
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
}
|
|
|
|
function formatAttributePercentValue(value: number) {
|
|
return `${formatAttributeMetricValue(value * 100)}%`;
|
|
}
|
|
|
|
export function getAttributeBonusPillClassName(bonus: number) {
|
|
if (bonus >= 0.05) {
|
|
return 'border-amber-400/25 bg-amber-500/12 text-amber-100';
|
|
}
|
|
if (bonus > 0) {
|
|
return 'border-emerald-400/20 bg-emerald-500/10 text-emerald-100';
|
|
}
|
|
return 'border-white/10 bg-black/20 text-zinc-500';
|
|
}
|
|
|
|
export function getAttributeEffectText(
|
|
slotId: string,
|
|
combatStats: RoleCombatStats,
|
|
resourceLabels: ReturnType<typeof getResourceLabelsForWorld>,
|
|
) {
|
|
switch (slotId) {
|
|
case 'axis_a':
|
|
return `攻击倍率 x${formatAttributeMetricValue(combatStats.attackPowerMultiplier)}`;
|
|
case 'axis_b':
|
|
return `${resourceLabels.maxHp} +${combatStats.maxHpBonus}`;
|
|
case 'axis_c':
|
|
return `${resourceLabels.hp}恢复 +${combatStats.storyRecovery}`;
|
|
case 'axis_d':
|
|
return `攻击速度 ${formatAttributeMetricValue(combatStats.turnSpeed)}`;
|
|
case 'axis_e':
|
|
return `暴击率 ${formatAttributePercentValue(combatStats.critChance)}`;
|
|
case 'axis_f':
|
|
return `暴击伤害 x${formatAttributeMetricValue(combatStats.critDamageMultiplier)}`;
|
|
default:
|
|
return '提升战斗表现';
|
|
}
|
|
}
|
|
|
|
export type ContributionRow = BuildDamageBreakdown['rows'][number];
|