Progress on abilities

This commit is contained in:
Clément Pons 2025-04-22 11:29:23 +02:00
parent cb00c093ff
commit 308c2974f1
8 changed files with 79 additions and 22 deletions

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import config from '#shared/character-config.json';
function raceOptionToText(option: RaceOption): string function raceOptionToText(option: RaceOption): string
{ {
const text = []; const text = [];
@ -10,13 +11,27 @@ function raceOptionToText(option: RaceOption): string
if(option.mana) text.push(`+${option.mana} mana max.`); if(option.mana) text.push(`+${option.mana} mana max.`);
return text.join('\n'); return text.join('\n');
} }
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
{
const characterData = config as CharacterConfig;
return progression.map(e => characterData.training[stat][e[0]][e[1]]);
}
const mainStatTexts: Record<MainStat, string> = {
"strength": "Force",
"dexterity": "Dextérité",
"constitution": "Constitution",
"intelligence": "Intelligence",
"curiosity": "Curiosité",
"charisma": "Charisme",
"psyche": "Psyché",
}
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import config from '#shared/character-config.json';
import { Icon } from '@iconify/vue/dist/iconify.js'; import { Icon } from '@iconify/vue/dist/iconify.js';
import PreviewA from '~/components/prose/PreviewA.vue'; import PreviewA from '~/components/prose/PreviewA.vue';
import { clamp } from '~/shared/general.util'; import { clamp } from '~/shared/general.util';
import type { Character, CharacterConfig, Level, MainStat, RaceOption, TrainingLevel } from '~/types/character'; import type { Ability, Character, CharacterConfig, DoubleIndex, Level, MainStat, RaceOption, TrainingLevel, TrainingOption } from '~/types/character';
definePageMeta({ definePageMeta({
guestsGoesTo: '/user/login', guestsGoesTo: '/user/login',
@ -42,6 +57,8 @@ const data = ref<Character>({
index: undefined, index: undefined,
progress: [[1, 0]], progress: [[1, 0]],
}, },
abilities: {},
modifiers: {},
}, },
}); });
@ -52,8 +69,13 @@ const trainingPoints = computed(() => {
return p + (options[v[0]][v[1]].training ?? 0) return p + (options[v[0]][v[1]].training ?? 0)
}, 0) : 0; }, 0) : 0;
}); });
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<string, number>)); 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 trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v)); const trainingSpent = computed(() => Object.values(maxTraining.value).reduce((p, v) => p + v));
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 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 abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[1], 0));
if(id !== 'new') if(id !== 'new')
{ {
@ -242,43 +264,43 @@ useShortcuts({
</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">
<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 }} (+{{ Math.floor(maxTraining.strength / 3) }})</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">Force<span v-if="maxTraining.strength >= 0">: Niveau {{ maxTraining.strength }} (+{{ modifiers.strength }})</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.strength" :class="{ 'opacity-30': index > maxTraining.strength + 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('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> </div>
</div> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.dexterity / 3) }})</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">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="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 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> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.constitution / 3) }})</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">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="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 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> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.intelligence / 3) }})</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">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="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 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> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.curiosity / 3) }})</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">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="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 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> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.charisma / 3) }})</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">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="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 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> </div>
<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">
<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 }} (+{{ Math.floor(maxTraining.psyche / 3) }})</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">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="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 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>
@ -292,9 +314,21 @@ 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">
<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 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-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
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" />
</NumberFieldRoot>
<span class="font-bold col-span-4">(max: {{ abilityMax[index] }})</span>
</div>
</div>
</template> </template>
</Collapsible> </Collapsible>
<div class="h-[50vh]"></div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -14,7 +14,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
<Title>d[any] - Chargement ...</Title> <Title>d[any] - Chargement ...</Title>
</Head> </Head>
</div> </div>
<div v-else-if="status === 'success' && character"> <div v-else-if="status === 'success' && character && !error">
<Head> <Head>
<Title>d[any] - {{ character.name }}</Title> <Title>d[any] - {{ character.name }}</Title>
</Head> </Head>
@ -97,26 +97,24 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
<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> </div>
<div class="flex flex-col border-l border-light-30 dark:border-dark-30 ps-8 gap-4 py-8 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">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold">Actions</span> <span class="text-lg font-semibold">Actions</span>
<span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span> <span class="text-sm text-light-70 dark:text-dark-70">Attaquer - Saisir - Faire chuter - Déplacer - Courir - Pas de coté - Lancer un sort - S'interposer - Se transformer - Utiliser un objet - Anticiper une action - Improviser</span>
<MarkdownRenderer :content="character.features.action.join('\n')" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold">Réactions</span> <span class="text-lg font-semibold">Réactions</span>
<span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span> <span class="text-sm text-light-70 dark:text-dark-70">Parade - Esquive - Saisir une opportunité - Prendre en tenaille - Intercepter - Désarmer</span>
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold">Actions libre</span> <span class="text-lg font-semibold">Actions libre</span>
<span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span> <span class="text-sm text-light-70 dark:text-dark-70">Analyser une situation - Communiquer</span>
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
</div> </div>
</div> </div>
<div class="grid grid-cols-3 gap-2">
<div><MarkdownRenderer :content="character.features.action.join('\n')" /></div>
<div><MarkdownRenderer :content="character.features.reaction.join('\n')" /></div>
<div><MarkdownRenderer :content="character.features.freeaction.join('\n')" /></div>
</div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold">Aptitudes</span> <span class="text-lg font-semibold">Aptitudes</span>
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" /> <MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />

