Spell selection in creator + rebalancing
This commit is contained in:
parent
878bcc0a16
commit
3f58114091
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<Label class="my-2 flex flex-1 items-center justify-between flex-col md:flex-row">
|
||||
<span class="pb-1 md:p-0">{{ label }}</span>
|
||||
<ComboboxRoot v-model:model-value="model" v-model:open="open" :multiple="multiple">
|
||||
<ComboboxAnchor :disabled="disabled" class="mx-4 inline-flex min-w-[150px] items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1
|
||||
bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 outline-none data-[placeholder]:font-normal
|
||||
data-[placeholder]:text-light-50 dark:data-[placeholder]:text-dark-50 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40
|
||||
hover:border-light-50 dark:hover:border-dark-50">
|
||||
<ComboboxTrigger class="flex flex-1 justify-between !cursor-pointer">
|
||||
<span v-if="!multiple">{{ model !== undefined ? options.find(e => e[1] === model)![0] : "" }}</span>
|
||||
<span class="flex gap-2" v-else><span v-if="model !== undefined">{{ options.find(e => e[1] === (model as T[])[0]) !== undefined ? options.find(e => e[1] === (model as T[])[0])![0] : undefined }}</span><span v-if="model !== undefined && (model as T[]).length > 1">{{((model as T[]).length > 1 ? `+${(model as T[]).length - 1}` : "") }}</span></span>
|
||||
<Icon icon="radix-icons:caret-down" class="h-4 w-4" />
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxPortal :disabled="disabled">
|
||||
<ComboboxContent :position="position" align="start" class="min-w-[150px] bg-light-20 dark:bg-dark-20 will-change-[opacity,transform] z-50">
|
||||
<ComboboxViewport>
|
||||
<ComboboxItem v-for="[label, value] of options" :value="value" :disabled="disabled" class="text-base py-2 leading-none text-light-60 dark:text-dark-60 flex items-center px-6 relative Combobox-none data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-light-30 dark:data-[highlighted]:bg-dark-30 data-[highlighted]:text-light-100 dark:data-[highlighted]:text-dark-100">
|
||||
<span class="">{{ label }}</span>
|
||||
<ComboboxItemIndicator class="absolute left-1 w-4 inline-flex items-center justify-center">
|
||||
<Icon icon="radix-icons:check" />
|
||||
</ComboboxItemIndicator>
|
||||
</ComboboxItem>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends string | number | boolean | Record<string, any>">
|
||||
import { ComboboxInput, ComboboxTrigger, ComboboxViewport, ComboboxContent, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
const { disabled = false, position = 'popper', multiple = false } = defineProps<{
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
position?: 'inline' | 'popper'
|
||||
label?: string
|
||||
multiple?: boolean
|
||||
options: Array<[string, T]>
|
||||
}>();
|
||||
const open = ref(false);
|
||||
const model = defineModel<T | T[]>();
|
||||
</script>
|
||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import config from '#shared/character-config.json';
|
||||
|
||||
function raceOptionToText(option: RaceOption): string
|
||||
{
|
||||
const text = [];
|
||||
|
|
@ -9,6 +10,7 @@ function raceOptionToText(option: RaceOption): string
|
|||
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.mana) text.push(`+${option.mana} mana max.`);
|
||||
if(option.spellslots) text.push(`+${option.spellslots} sort${option.spellslots > 1 ? 's' : ''} maitrisé${option.spellslots > 1 ? 's' : ''}.`);
|
||||
return text.join('\n');
|
||||
}
|
||||
function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]): TrainingOption[]
|
||||
|
|
@ -17,15 +19,7 @@ function getFeaturesOf(stat: MainStat, progression: DoubleIndex<TrainingLevel>[]
|
|||
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é",
|
||||
}
|
||||
|
||||
|
||||
function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<TrainingLevel>[], value: number): number
|
||||
{
|
||||
|
|
@ -43,7 +37,7 @@ function abilitySpecialFeatures(type: "points" | "max", curiosity: DoubleIndex<T
|
|||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import { clamp } from '~/shared/general.util';
|
||||
import type { Ability, Character, CharacterConfig, DoubleIndex, Level, MainStat, RaceOption, SpellType, TrainingLevel, TrainingOption } from '~/types/character';
|
||||
import { elementTexts, mainStatTexts, spellTypeTexts, type Ability, type Character, type CharacterConfig, type DoubleIndex, type Level, type MainStat, type RaceOption, type SpellConfig, type SpellElement, type SpellType, type TrainingLevel, type TrainingOption } from '~/types/character';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
|
|
@ -75,6 +69,19 @@ const data = ref<Character>({
|
|||
notes: "",
|
||||
},
|
||||
});
|
||||
const spellFilter = ref<{
|
||||
ranks: Array<1 | 2 | 3>,
|
||||
types: Array<SpellType>,
|
||||
text: string,
|
||||
elements: Array<SpellElement>,
|
||||
tags: string[],
|
||||
}>({
|
||||
ranks: [],
|
||||
types: [],
|
||||
text: "",
|
||||
elements: [],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
const peopleOpen = ref(false), trainingOpen = ref(false), abilityOpen = ref(false), spellOpen = ref(false), notesOpen = ref(false), trainingTab = ref(0);
|
||||
const raceOptions = computed(() => data.value.progress.race.index !== undefined ? characterConfig.peoples[data.value.progress.race.index!].options : undefined);
|
||||
|
|
@ -101,7 +108,7 @@ if(id !== 'new')
|
|||
throw new Error('Donnée du personnage introuvables');
|
||||
}
|
||||
|
||||
data.value = { name: character.name, progress: character.progress } as Character;
|
||||
data.value = { name: character.name, progress: Object.assign(data.value.progress, character.progress) } as Character;
|
||||
}
|
||||
|
||||
function selectRaceOption(level: Level, choice: number)
|
||||
|
|
@ -183,6 +190,19 @@ function updateLevel()
|
|||
|
||||
data.value = character;
|
||||
}
|
||||
function filterSpells(spells: SpellConfig[])
|
||||
{
|
||||
const filter = spellFilter.value
|
||||
let list = [...spells];
|
||||
list = list.filter(e => spellranks.value[e.type] >= e.rank);
|
||||
if(filter.text.length > 0) list = list.filter(e => e.name.toLowerCase().includes(filter.text.toLowerCase()));
|
||||
if(filter.types.length > 0) list = list.filter(e => filter.types.includes(e.type));
|
||||
if(filter.ranks.length > 0) list = list.filter(e => filter.ranks.includes(e.rank));
|
||||
if(filter.elements.length > 0) list = list.filter(e => filter.elements.some(f => e.elements.includes(f)));
|
||||
if(filter.tags.length > 0) list = list.filter(e => !e.tags || filter.tags.some(f => e.tags!.includes(f)));
|
||||
|
||||
return list;
|
||||
}
|
||||
async function save(leave: boolean)
|
||||
{
|
||||
if(data.value.name === '' || data.value.progress.race.index === undefined || data.value.progress.race.index === -1)
|
||||
|
|
@ -257,9 +277,7 @@ useShortcuts({
|
|||
</template>
|
||||
<template #default>
|
||||
<div class="m-2 overflow-auto">
|
||||
<Select label="Peuple de votre personnage" :v-model="data.progress.race.index" :default-value="data.progress.race.index?.toString() ?? ''" @update:model-value="(index) => { data.progress.race.index = parseInt(index ?? '-1'); data.progress.race.progress = [[1, 0]]}">
|
||||
<SelectItem v-for="(people, index) of characterConfig.peoples" :label="people.name" :value="index.toString()" :key="index" />
|
||||
</Select>
|
||||
<Combobox label="Peuple de votre personnage" :v-model="data.progress.race.index!" :default-value="data.progress.race.index" :options="config.peoples.map((people, index) => [people.name, index])" @update:model-value="(index) => { data.progress.race.index = index as number | undefined; data.progress.race.progress = [[1, 0]]}" />
|
||||
<template v-if="data.progress.race.index !== undefined">
|
||||
<div class="w-full border-b border-light-30 dark:border-dark-30 pb-4">
|
||||
<span class="text-sm text-light-70 dark:text-dark-70">{{ characterConfig.peoples[data.progress.race.index].description }}</span>
|
||||
|
|
@ -336,11 +354,32 @@ useShortcuts({
|
|||
</template>
|
||||
<template #default>
|
||||
<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': spellsPoints < (data.progress.spells?.length ?? 0) }">Sorts disponibles: {{ spellsPoints - (data.progress.spells?.length ?? 0) }}</span>
|
||||
<div class="sticky top-0 py-2 bg-light-0 dark:bg-dark-0 z-10 flex gap-2 items-center">
|
||||
<span class="text-xl pe-4" :class="{ 'text-light-red dark:text-dark-red': spellsPoints < (data.progress.spells?.length ?? 0) }">Sorts: {{ data.progress.spells?.length ?? 0 }}/{{ spellsPoints }}</span>
|
||||
<TextInput label="Nom" v-model="spellFilter.text" />
|
||||
<Combobox label="Rang" v-model="spellFilter.ranks" multiple :options="[['Rang 1', 1], ['Rang 2', 2], ['Rang 3', 3]]" />
|
||||
<Combobox label="Type" v-model="spellFilter.types" multiple :options="[['Précision', 'precision'], ['Savoir', 'knowledge'], ['Instinct', 'instinct']]" />
|
||||
<Combobox label="Element" v-model="spellFilter.elements" multiple :options="[['Feu', 'fire'], ['Glace', 'ice'], ['Foudre', 'thunder'], ['Terre', 'earth'], ['Arcane', 'arcana'], ['Air', 'air'], ['Nature', 'nature'], ['Lumière', 'light'], ['Psy', 'psyche']]" />
|
||||
</div>
|
||||
<div class="grid gap-4 grid-cols-6">
|
||||
|
||||
<div class="grid gap-4 grid-cols-2">
|
||||
<div class="py-1 px-2 border border-light-30 dark:border-dark-30 flex flex-col hover:border-light-50 dark:hover:border-dark-50 cursor-pointer" v-for="spell of filterSpells(config.spells)" :class="{ '!border-accent-blue bg-accent-blue bg-opacity-20': data.progress.spells?.find(e => e === spell.id) }"
|
||||
@click="() => data.progress.spells?.includes(spell.id) ? data.progress.spells.splice(data.progress.spells.findIndex((e: string) => e === spell.id), 1) : data.progress.spells!.push(spell.id)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
import config from '#shared/character-config.json';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import PreviewA from '~/components/prose/PreviewA.vue';
|
||||
import type { SpellConfig } from '~/types/character';
|
||||
import { elementTexts, spellTypeTexts, type CharacterConfig } from '~/types/character';
|
||||
|
||||
const characterConfig = config as CharacterConfig;
|
||||
|
||||
const id = useRouter().currentRoute.value.params.id;
|
||||
const { user } = useUserSession();
|
||||
|
|
@ -28,7 +32,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold">Niveau {{ character.level }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : config.peoples[character.race].name }}</span>
|
||||
<span>{{ character.race === -1 ? "Race inconnue" : characterConfig.peoples[character.race].name }}</span>
|
||||
</div>
|
||||
<span class="h-full border-l border-light-30 dark:border-dark-30"></span>
|
||||
<span>PV: {{ character.health }}</span>
|
||||
|
|
@ -64,7 +68,7 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 px-8">
|
||||
<div class="flex flex-col pe-8 gap-4 py-8 w-80">
|
||||
<div class="flex flex-col pe-8 gap-4 py-8 w-80 border-r border-light-30 dark:border-dark-30">
|
||||
<div class="flex flex-col">
|
||||
<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">
|
||||
|
|
@ -99,43 +103,77 @@ const { data: character, status, error } = await useAsyncData(() => useRequestFe
|
|||
<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 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[characterConfig.resistances[resistance].statistic as MainStat] }}</span><span>{{ characterConfig.resistances[resistance].name }}</span></div>
|
||||
</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 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 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>{{ characterConfig.abilities[ability].name }}</span></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="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
<TabsRoot default-value="features" class="w-[60rem]">
|
||||
<TabsList class="flex flex-row gap-4 relative px-4">
|
||||
<TabsIndicator class="absolute px-8 left-0 h-[3px] bottom-0 w-[--radix-tabs-indicator-size] translate-x-[--radix-tabs-indicator-position] transition-[width,transform] duration-300 bg-accent-blue"></TabsIndicator>
|
||||
<TabsTrigger value="features" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Aptitudes</TabsTrigger>
|
||||
<TabsTrigger value="spells" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Sorts</TabsTrigger>
|
||||
<TabsTrigger value="notes" class="px-2 py-1 border-b border-transparent hover:border-accent-blue">Notes</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="features">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.action.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.reaction.join('\n')" />
|
||||
</TabsContent>
|
||||
<TabsContent v-if="character.spells.length > 0" value="spells">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<div class="flex flex-col">
|
||||
<div class="pb-4 px-2 mt-4 border-b last:border-none border-light-30 dark:border-dark-30 flex flex-col" v-for="spell of character.spells.map(e => characterConfig.spells.find((f: SpellConfig) => f.id === e)).filter(e => !!e)">
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-lg font-bold">{{ spell.name }}</span>
|
||||
<div class="flex flex-row items-center gap-6">
|
||||
<div class="flex flex-row text-sm gap-2">
|
||||
<span v-for="element of spell.elements" :class="elementTexts[element].class">{{ elementTexts[element].text }}</span>
|
||||
</div>
|
||||
<div class="flex flex-row text-sm gap-1">
|
||||
<span class="">Rang {{ spell.rank }}</span><span>/</span>
|
||||
<span class="">{{ spellTypeTexts[spell.type] }}</span><span>/</span>
|
||||
<span class="">{{ spell.cost }} mana</span><span>/</span>
|
||||
<span class="">{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownRenderer :content="spell.effect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<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>
|
||||
<MarkdownRenderer :content="character.features.freeaction.join('\n')" />
|
||||
</TabsContent>
|
||||
<TabsContent value="notes">
|
||||
<div class="flex flex-1 flex-col ps-8 gap-4 py-8">
|
||||
<MarkdownRenderer :content="character.notes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold">Aptitudes</span>
|
||||
<MarkdownRenderer :content="character.features.misc.map(e => `> ${e}`).join('\n\n')" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-lg font-semibold border-b border-light-30 dark:border-dark-30">Notes</span>
|
||||
<MarkdownRenderer :content="character.notes" />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</TabsRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ function compileCharacter(character: Character & { username?: string }): Compile
|
|||
precision: 0,
|
||||
arts: 0,
|
||||
},
|
||||
spells: character.progress.spells ?? [],
|
||||
speed: false,
|
||||
defense: {
|
||||
static: 6,
|
||||
|
|
@ -215,6 +216,7 @@ function applyTrainingOption(stat: MainStat, option: TrainingOption, character:
|
|||
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));
|
||||
|
||||
|
|
|
|||
|
|
@ -39,24 +39,24 @@
|
|||
},
|
||||
"options": {
|
||||
"1": [ { "training": 35, "health": 14 } ],
|
||||
"2": [ { "training": 1, "health": 4, "mana": 2 }, { "health": 7, "mana": 4, "abilities": 1 } ],
|
||||
"3": [ { "training": 2, "health": 4, "mana": 2, "abilities": 1 } ],
|
||||
"2": [ { "training": 1, "health": 3, "mana": 2 }, { "health": 6, "mana": 3, "abilities": 1 } ],
|
||||
"3": [ { "training": 2, "health": 3, "mana": 1, "abilities": 1 } ],
|
||||
"4": [ { "training": 1, "health": 4, "mana": 2, "abilities": 2 } ],
|
||||
"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": 3, "mana": 3 }, { "training": 1, "abilities": 3 } ],
|
||||
"7": [ { "training": 2, "health": 4, "mana": 6 }, { "training": 2, "health": 6, "mana": 2 } ],
|
||||
"8": [ { "training": 3 }, { "training": 1, "health": 8, "mana": 8 } ],
|
||||
"9": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 3, "mana": 1, "abilities": 2 } ],
|
||||
"5": [ { "training": 1, "health": 4, "mana": 2, "abilities": 2 }, { "training": 1, "shaping": 1, "health": 8, "mana": 4 }, { "training": 2, "health": 7, "mana": 2 } ],
|
||||
"6": [ { "training": 1, "health": 3, "mana": 3 }, { "training": 1, "abilities": 3, "spellslots": 1 } ],
|
||||
"7": [ { "training": 2, "health": 3, "mana": 5 }, { "training": 2, "health": 5, "mana": 2 } ],
|
||||
"8": [ { "training": 3 }, { "training": 1, "health": 6, "mana": 6, "spellslots": 1 } ],
|
||||
"9": [ { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "health": 2, "abilities": 2 } ],
|
||||
"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 }, { "training": 1, "abilities": 2 } ],
|
||||
"12": [ { "training": 2, "health": 4, "mana": 2 }, { "training": 2, "health": 8 }, { "training": 2, "mana": 7 } ],
|
||||
"11": [ { "training": 1, "health": 7, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 }, { "training": 1, "abilities": 2 } ],
|
||||
"12": [ { "training": 2, "spellslots": 1 }, { "training": 2, "health": 8 }, { "training": 2, "mana": 7 } ],
|
||||
"13": [ { "training": 1, "health": 2, "mana": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "health": 4, "mana": 4 } ],
|
||||
"14": [ { "training": 3, "health": 4, "mana": 4 }, { "training": 3, "health": 6, "mana": 2 } ],
|
||||
"15": [ { "training": 1 }, { "health": 6, "mana": 6, "abilities": 1 } ],
|
||||
"16": [ { "training": 1, "health": 4, "mana": 6 }, { "training": 1, "health": 6, "mana": 2 } ],
|
||||
"17": [ { "training": 2, "abilities": 1 }, { "training": 1, "shaping": 1, "abilities": 2 }, { "training": 1, "health": 6, "mana": 4, "abilities": 1 } ],
|
||||
"14": [ { "training": 3, "health": 3, "mana": 5 }, { "training": 3, "health": 6, "mana": 1 } ],
|
||||
"15": [ { "training": 1 }, { "health": 5, "mana": 5, "abilities": 1 } ],
|
||||
"16": [ { "training": 1, "health": 3, "mana": 5 }, { "training": 1, "health": 5, "mana": 2 } ],
|
||||
"17": [ { "training": 2, "abilities": 1, "spellslots": 1 }, { "training": 1, "shaping": 1, "abilities": 2, "spellslots": 1 }, { "training": 1, "health": 7, "mana": 5, "abilities": 1 } ],
|
||||
"18": [ { "training": 1, "health": 6, "mana": 1 }, { "training": 1, "health": 2, "mana": 5 } ],
|
||||
"19": [ { "training": 2, "health": 6, "mana": 2, "abilities": 1 }, { "training": 2, "health": 3, "mana": 5, "abilities": 1 } ],
|
||||
"19": [ { "training": 2, "health": 6, "mana": 3, "abilities": 2 }, { "training": 2, "health": 2, "mana": 5, "spellslots": 1 } ],
|
||||
"20": [ { "training": 2 }, { "modifier": 1, "abilities": 1 } ]
|
||||
}
|
||||
}
|
||||
|
|
@ -1555,10 +1555,11 @@
|
|||
{
|
||||
"description": [
|
||||
{
|
||||
"text": "",
|
||||
"disposable": false
|
||||
"text": "Vous avez un bonus de +1 aux jets de résistance des [[1. Magie#Les sorts de savoir|sorts de savoir]] en tant qu'attaquant.",
|
||||
"disposable": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"resistance": [["knowledge", "attack"]]
|
||||
}
|
||||
],
|
||||
"11": [
|
||||
|
|
|
|||
|
|
@ -4,10 +4,33 @@ export type Level = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
|
|||
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" | "arts";
|
||||
export type Category = "action" | "reaction" | "freeaction" | "misc";
|
||||
export type SpellElement = "fire" | "ice" | "thunder" | "earth" | "arcana" | "air" | "nature" | "light" | "psyche";
|
||||
export type Resistance = keyof CompiledCharacter["resistance"];
|
||||
|
||||
export type DoubleIndex<T extends number | string> = [T, number];
|
||||
|
||||
export const mainStatTexts: Record<MainStat, string> = {
|
||||
"strength": "Force",
|
||||
"dexterity": "Dextérité",
|
||||
"constitution": "Constitution",
|
||||
"intelligence": "Intelligence",
|
||||
"curiosity": "Curiosité",
|
||||
"charisma": "Charisme",
|
||||
"psyche": "Psyché",
|
||||
}
|
||||
export const elementTexts: Record<SpellElement, { class: string, text: string }> = {
|
||||
fire: { class: 'text-light-red dark:text-dark-red', text: 'Feu' },
|
||||
ice: { class: 'text-light-blue dark:text-dark-blue', text: 'Glace' },
|
||||
thunder: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Foudre' },
|
||||
earth: { class: 'text-light-orange dark:text-dark-orange', text: 'Terre' },
|
||||
arcana: { class: 'text-light-purple dark:text-dark-purple', text: 'Arcane' },
|
||||
air: { class: 'text-light-green dark:text-dark-green', text: 'Air' },
|
||||
nature: { class: 'text-light-green dark:text-dark-green', text: 'Nature' },
|
||||
light: { class: 'text-light-yellow dark:text-dark-yellow', text: 'Lumière' },
|
||||
psyche: { class: 'text-light-purple dark:text-dark-purple', text: 'Psy' },
|
||||
}
|
||||
export const spellTypeTexts: Record<SpellType, string> = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision" };
|
||||
|
||||
export type Progression = {
|
||||
training: Record<MainStat, DoubleIndex<TrainingLevel>[]>;
|
||||
race: {
|
||||
|
|
@ -41,7 +64,7 @@ export type SpellConfig = {
|
|||
type: SpellType;
|
||||
cost: number;
|
||||
speed: "action" | "reaction" | number;
|
||||
elements: Array<"fire" | "ice" | "thunder" | "earth" | "arcana" | "air" | "nature" | "light" | "psyche">;
|
||||
elements: Array<SpellElement>;
|
||||
effect: string;
|
||||
tags?: string[];
|
||||
};
|
||||
|
|
@ -69,6 +92,7 @@ export type RaceOption = {
|
|||
shaping?: number;
|
||||
modifier?: number;
|
||||
abilities?: number;
|
||||
spellslots?: number;
|
||||
};
|
||||
export type Feature = {
|
||||
text?: string;
|
||||
|
|
@ -113,6 +137,7 @@ export type TrainingOption = {
|
|||
spellrank?: SpellType;
|
||||
defense?: Array<keyof CompiledCharacter["defense"]>;
|
||||
resistance?: [Resistance, "attack" | "defense"][];
|
||||
spell?: string;
|
||||
|
||||
//Used during character creation, not used by compiler
|
||||
modifier?: number;
|
||||
|
|
@ -137,6 +162,7 @@ export type CompiledCharacter = {
|
|||
aspect: string;
|
||||
speed: number | false;
|
||||
initiative: number;
|
||||
spells: string[];
|
||||
|
||||
defense: {
|
||||
static: number;
|
||||
Loading…
Reference in New Issue