Character creation UI fixes, updates and resistances are displayed.

This commit is contained in:
Peaceultime 2025-04-23 22:44:34 +02:00
parent 0771d5ebd1
commit 7a11c5382c
9 changed files with 135 additions and 100 deletions

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,9 +4,9 @@ function raceOptionToText(option: RaceOption): string
{ {
const text = []; const text = [];
if(option.training) text.push(`+${option.training} point${option.training > 1 ? 's' : ''} de statistique${option.training > 1 ? 's' : ''}.`); if(option.training) text.push(`+${option.training} point${option.training > 1 ? 's' : ''} de statistique${option.training > 1 ? 's' : ''}.`);
if(option.spec) text.push(`+${option.spec} spécialisation${option.spec > 1 ? 's' : ''}.`);
if(option.shaping) text.push(`+${option.shaping} transformation${option.shaping > 1 ? 's' : ''} par jour.`); if(option.shaping) text.push(`+${option.shaping} transformation${option.shaping > 1 ? 's' : ''} par jour.`);
if(option.modifier) text.push(`+${option.modifier} au modifieur de votre choix.`); if(option.modifier) text.push(`+${option.modifier} au modifieur de votre choix.`);
if(option.abilities) text.push(`+${option.abilities} point${option.abilities > 1 ? 's' : ''} de compétence${option.abilities > 1 ? 's' : ''}.`);
if(option.health) text.push(`+${option.health} PV max.`); if(option.health) text.push(`+${option.health} PV max.`);
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');
@ -31,9 +31,9 @@ function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<T
{ {
if(type === 'points') if(type === 'points')
{ {
if(curiosity.find(e => e[0] == 7 && e[1] === 0)) if(curiosity.find(e => e[0] == 6 && e[1] === 0))
return Math.max(6, value); return Math.max(6, value);
if(curiosity.find(e => e[0] == 7 && e[1] === 2)) if(curiosity.find(e => e[0] == 6 && e[1] === 2))
return value + 1; return value + 1;
} }
return value; return value;
@ -80,13 +80,13 @@ const raceOptions = computed(() => data.value.progress.race.index !== undefined
const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.progress.race.progress!.map(e => raceOptions.value![e[0]][e[1]]) : undefined); const selectedRaceOptions = computed(() => raceOptions !== undefined ? data.value.progress.race.progress!.map(e => raceOptions.value![e[0]][e[1]]) : undefined);
const trainingPoints = computed(() => raceOptions.value ? data.value.progress.race.progress?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0); const trainingPoints = computed(() => raceOptions.value ? data.value.progress.race.progress?.reduce((p, v) => p + (raceOptions.value![v[0]][v[1]].training ?? 0), 0) : 0);
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] as MainStat] = 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, 0)); 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] as MainStat] ?? 0) : 0); return p; }, {} as Record<MainStat, number>)) const modifiers = computed(() => Object.entries(maxTraining.value).reduce((p, v) => { p[v[0] as MainStat] = 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 modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0)); const modifierPoints = computed(() => (selectedRaceOptions.value ? selectedRaceOptions.value.reduce((p, v) => p + (v?.modifier ?? 0), 0) : 0) + training.value.reduce((p, v) => p + v[1].reduce((_p, _v) => _p + (_v?.modifier ?? 0), 0), 0));
const modifierSpent = computed(() => Object.values(data.value.progress.modifiers ?? {}).reduce((p, v) => p + v, 0)); const modifierSpent = computed(() => Object.values(data.value.progress.modifiers ?? {}).reduce((p, v) => p + v, 0));
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]] = 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 abilityMax = computed(() => Object.entries(characterConfig.abilities).reduce((p, v) => { p[v[0] as Ability] = 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[0], 0)); const abilitySpent = computed(() => Object.values(data.value.progress.abilities ?? {}).reduce((p, v) => p + v[0], 0));
if(id !== 'new') if(id !== 'new')
@ -277,14 +277,14 @@ useShortcuts({
</template> </template>
<template #default> <template #default>
<div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative overflow-y-auto overflow-x-hidden"> <div class="flex flex-col gap-4 max-h-[50vh] pe-4 relative overflow-y-auto overflow-x-hidden">
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex justify-between"> <div class="sticky top-0 z-10 py-2 bg-light-0 dark:bg-dark-0 flex justify-between">
<Icon icon="radix-icons:caret-left" 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-left" class="w-6 h-6 border border-light-30 dark:border-dark-30 cursor-pointer" @click="() => trainingTab = clamp(trainingTab - 1, 0, 6)" />
<span class="text-xl" :class="{ 'text-light-red dark:text-dark-red': (trainingPoints ?? 0) < trainingSpent }">Points d'entrainement restants: {{ (trainingPoints ?? 0) - trainingSpent }}</span> <span class="text-xl" :class="{ 'text-light-red dark:text-dark-red': (trainingPoints ?? 0) < trainingSpent }">Points d'entrainement restants: {{ (trainingPoints ?? 0) - trainingSpent }}</span>
<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" v-for="(text, stat) of mainStatTexts"> <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 flex justify-between"> <div class="sticky top-1 mx-16 z-10 flex justify-between">
<div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }} <div class="py-1 px-3 bg-light-0 dark:bg-dark-0 z-10 text-xl font-bold border border-light-30 dark:border-dark-30 flex">{{ text }}
<div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }} <div class="flex gap-2" v-if="maxTraining[stat] >= 0">: Niveau {{ maxTraining[stat] }} (+{{ modifiers[stat] }}
<NumberFieldRoot :default-value="data.progress.modifiers[stat] ?? 0" v-model="data.progress.modifiers[stat]" 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 :default-value="data.progress.modifiers[stat] ?? 0" v-model="data.progress.modifiers[stat]" 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
@ -312,9 +312,9 @@ useShortcuts({
<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>
<div class="grid gap-4 grid-cols-6"> <div class="grid gap-4 grid-cols-6">
<div v-for="(ability, index) of characterConfig.ability" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2"> <div v-for="(ability, index) of characterConfig.abilities" class="flex flex-col items-center border border-light-30 dark:border-dark-30 p-2">
<div class="flex items-center justify-center gap-4"> <div class="flex items-center justify-center gap-4">
<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]; }" class="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" :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]; }" class="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-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" /> <NumberFieldInput class="tabular-nums w-8 bg-transparent px-3 py-1 outline-none caret-light-50 dark:caret-dark-50" />
</NumberFieldRoot> </NumberFieldRoot>

View File

@ -64,7 +64,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
</div> </div>
</div> </div>
<div class="flex flex-1 px-8"> <div class="flex flex-1 px-8">
<div class="flex flex-col pe-8 gap-8 py-8 w-80"> <div class="flex flex-col pe-8 gap-4 py-8 w-80">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Maitrise d'arme</span> <span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Maitrise d'arme</span>
<div class="grid grid-cols-2 gap-x-3 gap-y-1"> <div class="grid grid-cols-2 gap-x-3 gap-y-1">
@ -96,10 +96,16 @@ 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 flex items-center gap-4">Résistances (Attaque/Défense) <Tooltip side="right" message="Les défenses affichées incluent déjà leur modifieur de statistique."><Icon icon="radix-icons:question-mark-circled" /></Tooltip></span>
<div class="grid grid-cols-3 gap-1">
<div class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, resistance) of character.resistance"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value[0] }}/+{{ value[1] + character.modifier[config.resistances[resistance].statistic as MainStat] }}</span><span>{{ config.resistances[resistance].name }}</span></div>
</div>
</div>
<div class="flex flex-col"> <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> <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="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 class="flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70" v-for="(value, ability) of character.abilities"><span class="font-bold text-base text-light-100 dark:text-dark-100">+{{ value }}</span><span>{{ config.abilities[ability].name }}</span></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -126,7 +126,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
health: raceOptions.reduce((p, v) => p + (v.health ?? 0), 0), health: raceOptions.reduce((p, v) => p + (v.health ?? 0), 0),
mana: raceOptions.reduce((p, v) => p + (v.mana ?? 0), 0), mana: raceOptions.reduce((p, v) => p + (v.mana ?? 0), 0),
race: character.progress.race.index ?? -1, race: character.progress.race.index ?? -1,
modifier: features.map(e => [e[0], Math.floor(e[1].length / 3)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>), modifier: features.map(e => [e[0], Math.floor((e[1].length - 1) / 3) + (character.progress.modifiers[e[0]] ?? 0)] as [MainStat, number]).reduce((p, v) => { p[v[0]] = v[1]; return p }, {} as Record<MainStat, number>),
level: character.progress.level, level: character.progress.level,
features: { features: {
action: [], action: [],
@ -154,10 +154,12 @@ function compileCharacter(character: Character & { username?: string }): Compile
deception: 0 deception: 0
}, },
spellslots: 0, spellslots: 0,
artslots: 0,
spellranks: { spellranks: {
instinct: 0, instinct: 0,
knowledge: 0, knowledge: 0,
precision: 0 precision: 0,
arts: 0,
}, },
speed: false, speed: false,
defense: { defense: {
@ -180,7 +182,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
resistance: { resistance: {
stun: [0, 0], stun: [0, 0],
bleed: [0, 0], bleed: [0, 0],
posion: [0, 0], poison: [0, 0],
fear: [0, 0], fear: [0, 0],
influence: [0, 0], influence: [0, 0],
charm: [0, 0], charm: [0, 0],
@ -211,6 +213,8 @@ function applyTrainingOption(stat: MainStat, option: TrainingOption, character:
if(option.spellrank) character.spellranks[option.spellrank]++; if(option.spellrank) character.spellranks[option.spellrank]++;
if(option.defense) option.defense.forEach(e => character.defense[e]++); 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.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;
option.description.forEach(line => !line.disposable && (last || !line.replaced) && character.features[line.category ?? "misc"].push(line.text)); option.description.forEach(line => !line.disposable && (last || !line.replaced) && character.features[line.category ?? "misc"].push(line.text));

View File

@ -33,7 +33,7 @@ const transport = nodemailer.createTransport({
user: config.mail.user, user: config.mail.user,
pass: config.mail.passwd, pass: config.mail.passwd,
}, },
tls: { rejectUnauthorized: config.mail.port !== "465" }, tls: { rejectUnauthorized: false },
dkim: { dkim: {
domainName: domain, domainName: domain,
keySelector: selector, keySelector: selector,

View File

@ -1,5 +1,5 @@
{ {
"ability": { "abilities": {
"athletics": { "max": ["strength", "constitution"], "name": "Athlétisme", "description": "La capacité à effectuer un acte physique intense ou prolongé. Permet de pousser, contraindre, nager, courir." }, "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." }, "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." }, "intimidation": { "max": ["strength", "charisma"], "name": "Intimidation", "description": "La capacité à intimider et inspirer la crainte." },
@ -18,6 +18,18 @@
"animalhandling": { "max": ["charisma", "psyche"], "name": "Dressage", "description": "" }, "animalhandling": { "max": ["charisma", "psyche"], "name": "Dressage", "description": "" },
"deception": { "max": ["charisma", "psyche"], "name": "Mensonge", "description": "" } "deception": { "max": ["charisma", "psyche"], "name": "Mensonge", "description": "" }
}, },
"resistances": {
"stun": { "name": "Hébètement", "statistic": "strength" },
"bleed": { "name": "Saignement", "statistic": "constitution" },
"poison": { "name": "Empoisonement", "statistic": "constitution" },
"fear": { "name": "Peur", "statistic": "psyche" },
"influence": { "name": "Influence", "statistic": "curiosity" },
"charm": { "name": "Charme", "statistic": "charisma" },
"possesion": { "name": "Possession", "statistic": "psyche" },
"precision": { "name": "Précision", "statistic": "dexterity" },
"knowledge": { "name": "Savoir", "statistic": "intelligence" },
"instinct": { "name": "Instinct", "statistic": "psyche" }
},
"peoples": [ "peoples": [
{ {
"name": "Humain", "name": "Humain",
@ -26,26 +38,26 @@
"maxOption": 3 "maxOption": 3
}, },
"options": { "options": {
"1": [ { "training": 43, "health": 14 } ], "1": [ { "training": 35, "health": 14 } ],
"2": [ { "training": 1, "health": 4, "mana": 2 }, { "spec": 1, "health": 4, "mana": 2 } ], "2": [ { "training": 1, "health": 4, "mana": 2 }, { "health": 7, "mana": 4, "abilities": 1 } ],
"3": [ { "training": 1, "health": 4, "mana": 2 } ], "3": [ { "training": 2, "health": 4, "mana": 2, "abilities": 1 } ],
"4": [ { "training": 1, "health": 4, "mana": 2 } ], "4": [ { "training": 1, "health": 4, "mana": 2, "abilities": 2 } ],
"5": [ { "training": 1, "health": 6, "mana": 2 }, { "shaping": 1, "health": 8, "mana": 3 }, { "spec": 1, "health": 8, "mana": 3 } ], "5": [ { "training": 1, "health": 6, "mana": 2, "abilities": 2 }, { "training": 1, "shaping": 1, "health": 9, "mana": 5 }, { "training": 2, "health": 8, "mana": 3 } ],
"6": [ { "training": 1, "health": 2, "mana": 2, "spec": 1 }, { "training": 2 } ], "6": [ { "training": 1, "health": 3, "mana": 3 }, { "training": 1, "abilities": 3 } ],
"7": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ], "7": [ { "training": 2, "health": 4, "mana": 6 }, { "training": 2, "health": 6, "mana": 2 } ],
"8": [ { "training": 1 }, { "health": 8, "mana": 8 } ], "8": [ { "training": 3 }, { "training": 1, "health": 8, "mana": 8 } ],
"9": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ], "9": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 3, "mana": 1, "abilities": 2 } ],
"10": [ { "training": 2 }, { "training": 1, "shaping": 1 }, { "modifier": 1 } ], "10": [ { "training": 2 }, { "training": 1, "shaping": 1, "abilities": 2 }, { "modifier": 1, "abilities": 1 } ],
"11": [ { "training": 1, "health": 8, "mana": 1 }, { "training": 1, "health": 3, "mana": 5 } ], "11": [ { "training": 1, "health": 8, "mana": 1 }, { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "abilities": 2 } ],
"12": [ { "training": 1, "health": 4, "mana": 2 }, { "training": 1, "health": 8 }, { "training": 1, "mana": 7 } ], "12": [ { "training": 2, "health": 4, "mana": 2 }, { "training": 2, "health": 8 }, { "training": 2, "mana": 7 } ],
"13": [ { "spec": 1, "health": 2, "mana": 2 }, { "shaping": 1, "health": 2, "mana": 2 } ], "13": [ { "training": 1, "health": 2, "mana": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "health": 4, "mana": 4 } ],
"14": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ], "14": [ { "training": 3, "health": 4, "mana": 4 }, { "training": 3, "health": 6, "mana": 2 } ],
"15": [ { "training": 1 }, { "health": 8, "mana": 8 } ], "15": [ { "training": 1 }, { "health": 6, "mana": 6, "abilities": 1 } ],
"16": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ], "16": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ],
"17": [ { "training": 1, "spec": 1 }, { "training": 1, "shaping": 1 }, { "training": 1, "health": 6, "mana": 4 } ], "17": [ { "training": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "abilities": 2 }, { "training": 1, "health": 6, "mana": 4, "abilities": 1 } ],
"18": [ { "training": 1, "health": 6, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 } ], "18": [ { "training": 1, "health": 6, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 } ],
"19": [ { "training": 1, "health": 6, "mana": 2 }, { "training": 1, "health": 3, "mana": 5 } ], "19": [ { "training": 2, "health": 6, "mana": 2, "abilities": 1 }, { "training": 2, "health": 3, "mana": 5, "abilities": 1 } ],
"20": [ { "training": 2 }, { "modifier": 1 } ] "20": [ { "training": 2 }, { "modifier": 1, "abilities": 1 } ]
} }
} }
], ],
@ -1771,6 +1783,15 @@
} }
], ],
"ability": 4 "ability": 4
},
{
"description": [
{
"text": "+1 spécialisation.",
"disposable": true
}
],
"spec": 1
} }
], ],
"5": [ "5": [
@ -1809,39 +1830,6 @@
} }
], ],
"6": [ "6": [
{
"description": [
{
"text": "À chaque augmentation de modifieur, vous pouvez ajouter 1 point à n'importe quel compétence dont le maximum a été augmenté.",
"disposable": false
}
]
},
{
"description": [
{
"text": "En combat, vous pouvez lancer 2 [[1. Règles/99. Annexes/4. Équipement#Les armes de jet|armes de jet]] en 3 points d'action.",
"disposable": false,
"category": "action"
},
{
"text": "+1 point de compétence.",
"disposable": false
}
],
"ability": 1
},
{
"description": [
{
"text": "+3 points de compétence.",
"disposable": true
}
],
"ability": 3
}
],
"7": [
{ {
"description": [ "description": [
{ {
@ -1858,10 +1846,20 @@
{ {
"description": [ "description": [
{ {
"text": "Lorsque vous utilisez un objet consommable, vous pouvez lancer un d12. Si vous faites 12, l'objet ne se consomme pas. Cela fonctionne également sur les objets à charges.", "text": "En combat, vous pouvez lancer 2 [[1. Règles/99. Annexes/4. Équipement#Les armes de jet|armes de jet]] en 3 points d'action.",
"disposable": false,
"category": "action"
},
{
"text": "Vous êtes capable de fabriquer des objets magiques d'une rareté accrue.",
"disposable": false "disposable": false
},
{
"text": "+1 point de compétence.",
"disposable": true
} }
] ],
"ability": 1
}, },
{ {
"description": [ "description": [
@ -1877,7 +1875,15 @@
"ability": 2 "ability": 2
} }
], ],
"8": [ "7": [
{
"description": [
{
"text": "Lorsque vous utilisez un objet consommable, vous pouvez lancer un d12. Si vous faites 12, l'objet ne se consomme pas. Cela fonctionne également sur les objets à charges.",
"disposable": false
}
]
},
{ {
"description": [ "description": [
{ {
@ -1886,6 +1892,17 @@
} }
] ]
}, },
{
"description": [
{
"text": "+1 spécialisation.",
"disposable": true
}
],
"spec": 1
}
],
"8": [
{ {
"description": [ "description": [
{ {
@ -1897,22 +1914,24 @@
{ {
"description": [ "description": [
{ {
"text": "+4 points de compétence.", "text": "+3 points de compétence.",
"disposable": true "disposable": true
} }
], ],
"ability": 4 "ability": 3
} },
],
"9": [
{ {
"description": [ "description": [
{ {
"text": "", "text": "Vous pouvez maitriser un nombre de [[1. Magie|sorts]] et d'[[7. Œuvres|œuvres]] supplémentaires égal à votre modifieur de curiosité.",
"disposable": false "disposable": true
} }
] ],
}, "spellslot": "curiosity",
"arts": "curiosity"
}
],
"9": [
{ {
"description": [ "description": [
{ {
@ -1950,22 +1969,14 @@
{ {
"description": [ "description": [
{ {
"text": "+4 points de compétence.", "text": "+1 spécialisation.",
"disposable": true "disposable": true
} }
], ],
"ability": 4 "spec": 1
} }
], ],
"11": [ "11": [
{
"description": [
{
"text": "",
"disposable": false
}
]
},
{ {
"description": [ "description": [
{ {
@ -2236,9 +2247,10 @@
"description": [ "description": [
{ {
"text": "Vous pouvez retenir un nombre d'œuvre supplémentaire égal à votre mod. de charisme.", "text": "Vous pouvez retenir un nombre d'œuvre supplémentaire égal à votre mod. de charisme.",
"disposable": false "disposable": true
} }
] ],
"arts": "charisma"
} }
], ],
"8": [ "8": [

29
types/character.d.ts vendored
View File

@ -2,7 +2,7 @@ export type MainStat = "strength" | "dexterity" | "constitution" | "intelligence
export type Ability = "athletics" | "acrobatics" | "intimidation" | "sleightofhand" | "stealth" | "survival" | "investigation" | "history" | "religion" | "arcana" | "understanding" | "perception" | "performance" | "medecine" | "persuasion" | "animalhandling" | "deception"; export type Ability = "athletics" | "acrobatics" | "intimidation" | "sleightofhand" | "stealth" | "survival" | "investigation" | "history" | "religion" | "arcana" | "understanding" | "perception" | "performance" | "medecine" | "persuasion" | "animalhandling" | "deception";
export type Level = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20; export type Level = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
export type TrainingLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; export type TrainingLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
export type SpellType = "precision" | "knowledge" | "instinct"; export type SpellType = "precision" | "knowledge" | "instinct" | "arts";
export type Category = "action" | "reaction" | "freeaction" | "misc"; export type Category = "action" | "reaction" | "freeaction" | "misc";
export type Resistance = keyof CompiledCharacter["resistance"]; export type Resistance = keyof CompiledCharacter["resistance"];
@ -30,13 +30,18 @@ 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>; abilities: Record<Ability, AbilityConfig>;
resistances: Record<Resistance, ResistanceConfig>;
}; };
export type AbilityConfig = { export type AbilityConfig = {
max: [MainStat, MainStat]; max: [MainStat, MainStat];
name: string; name: string;
description: string; description: string;
} };
export type ResistanceConfig = {
name: string;
statistic: MainStat;
};
export type Race = { export type Race = {
name: string; name: string;
description: string; description: string;
@ -49,9 +54,9 @@ export type RaceOption = {
training?: number; training?: number;
health?: number; health?: number;
mana?: number; mana?: number;
spec?: number;
shaping?: number; shaping?: number;
modifier?: number; modifier?: number;
abilities?: number;
}; };
export type Feature = { export type Feature = {
text?: string; text?: string;
@ -86,17 +91,24 @@ export type TrainingOption = {
replaced?: boolean; replaced?: boolean;
category?: Category; category?: Category;
}>; }>;
//Automatically calculated by compiler
mana?: number; mana?: number;
health?: number; health?: number;
modifier?: number;
ability?: number;
speed?: false | number; speed?: false | number;
initiative?: number; initiative?: number;
mastery?: keyof CompiledCharacter["mastery"]; mastery?: keyof CompiledCharacter["mastery"];
spellrank?: SpellType; spellrank?: SpellType;
spellslot?: number | MainStat;
defense?: Array<keyof CompiledCharacter["defense"]>; defense?: Array<keyof CompiledCharacter["defense"]>;
resistance?: [Resistance, "attack" | "defense"][]; resistance?: [Resistance, "attack" | "defense"][];
//Used during character creation, not used by compiler
modifier?: number;
ability?: number;
spec?: number;
spellslot?: number | MainStat;
arts?: number | MainStat;
features?: Feature[]; //TODO features?: Feature[]; //TODO
}; };
export type CompiledCharacter = { export type CompiledCharacter = {
@ -108,6 +120,7 @@ export type CompiledCharacter = {
mana: number; mana: number;
race: number; race: number;
spellslots: number; spellslots: number;
artslots: number;
spellranks: Record<SpellType, 0 | 1 | 2 | 3>; spellranks: Record<SpellType, 0 | 1 | 2 | 3>;
aspect: string; aspect: string;
speed: number | false; speed: number | false;
@ -135,7 +148,7 @@ export type CompiledCharacter = {
resistance: { //First is attack, second is defense resistance: { //First is attack, second is defense
stun: [number, number]; stun: [number, number];
bleed: [number, number]; bleed: [number, number];
posion: [number, number]; poison: [number, number];
fear: [number, number]; fear: [number, number];
influence: [number, number]; influence: [number, number];
charm: [number, number]; charm: [number, number];