diff --git a/extensions/pixelhero-config-editor/__tests__/buildSkillDesc.test.ts b/extensions/pixelhero-config-editor/__tests__/buildSkillDesc.test.ts new file mode 100644 index 00000000..0ae3be92 --- /dev/null +++ b/extensions/pixelhero-config-editor/__tests__/buildSkillDesc.test.ts @@ -0,0 +1,25 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { buildSkillDesc } from '../src/shared/desc/buildSkillDesc'; +import { RecordValue } from '../src/io/recordValue'; + +const skillSet: Record = { + 6301: { kind: 'obj', props: { name: { kind: 'str', value: '护盾' }, kind: { kind: 'enumRef', qualifier: 'SkillKind', member: 'Shield' }, ap: { kind: 'num', value: 4 } } }, +}; +const fieldSet: Record = { + 7015: { kind: 'obj', props: { name: { kind: 'str', value: '死亡强化' }, info: { kind: 'str', value: '死亡触发技能次数+1' } } }, +}; +const hero: RecordValue = { kind: 'obj', props: { + atked: { kind: 'arr', items: [{ kind: 'obj', props: { s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 }, overrides: { kind: 'obj', props: { ap: { kind: 'num', value: 4 } } } } }] }, + field: { kind: 'arr', items: [{ kind: 'num', value: 7015 }] }, +} }; + +test('renders atked trigger with shield effect', () => { + const out = buildSkillDesc(hero, skillSet, fieldSet); + assert.match(out, /受击3次:护盾/); + assert.match(out, /护盾4次/); +}); +test('renders field aura', () => { + const out = buildSkillDesc(hero, skillSet, fieldSet); + assert.match(out, /场上存活:死亡强化 死亡触发技能次数\+1/); +}); diff --git a/extensions/pixelhero-config-editor/src/shared/desc/buildSkillDesc.ts b/extensions/pixelhero-config-editor/src/shared/desc/buildSkillDesc.ts new file mode 100644 index 00000000..7732a2f3 --- /dev/null +++ b/extensions/pixelhero-config-editor/src/shared/desc/buildSkillDesc.ts @@ -0,0 +1,63 @@ +import { RecordValue } from '../../io/recordValue'; + +const TRIGGER_KEYS = ['call', 'dead', 'fstart', 'fend', 'atking', 'atked'] as const; +const TRIGGER_DESC: Record = { + call: '召唤时', dead: '死亡时', fstart: '战斗开始时', fend: '战斗结束时', + field: '场上存活', atking: '攻击n次', atked: '受击n次', revive: '复活时', +}; + +function num(v: RecordValue | undefined): number | undefined { return v && v.kind === 'num' ? v.value : undefined; } +function str(v: RecordValue | undefined): string | undefined { return v && v.kind === 'str' ? v.value : undefined; } +function member(v: RecordValue | undefined): string | undefined { return v && v.kind === 'enumRef' ? v.member : undefined; } + +function buildEffect(merged: RecordValue): string { + if (merged.kind !== 'obj') return ''; + const kind = member(merged.props['kind']); + const ap = num(merged.props['ap']) ?? 0; + const parts: string[] = []; + if (kind === 'Heal') parts.push(`治疗伙伴${ap}`); + else if (kind === 'Shield') parts.push(`护盾${ap}次`); + else if (kind === 'Gold') parts.push(`金币+${num(merged.props['gold']) ?? 0}`); + else if (kind === 'Support') parts.push(String(str(merged.props['info']) ?? '')); + else { // Damage / undefined + parts.push(`伤害${ap}%`); + if ((num(merged.props['hit_count']) ?? 1) > 1) parts.push(`${num(merged.props['hit_count'])}段`); + if (num(merged.props['crt'])) parts.push(`暴击+${num(merged.props['crt'])}%`); + if (num(merged.props['stun'])) parts.push(`击晕+${num(merged.props['stun'])}%`); + } + return parts.join(' '); +} + +/** 合并 overrides 到 base(浅合并对象 props) */ +function merge(base: RecordValue, overrides: RecordValue | undefined): RecordValue { + if (!overrides || overrides.kind !== 'obj' || base.kind !== 'obj') return base; + return { kind: 'obj', props: { ...base.props, ...overrides.props } }; +} + +export function buildSkillDesc(hero: RecordValue, skillSet: Record, fieldSet: Record): string { + if (hero.kind !== 'obj') return ''; + const lines: string[] = []; + for (const key of TRIGGER_KEYS) { + const arr = hero.props[key]; + if (!arr || arr.kind !== 'arr') continue; + const tpl = TRIGGER_DESC[key] ?? key; + for (const it of arr.items) { + if (it.kind !== 'obj') continue; + const su = num(it.props['s_uuid']); if (su === undefined) continue; + const base = skillSet[su]; if (!base) continue; + const merged = merge(base, it.props['overrides']); + const trigger = tpl.includes('n') ? tpl.replace('n', String(num(it.props['t_num']) ?? '')) : tpl; + lines.push(`${trigger}:${str(base.props['name'])} ${buildEffect(merged)}`); + } + } + const fl = hero.props['field']; + if (fl && fl.kind === 'arr') { + for (const it of fl.items) { const u = num(it); if (u === undefined) continue; const fs = fieldSet[u]; if (fs) lines.push(`${TRIGGER_DESC.field}:${str(fs.props['name'])} ${str(fs.props['info'])}`); } + } + const rv = hero.props['revive']; + if (rv && rv.kind === 'obj') { + const su = num(rv.props['s_uuid']); const base = su !== undefined ? skillSet[su] : undefined; + if (base) lines.push(`${TRIGGER_DESC.revive} : ${str(base.props['name'])} ${buildEffect(merge(base, undefined))}`); + } + return lines.join('\n'); +}