- Login with registration code, sessions, rate limiting, per-user characters - Share characters with other users (read-only), admin CLI - Rules presets with a Presets tab, applied/pending changes with diff in a side sheet - Equipment as tiles with amounts, starting kit removed - Rename nav tab to "Character", add "New character" button - Stop tracking _to_delete/ Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
47 lines
2.7 KiB
JavaScript
47 lines
2.7 KiB
JavaScript
import { compute, effectiveRules, kitItems } from './calc.js'
|
||
|
||
// Flat, comparable view of a character, used by the side panel to show what changed.
|
||
export function sheetModel(ch, baseRules) {
|
||
const rules = effectiveRules(baseRules, ch)
|
||
const c = compute(ch, rules)
|
||
const prof = rules.professions.find((p) => p.id === ch.profession)
|
||
const cls = rules.classes.find((k) => k.id === ch.class_id)
|
||
const sub = cls?.subclasses.find((x) => x.id === ch.subclass_id)
|
||
const preset = ch.preset === 'custom' ? 'Custom' : baseRules.presets?.find((p) => p.id === ch.preset)?.name || 'Custom'
|
||
const training = (s) => (s.pips === 2 ? 'Expertise' : s.pips === 1 ? 'Proficient' : !s.usable ? 'Locked' : s.type === 'advanced' ? 'Unlocked' : '')
|
||
return {
|
||
c,
|
||
head: [
|
||
{ k: 'name', label: 'Name', val: ch.name || 'Nameless' },
|
||
{ k: 'profession', label: 'Profession', val: prof?.name || '–' },
|
||
{ k: 'class', label: 'Class', val: cls ? [cls.name, sub?.name].filter(Boolean).join(' / ') : '–' },
|
||
{ k: 'preset', label: 'Rules', val: preset },
|
||
],
|
||
attrs: rules.attributes.map((a) => ({ k: a.id, label: a.name, val: c.attrs[a.id], extra: `Mod ${c.mods[a.id]}` })),
|
||
derived: rules.derived.map((d) => ({ k: d.id, label: d.name, val: c.derived[d.id] })),
|
||
skills: c.skills.map((s) => ({ k: s.id, label: s.name, val: s.total, extra: training(s), dim: !s.usable })),
|
||
talents: rules.talents.filter((t) => ch.talents.includes(t.id)).map((t) => ({ k: t.id, label: t.name, desc: t.desc })),
|
||
augments: rules.augments.filter((a) => ch.augments.includes(a.id)).map((a) => ({ k: a.id, label: a.name, desc: `${a.kind === 'implant' ? 'Implant' : 'Mutation'} · cost ${a.cost}${a.desc ? ` · ${a.desc}` : ''}` })),
|
||
gear: kitItems(ch, rules).map((g) => ({ k: g.item.id, label: g.item.name, val: g.qty, desc: [g.item.stat, `${g.item.kg} kg`, g.item.note].filter(Boolean).join(' · ') })),
|
||
}
|
||
}
|
||
|
||
// Merges two row lists into one with a status per row: same | changed | added | removed.
|
||
export function diffRows(now, before) {
|
||
const old = new Map(before.map((r) => [r.k, r]))
|
||
const seen = new Set()
|
||
const out = now.map((r) => {
|
||
seen.add(r.k)
|
||
const o = old.get(r.k)
|
||
if (!o) return { ...r, status: 'added' }
|
||
return { ...r, old: o, status: o.val === r.val && (o.extra || '') === (r.extra || '') ? 'same' : 'changed' }
|
||
})
|
||
before.forEach((o) => { if (!seen.has(o.k)) out.push({ ...o, status: 'removed' }) })
|
||
return out
|
||
}
|
||
|
||
export const countChanges = (a, b) => {
|
||
const n = (rows, bef) => diffRows(rows, bef).filter((r) => r.status !== 'same').length
|
||
return n(a.head, b.head) + n(a.attrs, b.attrs) + n(a.skills, b.skills) + n(a.talents, b.talents) + n(a.augments, b.augments) + n(a.gear, b.gear)
|
||
}
|