Try to add character editor inside the character sheet

This commit is contained in:
Clément Pons
2026-02-13 17:34:35 +01:00
parent 898d95793a
commit 9face0ac3b
8 changed files with 14833 additions and 320 deletions

View File

@@ -290,8 +290,8 @@ export class CharacterCompiler
{
private _dirty: boolean = true;
protected _character!: Character;
protected _result!: CompiledCharacter;
protected _character: Character | undefined;
protected _result: CompiledCharacter | undefined;
protected _buffer: Record<string, PropertySum> = {
'modifier/strength': { value: 0, _dirty: false, min: -Infinity, list: [] },
'modifier/dexterity': { value: 0, _dirty: false, min: -Infinity, list: [] },
@@ -304,9 +304,9 @@ export class CharacterCompiler
protected _trees: Record<string, TreeState> = {};
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
constructor(character: Character)
constructor(character?: Character)
{
this.character = character;
if(character) this.character = character;
}
set character(value: Character)
@@ -333,7 +333,7 @@ export class CharacterCompiler
Object.entries(value.abilities).forEach(e => this._buffer[`abilities/${e[0]}`] = { value: 0, _dirty: true, min: -Infinity, list: [{ id: '', operation: 'add', value: e[1] }] });
reactivity(() => value.variables.transformed, (v) => {
if(value.aspect && config.aspects[value.aspect])
if(this._character && this._result && value.aspect && config.aspects[value.aspect])
{
const aspect = config.aspects[value.aspect]!;
if(v)
@@ -349,19 +349,19 @@ export class CharacterCompiler
idx !== -1 && this._buffer[`modifier/${aspect.stat}`]!.list.splice(idx, 1);
this._buffer[`modifier/${aspect.stat}`]!._dirty = true;
}
this.compile([`modifier/${aspect.stat}`]);
this.compile([`modifier/${aspect.stat}`], this._buffer, this._result);
this.saveVariables();
}
})
}
}
get character(): Character
get character(): Character | undefined
{
return this._character;
}
get compiled(): CompiledCharacter
get compiled(): CompiledCharacter | undefined
{
if(this._dirty)
if(this._character && this._result && this._dirty)
{
Object.keys(this._trees).forEach(tree => {
if(!this._trees[tree]!._dirty || !config.trees[tree])
@@ -376,7 +376,7 @@ export class CharacterCompiler
this._trees[tree]!.validated = validated;
});
this.compile(Object.keys(this._buffer));
this.compile(Object.keys(this._buffer), this._buffer, this._result);
this._dirty = false;
}
@@ -384,7 +384,7 @@ export class CharacterCompiler
}
get values(): Record<string, number>
{
if(this._dirty)
if(this._character && this._result && this._dirty)
{
Object.keys(this._trees).forEach(tree => {
if(!this._trees[tree]!._dirty || !config.trees[tree])
@@ -399,7 +399,7 @@ export class CharacterCompiler
this._trees[tree]!.validated = validated;
});
this.compile(Object.keys(this._buffer));
this.compile(Object.keys(this._buffer), this._buffer, this._result);
this._dirty = false;
}
@@ -410,16 +410,16 @@ export class CharacterCompiler
}
get armor()
{
const armor = this._character.variables.items.find(e => e.equipped && config.items[e.id]?.category === 'armor');
return armor ? { max: (config.items[armor.id] as ArmorConfig).health, current: (config.items[armor.id] as ArmorConfig).health - ((armor.state as ArmorState)?.loss ?? 0), flat: (config.items[armor.id] as ArmorConfig).absorb.static + ((armor.state as ArmorState)?.absorb.flat ?? 0), percent: (config.items[armor.id] as ArmorConfig).absorb.percent + ((armor.state as ArmorState)?.absorb.percent ?? 0) } : undefined;
const armor = this._character?.variables.items.find(e => e.equipped && config.items[e.id]?.category === 'armor');
return armor ? { max: (config.items[armor.id] as ArmorConfig).health, current: (config.items[armor.id] as ArmorConfig).health - ((armor.state as ArmorState)?.loss ?? 0), flat: (config.items[armor.id] as ArmorConfig).absorb.static + ((armor.state as ArmorState)?.absorb.flat ?? 0), percent: (config.items[armor.id] as ArmorConfig).absorb.percent + ((armor.state as ArmorState)?.absorb.percent ?? 0) } : { max: 0, current: 0, flat: 0, percent: 0, };
}
get weight()
{
return this._character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
return this._character?.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0) ?? 0;
}
get power()
{
return this._character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0);
return this._character?.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0) ?? 0;
}
enchant(item: ItemState)
@@ -447,9 +447,11 @@ export class CharacterCompiler
}
saveVariables()
{
if(!this._character) return;
clearTimeout(this._variableDebounce);
this._variableDebounce = setTimeout(() => {
useRequestFetch()(`/api/character/${this.character.id}/variables`, {
if(!this._character) return;
useRequestFetch()(`/api/character/${this._character.id}/variables`, {
method: 'POST',
body: raw(this._character.variables),
}).then(() => {}).catch(() => {
@@ -459,7 +461,8 @@ export class CharacterCompiler
}
saveNotes()
{
return useRequestFetch()(`/api/character/${this.character.id}/notes`, {
if(!this._character) return Promise.resolve();
return useRequestFetch()(`/api/character/${this._character.id}/notes`, {
method: 'POST',
body: this._character.notes,
}).then(() => {}).catch(() => {
@@ -482,8 +485,9 @@ export class CharacterCompiler
}
protected apply(feature?: FeatureItem)
{
if(!feature)
return;
if(!this._character) return;
if(!this._result) return;
if(!feature) return;
this._dirty = true;
switch(feature.category)
@@ -529,8 +533,9 @@ export class CharacterCompiler
}
protected undo(feature?: FeatureItem)
{
if(!feature)
return;
if(!this._character) return;
if(!this._result) return;
if(!feature) return;
this._dirty = true;
switch(feature.category)
@@ -574,7 +579,7 @@ export class CharacterCompiler
return;
}
}
protected compile(queue: string[], _buffer: Record<string, PropertySum> = this._buffer, _result: Record<string, any> = this._result)
protected compile(queue: string[], _buffer: Record<string, PropertySum> = this._buffer, _result: Record<string, any>)
{
for(let i = 0; i < queue.length; i++)
{
@@ -724,7 +729,7 @@ function setProperty<T>(root: any, path: string, value: T | ((old: T) => T), for
if(force || object.hasOwnProperty(arr.slice(-1)[0]!))
object[arr.slice(-1)[0]!] = typeof value === 'function' ? (value as (old: T) => T)(object[arr.slice(-1)[0]!]) : value;
}
export class CharacterBuilder extends CharacterCompiler
/* export class CharacterBuilder extends CharacterCompiler
{
private _container: HTMLElement;
private _content?: HTMLElement;
@@ -1421,7 +1426,7 @@ class AspectPicker extends BuilderTab
return true;
}
}
} */
export const masteryTexts: Record<CompiledCharacter['mastery'][number], { text: string, href: string }> = {
"armor/light": { text: "Armure légère", href: "regles/annexes/equipement#Les armures légères" },
@@ -1522,105 +1527,112 @@ export const stateFactory = (item: ItemConfig) => {
export class CharacterSheet
{
private user: ComputedRef<User | null>;
private character?: CharacterCompiler;
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-center');
private compiler: CharacterCompiler = reactive(new CharacterCompiler());
container: HTMLElement = div('flex flex-1 h-full w-full items-start justify-between');
editingDOM: HTMLElement = div();
readingDOM: HTMLElement = div();
private state: { exists: boolean, edit: boolean };
private tabs?: HTMLElement;
private tab: string = localStorage.getItem('character-tab') ?? 'actions';
ws?: Socket;
constructor(id: string, user: ComputedRef<User | null>)
constructor(id: string, user: ComputedRef<User | null>, editing: boolean = false)
{
this.user = user;
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(reactive(character));
this.state = reactive({ exists: id !== 'new', edit: id === 'new' || editing });
if(character.campaign)
this.render();
this.edit();
if(this.state.exists)
{
useRequestFetch()(`/api/character/${id}`).then(character => {
if(character)
{
/* this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
this.compiler.character = reactive(character);
this.ws.handleMessage('SYNC', () => {
useRequestFetch()(`/api/character/${id}`).then(character => {
if(character)
if(character.campaign)
{
/* this.ws = new Socket(`/ws/campaign/${character.campaign}`, true);
this.ws.handleMessage('SYNC', () => {
useRequestFetch()(`/api/character/${id}`).then(character => {
if(character)
{
this.character!.character = reactive(character);
this.character!.values;
'update' in this.container! && this.container!.update!(true);
}
});
})
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
const prop = this.character!.character.variables[variable.key];
if(variable.action === 'set')
this.character!.character.variables[variable.key] = variable.value;
else if(Array.isArray(prop))
{
this.character!.character = reactive(character);
this.character!.values;
'update' in this.container! && this.container!.update!(true);
if(variable.action === 'add')
prop.push(variable.value);
else if(variable.action === 'remove')
{
const idx = prop.findIndex(e => deepEquals(e, variable.value));
if(idx !== -1) prop.splice(idx, 1);
}
}
});
})
this.ws.handleMessage<{ action: 'set' | 'add' | 'remove', key: keyof CharacterVariables, value: any }>('VARIABLE', (variable) => {
const prop = this.character!.character.variables[variable.key];
if(variable.action === 'set')
this.character!.character.variables[variable.key] = variable.value;
else if(Array.isArray(prop))
{
if(variable.action === 'add')
prop.push(variable.value);
else if(variable.action === 'remove')
{
const idx = prop.findIndex(e => deepEquals(e, variable.value));
if(idx !== -1) prop.splice(idx, 1);
}
}
}) */
}) */
}
document.title = `d[any] - ${character.name}`;
load.remove();
this.container.replaceChildren(this.editingDOM, this.readingDOM);
}
else
throw new Error();
}).catch((e) => {
console.error(e);
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
span('text-2xl font-bold tracking-wider', 'Personnage introuvable'),
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
div('flex flex-row gap-4 justify-center items-center', [
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
])
]))
});
}
else
this.state.edit = true;
document.title = `d[any] - ${character.name}`;
load.remove();
this.render();
}
else
throw new Error();
}).catch((e) => {
console.error(e);
this.container.replaceChildren(div('flex flex-col items-center justify-center flex-1 h-full gap-4', [
span('text-2xl font-bold tracking-wider', 'Personnage introuvable'),
span(undefined, 'Ce personnage n\'existe pas ou est privé.'),
div('flex flex-row gap-4 justify-center items-center', [
button(text('Personnages publics'), () => useRouter().push({ name: 'character-list' }), 'px-2 py-1'),
button(text('Créer un personange'), () => useRouter().push({ name: 'character-id-edit', params: { id: 'new' } }), 'px-2 py-1')
])
]))
});
reactivity(this.state, (state) => {
state.edit ? this.container.replaceChildren(this.editingDOM, this.readingDOM) : this.container.replaceChildren(this.readingDOM);
})
}
render()
{
if(!this.character)
return;
const character = this.character.compiled;
const publicNotes = new MarkdownEditor();
const privateNotes = new MarkdownEditor();
const healthPanel = this.healthPanel(character);
const healthPanel = this.healthPanel(this.compiler.compiled);
const loadableIcon = icon('radix-icons:paper-plane', { width: 16, height: 16 });
const saveLoading = loading('small');
const saveNotes = () => { loadableIcon.replaceWith(saveLoading); this.character?.saveNotes().finally(() => { saveLoading.replaceWith(loadableIcon) }); }
publicNotes.onChange = (v) => this.character!.character.notes!.public = v;
privateNotes.onChange = (v) => this.character!.character.notes!.private = v;
publicNotes.content = this.character!.character.notes!.public!;
privateNotes.content = this.character!.character.notes!.private!;
const saveNotes = () => { loadableIcon.replaceWith(saveLoading); this.compiler.saveNotes().finally(() => { saveLoading.replaceWith(loadableIcon) }); }
this.tabs = tabgroup([
{ id: 'actions', title: [ text('Actions') ], content: this.actionsTab(character) },
{ id: 'actions', title: [ text('Actions') ], content: () => this.actionsTab(this.compiler.compiled) },
{ id: 'abilities', title: [ text('Aptitudes') ], content: this.abilitiesTab(character) },
{ id: 'abilities', title: [ text('Aptitudes') ], content: () => this.abilitiesTab(this.compiler.compiled) },
{ id: 'spells', title: [ text('Sorts') ], content: this.spellTab(character) },
{ id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(this.compiler.compiled) },
{ id: 'inventory', title: [ text('Inventaire') ], content: this.itemsTab(character) },
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab(this.compiler.compiled) },
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': character.variables.transformed }), 'Aspect') ], content: () => this.aspectTab(character) },
{ id: 'aspect', title: [ span(() => ({ 'relative before:absolute before:top-0 before:-right-2 before:w-2 before:h-2 before:rounded-full before:bg-accent-blue': this.compiler.compiled?.variables?.transformed ?? false }), 'Aspect') ], content: () => this.aspectTab(this.compiler.compiled) },
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab(character) },
{ id: 'effects', title: [ text('Afflictions') ], content: () => this.effectsTab(this.compiler.compiled) },
{ id: 'notes', title: [ text('Notes') ], content: () => [
div('flex flex-col h-full divide-y divide-light-30 dark:divide-dark-30', [
@@ -1634,9 +1646,9 @@ export class CharacterSheet
}),
])
] },
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('character-tab', v); } });
], { focused: this.tab, class: { container: 'flex-1 gap-4 px-4 max-w-[960px] h-full', content: 'overflow-auto h-full' }, switch: v => { this.tab = v; localStorage.setItem('this.compiler.compiled-tab', v); } });
this.container.replaceChildren(div('flex flex-col justify-start gap-1 h-full w-full', [
this.readingDOM = div('flex flex-col justify-start gap-1 h-full w-full min-w-half', [
div("flex flex-row gap-4 justify-between", [
div(),
@@ -1649,107 +1661,107 @@ export class CharacterSheet
]),
div("flex flex-col", [
dom("span", { class: "text-xl font-bold", text: character.name }),
dom("span", { class: "text-sm", text: `De ${character.username}` })
span("text-xl font-bold", () => this.compiler.compiled?.name ?? "Inconnu"),
span("text-sm", () => this.compiler.compiled ? `De ${this.compiler.compiled.username}` : '')
]),
div("flex flex-col", [
dom("span", { class: "font-bold", text: `Niveau ${character.level}` }),
dom("span", { text: config.peoples[character.race]?.name ?? 'Peuple inconnu' })
span("font-bold", () =>`Niveau ${this.compiler.compiled?.level ?? 0}`),
span('', () => this.compiler.compiled && this.compiler.compiled.race ? config.peoples[this.compiler.compiled.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" }, [
div("flex flex-row items-center gap-2 text-3xl font-light", [
text("PV: "),
dom("span", {
() => this.compiler.compiled ? 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: () => `${this.compiler.compiled.health - this.compiler.compiled.variables.health}`,
listeners: { click: healthPanel.show },
}),
text('/'),
text(() => character.health),
}) : undefined,
() => this.compiler.compiled ? text('/') : text('-'),
() => this.compiler.compiled ? text(() => this.compiler.compiled.health) : undefined,
]),
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
div("flex flex-row items-center gap-2 text-3xl font-light", [
text("Mana: "),
dom("span", {
() => this.compiler.compiled ? 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: () => `${this.compiler.compiled.mana - this.compiler.compiled.variables.mana}`,
listeners: { click: healthPanel.show },
}),
text('/'),
text(() => character.mana),
}) : undefined,
() => this.compiler.compiled ? text('/') : text('-'),
() => this.compiler.compiled ? text(() => this.compiler.compiled.mana) : undefined,
]),
]),
]),
div("self-center", [
this.user.value && this.user.value.id === character.owner ?
button(icon("radix-icons:pencil-2"), () => useRouter().push({ name: 'character-id-edit', params: { id: this.character?.character.id } }), "p-1")
this.user.value && this.compiler.compiled && this.user.value.id === this.compiler.compiled.owner ?
() => this.state.edit ? button(icon("ph:floppy-disk"), () => this.saveEdits(), "p-1") : button(icon("radix-icons:pencil-2"), () => this.state.edit = true, "p-1")
: 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-row justify-center gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
div("flex 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-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'strength' }] }, [ text(() => `+${character.modifier.strength}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Force" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'strength' }], () => `+${this.compiler.compiled.modifier.strength}`),
span("text-sm ", "Force")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'dexterity' }] }, [ text(() => `+${character.modifier.dexterity}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Dextérité" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'dexterity' }], () => `+${this.compiler.compiled.modifier.dexterity}`),
span("text-sm ", "Dextérité")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'constitution' }] }, [ text(() => `+${character.modifier.constitution}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Constitution" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'constitution' }], () => `+${this.compiler.compiled.modifier.constitution}`),
span("text-sm ", "Constitution")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'intelligence' }] }, [ text(() => `+${character.modifier.intelligence}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Intelligence" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'intelligence' }], () => `+${this.compiler.compiled.modifier.intelligence}`),
span("text-sm ", "Intelligence")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'curiosity' }] }, [ text(() => `+${character.modifier.curiosity}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Curiosité" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'curiosity' }], () => `+${this.compiler.compiled.modifier.curiosity}`),
span("text-sm ", "Curiosité")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'charisma' }] }, [ text(() => `+${character.modifier.charisma}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Charisme" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'charisma' }], () => `+${this.compiler.compiled.modifier.charisma}`),
span("text-sm ", "Charisme")
]),
div("flex flex-col items-center px-2", [
dom("span", { class: () => ["2xl:text-2xl text-xl font-bold", { 'text-accent-blue': character.variables.transformed && config.aspects[character.aspect.id]?.stat === 'psyche' }] }, [ text(() => `+${character.modifier.psyche}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Psyché" })
() => !this.compiler.compiled ? span('text-xl font-bold', '-') : span(() => ["text-xl font-bold", { 'text-accent-blue': this.compiler.compiled.variables.transformed && config.aspects[this.compiler.compiled.aspect.id]?.stat === 'psyche' }], () => `+${this.compiler.compiled.modifier.psyche}`),
span("text-sm ", "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 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" })
span("text-xl font-bold", () => !this.compiler.compiled ? '-' : `+${this.compiler.compiled.initiative}`),
span("text-sm ", "Initiative")
]),
div("flex flex-col px-2 items-center", [
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(() => character.speed === false ? "N/A" : `${character.speed}`) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Course" })
span("text-xl font-bold", () => !this.compiler.compiled ? '-' : this.compiler.compiled.speed === false ? "N/A" : `${this.compiler.compiled.speed}`),
span("text-sm ", "Course")
])
]),
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 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.passiveparry + character.defense.passivedodge, 0, character.defense.hardcap)) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Passive" })
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.passiveparry + this.compiler.compiled.defense.passivedodge, 0, this.compiler.compiled.defense.hardcap)),
span("text-sm ", "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.activeparry + character.defense.passivedodge, 0, character.defense.hardcap)) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Blocage" })
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.activeparry + this.compiler.compiled.defense.passivedodge, 0, this.compiler.compiled.defense.hardcap)),
span("text-sm ", "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.passiveparry + character.defense.activedodge, 0, character.defense.hardcap)) ]),
dom("span", { class: "text-sm 2xl:text-base", text: "Esquive" })
span(" text-xl font-bold", () => !this.compiler.compiled ? '-' : clamp(this.compiler.compiled.defense.static + this.compiler.compiled.defense.passiveparry + this.compiler.compiled.defense.activedodge, 0, this.compiler.compiled.defense.hardcap)),
span("text-sm ", "Esquive")
])
]),
]),
@@ -1763,10 +1775,10 @@ export class CharacterSheet
]),
div("grid grid-cols-2 gap-2",
Object.keys(character.abilities).map((ability) =>
ABILITIES.map((ability) =>
div("flex flex-row px-1 justify-between items-center", [
proses('a', preview, [ span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate cursor-help decoration-dotted underline", abilityTexts[ability as Ability] || ability) ], { href: `regles/l'entrainement/competences#${abilityTexts[ability as Ability]}`, label: abilityTexts[ability as Ability], navigate: false }),
span("font-bold text-base text-light-100 dark:text-dark-100", text(() => `+${character.abilities[ability as Ability] ?? 0}`)),
span("font-bold text-base text-light-100 dark:text-dark-100", () => !this.compiler.compiled ? '-' : `+${this.compiler.compiled.abilities[ability as Ability] ?? 0}`),
])
)
),
@@ -1776,12 +1788,12 @@ export class CharacterSheet
div("flex flex-1 border-t border-dashed border-light-50 dark:border-dark-50")
]),
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: character.mastery, render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", { list: () => this.compiler.compiled?.mastery ?? [], render: (e, _c) => proses('a', preview, [ text(masteryTexts[e].text) ], { href: masteryTexts[e].href, label: masteryTexts[e].text, class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline', }) }),
div("grid grid-cols-2 gap-x-3 gap-y-1 text-sm", [
() => character.spellranks.precision > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.precision)) ]) : undefined,
() => character.spellranks.knowledge > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.knowledge)) ]) : undefined,
() => character.spellranks.instinct > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.instinct)) ]) : undefined,
() => character.spellranks.arts > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Oeuvres') ], { href: 'regles/annexes/œuvres', label: 'Oeuvres', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', text(() => character.spellranks.arts)) ]) : undefined,
() => (this.compiler.compiled?.spellranks?.precision ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Précision') ], { href: 'regles/la-magie/magie#Les sorts de précision', label: 'Précision', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.precision ?? 0) ]) : undefined,
() => (this.compiler.compiled?.spellranks?.knowledge ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Savoir') ], { href: 'regles/la-magie/magie#Les sorts de savoir', label: 'Savoir', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.knowledge ?? 0) ]) : undefined,
() => (this.compiler.compiled?.spellranks?.instinct ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Instinct') ], { href: 'regles/la-magie/magie#Les sorts instinctif', label: 'Instinct', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.instinct ?? 0) ]) : undefined,
() => (this.compiler.compiled?.spellranks?.arts ?? 0) > 0 ? div('flex flex-row items-center gap-2', [ proses('a', preview, [ text('Oeuvres') ], { href: 'regles/annexes/œuvres', label: 'Oeuvres', class: 'text-sm text-light-70 dark:text-dark-70 cursor-help decoration-dotted underline' }), span('font-bold', () => this.compiler.compiled?.spellranks?.arts ?? 0) ]) : undefined,
])
])
]),
@@ -1790,9 +1802,91 @@ export class CharacterSheet
this.tabs,
])
]));
]);
reactivity(this.compiler.character, (c) => {
if(c)
{
c.notes ??= { public: '', private: '' };
publicNotes.onChange = (v) => c.notes!.public = v;
privateNotes.onChange = (v) => c.notes!.private = v;
publicNotes.content = c.notes!.public!;
privateNotes.content = c.notes!.private!;
}
});
}
healthPanel(character: CompiledCharacter)
edit()
{
const editingData = reactive({
1: ["Peuple", "Entrainement", "Compétences", "Aspect", "Spécialisations"],
2: ["Entrainement"],
3: ["Entrainement"],
4: ["Entrainement"],
5: ["Entrainement"],
6: ["Entrainement"],
7: ["Entrainement"],
8: ["Entrainement"],
9: ["Entrainement"],
10: ["Entrainement"],
11: ["Entrainement"],
12: ["Entrainement"],
13: ["Entrainement"],
14: ["Entrainement"],
15: ["Entrainement"],
16: ["Entrainement"],
17: ["Entrainement"],
18: ["Entrainement"],
19: ["Entrainement"],
20: ["Entrainement"],
})
this.editingDOM = div('flex flex-1 max-w-full flex-col align-center relative', [
div('h-full w-0 border-l-2 border-light-35 dark:border-dark-35 absolute z-0 left-[89px]'),
...Array(20).fill(0).map((_, i) => div(() => ['flex flex-row gap-4', { 'opacity-30': (this.compiler.character?.level ?? 0) < (i + 1) }], [
div('flex flex-row gap-2 justify-end items-start w-24 py-4', [
div('flex flex-row items-center gap-2', [ span('italic text-xs tracking-tight text-end', `Niveau ${i + 1}`), span('w-3 h-3 rounded-full items-center justify-center bg-light-50 dark:bg-dark-50 z-10') ]),
]),
div('flex flex-col px-2 items-center justify-center divide-y divide-light-35 dark:divide-dark-35', { list: [], render: (e, _c) => _c ?? span('flex px-2 w-full py-2 items-center justify-center cursor-pointer hover:bg-light-20 dark:hover:bg-dark-20', e) }),
]))
]);
}
async saveEdits()
{
if(!this.compiler.character)
return;
if(!this.state.exists)
{
const result = await useRequestFetch()(`/api/character`, {
method: 'post',
body: this.compiler.character,
onResponseError: (e) => {
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 });
this.compiler.character!.id = -1;
}
});
if(result !== undefined) this.compiler.character.id = this.compiler.compiled.id = result as number;
Toaster.add({ content: 'Personnage créé', type: 'success', duration: 25000, timer: true });
useRouter().push({ name: 'character-id', params: { id: this.compiler.compiled.id } });
this.state.edit = false;
}
else
{
await useRequestFetch()(`/api/character/${this.compiler!.character.id}`, {
method: 'post',
body: this.compiler!.character,
onResponseError: (e) => {
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 });
}
});
Toaster.add({ content: 'Personnage enregistré', type: 'success', duration: 25000, timer: true });
this.state.edit = false;
}
}
healthPanel(character: CompiledCharacter | undefined)
{
const inputs = reactive({
health: {
@@ -1808,10 +1902,10 @@ export class CharacterSheet
},
mana: 0,
});
const armor = this.character?.armor;
const armor = this.compiler.armor;
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-[480px] 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', [
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.health - character.variables.health)), text('/'), text(() => character.health) ]) ]),
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Edititon de vie'), () => !character ? span('text-xl font-bold', '-') : div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.health - character.variables.health)), text('/'), text(() => character.health) ]) ]),
tooltip(button(icon("radix-icons:cross-1", { width: 24, height: 24 }), () => {
setTimeout(blocker.close, 150);
container.setAttribute('data-state', 'inactive');
@@ -1823,39 +1917,39 @@ export class CharacterSheet
])))
], [
div('flex flex-row justify-between items-center', [
span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
() => !character ? undefined : span('text-lg', 'Total'), div('flex flew-row gap-4 justify-end', [
() => armor ? tooltip(button(div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${armor.current}/${armor.max} (${[armor.flat > 0 ? '-' + armor.flat : undefined, armor.percent > 0 ? armor.percent + '%' : undefined].filter(e => !!e).join('/')})`) ]), () => {
//TODO
}, 'px-2 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left') : undefined,
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
character.variables.health += inputs.health.sum;
character!.variables.health += inputs.health.sum;
inputs.health.sum = 0;
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
numberpicker({ defaultValue: () => inputs.health.sum, input: v => { inputs.health.sum = v }, min: 0, disabled: () => inputs.health.open, class: 'h-8 !m-0' }),
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
character.variables.health = Math.max(character.variables.health - inputs.health.sum, 0);
character!.variables.health = Math.max(character!.variables.health - inputs.health.sum, 0);
inputs.health.sum = 0;
DAMAGE_TYPES.forEach(e => inputs.health[e] = 0);
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
])
])
], { class: { container: 'gap-2', title: 'ps-2' }, open: false, onFold: v => { inputs.health.open = v; if(v) { inputs.health.sum = 0; }} }),
div('flex flex-row justify-between items-center', [
() => !character ? undefined : div('flex flex-row justify-between items-center', [
div('flex flex-row gap-8 items-center', [ span('text-xl font-bold', 'Mana'), div('flex flex-row items-center gap-1', [ span('text-xl font-bold', () => (character.mana - character.variables.mana)), text('/'), text(() => character.mana) ]) ]),
div('flex flex-row gap-4 justify-end', [
tooltip(button(icon('radix-icons:minus', { width: 16, height: 16 }), () => {
character.variables.mana += inputs.mana;
inputs.mana = 0;
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'w-8 h-8 border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red focus:border-light-red dark:focus:border-dark-red focus:shadow-light-red dark:focus:shadow-dark-red'), 'Dégats', 'left'),
numberpicker({ defaultValue: () => inputs.mana, input: v => { inputs.mana = v }, min: 0, class: 'h-8 !m-0' }),
tooltip(button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
character.variables.mana = Math.max(character.variables.mana - inputs.mana, 0);
inputs.mana = 0;
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'w-8 h-8 border-light-green dark:border-dark-green hover:border-light-green dark:hover:border-dark-green focus:border-light-green dark:focus:border-dark-green focus:shadow-light-green dark:focus:shadow-dark-green'), 'Soin', 'left'),
])
]),
@@ -1870,8 +1964,10 @@ export class CharacterSheet
container.setAttribute('data-state', 'inactive');
}};
}
actionsTab(character: CompiledCharacter)
actionsTab()
{
const character = this.compiler.compiled;
return [
div('flex flex-col gap-8', [
div('flex flex-col gap-2', [
@@ -1886,7 +1982,7 @@ export class CharacterSheet
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
]), list: character.lists.action }),
]), list: () => character ? character.lists.action ?? [] : [] }),
]),
]),
div('flex flex-col gap-2', [
@@ -1901,7 +1997,7 @@ export class CharacterSheet
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
]), list: character.lists.reaction }),
]), list: () => character ? character.lists.reaction ?? [] : [] }),
]),
]),
div('flex flex-col gap-2', [
@@ -1915,13 +2011,13 @@ export class CharacterSheet
div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
div('flex flex-row justify-between', [dom('span', { class: 'text-lg font-semibold', text: config.freeaction[e]?.name }) ]),
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
]), list: character.lists.reaction })
]), list: () => character ? character.lists.freeaction ?? [] : [] })
]),
]),
]),
]
}
abilitiesTab(character: CompiledCharacter)
abilitiesTab(character: CompiledCharacter | undefined)
{
return [ div('flex flex-col gap-4', [
foldable(() => [div('flex flex-col gap-2', { render: (e, _c) => _c ?? div('flex flex-col gap-1', [
@@ -1944,7 +2040,7 @@ export class CharacterSheet
], { open: false }),
]) ];
}
spellTab(character: CompiledCharacter)
spellTab(character: CompiledCharacter | undefined)
{
const preference = reactive({
sort: localStorage.getItem('character-sort') ?? 'rank-asc',
@@ -2015,7 +2111,7 @@ export class CharacterSheet
])
]
}
spellPanel(character: CompiledCharacter)
spellPanel(character: CompiledCharacter | undefined)
{
const spells = character.variables.spells;
const filters = reactive<{ tag: Array<string>, rank: Array<SpellConfig['rank']>, type: Array<SpellConfig['type']>, element: Array<SpellConfig['elements'][number]>, cost: { min: number, max: number }, range: Array<SpellConfig['range']>, speed: Array<SpellConfig['speed']> }>({
@@ -2086,7 +2182,7 @@ export class CharacterSheet
if(idx !== -1) spells.splice(idx, 1);
else spells.push(spell.id);
this.character?.saveVariables();
this.compiler.saveVariables();
}, "px-2 py-1 text-sm font-normal"),
]),
]) ], { open: false, class: { container: "px-2 flex flex-col border-light-35 dark:border-dark-35", content: 'py-2' } })
@@ -2102,7 +2198,7 @@ export class CharacterSheet
container.setAttribute('data-state', 'inactive');
}};
}
itemsTab(character: CompiledCharacter)
itemsTab(character: CompiledCharacter | undefined)
{
const items = character.variables.items;
const panel = this.itemsPanel(character);
@@ -2110,7 +2206,7 @@ export class CharacterSheet
const money = {
readonly: dom('div', { listeners: { click: () => { money.readonly.replaceWith(money.edit); money.edit.focus(); } }, class: 'cursor-pointer border border-transparent hover:border-light-40 dark:hover:border-dark-40 px-2 py-px flex flex-row gap-1 items-center' }, [ span('text-lg font-bold', () => character.variables.money.toLocaleString(undefined, { useGrouping: true })), icon('ph:coin', { width: 16, height: 16 }) ]),
edit: numberpicker({ defaultValue: character.variables.money, change: v => { character.variables.money = v; this.character?.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { character.variables.money = v; this.character?.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
edit: numberpicker({ defaultValue: character.variables.money, change: v => { character.variables.money = v; this.compiler.saveVariables(); money.edit.replaceWith(money.readonly); }, blur: v => { character.variables.money = v; this.compiler.saveVariables(); money.edit.replaceWith(money.readonly); }, min: 0, class: 'w-24' }),
};
return [
@@ -2120,8 +2216,8 @@ export class CharacterSheet
div('flex flex-row gap-1 items-center', [ span('italic text-sm', 'Argent'), money.readonly ]),
]),
div('flex flex-row justify-end items-center gap-8', [
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.power > character.itempower }], text: () => `Puissance magique: ${this.character!.power}/${character.itempower}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.character!.weight}/${character.capacity}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.power > character.itempower }], text: () => `Puissance magique: ${this.compiler!.power}/${character.itempower}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.compiler!.weight}/${character.capacity}` }),
button(text('Modifier'), () => panel.show(), 'py-1 px-4'),
]),
]),
@@ -2140,7 +2236,7 @@ export class CharacterSheet
markdown(getText(item.description)),
div('flex flex-row gap-1', { list: () => e.enchantments!.map(e => config.enchantments[e]).filter(e => !!e), render: (e, _c) => _c ?? floater(div(() => ['flex flex-row gap-2 border px-2 rounded-full py-px !bg-opacity-20', { 'border-accent-blue bg-accent-blue': !e.cursed, 'border-light-purple bg-light-purple dark:border-dark-purple dark:bg-dark-purple': e.cursed }], [ span('text-sm font-semibold tracking-tight', e.name), div('flex flex-row gap-1 items-center', [icon('game-icons:bolt-drop', { width: 12, height: 12 }), span('text-sm font-light', e.power)]) ]), () => [markdown(getText(e.description), undefined, { tags: { a: preview } })], { class: 'max-w-96 max-h-48 p-2', position: "right" }) }),
div('flex flex-row justify-center gap-1', [
this.character?.character.campaign ? button(text('Partager'), () => {
this.compiler.character?.campaign ? button(text('Partager'), () => {
}, 'px-2 text-sm h-5 box-content') : undefined,
button(icon(() => e.amount === 1 ? 'radix-icons:trash' : 'radix-icons:minus', { width: 12, height: 12 }), () => {
@@ -2150,7 +2246,7 @@ export class CharacterSheet
items[idx]!.amount--;
if(items[idx]!.amount <= 0) items.splice(idx, 1);
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'p-1'),
button(icon('radix-icons:plus', { width: 12, height: 12 }), () => {
const idx = items.findIndex(_e => _e === e);
@@ -2160,7 +2256,7 @@ export class CharacterSheet
else if(items.find(_e => _e === e)) items.find(_e => _e === e)!.amount++;
else items.push(stateFactory(item));
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'p-1'),
() => !item.capacity ? undefined : button(text("Enchanter"), () => {
enchant.show(e);
@@ -2170,7 +2266,7 @@ export class CharacterSheet
div('flex flex-row items-center gap-y-1 gap-x-4 flex-wrap', [
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
e.equipped = v;
this.character?.enchant(e);
this.compiler.enchant(e);
}, class: { container: '!w-5 !h-5' } }) : undefined,
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
item.category === 'armor' ? div('flex flex-row gap-2 items-center text-sm', [ icon('game-icons:shoulder-armor', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('italic', () => `${item.health + ((e.state as ArmorState)?.health ?? 0) - ((e.state as ArmorState)?.loss ?? 0)}/${item.health + ((e.state as ArmorState)?.health ?? 0)} (${[item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0) > 0 ? '-' + (item.absorb.static + ((e.state as ArmorState).absorb?.flat ?? 0)) : undefined, item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0) > 0 ? '-' + (item.absorb.percent + ((e.state as ArmorState).absorb?.percent ?? 0)) + '%' : undefined].filter(e => !!e).join('/')})`) ]) :
@@ -2189,7 +2285,7 @@ export class CharacterSheet
])
];
}
itemsPanel(character: CompiledCharacter)
itemsPanel(character: CompiledCharacter | undefined)
{
const filters: { category: Category[], rarity: Rarity[], name: string, power: { min: number, max: number } } = reactive({
category: [],
@@ -2202,7 +2298,7 @@ export class CharacterSheet
div("flex flex-row justify-between items-center mb-4", [
dom("h2", { class: "text-xl font-bold", text: "Gestion de l'inventaire" }),
div('flex flex-row gap-8 items-center justify-end', [
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.character!.weight}/${character.capacity}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.weight > (character.capacity === false ? 0 : character.capacity) }], text: () => `Poids total: ${this.compiler!.weight}/${character.capacity}` }),
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
setTimeout(blocker.close, 150);
container.setAttribute('data-state', 'inactive');
@@ -2235,12 +2331,12 @@ export class CharacterSheet
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
const list = this.character!.character.variables.items;
const list = this.compiler.character!.variables.items;
if(item.equippable) list.push(stateFactory(item));
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
else list.push(stateFactory(item));
this.character?.saveVariables();
this.compiler.saveVariables();
}, 'p-1 !border-solid !border-r'),
]),
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
@@ -2256,7 +2352,7 @@ export class CharacterSheet
container.setAttribute('data-state', 'inactive');
}};
}
enchantPanel(character: CompiledCharacter)
enchantPanel(character: CompiledCharacter | undefined)
{
const current = reactive({
item: undefined as ItemState | undefined,
@@ -2284,7 +2380,7 @@ export class CharacterSheet
dom("h2", { class: "text-xl font-bold", text: "Enchantements" }),
div('flex flex-row gap-8 items-center justify-end', [
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': current.item && config.items[current.item.id] !== undefined ? itempower() > (config.items[current.item.id]!.capacity ?? 0) : false }], text: () => `Puissance de l'objet: ${current.item && config.items[current.item.id] !== undefined ? itempower() : false}/${current.item ? (config.items[current.item.id]!.capacity ?? 0) : 0}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.character!.power > character!.itempower }], text: () => `Puissance du personnage: ${this.character!.power}/${character.itempower}` }),
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': this.compiler!.power > character!.itempower }], text: () => `Puissance du personnage: ${this.compiler!.power}/${character.itempower}` }),
tooltip(button(icon("radix-icons:cross-1", { width: 20, height: 20 }), () => {
setTimeout(blocker.close, 150);
container.setAttribute('data-state', 'inactive');
@@ -2304,7 +2400,7 @@ export class CharacterSheet
else
current.item!.enchantments?.splice(idx, 1);
this.character?.enchant(current.item!);
this.compiler.enchant(current.item!);
}, 'p-1 !border-solid !border-r'),
]),
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } })
@@ -2321,7 +2417,7 @@ export class CharacterSheet
container.setAttribute('data-state', 'inactive');
}};
}
aspectTab(character: CompiledCharacter)
aspectTab(character: CompiledCharacter | undefined)
{
return [
div('flex flex-col gap-2', [
@@ -2337,7 +2433,7 @@ export class CharacterSheet
])
]
}
effectsTab(character: CompiledCharacter)
effectsTab(character: CompiledCharacter | undefined)
{
return [