View File

@ -257,7 +257,7 @@ function applyFeature(feature: Feature, character: CompiledCharacter)
{ {
} */ } */
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[] export function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
{ {
const config = characterData as CharacterConfig; const config = characterData as CharacterConfig;
return progression.map(e => config.training[stat][e[0]][e[1]]); return progression.map(e => config.training[stat][e[0]][e[1]]);

View File

@ -1,4 +1,23 @@
{ {
"ability": {
"athletics": { "max": ["strength", "constitution"], "name": "Athlétisme", "description": "La capacité à effectuer un acte physique intense ou prolongé. Permet de pousser, contraindre, nager, courir." },
"acrobatics": { "max": ["strength", "dexterity"], "name": "Acrobatique", "description": "La capacité à se mouvoir avec souplesse sous la contrainte. Permet d'escalader, d'enjamber, de sauter." },
"intimidation": { "max": ["strength", "charisma"], "name": "Intimidation", "description": "La capacité à intimider et inspirer la crainte." },
"sleightofhand": { "max": ["dexterity", "dexterity"], "name": "Doigté", "description": "La capacité à faire des actions précises avec ses mains. Permet de voler à la tire, de crocheter." },
"stealth": { "max": ["dexterity", "dexterity"], "name": "Discrétion", "description": "La capacité à dissimuler sa présence. Permet de se cacher, de se mouvoir sans bruit." },
"survival": { "max": ["constitution", "psyche"], "name": "Survie", "description": "La capacité à survivre dans des conditions difficiles. Permet de pister, de collecter de la nourriture, de retrouver son chemin." },
"investigation": { "max": ["intelligence", "curiosity"], "name": "Enquête", "description": "La capacité à demander au MJ de l'aide parce que vous puez la merde." },
"history": { "max": ["intelligence", "curiosity"], "name": "Histoire", "description": "La capacité à connaitre et à retenir le passé du monde." },
"religion": { "max": ["intelligence", "curiosity"], "name": "Religion", "description": "La capacité a connaitre les pratiques et les coutumes religieuses." },
"arcana": { "max": ["intelligence", "psyche"], "name": "Arcanes", "description": "La capacité à comprendre et percevoir la magie. Permet de comprendre un sort en cours, de détecter de la magie." },
"understanding": { "max": ["intelligence", "charisma"], "name": "Compréhension", "description": "La capacité à déterminer les intentions des interlocuteurs. Permet de déceler des mensonges, de l'influence." },
"perception": { "max": ["curiosity", "curiosity"], "name": "Perception", "description": "La capacité à observer le monde à travers ces sens. Permet d'observer, d'entendre, de sentir." },
"performance": { "max": ["curiosity", "charisma"], "name": "Représentation", "description": "La capacité à se mettre en scène et à utiliser les arts. Permet de se produire en spectacle, de jouer d'un instrument, de chanter, de danser." },
"medecine": { "max": ["curiosity", "psyche"], "name": "Médicine", "description": "La capacité à apporter des soins. Permet de stabiliser un joueur mourant, de soigner durant un repos." },
"persuasion": { "max": ["charisma", "psyche"], "name": "Persuasion", "description": "" },
"animalhandling": { "max": ["charisma", "psyche"], "name": "Dressage", "description": "" },
"deception": { "max": ["charisma", "psyche"], "name": "Mensonge", "description": "" }
},
"peoples": [ "peoples": [
{ {
"name": "Humain", "name": "Humain",

12
types/character.d.ts vendored
View File

@ -15,7 +15,7 @@ export type Progression = {
progress?: DoubleIndex<Level>[]; progress?: DoubleIndex<Level>[];
}; };
level: number; level: number;
abilities?: Partial<Record<Ability, number>>; abilities: Partial<Record<Ability, [number, number]>>; //First is the ability, second is the max increment
spells?: string[]; //Spell ID spells?: string[]; //Spell ID
modifiers?: Partial<Record<MainStat, number>>; modifiers?: Partial<Record<MainStat, number>>;
aspect?: string; aspect?: string;
@ -29,7 +29,13 @@ export type Character = {
export type CharacterConfig = { export type CharacterConfig = {
peoples: Race[], peoples: Race[],
training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>; training: Record<MainStat, Record<TrainingLevel, TrainingOption[]>>;
ability: Record<Ability, AbilityConfig>;
}; };
export type AbilityConfig = {
max: [MainStat, MainStat];
name: string;
description: string;
}
export type Race = { export type Race = {
name: string; name: string;
description: string; description: string;
@ -81,8 +87,8 @@ export type TrainingOption = {
}>; }>;
mana?: number; mana?: number;
health?: number; health?: number;
modifier?: [MainStat, number][]; modifier?: number;
ability?: [Ability, number][]; ability?: number;
speed?: false | number; speed?: false | number;
initiative?: number; initiative?: number;
mastery?: keyof CompiledCharacter["mastery"]; mastery?: keyof CompiledCharacter["mastery"];