Completed ability editor

This commit is contained in:
Clément Pons 2025-04-22 13:24:48 +02:00
parent 308c2974f1
commit 1de2439a8a
8 changed files with 87 additions and 84 deletions

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -26,6 +26,18 @@ const mainStatTexts: Record<MainStat, string> = {
"charisma": "Charisme", "charisma": "Charisme",
"psyche": "Psyché", "psyche": "Psyché",
} }
function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<TrainingLevel>[], value: number): number
{
if(type === 'points')
{
if(curiosity.find(e => e[0] == 7 && e[1] === 0))
return Math.max(6, value);
if(curiosity.find(e => e[0] == 7 && e[1] === 2))
return value + 1;
}
return value;
}
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js'; import { Icon } from '@iconify/vue/dist/iconify.js';
@ -71,11 +83,11 @@ const trainingPoints = computed(() => {
}); });
const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][]) const training = computed(() => Object.entries(characterConfig.training).map(e => [e[0], getFeaturesOf(e[0] as MainStat, data.value.progress.training[e[0] as MainStat])]) as [MainStat, TrainingOption[]][])
const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0]] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>)); const maxTraining = computed(() => Object.entries(data.value.progress.training).reduce((p, v) => { p[v[0]] = v[1].reduce((_p, _v) => Math.max(_p, _v[0]) , 0); return p; }, {} as Record<MainStat, number>));
const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v)); const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v, 0));
const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0]] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? data.value.progress.modifiers[v[0]] : 0); return p; }, {} as Record<MainStat, number>)) const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0]] = Math.floor(v[1] / 3) + (data.value.progress.modifiers ? (data.value.progress.modifiers[v[0] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>))
const abilityPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0)); const abilityPoints = computed(() => training.value.flatMap(e => e[1].filter(_e => _e.ability !== undefined)).reduce((p, v) => p + v.ability!, 0));
const abilityMax = computed(() => Object.entries(characterConfig.ability).reduce((p, v) => { p[v[0]] = Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3); return p; }, {} as Record<Ability, number>)); const abilityMax = computed(() => Object.entries(characterConfig.ability).reduce((p, v) => { p[v[0]] = abilitySpecialFeatures("max", data.value.progress.training.curiosity, Math.floor(maxTraining.value[v[1].max[0]] / 3) + Math.floor(maxTraining.value[v[1].max[1]] / 3)); return p; }, {} as Record<Ability, number>));
const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[1], 0)); const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));
if(id !== 'new') if(id !== 'new')
{ {
@ -167,6 +179,11 @@ function updateLevel()
} }
async function save(leave: boolean) async function save(leave: boolean)
{ {
if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
{
add({ title: 'Données manquantes', content: "Merci de saisir un nom et une race avant de pouvoir enregistrer votre personnage", type: 'error', duration: 25000, timer: true });
return;
}
if(id === 'new') if(id === 'new')
{ {
id = await useRequestFetch()(`/api/character`, { id = await useRequestFetch()(`/api/character`, {
@ -263,46 +280,10 @@ useShortcuts({
<Icon icon="radix-icons:caret-right" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab + 1, 0, 6)" /> <Icon icon="radix-icons:caret-right" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab + 1, 0, 6)" />
</div> </div>
<div class="flex gap-4 relative" :style="`left: calc(calc(-100% - 1em) * ${trainingTab}); transition: left .5s ease;`"> <div class="flex gap-4 relative" :style="`left: calc(calc(-100% - 1em) * ${trainingTab}); transition: left .5s ease;`">
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative"> <div class="flex w-full flex-shrink-0 flex-col gap-2 relative" v-for="(text, stat) of mainStatTexts">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Force<span v-if="maxTraining.strength >= 0">: Niveau {{ maxTraining.strength }} (+{{ modifiers.strength }})</span></div> <div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">{{ text }}<span v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.strength" :class="{ 'opacity-30': index > maxTraining.strength + 1 }"> <div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training[stat]" :class="{ 'opacity-30': index > maxTraining[stat] + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('strength', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.strength + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.strength?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div> <div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption(stat, index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining[stat] + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training[stat]?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Dextérité<span v-if="maxTraining.dexterity >= 0">: Niveau {{ maxTraining.dexterity }} (+{{ modifiers.dexterity }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.dexterity" :class="{ 'opacity-30': index > maxTraining.dexterity + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('dexterity', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.dexterity + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.dexterity?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Constitution<span v-if="maxTraining.constitution >= 0">: Niveau {{ maxTraining.constitution }} (+{{ modifiers.constitution }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.constitution" :class="{ 'opacity-30': index > maxTraining.constitution + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('constitution', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.constitution + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.constitution?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Intelligence<span v-if="maxTraining.intelligence >= 0">: Niveau {{ maxTraining.intelligence }} (+{{ modifiers.intelligence }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.intelligence" :class="{ 'opacity-30': index > maxTraining.intelligence + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('intelligence', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.intelligence + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.intelligence?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Curiosité<span v-if="maxTraining.curiosity >= 0">: Niveau {{ maxTraining.curiosity }} (+{{ modifiers.curiosity }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.curiosity" :class="{ 'opacity-30': index > maxTraining.curiosity + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('curiosity', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.curiosity + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.curiosity?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Charisme<span v-if="maxTraining.charisma >= 0">: Niveau {{ maxTraining.charisma }} (+{{ modifiers.charisma }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.charisma" :class="{ 'opacity-30': index > maxTraining.charisma + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('charisma', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.charisma + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.charisma?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div>
</div>
<div class="flex w-full flex-shrink-0 flex-col gap-2 relative">
<div class="sticky top-1 left-16 py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold self-start border border-light-30 dark:border-dark-30">Psyché<span v-if="maxTraining.psyche >= 0">: Niveau {{ maxTraining.psyche }} (+{{ modifiers.psyche }})</span></div>
<div class="flex flex-row gap-4 justify-center" v-for="(level, index) of characterConfig.training.psyche" :class="{ 'opacity-30': index > maxTraining.psyche + 1 }">
<div class="border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-1/3" v-for="(option, i) of level" @click="switchTrainingOption('psyche', index as TrainingLevel, i)" :class="{ 'hover:border-light-60 dark:hover:border-dark-60': index <= maxTraining.psyche + 1, '!border-accent-blue bg-accent-blue bg-opacity-20': index == 0 || (data.progress.training.psyche?.some(e => e[0] == index && e[1] === i) ?? false) }"><MarkdownRenderer :proses="{ 'a': PreviewA }" :content="option.description.map(e => e.text).join('\n')" /></div>
</div> </div>
</div> </div>
</div> </div>
@ -314,12 +295,14 @@ useShortcuts({
<span class="font-bold text-xl">Compétences</span> <span class="font-bold text-xl">Compétences</span>
</template> </template>
<template #default> <template #default>
<div class="flex flex-col gap-2 px-4"> <div class="flex flex-col gap-2 max-h-[50vh] px-4 relative overflow-y-auto">
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between">
<span class="text-xl -mx-2" :class="{ 'text-light-red dark:text-dark-red': (abilityPoints ?? 0) < abilitySpent }">Points d'entrainement restants: {{ (abilityPoints ?? 0) - abilitySpent }}</span> <span class="text-xl -mx-2" :class="{ 'text-light-red dark:text-dark-red': (abilityPoints ?? 0) < abilitySpent }">Points d'entrainement restants: {{ (abilityPoints ?? 0) - abilitySpent }}</span>
</div>
<div v-for="(ability, index) of characterConfig.ability" class="grid grid-cols-10 gap-4 items-center"> <div v-for="(ability, index) of characterConfig.ability" class="grid grid-cols-10 gap-4 items-center">
<span class="text-lg col-span-2">{{ ability.name }}</span> <span class="text-lg col-span-2">{{ ability.name }}</span>
<span class="text-sm text-light-70 dark:text-dark-70 col-span-2">({{ mainStatTexts[ability.max[0]] }} + {{ mainStatTexts[ability.max[1]] }})</span> <span class="text-sm text-light-70 dark:text-dark-70 col-span-2">({{ mainStatTexts[ability.max[0]] }} + {{ mainStatTexts[ability.max[1]] }})</span>
<NumberFieldRoot :min="0" :max="20" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; console.log(abilitySpent) }" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 <NumberFieldRoot :min="0" :max="abilityMax[index]" :default-value="data.progress.abilities[index] ? data.progress.abilities[index][0] : 0" @update:model-value="(value) => { data.progress.abilities[index] = [value, data.progress.abilities[index] ? data.progress.abilities[index][1] : 0]; console.log(abilitySpent) }" class="flex justify-center border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20
data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40"> data-[disabled]:text-light-70 dark:data-[disabled]:text-dark-70 hover:border-light-50 dark:hover:border-dark-50 has-[:focus]:shadow-raw transition-[box-shadow] has-[:focus]:shadow-light-40 dark:has-[:focus]:shadow-dark-40">
<NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" /> <NumberFieldInput class="tabular-nums w-20 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
</NumberFieldRoot> </NumberFieldRoot>
@ -328,7 +311,6 @@ useShortcuts({
</div> </div>
</template> </template>
</Collapsible> </Collapsible>
<div class="h-[50vh]"></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -96,6 +96,12 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
<span>Sorts de savoir: <span class="font-bold">{{ character.spellranks.knowledge }}</span></span> <span>Sorts de savoir: <span class="font-bold">{{ character.spellranks.knowledge }}</span></span>
<span>Sorts d'instinct: <span class="font-bold">{{ character.spellranks.instinct }}</span></span> <span>Sorts d'instinct: <span class="font-bold">{{ character.spellranks.instinct }}</span></span>
</div> </div>
<div class="flex flex-col">
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30 mb-2">Compétences</span>
<div class="grid grid-cols-3 gap-1">
<div class="flex flex-col px-2 items-center" v-for="(value, ability) of character.abilities"><span class="font-bold">+{{ value }}</span><span>{{ config.ability[ability].name }}</span></div>
</div>
</div>
</div> </div>
<div class="flex flex-1 flex-col border-l border-light-30 dark:border-dark-30 ps-8 gap-4 py-8 max-w-[80rem]"> <div class="flex flex-1 flex-col border-l border-light-30 dark:border-dark-30 ps-8 gap-4 py-8 max-w-[80rem]">
<div class="grid grid-cols-3 gap-2"> <div class="grid grid-cols-3 gap-2">

View File

@ -135,23 +135,23 @@ function compileCharacter(character: Character & { username?: string }): Compile
misc: [], misc: [],
}, },
abilities: { abilities: {
athletics: [0, 0], athletics: 0,
acrobatics: [0, 0], acrobatics: 0,
intimidation: [0, 0], intimidation: 0,
sleightofhand: [0, 0], sleightofhand: 0,
stealth: [0, 0], stealth: 0,
survival: [0, 0], survival: 0,
investigation: [0, 0], investigation: 0,
history: [0, 0], history: 0,
religion: [0, 0], religion: 0,
arcana: [0, 0], arcana: 0,
understanding: [0, 0], understanding: 0,
perception: [0, 0], perception: 0,
performance: [0, 0], performance: 0,
medecine: [0, 0], medecine: 0,
persuasion: [0, 0], persuasion: 0,
animalhandling: [0, 0], animalhandling: 0,
deception: [0, 0] deception: 0
}, },
spellslots: 0, spellslots: 0,
spellranks: { spellranks: {
@ -195,14 +195,15 @@ function compileCharacter(character: Character & { username?: string }): Compile
features.forEach(e => e[1].forEach((_e, i) => applyTrainingOption(e[0], _e, compiled, i === e[1].length - 1))); features.forEach(e => e[1].forEach((_e, i) => applyTrainingOption(e[0], _e, compiled, i === e[1].length - 1)));
specialFeatures(compiled, character.progress.training); specialFeatures(compiled, character.progress.training);
Object.entries(character.progress.abilities).forEach(e => compiled.abilities[e[0]] += e[1][0]);
return compiled; return compiled;
} }
function applyTrainingOption(stat: MainStat, option: TrainingOption, character: CompiledCharacter, last: boolean) function applyTrainingOption(stat: MainStat, option: TrainingOption, character: CompiledCharacter, last: boolean)
{ {
if(option.health) character.health += option.health; if(option.health) character.health += option.health;
if(option.mana) character.mana += option.mana; if(option.mana) character.mana += option.mana;
if(option.modifier) option.modifier.forEach(e => character.modifier[e[0]] += e[1]);
if(option.ability) option.ability.forEach(e => character.abilities[e[0]]![0] += e[1]);
if(option.mastery) character.mastery[option.mastery]++; if(option.mastery) character.mastery[option.mastery]++;
if(option.speed) character.speed = option.speed; if(option.speed) character.speed = option.speed;
if(option.initiative) character.initiative += option.initiative; if(option.initiative) character.initiative += option.initiative;

View File

@ -1720,7 +1720,8 @@
"disposable": false, "disposable": false,
"replaced": true "replaced": true
} }
] ],
"ability": 6
} }
], ],
"1": [ "1": [
@ -1735,7 +1736,8 @@
"disposable": false, "disposable": false,
"replaced": true "replaced": true
} }
] ],
"ability": 3
} }
], ],
"2": [ "2": [
@ -1745,7 +1747,8 @@
"text": "+5 points de compétence.", "text": "+5 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 5
} }
], ],
"3": [ "3": [
@ -1755,7 +1758,8 @@
"text": "+3 points de compétence.", "text": "+3 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 3
} }
], ],
"4": [ "4": [
@ -1765,7 +1769,8 @@
"text": "+4 points de compétence.", "text": "+4 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 4
} }
], ],
"5": [ "5": [
@ -1799,7 +1804,8 @@
"text": "+3 points de compétence.", "text": "+3 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 3
} }
], ],
"6": [ "6": [
@ -1822,7 +1828,8 @@
"text": "+1 point de compétence.", "text": "+1 point de compétence.",
"disposable": false "disposable": false
} }
] ],
"ability": 1
}, },
{ {
"description": [ "description": [
@ -1830,7 +1837,8 @@
"text": "+3 points de compétence.", "text": "+3 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 3
} }
], ],
"7": [ "7": [
@ -1838,13 +1846,14 @@
"description": [ "description": [
{ {
"text": "Le maximum de toutes les compétences est de 6 points, sauf s'il est déjà supérieur.", "text": "Le maximum de toutes les compétences est de 6 points, sauf s'il est déjà supérieur.",
"disposable": false "disposable": true
}, },
{ {
"text": "+2 points de compétence.", "text": "+2 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 2
}, },
{ {
"description": [ "description": [
@ -1858,13 +1867,14 @@
"description": [ "description": [
{ {
"text": "Le maximum de toutes les compétences augmente de 1 point.", "text": "Le maximum de toutes les compétences augmente de 1 point.",
"disposable": false "disposable": true
}, },
{ {
"text": "+2 points de compétence.", "text": "+2 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 2
} }
], ],
"8": [ "8": [
@ -1890,7 +1900,8 @@
"text": "+4 points de compétence.", "text": "+4 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 4
} }
], ],
"9": [ "9": [
@ -1942,7 +1953,8 @@
"text": "+4 points de compétence.", "text": "+4 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 4
} }
], ],
"11": [ "11": [
@ -1968,7 +1980,8 @@
"text": "+4 points de compétence.", "text": "+4 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 4
} }
], ],
"12": [ "12": [
@ -2020,7 +2033,8 @@
"text": "+5 points de compétence.", "text": "+5 points de compétence.",
"disposable": true "disposable": true
} }
] ],
"ability": 5
} }
], ],
"14": [ "14": [

View File

@ -145,7 +145,7 @@ export type CompiledCharacter = {
}; };
modifier: Record<MainStat, number>; modifier: Record<MainStat, number>;
abilities: Partial<Record<Ability, [number, number]>>; abilities: Partial<Record<Ability, number>>;
level: number; level: number;
features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later features: Record<Category, string[]>; //Currently: List of training option as text. TODO: Update to a more complex structure later
}; };