@@ -167,8 +172,8 @@ text-light-purple dark:text-dark-purple border-light-purple dark:border-dark-pur
{{ elementTexts[element].text }}
- Rang {{ spell.rank }}/
- {{ spellTypeTexts[spell.type] }}/
+ Rang {{ spell.rank }}/
+ {{ spellTypeTexts[spell.type] }}/
{{ spell.cost }} mana/
{{ typeof spell.speed === 'string' ? spell.speed : `${spell.speed} minutes` }}
diff --git a/pages/character/index.client.vue b/pages/character/index.client.vue
index 9e75d61..91af145 100644
--- a/pages/character/index.client.vue
+++ b/pages/character/index.client.vue
@@ -1,12 +1,11 @@
diff --git a/pages/explore/edit/index.vue b/pages/explore/edit/index.vue
index 8fdc645..a619549 100644
--- a/pages/explore/edit/index.vue
+++ b/pages/explore/edit/index.vue
@@ -39,13 +39,13 @@ import { Content, Editor } from '#shared/content.util';
import { button, loading } from '#shared/components.util';
import { dom, icon, text } from '#shared/dom.util';
import { modal, popper, tooltip } from '#shared/floating.util';
+import { Toaster } from '#shared/components.util';
definePageMeta({
rights: ['admin', 'editor'],
layout: 'null',
});
-const toaster = useToast();
const { user } = useUserSession();
const tree = useTemplateRef('tree'), container = useTemplateRef('container');
let editor: Editor;
@@ -53,9 +53,9 @@ let editor: Editor;
function pull()
{
Content.pull().then(e => {
- toaster.add({ type: 'success', content: 'Données mises à jour avec succès.', timer: true, duration: 7500 });
+ Toaster.add({ type: 'success', content: 'Données mises à jour avec succès.', timer: true, duration: 7500 });
}).catch(e => {
- toaster.add({ type: 'success', content: 'Une erreur est survenue durant la récupération des données.', timer: true, duration: 7500 });
+ Toaster.add({ type: 'success', content: 'Une erreur est survenue durant la récupération des données.', timer: true, duration: 7500 });
console.error(e);
});
}
@@ -64,10 +64,10 @@ function push()
const { close } = modal([dom('div', { class: 'flex flex-col gap-4 justify-center items-center' }, [ dom('div', { class: 'text-xl', text: 'Mise à jour des données' }), loading('large') ])], { priority: false, closeWhenOutside: true, });
Content.push().then(e => {
close();
- toaster.add({ type: 'success', content: 'Données mises à jour avec succès.', timer: true, duration: 7500 });
+ Toaster.add({ type: 'success', content: 'Données mises à jour avec succès.', timer: true, duration: 7500 });
}).catch(e => {
close();
- toaster.add({ type: 'success', content: 'Une erreur est survenue durant l\'enregistrement des données.', timer: true, duration: 7500 });
+ Toaster.add({ type: 'success', content: 'Une erreur est survenue durant l\'enregistrement des données.', timer: true, duration: 7500 });
console.error(e);
});
}
diff --git a/pages/user/(automatic)/reset-password.vue b/pages/user/(automatic)/reset-password.vue
index 562467f..25da817 100644
--- a/pages/user/(automatic)/reset-password.vue
+++ b/pages/user/(automatic)/reset-password.vue
@@ -25,8 +25,6 @@ definePageMeta({
usersGoesTo: '/user/profile',
});
-const toaster = useToast();
-
const email = ref(''), status = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
async function submit()
diff --git a/pages/user/(automatic)/resetting-password.vue b/pages/user/(automatic)/resetting-password.vue
index e8ae7fd..8779c75 100644
--- a/pages/user/(automatic)/resetting-password.vue
+++ b/pages/user/(automatic)/resetting-password.vue
@@ -25,6 +25,7 @@
\ No newline at end of file
diff --git a/pages/user/changing-password.vue b/pages/user/changing-password.vue
index a51c338..d6cc944 100644
--- a/pages/user/changing-password.vue
+++ b/pages/user/changing-password.vue
@@ -26,13 +26,13 @@
\ No newline at end of file
diff --git a/pages/user/login.vue b/pages/user/login.vue
index 9a1ef45..a42e1d0 100644
--- a/pages/user/login.vue
+++ b/pages/user/login.vue
@@ -21,14 +21,13 @@
import type { ZodError } from 'zod/v4';
import { schema, type Login } from '~/schemas/login';
import { Icon } from '@iconify/vue/dist/iconify.js';
+import { Toaster } from '#shared/components.util';
definePageMeta({
layout: 'login',
usersGoesTo: '/user/profile',
});
-const { add: addToast, clear: clearToasts } = useToast();
-
const state = reactive
({
usernameOrEmail: '',
password: ''
@@ -47,9 +46,9 @@ const toastMessage = ref('');
async function submit()
{
if(state.usernameOrEmail === "")
- return addToast({ content: 'Veuillez saisir un nom d\'utilisateur ou un email', timer: true, duration: 10000 });
+ return Toaster.add({ content: 'Veuillez saisir un nom d\'utilisateur ou un email', timer: true, duration: 10000 });
if(state.password === "")
- return addToast({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
+ return Toaster.add({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
const data = schema.safeParse(state);
@@ -64,8 +63,8 @@ async function submit()
}
else if(status.value === 'success' && login.success)
{
- clearToasts();
- addToast({ duration: 10000, content: 'Vous êtes maintenant connecté', timer: true, type: 'success' });
+ Toaster.clear();
+ Toaster.add({ duration: 10000, content: 'Vous êtes maintenant connecté', timer: true, type: 'success' });
await navigateTo('/user/profile');
}
}
@@ -85,12 +84,12 @@ function handleErrors(error: Error | ZodError)
{
for(const err of (error as ZodError).issues)
{
- return addToast({ content: err.message, timer: true, duration: 10000, type: 'error' });
+ return Toaster.add({ content: err.message, timer: true, duration: 10000, type: 'error' });
}
}
else
{
- return addToast({ content: error?.message ?? 'Une erreur est survenue', timer: true, duration: 10000, type: 'error' });
+ return Toaster.add({ content: error?.message ?? 'Une erreur est survenue', timer: true, duration: 10000, type: 'error' });
}
}
\ No newline at end of file
diff --git a/pages/user/profile.vue b/pages/user/profile.vue
index c6bacaa..75d4cf5 100644
--- a/pages/user/profile.vue
+++ b/pages/user/profile.vue
@@ -1,11 +1,11 @@
\ No newline at end of file
diff --git a/shared/character-config.json b/shared/character-config.json
index 624660e..58c1e7a 100644
--- a/shared/character-config.json
+++ b/shared/character-config.json
@@ -557,186 +557,6 @@
"fr_FR": "Vous pouvez vous transformer sans faire de jet avec 1 point d'action."
}
},
- "abilities": {
- "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": [
- "psyche",
- "intelligence"
- ],
- "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": "La capacité à convaincre et à argumenter avec un interlocuteur. Permet de négocier. "
- },
- "animalhandling": {
- "max": [
- "charisma",
- "psyche"
- ],
- "name": "Dressage",
- "description": "La capacité à dresser, à comprendre et à pacifier les animaux. Permet de pacifier et de convaincre les animaux."
- },
- "deception": {
- "max": [
- "charisma",
- "psyche"
- ],
- "name": "Mensonge",
- "description": "La capacité à dissimuler et à inviter de nouvelles vérités. Permet de mentir à un interlocuteur."
- }
- },
- "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": "Sorts de précision",
- "statistic": "dexterity"
- },
- "knowledge": {
- "name": "Sorts de savoir",
- "statistic": "intelligence"
- },
- "instinct": {
- "name": "Sorts d'instinct",
- "statistic": "psyche"
- }
- },
"lists": {
"sickness": [
{
@@ -5248,30 +5068,34 @@
"id": "9z2sgu5hklkh04hguawdn0w5cf4rgpxw",
"category": "choice",
"text": "Vous avez un bonus de +1 aux jets de résistance en ",
+ "settings": {
+ "amount": 2,
+ "exclusive": true
+ },
"options": [
{
"id": "sx1vca2kzustsjatvslbjl68guv45m0b",
"category": "value",
- "text": "",
+ "text": "Force",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/strength",
+ "value": 1
},
{
"id": "41mflh7px0otbj169q8mr5btc8qie18g",
"category": "value",
- "text": "",
+ "text": "Dextérité",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/dexterity",
+ "value": 1
},
{
"id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
"category": "value",
- "text": "",
+ "text": "Constitution",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/constitution",
+ "value": 1
}
]
}
@@ -5421,26 +5245,26 @@
{
"id": "sx1vca2kzustsjatvslbjl68guv45m0b",
"category": "value",
- "text": "",
+ "text": "Force",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/strength",
+ "value": 1
},
{
"id": "41mflh7px0otbj169q8mr5btc8qie18g",
"category": "value",
- "text": "",
+ "text": "Dextérité",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/dexterity",
+ "value": 1
},
{
"id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
"category": "value",
- "text": "",
+ "text": "Constitution",
"operation": "add",
- "property": "",
- "value": 0
+ "property": "bonus/defense/constitution",
+ "value": 1
}
]
}
@@ -6342,6 +6166,244 @@
"property": "itempower",
"operation": "add",
"value": "modifier/curiosity"
+ },
+ {
+ "id": "otqfrgxvg4lgzs97fwb38jclywho5u45",
+ "category": "value",
+ "property": "bonus/abilities/athletics",
+ "operation": "add",
+ "value": "modifier/strength"
+ },
+ {
+ "id": "y4rnemfjxff5nz4qzzlrcsr0ay717ej7",
+ "category": "value",
+ "property": "bonus/abilities/athletics",
+ "operation": "add",
+ "value": "modifier/constitution"
+ },
+ {
+ "id": "lq9wgvuuxoiy97n2ae7osgazq9cyxtwk",
+ "category": "value",
+ "property": "bonus/abilities/acrobatics",
+ "operation": "add",
+ "value": "modifier/strength"
+ },
+ {
+ "id": "ll7uuzhxatwh7uj5rebj8z633sjaw5gj",
+ "category": "value",
+ "property": "bonus/abilities/acrobatics",
+ "operation": "add",
+ "value": "modifier/dexterity"
+ },
+ {
+ "id": "gj99z3jsb9ygxokwizv1x73wkmxk92uu",
+ "category": "value",
+ "property": "bonus/abilities/intimidation",
+ "operation": "add",
+ "value": "modifier/strength"
+ },
+ {
+ "id": "ao8nqb6007iyqmxftlai3xl1okzueg1w",
+ "category": "value",
+ "property": "bonus/abilities/intimidation",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "6jg5iaptt3jb83s4hap95f2e2ox2byn9",
+ "category": "value",
+ "property": "bonus/abilities/sleightofhand",
+ "operation": "add",
+ "value": "modifier/dexterity"
+ },
+ {
+ "id": "9bmocid777yvmrg1iage1luqvgm7z395",
+ "category": "value",
+ "property": "bonus/abilities/sleightofhand",
+ "operation": "add",
+ "value": "modifier/dexterity"
+ },
+ {
+ "id": "w1gohbd8uw1ow5tena0lx0uld11mlx32",
+ "category": "value",
+ "property": "bonus/abilities/stealth",
+ "operation": "add",
+ "value": "modifier/dexterity"
+ },
+ {
+ "id": "81mba252ha96tvv1od31oyqzdwxqbj5d",
+ "category": "value",
+ "property": "bonus/abilities/stealth",
+ "operation": "add",
+ "value": "modifier/dexterity"
+ },
+ {
+ "id": "sasdo04fx1xz88nhsc5oft684haz4p3v",
+ "category": "value",
+ "property": "bonus/abilities/survival",
+ "operation": "add",
+ "value": "modifier/constitution"
+ },
+ {
+ "id": "gmu6a9oi9eyajzpku21jmdna3tatnlto",
+ "category": "value",
+ "property": "bonus/abilities/survival",
+ "operation": "add",
+ "value": "modifier/psyche"
+ },
+ {
+ "id": "md4z2oz77oao7x87acb5oe2onox0670b",
+ "category": "value",
+ "property": "bonus/abilities/investigation",
+ "operation": "add",
+ "value": "modifier/intelligence"
+ },
+ {
+ "id": "be74pl6bftog9u0wpb48p3oesg2cmn06",
+ "category": "value",
+ "property": "bonus/abilities/investigation",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "j68zu0clx72muaa3sa10atc9t03f4qxx",
+ "category": "value",
+ "property": "bonus/abilities/history",
+ "operation": "add",
+ "value": "modifier/intelligence"
+ },
+ {
+ "id": "em3r7b6j94vcb8sgrfsoad6dl2tadhmy",
+ "category": "value",
+ "property": "bonus/abilities/history",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "i589zlj0e0zpp4r2or2t50j411tenvd5",
+ "category": "value",
+ "property": "bonus/abilities/religion",
+ "operation": "add",
+ "value": "modifier/intelligence"
+ },
+ {
+ "id": "me17rbtl5tc3g9esb9v54qfx8z81ixre",
+ "category": "value",
+ "property": "bonus/abilities/religion",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "5evkffnzjpt04z44g4tugbmtj2lccuat",
+ "category": "value",
+ "property": "bonus/abilities/arcana",
+ "operation": "add",
+ "value": "modifier/psyche"
+ },
+ {
+ "id": "n3djq9egs5owpln3vy6twah5rr9q2i0o",
+ "category": "value",
+ "property": "bonus/abilities/arcana",
+ "operation": "add",
+ "value": "modifier/intelligence"
+ },
+ {
+ "id": "zq8zz73uhg0af8k5bk5790ewn0jth1k5",
+ "category": "value",
+ "property": "bonus/abilities/understanding",
+ "operation": "add",
+ "value": "modifier/intelligence"
+ },
+ {
+ "id": "3rkcc8jfxj5yk2ojew66f1xgmzeturu7",
+ "category": "value",
+ "property": "bonus/abilities/understanding",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "mvtsq4fr4pxwxuiauzaiwq8c7h7a02qz",
+ "category": "value",
+ "property": "bonus/abilities/perception",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "gdt9ob3se4s135w8o86tmdvz8ybfbwe8",
+ "category": "value",
+ "property": "bonus/abilities/perception",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "iluvo2ox9aol0lxh5whepb2phrgawwej",
+ "category": "value",
+ "property": "bonus/abilities/performance",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "mjydvs9nxru3z4umnymh9038kps9430x",
+ "category": "value",
+ "property": "bonus/abilities/performance",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "pr51amybbpbotn16ryhqo6xffxyvjblz",
+ "category": "value",
+ "property": "bonus/abilities/medecine",
+ "operation": "add",
+ "value": "modifier/curiosity"
+ },
+ {
+ "id": "empa29oxpnfg1o4y1i07c2u5ai83twnn",
+ "category": "value",
+ "property": "bonus/abilities/medecine",
+ "operation": "add",
+ "value": "modifier/psyche"
+ },
+ {
+ "id": "a5l339w5cfp5a9fx4u3a94y7ijevf538",
+ "category": "value",
+ "property": "bonus/abilities/persuasion",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "dbmscujttru3o42w5b4aix0e4hxnp8fc",
+ "category": "value",
+ "property": "bonus/abilities/persuasion",
+ "operation": "add",
+ "value": "modifier/psyche"
+ },
+ {
+ "id": "m9rxyr4ow7d5tfvp68xpog2b7fbs45x1",
+ "category": "value",
+ "property": "bonus/abilities/animalhandling",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "4rre055rn6j1x6m2ye8n2vfz1bkz4wio",
+ "category": "value",
+ "property": "bonus/abilities/animalhandling",
+ "operation": "add",
+ "value": "modifier/psyche"
+ },
+ {
+ "id": "b2p2sijbihg11vqxjixemmkn07y8fkyk",
+ "category": "value",
+ "property": "bonus/abilities/deception",
+ "operation": "add",
+ "value": "modifier/charisma"
+ },
+ {
+ "id": "c922prww5dag7i5pgxw3fz05l93w0d17",
+ "category": "value",
+ "property": "bonus/abilities/deception",
+ "operation": "add",
+ "value": "modifier/psyche"
}
]
},
@@ -8152,6 +8214,13 @@
"list": "passive",
"action": "add",
"item": "3h7r14qqma4pac9croeqk1ohr0fskrnk"
+ },
+ {
+ "id": "fihfrfx3tmryho0a2owipdjk4jd5l9u6",
+ "category": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
}
]
},
@@ -8633,6 +8702,13 @@
"list": "passive",
"action": "add",
"item": "nh24eoi1nsb0wop9e30rb5gkf3d8qy68"
+ },
+ {
+ "id": "zgc2783gxf02ud28fnk4no2m5ifoviyu",
+ "category": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
}
]
},
@@ -8646,6 +8722,13 @@
"list": "passive",
"action": "add",
"item": "6joo8c9z2m0xp23yo417ig727fs2dq7e"
+ },
+ {
+ "id": "5oe2h6fn4604wkxsrz2mtfytkb878irq",
+ "category": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
}
]
},
@@ -8659,11 +8742,18 @@
"list": "action",
"action": "add",
"item": "vrdfmfg8wavw7nxx31mimhsdvtj8vyq3"
+ },
+ {
+ "id": "bzn7adtmcl4698ph16uzap582zlrypa7",
+ "category": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
}
]
},
"lzzn957vhac2rnaf97b1h2gyflf7oy9u": {
- "description": "+35 points de statistiques.\n+14 PV max.",
+ "description": "+35 points d'entrainement.\n+14 PV max.",
"effect": [
{
"category": "value",
@@ -8683,40 +8773,40 @@
"id": "lzzn957vhac2rnaf97b1h2gyflf7oy9u"
},
"lxucqrfvg76vkmbz3h9e9q6d9ju0zjl0": {
- "description": "+1 point de statistique.\n+3 PV max.\n+2 mana max.",
+ "description": "+2 points d'entrainement.\n+4 PV max.\n+3 mana max.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "e2o6bk8vkvggvobzxqxubg4v3stik2bu"
},
{
"category": "value",
"operation": "add",
"property": "health",
- "value": 3,
+ "value": 4,
"id": "1vlg5gp5mpyjggl23xq7k1lpotq5hg5i"
},
{
"category": "value",
"operation": "add",
"property": "mana",
- "value": 2,
+ "value": 3,
"id": "3mldjvb14nh94u5o6tktcnqvuahft1it"
}
],
"id": "lxucqrfvg76vkmbz3h9e9q6d9ju0zjl0"
},
"5gadn1dxe6n1j3xjvlcf7mowxftsw7kn": {
- "description": "+1 point de compétence.\n+6 PV max.\n+3 mana max.",
+ "description": "+1 point d'entrainement.\n+2 points de compétence.\n+6 PV max.\n+3 mana max.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "ability",
- "value": 1,
+ "value": 2,
"id": "8prhhl5rj1524l32t36ozqy14nx15xni"
},
{
@@ -8732,12 +8822,19 @@
"property": "mana",
"value": 3,
"id": "ea73j8itl2nun8o9memts6c1s6f0cjl5"
+ },
+ {
+ "id": "mlbjav2jfb2ymduu90xtf81ti4edpl9o",
+ "category": "value",
+ "property": "training",
+ "operation": "add",
+ "value": 1
}
],
"id": "5gadn1dxe6n1j3xjvlcf7mowxftsw7kn"
},
"4w03h8xzjluombr5jgncinh5nek4at5u": {
- "description": "+2 points de statistiques.\n+1 point de compétence.\n+3 PV max.\n+1 mana max.",
+ "description": "+2 points d'entrainement.\n+1 point de compétence.\n+3 PV max.\n+1 mana max.",
"effect": [
{
"category": "value",
@@ -8771,7 +8868,7 @@
"id": "4w03h8xzjluombr5jgncinh5nek4at5u"
},
"4lxwrhbvxavu520i04dt58ovx31myj1l": {
- "description": "+1 point de statistique.\n+2 points de compétences.\n+4 PV max.\n+2 mana max.",
+ "description": "+1 point d'entrainement.\n+2 points de compétence.\n+4 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -8805,7 +8902,7 @@
"id": "4lxwrhbvxavu520i04dt58ovx31myj1l"
},
"gvsijzlj3wznhvk0uikpznbzdqv34qrs": {
- "description": "+1 point de statistique.\n+2 points de compétences.\n+4 PV max.\n+2 mana max.",
+ "description": "+1 point d'entrainement.\n+2 points de compétence.\n+4 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -8839,7 +8936,7 @@
"id": "gvsijzlj3wznhvk0uikpznbzdqv34qrs"
},
"s15lx58vega0q02jx0563nzcunygeo5c": {
- "description": "+1 point de statistique.\n+1 transformation par jour.\n+8 PV max.\n+4 mana max.",
+ "description": "+1 point d'entrainement.\n+1 transformation par jour.\n+8 PV max.\n+4 mana max.",
"effect": [
{
"category": "value",
@@ -8873,7 +8970,7 @@
"id": "s15lx58vega0q02jx0563nzcunygeo5c"
},
"y24gprm397s7dx578cv4uhliedg73hkw": {
- "description": "+2 points de statistiques.\n+7 PV max.\n+2 mana max.",
+ "description": "+2 points d'entrainement.\n+7 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -8900,7 +8997,7 @@
"id": "y24gprm397s7dx578cv4uhliedg73hkw"
},
"pffkc0ms5mhcmztrxv2ifhq6ydiv902o": {
- "description": "+1 point de statistique.\n+3 PV max.\n+3 mana max.",
+ "description": "+1 point d'entrainement.\n+4 PV max.\n+4 mana max.",
"effect": [
{
"category": "value",
@@ -8913,21 +9010,21 @@
"category": "value",
"operation": "add",
"property": "health",
- "value": 3,
+ "value": 4,
"id": "salmhd6apb5xz9ztqwypdazt9ug36huw"
},
{
"category": "value",
"operation": "add",
"property": "mana",
- "value": 3,
+ "value": 4,
"id": "ow2mkjcg4xvvp64k5jiis2q6tkxuzeqg"
}
],
"id": "pffkc0ms5mhcmztrxv2ifhq6ydiv902o"
},
"42bz5a6hqi80fmi3roudx7za3fjnlna3": {
- "description": "+1 point de statistique.\n+3 points de compétences.\n+1 sort maitrisé.",
+ "description": "+1 point d'entrainement.\n+3 points de compétence.\n+1 sort maitrisé.",
"effect": [
{
"category": "value",
@@ -8954,7 +9051,7 @@
"id": "42bz5a6hqi80fmi3roudx7za3fjnlna3"
},
"vkftntfuscpzy7avxyv4gkbw57mwuozi": {
- "description": "+2 points de statistiques.\n+3 PV max.\n+5 mana max.",
+ "description": "+2 points d'entrainement.\n+3 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -8981,7 +9078,7 @@
"id": "vkftntfuscpzy7avxyv4gkbw57mwuozi"
},
"fr6iflohcwavqv8g4br5o172lo1w0o1y": {
- "description": "+2 points de statistiques.\n+5 PV max.\n+2 mana max.",
+ "description": "+2 points d'entrainement.\n+5 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -9008,7 +9105,7 @@
"id": "fr6iflohcwavqv8g4br5o172lo1w0o1y"
},
"ty5pd7kp5qgf1y0sqm9xm88djtpqdeso": {
- "description": "+3 points de statistiques.",
+ "description": "+3 points d'entrainement.\n+2 points de compétence.",
"effect": [
{
"category": "value",
@@ -9016,25 +9113,32 @@
"property": "training",
"value": 3,
"id": "23yxovem2h2rua231xuh1audf02p4500"
+ },
+ {
+ "id": "cma9eqgckv39twv7wqxzy4zeczvd7wjw",
+ "category": "value",
+ "property": "ability",
+ "operation": "add",
+ "value": 2
}
],
"id": "ty5pd7kp5qgf1y0sqm9xm88djtpqdeso"
},
"xw8e1mmqdtvuuqtd6g6spf4ql60l1l9h": {
- "description": "+1 point de statistique.\n+6 PV max.\n+6 mana max.\n+1 sort maitrisé.",
+ "description": "+2 points d'entrainement.\n+2 sorts maitrisés.\n+6 PV max.\n+6 mana max.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "y0axnjch2u34k6ut35fs0rkkeubh9o6e"
},
{
"category": "value",
"operation": "add",
"property": "spellslots",
- "value": 1,
+ "value": 2,
"id": "ikcm66zz9s7hm1dgggj51smgwvr81dna"
},
{
@@ -9055,7 +9159,7 @@
"id": "xw8e1mmqdtvuuqtd6g6spf4ql60l1l9h"
},
"q0ddn6z4s5v9092p0w321qm62hgfz6bi": {
- "description": "+1 point de statistique.\n+3 PV max.\n+5 mana max.",
+ "description": "+1 point d'entrainement.\n+3 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9082,7 +9186,7 @@
"id": "q0ddn6z4s5v9092p0w321qm62hgfz6bi"
},
"gk3kuvenl1ne0d7cmgpqhffuictwg1mw": {
- "description": "+1 point de statistique.\n+2 points de compétences.\n+2 PV max.",
+ "description": "+1 point d'entrainement.\n+2 points de compétence.\n+2 PV max.",
"effect": [
{
"category": "value",
@@ -9109,7 +9213,7 @@
"id": "gk3kuvenl1ne0d7cmgpqhffuictwg1mw"
},
"2ftgh4e79xkzp05kt0xnn2o2ifo1plev": {
- "description": "+2 points de statistiques.",
+ "description": "+2 points d'entrainement.",
"effect": [
{
"category": "value",
@@ -9122,7 +9226,7 @@
"id": "2ftgh4e79xkzp05kt0xnn2o2ifo1plev"
},
"0b2t1ubfukhybi18is67s7zzmah27ona": {
- "description": "+1 point de statistique.\n+1 transformation par jour.\n+2 points de compétences.",
+ "description": "+1 point d'entrainement.\n+1 transformation par jour.\n+2 points de compétence.",
"effect": [
{
"category": "value",
@@ -9218,13 +9322,13 @@
"id": "0j9fujnkcevbx0fax8y7d5ge60c74pkk"
},
"17g09264zrgnkjql1kyk4wuv498ywjy1": {
- "description": "+1 point de statistique.\n+7 PV max.\n+1 mana max.",
+ "description": "+2 points d'entrainement.\n+7 PV max.\n+1 mana max.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "74a9xvyyitoxa7izgy6wtkux2zu7q4wy"
},
{
@@ -9245,40 +9349,40 @@
"id": "17g09264zrgnkjql1kyk4wuv498ywjy1"
},
"9eowun33q57t0ihdibl41348etflhuz6": {
- "description": "+1 point de statistique.\n+2 PV max.\n+5 mana max.",
+ "description": "+2 points d'entrainement.\n+4 mana max.\n+1 sort maitrisé.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "co391xm7hvx4svlsdgejc1uee98eo5x9"
},
{
"category": "value",
"operation": "add",
- "property": "health",
- "value": 2,
- "id": "kzy5agc5i7kvjvacujttn9zo5o6ptfks"
+ "property": "mana",
+ "value": 4,
+ "id": "pp14urths2ke26gybenfw7cak0u29o5a"
},
{
+ "id": "awx3tjhv6cuxf6uj5o5nhvelwfrkabs5",
"category": "value",
+ "property": "spellslots",
"operation": "add",
- "property": "mana",
- "value": 5,
- "id": "pp14urths2ke26gybenfw7cak0u29o5a"
+ "value": 1
}
],
"id": "9eowun33q57t0ihdibl41348etflhuz6"
},
"0wszsrkcn2xez98cai8bnl757jl2oq2d": {
- "description": "+1 point de statistique.\n+2 points de compétences.",
+ "description": "+2 points d'entrainement.\n+2 points de compétence.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "p7g96tdoy7mzfkc5jx4513aa68iwrhjx"
},
{
@@ -9292,7 +9396,7 @@
"id": "0wszsrkcn2xez98cai8bnl757jl2oq2d"
},
"yut7s8ko49uv5cgp8p257w6ww517xtc7": {
- "description": "+2 points de statistiques.\n+1 sort maitrisé.",
+ "description": "+2 points d'entrainement.\n+1 sort maitrisé.",
"effect": [
{
"category": "value",
@@ -9312,7 +9416,7 @@
"id": "yut7s8ko49uv5cgp8p257w6ww517xtc7"
},
"fweoy5ut4e21ddz57g4uydjxj4yp28sm": {
- "description": "+2 points de statistiques.\n+8 PV max.",
+ "description": "+2 points d'entrainement.\n+8 PV max.",
"effect": [
{
"category": "value",
@@ -9332,7 +9436,7 @@
"id": "fweoy5ut4e21ddz57g4uydjxj4yp28sm"
},
"kdq78so50ehc6a6dbygi50woieir0td7": {
- "description": "+2 points de statistiques.\n+7 mana max.",
+ "description": "+2 points d'entrainement.\n+7 mana max.",
"effect": [
{
"category": "value",
@@ -9342,17 +9446,17 @@
"id": "t2ij8iyp44ux9lfj3oo0kb384fu2lms4"
},
{
+ "id": "769njy3xt4yj71g3u2imwxtrwpfk6py7",
"category": "value",
+ "property": "mana",
"operation": "add",
- "property": "health",
- "value": 7,
- "id": "769njy3xt4yj71g3u2imwxtrwpfk6py7"
+ "value": 7
}
],
"id": "kdq78so50ehc6a6dbygi50woieir0td7"
},
"2wmdst5nzump32tqzjla1nhah6b4xlun": {
- "description": "+1 point de statistique.\n+1 point de compétence.\n+2 PV max.\n+2 mana max.",
+ "description": "+1 point d'entrainement.\n+1 point de compétence.\n+2 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -9386,7 +9490,7 @@
"id": "2wmdst5nzump32tqzjla1nhah6b4xlun"
},
"ku75g8by5922259p92wifgk4yo4vnpeo": {
- "description": "+1 point de statistique.\n+1 transformation par jour.\n+4 PV max.\n+4 mana max.",
+ "description": "+1 point d'entrainement.\n+1 transformation par jour.\n+4 PV max.\n+4 mana max.",
"effect": [
{
"category": "value",
@@ -9420,7 +9524,7 @@
"id": "ku75g8by5922259p92wifgk4yo4vnpeo"
},
"j7vnwxmliu2rpf9gsgnib39kzcqkfgo7": {
- "description": "+3 points de statistiques.\n+3 PV max.\n+5 mana max.",
+ "description": "+3 points d'entrainement.\n+3 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9447,7 +9551,7 @@
"id": "j7vnwxmliu2rpf9gsgnib39kzcqkfgo7"
},
"gyqhh4lf3xy7qz983lh0le6byj013qak": {
- "description": "+3 points de statistiques.\n+6 PV max.\n+1 mana max.",
+ "description": "+3 points d'entrainement.\n+6 PV max.\n+1 mana max.",
"effect": [
{
"category": "value",
@@ -9474,47 +9578,47 @@
"id": "gyqhh4lf3xy7qz983lh0le6byj013qak"
},
"ei7wm640sqixqz1ce0suyqpr6hvtpul5": {
- "description": "+1 point de statistique.",
+ "description": "+2 points d'entrainement.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "training",
- "value": 1,
+ "value": 2,
"id": "zu73rm9gtmtbxvpsni9epzhgt87cjz9w"
}
],
"id": "ei7wm640sqixqz1ce0suyqpr6hvtpul5"
},
"0u0ja87wxvl5cyhw0gkxks7u8l6zew8l": {
- "description": "+1 point de compétence.\n+5 PV max.\n+5 mana max.",
+ "description": "+3 points de compétence.\n+7 PV max.\n+7 mana max.",
"effect": [
{
"category": "value",
"operation": "add",
"property": "ability",
- "value": 1,
+ "value": 3,
"id": "6dvxx4rr6cwhbao2ajubvjaczoujpjm3"
},
{
"category": "value",
"operation": "add",
"property": "health",
- "value": 5,
+ "value": 7,
"id": "en1ovogsknqbvr7iwbybgo4wvu3ick44"
},
{
"category": "value",
"operation": "add",
"property": "mana",
- "value": 5,
+ "value": 7,
"id": "18w9bfp98cnnx9ozzgz2gzb6jkxr80az"
}
],
"id": "0u0ja87wxvl5cyhw0gkxks7u8l6zew8l"
},
"do46po1ewfxvzt1p8v3fsypn68qqs1os": {
- "description": "+1 point de statistique.\n+3 PV max.\n+5 mana max.",
+ "description": "+1 point d'entrainement.\n+3 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9541,7 +9645,7 @@
"id": "do46po1ewfxvzt1p8v3fsypn68qqs1os"
},
"4uhj49zt0l594tb3jbo955okel6nl1q5": {
- "description": "+1 point de statistique.\n+5 PV max.\n+2 mana max.",
+ "description": "+1 point d'entrainement.\n+5 PV max.\n+2 mana max.",
"effect": [
{
"category": "value",
@@ -9568,7 +9672,7 @@
"id": "4uhj49zt0l594tb3jbo955okel6nl1q5"
},
"uxmiwilhomg6m9k38w7e3svcrswyn7l7": {
- "description": "+2 points de statistiques.\n+1 point de compétence.\n+1 sort maitrisé.",
+ "description": "+2 points d'entrainement.\n+1 point de compétence.\n+1 sort maitrisé.",
"effect": [
{
"category": "value",
@@ -9595,7 +9699,7 @@
"id": "uxmiwilhomg6m9k38w7e3svcrswyn7l7"
},
"cev8sz6kj05cbsi9zh4s9kutswl9jtga": {
- "description": "+1 point de statistique.\n+1 transformation par jour.\n+2 points de compétences.\n+1 sort maitrisé.",
+ "description": "+1 point d'entrainement.\n+1 transformation par jour.\n+2 points de compétence.\n+1 sort maitrisé.",
"effect": [
{
"category": "value",
@@ -9629,7 +9733,7 @@
"id": "cev8sz6kj05cbsi9zh4s9kutswl9jtga"
},
"dbgfwilt3eoer4j43jr50hdzznjb5hdy": {
- "description": "+1 point de statistique.\n+1 point de compétence.\n+7 PV max.\n+5 mana max.",
+ "description": "+1 point d'entrainement.\n+2 points de compétence.\n+7 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9642,7 +9746,7 @@
"category": "value",
"operation": "add",
"property": "ability",
- "value": 1,
+ "value": 2,
"id": "b2622quwgf9pbce9u9ayj49y7u7j6f80"
},
{
@@ -9663,7 +9767,7 @@
"id": "dbgfwilt3eoer4j43jr50hdzznjb5hdy"
},
"vnl1syzkdqjfn3dy8xm0ewtw77ueocuk": {
- "description": "+1 point de statistique.\n+6 PV max.\n+1 mana max.",
+ "description": "+1 point d'entrainement.\n+6 PV max.\n+1 mana max.",
"effect": [
{
"category": "value",
@@ -9690,7 +9794,7 @@
"id": "vnl1syzkdqjfn3dy8xm0ewtw77ueocuk"
},
"oimwzwl9xp4c0p4uc3dreietarqtkofd": {
- "description": "+1 point de statistique.\n+2 PV max.\n+5 mana max.",
+ "description": "+1 point d'entrainement.\n+2 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9717,7 +9821,7 @@
"id": "oimwzwl9xp4c0p4uc3dreietarqtkofd"
},
"16fxrd8aqzpw76bphaz7r17kfnaa76pi": {
- "description": "+2 points de statistiques.\n+2 points de compétences.\n+6 PV max.\n+3 mana max.",
+ "description": "+2 points d'entrainement.\n+2 points de compétence.\n+6 PV max.\n+3 mana max.",
"effect": [
{
"category": "value",
@@ -9751,7 +9855,7 @@
"id": "16fxrd8aqzpw76bphaz7r17kfnaa76pi"
},
"rjw4vvivyo6fncuxoxbcaa4tsnizv3fv": {
- "description": "+2 points de statistiques.\n+2 PV max.\n+5 mana max.\n+1 sort maitrisé.",
+ "description": "+2 points d'entrainement.\n+1 sort maitrisé.\n+2 PV max.\n+5 mana max.",
"effect": [
{
"category": "value",
@@ -9785,7 +9889,7 @@
"id": "rjw4vvivyo6fncuxoxbcaa4tsnizv3fv"
},
"5zghm6wfve9t32rz7kd8zianc0be600v": {
- "description": "+2 points de statistiques.",
+ "description": "+2 points d'entrainement.",
"effect": [
{
"category": "value",
@@ -9853,7 +9957,8 @@
"value": 1,
"property": "modifier/psyche"
}
- ]
+ ],
+ "text": "+1 au modifieur de "
},
{
"category": "value",
@@ -9965,5 +10070,7 @@
"description": "Nicolas",
"effect": []
}
- }
+ },
+ "items": {},
+ "enchantments": {}
}
\ No newline at end of file
diff --git a/shared/character.util.ts b/shared/character.util.ts
index e0a2fe8..b656006 100644
--- a/shared/character.util.ts
+++ b/shared/character.util.ts
@@ -1,10 +1,10 @@
-import type { Ability, Alignment, Character, CharacterConfig, CompiledCharacter, Feature, FeatureID, FeatureItem, Level, MainStat, SpellElement, SpellType, TrainingLevel } from "~/types/character";
+import type { Ability, Alignment, Character, CharacterConfig, CompiledCharacter, FeatureItem, Level, MainStat, Resistance, SpellElement, SpellType, TrainingLevel } from "~/types/character";
import { z } from "zod/v4";
import characterConfig from '#shared/character-config.json';
import { fakeA } from "#shared/proses";
-import { button, input, loading, numberpicker, select, toggle } from "#shared/components.util";
-import { div, dom, icon, mergeClasses, text, type Class } from "#shared/dom.util";
-import { followermenu, popper, tooltip } from "#shared/floating.util";
+import { button, input, loading, numberpicker, select, Toaster, toggle } from "#shared/components.util";
+import { div, dom, icon, text } from "#shared/dom.util";
+import { followermenu, tooltip } from "#shared/floating.util";
import { clamp } from "#shared/general.util";
import markdownUtil from "#shared/markdown.util";
@@ -18,6 +18,7 @@ export const SPELL_TYPES = ["precision","knowledge","instinct","arts"] as const;
export const CATEGORIES = ["action","reaction","freeaction","misc"] as const;
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const;
export const ALIGNMENTS = ['loyal_good', 'neutral_good', 'chaotic_good', 'loyal_neutral', 'neutral_neutral', 'chaotic_neutral', 'loyal_evil', 'neutral_evil', 'chaotic_evil'] as const;
+export const RESISTANCES = ['stun','bleed','poison','fear','influence','charm','possesion','precision','knowledge','instinct'] as const;
export const defaultCharacter: Character = {
id: -1,
@@ -119,7 +120,7 @@ const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (c
freeaction: [],
reaction: [],
passive: [],
- spells: character.variables.spells,
+ spells: [],
},
aspect: "",
notes: character.notes ?? "",
@@ -169,6 +170,39 @@ export const alignmentTexts: Record = {
};
export const spellTypeTexts: Record = { "instinct": "Instinct", "knowledge": "Savoir", "precision": "Précision", "arts": "Oeuvres" };
+export const abilityTexts: Record = {
+ "athletics": "Athlétisme",
+ "acrobatics": "Acrobatique",
+ "intimidation": "Intimidation",
+ "sleightofhand": "Doigté",
+ "stealth": "Discrétion",
+ "survival": "Survie",
+ "investigation": "Enquête",
+ "history": "Histoire",
+ "religion": "Religion",
+ "arcana": "Arcanes",
+ "understanding": "Compréhension",
+ "perception": "Perception",
+ "performance": "Représentation",
+ "medecine": "Médicine",
+ "persuasion": "Persuasion",
+ "animalhandling": "Dressage",
+ "deception": "Mensonge"
+};
+
+export const resistanceTexts: Record = {
+ 'stun': 'Hébètement',
+ 'bleed': 'Saignement',
+ 'poison': 'Empoisonement',
+ 'fear': 'Peur',
+ 'influence': 'Influence',
+ 'charm': 'Charme',
+ 'possesion': 'Possession',
+ 'precision': 'Sorts de précision',
+ 'knowledge': 'Sorts de savoir',
+ 'instinct': 'Sorts d\'instinct',
+};
+
export const CharacterValidation = z.object({
id: z.number(),
name: z.string(),
@@ -176,9 +210,9 @@ export const CharacterValidation = z.object({
level: z.number().min(1).max(20),
aspect: z.number().nullable().optional(),
notes: z.string().nullable().optional(),
- training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number())),
- leveling: z.record(z.enum(LEVELS.map(String)), z.number()),
- abilities: z.record(z.enum(ABILITIES), z.number()),
+ training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number().optional())),
+ leveling: z.record(z.enum(LEVELS.map(String)), z.number().optional()),
+ abilities: z.record(z.enum(ABILITIES), z.number().optional()),
choices: z.record(z.string(), z.array(z.number())),
variables: z.object({
health: z.number(),
@@ -234,12 +268,15 @@ export class CharacterCompiler
}
get compiled(): CompiledCharacter
{
+ Object.entries(this._character.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]);
this.compile(Object.keys(this._buffer));
return this._result;
}
get values(): Record
{
+ Object.entries(this._character.abilities).forEach(e => this._result.abilities[e[0] as Ability] = e[1]);
+
const keys = Object.keys(this._buffer);
this.compile(keys);
@@ -289,6 +326,7 @@ export class CharacterCompiler
this._buffer[feature.property]!.list.push({ operation: feature.operation, id: feature.id, value: feature.value });
+ this._buffer[feature.property]!.min = -Infinity;
this._buffer[feature.property]!._dirty = true;
return;
@@ -322,6 +360,7 @@ export class CharacterCompiler
this._buffer[feature.property]!.list.splice(this._buffer[feature.property]!.list.findIndex(e => e.id === feature.id), 1);
+ this._buffer[feature.property]!.min = -Infinity;
this._buffer[feature.property]!._dirty = true;
return;
@@ -456,6 +495,7 @@ export class CharacterBuilder extends CharacterCompiler
];
this._stepsHeader = this._steps.map((e, i) =>
dom("div", { class: "group flex items-center", }, [
+ i !== 0 ? icon("radix-icons:chevron-right", { class: "w-6 h-6 flex justify-center items-center group-data-[disabled]:text-light-50 dark:group-data-[disabled]:text-dark-50 group-data-[disabled]:hover:border-transparent me-4" }) : undefined,
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue disabled:text-light-50 dark:disabled:text-dark-50 disabled:hover:border-transparent group-data-[state=active]:text-accent-blue cursor-pointer", listeners: { click: () => this.display(i) } }, [text(e.header)]),
])
);
@@ -473,6 +513,16 @@ export class CharacterBuilder extends CharacterCompiler
if(step < 0 || step >= this._stepsHeader.length)
return;
+ for(let i = 0; i < step; i++)
+ {
+ if(!this._steps[i]?.validate(this))
+ {
+ Toaster.add({ title: 'Erreur de validation', content: this._steps[i]!.errorMessage, type: 'error', duration: 25000, timer: true })
+ return;
+ }
+ else
+ {}
+ }
if(step !== 0 && this._steps.slice(0, step).some(e => !e.validate(this)))
return;
@@ -492,10 +542,10 @@ export class CharacterBuilder extends CharacterCompiler
method: 'post',
body: this._character,
onResponseError: (e) => {
- //add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
+ Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
}
});
- //add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
+ Toaster.add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
useRouter().replace({ name: 'character-id-edit', params: { id: this.id } })
if(leave) useRouter().push({ name: 'character-id', params: { id: this.id } });
}
@@ -506,10 +556,10 @@ export class CharacterBuilder extends CharacterCompiler
method: 'post',
body: this._character,
onResponseError: (e) => {
- //add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', duration: 25000, timer: true });
+ Toaster.add({ title: 'Erreur d\'enregistrement', content: e.response.status === 401 ? "Vous n'êtes pas autorisé à effectué cette opération" : e.response.statusText, type: 'error', closeable: true, duration: 25000, timer: true });
}
});
- //add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
+ Toaster.add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
if(leave) useRouter().push({ name: 'character-id', params: { id: this.id } });
}
}
@@ -526,9 +576,11 @@ export class CharacterBuilder extends CharacterCompiler
if(this._character.leveling.hasOwnProperty(level))
{
const option = this._character.leveling[level]!;
+ const feature = config.peoples[this._character.people!]!.options[level][option]!;
delete this._character.leveling[level];
+ if(this._character.choices.hasOwnProperty(feature)) delete this._character.choices[feature];
- this.remove(config.peoples[this._character.people!]!.options[level][option]);
+ this.remove(feature);
}
}
}
@@ -555,9 +607,11 @@ export class CharacterBuilder extends CharacterCompiler
if(this._character.leveling.hasOwnProperty(level) && this._character.leveling[level] !== choice) //If the given level is already selected, switch to the new choice
{
- this.remove(config.peoples[this._character.people!]!.options[level][this._character.leveling[level]!]);
- this.add(config.peoples[this._character.people!]!.options[level][choice]);
+ const feature = config.peoples[this._character.people!]!.options[level][this._character.leveling[level]!]!;
+ this.remove(feature);
+ if(this._character.choices.hasOwnProperty(feature)) delete this._character.choices[feature];
+ this.add(config.peoples[this._character.people!]!.options[level][choice]);
this._character.leveling[level] = choice;
}
else if(!this._character.leveling.hasOwnProperty(level))
@@ -586,14 +640,18 @@ export class CharacterBuilder extends CharacterCompiler
{
if(this._character.training[stat].hasOwnProperty(i))
{
- this.remove(config.training[stat][i as TrainingLevel][this._character.training[stat][i as TrainingLevel]!]);
+ const feature = config.training[stat][i as TrainingLevel][this._character.training[stat][i as TrainingLevel]!]!;
+ this.remove(feature);
+ if(this._character.choices.hasOwnProperty(feature)) delete this._character.choices[feature];
delete this._character.training[stat][i as TrainingLevel];
}
}
}
else
{
- this.remove(config.training[stat][level][this._character.training[stat][level]!]);
+ const feature = config.training[stat][level][this._character.training[stat][level]!]!;
+ this.remove(feature);
+ if(this._character.choices.hasOwnProperty(feature)) delete this._character.choices[feature];
this._character.training[stat][level] = choice;
this.add(config.training[stat][level][choice]);
}
@@ -604,95 +662,27 @@ export class CharacterBuilder extends CharacterCompiler
this.add(config.training[stat][level][choice]);
}
}
- private choose(id: string, choices: number[])
+ handleChoice(element: HTMLElement, feature: string)
{
- const current = this._character.choices[id];
- const [ feature, effect ] = id.split('-');
- const option = config.features[feature!]!.effect.find(e => e.id === effect);
-
- if(option?.category === 'choice')
- {
- if(current !== undefined)
- {
- current.forEach(e => this.undo(option.options[e]));
- }
- if(choices.length > 0)
- {
- choices.forEach(e => this.apply(option.options[e]));
- }
-
- this._character.choices[id] = choices;
- }
- }
-}
-
-type PickableFeatureSettings = { state?: boolean, onToggle?: (state: boolean) => void, onChoice?: (options: number[]) => void, disabled?: boolean, class?: { selected?: Class, container?: Class, disabled?: Class }, choices?: Record, };
-export class PickableFeature
-{
- private _content: HTMLElement;
-
- private _feature: Feature;
-
- private _characterChoices?: Record;
- private _choiceDom?: HTMLElement;
- private _choices?: Extract[];
-
- private _settings?: PickableFeatureSettings;
-
- constructor(feature: FeatureID, settings?: PickableFeatureSettings)
- {
- this._feature = config.features[feature]!;
- this._settings = settings;
-
- if(settings?.choices)
- {
- this._characterChoices = settings.choices;
- this._choices = this._feature.effect.filter(e => e.category === 'choice');
- this._choiceDom = this._choices.length > 0 ? dom('div', { class: 'absolute -bottom-px -right-px border border-light-50 dark:border-dark-50 bg-light-10 dark:bg-dark-10 group-data-[active]:hover:border-light-70 dark:group-data-[active]:hover:border-dark-70 flex p-1 justify-center items-center', listeners: { click: (e) => e.stopImmediatePropagation() ?? this.choose() } }, [ icon('radix-icons:gear') ]) : undefined;
- }
-
- this._content = dom("div", { attributes: { 'data-active': settings?.state, 'data-disabled': settings?.disabled ?? false }, class: ["group border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-[400px] hover:border-light-70 dark:hover:border-dark-70 relative data-[active]:!border-accent-blue data-[active]:bg-accent-blue data-[active]:bg-opacity-20 data-[disabled]:hover:border-light-40 dark:data-[disabled]:hover:border-dark-40 data-[disabled]:opacity-30 data-[disabled]:cursor-default", settings?.class?.container, settings?.class?.selected ? mergeClasses(settings?.class?.selected).split(' ').map(e => `data-[state='active']:${e}`).join(' ') : undefined, settings?.class?.disabled ? mergeClasses(settings?.class?.disabled).split(' ').map(e => `data-[disabled]:${e}`).join(' ') : undefined], listeners: { click: e => this.toggle() }}, [
- markdownUtil(this._feature.description, undefined, { tags: { a: fakeA } }),
- this._choiceDom,
- ]);
- }
- toggle(state?: boolean)
- {
- if(this._content.hasAttribute('data-disabled'))
- return this._content.hasAttribute('data-active');
-
- const s = this._content.toggleAttribute('data-active', state);
-
- this._settings?.onToggle && this._settings?.onToggle(s);
-
- return s;
- }
- choose()
- {
- if(!this._choices || this._choices.length === 0)
+ const choices = config.features[feature]!.effect.filter(e => e.category === 'choice');
+ if(choices.length === 0)
return;
- const menu = followermenu(this._choiceDom!, [ div('px-24 py-6 flex flex-col items-center text-light-100 dark:text-dark-100', this._choices.map(e => div('flex flex-row items-center', [ text(e.text), div('flex flex-col', Array(e.settings?.amount ?? 1).fill(0).map((_, i) => (
- select(e.options.map((_e, _i) => ({ text: _e.text, value: _i })), { defaultValue: this._characterChoices![e.id] !== undefined ? this._characterChoices![e.id]![i] : undefined, change: (value) => { this._characterChoices![e.id] ??= []; this._characterChoices![e.id]![i] = value }, class: { container: 'w-32' } })
+ const menu = followermenu(element, [ div('px-24 py-6 flex flex-col items-center text-light-100 dark:text-dark-100', choices.map(e => div('flex flex-row items-center', [ text(e.text), div('flex flex-col', Array(e.settings?.amount ?? 1).fill(0).map((_, i) => (
+ select(e.options.map((_e, _i) => ({ text: _e.text, value: _i })), { defaultValue: this._character.choices![e.id] !== undefined ? this._character.choices![e.id]![i] : undefined, change: (value) => {
+ this._character.choices![e.id] ??= [];
+ this._character.choices![e.id]![i] = value;
+ }, class: { container: 'w-32' } })
))) ]))) ], { arrow: true, offset: { mainAxis: 8 }, cover: 'width', placement: 'bottom', priority: false, viewport: document.getElementById('characterEditorContainer') ?? undefined, });
}
- get dom()
- {
- return this._content;
- }
-}
-class FeatureTable
-{
- constructor(table: Record)
- {
-
- }
}
+
abstract class BuilderTab {
protected _builder: CharacterBuilder;
protected _content!: Array;
static header: string;
static description: string;
+ static errorMessage: string;
constructor(builder: CharacterBuilder) { this._builder = builder; }
update() { }
@@ -703,6 +693,7 @@ type BuilderTabConstructor = {
new (builder: CharacterBuilder): BuilderTab;
header: string;
description: string;
+ errorMessage: string;
validate(builder: CharacterBuilder): boolean;
}
class PeoplePicker extends BuilderTab
@@ -713,6 +704,7 @@ class PeoplePicker extends BuilderTab
static override header = 'Peuple';
static override description = 'Choisissez un peuple afin de définir la progression de votre personnage au fil des niveaux.';
+ static override errorMessage = 'Veuillez choisir un peuple pour continuer.';
constructor(builder: CharacterBuilder)
{
@@ -732,7 +724,7 @@ class PeoplePicker extends BuilderTab
"border-accent-blue outline-2 outline outline-accent-blue".split(" ").forEach(e => this._options.forEach(f => f?.classList.toggle(e, false)));
"border-accent-blue outline-2 outline outline-accent-blue".split(" ").forEach(e => this._options[i]?.classList.toggle(e, true));
}
- } }, [div("h-[320px]"), div("text-xl font-bold text-center", [text(people.name)]), div("w-full border-b border-light-50 dark:border-dark-50"), div("text-wrap word-break", [text(people.description)])]),
+ }, attributes: { 'data-people': people.id } }, [div("h-[320px]"), div("text-xl font-bold text-center", [text(people.name)]), div("w-full border-b border-light-50 dark:border-dark-50"), div("text-wrap word-break", [text(people.description)])]),
);
this._content = [ div("flex flex-1 gap-12 px-2 py-4 justify-center items-center", [
@@ -753,6 +745,11 @@ class PeoplePicker extends BuilderTab
{
this._nameInput.value = this._builder.character.name;
this._visibilityInput.setAttribute('data-state', this._builder.character.visibility === "private" ? "checked" : "unchecked");
+
+ if(this._builder.character.people !== undefined)
+ {
+ "border-accent-blue outline-2 outline outline-accent-blue".split(" ").forEach(e => this._options.find(e => e.getAttribute('data-people') === this._builder.character.people)?.classList.toggle(e, true));
+ }
}
static override validate(builder: CharacterBuilder): boolean
{
@@ -765,10 +762,12 @@ class LevelPicker extends BuilderTab
private _pointsInput: HTMLInputElement;
private _healthText: Text;
private _manaText: Text;
- private _options: HTMLDivElement[][];
+
+ private _options: HTMLElement[][];
static override header = 'Niveaux';
static override description = 'Déterminez la progression de votre personnage en choisissant une option par niveau disponible.';
+ static override errorMessage = 'Vous avez attribué trop de niveaux.';
constructor(builder: CharacterBuilder)
{
@@ -780,10 +779,19 @@ class LevelPicker extends BuilderTab
} });
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
this._healthText = text("0"), this._manaText = text("0");
-
+
this._options = Object.entries(config.peoples[this._builder.character.people!]!.options).map(
(level) => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative left-4" }, [ text(level[0]) ])]),
- div("flex flex-row gap-4 justify-center", level[1].map((option, j) => new PickableFeature(option, { disabled: parseInt(level[0], 10) > this._builder.character.level, state: this._builder.character.leveling[parseInt(level[0], 10) as Level] === j, choices: this._builder.character.choices }).dom))
+ div("flex flex-row gap-4 justify-center", level[1].map((option, j) => {
+ const choice = config.features[option]!.effect.some(e => e.category === 'choice') ? dom('div', { class: 'absolute -bottom-px -right-px border border-light-50 dark:border-dark-50 bg-light-10 dark:bg-dark-10 hover:border-light-70 dark:hover:border-dark-70 flex p-1 justify-center items-center', listeners: { click: (e) => {
+ e.stopImmediatePropagation();
+ this._builder.character.leveling[level[0] as any as Level] === j && this._builder.handleChoice(choice!, config.features[option]!.id);
+ } } }, [ icon('radix-icons:gear') ]) : undefined;
+ return dom("div", { class: ["flex border border-light-50 dark:border-dark-50 px-4 py-2 w-[400px] relative", { 'hover:border-light-70 dark:hover:border-dark-70 cursor-pointer': (level[0] as any as Level) <= this._builder.character.level, '!border-accent-blue bg-accent-blue bg-opacity-20': this._builder.character.leveling[level[0] as any as Level] === j }], listeners: { click: e => {
+ this._builder.toggleLevelOption(parseInt(level[0]) as Level, j);
+ this.update();
+ }}}, [ dom('span', { class: "text-wrap whitespace-pre", text: config.features[option]!.description }), choice ]);
+ }))
]);
this._content = [ div("flex flex-1 gap-12 px-2 py-4 justify-center items-center", [
@@ -824,14 +832,14 @@ class LevelPicker extends BuilderTab
this._builder.updateLevel(this._builder.character.level as Level);
this._pointsInput.value = (this._builder.character.level - Object.keys(this._builder.character.leveling).length).toString();
- /* this._options.forEach((e, i) => {
+ this._options.forEach((e, i) => {
e[0]?.classList.toggle("opacity-30", ((i + 1) as Level) > this._builder.character.level);
e[1]?.classList.toggle("opacity-30", ((i + 1) as Level) > this._builder.character.level);
e[1]?.childNodes.forEach((option, j) => {
'hover:border-light-70 dark:hover:border-dark-70 cursor-pointer'.split(" ").forEach(_e => (option as HTMLDivElement).classList.toggle(_e, ((i + 1) as Level) <= this._builder.character.level));
'!border-accent-blue bg-accent-blue bg-opacity-20'.split(" ").forEach(_e => (option as HTMLDivElement).classList.toggle(_e, this._builder.character.leveling[((i + 1) as Level)] === j));
});
- }); */
+ });
}
static override validate(builder: CharacterBuilder): boolean
{
@@ -851,6 +859,7 @@ class TrainingPicker extends BuilderTab
static override header = 'Entrainement';
static override description = 'Spécialisez votre personnage en attribuant vos points d\'entrainement parmi les 7 branches disponibles.\nChaque paliers de 3 points augmentent votre modifieur.';
+ static override errorMessage = 'Vous avez dépensé trop de points d\'entrainement.';
constructor(builder: CharacterBuilder)
{
@@ -858,8 +867,17 @@ class TrainingPicker extends BuilderTab
const statRenderBlock = (stat: MainStat) => {
return Object.entries(config.training[stat]).map(
(level) => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative" }, [ text(level[0]) ])]),
- div("flex flex-row gap-4 justify-center", level[1].map((option, j) => new PickableFeature(option, { state: level[0] == '0' || this._builder.character.training[stat as MainStat][level[0] as any as TrainingLevel] === j, choices: this._builder.character.choices }).dom))
- ])
+ div("flex flex-row gap-4 justify-center", level[1].map((option, j) => {
+ const choice = config.features[option]!.effect.some(e => e.category === 'choice') ? dom('div', { class: 'absolute -bottom-px -right-px border border-light-50 dark:border-dark-50 bg-light-10 dark:bg-dark-10 hover:border-light-70 dark:hover:border-dark-70 flex p-1 justify-center items-center', listeners: { click: (e) => {
+ e.stopImmediatePropagation();
+ this._builder.character.training[stat as MainStat][parseInt(level[0], 10) as TrainingLevel] === j && this._builder.handleChoice(choice!, config.features[option]!.id);
+ } } }, [ icon('radix-icons:gear') ]) : undefined;
+ return dom("div", { class: ["border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-[400px] hover:border-light-50 dark:hover:border-dark-50 relative"], listeners: { click: e => {
+ this._builder.toggleTrainingOption(stat, parseInt(level[0]) as TrainingLevel, j);
+ this.update();
+ }}}, [ markdownUtil(config.features[option]!.description, undefined, { tags: { a: fakeA } }), choice ]);
+ }))
+ ]);
}
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
@@ -912,6 +930,17 @@ class TrainingPicker extends BuilderTab
this._pointsInput.value = ((values.training ?? 0) - training).toString();
this._healthText.textContent = values.health?.toString() ?? '0';
this._manaText.textContent = values.mana?.toString() ?? '0';
+
+ Object.keys(this._options).forEach(stat => {
+ const max = Object.keys(this._builder.character.training[stat as MainStat]).length;
+ this._options[stat as MainStat].forEach((e, i) => {
+ e[0]?.classList.toggle("opacity-30", (i as TrainingLevel) > max);
+ e[1]?.classList.toggle("opacity-30", (i as TrainingLevel) > max);
+ e[1]?.childNodes.forEach((option, j) => {
+ '!border-accent-blue bg-accent-blue bg-opacity-20'.split(" ").forEach(_e => (option as HTMLDivElement).classList.toggle(_e, i == 0 || (this._builder.character.training[stat as MainStat][i as TrainingLevel] === j)));
+ })
+ })
+ });
}
static override validate(builder: CharacterBuilder): boolean
{
@@ -926,70 +955,27 @@ class AbilityPicker extends BuilderTab
private _pointsInput: HTMLInputElement;
private _options: HTMLDivElement[];
- private _tooltips: Text[] = [];
private _maxs: HTMLElement[] = [];
static override header = 'Compétences';
static override description = 'Diversifiez vos possibilités en affectant vos points dans les différentes compétences disponibles.';
+ static override errorMessage = 'Une compétence est incorrectement saisie ou vous avez dépassé le nombre de points à attribuer.';
constructor(builder: CharacterBuilder)
{
super(builder);
- const numberInput = (value?: number, update?: (value: number) => number | undefined) => {
- const input = dom("input", { class: `w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:bg-light-20 dark:data-[disabled]:bg-dark-20 data-[disabled]:border-light-20 dark:data-[disabled]:border-dark-20`, listeners: {
- input: (e: Event) => {
- input.value = (update && update(parseInt(input.value))?.toString()) ?? input.value;
- },
- keydown: (e: KeyboardEvent) => {
- let value = isNaN(parseInt(input.value)) ? '0' : input.value;
- switch(e.key)
- {
- case "ArrowUp":
- value = clamp(parseInt(value) + 1, 0, 99).toString();
- break;
- case "ArrowDown":
- value = clamp(parseInt(value) - 1, 0, 99).toString();
- break;
- default:
- break;
- }
-
- if(input.value !== value)
- {
- input.value = (update && update(parseInt(value))?.toString()) ?? value;
- }
- }
- }});
-
- input.value = value?.toString() ?? "0";
- return input;
- };
- function pushAndReturn(arr: Array, value: T): T
- {
- arr.push(value);
- return value;
- }
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
- this._options = ABILITIES.map((e, i) => div('flex flex-col border border-light-50 dark:border-dark-50 p-4 gap-2 w-[200px] relative', [
- div('flex justify-between', [ numberpicker({ defaultValue: this._builder.character.abilities[e], input: (value) => {
- const values = this._builder.values;
- const max = (values[`abilities/${e}/max`] ?? 0) + (values[`modifier/${config.abilities[e].max[0]}`] ?? 0) + (values[`modifier/${config.abilities[e].max[1]}`] ?? 0);
-
- this._builder.character.abilities[e] = clamp(value, 0, max);
- Object.assign((this._options[i]?.lastElementChild as HTMLSpanElement | undefined)?.style ?? {}, { width: `${(max === 0 ? 0 : (this._builder.character.abilities[e] ?? 0) / max) * 100}%` });
- this._tooltips[i]!.textContent = `${mainStatTexts[config.abilities[e].max[0]]} (${values[`modifier/${config.abilities[e].max[0]}`] ?? 0}) + ${mainStatTexts[config.abilities[e].max[1]]} (${values[`modifier/${config.abilities[e].max[1]}`] ?? 0}) + ${values[`abilities/${e}/max`] ?? 0}`;
- this._maxs[i]!.textContent = `/ ${max ?? 0}`;
-
- const abilities = Object.values(this._builder.character.abilities).reduce((p, v) => p + v, 0);
- this._pointsInput.value = ((values.ability ?? 0) - abilities).toString();
-
- return this._builder.character.abilities[e];
- }}), tooltip(pushAndReturn(this._maxs, dom('span', { class: 'text-lg text-end cursor-pointer', text: '' })), pushAndReturn(this._tooltips, text('')), 'bottom-end')]),
- dom('span', { class: "text-xl text-center font-bold", text: config.abilities[e].name }),
- dom('span', { class: "absolute -bottom-px -left-px h-[3px] bg-accent-blue" }),
- ]));
+ this._options = ABILITIES.map((e, i) => {
+ const max = dom('span', { class: 'text-lg text-end font-bold' });
+ this._maxs.push(max);
+ return div('flex flex-col border border-light-50 dark:border-dark-50 p-4 gap-2 w-[200px] relative', [
+ div('flex justify-between', [ numberpicker({ defaultValue: this._builder.character.abilities[e], input: (value) => { this._builder.character.abilities[e] = value; this.update(); }}), max ]),
+ dom('span', { class: "text-xl text-center font-bold", text: abilityTexts[e] }),
+ dom('span', { class: "absolute -bottom-px -left-px h-[3px] bg-accent-blue" }),
+ ])
+ });
this._content = [ div("flex flex-1 gap-12 px-2 py-4 justify-center items-center sticky top-0 bg-light-0 dark:bg-dark-0 w-full z-10", [
dom("label", { class: "flex justify-center items-center my-2" }, [
@@ -1003,25 +989,33 @@ class AbilityPicker extends BuilderTab
}
override update()
{
- const values = this._builder.values;
+ const values = this._builder.values, compiled = this._builder.compiled;
const abilities = Object.values(this._builder.character.abilities).reduce((p, v) => p + v, 0);
this._pointsInput.value = ((values.ability ?? 0) - abilities).toString();
ABILITIES.forEach((e, i) => {
- const max = (values[`bonus/abilities/${e}`] ?? 0) + (values[`modifier/${config.abilities[e].max[0]}`] ?? 0) + (values[`modifier/${config.abilities[e].max[1]}`] ?? 0);
+ const max = (values[`bonus/abilities/${e}`] ?? 0);
- Object.assign((this._options[i]?.lastElementChild as HTMLSpanElement | undefined)?.style ?? {}, { width: `${(max === 0 ? 0 : (this._builder.character.abilities[e] ?? 0) / max) * 100}%` });
- this._tooltips[i]!.textContent = `${mainStatTexts[config.abilities[e].max[0]]} (${values[`modifier/${config.abilities[e].max[0]}`] ?? 0}) + ${mainStatTexts[config.abilities[e].max[1]]} (${values[`modifier/${config.abilities[e].max[1]}`] ?? 0}) + ${values[`bonus/abilities/${e}`] ?? 0}`;
+ const load = this._options[i]?.lastElementChild as HTMLSpanElement | undefined;
+ const valid = (compiled.abilities[e] ?? 0) <= max;
+ if(load)
+ {
+ Object.assign(load.style ?? {}, { width: `${clamp((max === 0 ? 0 : (this._builder.character.abilities[e] ?? 0) / max) * 100, 0, 100)}%` });
+ 'bg-accent-blue'.split(' ').forEach(_e => load.classList.toggle(_e, valid));
+ 'bg-light-red dark:bg-dark-red'.split(' ').forEach(_e => load.classList.toggle(_e, !valid));
+ }
this._maxs[i]!.textContent = `/ ${max ?? 0}`;
})
}
static override validate(builder: CharacterBuilder): boolean
{
- const values = builder.values;
+ const values = builder.values, compiled = builder.compiled;
const abilities = Object.values(builder.character.abilities).reduce((p, v) => p + v, 0);
- return (values.ability ?? 0) - abilities >= 0;
+ console.log(ABILITIES.map(e => (values[`bonus/abilities/${e}`] ?? 0) >= (compiled.abilities[e] ?? 0)));
+
+ return ABILITIES.map(e => (values[`bonus/abilities/${e}`] ?? 0) >= (compiled.abilities[e] ?? 0)).every(e => e) && (values.ability ?? 0) - abilities >= 0;
}
}
class AspectPicker extends BuilderTab
@@ -1036,6 +1030,7 @@ class AspectPicker extends BuilderTab
static override header = 'Aspect';
static override description = 'Déterminez l\'Aspect qui vous corresponds et benéficiez de puissants bonus.';
+ static override errorMessage = 'Veuillez choisir un Aspect.';
constructor(builder: CharacterBuilder)
{
@@ -1130,21 +1125,21 @@ class AspectPicker extends BuilderTab
}
static override validate(builder: CharacterBuilder): boolean
{
- const physic = Object.values(builder.character.training['strength']).length + Object.values(builder.character.training['dexterity']).length + Object.values(builder.character.training['constitution']).length;
+ /* const physic = Object.values(builder.character.training['strength']).length + Object.values(builder.character.training['dexterity']).length + Object.values(builder.character.training['constitution']).length;
const mental = Object.values(builder.character.training['intelligence']).length + Object.values(builder.character.training['curiosity']).length;
- const personality = Object.values(builder.character.training['charisma']).length + Object.values(builder.character.training['psyche']).length;
+ const personality = Object.values(builder.character.training['charisma']).length + Object.values(builder.character.training['psyche']).length; */
if(builder.character.aspect === undefined)
return false;
- const aspect = config.aspects[builder.character.aspect]!
+ /* const aspect = config.aspects[builder.character.aspect]!
if(physic > aspect.physic.max || physic < aspect.physic.min)
return false;
if(mental > aspect.mental.max || mental < aspect.mental.min)
return false;
if(personality > aspect.personality.max || personality < aspect.personality.min)
- return false;
+ return false; */
return true;
}
diff --git a/shared/components.util.ts b/shared/components.util.ts
index 45ac9da..9f336db 100644
--- a/shared/components.util.ts
+++ b/shared/components.util.ts
@@ -424,4 +424,85 @@ export function toggle(settings?: { defaultValue?: boolean, change?: (value: boo
}
}, [ div('block w-[18px] h-[18px] translate-x-[2px] will-change-transform transition-transform bg-light-50 dark:bg-dark-50 group-data-[state=checked]:translate-x-[26px] group-data-[disabled]:bg-light-30 dark:group-data-[disabled]:bg-dark-30 group-data-[disabled]:border-light-30 dark:group-data-[disabled]:border-dark-30') ]);
return element;
+}
+
+export interface ToastConfig
+{
+ closeable?: boolean
+ duration: number
+ title?: string
+ content?: string
+ timer?: boolean
+ type?: ToastType
+}
+type ToastDom = ToastConfig & { dom: HTMLElement };
+export type ToastType = 'info' | 'success' | 'error';
+export class Toaster
+{
+ private static _MAX_DRAG = 130;
+ private static _list: Array = [];
+ private static _container: HTMLDivElement;
+
+ static init()
+ {
+ Toaster._container = div('fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72');
+ document.body.appendChild(Toaster._container);
+ }
+ static add(_config: ToastConfig)
+ {
+ let start: number;
+ const dragstart = (e: MouseEvent) => {
+ window.addEventListener('mousemove', dragmove);
+ window.addEventListener('mouseup', dragend);
+
+ start = e.clientX;
+ };
+ const dragmove = (e: MouseEvent) => {
+ const drag = e.clientX - start;
+ if(drag > Toaster._MAX_DRAG)
+ {
+ dragend();
+ config.dom.animate([{ transform: `translateX(${drag}px)` }, { transform: `translateX(150%)` }], { duration: 100, easing: 'ease-out' });
+ Toaster.close(config);
+ }
+ else if(drag > 0)
+ {
+ config.dom.style.transform = `translateX(${drag}px)`;
+ }
+ };
+ const dragend = () => {
+ window.removeEventListener('mousemove', dragmove);
+ window.removeEventListener('mouseup', dragend);
+
+ config.dom.style.transform = `translateX(0px)`;
+ };
+ const config = _config as ToastDom;
+ const loader = config.timer ? div('bg-light-50 dark:bg-dark-50 h-full w-full transition-[width] ease-linear') : undefined;
+ loader?.animate([{ width: '0' }, { width: '100%' }], { duration: config.duration, easing: 'linear' });
+ config.dom = dom('div', { class: 'ToastRoot bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 group select-none', attributes: { 'data-type': config.type, 'data-state': 'open' } }, [
+ div('grid grid-cols-8 px-3 pt-2 pb-2', [
+ config.title ? dom('h4', { class: 'font-semibold text-xl col-span-7 text-light-100 dark:text-dark-100', text: config.title }) : undefined,
+ config.closeable ? dom('span', { class: 'translate-x-4 text-light-100 dark:text-dark-100', listeners: { click: () => Toaster.close(config), } }, [ icon('radix-icons:cross-1', { width: 12, height: 12, noobserver: true, class: 'cursor-pointer' }) ]) : undefined,
+ config.content ? dom('span', { class: 'text-sm col-span-8 text-light-100 dark:text-dark-100', text: config.content }) : undefined,
+ ]),
+ config.timer ? dom('div', { class: 'relative overflow-hidden bg-light-25 dark:bg-dark-25 h-1 mb-0 mt-0 w-full group-data-[type=error]:*:bg-light-red dark:group-data-[type=error]:*:bg-dark-red group-data-[type=success]:*:bg-light-green dark:group-data-[type=success]:*:bg-dark-green group-data-[type=error]:bg-light-red dark:group-data-[type=error]:bg-dark-red group-data-[type=success]:bg-light-green dark:group-data-[type=success]:bg-dark-green !bg-opacity-50' }, [ loader ]) : undefined
+ ]);
+ config.dom.addEventListener('mousedown', dragstart);
+ config.dom.animate([{ transform: 'translateX(100%)' }, { transform: 'translateX(0)' }], { duration: 150, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' });
+ Toaster._container?.appendChild(config.dom);
+ Toaster._list.push(config);
+
+ setTimeout(() => Toaster.close(config), config.duration);
+ }
+ static clear(type?: ToastType)
+ {
+ Toaster._list.filter(e => e.type !== type || (Toaster.close(e), false));
+ }
+ private static close(config: ToastDom)
+ {
+ config.dom.animate([
+ { opacity: 1 }, { opacity: 0 },
+ ], { easing: 'ease-in', duration: 100 }).onfinish = () => config.dom.remove();
+ Toaster._list = Toaster._list.filter(e => e !== config);
+ }
}
\ No newline at end of file
diff --git a/shared/feature.util.ts b/shared/feature.util.ts
index f7d2bff..222d6db 100644
--- a/shared/feature.util.ts
+++ b/shared/feature.util.ts
@@ -4,7 +4,7 @@ import { MarkdownEditor } from "#shared/editor.util";
import { fakeA } from "#shared/proses";
import { button, combobox, foldable, input, multiselect, numberpicker, select, table, toggle, type Option } from "#shared/components.util";
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
-import { ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
+import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
import characterConfig from "#shared/character-config.json";
import { getID } from "#shared/general.util";
import renderMarkdown, { renderText } from "#shared/markdown.util";
@@ -32,18 +32,15 @@ export class HomebrewBuilder
this._tabsHeader = [
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(0) } }, [text("Peuples")]),
dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(1) } }, [text("Entrainement")]),
- dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(2) } }, [text("Compétences")]),
- dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(3) } }, [text("Aspect")]),
- dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(4) } }, [text("Sorts")]),
- dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(5) } }, [text("Listes")]),
+ dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(2) } }, [text("Aspect")]),
+ dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(3) } }, [text("Sorts")]),
+ //dom("div", { class: "px-2 py-1 border-b border-transparent hover:border-accent-blue cursor-pointer data-[state=active]:border-b-2 data-[state=active]:border-accent-blue", listeners: { click: e => this.display(4) } }, [text("Listes")]),
];
this._tabsContent = [
new PeopleEditor(this, this._config),
new TrainingEditor(this, this._config),
- new AbilityEditor(this, this._config),
new AspectEditor(this, this._config),
new SpellEditor(this, this._config),
- /* new ListEditor(this), */
];
this._content = div('flex-1 outline-none max-w-full w-full overflow-y-auto');
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
@@ -237,21 +234,6 @@ class TrainingEditor extends BuilderTab
this._statContainer.style.left = `-${tab * 100}%`;
}
}
-class AbilityEditor extends BuilderTab
-{
- constructor(builder: HomebrewBuilder, config: CharacterConfig)
- {
- super(builder, config);
-
- this._content = [ div('flex px-24 py-4', [table(Object.entries(config.abilities).map(e => ({
- max1: select(MAIN_STATS.map(e => ({ text: mainStatTexts[e], value: e })), { change: (value) => e[1].max[0] = value, defaultValue: e[1].max[0], class: { container: 'w-full !m-0' } }),
- max2: select(MAIN_STATS.map(e => ({ text: mainStatTexts[e], value: e })), { change: (value) => e[1].max[1] = value, defaultValue: e[1].max[1], class: { container: 'w-full !m-0' } }),
- name: input('text', { input: (value) => e[1].name = value, placeholder: 'Nom', defaultValue: e[1].name, class: 'w-full !m-0' }),
- description: input('text', { input: (value) => e[1].description = value, placeholder: 'Description', defaultValue: e[1].description, class: 'w-full !m-0' }),
- id: div('w-full !m-0', [ text(e[0]) ]),
- })), { id: 'ID', name: 'Nom', description: 'Description', max1: 'Stat 1', max2: 'Stat 2' }, { class: { table: 'flex-1' } })] ) ];
- }
-}
class AspectEditor extends BuilderTab
{
constructor(builder: HomebrewBuilder, config: CharacterConfig)
@@ -396,6 +378,7 @@ export class FeatureEditor
dom('span', { class: 'pb-1 md:p-0', text: "Description" }),
tooltip(button(icon('radix-icons:clipboard', { width: 20, height: 20 }), () => {
MarkdownEditor.singleton.content = this._feature?.effect.map(e => textFromEffect(e)).join('\n') ?? this._feature?.description ?? MarkdownEditor.singleton.content;
+ if(this._feature?.description) this._feature.description = MarkdownEditor.singleton.content;
}, 'p-1'), 'Description automatique', 'left'),
]),
div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ MarkdownEditor.singleton.dom ]),
@@ -606,8 +589,8 @@ const featureChoices: Option>[] = [
{ text: 'Arbre de magie (Instinct)', value: { category: 'value', property: 'mastery/magicinstinct', operation: 'add', value: 1 } }
] },
{ text: 'Compétences', value: [
- ...Object.keys(config.abilities).map((e) => ({ text: config.abilities[e as keyof typeof config.abilities].name, value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 1 } })) as Option>[],
- { text: 'Max de compétence', value: Object.keys(config.abilities).map((e) => ({ text: config.abilities[e as keyof typeof config.abilities].name, value: { category: 'value', property: `bonus/abilities/${e}`, operation: 'add', value: 1 } })) }
+ ...ABILITIES.map((e) => ({ text: abilityTexts[e as Ability], value: { category: 'value', property: `abilities/${e}`, operation: 'add', value: 1 } })) as Option>[],
+ { text: 'Max de compétence', value: ABILITIES.map((e) => ({ text: abilityTexts[e as Ability], value: { category: 'value', property: `bonus/abilities/${e}`, operation: 'add', value: 1 } })) }
] },
{ text: 'Modifieur', value: [
{ text: 'Modifieur de force', value: { category: 'value', property: 'modifier/strength', operation: 'add', value: 1 } },
@@ -645,7 +628,7 @@ const featureChoices: Option>[] = [
{ text: 'Psyché', egory: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 }
]} as Partial}
] },
- { text: 'Bonus', value: Object.keys(config.resistances).map((e: Resistance) => ({ text: config.resistances[e]!.name, value: { category: 'value', property: `resistance/${e}`, operation: 'add', value: 1 } })) },
+ { text: 'Bonus', value: RESISTANCES.map(e => ({ text: resistanceTexts[e as Resistance], value: { category: 'value', property: `resistance/${e}`, operation: 'add', value: 1 } })) },
{ text: 'Rang', value: [
{ text: 'Sorts de précision', value: { category: 'value', property: 'spellranks/precision', operation: 'add', value: 1 } },
{ text: 'Sorts de savoir', value: { category: 'value', property: 'spellranks/knowledge', operation: 'add', value: 1 } },
@@ -760,15 +743,15 @@ function textFromEffect(effect: Partial): string
switch(splited[1])
{
case 'resistance':
- return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: 'Maitrise des armes (for.) ', positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: 'Maitrise des armes (for.) fixée à ' }, suffix: { truely: '.' }, falsely: 'Opération interdite (Maitrise for = interdit).' });
+ return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ` aux jets de résistance de ${resistanceTexts[splited[2] as Resistance]}.` } }) : textFromValue(effect.value, { prefix: { truely: `Jets de résistance de ${resistanceTexts[splited[2] as Resistance]} = ` }, suffix: { truely: '.' }, falsely: `Opération interdite (Résistance ${resistanceTexts[splited[2] as Resistance]} = interdit).` });
case 'abilities':
- return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : effect.operation === 'set' ? textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${config.abilities[splited[2] as Ability].name} max = interdit).` }) : textFromValue(effect.value, { prefix: { truely: `Max de ${config.abilities[splited[2] as Ability].name} min à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${config.abilities[splited[2] as Ability].name} max = interdit).` });
+ return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : effect.operation === 'set' ? textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` }) : textFromValue(effect.value, { prefix: { truely: `Max de ${abilityTexts[splited[2] as Ability]} min à ` }, suffix: { truely: '.' }, falsely: `Opération interdite ( ${abilityTexts[splited[2] as Ability]} max = interdit).` });
default: return 'Bonus inconnu';
}
case 'resistance':
- return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${config.resistances[splited[1] as Resistance]!.name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${config.resistances[splited[1] as Resistance]!.name} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite (${config.resistances[splited[1] as Resistance]!.name} = interdit).` });
+ return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${resistanceTexts[splited[1] as Resistance]} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `Difficulté des jets de résistance de ${resistanceTexts[splited[1] as Resistance]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite (${resistanceTexts[splited[1] as Resistance]} = interdit).` });
case 'abilities':
- return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `${config.abilities[splited[1] as Ability].name} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `${config.abilities[splited[1] as Ability].name} fixé à ` }, suffix: { truely: '.' }, falsely: `Echec automatique de ${config.abilities[splited[1] as Ability].name}.` });
+ return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { truely: `${abilityTexts[splited[1] as Ability]} `, positive: '+', text: '+Mod. de ' }, suffix: { truely: '.' } }) : textFromValue(effect.value, { prefix: { truely: `${abilityTexts[splited[1] as Ability]} fixé à ` }, suffix: { truely: '.' }, falsely: `Echec automatique de ${abilityTexts[splited[1] as Ability]}.` });
case 'modifier':
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+' }, suffix: { truely: ` au mod. de ${mainStatTexts[splited[1] as MainStat]}.` } }) : textFromValue(effect.value, { prefix: { truely: `Mod. de ${mainStatTexts[splited[1] as MainStat]} fixé à ` }, suffix: { truely: '.' }, falsely: `Opération interdite (Mod. de ${mainStatShortTexts[splited[1] as MainStat]} = interdit).` });
default: break;
diff --git a/shared/floating.util.ts b/shared/floating.util.ts
index 308a794..6f0981c 100644
--- a/shared/floating.util.ts
+++ b/shared/floating.util.ts
@@ -290,7 +290,7 @@ export function tooltip(container: HTMLElement, txt: string | Text, placement: F
delay: delay,
content: [ typeof txt === 'string' ? text(txt) : txt ],
placement: placement,
- class: "fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50"
+ class: "fixed hidden TooltipContent border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50 max-w-96"
});
}
diff --git a/types/character.d.ts b/types/character.d.ts
index d130321..86c49a6 100644
--- a/types/character.d.ts
+++ b/types/character.d.ts
@@ -1,4 +1,4 @@
-import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS } from "#shared/character.util";
+import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES } from "#shared/character.util";
export type MainStat = typeof MAIN_STATS[number];
export type Ability = typeof ABILITIES[number];
@@ -8,10 +8,10 @@ export type SpellType = typeof SPELL_TYPES[number];
export type Category = typeof CATEGORIES[number];
export type SpellElement = typeof SPELL_ELEMENTS[number];
export type Alignment = typeof ALIGNMENTS[number];
+export type Resistance = typeof RESISTANCES[number];
export type FeatureID = string;
export type i18nID = string;
-export type Resistance = string;
export type Character = {
id: number;
@@ -51,9 +51,7 @@ type ItemState = {
};
export type CharacterConfig = {
peoples: Record;
- resistances: Record;
training: Record>;
- abilities: Record;
spells: SpellConfig[];
aspects: AspectConfig[];
features: Record;
@@ -108,11 +106,6 @@ export type SpellConfig = {
concentration: boolean;
tags?: string[];
};
-export type AbilityConfig = {
- max: [MainStat, MainStat];
- name: string;
- description: string;
-};
export type RaceConfig = {
id: string;
name: string;