+
+
\ No newline at end of file
diff --git a/shared/canvas.util.ts b/shared/canvas.util.ts
index def2532..7712d72 100644
--- a/shared/canvas.util.ts
+++ b/shared/canvas.util.ts
@@ -4,7 +4,7 @@ import { dom, icon, svg, text } from "#shared/dom.util";
import render from "#shared/markdown.util";
import { popper, tooltip } from "#shared/floating.util";
import { History } from "#shared/history.util";
-import { fakeA } from "#shared/proses";
+import { preview } from "#shared/proses";
import { SpatialGrid } from "#shared/physics.util";
import type { CanvasPreferences } from "~/types/general";
@@ -189,7 +189,7 @@ export class NodeEditable extends Node
this.nodeDom = dom('div', { class: ['absolute group', {'-z-10': this.properties.type === 'group', 'z-10': this.properties.type !== 'group'}], style: { transform: `translate(${this.properties.x}px, ${this.properties.y}px)`, width: `${this.properties.width}px`, height: `${this.properties.height}px`, '--canvas-color': this.properties.color?.hex } }, [
dom('div', { class: ['outline-0 transition-[outline-width] border-2 bg-light-20 dark:bg-dark-20 w-full h-full group-hover:outline-4', style.border, style.outline] }, [
- dom('div', { class: ['w-full h-full py-2 px-4 flex !bg-opacity-[0.07] overflow-auto', style.bg], listeners: this.properties.type === 'text' ? { mouseenter: e => this.dispatchEvent(new CustomEvent('focus', { detail: this })), click: e => this.dispatchEvent(new CustomEvent('select', { detail: this })), dblclick: e => this.dispatchEvent(new CustomEvent('edit', { detail: this })) } : undefined }, [this.properties.text ? dom('div', { class: 'flex items-center' }, [render(this.properties.text, undefined, { tags: { a: fakeA } })]) : undefined])
+ dom('div', { class: ['w-full h-full py-2 px-4 flex !bg-opacity-[0.07] overflow-auto', style.bg], listeners: this.properties.type === 'text' ? { mouseenter: e => this.dispatchEvent(new CustomEvent('focus', { detail: this })), click: e => this.dispatchEvent(new CustomEvent('select', { detail: this })), dblclick: e => this.dispatchEvent(new CustomEvent('edit', { detail: this })) } : undefined }, [this.properties.text ? dom('div', { class: 'flex items-center' }, [render(this.properties.text, undefined, { tags: { a: preview } })]) : undefined])
])
]);
diff --git a/shared/character-config.json b/shared/character-config.json
index 8b8c8a8..ccbf1df 100644
--- a/shared/character-config.json
+++ b/shared/character-config.json
@@ -4877,28 +4877,40 @@
"text": "Vous avez un bonus de +1 aux jets de résistance de ",
"options": [
{
- "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
+ "text": "Force",
+ "effects": [
+ {
+ "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/strength",
+ "value": 1
+ }
+ ]
},
{
- "id": "41mflh7px0otbj169q8mr5btc8qie18g",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
+ "text": "Dextérité",
+ "effects": [
+ {
+ "id": "41mflh7px0otbj169q8mr5btc8qie18g",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/dexterity",
+ "value": 1
+ }
+ ]
},
{
- "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
+ "text": "Constitution",
+ "effects": [
+ {
+ "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/constitution",
+ "value": 1
+ }
+ ]
}
]
},
@@ -5074,28 +5086,40 @@
},
"options": [
{
- "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
- "category": "value",
"text": "Force",
- "operation": "add",
- "property": "bonus/defense/strength",
- "value": 1
+ "effects": [
+ {
+ "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/strength",
+ "value": 1
+ }
+ ]
},
{
- "id": "41mflh7px0otbj169q8mr5btc8qie18g",
- "category": "value",
"text": "Dextérité",
- "operation": "add",
- "property": "bonus/defense/dexterity",
- "value": 1
+ "effects": [
+ {
+ "id": "41mflh7px0otbj169q8mr5btc8qie18g",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/dexterity",
+ "value": 1
+ }
+ ]
},
{
- "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
- "category": "value",
"text": "Constitution",
- "operation": "add",
- "property": "bonus/defense/constitution",
- "value": 1
+ "effects": [
+ {
+ "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/constitution",
+ "value": 1
+ }
+ ]
}
]
}
@@ -5243,28 +5267,40 @@
"text": "Une fois par [[3. Glossaire#Long repos|long repos]], vous pouvez réussir votre [[3. Résistance aux chocs#Le jet de résistance|jet de résistance]] de cette statistique sans lancer de dés.",
"options": [
{
- "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
- "category": "value",
"text": "Force",
- "operation": "add",
- "property": "bonus/defense/strength",
- "value": 1
+ "effects": [
+ {
+ "id": "sx1vca2kzustsjatvslbjl68guv45m0b",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/strength",
+ "value": 1
+ }
+ ]
},
{
- "id": "41mflh7px0otbj169q8mr5btc8qie18g",
- "category": "value",
"text": "Dextérité",
- "operation": "add",
- "property": "bonus/defense/dexterity",
- "value": 1
+ "effects": [
+ {
+ "id": "41mflh7px0otbj169q8mr5btc8qie18g",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/dexterity",
+ "value": 1
+ }
+ ]
},
{
- "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
- "category": "value",
"text": "Constitution",
- "operation": "add",
- "property": "bonus/defense/constitution",
- "value": 1
+ "effects": [
+ {
+ "id": "55vp7dpdto073hrqg11aemyxxo9skg0q",
+ "category": "value",
+ "operation": "add",
+ "property": "bonus/defense/constitution",
+ "value": 1
+ }
+ ]
}
]
}
@@ -5768,87 +5804,14 @@
]
},
"dxlevxrlacugpj4jvdjs5bxecraoxbnp": {
- "description": "Choisissez une [[1. Magie#Les éléments|classe élémentaire]]. Lorsque vous voyez un sort de cet élément être lancé à 12 cases de vous, vous pouvez [[2. Actions en combat#Saisir une opportunité|saisir l'opportunité]] pour dépenser l'intégralité du coût en mana à la place du lanceur. *Vous appliquez le coût en mana du lanceur d'origine.*",
+ "description": "Choisissez une [[1. Magie#Les éléments|classe élémentaire]]. Lorsque vous voyez un sort de cet élément être lancé à 12 cases de vous, vous pouvez [[2. Actions en combat#Saisir une opportunité|saisir l'opportunité]] pour dépenser l'intégralité du coût en mana à la place du lanceur. *Vous appliquez le coût en mana du lanceur d'origine.* #todo",
"id": "dxlevxrlacugpj4jvdjs5bxecraoxbnp",
"effect": [
{
"id": "ix2y02up7p04hzv1bhyer0cnl5eedjj3",
"category": "choice",
"text": "Lorsque vous voyez un sort de cet élément être lancé à 12 cases de vous, vous pouvez saisir l'opportunité pour dépenser l'intégralité du coût en mana à la place du lanceur.",
- "options": [
- {
- "id": "hrz76l4hu874uyz1to2yh2cej71hyk67",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "yp8ito93tx24f8htq4fdougeyjf31y85",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "o8v66orebhiemsnn4rkffs705sqjml0f",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "w7hvncya5m7xbi6igda7r9tlbdkuyg1t",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "ubtwd3sl3y27heps9ev99w8piur2l0zv",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "7kwst41c2eecgop178rfcszidz2t44pf",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "47aqw1fy16dszuircpp1vxco2twq9gah",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "xz3bfma0nh3q7csn83kt2ftij6irpxsg",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "vtowf22lk7gl0rgpjfkss8uh50peaj3o",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- }
- ]
+ "options": []
}
]
},
@@ -6993,52 +6956,80 @@
"options": [
{
"text": "Force",
- "category": "value",
- "property": "modifier/strength",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/strength",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Dextérité",
- "category": "value",
- "property": "modifier/dexterity",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/dexterity",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Constitution",
- "category": "value",
- "property": "modifier/constitution",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/constitution",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Intelligence",
- "category": "value",
- "property": "modifier/intelligence",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/intelligence",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Curiosité",
- "category": "value",
- "property": "modifier/curiosity",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/curiosity",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Charisme",
- "category": "value",
- "property": "modifier/charisma",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/charisma",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Psyché",
- "egory": "value",
- "property": "modifier/psyche",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "egory": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
+ }
+ ]
}
]
},
@@ -7144,151 +7135,14 @@
]
},
"7ii1ig85j7a1gacorzkn6oyjdt3w6jzh": {
- "description": "Choisissez une compétence. Si vous faites 6 ou moins à votre jet, vous considérez que votre jet est un 6. *Ne fonctionne pas sur les jets de fabrications et les jets d'œuvres*",
+ "description": "Choisissez une compétence. Si vous faites 6 ou moins à votre jet, vous considérez que votre jet est un 6. *Ne fonctionne pas sur les jets de fabrications et les jets d'œuvres* #todo",
"id": "7ii1ig85j7a1gacorzkn6oyjdt3w6jzh",
"effect": [
{
"id": "v0lf1gwsairuei43r3u3eyc5v57segtc",
"category": "choice",
"text": "Vous ne pouvez pas faire moins de 6 sur vos jets de ",
- "options": [
- {
- "id": "cr0vgpzpca3gcjfiqfq4yy4ta14n6fjr",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "34ghl5vv2rwmr0zxznqee4wld2ng9q94",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "xceir7gw2atr8om3dt89jbjjihijbhre",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "0sg0evb589nlihsoleqvchdp5orqx9dv",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "7sdozpx5qmrisc7wcxule5w7fk931rxi",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "z482fmbjy7i5r1g51ie223392rb161gi",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "nd02a9m53ypyk3jen1mzt1nn9spj92dx",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "g3h2igg57rxpchnyg53jxk64vggzjcs4",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "hdeskp2s6zlw4xcfcaal5svo907bozhc",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "i9ie2gpcmn2pzxzz2dx4n0il92n68s6o",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "804h26knc7eh06s1t94x5gfg9i57sikz",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "luf7aprc50wr15wdrt1w1mimj3wgp2f6",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "a9ie2rutp48b0zjfvrjmz4rff3r7ujqi",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "rxrlyyf2azjs04fg4lno6oaao3fqwp2l",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "zw6ifv9b5a62asi43p8jtll8y34tfihq",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "ugxqcqnsl6u76tbv1hl0nvykeabp3a5f",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "dxpo0dq6qy48n1plte8ii2n7z4pg6tlw",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- }
- ]
+ "options": []
}
]
},
@@ -7343,52 +7197,80 @@
"options": [
{
"text": "Modifieur de force",
- "category": "value",
- "property": "modifier/strength",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/strength",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur de dextérité",
- "category": "value",
- "property": "modifier/dexterity",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/dexterity",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur de constitution",
- "category": "value",
- "property": "modifier/constitution",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/constitution",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur d'intelligence",
- "category": "value",
- "property": "modifier/intelligence",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/intelligence",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur de curiosité",
- "category": "value",
- "property": "modifier/curiosity",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/curiosity",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur de charisme",
- "category": "value",
- "property": "modifier/charisma",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "category": "value",
+ "property": "modifier/charisma",
+ "operation": "add",
+ "value": 1
+ }
+ ]
},
{
"text": "Modifieur de psyché",
- "egory": "value",
- "property": "modifier/psyche",
- "operation": "add",
- "value": 1
+ "effects": [
+ {
+ "egory": "value",
+ "property": "modifier/psyche",
+ "operation": "add",
+ "value": 1
+ }
+ ]
}
]
}
@@ -8225,47 +8107,14 @@
]
},
"s5kidncgfzw85ffubl718lx2f68suhqf": {
- "description": "Votre connexion innée avec la magie vous a bénie d'un don pour cet art. Choisissez une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]]. Vous gagnez le premier niveau de cette branche.",
+ "description": "Votre connexion innée avec la magie vous a bénie d'un don pour cet art. Choisissez une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]]. Vous gagnez le premier niveau de cette branche. #todo",
"id": "s5kidncgfzw85ffubl718lx2f68suhqf",
"effect": [
{
"id": "yjc9xk64ygtc5tugluia031nhxgxvi6z",
"category": "choice",
"text": "Vous gagnez le premier niveau de la branche de ",
- "options": [
- {
- "id": "l8b2fdpvjpihjeaqj1rs3el5w5jj0zww",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "xxscipqcvk2q5q97a3c926t523x6awth",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "i413jzkxi0tdjjj1aaz6m0bkm94uqahf",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "82pzzioqy1whd59xfdaiw88osvqqbqjw",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- }
- ]
+ "options": []
},
{
"id": "pzgqz28pnupmfmcf6mc7wmuhry775f7f",
@@ -8394,47 +8243,14 @@
]
},
"qf3eru17f8u3hysq56k246mlq7p2rbc9": {
- "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau.",
+ "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau. #todo",
"id": "qf3eru17f8u3hysq56k246mlq7p2rbc9",
"effect": [
{
"id": "460k5ti0iesdfc8j4mlh6nrdrzg67g6f",
"category": "choice",
"text": "Vous gagnez un niveau dans la branche de ",
- "options": [
- {
- "id": "l8b2fdpvjpihjeaqj1rs3el5w5jj0zww",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "xxscipqcvk2q5q97a3c926t523x6awth",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "i413jzkxi0tdjjj1aaz6m0bkm94uqahf",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "82pzzioqy1whd59xfdaiw88osvqqbqjw",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- }
- ]
+ "options": []
},
{
"id": "8qddimnu5vwleys9fjoq84ju3d09ejpq",
@@ -8563,47 +8379,14 @@
]
},
"sw45zzv7bf6v35h064f6zhcj1e7xbbr5": {
- "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau.",
+ "description": "Vous gagnez un niveau dans une branche de l'[[1. Les évolutions de valeur.canvas#L'arbre de magie|arbre de magie]] dans laquelle vous avez déjà au moins un niveau. #todo",
"id": "sw45zzv7bf6v35h064f6zhcj1e7xbbr5",
"effect": [
{
"id": "pbpmdu5tgvi1saopqseq5mj7qqml3z3k",
"category": "choice",
"text": "Vous gagnez un niveau dans la branche de ",
- "options": [
- {
- "id": "l8b2fdpvjpihjeaqj1rs3el5w5jj0zww",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "xxscipqcvk2q5q97a3c926t523x6awth",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "i413jzkxi0tdjjj1aaz6m0bkm94uqahf",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- },
- {
- "id": "82pzzioqy1whd59xfdaiw88osvqqbqjw",
- "category": "value",
- "text": "",
- "operation": "add",
- "property": "",
- "value": 0
- }
- ]
+ "options": []
},
{
"id": "qpq7g3m86jfpaopm1jofyfz6j69wk2nq",
@@ -9262,52 +9045,80 @@
"options": [
{
"text": "Force",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/strength"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/strength"
+ }
+ ]
},
{
"text": "Dextérité",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/dexterity"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/dexterity"
+ }
+ ]
},
{
"text": "Constitution",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/constitution"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/constitution"
+ }
+ ]
},
{
"text": "Intelligence",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/intelligence"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/intelligence"
+ }
+ ]
},
{
"text": "Curiosité",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/curiosity"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/curiosity"
+ }
+ ]
},
{
"text": "Charisme",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/charisma"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/charisma"
+ }
+ ]
},
{
"text": "Psyché",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/psyche"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/psyche"
+ }
+ ]
}
]
},
@@ -9910,52 +9721,80 @@
"options": [
{
"text": "Force",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/strength"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/strength"
+ }
+ ]
},
{
"text": "Dextérité",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/dexterity"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/dexterity"
+ }
+ ]
},
{
"text": "Constitution",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/constitution"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/constitution"
+ }
+ ]
},
{
"text": "Intelligence",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/intelligence"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/intelligence"
+ }
+ ]
},
{
"text": "Curiosité",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/curiosity"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/curiosity"
+ }
+ ]
},
{
"text": "Charisme",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/charisma"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/charisma"
+ }
+ ]
},
{
"text": "Psyché",
- "category": "value",
- "operation": "add",
- "value": 1,
- "property": "modifier/psyche"
+ "effects": [
+ {
+ "category": "value",
+ "operation": "add",
+ "value": 1,
+ "property": "modifier/psyche"
+ }
+ ]
}
],
"text": "+1 au modifieur de "
@@ -9992,12 +9831,12 @@
},
"dx5khvrhwkhhn8fv4b8pecuh8i5wtwij": {
"id": "dx5khvrhwkhhn8fv4b8pecuh8i5wtwij",
- "description": "",
+ "description": "le Temps des Tempetes",
"effect": []
},
"pfzopr4oyrsgxg0cbva16zzzly3kke9z": {
"id": "pfzopr4oyrsgxg0cbva16zzzly3kke9z",
- "description": "",
+ "description": "pour Audible",
"effect": []
},
"fk0wmg94tlq78khq8zot2o5u4nnxr2gb": {
diff --git a/shared/character.util.ts b/shared/character.util.ts
index 7b64b7e..fd6d404 100644
--- a/shared/character.util.ts
+++ b/shared/character.util.ts
@@ -1,12 +1,14 @@
import type { Ability, Alignment, Character, CharacterConfig, CharacterVariables, 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, Toaster, toggle } from "#shared/components.util";
+import proses, { preview } from "#shared/proses";
+import { button, buttongroup, foldable, input, loading, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
import { div, dom, icon, text } from "#shared/dom.util";
-import { followermenu, tooltip } from "#shared/floating.util";
+import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
import { clamp } from "#shared/general.util";
-import markdownUtil from "#shared/markdown.util";
+import markdown from "#shared/markdown.util";
+import { getText } from "./i18n";
+import type { User } from "~/types/auth";
const config = characterConfig as CharacterConfig;
@@ -339,7 +341,7 @@ export class CharacterCompiler
const choice = this._character.choices[feature.id];
if(choice)
- choice.forEach(e => this.apply(feature.options[e]!));
+ choice.forEach(e => feature.options[e]!.effects.forEach(this.apply.bind(this)));
return;
default:
@@ -373,7 +375,7 @@ export class CharacterCompiler
const choice = this._character.choices[feature.id];
if(choice)
- choice.forEach(e => this.undo(feature.options[e]!));
+ choice.forEach(e => feature.options[e]!.effects.forEach(this.undo.bind(this)));
return;
default:
@@ -726,6 +728,7 @@ class PeoplePicker extends BuilderTab
this._options = Object.values(config.peoples).map(
(people, i) => dom("div", { class: "flex flex-col flex-nowrap gap-2 p-2 border border-light-35 dark:border-dark-35 cursor-pointer hover:border-light-70 dark:hover:border-dark-70 w-[320px]", listeners: { click: () => {
this._builder.character.people = people.id;
+ this._builder.character = { ...this._builder.character, people: people.id };
"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));
}
@@ -880,7 +883,7 @@ class TrainingPicker extends BuilderTab
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 ]);
+ }}}, [ markdown(config.features[option]!.description, undefined, { tags: { a: preview } }), choice ]);
}))
]);
}
@@ -1135,4 +1138,376 @@ class AspectPicker extends BuilderTab
return true;
}
+}
+
+export class CharacterSheet
+{
+ character?: CharacterCompiler;
+ container: HTMLElement = div();
+ constructor(id: string, user: ComputedRef
)
+ {
+ const load = div("flex justify-center items-center w-full h-full", [ loading('large') ]);
+ this.container.replaceChildren(load);
+ useRequestFetch()(`/api/character/${id}`).then(character => {
+ if(character)
+ {
+ this.character = new CharacterCompiler(character);
+
+ document.title = `d[any] - ${character.name}`;
+ load.remove();
+
+ this.render();
+ }
+ else
+ {
+ //ERROR
+ }
+ });
+ }
+ render()
+ {
+ if(!this.character)
+ return;
+
+ const character = this.character.compiled;
+ console.log(character);
+ this.container.replaceChildren(div('flex flex-col justify-center gap-1', [
+ div("flex flex-row gap-4 justify-between", [
+ div(),
+
+ div("flex lg:flex-row flex-col gap-6 items-center justify-center", [
+ div("flex gap-6 items-center", [
+ div('inline-flex select-none items-center justify-center overflow-hidden align-middle h-16', [
+ div('text-light-100 dark:text-dark-100 leading-1 flex p-4 items-center justify-center bg-light-25 dark:bg-dark-25 font-medium', [
+ icon("radix-icons:person", { width: 16, height: 16 }),
+ ])
+ ]),
+
+ div("flex flex-col", [
+ dom("span", { class: "text-xl font-bold", text: character.name }),
+ dom("span", { class: "text-sm", text: `De ${character.username}` })
+ ]),
+
+ div("flex flex-col", [
+ dom("span", { class: "font-bold", text: `Niveau ${character.level}` }),
+ dom("span", { text: config.peoples[character.race]?.name ?? 'Peuple inconnu' })
+ ])
+ ]),
+
+ div("flex flex-row lg:border-l border-light-35 dark:border-dark-35 py-4 ps-4 gap-8", [
+ dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
+ text("PV: "),
+ dom("span", {
+ class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
+ text: `${character.health - character.variables.health}`
+ }),
+ text(`/ ${character.health}`)
+ ]),
+ dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
+ text("Mana: "),
+ dom("span", {
+ class: "font-bold px-2 border-transparent border cursor-pointer hover:border-light-35 dark:hover:border-dark-35",
+ text: `${character.mana - character.variables.mana}`
+ }),
+ text(`/ ${character.mana}`)
+ ])
+ ])
+ ]),
+
+ div("self-center", [
+ /* user && user.id === character.owner ?
+ button(icon("radix-icons:pencil-2"), () => {
+ }, "icon")
+ : div() */
+ ])
+ ]),
+
+ div("flex flex-row justify-center 2xl:gap-4 gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
+ div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.strength}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Force" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.dexterity}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Dextérité" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.constitution}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Constitution" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.intelligence}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Intelligence" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.curiosity}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Curiosité" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.charisma}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Charisme" })
+ ]),
+ div("flex flex-col items-center px-2", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.psyche}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Psyché" })
+ ])
+ ]),
+
+ div('border-l border-light-35 dark:border-dark-35'),
+
+ div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
+ div("flex flex-col px-2 items-center", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.initiative}` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Initiative" })
+ ]),
+ div("flex flex-col px-2 items-center", [
+ dom("span", { class: "2xl:text-2xl text-xl font-bold", text: character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Course" })
+ ])
+ ]),
+
+ div('border-l border-light-35 dark:border-dark-35'),
+
+ div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
+ icon("game-icons:checked-shield", { width: 32, height: 32 }),
+ div("flex flex-col px-2 items-center", [
+ dom("span", {
+ class: "2xl:text-2xl text-xl font-bold",
+ text: `${clamp(character.defense.static + character.defense.passivedodge + character.defense.passiveparry, 0, character.defense.hardcap)}`
+ }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Passive" })
+ ]),
+ div("flex flex-col px-2 items-center", [
+ dom("span", {
+ class: "2xl:text-2xl text-xl font-bold",
+ text: `${clamp(character.defense.static + character.defense.passivedodge + character.defense.activeparry, 0, character.defense.hardcap)}`
+ }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Blocage" })
+ ]),
+ div("flex flex-col px-2 items-center", [
+ dom("span", {
+ class: "2xl:text-2xl text-xl font-bold",
+ text: `${clamp(character.defense.static + character.defense.activedodge + character.defense.passiveparry, 0, character.defense.hardcap)}`
+ }),
+ dom("span", { class: "text-sm 2xl:text-base", text: "Esquive" })
+ ])
+ ]),
+ ]),
+
+ div("flex flex-1 flex-row items-stretch justify-center py-2 gap-4", [
+ div("flex flex-col gap-4 py-1 w-80", [
+ div("flex flex-col py-1 gap-4", [
+ div("flex flex-row items-center justify-center gap-4", [
+ div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-semibold', text: "Compétences" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/l\'entrainement/competences', class: 'h-4' }) ]),
+ div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
+ ]),
+
+ div("grid grid-cols-3 gap-2",
+ Object.entries(character.abilities).map(([ability, value]) =>
+ div("flex flex-col px-2 items-center text-sm text-light-70 dark:text-dark-70", [
+ dom("span", { class: "font-bold text-base text-light-100 dark:text-dark-100", text: `+${value}` }),
+ dom("span", { text: abilityTexts[ability as Ability] || ability })
+ ])
+ )
+ ),
+
+
+ div("flex flex-row items-center justify-center gap-4", [
+ div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-xl font-semibold', text: "Maitrises" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/l\'entrainement/competences', class: 'h-4' }) ]),
+ div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
+ ]),
+
+ character.mastery.strength + character.mastery.dexterity > 0 ? div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
+ character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme légère') ], { href: 'regles/annexes/equipement#Les armes légères' }) : undefined,
+ character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme de jet') ], { href: 'regles/annexes/equipement#Les armes de jet' }) : undefined,
+ character.mastery.strength + character.mastery.dexterity > 0 ? proses('a', preview, [ text('Arme naturelle') ], { href: 'regles/annexes/equipement#Les armes naturelles' }) : undefined,
+ character.mastery.strength > 1 ? proses('a', preview, [ text('Arme standard') ], { href: 'regles/annexes/equipement#Les armes' }) : undefined,
+ character.mastery.strength > 1 ? proses('a', preview, [ text('Arme improvisée') ], { href: 'regles/annexes/equipement#Les armes improvisées' }) : undefined,
+ character.mastery.strength > 2 ? proses('a', preview, [ text('Arme lourde') ], { href: 'regles/annexes/equipement#Les armes lourdes' }) : undefined,
+ character.mastery.strength > 3 ? proses('a', preview, [ text('Arme à deux mains') ], { href: 'regles/annexes/equipement#Les armes à deux mains' }) : undefined,
+ character.mastery.dexterity > 0 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme maniable') ], { href: 'regles/annexes/equipement#Les armes maniables' }) : undefined,
+ character.mastery.dexterity > 1 && character.mastery.strength > 1 ? proses('a', preview, [ text('Arme à projectiles') ], { href: 'regles/annexes/equipement#Les armes à projectiles' }) : undefined,
+ character.mastery.dexterity > 1 && character.mastery.strength > 2 ? proses('a', preview, [ text('Arme longue') ], { href: 'regles/annexes/equipement#Les armes longues' }) : undefined,
+ character.mastery.shield > 0 ? proses('a', preview, [ text('Bouclier') ], { href: 'regles/annexes/equipement#Les boucliers' }) : undefined,
+ character.mastery.shield > 0 && character.mastery.strength > 3 ? proses('a', preview, [ text('Bouclier à deux mains') ], { href: 'regles/annexes/equipement#Les boucliers à deux mains' }) : undefined,
+ ]) : undefined,
+
+ character.mastery.armor > 0 ? div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
+ character.mastery.armor > 0 ? proses('a', preview, [ text('Armure légère') ], { href: 'regles/annexes/equipement#Les armures légères' }) : undefined,
+ character.mastery.armor > 1 ? proses('a', preview, [ text('Armure standard') ], { href: 'regles/annexes/equipement#Les armures' }) : undefined,
+ character.mastery.armor > 2 ? proses('a', preview, [ text('Armure lourde') ], { href: 'regles/annexes/equipement#Les armures lourdes' }) : undefined,
+ ]) : undefined,
+
+ div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
+ div('flex flex-row items-center gap-2', [ text('Précision'), dom('span', { text: character.spellranks.precision.toString(), class: 'font-bold' }) ]),
+ div('flex flex-row items-center gap-2', [ text('Savoir'), dom('span', { text: character.spellranks.knowledge.toString(), class: 'font-bold' }) ]),
+ div('flex flex-row items-center gap-2', [ text('Instinct'), dom('span', { text: character.spellranks.instinct.toString(), class: 'font-bold' }) ]),
+ div('flex flex-row items-center gap-2', [ text('Oeuvres'), dom('span', { text: character.spellranks.arts.toString(), class: 'font-bold' }) ]),
+ ])
+ ])
+ ]),
+
+ div('border-l border-light-35 dark:border-dark-35'),
+
+ tabgroup([
+ { id: 'actions', title: [ text('Actions') ], content: () => [
+ div('flex flex-col gap-8', [
+ div('flex flex-col gap-2', [
+ div("flex flex-row items-center justify-center gap-4", [
+ div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-lg font-semibold', text: "Actions" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 12, height: 12, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/le-combat/actions-en-combat#Actions', class: 'h-4' }) ]),
+ div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
+ div('flex flex-row items-center gap-2', [ ...Array(character.action).fill(undefined).map(e => div('border border-dashed border-light-50 dark:border-dark-50 w-5 h-5')), dom('span', { class: 'tracking-tight', text: '/ round' }) ]),
+ ]),
+
+ div('flex flex-col gap-2', [
+ div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Attaquer", "Désarmer", "Saisir", "Faire chuter", "Déplacer", "Courir", "Pas de coté", "Charger", "Lancer un sort", "S'interposer", "Se transformer", "Utiliser un objet", "Anticiper une action", "Improviser"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
+ ...(character.lists.action?.map(e => div('flex flex-col gap-1', [
+ //div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: e.title }), e.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: e.cost }), text(`point${e.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
+ markdown(getText(e), undefined, { tags: { a: preview } }),
+ ])) ?? [])
+ ]),
+ ]),
+ div('flex flex-col gap-2', [
+ div("flex flex-row items-center justify-center gap-4", [
+ div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-lg font-semibold', text: "Réactions" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 12, height: 12, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/le-combat/actions-en-combat#Réaction', class: 'h-4' }) ]),
+ div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
+ div('flex flex-row items-center gap-2', [ ...Array(character.reaction).fill(undefined).map(e => div('border border-dashed border-light-50 dark:border-dark-50 w-5 h-5')), dom('span', { class: 'tracking-tight', text: '/ round' }) ]),
+ ]),
+
+ div('flex flex-col gap-2', [
+ div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Parer", "Esquiver", "Saisir une opportunité", "Prendre en tenaille", "Intercepter"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
+ ...(character.lists.reaction?.map(e => div('flex flex-col gap-1', [
+ //div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: e.title }), e.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: e.cost }), text(`point${e.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
+ markdown(getText(e), undefined, { tags: { a: preview } }),
+ ])) ?? [])
+ ]),
+ ]),
+ div('flex flex-col gap-2', [
+ div("flex flex-row items-center justify-center gap-4", [
+ div("flex flex-row items-center justify-center gap-2", [ dom("div", { class: 'text-lg font-semibold', text: "Actions libres" }), proses('a', preview, [ icon('radix-icons:question-mark-circled', { width: 12, height: 12, class: 'text-light-70 dark:text-dark-70' }) ], { href: 'regles/le-combat/actions-en-combat#Action libre', class: 'h-4' }) ]),
+ div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50"),
+ ]),
+
+ div('flex flex-col gap-2', [
+ div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Analyser une situation", "Communiquer", "Dégainer", "Attraper un objet"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
+ ...(character.lists.freeaction?.map(e => div('flex flex-col gap-1', [
+ //div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: e.title }), e.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: e.cost }), text(`action${e.cost > 1 ? 's' : ''} libre`)]) : undefined]),
+ markdown(getText(e), undefined, { tags: { a: preview } }),
+ ])) ?? [])
+ ]),
+ ]),
+ ]),
+ ] },
+
+ { id: 'abilities', title: [ text('Aptitudes') ], content: () => this.abilitiesTab(character) },
+
+ { id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(character) },
+
+ { id: 'inventory', title: [ text('Inventaire') ], content: () => [
+
+ ] },
+
+ { id: 'notes', title: [ text('Notes') ], content: () => [
+
+ ] },
+ ], { focused: 'abilities', class: { container: 'flex-1 gap-4 px-4 w-[960px]' } }),
+ ])
+ ]));
+ }
+ abilitiesTab(character: CompiledCharacter)
+ {
+ return [
+ div('flex flex-col gap-2', [
+ ...(character.lists.passive?.map(e => div('flex flex-col gap-1', [
+ //div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: e.title }), e.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: e.cost }), text(`action${e.cost > 1 ? 's' : ''} libre`)]) : undefined]),
+ markdown(getText(e), undefined, { tags: { a: preview } }),
+ ])) ?? []),
+ ]),
+ ];
+ }
+ spellTab(character: CompiledCharacter)
+ {
+ return [
+ div('flex flex-col gap-2', [
+ div('flex flex-row justify-between items-center', [
+ div('flex flex-row gap-2 items-center', [
+ dom('span', { class: 'italic tracking-tight text-sm', text: 'Trier par' }),
+ buttongroup([{ text: 'Rang', value: 'rank' }, { text: 'Type', value: 'type' }, { text: 'Element', value: 'element' }], { value: 'rank', class: { option: 'px-2 py-1 text-sm' } }),
+ ])
+ ])
+ ])
+ ]
+ }
+ spellPanel()
+ {
+ if(!this.character)
+ return;
+
+ const character = this.character.compiled;
+ const availableSpells = Object.values(config.spells).filter(spell => {
+ if (spell.rank === 4) return false;
+ if (character.spellranks[spell.type] < spell.rank) return false;
+ return true;
+ });
+
+ const textAmount = text(character.variables.spells.length.toString()), textMax = text(character.spellslots.toString());
+ const container = div("border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-4 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]", [
+ div("flex flex-row justify-between items-center mb-4", [
+ dom("h2", { class: "text-xl font-bold", text: "Ajouter un sort" }),
+ div('flex flex-row gap-4 items-center', [ dom('span', { class: 'italic text-light-70 dark:text-dark-70 text-sm' }, [ textAmount, text(' / '), textMax, text(' sorts maitrisés') ]), tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
+ setTimeout(blocker.close, 150);
+ container.setAttribute('data-state', 'inactive');
+ }, "p-1"), "Fermer", "left") ])
+ ]),
+ div('flex flex-col divide-y *:py-2 -my-2 overflow-y-auto', availableSpells.map(spell => {
+ let state = character.lists.spells?.includes(spell.id) ? 'given' : character.variables.spells.includes(spell.id) ? 'choosen' : 'empty';
+ const toggleText = text(state === 'choosen' ? 'Supprimer' : state === 'given' ? 'Inné' : 'Ajouter'), toggleButton = button(toggleText, () => {
+ if(state === 'choosen')
+ {
+ //this.character.variable('spells', character.variables.spells.filter(e => e !== spell.id)); //TO REWORK
+ state = 'empty';
+ }
+ else if(state === 'empty')
+ {
+ //this.character.variable('spells', [...character.variables.spells, spell.id]); //TO REWORK
+ state = 'choosen';
+ }
+ //character = compiler.compiled; //TO REWORK
+ toggleText.textContent = state === 'choosen' ? 'Supprimer' : state === 'given' ? 'Inné' : 'Ajouter';
+ textAmount.textContent = character.variables.spells.length.toString();
+ }, "px-2 py-1 text-sm font-normal");
+ toggleButton.disabled = state === 'given';
+ return foldable(() => [
+ markdown(spell.effect),
+ ], [ div("flex flex-row justify-between gap-2", [
+ dom("span", { class: "text-lg font-bold", text: spell.name }),
+ div("flex flex-row items-center gap-6", [
+ div("flex flex-row text-sm gap-2",
+ spell.elements.map(el =>
+ dom("span", {
+ class: [`border !border-opacity-50 rounded-full !bg-opacity-20 px-2 py-px`, elementTexts[el].class],
+ text: elementTexts[el].text
+ })
+ )
+ ),
+ div("flex flex-row text-sm gap-1", [
+ ...(spell.rank !== 4 ? [
+ dom("span", { text: `Rang ${spell.rank}` }),
+ text("/"),
+ dom("span", { text: spellTypeTexts[spell.type] }),
+ text("/")
+ ] : []),
+ dom("span", { text: `${spell.cost} mana` }),
+ text("/"),
+ dom("span", { text: typeof spell.speed === "string" ? spell.speed : `${spell.speed} minutes` })
+ ]),
+ toggleButton,
+ ]),
+ ]) ], { open: false, class: { container: "px-2 flex flex-col border-light-35 dark:border-dark-35", content: 'py-2' } });
+ }))
+ ]);
+ const blocker = fullblocker([ container ], { closeWhenOutside: true });
+ setTimeout(() => container.setAttribute('data-state', 'active'), 1);
+ }
}
\ No newline at end of file
diff --git a/shared/components.util.ts b/shared/components.util.ts
index 84565f3..4c71b43 100644
--- a/shared/components.util.ts
+++ b/shared/components.util.ts
@@ -48,6 +48,26 @@ export function button(content: Node, onClick?: () => void, cls?: Class)
})
return btn;
}
+export function buttongroup(options: Array<{ text: string, value: T }>, settings?: { class?: { container?: Class, option?: Class }, value?: T, onChange?: (value: T) => boolean })
+{
+ let currentValue = settings?.value;
+ const elements = options.map(e => dom('div', { class: [`cursor-pointer text-light-100 dark:text-dark-100 hover:bg-light-30 dark:hover:bg-dark-30 flex items-center justify-center bg-light-20 dark:bg-dark-20 leading-none outline-none
+ border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[selected]:z-10 data-[selected]:border-light-50 dark:data-[selected]:border-dark-50
+ data-[selected]:shadow-raw transition-[box-shadow] data-[selected]:shadow-light-50 dark:data-[selected]:shadow-dark-50 focus:shadow-raw focus:shadow-light-40 dark:focus:shadow-dark-40`,
+ settings?.class?.option], text: e.text, attributes: { 'data-selected': settings?.value === e.value }, listeners: { click: function() {
+ if(currentValue !== e.value)
+ {
+ elements.forEach(e => e.toggleAttribute('data-selected', false));
+ this.toggleAttribute('data-selected', true);
+
+ if(!settings?.onChange || settings?.onChange(e.value))
+ {
+ currentValue = e.value;
+ }
+ }
+ }}}))
+ return div(['flex flex-row', settings?.class?.container], elements);
+}
export type Option = { text: string, render?: () => HTMLElement, value: T | Option[] } | undefined;
type StoredOption = { item: Option, dom: HTMLElement, container?: HTMLElement, children?: Array> };
export function select>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
@@ -434,6 +454,26 @@ 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 function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: NodeChildren | (() => NodeChildren) }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class } })
+{
+ const focus = settings?.focused ?? tabs[0]?.id;
+ const titles = tabs.map((e, i) => dom('div', { class: ['px-2 py-1 border-b border-transparent hover:border-accent-blue data-[focus]:border-accent-blue data-[focus]:border-b-[3px] cursor-pointer', settings?.class?.title], attributes: { 'data-focus': e.id === focus }, listeners: { click: function() {
+ if(this.hasAttribute('data-focus'))
+ return;
+
+ titles.forEach(e => e.toggleAttribute('data-focus', false));
+ this.toggleAttribute('data-focus', true);
+ const _content = typeof e.content === 'function' ? e.content() : e.content;
+ //@ts-expect-error
+ content.replaceChildren(..._content);
+ }}}, e.title));
+ const _content = tabs.find(e => e.id === focus)?.content;
+ const content = div(['', settings?.class?.content], typeof _content === 'function' ? _content() : _content);
+ return div(['flex flex-col', settings?.class?.container], [
+ div(['flex flex-row items-center gap-1', settings?.class?.tabbar], titles),
+ content
+ ]);
+}
export interface ToastConfig
{
diff --git a/shared/dom.util.ts b/shared/dom.util.ts
index 7749848..62e914f 100644
--- a/shared/dom.util.ts
+++ b/shared/dom.util.ts
@@ -4,9 +4,9 @@ export type Node = HTMLElement | SVGElement | Text | undefined;
export type NodeChildren = Array;
export type Class = string | Array | Record | undefined;
-type Listener = | ((ev: HTMLElementEventMap[K]) => any) | {
+type Listener = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | {
options?: boolean | AddEventListenerOptions;
- listener: (ev: HTMLElementEventMap[K]) => any;
+ listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any;
} | undefined;
export interface NodeProperties
@@ -42,9 +42,9 @@ export function dom(tag: K, properties?:
{
const key = k as keyof HTMLElementEventMap, value = v as Listener;
if(typeof value === 'function')
- element.addEventListener(key, value);
+ element.addEventListener(key, value.bind(element));
else if(value)
- element.addEventListener(key, value.listener, value.options);
+ element.addEventListener(key, value.listener.bind(element), value.options);
}
}
@@ -56,6 +56,10 @@ export function div(cls?: Class, children?: NodeChildren): HTMLDivElement
{
return dom("div", { class: cls }, children);
}
+export function span(cls?: Class, children?: NodeChildren): HTMLSpanElement
+{
+ return dom("span", { class: cls }, children);
+}
export function svg(tag: K, properties?: NodeProperties, children?: Omit): SVGElementTagNameMap[K]
{
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
diff --git a/shared/feature.util.ts b/shared/feature.util.ts
index a6f7925..ce0b6c0 100644
--- a/shared/feature.util.ts
+++ b/shared/feature.util.ts
@@ -1,7 +1,7 @@
import type { Ability, AspectConfig, CharacterConfig, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureValue, i18nID, Level, MainStat, RaceConfig, Resistance, SpellConfig, TrainingLevel } from "~/types/character";
import { div, dom, icon, text, type NodeChildren } from "#shared/dom.util";
import { MarkdownEditor } from "#shared/editor.util";
-import { fakeA } from "#shared/proses";
+import { preview } 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 { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
@@ -65,7 +65,7 @@ export class HomebrewBuilder
const promise: Promise = this._editor.edit(feature).then(f => {
this._config.features[feature.id] = f;
return f;
- }).catch(() => feature).finally(() => {
+ }).catch((e) => { if(e) console.error(e); return feature; }).finally(() => {
setTimeout(popup.close, 150);
this._editor.container.setAttribute('data-state', 'inactive');
});
@@ -133,7 +133,7 @@ class PeopleEditor extends BuilderTab
const render = (people: string, level: Level, feature: string) => {
let element = 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"], listeners: { click: e => {
this._builder.edit(config.features[feature]!).then(e => {
- element.replaceChildren(markdownUtil(config.features[feature]!.description, undefined, { tags: { a: fakeA } }));
+ element.replaceChildren(markdownUtil(config.features[feature]!.description, undefined, { tags: { a: preview } }));
});
}, contextmenu: (e) => {
e.preventDefault();
@@ -154,7 +154,7 @@ class PeopleEditor extends BuilderTab
}
}) } } }, [ text('Supprimer') ]) : undefined,
], { placement: "right-start", priority: false });
- }}}, [ markdownUtil(config.features[feature]!.description, undefined, { tags: { a: fakeA } }) ]);
+ }}}, [ markdownUtil(config.features[feature]!.description, undefined, { tags: { a: preview } }) ]);
return element;
}
const peopleRender = (people: RaceConfig) => {
@@ -180,7 +180,7 @@ class TrainingEditor extends BuilderTab
const render = (stat: MainStat, level: TrainingLevel, feature: string) => {
let element = 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"], listeners: { click: e => {
this._builder.edit(config.features[feature]!).then(e => {
- element.replaceChildren(markdownUtil(config.features[feature]!.description, undefined, { tags: { a: fakeA } }));
+ element.replaceChildren(markdownUtil(config.features[feature]!.description, undefined, { tags: { a: preview } }));
});
}, contextmenu: (e) => {
e.preventDefault();
@@ -201,7 +201,7 @@ class TrainingEditor extends BuilderTab
}
}) } } }, [ text('Supprimer') ]) : undefined,
], { placement: "right-start", priority: false });
- }}}, [ markdownUtil(config.features[feature]!.description, undefined, { tags: { a: fakeA } }) ]);
+ }}}, [ markdownUtil(config.features[feature]!.description, undefined, { tags: { a: preview } }) ]);
return element;
};
const statRenderBlock = (stat: MainStat) => {
@@ -411,7 +411,7 @@ export class FeatureEditor
private _renderEffect(effect: Partial): HTMLDivElement
{
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
- div('px-4 flex items-center h-full', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: fakeA } }) ]),
+ div('px-4 flex items-center h-full', [ renderMarkdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
content.replaceWith(this._edit(effect));
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
@@ -503,14 +503,15 @@ export class FeatureEditor
break;
case 'choice':
const add = () => {
- const option: Extract["options"][number] = { id: getID(), category: 'value', text: '', operation: 'add', property: '', value: 0 };
- (buffer as Extract).options.push(option);
+ const option: { text: string; effects: (Partial)[]; } = { effects: [{ id: getID() }], text: '' };
+ (buffer as FeatureChoice).options.push(option as FeatureChoice["options"][number]);
list.appendChild(render(option, true));
};
- const render = (option: FeatureEffect & { text: string }, state: boolean): HTMLElement => {
- const { top: _top, bottom: _bottom } = drawByCategory(option);
+ const render = (option: { text: string; effects: (Partial)[]; }, state: boolean): HTMLElement => {
+
+ /* const { top: _top, bottom: _bottom } = drawByCategory(option);
const combo = combobox([...featureChoices].filter(e => (e?.value as FeatureItem)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureItem)?.category !== 'choice') : e.value; return e; }), { defaultValue: match(option), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => {
- option = { id: option.id, ...e } as FeatureEffect & { text: string };
+ option = { id: option.id, ...e } as { text: string; effects: (Partial)[]; };
const element = render(option, true);
_content.replaceWith(element);
_content = element;
@@ -519,7 +520,7 @@ export class FeatureEditor
_content.remove();
(buffer as Extract).options = (buffer as Extract).options.filter(e => e.id !== option.id);
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Supprimer', 'bottom') ]) ], { class: { title: 'border-b border-light-35 dark:border-dark-35', icon: 'w-[34px] h-[34px]', content: 'border-b border-light-35 dark:border-dark-35' }, open: state });
- return _content;
+ return _content; */
}
const list = div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35 gap-2', buffer.options?.map(e => render(e, false)) ?? []);
top = [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as Extract).text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }), tooltip(button(icon('radix-icons:plus'), () => add(), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Ajouter une option', 'bottom') ];
@@ -590,42 +591,42 @@ const featureChoices: Option>[] = [
] },
{ text: 'Compétences', value: [
...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: 'Max de compétence', value: ABILITIES.map((e) => ({ text: `Max > ${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 } },
- { text: 'Modifieur de dextérité', value: { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 } },
- { text: 'Modifieur de constitution', value: { category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 } },
- { text: 'Modifieur d\'intelligence', value: { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 } },
- { text: 'Modifieur de curiosité', value: { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 } },
- { text: 'Modifieur de charisme', value: { category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 } },
- { text: 'Modifieur de psyché', value: { category: 'value', property: 'modifier/psyche', operation: 'add', value: 1 } },
- { text: 'Modifieur au choix', value: { category: 'choice', text: '+1 au modifieur de ', options: [
- { text: 'Modifieur de force', effects: [ { category: 'value', property: 'modifier/strength', operation: 'add', value: 1 } ] },
- { text: 'Modifieur de dextérité', effects: [ { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 } ] },
- { text: 'Modifieur de constitution', effects: [ { category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 } ] },
- { text: 'Modifieur d\'intelligence', effects: [ { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 } ] },
- { text: 'Modifieur de curiosité', effects: [ { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 } ] },
- { text: 'Modifieur de charisme', effects: [ { category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 } ] },
- { text: 'Modifieur de psyché', effects: [ { category: 'value', property: 'modifier/psyche', operation: 'add', value: 1 } ] }
+ { text: 'Mod. de force', value: { category: 'value', property: 'modifier/strength', operation: 'add', value: 1 } },
+ { text: 'Mod. de dextérité', value: { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 } },
+ { text: 'Mod. de constitution', value: { category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 } },
+ { text: 'Mod. d\'intelligence', value: { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 } },
+ { text: 'Mod. de curiosité', value: { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 } },
+ { text: 'Mod. de charisme', value: { category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 } },
+ { text: 'Mod. de psyché', value: { category: 'value', property: 'modifier/psyche', operation: 'add', value: 1 } },
+ { text: 'Mod. au choix', value: { category: 'choice', text: '+1 au modifieur de ', options: [
+ { text: 'Mod. de force', effects: [ { category: 'value', property: 'modifier/strength', operation: 'add', value: 1 } ] },
+ { text: 'Mod. de dextérité', effects: [ { category: 'value', property: 'modifier/dexterity', operation: 'add', value: 1 } ] },
+ { text: 'Mod. de constitution', effects: [ { category: 'value', property: 'modifier/constitution', operation: 'add', value: 1 } ] },
+ { text: 'Mod. d\'intelligence', effects: [ { category: 'value', property: 'modifier/intelligence', operation: 'add', value: 1 } ] },
+ { text: 'Mod. de curiosité', effects: [ { category: 'value', property: 'modifier/curiosity', operation: 'add', value: 1 } ] },
+ { text: 'Mod. de charisme', effects: [ { category: 'value', property: 'modifier/charisma', operation: 'add', value: 1 } ] },
+ { text: 'Mod. de psyché', effects: [ { category: 'value', property: 'modifier/psyche', operation: 'add', value: 1 } ] }
]} as Partial}
] },
{ text: 'Jet de résistance', value: [
- { text: 'Force', value: { category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 } },
- { text: 'Dextérité', value: { category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 } },
- { text: 'Constitution', value: { category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 } },
- { text: 'Intelligence', value: { category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 } },
- { text: 'Curiosité', value: { category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 } },
- { text: 'Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } },
- { text: 'Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } },
+ { text: 'Résistance > Force', value: { category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 } },
+ { text: 'Résistance > Dextérité', value: { category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 } },
+ { text: 'Résistance > Constitution', value: { category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 } },
+ { text: 'Résistance > Intelligence', value: { category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 } },
+ { text: 'Résistance > Curiosité', value: { category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 } },
+ { text: 'Résistance > Charisme', value: { category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 } },
+ { text: 'Résistance > Psyché', value: { category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 } },
{ text: 'Résistance au choix', value: { category: 'choice', text: '+1 au jet de résistance de ', options: [
- { text: 'Force', effects: [{ category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 }] },
- { text: 'Dextérité', effects: [{ category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 }] },
- { text: 'Constitution', effects: [{ category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 }] },
- { text: 'Intelligence', effects: [{ category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 }] },
- { text: 'Curiosité', effects: [{ category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 }] },
- { text: 'Charisme', effects: [{ category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 }] },
- { text: 'Psyché', effects: [{ category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 }] }
+ { text: 'Résistance > Force', effects: [{ category: 'value', property: 'bonus/defense/strength', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Dextérité', effects: [{ category: 'value', property: 'bonus/defense/dexterity', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Constitution', effects: [{ category: 'value', property: 'bonus/defense/constitution', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Intelligence', effects: [{ category: 'value', property: 'bonus/defense/intelligence', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Curiosité', effects: [{ category: 'value', property: 'bonus/defense/curiosity', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Charisme', effects: [{ category: 'value', property: 'bonus/defense/charisma', operation: 'add', value: 1 }] },
+ { text: 'Résistance > Psyché', effects: [{ category: 'value', property: 'bonus/defense/psyche', operation: 'add', value: 1 }] }
]} as Partial}
] },
{ text: 'Bonus', value: RESISTANCES.map(e => ({ text: resistanceTexts[e as Resistance], value: { category: 'value', property: `resistance/${e}`, operation: 'add', value: 1 } })) },
diff --git a/shared/proses.ts b/shared/proses.ts
index 39471d8..7e8914e 100644
--- a/shared/proses.ts
+++ b/shared/proses.ts
@@ -20,8 +20,8 @@ export const a: Prose = {
const link = overview ? { name: 'explore-path', params: { path: overview.path }, hash: hash } : href, nav = router.resolve(link);
- const el = dom('a', { class: 'text-accent-blue inline-flex items-center', attributes: { href: nav.href }, listeners: {
- 'click': (e) => {
+ const el = dom('a', { class: ['text-accent-blue inline-flex items-center', properties?.class], attributes: { href: nav.href }, listeners: {
+ 'click': (e) => {
e.preventDefault();
router.push(link);
}
@@ -45,7 +45,7 @@ export const a: Prose = {
return [async('large', Content.getContent(overview.id).then((_content) => {
if(_content?.type === 'markdown')
{
- return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto py-4 px-6' });
+ return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' });
}
if(_content?.type === 'canvas')
{
@@ -63,7 +63,7 @@ export const a: Prose = {
return el;
}
}
-export const fakeA: Prose = {
+export const preview: Prose = {
custom(properties, children) {
const href = properties.href as string;
const { hash, pathname } = parseURL(href);
@@ -71,11 +71,9 @@ export const fakeA: Prose = {
const overview = Content.getFromPath(pathname === '' && hash.length > 0 ? unifySlug(router.currentRoute.value.params.path ?? '') : pathname);
- const el = dom('span', { class: 'cursor-pointer text-accent-blue inline-flex items-center' }, [
- dom('span', {}, [
- ...(children ?? []),
- overview && overview.type !== 'markdown' ? icon(iconByType[overview.type], { class: 'w-4 h-4 inline-block', inline: true }) : undefined
- ])
+ const el = dom('span', { class: ['cursor-pointer text-accent-blue inline-flex items-center', properties?.class] }, [
+ ...(children ?? []),
+ overview && overview.type !== 'markdown' ? icon(iconByType[overview.type], { class: 'w-4 h-4 inline-block', inline: true }) : undefined
]);
@@ -93,7 +91,7 @@ export const fakeA: Prose = {
return [async('large', Content.getContent(overview.id).then((_content) => {
if(_content?.type === 'markdown')
{
- return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] w-full overflow-auto py-4 px-6' });
+ return render((_content as LocalContent<'markdown'>).content ?? '', hash.length > 0 ? hash.substring(1) : undefined, { class: 'w-full max-h-full overflow-auto py-4 px-6' });
}
if(_content?.type === 'canvas')
{
diff --git a/types/character.d.ts b/types/character.d.ts
index 86c49a6..e983412 100644
--- a/types/character.d.ts
+++ b/types/character.d.ts
@@ -1,4 +1,5 @@
import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES } from "#shared/character.util";
+import type { Localized } from "#shared/general";
export type MainStat = typeof MAIN_STATS[number];
export type Ability = typeof ABILITIES[number];
@@ -13,14 +14,22 @@ export type Resistance = typeof RESISTANCES[number];
export type FeatureID = string;
export type i18nID = string;
+export type RecursiveKeyOf = {
+ [TKey in keyof TObj & (string | number)]:
+ TObj[TKey] extends any[] ? `${TKey}` :
+ TObj[TKey] extends object
+ ? `${TKey}` | `${TKey}/${RecursiveKeyOf}`
+ : `${TKey}`;
+}[keyof TObj & (string | number)];
+
export type Character = {
id: number;
- name: string;
- people?: string;
+ name: string; //Free text
+ people?: string; //People ID
level: number;
aspect?: number;
- notes?: string | null;
+ notes?: { public?: string, private?: string }; //Free text
training: Record>>;
leveling: Partial>;
@@ -38,11 +47,12 @@ export type CharacterVariables = {
exhaustion: number;
sickness: Array<{ id: string, state: number | true }>;
+ poisons: Array<{ id: string, state: number | true }>;
spells: string[]; //Spell ID
items: ItemState[];
};
type ItemState = {
- id: string,
+ id: string;
amount: number;
enchantments?: [];
charges?: number;
@@ -55,11 +65,16 @@ export type CharacterConfig = {
spells: SpellConfig[];
aspects: AspectConfig[];
features: Record;
- enchantments: Record; //TODO
+ enchantments: Record; //TODO
items: Record;
- lists: Record;
+ lists: Record }>;
texts: Record;
};
+export type EnchantementConfig = {
+ name: string; //TODO -> TextID
+ effect: Array;
+ power: number;
+}
export type ItemConfig = CommonItemConfig & (ArmorConfig | WeaponConfig | WondrousConfig | MundaneConfig);
type CommonItemConfig = {
id: string;
@@ -87,7 +102,7 @@ type WondrousConfig = {
category: 'wondrous';
name: string; //TODO -> TextID
description: i18nID;
- effect: FeatureEffect[];
+ effect: FeatureItem[];
};
type MundaneConfig = {
category: 'mundane';
@@ -96,25 +111,25 @@ type MundaneConfig = {
};
export type SpellConfig = {
id: string;
- name: string;
+ name: string; //TODO -> TextID
rank: 1 | 2 | 3 | 4;
type: SpellType;
cost: number;
speed: "action" | "reaction" | number;
elements: Array;
- effect: string;
+ effect: string; //TODO -> TextID
concentration: boolean;
tags?: string[];
};
export type RaceConfig = {
id: string;
- name: string;
- description: string;
+ name: string; //TODO -> TextID
+ description: string; //TODO -> TextID
options: Record;
};
export type AspectConfig = {
name: string;
- description: string;
+ description: string; //TODO -> TextID
stat: MainStat | 'special';
alignment: Alignment;
magic: boolean;
@@ -122,33 +137,42 @@ export type AspectConfig = {
physic: { min: number, max: number };
mental: { min: number, max: number };
personality: { min: number, max: number };
- options: FeatureEffect[];
+ options: FeatureItem[];
};
-export type FeatureEffect = {
+export type FeatureValue = {
id: FeatureID;
category: "value";
operation: "add" | "set" | "min";
- property: string;
+ property: RecursiveKeyOf | 'spec' | 'ability' | 'training';
value: number | `modifier/${MainStat}` | false;
-} | {
+}
+export type FeatureEquipment = {
+ id: FeatureID;
+ category: "value";
+ operation: "add" | "set" | "min";
+ property: 'weapon/damage' | 'armor/health' | 'armor/absorb/flat' | 'armor/absorb/percent';
+ value: number | `modifier/${MainStat}` | false;
+}
+export type FeatureList = {
id: FeatureID;
category: "list";
list: "spells" | "sickness" | "action" | "reaction" | "freeaction" | "passive";
action: "add" | "remove";
- item: string;
+ item: string | i18nID;
extra?: any;
};
-export type FeatureItem = FeatureEffect | {
+export type FeatureChoice = {
id: FeatureID;
category: "choice";
- text: string;
+ text: string; //TODO -> TextID
settings?: { //If undefined, amount is 1 by default
amount: number;
exclusive: boolean; //Disallow to pick the same option twice
};
- options: Array;
+ options: Array<{ text: string, effects: Array }>; //TODO -> TextID
};
+export type FeatureItem = FeatureValue | FeatureList | FeatureChoice;
export type Feature = {
id: FeatureID;
description: i18nID;
@@ -199,13 +223,16 @@ export type CompiledCharacter = {
magicinstinct: number;
};
- bonus: Record; //Any special bonus goes here
+ bonus: {
+ defense: Partial>;
+ abilities: Partial>;
+ }; //Any special bonus goes here
resistance: Record;
modifier: Record;
abilities: Partial>;
level: number;
- lists: { [K in Extract["list"]]?: string[] };
+ lists: { [K in FeatureList['list']]?: string[] }; //string => ListItem ID
- notes: string;
+ notes: { public: string, private: string };
};
\ No newline at end of file