import { useState } from 'react' const BLANK = { id: null, name: '', desc: '', creation: {} } export default function Presets({ rules, reload }) { const [sel, setSel] = useState(rules.default_preset || 'standard') const [draft, setDraft] = useState(null) // preset being edited/created, null = viewing const [msg, setMsg] = useState('') const presets = (rules.presets || []).filter((p) => !p.npc) const current = draft || presets.find((p) => p.id === sel) || presets[0] const editable = !!draft const own = !!current?.mine const base = rules.creation const groups = [...new Set((rules.tunables || []).map((x) => x.group))] const value = (k) => (current.creation?.[k] ?? base[k]) const flash = (t) => { setMsg(t); setTimeout(() => setMsg(''), 4000) } const startNew = (from) => setMsg('') || setDraft({ id: null, name: from ? `${from.name} (copy)` : '', desc: from?.desc || '', creation: { ...(from?.creation || {}) } }) const edit = () => setMsg('') || setDraft({ id: current.id, name: current.name, desc: current.desc || '', creation: { ...current.creation } }) const setVal = (x, raw) => { const v = parseInt(raw, 10) if (!Number.isFinite(v)) return setDraft((d) => ({ ...d, creation: { ...d.creation, [x.key]: Math.max(x.min, Math.min(x.max, v)) } })) } const save = async () => { const body = { name: draft.name, desc: draft.desc, creation: draft.creation } if (!draft.name.trim()) return flash('Give the preset a name.') try { const res = await fetch(draft.id ? `/api/presets/${draft.id}` : '/api/presets', { method: draft.id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (!res.ok) throw new Error((await res.json().catch(() => ({}))).detail || 'Saving failed') const data = await res.json() let id = draft.id if (!id) id = data.id await reload() setSel(id); setDraft(null); flash('Preset saved.') } catch (e) { flash(String(e.message || e)) } } const remove = async () => { if (!window.confirm(`Delete the preset "${current.name}"? Characters that already use it keep their numbers.`)) return try { const res = await fetch(`/api/presets/${current.id}`, { method: 'DELETE' }) if (!res.ok) throw new Error('Deleting failed') await reload(); setSel(rules.default_preset || 'standard') } catch (e) { flash(String(e.message || e)) } } const kind = (p) => (p.builtin ? 'Built in' : p.mine ? 'Yours' : p.author ? `By ${p.author}` : 'Shared') return (

Rules presets

A preset is a named set of creation numbers (points for attributes and skills, talents, proficiency ...). Pick one on the Concept page of the creator. Presets are stored on the server, so everyone using this site can use them; only the account that created a preset can change or delete it. Built-in presets come from rules.json. Duplicate one to make your own version.

{editable ? ( <>

{draft.id ? 'Edit preset' : 'New preset'}

) : ( <>

{current.name} {kind(current)}

{current.desc || 'No description.'}

)} {groups.map((g) => (

{g}

{rules.tunables.filter((x) => x.group === g).map((x) => ( ))}
))} {msg &&

{msg}

}
{editable ? ( <> ) : ( <> {own && } {own && } )}
) }