New feature system for training and homebrew

This commit is contained in:
Clément Pons
2025-06-03 16:42:41 +02:00
parent df3577f673
commit 42915d699f
18 changed files with 677 additions and 207 deletions

View File

@@ -1,9 +1,9 @@
import useDatabase from '~/composables/useDatabase';
import { defaultCharacter, type Ability, type Character, type CharacterConfig, type CompiledCharacter, type DoubleIndex, type Feature, type Level, type MainStat, type TrainingLevel, type TrainingOption } from '~/types/character';
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 defineEventHandler(async (e) => {
export default defineCachedEventHandler(async (e) => {
const id = getRouterParam(e, "id");
if(!id)
{
@@ -53,7 +53,7 @@ export default defineEventHandler(async (e) => {
setResponseStatus(e, 404);
return;
}/* , { name: "character", getKey: (e) => getRouterParam(e, "id") || 'error' } */);
}, { name: "character", getKey: (e) => getRouterParam(e, "id") || 'error' });
function compileCharacter(character: Character & { username?: string }): CompiledCharacter
{
@@ -112,6 +112,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
spells: character.spells ?? [],
speed: false,
defense: {
hardcap: Infinity,
static: 6,
activeparry: 0,
activedodge: 0,
@@ -126,7 +127,8 @@ function compileCharacter(character: Character & { username?: string }): Compile
multiattack: 1,
magicpower: 0,
magicspeed: 0,
magicelement: 0
magicelement: 0,
magicinstinct: 0,
},
resistance: {
stun: [0, 0],
@@ -145,74 +147,73 @@ function compileCharacter(character: Character & { username?: string }): Compile
notes: character.notes ?? "",
};
features.forEach(e => e[1].forEach((_e, i) => applyTrainingOption(e[0], _e, compiled, i === e[1].length - 1)));
specialFeatures(compiled, character.training);
Object.entries(character.abilities).forEach(e => compiled.abilities[e[0] as Ability]! += e[1][0]);
features.forEach(e => e[1].forEach(_e => _e.features?.forEach(f => applyFeature(compiled, f))));
return compiled;
}
function applyTrainingOption(stat: MainStat, option: TrainingOption, character: CompiledCharacter, last: boolean)
function applyFeature(character: CompiledCharacter, f: FeatureItem)
{
if(option.health) character.health += option.health;
if(option.mana) character.mana += option.mana;
if(option.mastery) character.mastery[option.mastery]++;
if(option.speed) character.speed = option.speed;
if(option.initiative) character.initiative += option.initiative;
if(option.spellrank) character.spellranks[option.spellrank]++;
if(option.defense) option.defense.forEach(e => character.defense[e]++);
if(option.resistance) option.resistance.forEach(e => character.resistance[e[0]][e[1] === "attack" ? 0 : 1]++);
if(option.spellslot) character.spellslots += option.spellslot in character.modifier ? character.modifier[option.spellslot as MainStat] : option.spellslot as number;
if(option.arts) character.artslots += option.arts in character.modifier ? character.modifier[option.arts as MainStat] : option.arts as number;
if(option.spell) character.spells.push(option.spell);
option.description.forEach(line => !line.disposable && (last || !line.replaced) && character.features[line.category ?? "misc"].push(line.text));
//if(option.features) option.features.forEach(e => applyFeature(e, character));
}
function specialFeatures(character: CompiledCharacter, levels: Record<MainStat, DoubleIndex<TrainingLevel>[]>)
{
//Cap la défense
const strengthCap3 = levels.strength.some(e => e[0] === 0);
const strengthCap6 = levels.strength.some(e => e[0] === 1);
const strengthUncapped = levels.strength.some(e => e[0] === 2);
const dexterityCap3 = levels.dexterity.some(e => e[0] === 0);
const dexterityCap3Stat = levels.dexterity.some(e => e[0] === 1);
const dexterityUncapped = levels.dexterity.some(e => e[0] === 2);
if(!strengthUncapped || !dexterityUncapped)
switch(f.category)
{
if(strengthCap6)
{
character.defense = {
static: 6,
activeparry: 0,
activedodge: 0,
passiveparry: 0,
passivedodge: 0,
};
}
else if(strengthCap3 || dexterityCap3)
{
character.defense = {
static: 3,
activeparry: 0,
activedodge: 0,
passiveparry: 0,
passivedodge: 0,
};
}
else if(dexterityCap3Stat)
{
character.defense.static = 3;
}
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;
}
}/*
function applyFeature(feature: Feature, character: CompiledCharacter)
{
} */
}
export function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
{
const config = characterData as CharacterConfig;

View File

@@ -1,6 +1,6 @@
import { eq } from 'drizzle-orm';
import useDatabase from '~/composables/useDatabase';
import { characterTable } from '~/db/schema';
import { characterAbilitiesTable, characterLevelingTable, characterModifiersTable, characterSpellsTable, characterTable, characterTrainingTable } from '~/db/schema';
export default defineEventHandler(async (e) => {
const id = getRouterParam(e, "id");
@@ -25,13 +25,47 @@ export default defineEventHandler(async (e) => {
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 returned = await db.insert(characterTable).values({
name: `Copie de ${old.name}`,
progress: old.progress,
owner: session.user.id,
}).returning({ id: characterTable.id });
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();
setResponseStatus(e, 201);
return returned[0].id;
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

@@ -21,12 +21,13 @@ export default defineEventHandler(async (e) => {
const db = useDatabase();
const character = db.select({
values: characterTable.values
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.values as CharacterValues;
return character as CharacterValues;
}
setResponseStatus(e, 404);

View File

@@ -1,6 +1,7 @@
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");
@@ -10,7 +11,7 @@ export default defineEventHandler(async (e) => {
return;
}
const body = await readBody(e);
const body = await readBody(e) as CharacterValues;
if(!body)
{
setResponseStatus(e, 400);
@@ -34,7 +35,8 @@ export default defineEventHandler(async (e) => {
}
db.update(characterTable).set({
values: body,
health: body.health,
mana: body.mana,
}).where(eq(characterTable.id, parseInt(id, 10))).run();
setResponseStatus(e, 200);

View File

@@ -0,0 +1,22 @@
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;
}
console.log(id);
setResponseStatus(e, 200);
return {};
});

View File

@@ -0,0 +1,23 @@
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;
}
console.log(id);
console.log(await readBody(e));
setResponseStatus(e, 200);
return;
});