import useDatabase from '~/composables/useDatabase'; import { defaultCharacter, type Ability, type Character, type CharacterConfig, type CompiledCharacter, type DoubleIndex, type Feature, type FeatureItem, type Level, type MainStat, type TrainingLevel, type TrainingOption } from '~/types/character'; import characterData from '#shared/character-config.json'; import { group } from '~/shared/general.util'; export default defineCachedEventHandler(async (e) => { const id = getRouterParam(e, "id"); if(!id) { setResponseStatus(e, 400); return; } const db = useDatabase(); const character = db.query.characterTable.findFirst({ with: { abilities: true, levels: true, modifiers: true, spells: true, training: true, user: { columns: { username: true } } }, where: (character, { eq }) => eq(character.id, parseInt(id, 10)), }).sync(); if(character !== undefined) { return compileCharacter(Object.assign(defaultCharacter, { id: character.id, name: character.name, people: character.people, level: character.level, aspect: character.aspect, notes: character.notes, health: character.health, mana: character.mana, training: character.training.reduce((p, v) => { if(!(v.stat in p)) p[v.stat] = []; p[v.stat].push([v.level as TrainingLevel, v.choice]); return p; }, {} as Record[]>), leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex), abilities: group(character.abilities.map(e => ({ ...e, value: [e.value, e.max] as [number, number] })), "ability", "value"), spells: character.spells.map(e => e.value), modifiers: group(character.modifiers, "modifier", "value"), owner: character.owner, username: character.user.username, visibility: character.visibility, } as Character) as Character); } setResponseStatus(e, 404); return; }, { name: "character", getKey: (e) => getRouterParam(e, "id") || 'error' }); function compileCharacter(character: Character & { username?: string }): CompiledCharacter { const config = characterData as CharacterConfig; const race = character.people !== undefined ? config.peoples[character.people] : undefined; const raceOptions = race ? character.leveling!.map(e => race.options[e[0]][e[1]]) : []; const features = Object.entries(config.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, character.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]; const compiled: CompiledCharacter = { id: character.id, owner: character.owner, username: character.username, name: character.name, health: raceOptions.reduce((p, v) => p + (v.health ?? 0), 0), mana: raceOptions.reduce((p, v) => p + (v.mana ?? 0), 0), race: character.people!, modifier: features.map(e => [e[0], Math.floor((e[1].length - 1) / 3) + (character.modifiers[e[0]] ?? 0)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record), level: character.level, values: { health: character.health, mana: character.mana }, features: { action: [], reaction: [], freeaction: [], misc: [], }, abilities: { athletics: 0, acrobatics: 0, intimidation: 0, sleightofhand: 0, stealth: 0, survival: 0, investigation: 0, history: 0, religion: 0, arcana: 0, understanding: 0, perception: 0, performance: 0, medecine: 0, persuasion: 0, animalhandling: 0, deception: 0 }, spellslots: 0, artslots: 0, spellranks: { instinct: 0, knowledge: 0, precision: 0, arts: 0, }, spells: character.spells ?? [], speed: false, defense: { hardcap: Infinity, static: 6, activeparry: 0, activedodge: 0, passiveparry: 0, passivedodge: 0, }, mastery: { strength: 0, dexterity: 0, shield: 0, armor: 0, multiattack: 1, magicpower: 0, magicspeed: 0, magicelement: 0, magicinstinct: 0, }, resistance: { stun: [0, 0], bleed: [0, 0], poison: [0, 0], fear: [0, 0], influence: [0, 0], charm: [0, 0], possesion: [0, 0], precision: [0, 0], knowledge: [0, 0], instinct: [0, 0] }, initiative: 0, aspect: "", notes: character.notes ?? "", }; features.forEach(e => e[1].forEach(_e => _e.features?.forEach(f => applyFeature(compiled, f)))); return compiled; } function applyFeature(character: CompiledCharacter, f: FeatureItem) { switch(f.category) { case "action": character.features.action.push(f.text); return; case "reaction": character.features.reaction.push(f.text); return; case "freeaction": character.features.freeaction.push(f.text); return; case "misc": character.features.misc.push(f.text); return; case "asset": if(f.type === 'add') character[f.kind].push(f.asset); else character[f.kind] = character[f.kind].filter(e => e !== f.asset); return; case "value": const path = f.property.split("."); const object = path.slice(0, -1).reduce((p, v) => p[v], character as any); switch(f.type) { case "add": if(!['number'].includes(typeof object[path[path.length - 1]])) break; object[path[path.length - 1]] += f.value; break; case "remove": if(!['number'].includes(typeof object[path[path.length - 1]])) break; object[path[path.length - 1]] -= f.value as number; break; case "set": if(!['number', 'boolean'].includes(typeof object[path[path.length - 1]])) break; object[path[path.length - 1]] = f.value; break; default: break; } return; default: return; } } export function getFeaturesOf(stat: MainStat, progression: DoubleIndex[]): TrainingOption[] { const config = characterData as CharacterConfig; return progression.map(e => config.training[stat][e[0]][e[1]]); }