Files
Genarrative/src/components/CharacterInfoHelpers.ts
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

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];