Merge branch 'character' into dev

This commit is contained in:
Clément Pons
2025-07-21 18:00:00 +02:00
82 changed files with 15935 additions and 1000 deletions

View File

@@ -0,0 +1,149 @@
import useDatabase from '~/composables/useDatabase';
import { type Character, type CharacterConfig, type CompiledCharacter, type DoubleIndex, type Level, type MainStat, type TrainingLevel, type TrainingOption } from '~/types/character';
import characterData from '#shared/character-config.json';
import { group } from '#shared/general.util';
import { defaultCharacter, MAIN_STATS } from '#shared/character.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<MainStat, DoubleIndex<TrainingLevel>[]>),
leveling: character.levels.map(e => [e.level as Level, e.choice] as DoubleIndex<Level>),
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<MainStat, number>),
level: character.level,
values: {
health: character.health,
mana: character.mana
},
features: {
action: [],
reaction: [],
freeaction: [],
passive: [],
},
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: {},//Object.fromEntries(MAIN_STATS.map(e => [e as MainStat, [0, 0]])) as Record<MainStat, [number, number]>,
initiative: 0,
aspect: "",
notes: character.notes ?? "",
};
//features.forEach(e => e[1].forEach(_e => _e.features?.forEach(f => applyFeature(compiled, f))));
return compiled;
}
export function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
{
const config = characterData as CharacterConfig;
return progression.map(e => config.training[stat][e[0]][e[1]]);
}

View File

@@ -0,0 +1,71 @@
import { eq } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase';
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id");
if(!id)
{
setResponseStatus(e, 400);
return;
}
const db = useDatabase();
const old = db.select().from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
if(!old)
{
setResponseStatus(e, 404);
return;
}
const session = await getUserSession(e);
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
{
setResponseStatus(e, 401);
return;
}
try
{
const _id = db.transaction((tx) => {
const _id = tx.insert(characterTable).values({
name: old.name,
owner: session.user!.id,
people: old.people!,
level: old.level,
aspect: old.aspect,
notes: old.notes,
health: old.health,
mana: old.mana,
visibility: old.visibility,
thumbnail: old.thumbnail,
}).returning({ id: characterTable.id }).get().id;
const leveling = tx.select().from(characterLevelingTable).where(eq(characterLevelingTable.character, parseInt(id, 10))).all();
if(leveling.length > 0) tx.insert(characterLevelingTable).values(leveling.map(e => ({ character: _id, level: e.level, choice: e.choice }))).run();
const training = tx.select().from(characterTrainingTable).where(eq(characterTrainingTable.character, parseInt(id, 10))).all();
if(training.length > 0) tx.insert(characterTrainingTable).values(training.map(e => ({ character: _id, stat: e.stat, level: e.level, choice: e.choice }))).run();
const modifiers = tx.select().from(characterModifiersTable).where(eq(characterModifiersTable.character, parseInt(id, 10))).all();
if(modifiers.length > 0) tx.insert(characterModifiersTable).values(modifiers.map(e => ({ character: _id, modifier: e.modifier, value: e.value }))).run();
const spells = tx.select().from(characterSpellsTable).where(eq(characterSpellsTable.character, parseInt(id, 10))).all();
if(spells.length > 0) tx.insert(characterSpellsTable).values(spells.map(e => ({ character: _id, value: e.value }))).run();
const abilities = tx.select().from(characterAbilitiesTable).where(eq(characterAbilitiesTable.character, parseInt(id, 10))).all();
if(abilities.length > 0) tx.insert(characterAbilitiesTable).values(abilities.map(e => ({ character: _id, ability: e.ability, value: e.value, max: e.max }))).run();
return _id;
});
setResponseStatus(e, 201);
return _id;
}
catch(_e)
{
setResponseStatus(e, 201);
throw _e;
}
});

View File

@@ -0,0 +1,35 @@
import { and, eq, sql } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase';
import { characterTable } from '~/db/schema';
import type { Character, CharacterValues } from '~/types/character';
export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id");
if(!id)
{
setResponseStatus(e, 400);
return;
}
const session = await getUserSession(e);
if(!session.user)
{
setResponseStatus(e, 401);
return;
}
const db = useDatabase();
const character = db.select({
health: characterTable.health,
mana: characterTable.mana,
}).from(characterTable).where(and(eq(characterTable.id, parseInt(id, 10)), eq(characterTable.owner, session.user.id))).get();
if(character !== undefined)
{
return character as CharacterValues;
}
setResponseStatus(e, 404);
return;
});

View File

@@ -0,0 +1,44 @@
import { eq } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase';
import { characterTable } from '~/db/schema';
import type { CharacterValues } from '~/types/character';
export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id");
if(!id)
{
setResponseStatus(e, 400);
return;
}
const body = await readBody(e) as CharacterValues;
if(!body)
{
setResponseStatus(e, 400);
return;
}
const db = useDatabase();
const old = db.select({ id: characterTable.id, owner: characterTable.owner }).from(characterTable).where(eq(characterTable.id, parseInt(id, 10))).get();
if(!old)
{
setResponseStatus(e, 404);
return;
}
const session = await getUserSession(e);
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
{
setResponseStatus(e, 401);
return;
}
db.update(characterTable).set({
health: body.health,
mana: body.mana,
}).where(eq(characterTable.id, parseInt(id, 10))).run();
setResponseStatus(e, 200);
return;
});