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; 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, ) { 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];